import axios from 'axios'; /** * 选线:本地缓存线路 → 内置兜底线路 → DNS 解析,同安卓 DetectLineManager。 * * 内置线路全挂时,去解析 VITE_DNS_DOMAIN 的 TXT 记录拿线路(运维提供,多条用 _ 分隔), * 解析出来的线路存本地,下次启动优先用它们。 */ /** DNS 解析出来的线路缓存 key(同安卓 StoreKeys.DETECT_LINE) */ const LINE_CACHE_KEY = 'detect_line'; /** 单次 ping / DNS 请求超时 */ const TIMEOUT = 1000 * 10; /** DoH 服务商,按顺序试:阿里(安卓用的也是这家)→ Cloudflare 兜底 */ const DNS_PROVIDERS = ['https://dns.alidns.com/resolve', 'https://cloudflare-dns.com/dns-query']; /** 内置兜底线路(.env 的 VITE_BASE_URL) */ const builtinLines = (): string[] => { try { const lines = JSON.parse(process.env.VITE_BASE_URL || '[]'); return Array.isArray(lines) ? lines.filter(Boolean) : []; } catch { return []; } }; /** 上次 DNS 解析出来的线路 */ const cachedLines = (): string[] => { try { const lines = JSON.parse(localStorage.getItem(LINE_CACHE_KEY) || '[]'); return Array.isArray(lines) ? lines.filter(Boolean) : []; } catch { return []; } }; const saveCachedLines = (lines: string[]): void => { try { localStorage.setItem(LINE_CACHE_KEY, JSON.stringify(lines)); } catch { // 无痕模式等写不进去,不影响本次选线 } }; /** ping 单条线路,不抛错:通了返回 true */ const pingLine = async (line: string): Promise => { try { const { data } = await axios.get(line + '/api/app/ping/check', { timeout: TIMEOUT }); return data?.code == 200; } catch { return false; } }; /** 并发 ping 一批线路:谁先成功用谁;全失败返回 '' */ const pingBatch = (lines: string[]): Promise => new Promise((resolve) => { if (!lines.length) return resolve(''); let failCount = 0; let settled = false; lines.forEach((line) => { void pingLine(line).then((ok) => { if (settled) return; if (ok) { settled = true; resolve(line); return; } if (++failCount >= lines.length) { settled = true; resolve(''); } }); }); }); /** * 解析 DNS TXT 记录拿线路。 * TXT 值形如 "https://a.com_https://b.com",带引号,按 _ 拆成多条 */ const dnsSolve = async (): Promise => { const domain = process.env.VITE_DNS_DOMAIN; if (!domain) return []; for (const provider of DNS_PROVIDERS) { try { const { data } = await axios.get(provider, { params: { name: domain, type: 'TXT' }, headers: { accept: 'application/dns-json' }, timeout: TIMEOUT, }); const lines: string[] = (data?.Answer || []) .flatMap((record: any) => String(record?.data || '').split('_')) .map((line: string) => line.replace(/"/g, '').trim()) .filter(Boolean); if (lines.length) return lines; } catch { // 这家解析不出来就换下一家 } } return []; }; /** * 完整选线:缓存线路(没有就用内置线路)全挂时回退 DNS 解析。 * 返回通了的线路,全都不通返回 '' */ export const detectLine = async (): Promise => { const cached = cachedLines(); const line = await pingBatch(cached.length ? cached : builtinLines()); if (line) return line; // 本地线路全部失败,回退 DNS 解析 const dnsLines = await dnsSolve(); if (!dnsLines.length) return ''; saveCachedLines(dnsLines); return await pingBatch(dnsLines); }; export default detectLine;