Jelajahi Sumber

fix(frontend): derive QUIC/UDP-hop switch state from data presence (B22)

The QUIC Params and UDP Hop toggles previously persisted as separate
boolean flags (enableQuicParams / hasUdpHop) which weren't part of the
xray wire format and weren't restored when a config was pasted into the
modal. Use data presence as the single source of truth: the switch is
on iff the corresponding sub-object exists. Switching off clears it
back to undefined.
MHSanaei 22 jam lalu
induk
melakukan
6e90b24af1
1 mengubah file dengan 19 tambahan dan 17 penghapusan
  1. 19 17
      frontend/src/components/FinalMaskForm.tsx

+ 19 - 17
frontend/src/components/FinalMaskForm.tsx

@@ -80,18 +80,11 @@ function defaultNoiseItem(): Record<string, unknown> {
 }
 }
 
 
 function defaultQuicParams(): Record<string, unknown> {
 function defaultQuicParams(): Record<string, unknown> {
-  // Seeded with the xray-core / hysteria recommended defaults so the QUIC
-  // Params sub-form doesn't show blank InputNumber fields when first
-  // enabled. The schema declares these as .optional() (no Zod default)
-  // because the wire shape omits them when xray's built-in default
-  // applies — but the panel needs values to render the controls.
   return {
   return {
     congestion: 'bbr',
     congestion: 'bbr',
     debug: false,
     debug: false,
     brutalUp: 0,
     brutalUp: 0,
     brutalDown: 0,
     brutalDown: 0,
-    hasUdpHop: false,
-    udpHop: { ports: '20000-50000', interval: '5-10' },
     maxIdleTimeout: 30,
     maxIdleTimeout: 30,
     keepAlivePeriod: 10,
     keepAlivePeriod: 10,
     disablePathMTUDiscovery: false,
     disablePathMTUDiscovery: false,
@@ -103,13 +96,18 @@ function defaultQuicParams(): Record<string, unknown> {
   };
   };
 }
 }
 
 
+function defaultUdpHop(): Record<string, unknown> {
+  return { ports: '20000-50000', interval: '5-10' };
+}
+
 export default function FinalMaskForm({ name, network, protocol, form }: FinalMaskFormProps) {
 export default function FinalMaskForm({ name, network, protocol, form }: FinalMaskFormProps) {
   const base = asPath(name);
   const base = asPath(name);
   const isHysteria = protocol === OutboundProtocols.Hysteria || protocol === 'hysteria';
   const isHysteria = protocol === OutboundProtocols.Hysteria || protocol === 'hysteria';
   const showTcp = TCP_NETWORKS.includes(network);
   const showTcp = TCP_NETWORKS.includes(network);
   const showUdp = isHysteria || network === 'kcp';
   const showUdp = isHysteria || network === 'kcp';
   const showQuic = isHysteria || network === 'xhttp';
   const showQuic = isHysteria || network === 'xhttp';
-  const enableQuic = Form.useWatch([...base, 'enableQuicParams'], form);
+  const quicParams = Form.useWatch([...base, 'quicParams'], { form, preserve: true });
+  const hasQuicParams = quicParams != null;
 
 
   if (!showTcp && !showUdp && !showQuic) return null;
   if (!showTcp && !showUdp && !showQuic) return null;
 
 
@@ -119,17 +117,15 @@ export default function FinalMaskForm({ name, network, protocol, form }: FinalMa
       {showUdp && <UdpMasksList base={base} form={form} isHysteria={isHysteria} network={network} />}
       {showUdp && <UdpMasksList base={base} form={form} isHysteria={isHysteria} network={network} />}
       {showQuic && (
       {showQuic && (
         <>
         <>
-          <Form.Item label="QUIC Params" name={[...base, 'enableQuicParams']} valuePropName="checked">
+          <Form.Item label="QUIC Params">
             <Switch
             <Switch
+              checked={hasQuicParams}
               onChange={(v) => {
               onChange={(v) => {
-                if (v) {
-                  const current = form.getFieldValue([...base, 'quicParams']);
-                  if (!current) form.setFieldValue([...base, 'quicParams'], defaultQuicParams());
-                }
+                form.setFieldValue([...base, 'quicParams'], v ? defaultQuicParams() : undefined);
               }}
               }}
             />
             />
           </Form.Item>
           </Form.Item>
-          {enableQuic && <QuicParamsForm base={[...base, 'quicParams']} form={form} />}
+          {hasQuicParams && <QuicParamsForm base={[...base, 'quicParams']} form={form} />}
         </>
         </>
       )}
       )}
     </>
     </>
@@ -669,7 +665,8 @@ function ItemEditor({
 
 
 function QuicParamsForm({ base, form }: { base: (string | number)[]; form: FormInstance }) {
 function QuicParamsForm({ base, form }: { base: (string | number)[]; form: FormInstance }) {
   const congestion = Form.useWatch([...base, 'congestion'], form) as string | undefined;
   const congestion = Form.useWatch([...base, 'congestion'], form) as string | undefined;
-  const hasUdpHop = Form.useWatch([...base, 'hasUdpHop'], form) as boolean | undefined;
+  const udpHop = Form.useWatch([...base, 'udpHop'], { form, preserve: true }) as Record<string, unknown> | undefined;
+  const hasUdpHop = udpHop != null;
 
 
   return (
   return (
     <>
     <>
@@ -698,8 +695,13 @@ function QuicParamsForm({ base, form }: { base: (string | number)[]; form: FormI
         </>
         </>
       )}
       )}
 
 
-      <Form.Item label="UDP Hop" name={[...base, 'hasUdpHop']} valuePropName="checked">
-        <Switch />
+      <Form.Item label="UDP Hop">
+        <Switch
+          checked={hasUdpHop}
+          onChange={(v) => {
+            form.setFieldValue([...base, 'udpHop'], v ? defaultUdpHop() : undefined);
+          }}
+        />
       </Form.Item>
       </Form.Item>
       {hasUdpHop && (
       {hasUdpHop && (
         <>
         <>