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

128 lines
4.7 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 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 失败时静默降级
}
}