diff --git a/frontend/src/lib/xray/outbound-form-adapter.ts b/frontend/src/lib/xray/outbound-form-adapter.ts index 2c5a40b6a..a1ed479f5 100644 --- a/frontend/src/lib/xray/outbound-form-adapter.ts +++ b/frontend/src/lib/xray/outbound-form-adapter.ts @@ -541,7 +541,9 @@ function hydrateStreamForm(stream: Raw): OutboundStreamFormValues { } export function rawOutboundToFormValues(raw: RawOutboundRow): OutboundFormValues { - const protocol = asString(raw.protocol, 'vless'); + // The core lowercases a protocol id before it looks the handler up, so a + // template pasted as "Freedom" must not fall through to the vless default. + const protocol = asString(raw.protocol, 'vless').toLowerCase(); const settings = asObject(raw.settings); const tag = asString(raw.tag); const sendThrough = asString(raw.sendThrough); diff --git a/frontend/src/pages/xray/basics/BasicsTab.tsx b/frontend/src/pages/xray/basics/BasicsTab.tsx index 068196591..2ae686512 100644 --- a/frontend/src/pages/xray/basics/BasicsTab.tsx +++ b/frontend/src/pages/xray/basics/BasicsTab.tsx @@ -25,7 +25,13 @@ import { MASK_ADDRESS, ROUTING_DOMAIN_STRATEGIES, } from './constants'; -import { directFreedomStrategy, setDirectFreedomStrategy } from './helpers'; +import { + directFreedomStrategy, + ensureDirectFreedomOutbound, + isDirectFreedomOutbound, + isDirectTagTaken, + setDirectFreedomStrategy, +} from './helpers'; interface BasicsTabProps { templateSettings: XraySettingsValue | null; @@ -112,9 +118,10 @@ export default function BasicsTab({ const freedomStrategy = directFreedomStrategy(templateSettings); - const directFreedomOutbound = templateSettings?.outbounds?.find( - (o) => o?.protocol === 'freedom' && o?.tag === 'direct', + const directFreedomOutbound = templateSettings?.outbounds?.find((o) => + isDirectFreedomOutbound(o), ); + const directTagTaken = isDirectTagTaken(templateSettings); const directHappyEyeballs = (() => { const sockopt = ( directFreedomOutbound?.streamSettings as { sockopt?: { happyEyeballs?: unknown } } | undefined @@ -128,13 +135,8 @@ export default function BasicsTab({ const setDirectHappyEyeballs = useCallback( (next: ReturnType | null) => { mutate((tt) => { - if (!tt.outbounds) tt.outbounds = []; - let idx = tt.outbounds.findIndex((o) => o?.protocol === 'freedom' && o?.tag === 'direct'); - if (idx < 0) { - tt.outbounds.push({ protocol: 'freedom', tag: 'direct', settings: {} }); - idx = tt.outbounds.length - 1; - } - const ob = tt.outbounds[idx]; + const ob = ensureDirectFreedomOutbound(tt); + if (!ob) return; const stream = (ob.streamSettings ?? {}) as Record; const sockopt = (stream.sockopt ?? {}) as Record; if (next == null) { @@ -181,6 +183,7 @@ export default function BasicsTab({ control={