fix(providers): migrate web cookie TLS transport to wreq-js (#12429)

Migrates the Claude, Grok, LMArena, Notion and Perplexity web-cookie transports from the tls-client-node/Koffi sidecar to the exactly pinned wreq-js 3.2.0 runtime, keeping the per-provider browser/OS profiles, making request cookies ephemeral, bounding and generation-protecting the shared native transport pool, removing the legacy downloader and repair path, and carrying the native binding and license evidence through the npm, standalone, Electron, Docker and Bun packaging surfaces.

This is the consolidation of the two competing migrations, and the consolidation was decided by evidence rather than by preference. #11753's six suites were installed over this implementation and run as an independent specification: 31 of 36 passed. All five failures are artefacts of #11753 being the older design, not coverage gaps —

- two hardcode the 3.0.0 pin in their assertions (this branch pins 3.2.0, which is what the release tip already resolves; #11753's 3.0.0 would have conflicted);
- one reads open-sse/services/chatgptTlsClient.ts, deleted when #11754 retired ChatGPT Web, so the test is stale against the current tip;
- two import WREQ_JS_NATIVE_BINARY_NAMES / resolveWreqJsNativeBinaryName, which this branch redesigned into WREQ_JS_NATIVE_BINDINGS / resolveWreqJsNativeBinding plus WREQ_JS_VERSION — a rename from modelling natives as file names to modelling them as package bindings, verified as an API difference rather than a lost capability (the linux-x64-gnu .node is present and serviceable).

This branch is also the strict superset by scope: 7 files exclusive to it, including the wreq-js Rust license inventory and notices, .trivyignore, open-sse/utils/tlsClient.ts and assembleStandalone.mjs. #11753 had one exclusive file, its changelog fragment. Nothing needed porting, so #11753 is superseded rather than merged, and the changelog entry credits both.

Reconciled on merge: clean against the tip. The new migration suite (tests/unit/tls-client-wreq-migration.test.ts, 1374 lines, 31 cases) is frozen at its exact LOC with the rationale — it shares one native-transport harness, so splitting it mid-merge would duplicate that harness for no coverage gain. Verified that no existing cap moves.

Verified: 182/182 across the eight TLS, native-manifest, postinstall, standalone-bundle, pack-artifact and provider-validation suites, typecheck:core clean, check:cycles OK, check-changelog-integrity OK, check-file-size OK, and every changed TypeScript file parses.
This commit is contained in:
Diego Rodrigues de Sa e Souza
2026-09-02 10:41:00 -03:00
committed by GitHub
parent 7ed8ada432
commit 500568a1cd
60 changed files with 16506 additions and 1566 deletions

View File

@@ -49,6 +49,7 @@ import fs from "node:fs/promises";
import fsSync from "node:fs";
import path from "node:path";
import { colocateLlmlinguaOptionals, SEED_PACKAGES } from "./colocateOptionals.mjs";
import { WREQ_JS_NATIVE_BINDINGS } from "./wreqJsNative.mjs";
/**
* Check whether a path exists (async).
@@ -121,6 +122,31 @@ const EXTRA_MODULE_ENTRIES = [
src: ["node_modules", "wreq-js"],
dest: ["node_modules", "wreq-js"],
},
...WREQ_JS_NATIVE_BINDINGS.map((binding) => ({
label: `${binding.packageName} native binding`,
src: ["node_modules", ...binding.packageName.split("/")],
dest: ["node_modules", ...binding.packageName.split("/")],
})),
{
label: "third-party notices",
src: ["THIRD_PARTY_NOTICES.md"],
dest: ["THIRD_PARTY_NOTICES.md"],
},
{
label: "wreq-js native provenance manifest",
src: ["config", "release", "wreq-js-native-manifest.json"],
dest: ["config", "release", "wreq-js-native-manifest.json"],
},
{
label: "wreq-js Rust license inventory",
src: ["config", "release", "wreq-js-rust-license-inventory.json"],
dest: ["config", "release", "wreq-js-rust-license-inventory.json"],
},
{
label: "wreq-js Rust/native notice bundle",
src: ["config", "release", "wreq-js-rust-notices.md"],
dest: ["config", "release", "wreq-js-rust-notices.md"],
},
{
label: "@swc/helpers",
src: ["node_modules", "@swc", "helpers"],
@@ -557,9 +583,7 @@ function stampServiceWorkerBuildId(resolvedOutDir) {
const swDest = path.join(resolvedOutDir, "public", "sw.js");
if (!fsSync.existsSync(swDest)) return;
const buildId =
process.env.OMNIROUTE_SW_BUILD_ID ||
process.env.SOURCE_VERSION ||
String(Date.now());
process.env.OMNIROUTE_SW_BUILD_ID || process.env.SOURCE_VERSION || String(Date.now());
let sw = fsSync.readFileSync(swDest, "utf8");
sw = sw.replace(
/^const CACHE_NAME = "omniroute-pwa-v2";$/m,

View File

@@ -1,148 +0,0 @@
#!/usr/bin/env node
/**
* tls-client-node postinstall repair (#7802).
*
* tls-client-node's own postinstall.js fetches a platform-specific native
* binary (.so/.dylib/.dll) from the bogdanfinn/tls-client GitHub Releases
* API. That script is blocked by `npm ci --ignore-scripts` (the Dockerfile
* builder stage runs with scripts disabled for supply-chain hygiene) and,
* even when it does run, silently no-ops on a rate-limited/failed GitHub API
* call instead of raising — so `node_modules/tls-client-node/bin/` can end
* up empty with no visible signal until the first live request throws
* TlsClientUnavailableError (claude-web/grok-web/lmarena/
* perplexity-web all share this transport).
*
* This module:
* 1. Copies an already-fetched root `bin/` into the standalone
* `dist/node_modules/tls-client-node/bin/` bundle (same pattern as
* fixWreqJsBinary), so the published npm package works even though its
* own `files` allowlist never ships the binary.
* 2. When the root `bin/` is empty (--ignore-scripts blocked it, or a
* transient GitHub rate-limit ate the first attempt), retries the
* module's own postinstall.js with exponential backoff instead of
* giving up on the first failure.
*
* Best-effort throughout: a failure here never throws out of postinstall.mjs
* — it only warns, matching the other fix*Binary() steps. The runtime layer
* (perplexityTlsClient.ts and its 4 siblings) already surfaces a clear
* TlsClientUnavailableError pointing at the missing binary, so an operator
* who hits a still-empty bin/ after this repair gets an actionable message
* rather than an opaque crash.
*/
import { copyFileSync, existsSync, mkdirSync, readdirSync } from "node:fs";
import { join } from "node:path";
const DEFAULT_RETRY_DELAYS_MS = [1_000, 3_000, 8_000];
function hasAnyFile(dir) {
if (!existsSync(dir)) return false;
try {
return readdirSync(dir).length > 0;
} catch {
return false;
}
}
function copyBinDir(sourceDir, destDir) {
mkdirSync(destDir, { recursive: true });
for (const file of readdirSync(sourceDir)) {
copyFileSync(join(sourceDir, file), join(destDir, file));
}
}
async function sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
/**
* Re-run tls-client-node's own postinstall.js in-process, retrying with
* backoff when the attempt leaves `bin/` empty (covers transient GitHub API
* rate-limiting — the upstream script itself never throws on failure, it
* only warns, so "still empty after running it" is the only failure signal
* available).
*/
async function downloadWithRetry(rootTlsClientDir, retryDelaysMs, log) {
const postinstallScript = join(rootTlsClientDir, "scripts", "postinstall.js");
const binDir = join(rootTlsClientDir, "bin");
if (!existsSync(postinstallScript)) return false;
for (let attempt = 0; attempt <= retryDelaysMs.length; attempt++) {
if (attempt > 0) {
log(
` ⏳ tls-client-node native binary still missing — retrying download ` +
`(attempt ${attempt + 1}/${retryDelaysMs.length + 1}) after rate-limit/backoff...`
);
await sleep(retryDelaysMs[attempt - 1]);
}
try {
const { execFileSync } = await import("node:child_process");
execFileSync(process.execPath, [postinstallScript], {
cwd: rootTlsClientDir,
stdio: "pipe",
timeout: 30_000,
});
} catch (err) {
log(` ⚠️ tls-client-node postinstall attempt failed: ${err.message.split("\n")[0]}`);
}
if (hasAnyFile(binDir)) return true;
}
return false;
}
/**
* @param {object} opts
* @param {string} opts.rootDir - repo root
* @param {(msg: string) => void} [opts.log]
* @param {number[]} [opts.retryDelaysMs] - override for tests (avoid real sleeps)
*/
export async function fixTlsClientNodeBinary({
rootDir,
log = (m) => console.log(m),
retryDelaysMs = DEFAULT_RETRY_DELAYS_MS,
} = {}) {
const rootTlsClientDir = join(rootDir, "node_modules", "tls-client-node");
const rootBinDir = join(rootTlsClientDir, "bin");
const distTlsClientDir = join(rootDir, "dist", "node_modules", "tls-client-node");
if (!existsSync(rootTlsClientDir)) return;
if (!hasAnyFile(rootBinDir)) {
log(
"\n 🔧 tls-client-node native binary missing (blocked by --ignore-scripts or a " +
"failed fetch) — attempting repair...\n"
);
const recovered = await downloadWithRetry(rootTlsClientDir, retryDelaysMs, log);
if (!recovered) {
console.warn(
"\n ⚠️ Could not fetch tls-client-node's native binary " +
"(GitHub API rate-limited or unreachable after retries)."
);
console.warn(
" claude-web/grok-web/lmarena/perplexity-web will raise a clear " +
"TlsClientUnavailableError on first use until this is resolved."
);
console.warn(
` Manual fix: node ${join(rootTlsClientDir, "scripts", "postinstall.js")}\n`
);
return;
}
log(" ✅ tls-client-node native binary fetched successfully!\n");
}
if (!existsSync(distTlsClientDir) || !hasAnyFile(rootBinDir)) return;
const distBinDir = join(distTlsClientDir, "bin");
if (hasAnyFile(distBinDir)) return;
try {
copyBinDir(rootBinDir, distBinDir);
log(" ✅ tls-client-node native binary copied to standalone dist/node_modules.\n");
} catch (err) {
console.warn(` ⚠️ Could not copy tls-client-node binary into dist/: ${err.message}`);
}
}

View File

@@ -7,21 +7,26 @@
* matrix leg. Everything except install-machine-forked optional packages is
* platform-independent:
*
* - Bundled-for-all (verify only): koffi ships every triplet under
* `build/koffi/<os>_<arch>`, better-sqlite3 v13 ships Node-API prebuilds for
* 8 platforms, wreq-js ships `rust/wreq-js.<plat>-<arch>[-libc].node`, and
* onnxruntime-node ships `bin/napi-v6/<os>/<arch>`.
* - Bundled-for-all (verify only): better-sqlite3 v13 ships Node-API prebuilds
* for 8 platforms, and onnxruntime-node ships `bin/napi-v6/<os>/<arch>`.
* - Install-machine-forked (hydrate): `@img/sharp-*`, `@img/sharp-libvips-*`,
* `@ngrok/ngrok-*` and macOS-only `fsevents` resolve to whichever platform
* ran `npm ci`. The ubuntu-built tree carries the linux forks; each leg
* replaces them with the forks from its OWN `npm ci`d node_modules.
* `@ngrok/ngrok-*`, `@wreq-js/binding-*`, and macOS-only `fsevents` resolve
* to whichever platform ran `npm ci`. The ubuntu-built tree carries the
* linux forks; each leg replaces them with the forks from its OWN install.
*/
import fs from "node:fs";
import path from "node:path";
import { resolveWreqJsNativeBinding } from "./wreqJsNative.mjs";
/** Scope prefixes whose members are install-machine-forked. */
export const HYDRATED_SCOPES = ["@img/sharp-", "@img/sharp-libvips-", "@ngrok/ngrok-"];
export const HYDRATED_SCOPES = [
"@img/sharp-",
"@img/sharp-libvips-",
"@ngrok/ngrok-",
"@wreq-js/binding-",
];
/** Standalone packages that are not forked but must never be platform-forked. */
export const HYDRATED_ROOT_PACKAGES = ["fsevents"];
@@ -33,8 +38,7 @@ export const HYDRATED_ROOT_PACKAGES = ["fsevents"];
export const BUNDLED_EXEMPTIONS = new Set(["onnxruntime-node:darwin-x64"]);
function platformTriple(platform, arch) {
// koffi uses underscore triplets; better-sqlite3/wreq-js/onnx use dashes.
return { koffi: `${platform}_${arch}`, dash: `${platform}-${arch}` };
return { dash: `${platform}-${arch}` };
}
function rmrf(target) {
@@ -106,9 +110,6 @@ export function verifyBundledNatives({ nodeModulesDir, platform, arch }) {
const errors = [];
const triple = platformTriple(platform, arch);
const koffiDir = path.join(nodeModulesDir, "koffi", "build", "koffi", triple.koffi);
if (!fs.existsSync(koffiDir)) errors.push(`koffi: missing bundled triplet ${triple.koffi}`);
const sqlitePrebuild = path.join(
nodeModulesDir,
"better-sqlite3",
@@ -118,13 +119,23 @@ export function verifyBundledNatives({ nodeModulesDir, platform, arch }) {
if (!fs.existsSync(sqlitePrebuild))
errors.push(`better-sqlite3: missing prebuild ${triple.dash}.node`);
const wreqDir = path.join(nodeModulesDir, "wreq-js", "rust");
const wreqNames = fs.existsSync(wreqDir)
? fs
.readdirSync(wreqDir)
.filter((n) => n.startsWith(`wreq-js.${triple.dash}`) && n.endsWith(".node"))
: [];
if (wreqNames.length === 0) errors.push(`wreq-js: missing rust binary for ${triple.dash}`);
const wreqBinding = resolveWreqJsNativeBinding({
platform,
arch,
libc: platform === "linux" ? "gnu" : undefined,
});
if (!wreqBinding) {
errors.push(`wreq-js: unsupported target ${triple.dash}`);
} else {
const wreqBinary = path.join(
nodeModulesDir,
...wreqBinding.packageName.split("/"),
wreqBinding.fileName
);
if (!fs.existsSync(wreqBinary)) {
errors.push(`wreq-js: missing ${wreqBinding.packageName}/${wreqBinding.fileName}`);
}
}
const exempt = BUNDLED_EXEMPTIONS.has(`onnxruntime-node:${triple.dash}`);
if (!exempt) {

View File

@@ -94,6 +94,9 @@ export const PACK_ARTIFACT_ROOT_ALLOWED_EXACT_PATHS: string[] = [
"LICENSE",
"README.md",
"THIRD_PARTY_NOTICES.md",
"config/release/wreq-js-native-manifest.json",
"config/release/wreq-js-rust-license-inventory.json",
"config/release/wreq-js-rust-notices.md",
"bin/aliasResolver.mjs",
"bin/chatgpt-web-codex-mcp.mjs",
// #7808: ESM loader hook split out of bin/aliasResolver.mjs to silence CodeQL
@@ -136,12 +139,10 @@ export const PACK_ARTIFACT_ROOT_ALLOWED_EXACT_PATHS: string[] = [
"scripts/build/build-next-isolated.mjs",
"scripts/check/check-supported-node-runtime.ts",
"scripts/build/native-binary-compat.mjs",
"scripts/build/wreqJsNative.mjs",
"scripts/build/postinstall.mjs",
"scripts/build/postinstallSupport.mjs",
"scripts/build/colocateOptionals.mjs",
// #7802: imported by scripts/build/postinstall.mjs to repair tls-client-node's
// native binary (claude-web/grok-web/lmarena/perplexity-web transport).
"scripts/build/fixTlsClientNodeBinary.mjs",
// #8859: imported by scripts/build/postinstall.mjs to repair playwright-core's
// browser resolution on Termux/Android (no glibc, no bundled browsers).
"scripts/build/fixPlaywrightAndroid.mjs",
@@ -222,13 +223,16 @@ export const PACK_ARTIFACT_REQUIRED_PATHS: string[] = [
// or the CLI fails to boot — list them REQUIRED so a regression is loud.
"bin/aliasResolver.mjs",
"bin/aliasResolverHook.mjs",
"config/release/wreq-js-native-manifest.json",
"config/release/wreq-js-rust-license-inventory.json",
"config/release/wreq-js-rust-notices.md",
"package.json",
"scripts/build/native-binary-compat.mjs",
"scripts/build/postinstall.mjs",
"scripts/build/postinstallSupport.mjs",
"scripts/build/colocateOptionals.mjs",
"scripts/build/fixTlsClientNodeBinary.mjs",
"scripts/build/runtime-env.mjs",
"scripts/build/wreqJsNative.mjs",
// #10382: runtime imports of bin/cli/commands/packs.mjs (optional packs CLI) —
// listed REQUIRED so their absence from the tarball fails loudly.
"scripts/packs/optionalPackInstaller.mjs",

View File

@@ -14,8 +14,7 @@
*
* Modules repaired:
* - better-sqlite3 (SQLite bindings)
* - wreq-js (TLS client for OAuth providers)
* - tls-client-node (TLS client for claude-web/grok-web/lmarena/perplexity-web)
* - wreq-js (TLS client for OAuth and web-cookie providers)
* - sql.js (WASM SQLite fallback runtime)
* - node-machine-id (local CLI machine-token server runtime)
*
@@ -26,15 +25,7 @@
* Fixes: https://github.com/diegosouzapw/OmniRoute/issues/7802
*/
import {
copyFileSync,
cpSync,
existsSync,
mkdirSync,
readFileSync,
readdirSync,
writeFileSync,
} from "node:fs";
import { copyFileSync, cpSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
import { createRequire } from "node:module";
import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";
@@ -42,8 +33,8 @@ import { fileURLToPath } from "node:url";
import { PUBLISHED_BUILD_ARCH, PUBLISHED_BUILD_PLATFORM } from "./native-binary-compat.mjs";
import { hasStandaloneAppBundle, isTermux } from "./postinstallSupport.mjs";
import { colocateLlmlinguaOptionals } from "./colocateOptionals.mjs";
import { fixTlsClientNodeBinary } from "./fixTlsClientNodeBinary.mjs";
import { fixPlaywrightAndroid } from "./fixPlaywrightAndroid.mjs";
import { resolveWreqJsNativeBinding, WREQ_JS_VERSION } from "./wreqJsNative.mjs";
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
@@ -262,105 +253,60 @@ async function fixBetterSqliteBinary() {
console.warn("");
}
/**
* Fix wreq-js native binary for the standalone dist directory.
*
* wreq-js ships platform-specific .node binaries under rust/.
* The standalone build may only contain Linux binaries from the CI.
* This copies the correct platform binary from the root install.
*
* Fixes: https://github.com/diegosouzapw/OmniRoute/issues/1634
*/
/** Copy the current wreq-js 3.2 optional binding into the standalone dist tree. */
async function fixWreqJsBinary() {
// wreq-js native module is not loadable in Termux (libgcc path mismatch).
// The runtime already falls back gracefully when wreq-js is unavailable.
if (process.platform === "android" || isTermux()) {
console.log(
" [postinstall] wreq-js: skipped on Termux/Android " +
"(libgcc not available — OAuth TLS fingerprinting will use the fallback path)"
);
return;
}
const appWreqDir = join(ROOT, "dist", "node_modules", "wreq-js", "rust");
const rootWreqDir = join(ROOT, "node_modules", "wreq-js", "rust");
if (!existsSync(join(ROOT, "dist", "node_modules", "wreq-js"))) {
return;
}
const binaryName = `wreq-js.${process.platform}-${process.arch}.node`;
const appBinaryPath = join(appWreqDir, binaryName);
const rootBinaryPath = join(rootWreqDir, binaryName);
const runtimePlatform = isTermux() ? "android" : process.platform;
const binding = resolveWreqJsNativeBinding({
platform: runtimePlatform,
arch: process.arch,
});
if (!binding) {
console.warn(
` ⚠️ wreq-js ${WREQ_JS_VERSION} has no native binding for ` +
`${runtimePlatform}-${process.arch}.`
);
return;
}
const packageSegments = binding.packageName.split("/");
const rootBindingDir = join(ROOT, "node_modules", ...packageSegments);
const appBindingDir = join(ROOT, "dist", "node_modules", ...packageSegments);
const rootBinaryPath = join(rootBindingDir, binding.fileName);
const appBinaryPath = join(appBindingDir, binding.fileName);
// Check if the platform binary already exists and loads
if (existsSync(appBinaryPath)) {
try {
process.dlopen({ exports: {} }, appBinaryPath);
return; // Already working
return;
} catch (err) {
console.warn(` ⚠️ wreq-js binary exists but failed to load: ${err.message}`);
}
}
console.log(`\n 🔧 Fixing wreq-js binary for ${process.platform}-${process.arch}...`);
console.log(`\n 🔧 Fixing ${binding.packageName} for ${runtimePlatform}-${process.arch}...`);
// Strategy 1: Copy from root node_modules
if (existsSync(rootBinaryPath)) {
if (existsSync(rootBindingDir) && existsSync(rootBinaryPath)) {
try {
mkdirSync(appWreqDir, { recursive: true });
copyFileSync(rootBinaryPath, appBinaryPath);
mkdirSync(dirname(appBindingDir), { recursive: true });
cpSync(rootBindingDir, appBindingDir, { recursive: true, force: true });
process.dlopen({ exports: {} }, appBinaryPath);
console.log(" ✅ wreq-js native module fixed successfully!\n");
console.log(`${binding.packageName} copied to standalone successfully!\n`);
return;
} catch (err) {
console.warn(` ⚠️ Copied wreq-js binary failed to load: ${err.message}`);
console.warn(` ⚠️ Copied ${binding.packageName} failed to load: ${err.message}`);
}
}
// Strategy 2: Copy entire rust/ directory from root (gets all platform binaries)
if (existsSync(rootWreqDir)) {
try {
mkdirSync(appWreqDir, { recursive: true });
const files = readdirSync(rootWreqDir);
for (const file of files) {
if (file.endsWith(".node")) {
copyFileSync(join(rootWreqDir, file), join(appWreqDir, file));
}
}
if (existsSync(appBinaryPath)) {
process.dlopen({ exports: {} }, appBinaryPath);
console.log(" ✅ wreq-js native module fixed (full copy) successfully!\n");
return;
}
} catch (err) {
console.warn(` ⚠️ wreq-js full copy failed: ${err.message}`);
}
}
// Strategy 3: Rebuild wreq-js inside dist/
console.log(" 📥 Attempting npm rebuild wreq-js...");
try {
const { execSync } = await import("node:child_process");
execSync("npm rebuild wreq-js", {
cwd: join(ROOT, "dist"),
stdio: "inherit",
timeout: 120_000,
});
if (existsSync(appBinaryPath)) {
process.dlopen({ exports: {} }, appBinaryPath);
console.log(" ✅ wreq-js native module rebuilt successfully!\n");
return;
}
} catch (err) {
console.warn(` ⚠️ wreq-js rebuild failed: ${err.message}`);
}
console.warn(
`\n ⚠️ Could not fix wreq-js native module for ${process.platform}-${process.arch}.`
`\n ⚠️ Could not install ${binding.packageName}@${WREQ_JS_VERSION} for ` +
`${runtimePlatform}-${process.arch}.`
);
console.warn(" OAuth-based providers (Codex, Cursor, etc.) may not work.");
console.warn(` Manual fix: cd ${join(ROOT, "dist")} && npm install wreq-js --no-save\n`);
console.warn(" Browser-TLS OAuth and web-cookie providers may not work.");
console.warn(` Manual fix: npm install --include=optional wreq-js@${WREQ_JS_VERSION}\n`);
}
async function ensureSwcHelpers() {
@@ -470,7 +416,6 @@ async function ensureStandaloneRuntimePackages() {
await verifyDevNativeModules();
await fixBetterSqliteBinary();
await fixWreqJsBinary();
await fixTlsClientNodeBinary({ rootDir: ROOT });
await fixPlaywrightAndroid({ rootDir: ROOT });
await ensureSwcHelpers();
await ensureStandaloneRuntimePackages();

View File

@@ -0,0 +1,131 @@
import { readFileSync } from "node:fs";
/** Exact native binding set published by wreq-js 3.2.0. */
export const WREQ_JS_VERSION = "3.2.0";
export const WREQ_JS_NATIVE_BINDINGS = Object.freeze([
{
target: "android-arm64",
packageName: "@wreq-js/binding-android-arm64",
fileName: "wreq-js.android-arm64.node",
platform: "android",
arch: "arm64",
},
{
target: "darwin-arm64",
packageName: "@wreq-js/binding-darwin-arm64",
fileName: "wreq-js.darwin-arm64.node",
platform: "darwin",
arch: "arm64",
},
{
target: "darwin-x64",
packageName: "@wreq-js/binding-darwin-x64",
fileName: "wreq-js.darwin-x64.node",
platform: "darwin",
arch: "x64",
},
{
target: "linux-arm64-gnu",
packageName: "@wreq-js/binding-linux-arm64-gnu",
fileName: "wreq-js.linux-arm64-gnu.node",
platform: "linux",
arch: "arm64",
libc: "gnu",
},
{
target: "linux-arm64-musl",
packageName: "@wreq-js/binding-linux-arm64-musl",
fileName: "wreq-js.linux-arm64-musl.node",
platform: "linux",
arch: "arm64",
libc: "musl",
},
{
target: "linux-x64-gnu",
packageName: "@wreq-js/binding-linux-x64-gnu",
fileName: "wreq-js.linux-x64-gnu.node",
platform: "linux",
arch: "x64",
libc: "gnu",
},
{
target: "linux-x64-musl",
packageName: "@wreq-js/binding-linux-x64-musl",
fileName: "wreq-js.linux-x64-musl.node",
platform: "linux",
arch: "x64",
libc: "musl",
},
{
target: "win32-arm64-msvc",
packageName: "@wreq-js/binding-win32-arm64-msvc",
fileName: "wreq-js.win32-arm64-msvc.node",
platform: "win32",
arch: "arm64",
},
{
target: "win32-x64-msvc",
packageName: "@wreq-js/binding-win32-x64-msvc",
fileName: "wreq-js.win32-x64-msvc.node",
platform: "win32",
arch: "x64",
},
]);
function readSystemLdd() {
const failures = [];
for (const lddPath of ["/usr/bin/ldd", "/bin/ldd"]) {
try {
return readFileSync(lddPath, "utf8");
} catch (error) {
failures.push(error);
}
}
throw failures[0] ?? new Error("ldd is unavailable");
}
/** Detect the C library used by the current Linux runtime. */
export function detectRuntimeLibc(options = {}) {
const platform = options.platform ?? process.platform;
if (platform !== "linux") return undefined;
const getReport = options.getReport ?? (() => process.report?.getReport());
const readLdd = options.readLdd ?? readSystemLdd;
let reportError;
try {
const report = getReport();
if (report?.header?.glibcVersionRuntime) return "gnu";
if (report?.header) return "musl";
} catch (error) {
reportError = error;
}
let lddError;
try {
const ldd = String(readLdd());
if (/\bmusl\b/i.test(ldd)) return "musl";
if (/\b(?:glibc|gnu libc|gnu c library)\b/i.test(ldd)) return "gnu";
lddError = new Error("ldd output did not identify glibc or musl");
} catch (error) {
lddError = error;
}
const detail = [reportError, lddError]
.filter((error) => error instanceof Error)
.map((error) => error.message)
.join("; ");
throw new Error(`Unable to detect Linux libc${detail ? `: ${detail}` : ""}`);
}
/** Resolve the exact package and addon filename wreq-js 3.2.0 loads. */
export function resolveWreqJsNativeBinding({ platform, arch, libc }) {
const runtimeLibc = platform === "linux" ? (libc ?? detectRuntimeLibc()) : undefined;
return (
WREQ_JS_NATIVE_BINDINGS.find(
(binding) =>
binding.platform === platform &&
binding.arch === arch &&
(binding.libc === undefined || binding.libc === runtimeLibc)
) ?? null
);
}