mirror of
https://github.com/Wei-Shaw/claude-relay-service.git
synced 2026-01-24 05:48:41 +00:00
213 lines
6.3 KiB
JavaScript
213 lines
6.3 KiB
JavaScript
const { SocksProxyAgent } = require('socks-proxy-agent')
|
||
const { HttpsProxyAgent } = require('https-proxy-agent')
|
||
const logger = require('./logger')
|
||
const config = require('../../config/config')
|
||
|
||
/**
|
||
* 统一的代理创建工具
|
||
* 支持 SOCKS5 和 HTTP/HTTPS 代理,可配置 IPv4/IPv6
|
||
*/
|
||
class ProxyHelper {
|
||
/**
|
||
* 创建代理 Agent
|
||
* @param {object|string|null} proxyConfig - 代理配置对象或 JSON 字符串
|
||
* @param {object} options - 额外选项
|
||
* @param {boolean|number} options.useIPv4 - 是否使用 IPv4 (true=IPv4, false=IPv6, undefined=auto)
|
||
* @returns {Agent|null} 代理 Agent 实例或 null
|
||
*/
|
||
static createProxyAgent(proxyConfig, options = {}) {
|
||
if (!proxyConfig) {
|
||
return null
|
||
}
|
||
|
||
try {
|
||
// 解析代理配置
|
||
const proxy = typeof proxyConfig === 'string' ? JSON.parse(proxyConfig) : proxyConfig
|
||
|
||
// 验证必要字段
|
||
if (!proxy.type || !proxy.host || !proxy.port) {
|
||
logger.warn('⚠️ Invalid proxy configuration: missing required fields (type, host, port)')
|
||
return null
|
||
}
|
||
|
||
// 获取 IPv4/IPv6 配置
|
||
const useIPv4 = ProxyHelper._getIPFamilyPreference(options.useIPv4)
|
||
|
||
// 构建认证信息
|
||
const auth = proxy.username && proxy.password ? `${proxy.username}:${proxy.password}@` : ''
|
||
|
||
// 根据代理类型创建 Agent
|
||
if (proxy.type === 'socks5') {
|
||
const socksUrl = `socks5://${auth}${proxy.host}:${proxy.port}`
|
||
const socksOptions = {}
|
||
|
||
// 设置 IP 协议族(如果指定)
|
||
if (useIPv4 !== null) {
|
||
socksOptions.family = useIPv4 ? 4 : 6
|
||
}
|
||
|
||
return new SocksProxyAgent(socksUrl, socksOptions)
|
||
} else if (proxy.type === 'http' || proxy.type === 'https') {
|
||
const proxyUrl = `${proxy.type}://${auth}${proxy.host}:${proxy.port}`
|
||
const httpOptions = {}
|
||
|
||
// HttpsProxyAgent 支持 family 参数(通过底层的 agent-base)
|
||
if (useIPv4 !== null) {
|
||
httpOptions.family = useIPv4 ? 4 : 6
|
||
}
|
||
|
||
return new HttpsProxyAgent(proxyUrl, httpOptions)
|
||
} else {
|
||
logger.warn(`⚠️ Unsupported proxy type: ${proxy.type}`)
|
||
return null
|
||
}
|
||
} catch (error) {
|
||
logger.warn('⚠️ Failed to create proxy agent:', error.message)
|
||
return null
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 获取 IP 协议族偏好设置
|
||
* @param {boolean|number|string} preference - 用户偏好设置
|
||
* @returns {boolean|null} true=IPv4, false=IPv6, null=auto
|
||
* @private
|
||
*/
|
||
static _getIPFamilyPreference(preference) {
|
||
// 如果没有指定偏好,使用配置文件或默认值
|
||
if (preference === undefined) {
|
||
// 从配置文件读取默认设置,默认使用 IPv4
|
||
const defaultUseIPv4 = config.proxy?.useIPv4
|
||
if (defaultUseIPv4 !== undefined) {
|
||
return defaultUseIPv4
|
||
}
|
||
// 默认值:IPv4(兼容性更好)
|
||
return true
|
||
}
|
||
|
||
// 处理各种输入格式
|
||
if (typeof preference === 'boolean') {
|
||
return preference
|
||
}
|
||
if (typeof preference === 'number') {
|
||
return preference === 4 ? true : preference === 6 ? false : null
|
||
}
|
||
if (typeof preference === 'string') {
|
||
const lower = preference.toLowerCase()
|
||
if (lower === 'ipv4' || lower === '4') {
|
||
return true
|
||
}
|
||
if (lower === 'ipv6' || lower === '6') {
|
||
return false
|
||
}
|
||
if (lower === 'auto' || lower === 'both') {
|
||
return null
|
||
}
|
||
}
|
||
|
||
// 无法识别的值,返回默认(IPv4)
|
||
return true
|
||
}
|
||
|
||
/**
|
||
* 验证代理配置
|
||
* @param {object|string} proxyConfig - 代理配置
|
||
* @returns {boolean} 是否有效
|
||
*/
|
||
static validateProxyConfig(proxyConfig) {
|
||
if (!proxyConfig) {
|
||
return false
|
||
}
|
||
|
||
try {
|
||
const proxy = typeof proxyConfig === 'string' ? JSON.parse(proxyConfig) : proxyConfig
|
||
|
||
// 检查必要字段
|
||
if (!proxy.type || !proxy.host || !proxy.port) {
|
||
return false
|
||
}
|
||
|
||
// 检查支持的类型
|
||
if (!['socks5', 'http', 'https'].includes(proxy.type)) {
|
||
return false
|
||
}
|
||
|
||
// 检查端口范围
|
||
const port = parseInt(proxy.port)
|
||
if (isNaN(port) || port < 1 || port > 65535) {
|
||
return false
|
||
}
|
||
|
||
return true
|
||
} catch (error) {
|
||
return false
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 获取代理配置的描述信息
|
||
* @param {object|string} proxyConfig - 代理配置
|
||
* @returns {string} 代理描述
|
||
*/
|
||
static getProxyDescription(proxyConfig) {
|
||
if (!proxyConfig) {
|
||
return 'No proxy'
|
||
}
|
||
|
||
try {
|
||
const proxy = typeof proxyConfig === 'string' ? JSON.parse(proxyConfig) : proxyConfig
|
||
const hasAuth = proxy.username && proxy.password
|
||
return `${proxy.type}://${proxy.host}:${proxy.port}${hasAuth ? ' (with auth)' : ''}`
|
||
} catch (error) {
|
||
return 'Invalid proxy config'
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 脱敏代理配置信息用于日志记录
|
||
* @param {object|string} proxyConfig - 代理配置
|
||
* @returns {string} 脱敏后的代理信息
|
||
*/
|
||
static maskProxyInfo(proxyConfig) {
|
||
if (!proxyConfig) {
|
||
return 'No proxy'
|
||
}
|
||
|
||
try {
|
||
const proxy = typeof proxyConfig === 'string' ? JSON.parse(proxyConfig) : proxyConfig
|
||
|
||
let proxyDesc = `${proxy.type}://${proxy.host}:${proxy.port}`
|
||
|
||
// 如果有认证信息,进行脱敏处理
|
||
if (proxy.username && proxy.password) {
|
||
const maskedUsername =
|
||
proxy.username.length <= 2
|
||
? proxy.username
|
||
: proxy.username[0] +
|
||
'*'.repeat(Math.max(1, proxy.username.length - 2)) +
|
||
proxy.username.slice(-1)
|
||
const maskedPassword = '*'.repeat(Math.min(8, proxy.password.length))
|
||
proxyDesc += ` (auth: ${maskedUsername}:${maskedPassword})`
|
||
}
|
||
|
||
return proxyDesc
|
||
} catch (error) {
|
||
return 'Invalid proxy config'
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 创建代理 Agent(兼容旧的函数接口)
|
||
* @param {object|string|null} proxyConfig - 代理配置
|
||
* @param {boolean} useIPv4 - 是否使用 IPv4
|
||
* @returns {Agent|null} 代理 Agent 实例或 null
|
||
* @deprecated 使用 createProxyAgent 替代
|
||
*/
|
||
static createProxy(proxyConfig, useIPv4 = true) {
|
||
logger.warn('⚠️ ProxyHelper.createProxy is deprecated, use createProxyAgent instead')
|
||
return ProxyHelper.createProxyAgent(proxyConfig, { useIPv4 })
|
||
}
|
||
}
|
||
|
||
module.exports = ProxyHelper
|