/** * 线路延迟测试。 * * 单独成一个模块,是因为「测失败了该跟用户说什么」有六个分支,而它原本写在组件里, * 一条测试都加不了。这些话是用户唯一能看到的诊断信息,值得测。 */ import { ref } from 'vue' import { ApiError, api } from './api' /** 超时,毫秒。文案里会用到它,所以不能让接口和文案各写一个数。 */ export const TEST_TIMEOUT_MS = 5000 /** * 把内核的延迟测试结果翻译成一句说明原因的话。 * * 内核对这件事的回答是精确的(内核源码 hub/route/proxies.go:122-145): * 504 在给定超时内没有完成 * 503 连接建立失败,或者测得的延迟为 0 * 404 这个名字不在内核的全局出站表里 * 笼统说一句「测不通」会把它们混在一起,而这三种要查的地方完全不一样。 */ export function describeTestFailure( name: string, error: unknown, timeoutMs = TEST_TIMEOUT_MS, ): string { if (!(error instanceof ApiError)) return `${name} 延迟测试失败:${String(error)}` if (error.status === 504) { return `${name} 延迟测试超时,${String(timeoutMs / 1000)} 秒内没有取到测试地址` } if (error.status === 404) { return `${name} 不在内核的出站表里,无法测试。订阅线路需要从所属订阅进去测。` } const detail = String(error.payload.message ?? '') // 内核对连接失败给的是一句没有信息量的通用英文,替换成能指导排查的话 const generic = detail === '' || detail === 'An error occurred in the delay test' if (error.status === 503) { return generic ? `${name} 无法建立连接,线路可能已失效,或本机到该服务器的网络不通` : `${name} 无法建立连接:${detail}` } return `${name} 延迟测试失败(HTTP ${String(error.status)})${generic ? '' : `:${detail}`}` } /** * 管理「哪几条线路正在测」。 * * 测延迟是纯读操作,不影响配置也不影响别的线路,所以它只标记自己那一条忙碌, * 不禁用界面上的任何其它东西。 */ export function useDelayTest(onFinished: () => void, onFailure: (message: string) => void) { const testing = ref([]) async function test(name: string, provider?: string) { if (testing.value.includes(name)) return testing.value = [...testing.value, name] try { await api.testLine(name, provider, TEST_TIMEOUT_MS) onFinished() } catch (error) { // 测不通是关于线路的结论,不是 Patchbay 出了故障 onFailure(describeTestFailure(name, error)) } finally { testing.value = testing.value.filter((n) => n !== name) } } return { testing, test } }