bookworm-smart-assistant/scripts/proxy-bootstrap.js

128 lines
4.7 KiB
JavaScript
Raw Permalink Normal View History

/**
* Bookworm Smart Assistant - Node.js 代理引导脚本
*
* 解决两个代理穿透问题
* 1. Node.js 20 undici fetch 不自动使用 https_proxy 环境变量
* 2. ws (WebSocket) 库不自动走代理 导致 Browserbase MCP 连接失败
*
* 用法: NODE_OPTIONS="--require /path/to/proxy-bootstrap.js" node app.js
*/
'use strict';
const proxyUrl = process.env.https_proxy || process.env.HTTPS_PROXY || process.env.http_proxy || process.env.HTTP_PROXY;
if (proxyUrl) {
// 1) Patch undici fetch 走代理
try {
const { ProxyAgent, setGlobalDispatcher } = require('undici');
const agent = new ProxyAgent(proxyUrl);
setGlobalDispatcher(agent);
} catch (e) {
// undici 不可用时静默降级
}
// 2) Patch ws WebSocket 走代理(解决 Browserbase CDP 连接超时)
try {
const Module = require('module');
const path = require('path');
const originalLoad = Module._load;
// 缓存代理 agent延迟初始化
let proxyAgent = null;
let wsModulePath = null;
Module._load = function patchedLoad(request, parent, isMain) {
const result = originalLoad.apply(this, arguments);
// 只在首次加载 ws 模块时 patch
if (request === 'ws' && typeof result === 'function' && !result.__proxyPatched) {
// 记录 ws 模块的路径,用于后续解析 https-proxy-agent
try {
wsModulePath = path.dirname(Module._resolveFilename('ws', parent));
} catch (e) {
// ignore
}
const OrigWS = result;
// 用 Proxy 包装,保持完整的原型链和 instanceof 检查
const handler = {
construct(target, args) {
const address = args[0];
// 只对外部 wss:// 连接注入代理
if (typeof address === 'string' && address.startsWith('wss://')) {
// 延迟创建代理 agent从 ws 所在的 node_modules 解析 https-proxy-agent
if (!proxyAgent) {
try {
const agentPaths = [];
if (wsModulePath) agentPaths.push(wsModulePath);
// 也尝试 ws 的父目录node_modules 层级)
if (wsModulePath) agentPaths.push(path.join(wsModulePath, '..'));
const agentModPath = require.resolve('https-proxy-agent', { paths: agentPaths });
const { HttpsProxyAgent } = require(agentModPath);
proxyAgent = new HttpsProxyAgent(proxyUrl);
} catch (e) {
// 最后尝试全局解析
try {
const { HttpsProxyAgent } = require('https-proxy-agent');
proxyAgent = new HttpsProxyAgent(proxyUrl);
} catch (e2) {
// 无法获取代理 agent放弃
}
}
}
if (proxyAgent) {
if (args.length <= 1) {
// new WebSocket(url) → new WebSocket(url, { agent })
args = [address, { agent: proxyAgent }];
} else if (args.length === 2) {
const second = args[1];
if (typeof second === 'object' && second !== null && !Array.isArray(second)) {
// new WebSocket(url, options) → 注入 agent
if (!second.agent) second.agent = proxyAgent;
} else {
// new WebSocket(url, protocols) → 追加 options
args = [address, second, { agent: proxyAgent }];
}
} else if (args.length >= 3) {
// new WebSocket(url, protocols, options)
const opts = args[2];
if (typeof opts === 'object' && opts !== null) {
if (!opts.agent) opts.agent = proxyAgent;
} else {
args[2] = { agent: proxyAgent };
}
}
}
}
return new target(...args);
}
};
const proxied = new Proxy(OrigWS, handler);
proxied.__proxyPatched = true;
// 更新模块缓存,让后续 require('ws') 也返回 patched 版本
try {
const resolvedPath = Module._resolveFilename('ws', parent);
if (Module._cache[resolvedPath]) {
Module._cache[resolvedPath].exports = proxied;
}
} catch (e) {
// ignore
}
return proxied;
}
return result;
};
} catch (e) {
// ws patch 失败时静默降级
}
}