diff --git a/frontend/src/components/FinalMaskForm.tsx b/frontend/src/components/FinalMaskForm.tsx index e43b689e..91671515 100644 --- a/frontend/src/components/FinalMaskForm.tsx +++ b/frontend/src/components/FinalMaskForm.tsx @@ -91,7 +91,7 @@ function defaultQuicParams(): Record { brutalUp: 0, brutalDown: 0, hasUdpHop: false, - udpHop: { ports: '20000-50000', interval: 5 }, + udpHop: { ports: '20000-50000', interval: '5-10' }, maxIdleTimeout: 30, keepAlivePeriod: 10, disablePathMTUDiscovery: false, @@ -707,7 +707,7 @@ function QuicParamsForm({ base, form }: { base: (string | number)[]; form: FormI - + )} diff --git a/frontend/src/schemas/protocols/stream/finalmask.ts b/frontend/src/schemas/protocols/stream/finalmask.ts index 79326978..83d06808 100644 --- a/frontend/src/schemas/protocols/stream/finalmask.ts +++ b/frontend/src/schemas/protocols/stream/finalmask.ts @@ -47,10 +47,16 @@ export type QuicCongestion = z.infer; // udpHop randomizes the QUIC port between a range every `interval` seconds // to dodge port-based blocking. Both fields are dash-range strings on the -// wire (e.g. '20000-50000', '5-10'). +// wire (e.g. '20000-50000', '5-10'). preprocess coerces legacy DB rows +// where interval was stored as a number (UI bug — see B19 in commit history). +const StringRangeSchema = z.preprocess( + (v) => (typeof v === 'number' ? String(v) : v), + z.string(), +); + export const QuicUdpHopSchema = z.object({ - ports: z.string().default('20000-50000'), - interval: z.string().default('5-10'), + ports: StringRangeSchema.default('20000-50000'), + interval: StringRangeSchema.default('5-10'), }); export type QuicUdpHop = z.infer;