mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-08-23 11:27:13 +00:00
fix(outbound): parse xmux from imported share links (#5353)
The inbound link generator bundles xmux and downloadSettings as nested objects inside the `extra=` JSON blob, but the outbound link parser only pulled scalar fields and headers from it, silently dropping xmux on import. Extract the nested objects too so they round-trip into the outbound XMUX sub-form.
This commit is contained in:
@@ -9,8 +9,9 @@ import { Base64 } from '@/utils';
|
|||||||
// fields the common vmess:// / vless:// links carry as query params.
|
// fields the common vmess:// / vless:// links carry as query params.
|
||||||
// XHTTP advanced fields (xPaddingBytes, scMaxEachPostBytes,
|
// XHTTP advanced fields (xPaddingBytes, scMaxEachPostBytes,
|
||||||
// scMinPostsIntervalMs, uplinkChunkSize, noGRPCHeader) round-trip when
|
// scMinPostsIntervalMs, uplinkChunkSize, noGRPCHeader) round-trip when
|
||||||
// present in either the JSON or URL params. xmux, reality shortIds,
|
// present in either the JSON or URL params. xmux and downloadSettings
|
||||||
// padding obfs key/header/placement, hysteria udphop are still left
|
// round-trip through the `extra` JSON blob. reality shortIds, padding
|
||||||
|
// obfs key/header/placement, hysteria udphop are still left
|
||||||
// to the user to fill in after import — the legacy Outbound.fromLink
|
// to the user to fill in after import — the legacy Outbound.fromLink
|
||||||
// was ~250 lines of dense edge-case handling we don't need to
|
// was ~250 lines of dense edge-case handling we don't need to
|
||||||
// replicate verbatim for the common phone-to-panel workflow.
|
// replicate verbatim for the common phone-to-panel workflow.
|
||||||
@@ -33,6 +34,10 @@ const XHTTP_NUMBER_KEYS = [
|
|||||||
const XHTTP_BOOL_KEYS = [
|
const XHTTP_BOOL_KEYS = [
|
||||||
'xPaddingObfsMode', 'noSSEHeader', 'noGRPCHeader',
|
'xPaddingObfsMode', 'noSSEHeader', 'noGRPCHeader',
|
||||||
] as const;
|
] as const;
|
||||||
|
// Nested objects the inbound link bundles into the `extra` JSON blob
|
||||||
|
// (and vmess JSON carries inline). The outbound form adapter expands
|
||||||
|
// xmux into the XMUX sub-form (enableXmux) on load.
|
||||||
|
const XHTTP_OBJECT_KEYS = ['xmux', 'downloadSettings'] as const;
|
||||||
|
|
||||||
function asBool(s: string | null): boolean | undefined {
|
function asBool(s: string | null): boolean | undefined {
|
||||||
if (s === null) return undefined;
|
if (s === null) return undefined;
|
||||||
@@ -88,6 +93,10 @@ function applyXhttpStringFromJson(xhttp: Raw, json: Record<string, unknown>): vo
|
|||||||
for (const k of XHTTP_BOOL_KEYS) {
|
for (const k of XHTTP_BOOL_KEYS) {
|
||||||
if (typeof json[k] === 'boolean') xhttp[k] = json[k];
|
if (typeof json[k] === 'boolean') xhttp[k] = json[k];
|
||||||
}
|
}
|
||||||
|
for (const k of XHTTP_OBJECT_KEYS) {
|
||||||
|
const v = json[k];
|
||||||
|
if (v && typeof v === 'object' && !Array.isArray(v)) xhttp[k] = v;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildStream(network: string, security: string): Raw {
|
function buildStream(network: string, security: string): Raw {
|
||||||
|
|||||||
@@ -392,6 +392,23 @@ describe('parseVlessLink — extra / fm / x_padding_bytes (B20)', () => {
|
|||||||
expect(xhttp.xPaddingBytes).toBe('900-9000');
|
expect(xhttp.xPaddingBytes).toBe('900-9000');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('extracts the nested xmux object from the extra JSON blob', () => {
|
||||||
|
// The inbound link bundles xmux into `extra` as a nested object
|
||||||
|
// (sub/service.go). It must survive import so the outbound form's
|
||||||
|
// XMUX sub-form populates rather than silently dropping it (#5353).
|
||||||
|
const extra = encodeURIComponent(JSON.stringify({
|
||||||
|
xmux: { maxConcurrency: '8-16', hMaxRequestTimes: '700-1000' },
|
||||||
|
}));
|
||||||
|
const link = 'vless://u@h:1?type=xhttp&security=none&path=%2F&host=&mode=auto'
|
||||||
|
+ '&extra=' + extra + '#t';
|
||||||
|
const parsed = parseVlessLink(link);
|
||||||
|
const xhttp = (parsed!.streamSettings as Record<string, unknown>).xhttpSettings as Record<string, unknown>;
|
||||||
|
const xmux = xhttp.xmux as Record<string, unknown>;
|
||||||
|
expect(xmux).toBeDefined();
|
||||||
|
expect(xmux.maxConcurrency).toBe('8-16');
|
||||||
|
expect(xmux.hMaxRequestTimes).toBe('700-1000');
|
||||||
|
});
|
||||||
|
|
||||||
it('ignores malformed extra JSON without breaking the rest of the link', () => {
|
it('ignores malformed extra JSON without breaking the rest of the link', () => {
|
||||||
const link = 'vless://u@h:1?type=xhttp&security=none&path=%2F&host=&mode=auto'
|
const link = 'vless://u@h:1?type=xhttp&security=none&path=%2F&host=&mode=auto'
|
||||||
+ '&extra=not-json&fp=chrome#t';
|
+ '&extra=not-json&fp=chrome#t';
|
||||||
|
|||||||
Reference in New Issue
Block a user