mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-08-12 06:10:58 +00:00
fix(xhttp): stop XMUX maxConcurrency from reverting on save
XHttpXmuxSchema defaulted maxConnections to 6 (added to mirror xray-core v26.6.27's anti-RKN client default), so load-time hydration backfilled a non-zero maxConnections onto every config whose saved xmux lacked the key. Since maxConnections and maxConcurrency are mutually exclusive on the wire, the save-time exclusivity rule then saw both fields set and silently deleted the user's maxConcurrency; the missing key came back as the '16-32' schema default on the next load, so edits appeared to never save. Revert the bare schema default to 0 and seed the anti-RKN maxConnections=6 only when XMUX is freshly toggled on (XMUX_FRESH_DEFAULTS, with maxConcurrency left blank — xray-core parses an empty range string as 0), so the two strategies never start out conflicting. The inbound and outbound XMUX forms now also clear the opposing field live as soon as the user sets one, so whichever strategy was actually typed is the one persisted. Closes #5864
This commit is contained in:
@@ -4,10 +4,9 @@ import { useFormContext, useWatch } from 'react-hook-form';
|
||||
|
||||
import { HeaderMapEditor } from '@/components/form';
|
||||
import { FormField } from '@/components/form/rhf';
|
||||
import { XHTTP_SESSION_ID_TABLES, XHttpXmuxSchema } from '@/schemas/protocols/stream/xhttp';
|
||||
import { XHTTP_SESSION_ID_TABLES, XMUX_FRESH_DEFAULTS } from '@/schemas/protocols/stream/xhttp';
|
||||
import { validateSessionIDLength, validateSessionIDTable } from '@/lib/xray/xhttp-session-id';
|
||||
|
||||
const XMUX_DEFAULTS = XHttpXmuxSchema.parse({});
|
||||
import { int32RangeUpper } from '@/lib/xray/stream-wire-normalize';
|
||||
|
||||
function antdValidatorToRhf(fn: (rule: unknown, value: unknown) => Promise<void>) {
|
||||
return async (value: unknown): Promise<true | string> => {
|
||||
@@ -36,7 +35,21 @@ export default function XhttpForm() {
|
||||
const existing = getValues('streamSettings.xhttpSettings.xmux');
|
||||
const hasValues = existing && typeof existing === 'object' && Object.keys(existing).length > 0;
|
||||
if (hasValues) return;
|
||||
setValue('streamSettings.xhttpSettings.xmux', { ...XMUX_DEFAULTS });
|
||||
setValue('streamSettings.xhttpSettings.xmux', { ...XMUX_FRESH_DEFAULTS });
|
||||
}
|
||||
|
||||
function onXmuxMaxConcurrencyChange(value: unknown) {
|
||||
if (int32RangeUpper(value) <= 0) return;
|
||||
if (int32RangeUpper(getValues('streamSettings.xhttpSettings.xmux.maxConnections')) > 0) {
|
||||
setValue('streamSettings.xhttpSettings.xmux.maxConnections', 0);
|
||||
}
|
||||
}
|
||||
|
||||
function onXmuxMaxConnectionsChange(value: unknown) {
|
||||
if (int32RangeUpper(value) <= 0) return;
|
||||
if (int32RangeUpper(getValues('streamSettings.xhttpSettings.xmux.maxConcurrency')) > 0) {
|
||||
setValue('streamSettings.xhttpSettings.xmux.maxConcurrency', '');
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -295,12 +308,14 @@ export default function XhttpForm() {
|
||||
<FormField
|
||||
label={t('pages.xray.outboundForm.maxConcurrency')}
|
||||
name={['streamSettings', 'xhttpSettings', 'xmux', 'maxConcurrency']}
|
||||
onAfterChange={onXmuxMaxConcurrencyChange}
|
||||
>
|
||||
<Input placeholder="16-32" />
|
||||
</FormField>
|
||||
<FormField
|
||||
label={t('pages.xray.outboundForm.maxConnections')}
|
||||
name={['streamSettings', 'xhttpSettings', 'xmux', 'maxConnections']}
|
||||
onAfterChange={onXmuxMaxConnectionsChange}
|
||||
>
|
||||
<Input placeholder="0" />
|
||||
</FormField>
|
||||
|
||||
@@ -17,11 +17,11 @@ import { FormField, rhfZodValidate } from '@/components/form/rhf';
|
||||
import { JsonEditor } from '@/components/form';
|
||||
import { Wireguard } from '@/utils';
|
||||
import {
|
||||
XMUX_DEFAULTS,
|
||||
formValuesToWirePayload,
|
||||
rawOutboundToFormValues,
|
||||
} from '@/lib/xray/outbound-form-adapter';
|
||||
import { parseOutboundLink } from '@/lib/xray/outbound-link-parser';
|
||||
import { XMUX_FRESH_DEFAULTS } from '@/schemas/protocols/stream/xhttp';
|
||||
import {
|
||||
OutboundFormBaseSchema,
|
||||
type OutboundFormValues,
|
||||
@@ -255,7 +255,7 @@ export default function OutboundFormModal({
|
||||
const existing = methods.getValues('streamSettings.xhttpSettings.xmux');
|
||||
const hasValues = existing && typeof existing === 'object' && Object.keys(existing).length > 0;
|
||||
if (hasValues) return;
|
||||
methods.setValue('streamSettings.xhttpSettings.xmux', { ...XMUX_DEFAULTS });
|
||||
methods.setValue('streamSettings.xhttpSettings.xmux', { ...XMUX_FRESH_DEFAULTS });
|
||||
}
|
||||
|
||||
const duplicateTag = useMemo(() => {
|
||||
|
||||
@@ -5,6 +5,7 @@ import { useFormContext, useWatch } from 'react-hook-form';
|
||||
import { HeaderMapEditor } from '@/components/form';
|
||||
import { FormField } from '@/components/form/rhf';
|
||||
import { validateSessionIDLength, validateSessionIDTable } from '@/lib/xray/xhttp-session-id';
|
||||
import { int32RangeUpper } from '@/lib/xray/stream-wire-normalize';
|
||||
import { XHTTP_SESSION_ID_TABLES } from '@/schemas/protocols/stream/xhttp';
|
||||
|
||||
import { MODE_OPTIONS } from '../outbound-form-constants';
|
||||
@@ -28,7 +29,7 @@ const XH = 'streamSettings.xhttpSettings';
|
||||
|
||||
export default function XhttpForm({ onXmuxToggle }: XhttpFormProps) {
|
||||
const { t } = useTranslation();
|
||||
const { control } = useFormContext();
|
||||
const { control, getValues, setValue } = useFormContext();
|
||||
const mode = useWatch({ control, name: `${XH}.mode` }) as string | undefined;
|
||||
const obfs = !!useWatch({ control, name: `${XH}.xPaddingObfsMode` });
|
||||
const sessionPlacement = useWatch({ control, name: `${XH}.sessionIDPlacement` }) as string | undefined;
|
||||
@@ -37,6 +38,20 @@ export default function XhttpForm({ onXmuxToggle }: XhttpFormProps) {
|
||||
const uplinkDataPlacement = useWatch({ control, name: `${XH}.uplinkDataPlacement` }) as string | undefined;
|
||||
const enableXmux = !!useWatch({ control, name: `${XH}.enableXmux` });
|
||||
|
||||
function onXmuxMaxConcurrencyChange(value: unknown) {
|
||||
if (int32RangeUpper(value) <= 0) return;
|
||||
if (int32RangeUpper(getValues(`${XH}.xmux.maxConnections`)) > 0) {
|
||||
setValue(`${XH}.xmux.maxConnections`, 0);
|
||||
}
|
||||
}
|
||||
|
||||
function onXmuxMaxConnectionsChange(value: unknown) {
|
||||
if (int32RangeUpper(value) <= 0) return;
|
||||
if (int32RangeUpper(getValues(`${XH}.xmux.maxConcurrency`)) > 0) {
|
||||
setValue(`${XH}.xmux.maxConcurrency`, '');
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<FormField label={t('host')} name={['streamSettings', 'xhttpSettings', 'host']}>
|
||||
@@ -283,12 +298,14 @@ export default function XhttpForm({ onXmuxToggle }: XhttpFormProps) {
|
||||
<FormField
|
||||
label={t('pages.xray.outboundForm.maxConcurrency')}
|
||||
name={['streamSettings', 'xhttpSettings', 'xmux', 'maxConcurrency']}
|
||||
onAfterChange={onXmuxMaxConcurrencyChange}
|
||||
>
|
||||
<Input placeholder="16-32" />
|
||||
</FormField>
|
||||
<FormField
|
||||
label={t('pages.xray.outboundForm.maxConnections')}
|
||||
name={['streamSettings', 'xhttpSettings', 'xmux', 'maxConnections']}
|
||||
onAfterChange={onXmuxMaxConnectionsChange}
|
||||
>
|
||||
<Input placeholder="0" />
|
||||
</FormField>
|
||||
|
||||
Reference in New Issue
Block a user