|
@@ -1,7 +1,9 @@
|
|
|
import { XHttpXmuxSchema } from '@/schemas/protocols/stream/xhttp';
|
|
import { XHttpXmuxSchema } from '@/schemas/protocols/stream/xhttp';
|
|
|
|
|
+import { OutboundDomainStrategySchema } from '@/schemas/protocols/outbound';
|
|
|
import { normalizeStreamSettingsForWire } from '@/lib/xray/stream-wire-normalize';
|
|
import { normalizeStreamSettingsForWire } from '@/lib/xray/stream-wire-normalize';
|
|
|
import { Wireguard } from '@/utils';
|
|
import { Wireguard } from '@/utils';
|
|
|
import type { Sniffing, SniffingDest } from '@/schemas/primitives';
|
|
import type { Sniffing, SniffingDest } from '@/schemas/primitives';
|
|
|
|
|
+import type { OutboundDomainStrategy } from '@/schemas/protocols/outbound';
|
|
|
|
|
|
|
|
import type {
|
|
import type {
|
|
|
DnsOutboundFormSettings,
|
|
DnsOutboundFormSettings,
|
|
@@ -56,6 +58,16 @@ function asPort(value: unknown, fallback: number): number {
|
|
|
return n;
|
|
return n;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+// xray-core matches targetStrategy/domainStrategy case-insensitively;
|
|
|
|
|
+// normalize the wire value to the canonical spelling or '' (= AsIs).
|
|
|
|
|
+function targetStrategyFromWire(value: unknown): OutboundDomainStrategy | '' {
|
|
|
|
|
+ const s = asString(value);
|
|
|
|
|
+ if (!s) return '';
|
|
|
|
|
+ return OutboundDomainStrategySchema.options.find(
|
|
|
|
|
+ (v) => v.toLowerCase() === s.toLowerCase(),
|
|
|
|
|
+ ) ?? '';
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
const SNIFFING_DEST_VALUES: readonly SniffingDest[] = ['http', 'tls', 'quic', 'fakedns'];
|
|
const SNIFFING_DEST_VALUES: readonly SniffingDest[] = ['http', 'tls', 'quic', 'fakedns'];
|
|
|
|
|
|
|
|
const SNIFFING_DEFAULT: Sniffing = {
|
|
const SNIFFING_DEFAULT: Sniffing = {
|
|
@@ -285,14 +297,9 @@ function freedomFromWire(raw: Raw): FreedomOutboundFormSettings {
|
|
|
&& typeof raw.fragment === 'object'
|
|
&& typeof raw.fragment === 'object'
|
|
|
&& Object.keys(fragment).length > 0;
|
|
&& Object.keys(fragment).length > 0;
|
|
|
return {
|
|
return {
|
|
|
- domainStrategy: ((): FreedomOutboundFormSettings['domainStrategy'] => {
|
|
|
|
|
- const allowed = [
|
|
|
|
|
- 'AsIs', 'UseIP', 'UseIPv4', 'UseIPv6', 'UseIPv6v4', 'UseIPv4v6',
|
|
|
|
|
- 'ForceIP', 'ForceIPv6v4', 'ForceIPv6', 'ForceIPv4v6', 'ForceIPv4',
|
|
|
|
|
- ];
|
|
|
|
|
- const s = asString(raw.domainStrategy);
|
|
|
|
|
- return (allowed.includes(s) ? s : '') as FreedomOutboundFormSettings['domainStrategy'];
|
|
|
|
|
- })(),
|
|
|
|
|
|
|
+ domainStrategy: targetStrategyFromWire(
|
|
|
|
|
+ asString(raw.targetStrategy) || asString(raw.domainStrategy),
|
|
|
|
|
+ ),
|
|
|
redirect: asString(raw.redirect),
|
|
redirect: asString(raw.redirect),
|
|
|
userLevel: asNumber(raw.userLevel, 0),
|
|
userLevel: asNumber(raw.userLevel, 0),
|
|
|
proxyProtocol: ((): FreedomOutboundFormSettings['proxyProtocol'] => {
|
|
proxyProtocol: ((): FreedomOutboundFormSettings['proxyProtocol'] => {
|
|
@@ -374,6 +381,7 @@ export interface RawOutboundRow {
|
|
|
tag?: string;
|
|
tag?: string;
|
|
|
protocol?: string;
|
|
protocol?: string;
|
|
|
sendThrough?: string;
|
|
sendThrough?: string;
|
|
|
|
|
+ targetStrategy?: string;
|
|
|
settings?: unknown;
|
|
settings?: unknown;
|
|
|
streamSettings?: unknown;
|
|
streamSettings?: unknown;
|
|
|
mux?: unknown;
|
|
mux?: unknown;
|
|
@@ -401,6 +409,7 @@ export function rawOutboundToFormValues(raw: RawOutboundRow): OutboundFormValues
|
|
|
const settings = asObject(raw.settings);
|
|
const settings = asObject(raw.settings);
|
|
|
const tag = asString(raw.tag);
|
|
const tag = asString(raw.tag);
|
|
|
const sendThrough = asString(raw.sendThrough);
|
|
const sendThrough = asString(raw.sendThrough);
|
|
|
|
|
+ const targetStrategy = targetStrategyFromWire(raw.targetStrategy);
|
|
|
const mux = muxFromWire(raw.mux);
|
|
const mux = muxFromWire(raw.mux);
|
|
|
const hasStream = raw.streamSettings
|
|
const hasStream = raw.streamSettings
|
|
|
&& typeof raw.streamSettings === 'object'
|
|
&& typeof raw.streamSettings === 'object'
|
|
@@ -430,6 +439,7 @@ export function rawOutboundToFormValues(raw: RawOutboundRow): OutboundFormValues
|
|
|
...typed,
|
|
...typed,
|
|
|
tag,
|
|
tag,
|
|
|
sendThrough,
|
|
sendThrough,
|
|
|
|
|
+ targetStrategy,
|
|
|
mux,
|
|
mux,
|
|
|
streamSettings,
|
|
streamSettings,
|
|
|
};
|
|
};
|
|
@@ -543,6 +553,8 @@ function hysteriaToWire(s: HysteriaOutboundFormSettings) {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
function freedomToWire(s: FreedomOutboundFormSettings) {
|
|
function freedomToWire(s: FreedomOutboundFormSettings) {
|
|
|
|
|
+ // The strategy is emitted under the legacy domainStrategy key: new cores
|
|
|
|
|
+ // fall back to it when targetStrategy is absent, old cores only know it.
|
|
|
// Legacy semantics: emit fragment only when the user actually populated
|
|
// Legacy semantics: emit fragment only when the user actually populated
|
|
|
// at least one of the four sub-fields. Defaults like packets='1-3' alone
|
|
// at least one of the four sub-fields. Defaults like packets='1-3' alone
|
|
|
// are not enough — the modal's Fragment Switch sets all four together.
|
|
// are not enough — the modal's Fragment Switch sets all four together.
|
|
@@ -672,6 +684,7 @@ export function formValuesToWirePayload(values: OutboundFormValues): WireOutboun
|
|
|
settings,
|
|
settings,
|
|
|
};
|
|
};
|
|
|
if (values.tag) result.tag = values.tag;
|
|
if (values.tag) result.tag = values.tag;
|
|
|
|
|
+ if (values.targetStrategy) result.targetStrategy = values.targetStrategy;
|
|
|
|
|
|
|
|
// streamSettings emission gates on canEnableStream — non-stream protocols
|
|
// streamSettings emission gates on canEnableStream — non-stream protocols
|
|
|
// still emit just `sockopt` if that key is present (legacy behavior).
|
|
// still emit just `sockopt` if that key is present (legacy behavior).
|