Files
mihomo-patchbay/shared/outbound-chain.ts
T
im0o 43d410d02c 去掉解释性提示和链条上的类型标注
链条回到纯名字。类型标注和「某某组自己决定选中哪一条」那句解释是我上一版加的,
用来说明为什么那一层点不了——但不能点的东西本来就没画出来,解释一个看不见的东西
只是噪音。

默认入站那条压成一句,内核未连接那条压成四个字,没有附加规则时整行不显示而不是
显示一个破折号。shared 里跟着没人用的 autoDecidedHops 一并删掉。
2026-08-10 18:58:34 +08:00

82 lines
3.0 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.
/**
* 「这个端口现在到底走哪条线」——这个工具要回答的核心问题。
*
* 答案往往不是一跳能到的:一路入站落在 `PROXY` 组上,`PROXY` 选中了 `AUTO`,
* `AUTO` 又按延迟挑了某条线路。只显示第一层等于没回答。
*
* 这里只做纯计算,输入是内核报上来的运行时状态,输出是名字数组。放在 shared 里
* 是为了能被 `bun test` 覆盖——它带防环处理,而防环这种事写在组件的 computed 里
* 就再也没人测得到。
*/
/** 运行时状态里跟解析链路有关的部分。完整定义在 web/api.ts。 */
export interface RuntimeNode {
/** 出站类型。策略组是 Selector / URLTest / Fallback / LoadBalance 之一 */
type?: string
/** 策略组当前选中的成员。不是策略组就没有这个字段 */
now?: string
/** 策略组的成员列表 */
all?: string[]
}
/**
* 这个类型的策略组接不接受手动选择。
*
* 内核里只有 `Selector` 是「由人来选」的(内核源码 constant/adapters.go:240-248 列出
* 全部类型)。`URLTest` 按延迟挑最快的、`Fallback` 挑第一个可用的、`LoadBalance` 在成员
* 之间分摊——它们的选中结果是算出来的,界面上给一个能点的列表只会让人困惑:点完可能
* 立刻被算回去。`Relay` 已经被内核移除。
*/
export function acceptsManualChoice(type: string | undefined): boolean {
return type === 'Selector'
}
/**
* 从落脚的出站一路跟着「当前选中」往下走。
*
* 返回的数组第一项是起点,最后一项是走到头的那个名字。起点为 null 时返回空数组。
* 策略组理论上可以互相引用成环,遇到重复的名字就停下——内核自己会拒绝这种配置,
* 但界面不能因为一份坏配置就转到卡死。
*/
export function resolveChain(
runtime: Record<string, RuntimeNode>,
start: string | null,
): string[] {
const hops: string[] = []
let cursor = start
while (cursor && !hops.includes(cursor)) {
hops.push(cursor)
cursor = runtime[cursor]?.now ?? null
}
return hops
}
export interface SwitchableGroup {
group: string
members: string[]
now: string
}
/**
* 链条上最后一个能由人来选的策略组。
*
* 取最后一个而不是第一个,是因为越靠近线路的那一层越接近「换线路」这个意图;
* 前面的层换的是「走哪套策略」,那是另一回事。
*
* 跳过自动决定的组:让人去点一个 URLTest 组的成员,点完会被延迟测试算回去,
* 那不是选择,是错觉。
*/
export function lastSwitchableGroup(
runtime: Record<string, RuntimeNode>,
chain: string[],
): SwitchableGroup | null {
for (let i = chain.length - 1; i >= 0; i -= 1) {
const name = chain[i]!
const node = runtime[name]
if (!node?.all?.length || !acceptsManualChoice(node.type)) continue
return { group: name, members: node.all, now: node.now ?? '' }
}
return null
}