- Delete the xray settings page (routes/menu/title), geodata UI/service, XraySettingController/Service and routing-sync write paths; the stored xrayTemplateConfig row loses its last readers (GetXrayConfigTemplate now returns the embedded factory template; B1 already deleted the row). - Settings GeneralTab gains the four trafficStatsSwitches switches (settings.trafficStatsSwitches, B1) with the dual warning copy; entity AllSetting carries the key end to end (TestTrafficStatsSwitchesPlumbing). - Tag-directory consumers (panelOutbound, mtproto egress, node/host outbound pickers) move from POST /xray/ to GET /server/outboundDirectory. - Shared Mux/Sockopt forms and balancer helpers relocated to lib/xray; converter preserves sniffing ips/domains exclusions into the panel sidecar instead of dropping them (B2 gap closed by retention). - Freedom final-rules DB fix seeders retired; getDefaultJsonConfig endpoint removed (no in-repo consumer); locales pruned to referenced keys (pages.xray 331 -> 68) plus the five new settings keys.
126 lines
4.0 KiB
TypeScript
126 lines
4.0 KiB
TypeScript
import { rawOutboundToFormValues } from '@/lib/xray/outbound-form-adapter';
|
|
import { canEnableReality, canEnableTls } from '@/lib/xray/protocol-capabilities';
|
|
import type { OutboundFormValues } from '@/schemas/forms/outbound-form';
|
|
|
|
import { MUX_PROTOCOLS } from './outbound-form-constants';
|
|
|
|
export function isMuxAllowed(protocol: string, flow: string, network: string): boolean {
|
|
if (!MUX_PROTOCOLS.has(protocol)) return false;
|
|
if (protocol === 'vless' && flow) return false;
|
|
if (network === 'xhttp') return false;
|
|
return true;
|
|
}
|
|
|
|
// Per-network bootstrap. Mirrors the legacy class constructors so the
|
|
// initial state for each transport matches what xray-core expects.
|
|
export function newStreamSlice(network: string): Record<string, unknown> {
|
|
switch (network) {
|
|
case 'tcp':
|
|
return { network: 'tcp', tcpSettings: { header: { type: 'none' } } };
|
|
case 'kcp':
|
|
return {
|
|
network: 'kcp',
|
|
kcpSettings: {
|
|
mtu: 1350,
|
|
tti: 20,
|
|
uplinkCapacity: 5,
|
|
downlinkCapacity: 20,
|
|
cwndMultiplier: 1,
|
|
maxSendingWindow: 2097152,
|
|
},
|
|
};
|
|
case 'ws':
|
|
return {
|
|
network: 'ws',
|
|
wsSettings: { path: '/', host: '', headers: {}, heartbeatPeriod: 0 },
|
|
};
|
|
case 'grpc':
|
|
return {
|
|
network: 'grpc',
|
|
grpcSettings: { serviceName: '', authority: '', multiMode: false },
|
|
};
|
|
case 'httpupgrade':
|
|
return {
|
|
network: 'httpupgrade',
|
|
httpupgradeSettings: { path: '/', host: '', headers: {} },
|
|
};
|
|
case 'xhttp':
|
|
return {
|
|
network: 'xhttp',
|
|
xhttpSettings: {
|
|
path: '/',
|
|
host: '',
|
|
mode: '',
|
|
headers: [],
|
|
xPaddingBytes: '100-1000',
|
|
},
|
|
};
|
|
case 'hysteria':
|
|
return {
|
|
network: 'hysteria',
|
|
hysteriaSettings: {
|
|
version: 2,
|
|
auth: '',
|
|
udpIdleTimeout: 60,
|
|
},
|
|
};
|
|
default:
|
|
return { network: 'tcp', tcpSettings: { header: { type: 'none' } } };
|
|
}
|
|
}
|
|
|
|
// Hysteria2 always rides its own QUIC transport with TLS — the panel never
|
|
// offers another transport or 'none' security for it.
|
|
export function hysteriaStreamSlice(): Record<string, unknown> {
|
|
return {
|
|
...newStreamSlice('hysteria'),
|
|
security: 'tls',
|
|
tlsSettings: {
|
|
serverName: '',
|
|
alpn: ['h3'],
|
|
fingerprint: '',
|
|
echConfigList: '',
|
|
verifyPeerCertByName: '',
|
|
pinnedPeerCertSha256: '',
|
|
},
|
|
};
|
|
}
|
|
|
|
// Network change cascade: swap the per-network sub-key (tcpSettings,
|
|
// wsSettings, etc.) so the DU branch matches. Carry over the security mode
|
|
// and its settings (tlsSettings/realitySettings, including SNI serverName)
|
|
// when the new network still supports it; otherwise fall back to 'none'.
|
|
// Dropping tlsSettings here silently wiped the spoofed SNI on save (#4791).
|
|
export function applyNetworkChange(
|
|
protocol: string,
|
|
prevStream: Record<string, unknown> | undefined,
|
|
next: string,
|
|
): Record<string, unknown> {
|
|
if (next === 'hysteria') return hysteriaStreamSlice();
|
|
const stream = prevStream ?? {};
|
|
const currentSecurity = (stream.security as string) ?? 'none';
|
|
const stillTls = canEnableTls({
|
|
protocol,
|
|
streamSettings: { network: next, security: currentSecurity },
|
|
});
|
|
const stillReality = canEnableReality({
|
|
protocol,
|
|
streamSettings: { network: next, security: currentSecurity },
|
|
});
|
|
const newSecurity =
|
|
currentSecurity === 'tls' && !stillTls
|
|
? 'none'
|
|
: currentSecurity === 'reality' && !stillReality
|
|
? 'none'
|
|
: currentSecurity;
|
|
const newStream: Record<string, unknown> = { ...newStreamSlice(next), security: newSecurity };
|
|
if (newSecurity === 'tls' && stream.tlsSettings) newStream.tlsSettings = stream.tlsSettings;
|
|
else if (newSecurity === 'reality' && stream.realitySettings)
|
|
newStream.realitySettings = stream.realitySettings;
|
|
return newStream;
|
|
}
|
|
|
|
export function buildAddModeValues(): OutboundFormValues {
|
|
return rawOutboundToFormValues({});
|
|
}
|