码桶

发现社区成员的开源项目

奶狗 /

ng-webot

公开
main
ng-webot/lib/proxy.js
proxy.js3.8 KB
/**
 * 代理服务器设置
 * 读取 settings 表中的代理配置,应用到 axios(后端出站流量统一走 axios),
 * 并提供连通性测试。支持 http / https / socks5 协议,可带账号密码鉴权。
 */
const axios = require('axios');
const settings = require('./settings');

/** 读取代理设置 */
async function loadProxySettings() {
  const enabled = (await settings.getSetting('proxy_enabled', '0')) === '1';
  const ip = await settings.getSetting('proxy_ip', '');
  const port = await settings.getSetting('proxy_port', '');
  const user = await settings.getSetting('proxy_user', '');
  const pass = await settings.getSetting('proxy_pass', '');
  const protocol = (await settings.getSetting('proxy_protocol', 'http')).toLowerCase();
  return { enabled, ip, port, user, pass, protocol };
}

/** 根据设置拼出代理 URL;信息不全返回 null */
function buildProxyUrl(s) {
  if (!s.ip || !s.port) return null;
  let auth = '';
  if (s.user) {
    auth = encodeURIComponent(s.user) + (s.pass ? ':' + encodeURIComponent(s.pass) : '') + '@';
  }
  return `${s.protocol}://${auth}${s.ip}:${s.port}`;
}

/** 构建 axios 的 http/https agent(socks 协议共用一个 agent) */
async function buildAgents(s) {
  const url = buildProxyUrl(s);
  if (!url) return null;
  if (s.protocol === 'socks5' || s.protocol === 'socks4') {
    const { SocksProxyAgent } = require('socks-proxy-agent');
    const a = new SocksProxyAgent(url);
    return { httpAgent: a, httpsAgent: a };
  }
  const { HttpsProxyAgent } = require('https-proxy-agent');
  const httpsAgent = new HttpsProxyAgent(url);
  let httpAgent = httpsAgent;
  try {
    const { HttpProxyAgent } = require('http-proxy-agent');
    httpAgent = new HttpProxyAgent(url);
  } catch (e) { /* 仅 https 也能覆盖多数场景 */ }
  return { httpAgent, httpsAgent };
}

/** 应用代理到 axios 全局默认 agent;未启用则清除 */
async function applyProxy() {
  const s = await loadProxySettings();
  if (!s.enabled) {
    axios.defaults.httpAgent = undefined;
    axios.defaults.httpsAgent = undefined;
    axios.defaults.proxy = false;
    return { enabled: false };
  }
  try {
    const agents = await buildAgents(s);
    if (!agents) return { enabled: true, error: '代理配置不完整(需填写 IP 和端口)' };
    axios.defaults.httpAgent = agents.httpAgent;
    axios.defaults.httpsAgent = agents.httpsAgent;
    axios.defaults.proxy = false; // 禁用 axios 内置代理逻辑,改用上面的 agent
    console.log(`[proxy] 已启用代理: ${s.protocol}://${s.ip}:${s.port}`);
    return { enabled: true };
  } catch (e) {
    console.error('[proxy] 应用代理失败:', e.message);
    return { enabled: true, error: e.message };
  }
}

/** 测试代理连通性:透过代理请求一个外部地址,返回结果 */
async function testProxy(testUrl) {
  const s = await loadProxySettings();
  const url = buildProxyUrl(s);
  if (!url) return { ok: false, msg: '请先填写代理 IP 和端口' };
  try {
    const agents = await buildAgents(s);
    if (!agents) return { ok: false, msg: '代理配置不完整(需填写 IP 和端口)' };
    const target = testUrl || 'https://api.ipify.org?format=json';
    const resp = await axios.get(target, {
      httpAgent: agents.httpAgent,
      httpsAgent: agents.httpsAgent,
      proxy: false,
      timeout: 10000,
    });
    const body = typeof resp.data === 'string' ? resp.data : JSON.stringify(resp.data);
    return { ok: true, msg: `连通成功(HTTP ${resp.status})`, body: body.slice(0, 500) };
  } catch (e) {
    const detail = e.response ? `HTTP ${e.response.status}` : e.code || e.message;
    return { ok: false, msg: '连通失败: ' + detail };
  }
}

module.exports = { loadProxySettings, buildProxyUrl, applyProxy, testProxy };