codex-copilot-dx 0.9.23 → 0.9.24

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,92 @@
1
+ export const NATIVE_PREDICTION_GATE = "4290031608";
2
+
3
+ const LOCAL_PREDICTION_CONFIG = Object.freeze({
4
+ is_enabled: true,
5
+ prompt: 'Predict one useful next message the user may send, based on the conversation. Write from the user\'s perspective and use the user\'s language. Do not answer the message, call tools, or invent new requests, consent, or permission. Prefer a short, natural follow-up. If no useful follow-up is apparent, use an empty string. Return only a JSON object with exactly one key: {"suggestion":"the predicted user message"}.',
6
+ prompt_version: "ccdx-native-prediction-v1",
7
+ prediction_reasoning_effort: "low",
8
+ });
9
+
10
+ function isObject(value) {
11
+ return value !== null && typeof value === "object" && !Array.isArray(value);
12
+ }
13
+
14
+ function parseBody(body) {
15
+ if (typeof body !== "string" && !Buffer.isBuffer(body)) return null;
16
+ try { return JSON.parse(body.toString()); } catch { return null; }
17
+ }
18
+
19
+ function encodeBody(value, original) {
20
+ const text = JSON.stringify(value);
21
+ return Buffer.isBuffer(original) ? Buffer.from(text) : text;
22
+ }
23
+
24
+ export function classifyNativePredictionPath({ hostname, pathname } = {}) {
25
+ const host = typeof hostname === "string" ? hostname.toLowerCase() : "";
26
+ if (host === "chatgpt.com") {
27
+ if (pathname === "/backend-api/wham/statsig/bootstrap") return "bootstrap";
28
+ if (pathname === "/backend-api/wham/predictions/config") return "config";
29
+ }
30
+ if (host === "ab.chatgpt.com" && pathname === "/v1/initialize") return "refresh";
31
+ return null;
32
+ }
33
+
34
+ function enablePredictionGate(payload) {
35
+ if (!isObject(payload) || (payload.response_mode === "live_overlay" ? payload.has_updates === false : payload.has_updates !== true) ||
36
+ (payload.is_delta !== undefined && payload.is_delta !== false) ||
37
+ (payload.response_mode !== undefined && !["full", "live_overlay"].includes(payload.response_mode)) ||
38
+ !isObject(payload.feature_gates)) return false;
39
+ // Statsig V1 first resolves the literal name, then its djb2 lookup key.
40
+ let hash = 0;
41
+ for (const char of NATIVE_PREDICTION_GATE) hash = ((hash << 5) - hash + char.charCodeAt(0)) | 0;
42
+ const hashedKey = String(hash >>> 0);
43
+ const key = Object.hasOwn(payload.feature_gates, NATIVE_PREDICTION_GATE) ? NATIVE_PREDICTION_GATE
44
+ : Object.hasOwn(payload.feature_gates, hashedKey) ? hashedKey : NATIVE_PREDICTION_GATE;
45
+ const gate = payload.feature_gates[key];
46
+ if (gate !== undefined && (!isObject(gate) || typeof gate.value !== "boolean" || gate.value === true)) return false;
47
+ if (payload.response_mode === "live_overlay" && !Array.isArray(payload.live_entity_names?.feature_gates)) return false;
48
+ if (gate) gate.value = true;
49
+ else payload.feature_gates[key] = { name: key, value: true, rule_id: "ccdx-local-compatibility", secondary_exposures: [] };
50
+ if (payload.response_mode === "live_overlay" && !payload.live_entity_names.feature_gates.includes(key)) {
51
+ payload.live_entity_names.feature_gates.push(key);
52
+ }
53
+ return true;
54
+ }
55
+
56
+ // No-op results retain the original bytes; callers own HTTP headers and decoding.
57
+ export function applyNativePredictionOverlay({ hostname, pathname, statusCode, body, enabled = false } = {}) {
58
+ const kind = classifyNativePredictionPath({ hostname, pathname });
59
+ const result = { body, statusCode, changed: false, official: false, kind };
60
+ if (!enabled || !kind) return result;
61
+ if (kind === "config" && statusCode === 404) {
62
+ return { ...result, statusCode: 200, body: encodeBody(LOCAL_PREDICTION_CONFIG, body), changed: true };
63
+ }
64
+ if (statusCode !== 200) return result;
65
+ const parsed = parseBody(body);
66
+ if (!isObject(parsed)) return result;
67
+ if (kind === "config") {
68
+ if (parsed.is_enabled === true) return { ...result, official: true };
69
+ if (parsed.is_enabled !== false || typeof parsed.prompt !== "string" || !parsed.prompt.trim()) return result;
70
+ parsed.is_enabled = true;
71
+ } else if (kind === "bootstrap") {
72
+ const stringPayload = typeof parsed.statsigPayload === "string";
73
+ const payload = stringPayload ? parseBody(parsed.statsigPayload) : parsed.statsigPayload;
74
+ if (!enablePredictionGate(payload)) return result;
75
+ if (stringPayload) parsed.statsigPayload = JSON.stringify(payload);
76
+ } else if (!enablePredictionGate(parsed)) return result;
77
+ return { ...result, body: encodeBody(parsed, body), changed: true };
78
+ }
79
+
80
+ // Overlaying a delta would invalidate its checksum. Ask for a full response instead.
81
+ export function normalizeStatsigRequest({ hostname, pathname, body, enabled = false } = {}) {
82
+ const result = { body, changed: false };
83
+ if (!enabled || classifyNativePredictionPath({ hostname, pathname }) !== "refresh") return result;
84
+ const parsed = parseBody(body);
85
+ if (!isObject(parsed) || !isObject(parsed.user) ||
86
+ (parsed.responseMode !== undefined && !["full", "live_overlay"].includes(parsed.responseMode))) return result;
87
+ if (parsed.deltasResponseRequested === false && parsed.full_checksum === null && parsed.sinceTime === 0) return result;
88
+ parsed.deltasResponseRequested = false;
89
+ parsed.full_checksum = null;
90
+ parsed.sinceTime = 0;
91
+ return { body: encodeBody(parsed, body), changed: true };
92
+ }
@@ -0,0 +1,181 @@
1
+ import fs from "node:fs";
2
+ import os from "node:os";
3
+ import path from "node:path";
4
+ import { execFileSync } from "node:child_process";
5
+ import { createHash, X509Certificate, createPrivateKey, createPublicKey } from "node:crypto";
6
+ import { atomicWriteFileSync } from "./atomic-file.mjs";
7
+ import { userSettingsPath } from "./user-settings.mjs";
8
+
9
+ export function predictionPaths({ env = process.env, home = os.homedir() } = {}) {
10
+ const directory = path.join(path.dirname(userSettingsPath({ env, home })), "native-prediction");
11
+ const profile = env.CODEX_ELECTRON_USER_DATA_PATH?.trim()
12
+ || path.join(home, "Library", "Application Support", "Codex");
13
+ return { directory, settings: path.join(directory, "settings.json"), profile,
14
+ store: path.join(profile, "Default", "ServerCertificate") };
15
+ }
16
+
17
+ export function readPredictionSettings(options = {}) {
18
+ const { settings } = predictionPaths(options);
19
+ try {
20
+ const value = JSON.parse(fs.readFileSync(settings, "utf8"));
21
+ if (value?.version !== 1 || typeof value.enabled !== "boolean") throw new Error();
22
+ return value;
23
+ } catch (error) {
24
+ if (error.code === "ENOENT") return { version: 1, enabled: false };
25
+ throw new Error("Native prediction settings are invalid; no settings were changed");
26
+ }
27
+ }
28
+
29
+ export function writePredictionSettings(value, options = {}) {
30
+ atomicWriteFileSync(predictionPaths(options).settings, JSON.stringify(value, null, 2) + "\n", { preserveMode: false });
31
+ }
32
+
33
+ export function assertPredictionProxyCompatible({ env = process.env, systemProxy = "" } = {}) {
34
+ if (["HTTPS_PROXY", "HTTP_PROXY", "ALL_PROXY", "https_proxy", "http_proxy", "all_proxy", "CODEX_API_BASE_URL"]
35
+ .some((key) => env[key]?.trim())
36
+ || /(?:HTTPEnable|HTTPSEnable|SOCKSEnable|ProxyAutoConfigEnable|ProxyAutoDiscoveryEnable)\s*:\s*1/.test(systemProxy)) {
37
+ throw new Error("Native prediction setup skipped: an existing proxy or API route is configured; it was not replaced");
38
+ }
39
+ }
40
+
41
+ export function assertCodexClosed({ exec = execFileSync } = {}) {
42
+ for (const name of ["ChatGPT", "Codex"]) {
43
+ try {
44
+ exec("/usr/bin/pgrep", ["-x", name], { stdio: "pipe", timeout: 2000 });
45
+ } catch (error) {
46
+ if (error.status === 1) continue;
47
+ throw new Error("Could not check whether Codex is closed; prediction setup was skipped");
48
+ }
49
+ throw new Error("Quit Codex App before changing native prediction setup; no running App will be stopped");
50
+ }
51
+ }
52
+
53
+ export function installedPredictionApp({ exec = execFileSync } = {}) {
54
+ for (const app of ["/Applications/ChatGPT.app", "/Applications/Codex.app"]) {
55
+ const archive = path.join(app, "Contents", "Resources", "app.asar");
56
+ if (!fs.existsSync(archive)) continue;
57
+ // Check only during explicit setup; startup uses the cheap identity check.
58
+ const fd = fs.openSync(archive, "r");
59
+ try {
60
+ const prefix = Buffer.alloc(16); fs.readSync(fd, prefix, 0, 16, 0);
61
+ const headerSize = prefix.readUInt32LE(12);
62
+ if (headerSize > 32 * 1024 * 1024) continue;
63
+ const header = Buffer.alloc(headerSize); fs.readSync(fd, header, 0, header.length, 16);
64
+ const files = JSON.parse(header).files?.[".vite"]?.files?.build?.files;
65
+ const entry = Object.entries(files || {}).find(([name]) => /^main-[\w-]+\.js$/.test(name))?.[1];
66
+ if (!entry || entry.size > 32 * 1024 * 1024 || entry.unpacked) continue;
67
+ const bytes = Buffer.alloc(entry.size);
68
+ fs.readSync(fd, bytes, 0, bytes.length, 8 + prefix.readUInt32LE(4) + Number(entry.offset));
69
+ const source = bytes.toString();
70
+ if (!["/wham/predictions/config", "experimentalPredictionMode"].every((value) => source.includes(value))) continue;
71
+ const renderer = Object.entries(JSON.parse(header).files?.webview?.files?.assets?.files || {})
72
+ .find(([name]) => /^general-settings-[\w-]+\.js$/.test(name))?.[1];
73
+ if (!renderer || renderer.unpacked || renderer.size > 4 * 1024 * 1024) continue;
74
+ const rendererBytes = Buffer.alloc(renderer.size);
75
+ fs.readSync(fd, rendererBytes, 0, rendererBytes.length, 8 + prefix.readUInt32LE(4) + Number(renderer.offset));
76
+ if (!rendererBytes.includes(Buffer.from("4290031608"))) continue;
77
+ exec("/usr/bin/codesign", ["--verify", "--deep", "--strict", app], { stdio: "pipe", timeout: 15_000 });
78
+ const stat = fs.statSync(archive);
79
+ return { app, archive, size: stat.size, mtimeMs: stat.mtimeMs };
80
+ } finally { fs.closeSync(fd); }
81
+ }
82
+ throw new Error("This Codex App does not expose the verified native prediction protocol; setup was skipped");
83
+ }
84
+
85
+ export function predictionAppMatches(app) {
86
+ try { const stat = fs.statSync(app.archive); return stat.size === app.size && stat.mtimeMs === app.mtimeMs; }
87
+ catch { return false; }
88
+ }
89
+
90
+ function privateDirectory(directory) {
91
+ fs.mkdirSync(directory, { recursive: true, mode: 0o700 });
92
+ const stat = fs.lstatSync(directory);
93
+ if (!stat.isDirectory() || stat.isSymbolicLink() || (stat.mode & 0o077)) {
94
+ throw new Error("Prediction key directory must be private and must not be a symlink");
95
+ }
96
+ }
97
+
98
+ export function readPredictionCertificate(directory, { allowExpired = false } = {}) {
99
+ const cert = fs.readFileSync(path.join(directory, "bridge.pem"));
100
+ const ca = new X509Certificate(fs.readFileSync(path.join(directory, "ca.pem")));
101
+ const keyFile = path.join(directory, "bridge.key");
102
+ const info = fs.lstatSync(keyFile);
103
+ if (!info.isFile() || info.isSymbolicLink() || (info.mode & 0o077)) throw new Error("Prediction key is not private");
104
+ const key = fs.readFileSync(keyFile);
105
+ const x509 = new X509Certificate(cert);
106
+ const publicKey = createPublicKey(createPrivateKey(key)).export({ type: "spki", format: "der" });
107
+ if (!x509.publicKey.export({ type: "spki", format: "der" }).equals(publicKey)
108
+ || (!allowExpired && Date.parse(x509.validTo) <= Date.now()) || !x509.checkHost("chatgpt.com") || !x509.checkHost("ab.chatgpt.com")
109
+ || !x509.verify(ca.publicKey) || !ca.ca || x509.ca) {
110
+ throw new Error("Prediction certificate needs renewal; quit Codex and run ccdx prediction on");
111
+ }
112
+ return { cert, key, ca: ca.toString(), hash: createHash("sha256").update(ca.raw).digest("hex").toUpperCase(), der: ca.raw };
113
+ }
114
+
115
+ export function createPredictionCertificate(directory, { exec = execFileSync } = {}) {
116
+ privateDirectory(directory);
117
+ const temporary = fs.mkdtempSync(path.join(directory, "certificate-"));
118
+ try {
119
+ const config = `[req]\nprompt=no\ndistinguished_name=dn\nx509_extensions=ext\n[dn]\nCN=CCDX native prediction local bridge\n[ext]\nbasicConstraints=critical,CA:TRUE,pathlen:0\nkeyUsage=critical,keyCertSign,digitalSignature\nextendedKeyUsage=serverAuth\nsubjectAltName=DNS:chatgpt.com,DNS:ab.chatgpt.com\nnameConstraints=critical,permitted;DNS:chatgpt.com\n`;
120
+ const configFile = path.join(temporary, "openssl.cnf");
121
+ atomicWriteFileSync(configFile, config);
122
+ const certFile = path.join(temporary, "cert.pem"), keyFile = path.join(temporary, "key.pem");
123
+ const caFile = path.join(temporary, "ca.pem"), caKey = path.join(temporary, "ca.key");
124
+ const run = (args) => exec("/usr/bin/openssl", args, { stdio: "pipe", timeout: 15_000 });
125
+ run(["req", "-x509", "-newkey", "rsa:2048", "-nodes", "-days", "90", "-config", configFile, "-out", caFile, "-keyout", caKey]);
126
+ const csr = path.join(temporary, "leaf.csr"), extensions = path.join(temporary, "leaf.cnf");
127
+ atomicWriteFileSync(extensions, "basicConstraints=critical,CA:FALSE\nkeyUsage=critical,digitalSignature,keyEncipherment\nextendedKeyUsage=serverAuth\nsubjectAltName=DNS:chatgpt.com,DNS:ab.chatgpt.com\n");
128
+ run(["req", "-newkey", "rsa:2048", "-nodes", "-keyout", keyFile, "-out", csr, "-subj", "/CN=CCDX local prediction bridge"]);
129
+ run(["x509", "-req", "-in", csr, "-CA", caFile, "-CAkey", caKey, "-CAcreateserial", "-days", "90", "-extfile", extensions, "-out", certFile]);
130
+ atomicWriteFileSync(path.join(directory, "bridge.key"), fs.readFileSync(keyFile), { preserveMode: false });
131
+ atomicWriteFileSync(path.join(directory, "bridge.pem"), fs.readFileSync(certFile), { preserveMode: false });
132
+ atomicWriteFileSync(path.join(directory, "ca.pem"), fs.readFileSync(caFile), { preserveMode: false });
133
+ return readPredictionCertificate(directory);
134
+ } finally { fs.rmSync(temporary, { recursive: true, force: true }); }
135
+ }
136
+
137
+ // Chromium certificate metadata: trusted anchor, exact DNS constraints. This
138
+ // private record is scoped to one App profile; no system keychain is changed.
139
+ export function predictionTrustMetadata() {
140
+ const names = ["chatgpt.com", "ab.chatgpt.com"].map((name) => Buffer.concat([Buffer.from([10, Buffer.byteLength(name)]), Buffer.from(name)]));
141
+ const constraints = Buffer.concat(names);
142
+ return Buffer.concat([Buffer.from([10, 2, 8, 3, 18, constraints.length]), constraints]);
143
+ }
144
+
145
+ export async function updatePredictionTrust(store, certificate, { remove = false, priorHash = null } = {}) {
146
+ if (remove && !fs.existsSync(store)) return false;
147
+ if (fs.existsSync(store) && !fs.lstatSync(store).isFile()) throw new Error("Unexpected certificate-store file; skipped");
148
+ fs.mkdirSync(path.dirname(store), { recursive: true, mode: 0o700 });
149
+ const { DatabaseSync } = await import("node:sqlite");
150
+ const db = new DatabaseSync(store);
151
+ try {
152
+ db.exec("PRAGMA busy_timeout=1000; BEGIN IMMEDIATE");
153
+ const tables = db.prepare("SELECT name FROM sqlite_master WHERE type='table'").all().map((item) => item.name);
154
+ if (!tables.length && !remove) {
155
+ db.exec("CREATE TABLE meta(key LONGVARCHAR NOT NULL UNIQUE PRIMARY KEY,value LONGVARCHAR); CREATE TABLE certificates(sha256hash_hex TEXT PRIMARY KEY,der_cert BLOB NOT NULL,trust_settings BLOB NOT NULL)");
156
+ db.prepare("INSERT INTO meta VALUES(?,?)").run("version", "1");
157
+ db.prepare("INSERT INTO meta VALUES(?,?)").run("last_compatible_version", "1");
158
+ }
159
+ const meta = db.prepare("SELECT value FROM meta WHERE key='version'").get();
160
+ const columns = db.prepare("PRAGMA table_info(certificates)").all().map((item) => item.name);
161
+ if (String(meta?.value) !== "1" || columns.join(",") !== "sha256hash_hex,der_cert,trust_settings") throw new Error("Unknown certificate-store schema; skipped");
162
+ const metadata = predictionTrustMetadata();
163
+ const existing = db.prepare("SELECT der_cert,trust_settings FROM certificates WHERE sha256hash_hex=?").get(certificate.hash);
164
+ if (existing && (!Buffer.from(existing.der_cert).equals(certificate.der) || !Buffer.from(existing.trust_settings).equals(metadata))) throw new Error("Prediction trust was modified; it was preserved");
165
+ if (remove) {
166
+ if (existing) db.prepare("DELETE FROM certificates WHERE sha256hash_hex=?").run(certificate.hash);
167
+ } else {
168
+ if (priorHash && priorHash !== certificate.hash) {
169
+ const previous = db.prepare("SELECT trust_settings FROM certificates WHERE sha256hash_hex=?").get(priorHash);
170
+ if (previous && !Buffer.from(previous.trust_settings).equals(metadata)) throw new Error("Previous prediction trust was modified; it was preserved");
171
+ db.prepare("DELETE FROM certificates WHERE sha256hash_hex=?").run(priorHash);
172
+ }
173
+ if (!existing) db.prepare("INSERT INTO certificates VALUES(?,?,?)").run(certificate.hash, certificate.der, metadata);
174
+ }
175
+ db.exec("COMMIT");
176
+ return remove ? Boolean(existing) : !existing;
177
+ } catch (error) {
178
+ try { db.exec("ROLLBACK"); } catch {}
179
+ throw error;
180
+ } finally { db.close(); }
181
+ }
@@ -0,0 +1,242 @@
1
+ import http from "node:http";
2
+ import https from "node:https";
3
+ import { promisify } from "node:util";
4
+ import { gunzip, inflate, brotliDecompress } from "node:zlib";
5
+
6
+ export const PREDICTION_HOSTS = Object.freeze(["chatgpt.com", "ab.chatgpt.com"]);
7
+ const CONFIG_LIMIT = 16 * 1024 * 1024;
8
+ const REQUEST_LIMIT = 512 * 1024;
9
+ const decoders = { gzip: promisify(gunzip), deflate: promisify(inflate), br: promisify(brotliDecompress) };
10
+
11
+ export function predictionRoute(host, pathname) {
12
+ if (host === "chatgpt.com" && pathname === "/backend-api/wham/statsig/bootstrap") return "bootstrap";
13
+ if (host === "chatgpt.com" && pathname === "/backend-api/wham/predictions/config") return "prediction";
14
+ if (host === "ab.chatgpt.com" && pathname === "/v1/initialize") return "refresh";
15
+ return null;
16
+ }
17
+
18
+ export function predictionPac(port) {
19
+ if (!Number.isInteger(port) || port < 1 || port > 65535) throw new Error("Invalid prediction proxy port");
20
+ return `function FindProxyForURL(url, host) { if (url.indexOf('https:') === 0 && (host === 'chatgpt.com' || host === 'ab.chatgpt.com')) return 'PROXY 127.0.0.1:${port}; DIRECT'; return 'DIRECT'; }\n`;
21
+ }
22
+
23
+ function cleanHeaders(headers) {
24
+ const result = { ...headers };
25
+ for (const name of String(headers.connection || "").split(",")) delete result[name.trim().toLowerCase()];
26
+ for (const name of ["connection", "proxy-authorization", "proxy-authenticate", "keep-alive", "te", "trailer", "transfer-encoding", "upgrade"]) delete result[name];
27
+ return result;
28
+ }
29
+
30
+ function changedHeaders(headers, length) {
31
+ const result = cleanHeaders(headers);
32
+ for (const name of ["content-encoding", "etag", "content-md5", "digest", "content-digest"]) delete result[name];
33
+ result["content-length"] = String(length);
34
+ result["cache-control"] = "no-store";
35
+ return result;
36
+ }
37
+
38
+ async function decode(bytes, encoding, limit) {
39
+ if (!encoding || encoding === "identity") return bytes;
40
+ if (!decoders[encoding]) throw new Error("Unsupported configuration encoding");
41
+ return decoders[encoding](bytes, { maxOutputLength: limit });
42
+ }
43
+
44
+ function listen(server) {
45
+ return new Promise((resolve, reject) => {
46
+ server.once("error", reject);
47
+ server.listen(0, "127.0.0.1", () => {
48
+ server.removeListener("error", reject);
49
+ resolve(server.address().port);
50
+ });
51
+ });
52
+ }
53
+
54
+ // Only selected configuration bodies are buffered. Other traffic is streamed,
55
+ // without retries, redirects, credential storage or request/response logging.
56
+ export async function startPredictionTransport({
57
+ key, cert, overlay, normalizeRequest = (value) => value,
58
+ enabled = () => true, requestImpl = https.request,
59
+ timeoutMs = 30_000, maxConnections = 128,
60
+ }) {
61
+ const sockets = new Set();
62
+ const upstreams = new Set();
63
+ let closing = false;
64
+ const gateway = https.createServer({ key, cert, minVersion: "TLSv1.2" }, (req, res) => {
65
+ let target;
66
+ try {
67
+ target = new URL(req.url, `https://${req.headers.host}`);
68
+ if (!PREDICTION_HOSTS.includes(target.hostname) || target.port || target.protocol !== "https:"
69
+ || target.username || target.password || !req.url.startsWith("/") || req.url.startsWith("//")
70
+ || req.headers.host !== target.hostname || req.socket.servername !== target.hostname) throw new Error("Invalid target");
71
+ } catch {
72
+ res.writeHead(403); res.end(); return;
73
+ }
74
+ const route = predictionRoute(target.hostname, target.pathname);
75
+ const headers = cleanHeaders(req.headers);
76
+ // No decoding/re-encoding on pass-through paths.
77
+ if (route && enabled()) headers["accept-encoding"] = "identity";
78
+ let upstream;
79
+ const fail = () => {
80
+ if (!res.headersSent) { res.writeHead(502); res.end(); } else res.destroy();
81
+ };
82
+ const send = (body, streamRemainder = false) => {
83
+ if (res.destroyed || closing) return;
84
+ upstream = requestImpl({
85
+ protocol: "https:", hostname: target.hostname, port: 443,
86
+ servername: target.hostname, method: req.method,
87
+ path: req.url, headers,
88
+ rejectUnauthorized: true,
89
+ }, (reply) => {
90
+ const pass = () => {
91
+ res.writeHead(reply.statusCode, cleanHeaders(reply.headers));
92
+ reply.pipe(res);
93
+ };
94
+ reply.on("error", fail);
95
+ // Error and non-JSON responses are forwarded as received. The overlay
96
+ // decides whether the narrowly allowed missing-config response applies.
97
+ const mediaType = String(reply.headers["content-type"] || "").split(";", 1)[0].trim();
98
+ if (!route || !enabled() || mediaType !== "application/json") { pass(); return; }
99
+ let size = 0;
100
+ const parts = [];
101
+ let streaming = false;
102
+ reply.on("data", (chunk) => {
103
+ if (streaming) return;
104
+ parts.push(chunk); size += chunk.length;
105
+ if (size > CONFIG_LIMIT) {
106
+ streaming = true;
107
+ res.writeHead(reply.statusCode, cleanHeaders(reply.headers));
108
+ reply.pause();
109
+ const buffered = Buffer.concat(parts); parts.length = 0;
110
+ if (res.write(buffered)) reply.pipe(res);
111
+ else res.once("drain", () => reply.pipe(res));
112
+ }
113
+ });
114
+ reply.on("end", () => void (async () => {
115
+ if (streaming || res.destroyed) return;
116
+ const original = Buffer.concat(parts);
117
+ let next = null;
118
+ try {
119
+ const decoded = await decode(original, reply.headers["content-encoding"], CONFIG_LIMIT);
120
+ if (decoded.length <= CONFIG_LIMIT) next = overlay({
121
+ route, host: target.hostname, pathname: target.pathname,
122
+ status: reply.statusCode, body: decoded.toString("utf8"),
123
+ enabled: enabled(),
124
+ });
125
+ } catch { /* Preserve unexpected upstream payloads byte-for-byte. */ }
126
+ if (next?.body !== undefined && (next.body !== original.toString("utf8") || next.status !== reply.statusCode)) {
127
+ const bytes = Buffer.from(next.body);
128
+ res.writeHead(next.status ?? reply.statusCode, changedHeaders(reply.headers, bytes.length));
129
+ res.end(bytes);
130
+ } else {
131
+ res.writeHead(reply.statusCode, cleanHeaders(reply.headers)); res.end(original);
132
+ }
133
+ })().catch(fail));
134
+ });
135
+ upstreams.add(upstream);
136
+ upstream.once("close", () => upstreams.delete(upstream));
137
+ if (route && enabled()) upstream.setTimeout(timeoutMs, () => upstream.destroy(new Error("Upstream timeout")));
138
+ upstream.on("error", fail);
139
+ if (streamRemainder) {
140
+ if (upstream.write(body)) req.pipe(upstream);
141
+ else upstream.once("drain", () => req.pipe(upstream));
142
+ } else if (body !== undefined) upstream.end(body); else req.pipe(upstream);
143
+ };
144
+ req.on("aborted", () => upstream?.destroy());
145
+ req.on("error", () => upstream?.destroy());
146
+ res.on("close", () => { if (!res.writableFinished) upstream?.destroy(); });
147
+ if (route !== "refresh" || req.method !== "POST" || !enabled()) { send(); return; }
148
+ // Statsig deltas use a checksum of prior values. Ask for a complete response
149
+ // so the client never merges an upstream delta with locally changed values.
150
+ void (async () => {
151
+ const parts = []; let size = 0;
152
+ for await (const part of req.iterator({ destroyOnReturn: false })) {
153
+ size += part.length;
154
+ parts.push(part);
155
+ if (size > REQUEST_LIMIT) { send(Buffer.concat(parts), true); return; }
156
+ }
157
+ let body = Buffer.concat(parts);
158
+ try {
159
+ const decoded = await decode(body, headers["content-encoding"], REQUEST_LIMIT);
160
+ const encoded = target.searchParams.get("se") === "1";
161
+ const text = encoded ? Buffer.from(decoded.toString().split("").reverse().join(""), "base64").toString() : decoded.toString();
162
+ const parsed = JSON.parse(text);
163
+ const normalized = normalizeRequest(parsed);
164
+ if (normalized !== parsed) {
165
+ const json = JSON.stringify(normalized);
166
+ body = Buffer.from(encoded ? Buffer.from(json).toString("base64").split("").reverse().join("") : json);
167
+ delete headers["content-encoding"];
168
+ headers["content-length"] = String(body.length);
169
+ }
170
+ } catch { /* Unknown request encoding stays untouched. */ }
171
+ send(body);
172
+ })().catch(fail);
173
+ });
174
+ gateway.on("secureConnection", (socket) => socket.setTimeout(0));
175
+ gateway.on("upgrade", (req, socket, head) => {
176
+ const host = req.headers.host;
177
+ if (!PREDICTION_HOSTS.includes(host) || req.socket.servername !== host
178
+ || !req.url.startsWith("/") || req.url.startsWith("//")) { socket.destroy(); return; }
179
+ const upstream = requestImpl({
180
+ protocol: "https:", hostname: host, port: 443, servername: host,
181
+ method: req.method, path: req.url, rejectUnauthorized: true,
182
+ headers: { ...cleanHeaders(req.headers), connection: "Upgrade", upgrade: req.headers.upgrade },
183
+ });
184
+ upstreams.add(upstream);
185
+ upstream.on("error", () => socket.destroy());
186
+ upstream.on("response", (reply) => {
187
+ const headers = Object.entries(cleanHeaders(reply.headers)).flatMap(([name, values]) =>
188
+ (Array.isArray(values) ? values : [values]).map((value) => `${name}: ${value}`));
189
+ socket.write(`HTTP/1.1 ${reply.statusCode} ${reply.statusMessage}\r\n${headers.join("\r\n")}\r\nConnection: close\r\n\r\n`);
190
+ reply.on("error", () => socket.destroy()); reply.pipe(socket);
191
+ });
192
+ upstream.on("upgrade", (reply, peer, peerHead) => {
193
+ upstreams.delete(upstream);
194
+ sockets.add(peer); peer.once("close", () => sockets.delete(peer));
195
+ const headers = Object.entries(reply.headers).flatMap(([name, values]) =>
196
+ (Array.isArray(values) ? values : [values]).map((value) => `${name}: ${value}`));
197
+ socket.write(`HTTP/1.1 ${reply.statusCode} ${reply.statusMessage}\r\n${headers.join("\r\n")}\r\n\r\n`);
198
+ if (peerHead.length) socket.write(peerHead);
199
+ if (head.length) peer.write(head);
200
+ socket.on("error", () => peer.destroy()); peer.on("error", () => socket.destroy());
201
+ socket.once("close", () => peer.destroy()); peer.once("close", () => socket.destroy());
202
+ socket.pipe(peer); peer.pipe(socket);
203
+ });
204
+ socket.once("close", () => { upstreams.delete(upstream); upstream.destroy(); });
205
+ upstream.end();
206
+ });
207
+ gateway.on("tlsClientError", (_error, socket) => socket.destroy());
208
+ gateway.on("clientError", (_error, socket) => socket.destroy());
209
+ const proxy = http.createServer((req, res) => {
210
+ if (req.method === "GET" && req.url === "/proxy.pac" && !req.headers.origin
211
+ && ["127.0.0.1", "localhost"].includes(String(req.headers.host || "").split(":")[0])) {
212
+ res.writeHead(200, { "Content-Type": "application/x-ns-proxy-autoconfig", "Cache-Control": "no-store" });
213
+ res.end(predictionPac(proxy.address().port)); return;
214
+ }
215
+ res.writeHead(403); res.end();
216
+ });
217
+ proxy.on("connection", (socket) => {
218
+ if (closing || sockets.size >= maxConnections) { socket.destroy(); return; }
219
+ sockets.add(socket); socket.once("close", () => sockets.delete(socket));
220
+ });
221
+ proxy.on("connect", (req, socket, head) => {
222
+ if (closing || !PREDICTION_HOSTS.some((host) => req.url === `${host}:443`) || req.headers.origin) {
223
+ socket.end("HTTP/1.1 403 Forbidden\r\nConnection: close\r\n\r\n"); return;
224
+ }
225
+ socket.setTimeout(timeoutMs, () => socket.destroy());
226
+ socket.write("HTTP/1.1 200 Connection Established\r\n\r\n");
227
+ if (head.length) socket.unshift(head);
228
+ gateway.emit("connection", socket);
229
+ });
230
+ proxy.on("clientError", (_error, socket) => socket.destroy());
231
+ const port = await listen(proxy);
232
+ return {
233
+ port, pacUrl: `http://127.0.0.1:${port}/proxy.pac`,
234
+ async close() {
235
+ closing = true;
236
+ for (const upstream of upstreams) upstream.destroy();
237
+ for (const socket of sockets) socket.destroy();
238
+ await new Promise((resolve) => proxy.close(resolve));
239
+ gateway.close();
240
+ },
241
+ };
242
+ }