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 <t-way666@users.noreply.github.com>
This commit is contained in:
t-way666
2026-05-15 03:29:18 -03:00
committed by diegosouzapw
parent 4f80be1f2f
commit 4a84ab9c1b
4 changed files with 63 additions and 8 deletions

View File

@@ -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.";

View File

@@ -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",

View File

@@ -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");

View File

@@ -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(