From 980c02328761d2a659ab3c7cc6806772b8afeb99 Mon Sep 17 00:00:00 2001 From: MHSanaei Date: Wed, 15 Jul 2026 05:54:01 +0200 Subject: [PATCH] fix(frontend): tolerate a malformed happyEyeballs value in the Xray Basics tab MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit BasicsTab derived directHappyEyeballs by calling HappyEyeballsSchema.parse during render, guarding only against null/non-object. A wrong-typed field (e.g. happyEyeballs.tryDelayMs as a string) or any other shape mismatch — reachable via the Complete Template JSON editor or an imported config — threw straight out of render, white-screening the default Xray landing tab. Use safeParse and fall back to null so a bad value degrades to "no override" instead of crashing the page. --- frontend/src/pages/xray/basics/BasicsTab.tsx | 3 +- .../src/test/basics-happy-eyeballs.test.tsx | 36 +++++++++++++++++++ 2 files changed, 38 insertions(+), 1 deletion(-) create mode 100644 frontend/src/test/basics-happy-eyeballs.test.tsx diff --git a/frontend/src/pages/xray/basics/BasicsTab.tsx b/frontend/src/pages/xray/basics/BasicsTab.tsx index 115f031ab..d29a67f79 100644 --- a/frontend/src/pages/xray/basics/BasicsTab.tsx +++ b/frontend/src/pages/xray/basics/BasicsTab.tsx @@ -115,7 +115,8 @@ export default function BasicsTab({ ?.sockopt; const raw = sockopt?.happyEyeballs; if (raw == null || typeof raw !== 'object') return null; - return HappyEyeballsSchema.parse(raw); + const parsed = HappyEyeballsSchema.safeParse(raw); + return parsed.success ? parsed.data : null; })(); const setDirectHappyEyeballs = useCallback( diff --git a/frontend/src/test/basics-happy-eyeballs.test.tsx b/frontend/src/test/basics-happy-eyeballs.test.tsx new file mode 100644 index 000000000..76129b115 --- /dev/null +++ b/frontend/src/test/basics-happy-eyeballs.test.tsx @@ -0,0 +1,36 @@ +import { describe, it, expect, vi } from 'vitest'; + +import BasicsTab from '@/pages/xray/basics/BasicsTab'; +import type { XraySettingsValue } from '@/hooks/useXraySetting'; + +import { renderWithProviders } from './test-utils'; + +function settingsWithMalformedHappyEyeballs(): XraySettingsValue { + return { + outbounds: [ + { + protocol: 'freedom', + tag: 'direct', + streamSettings: { sockopt: { happyEyeballs: { tryDelayMs: 'fast' } } }, + }, + ], + } as unknown as XraySettingsValue; +} + +describe('BasicsTab malformed happyEyeballs', () => { + it('renders instead of white-screening on a wrong-typed happyEyeballs value', () => { + const errorSpy = vi.spyOn(console, 'error').mockImplementation(() => {}); + expect(() => + renderWithProviders( + , + ), + ).not.toThrow(); + errorSpy.mockRestore(); + }); +});