128 lines
4.7 KiB
JavaScript
128 lines
4.7 KiB
JavaScript
|
|
/**
|
|||
|
|
* 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 失败时静默降级
|
|||
|
|
}
|
|||
|
|
}
|