From 707e2c0f1e84f90c663d64bee990b3fef1795a77 Mon Sep 17 00:00:00 2001 From: Nasyarobby Putra Date: Tue, 8 Sep 2026 22:58:52 +0700 Subject: [PATCH] feat(plugins): enhance SMS sending functionality with raw HTTP handling - Added support for using the `net` module in the `send-sms` plugin to establish raw HTTP connections. - Refactored the `ztePost` function to `zteHttpPost`, allowing for custom HTTP request formatting and improved error handling. - Introduced a new utility function, `httpBodyFromRaw`, to process raw HTTP responses effectively. - Updated the `script-sandbox.js` to allow the use of the `net` and `node:net` modules in the sandbox environment. --- packages/server/script-sandbox.js | 2 + plugins/send-sms/script.js | 95 ++++++++++++++++++++++--------- 2 files changed, 69 insertions(+), 28 deletions(-) diff --git a/packages/server/script-sandbox.js b/packages/server/script-sandbox.js index a45d2f7..3807b61 100644 --- a/packages/server/script-sandbox.js +++ b/packages/server/script-sandbox.js @@ -22,7 +22,9 @@ const ALLOWED_MODULES = new Set([ "basic-ftp", "jsonata", "mustache", + "net", "node-html-parser", + "node:net", "node:stream", "nodemailer", "rss-parser", diff --git a/plugins/send-sms/script.js b/plugins/send-sms/script.js index 42ce1ba..7487c77 100644 --- a/plugins/send-sms/script.js +++ b/plugins/send-sms/script.js @@ -1,3 +1,5 @@ +import net from "node:net"; + function passContext(ctx) { if (ctx?.context != null && typeof ctx.context === "object" && !Array.isArray(ctx.context)) { return { ...ctx.context }; @@ -65,38 +67,75 @@ function parseResult(text) { } } -function leftoverFromError(err) { - let current = err; - for (let i = 0; i < 6 && current; i++) { - if (typeof current.data === "string") return current.data; - current = current.cause; +function httpBodyFromRaw(raw) { + const normalized = String(raw).replace(/\r\n/g, "\n"); + const split = normalized.indexOf("\n\n"); + return split === -1 ? normalized : normalized.slice(split + 2); +} + +/** + * ZTE Demo-Webs uses LF-only status lines (`HTTP/1.1 200 OK\n`). + * Node 24 fetch/http throw HPE_CR_EXPECTED before any body is read. + */ +function zteHttpPost(url, fields) { + const target = new URL(url); + if (target.protocol !== "http:") { + throw new Error(`send-sms: only http: is supported (got ${target.protocol})`); } - return null; + const body = new URLSearchParams(fields).toString(); + const request = [ + `POST ${target.pathname}${target.search} HTTP/1.1`, + `Host: ${target.host}`, + "accept: application/json, text/javascript, */*; q=0.01", + "accept-language: en-US,en;q=0.9", + "content-type: application/x-www-form-urlencoded; charset=UTF-8", + "x-requested-with: XMLHttpRequest", + `Referer: ${refererFromUrl(url)}`, + `Content-Length: ${Buffer.byteLength(body)}`, + "Connection: close", + "", + body, + ].join("\r\n"); + + return new Promise((resolve, reject) => { + const socket = net.connect( + { host: target.hostname, port: Number(target.port) || 80 }, + () => { + socket.write(request); + }, + ); + const chunks = []; + let settled = false; + const finish = (raw) => { + if (settled) return; + settled = true; + socket.removeAllListeners(); + socket.destroy(); + resolve(parseResult(httpBodyFromRaw(raw))); + }; + socket.on("data", (chunk) => { + chunks.push(chunk); + const raw = Buffer.concat(chunks).toString("utf8"); + const parsed = parseResult(httpBodyFromRaw(raw)); + if (parsed.result !== undefined) finish(raw); + }); + socket.on("end", () => finish(Buffer.concat(chunks).toString("utf8"))); + socket.on("error", (err) => { + if (settled) return; + settled = true; + reject(err); + }); + socket.setTimeout(15000, () => { + if (settled) return; + settled = true; + socket.destroy(); + reject(new Error("ZTE HTTP timeout")); + }); + }); } async function ztePost(url, fields) { - const body = new URLSearchParams(fields).toString(); - try { - const response = await fetch(url, { - method: "POST", - headers: { - accept: "application/json, text/javascript, */*; q=0.01", - "content-type": "application/x-www-form-urlencoded; charset=UTF-8", - "x-requested-with": "XMLHttpRequest", - Referer: refererFromUrl(url), - }, - body, - }); - return parseResult(await response.text()); - } catch (err) { - const leftover = leftoverFromError(err); - if (leftover != null) { - const parsed = parseResult(leftover); - log.info({ result: parsed.result ?? parsed }, "send-sms: recovered malformed response"); - return parsed; - } - throw err; - } + return zteHttpPost(url, fields); } async function sendZteSms(ctx) {