Files
91fans_h5/src/utils/detectLine.ts
T
2026-09-17 15:11:31 +07:00

122 lines
3.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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<boolean> => {
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<string> =>
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<string[]> => {
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<string> => {
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;