From 579a9daaa03e101c7cce985e5e11bc21a97c628a Mon Sep 17 00:00:00 2001 From: MHSanaei Date: Sun, 5 Jul 2026 20:12:34 +0200 Subject: [PATCH] fix(ui): make the Happy Eyeballs toggle produce a config xray actually enables Toggling Happy Eyeballs on filled the object with schema defaults, and tryDelayMs defaulted to 0. That broke the feature twice over: xray-core treats tryDelayMs=0 as happy-eyeballs-off, and the wire normalizer strips every field that equals its default, leaving an empty object it then deletes - so the switch silently flipped back off on reopen (the "disabled when Prefer IPv6 is off" symptom; prioritizeIPv6=true was the one non-default that let the object survive). Default tryDelayMs to the recommended 250ms so an enabled config survives serialization and is functional in the core. Closes #5780 --- frontend/src/schemas/protocols/stream/sockopt.ts | 2 +- frontend/src/test/stream-wire-normalize.test.ts | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/frontend/src/schemas/protocols/stream/sockopt.ts b/frontend/src/schemas/protocols/stream/sockopt.ts index 877b776ab..27df9acb7 100644 --- a/frontend/src/schemas/protocols/stream/sockopt.ts +++ b/frontend/src/schemas/protocols/stream/sockopt.ts @@ -33,7 +33,7 @@ export const AddressPortStrategySchema = z.enum([ export type AddressPortStrategy = z.infer; export const HappyEyeballsSchema = z.object({ - tryDelayMs: z.number().int().min(0).default(0), + tryDelayMs: z.number().int().min(0).default(250), prioritizeIPv6: z.boolean().default(false), interleave: z.number().int().min(1).default(1), maxConcurrentTry: z.number().int().min(0).default(4), diff --git a/frontend/src/test/stream-wire-normalize.test.ts b/frontend/src/test/stream-wire-normalize.test.ts index 81c5db658..cac527736 100644 --- a/frontend/src/test/stream-wire-normalize.test.ts +++ b/frontend/src/test/stream-wire-normalize.test.ts @@ -10,6 +10,7 @@ import { validateRealityTarget, } from '@/lib/xray/stream-wire-normalize'; import { InboundFormSchema } from '@/schemas/forms/inbound-form'; +import { HappyEyeballsSchema } from '@/schemas/protocols/stream/sockopt'; import type { InboundFormValues } from '@/schemas/forms/inbound-form'; import { XHttpXmuxSchema } from '@/schemas/protocols/stream/xhttp'; @@ -204,6 +205,14 @@ describe('normalizeSockoptForWire', () => { }); expect(out?.domainStrategy).toBe('UseIP'); }); + + it('keeps a freshly toggled happyEyeballs (schema defaults) across the wire round trip', () => { + const out = normalizeSockoptForWire({ + happyEyeballs: HappyEyeballsSchema.parse({}), + }); + + expect(out?.happyEyeballs).toEqual({ tryDelayMs: 250 }); + }); }); describe('normalizeStreamSettingsForWire reality', () => {