export const PROTOCOLS = ['vmess', 'trojan', 'shadowsocks', 'hysteria2'] as const; export type Protocol = (typeof PROTOCOLS)[number]; export const CLIENTS = ['surge', 'shadowrocket', 'clash'] as const; export type ClientType = (typeof CLIENTS)[number]; export const CLIENT_LABELS: Record = { surge: 'Surge (macOS)', shadowrocket: 'ShadowRocket (iOS)', clash: 'FlClash', }; export type Egress = 'direct' | 'warp' | 'unknown'; /** Surge 与 Clash 使用 IETF 命名,Xray 分享链接使用简名 */ export const SS_CIPHER_ALIASES: Record = { 'chacha20-poly1305': 'chacha20-ietf-poly1305', 'xchacha20-poly1305': 'xchacha20-ietf-poly1305', }; export function normalizeSsCipher(method: string): string { return SS_CIPHER_ALIASES[method] ?? method; } export interface NodeCapabilityInput { protocol: Protocol; network: 'tcp' | 'ws'; alterId?: number; security?: string; } export interface CapabilityVerdict { ok: boolean; reason?: string; } /** * 客户端能力矩阵:不支持的组合在下发订阅时自动屏蔽。 * Surge 的 ss 类型没有 WebSocket 传输(仅 simple-obfs),SS over WS 线路必须排除。 */ export function clientSupports(client: ClientType, node: NodeCapabilityInput): CapabilityVerdict { if (client === 'surge' && node.protocol === 'shadowsocks' && node.network === 'ws') { return { ok: false, reason: 'Surge 不支持 Shadowsocks over WebSocket' }; } if (client === 'surge' && node.protocol === 'vmess') { if ((node.alterId ?? 0) !== 0) return { ok: false, reason: 'Surge 下发仅支持 alterId=0 的 VMess AEAD 节点' }; if (!['auto', 'aes-128-gcm', 'chacha20-poly1305', 'chacha20-ietf-poly1305'].includes(node.security ?? 'auto')) { return { ok: false, reason: `Surge 不支持该 VMess 加密方式:${node.security}` }; } } if (client === 'clash' && node.protocol === 'vmess' && !['auto', 'none', 'zero', 'aes-128-gcm', 'chacha20-poly1305', 'chacha20-ietf-poly1305'].includes(node.security ?? 'auto')) { return { ok: false, reason: `Clash 不支持该 VMess 加密方式:${node.security}` }; } return { ok: true }; }