Files
3x-ui/frontend/src/pages/xray/basics/helpers.ts
T
Rouzbeh† ea24ef0a69 feat(xray): default outbound in basic routing (#5815)
* feat(xray): default outbound picker in basic routing

Let panel users choose which outbound handles unmatched traffic by
moving it to the first position in the template outbounds list.

* fix(xray): keep direct/blocked outbounds when changing default

* style(routing): revert incidental whitespace churn

Drop double blank lines and the reformatted function signature so the default-outbound diff stays focused on behavior.
2026-07-09 01:55:47 +02:00

113 lines
3.8 KiB
TypeScript

import type { XraySettingsValue } from '@/hooks/useXraySetting';
import { blockedSettings, directSettings } from './constants';
export function ruleGetter(t: XraySettingsValue | null, outboundTag: string, property: string): string[] {
if (!t?.routing?.rules) return [];
const out: string[] = [];
for (const rule of t.routing.rules) {
if (
rule &&
Object.prototype.hasOwnProperty.call(rule, property) &&
Object.prototype.hasOwnProperty.call(rule, 'outboundTag') &&
rule.outboundTag === outboundTag
) {
const v = (rule as Record<string, unknown>)[property];
if (Array.isArray(v)) out.push(...(v as string[]));
}
}
return out;
}
export function ruleSetter(t: XraySettingsValue, outboundTag: string, property: string, data: string[]): void {
if (!t.routing) return;
if (!Array.isArray(t.routing.rules)) t.routing.rules = [];
const current = ruleGetter(t, outboundTag, property);
if (current.length === 0) {
t.routing.rules.push({ type: 'field', outboundTag, [property]: data });
return;
}
const next: typeof t.routing.rules = [];
let inserted = false;
for (const rule of t.routing.rules) {
const matches =
rule &&
Object.prototype.hasOwnProperty.call(rule, property) &&
Object.prototype.hasOwnProperty.call(rule, 'outboundTag') &&
rule.outboundTag === outboundTag;
if (matches) {
if (!inserted && data.length > 0) {
(rule as Record<string, unknown>)[property] = data;
next.push(rule);
inserted = true;
}
} else {
next.push(rule);
}
}
t.routing.rules = next;
}
export function syncOutbound(t: XraySettingsValue, tag: string, settings: Record<string, unknown>) {
if (!t.outbounds || !t.routing) return;
const rules = t.routing.rules || [];
const haveRules = rules.some((r) => r?.outboundTag === tag);
const idx = t.outbounds.findIndex((o) => o?.tag === tag);
if (!haveRules && idx > 0) t.outbounds.splice(idx, 1);
if (haveRules && idx < 0) t.outbounds.push(settings as never);
}
export function getDefaultOutboundTag(t: XraySettingsValue | null): string {
const tag = t?.outbounds?.[0]?.tag;
return typeof tag === 'string' && tag.length > 0 ? tag : 'direct';
}
export function setDefaultOutboundTag(t: XraySettingsValue, tag: string): void {
if (!tag) return;
if (!Array.isArray(t.outbounds)) t.outbounds = [];
const idx = t.outbounds.findIndex((o) => o?.tag === tag);
if (idx < 0) {
if (tag === 'direct') t.outbounds.push(directSettings as never);
else if (tag === 'blocked') t.outbounds.push(blockedSettings as never);
else return;
const newIdx = t.outbounds.length - 1;
const [moved] = t.outbounds.splice(newIdx, 1);
t.outbounds.unshift(moved);
} else if (idx > 0) {
const [moved] = t.outbounds.splice(idx, 1);
t.outbounds.unshift(moved);
}
}
export function propagateOutboundTagRename(
t: XraySettingsValue,
oldTag: string,
newTag: string,
): void {
if (!oldTag || !newTag || oldTag === newTag) return;
const rules = t.routing?.rules;
if (Array.isArray(rules)) {
for (const rule of rules) {
if (rule?.outboundTag === oldTag) rule.outboundTag = newTag;
}
}
const balancers = t.routing?.balancers;
if (Array.isArray(balancers)) {
for (const balancer of balancers) {
if (balancer?.fallbackTag === oldTag) balancer.fallbackTag = newTag;
if (Array.isArray(balancer?.selector)) {
balancer.selector = balancer.selector.map((sel) => (sel === oldTag ? newTag : sel));
}
}
}
if (Array.isArray(t.outbounds)) {
for (const outbound of t.outbounds) {
const sockopt = (outbound as { streamSettings?: { sockopt?: { dialerProxy?: string } } })
?.streamSettings?.sockopt;
if (sockopt?.dialerProxy === oldTag) sockopt.dialerProxy = newTag;
}
}
}