diff --git a/lib/provider/ClashProvider.ts b/lib/provider/ClashProvider.ts index 1453c7ddd..a46f8bb63 100644 --- a/lib/provider/ClashProvider.ts +++ b/lib/provider/ClashProvider.ts @@ -323,7 +323,11 @@ export const parseClashConfig = ( 'udp-relay': resolveUdpRelay(item.udp, udpRelay), } as ShadowsocksrNodeConfig; - case 'trojan': + case 'trojan': { + const network = item.network; + const wsOpts = _.get(item, 'ws-opts', {}); + const wsHeaders = lowercaseHeaderKeys(_.get(wsOpts, 'headers', {})); + return { type: NodeTypeEnum.Trojan, nodeName: item.name, @@ -337,7 +341,11 @@ export const parseClashConfig = ( ...('sni' in item ? { sni: item.sni } : null), 'udp-relay': resolveUdpRelay(item.udp, udpRelay), tls13: tls13 ?? false, + ...(network === 'ws' + ? { network: 'ws', wsPath: _.get(wsOpts, 'path', '/'), wsHeaders } + : null), } as TrojanNodeConfig; + } default: logger.warn( diff --git a/lib/types.ts b/lib/types.ts index 42e3f47ac..9b7ea3ae6 100644 --- a/lib/types.ts +++ b/lib/types.ts @@ -251,6 +251,9 @@ export interface TrojanNodeConfig extends SimpleNodeConfig { readonly sni?: string; readonly 'udp-relay'?: boolean; readonly tls13?: boolean; + readonly network?: 'default' | 'ws'; + readonly wsPath?: string; + readonly wsHeaders?: Record; } export interface Socks5NodeConfig extends SimpleNodeConfig { diff --git a/lib/utils/index.ts b/lib/utils/index.ts index e7621143b..a38c70e84 100644 --- a/lib/utils/index.ts +++ b/lib/utils/index.ts @@ -31,6 +31,7 @@ import { ConfigCache } from './cache'; import { ERR_INVALID_FILTER, OBFS_UA } from '../constant'; import { validateFilter } from './filter'; import httpClient from './http-client'; +import { getSurgeExtendHeaders } from './surge'; import { formatVmessUri } from './v2ray'; const logger = createLogger({ service: 'surgio:utils' }); @@ -369,19 +370,13 @@ export const getSurgeNodes = function ( configList.push(`encrypt-method=${config.method}`); } - function getHeader(wsHeaders: Record): string { - return Object.keys(wsHeaders) - .map((headerKey) => `${headerKey}:${wsHeaders[headerKey]}`) - .join('|'); - } - if (config.network === 'ws') { configList.push('ws=true'); configList.push(`ws-path=${config.path}`); configList.push( 'ws-headers=' + JSON.stringify( - getHeader({ + getSurgeExtendHeaders({ host: config.host || config.hostname, 'user-agent': OBFS_UA, ..._.omit(config.wsHeaders, ['host']), // host 本质上是一个头信息,所以可能存在冲突的情况。以 host 属性为准。 @@ -501,6 +496,18 @@ export const getSurgeNodes = function ( : []), ]; + if (nodeConfig.network === 'ws') { + configList.push('ws=true'); + configList.push(`ws-path=${nodeConfig.wsPath}`); + + if (nodeConfig.wsHeaders) { + configList.push( + 'ws-headers=' + + JSON.stringify(getSurgeExtendHeaders(nodeConfig.wsHeaders)), + ); + } + } + return [nodeConfig.nodeName, configList.join(', ')].join(' = '); } @@ -729,6 +736,15 @@ export const getClashNodes = function ( ...(nodeConfig.alpn ? { alpn: nodeConfig.alpn } : null), ...(nodeConfig.sni ? { sni: nodeConfig.sni } : null), 'skip-cert-verify': nodeConfig.skipCertVerify === true, + ...(nodeConfig.network === 'ws' + ? { + network: 'ws', + 'ws-opts': { + path: nodeConfig.wsPath || '/', + ...nodeConfig.wsHeaders, + }, + } + : null), }; case NodeTypeEnum.Socks5: { diff --git a/lib/utils/surge.ts b/lib/utils/surge.ts new file mode 100644 index 000000000..c2abd1b40 --- /dev/null +++ b/lib/utils/surge.ts @@ -0,0 +1,7 @@ +export const getSurgeExtendHeaders = ( + wsHeaders: Record, +): string => { + return Object.keys(wsHeaders) + .map((headerKey) => `${headerKey}:${wsHeaders[headerKey]}`) + .join('|'); +};