From 4a84ab9c1b0a5fe71a45b0a53c62ec3c0e4aaae6 Mon Sep 17 00:00:00 2001 From: t-way666 Date: Fri, 15 May 2026 03:29:18 -0300 Subject: [PATCH] feat(termux): Android/Termux headless support (#2273) - Move wreq-js and tls-client-node to optionalDependencies - Lazy-load wreq-js WS proxy with graceful 503 when unavailable - Auto-detect Android platform for headless mode (no browser open) - Set GYP_DEFINES for better-sqlite3 build on Android/ARM - Extended build timeout to 600s for ARM compilation - Skip wreq-js binary fix on Android (unsupported platform) - Platform warnings for unsupported features (WS proxy, TLS, Electron, MITM) Co-authored-by: t-way666 --- bin/omniroute.mjs | 13 ++++++++++- package.json | 6 ++--- scripts/build/postinstall.mjs | 16 +++++++++++-- scripts/dev/responses-ws-proxy.mjs | 36 ++++++++++++++++++++++++++++-- 4 files changed, 63 insertions(+), 8 deletions(-) diff --git a/bin/omniroute.mjs b/bin/omniroute.mjs index 7b854c9c0c..bd6dfd59ec 100644 --- a/bin/omniroute.mjs +++ b/bin/omniroute.mjs @@ -386,7 +386,8 @@ if (portIdx !== -1 && args[portIdx + 1]) { const apiPort = parsePort(process.env.API_PORT || String(port), port); const dashboardPort = parsePort(process.env.DASHBOARD_PORT || String(port), port); -const noOpen = args.includes("--no-open"); +const isAndroid = process.platform === 'android'; +let noOpen = args.includes("--no-open") || isAndroid; console.log(` \x1b[36m ____ _ ____ _ @@ -397,6 +398,16 @@ console.log(` \\\\____/|_| |_| |_|_| |_|_|_| \\\\_\\\\___/ \\\\__,_|\\\\__\\\\___| \x1b[0m`); +if (isAndroid) { + console.log(`\x1b[33m[OmniRoute] Running on Android/Termux — headless mode enabled automatically\x1b[0m`); + console.log(`\x1b[33m[OmniRoute] Platform: android/${process.arch} — unsupported features:\x1b[0m + - Codex Responses WebSocket (wreq-js unavailable) + - ChatGPT Web TLS impersonation (tls-client-node unavailable) + - Electron desktop app + - MITM proxy / system certificate install +`); +} + const nodeSupport = getNodeRuntimeSupport(); if (!nodeSupport.nodeCompatible) { const runtimeWarning = getNodeRuntimeWarning() || "Unsupported Node.js runtime detected."; diff --git a/package.json b/package.json index 4c94dc04b0..2b5956ab0e 100644 --- a/package.json +++ b/package.json @@ -165,18 +165,18 @@ "react-markdown": "^10.1.0", "recharts": "^3.8.1", "selfsigned": "^5.5.0", - "tls-client-node": "^0.1.13", "tsx": "^4.21.1", "undici": "^8.2.0", "uuid": "^14.0.0", - "wreq-js": "^2.3.0", "xxhash-wasm": "^1.1.0", "yazl": "^3.3.1", "zod": "^4.4.3", "zustand": "^5.0.13" }, "optionalDependencies": { - "keytar": "^7.9.0" + "keytar": "^7.9.0", + "tls-client-node": "^0.1.13", + "wreq-js": "^2.3.0" }, "devDependencies": { "@playwright/test": "^1.60.0", diff --git a/scripts/build/postinstall.mjs b/scripts/build/postinstall.mjs index 210b18388f..73b8a42adf 100644 --- a/scripts/build/postinstall.mjs +++ b/scripts/build/postinstall.mjs @@ -141,10 +141,16 @@ async function fixBetterSqliteBinary() { ? "npm install better-sqlite3 --build-from-source --force" : "npm rebuild better-sqlite3"; + const env = { ...process.env }; + if (isAndroid) { + env.GYP_DEFINES = "android_ndk_path=''"; + } + execSync(rebuildCmd, { cwd: join(ROOT, "app"), stdio: "inherit", - timeout: 300_000, // 5 minutes for source builds + timeout: isAndroid ? 600_000 : 300_000, // ARM compilation is slower + env, }); process.dlopen({ exports: {} }, appBinary); @@ -153,7 +159,8 @@ async function fixBetterSqliteBinary() { } catch (err) { const isTimeout = err.killed || err.signal === "SIGTERM"; if (isTimeout) { - console.warn(" ⚠️ npm rebuild timed out after 300s."); + const secs = isAndroid ? 600 : 300; + console.warn(` ⚠️ npm rebuild timed out after ${secs}s.`); } else { console.warn(` ⚠️ npm rebuild failed: ${err.message}`); } @@ -189,6 +196,11 @@ async function fixBetterSqliteBinary() { * Fixes: https://github.com/diegosouzapw/OmniRoute/issues/1634 */ async function fixWreqJsBinary() { + if (process.platform === "android") { + console.log(" [postinstall] wreq-js: skipped on android (unsupported platform)"); + return; + } + const appWreqDir = join(ROOT, "app", "node_modules", "wreq-js", "rust"); const rootWreqDir = join(ROOT, "node_modules", "wreq-js", "rust"); diff --git a/scripts/dev/responses-ws-proxy.mjs b/scripts/dev/responses-ws-proxy.mjs index 334840c36f..a30a9b649d 100644 --- a/scripts/dev/responses-ws-proxy.mjs +++ b/scripts/dev/responses-ws-proxy.mjs @@ -1,6 +1,23 @@ import { createHash, randomUUID } from "node:crypto"; +import { createRequire } from "node:module"; import { STATUS_CODES } from "node:http"; -import { websocket } from "wreq-js"; + +const _wreqRequire = createRequire(import.meta.url); + +let _websocketFn = null; +let _wreqChecked = false; + +function getWebSocketTransport() { + if (_wreqChecked) return _websocketFn; + _wreqChecked = true; + try { + const mod = _wreqRequire("wreq-js"); + _websocketFn = typeof mod.websocket === "function" ? mod.websocket : null; + } catch { + _websocketFn = null; + } + return _websocketFn; +} export const RESPONSES_WS_PUBLIC_PATHS = new Set([ "/responses", @@ -562,7 +579,7 @@ export function createResponsesWsProxy({ baseUrl, bridgeSecret, fetchImpl = fetch, - wsFactory = websocket, + wsFactory = getWebSocketTransport(), pingIntervalMs = 25000, idleTimeoutMs = 90000, maxBufferBytes = DEFAULT_MAX_WS_BUFFER_BYTES, @@ -583,6 +600,21 @@ export function createResponsesWsProxy({ return false; } + if (!wsFactory) { + writeHttpError( + socket, + 503, + JSON.stringify({ + error: { + message: + "Responses WebSocket proxy unavailable: wreq-js is not installed on this platform", + code: "wreq_js_unavailable", + }, + }) + ); + return true; + } + const upgradeHeader = String(req.headers.upgrade || "").toLowerCase(); if (upgradeHeader !== "websocket") { writeHttpError(