feat(frontend): Hysteria stream sub-form (schema branch + outbound UI)
Add the 7th branch to NetworkSettingsSchema for Hysteria transport.
schemas/protocols/stream/hysteria.ts:
- HysteriaStreamSettingsSchema covers the full wire shape: version=2,
auth, congestion (''|'brutal'), up/down bandwidth strings, optional
udphop sub-object for port-hopping, receive-window tuning fields,
maxIdleTimeout, keepAlivePeriod, disablePathMTUDiscovery.
schemas/protocols/stream/index.ts:
- NetworkSchema gains 'hysteria'.
- NetworkSettingsSchema gains the 7th branch
{ network: 'hysteria', hysteriaSettings: HysteriaStreamSettingsSchema }.
OutboundFormModal.tsx:
- NETWORK_OPTIONS keeps the 6 standard transports for non-hysteria
protocols; when protocol === 'hysteria', a 7th option is appended
(matches the legacy [...NETWORKS, 'hysteria'] gate).
- newStreamSlice handles the 'hysteria' case with sensible defaults
matching the legacy HysteriaStreamSettings constructor.
- New sub-form when network === 'hysteria': 8 common fields (auth,
congestion, up, down, udphop Switch + 3 nested fields when on,
maxIdleTimeout, keepAlivePeriod, disablePathMTUDiscovery).
- Receive-window tuning fields are still edit-via-JSON (rarely
touched + would clutter the form).
2026-05-26 11:10:37 +00:00
|
|
|
import { z } from 'zod';
|
|
|
|
|
|
|
|
|
|
// Hysteria stream transport — the hysteria-specific knobs that ride
|
|
|
|
|
// alongside the connect target on outbound (and the inbound side too,
|
|
|
|
|
// where the listening peer needs matching auth / congestion / obfs).
|
|
|
|
|
// Wire shape mirrors xray-core's HysteriaConfig, with udphop nested
|
|
|
|
|
// when port-hopping is on and omitted otherwise.
|
|
|
|
|
|
|
|
|
|
export const HysteriaUdphopSchema = z.object({
|
|
|
|
|
port: z.string().default(''),
|
|
|
|
|
intervalMin: z.number().int().min(1).default(30),
|
|
|
|
|
intervalMax: z.number().int().min(1).default(30),
|
|
|
|
|
});
|
|
|
|
|
export type HysteriaUdphop = z.infer<typeof HysteriaUdphopSchema>;
|
|
|
|
|
|
|
|
|
|
// `congestion` is `''` (BBR, the default) or `'brutal'`. Both empty and
|
|
|
|
|
// missing are equivalent on the wire so we accept either.
|
|
|
|
|
export const HysteriaCongestionSchema = z.union([z.literal(''), z.literal('brutal')]);
|
|
|
|
|
|
feat(frontend): inbound Hysteria stream sub-form (auth + udpIdleTimeout + masquerade)
Restore the inbound side of Hysteria stream configuration that was
previously hidden — the legacy modal exposed these knobs but the
Pattern A rewrite gated them out.
schemas/protocols/stream/hysteria.ts:
- HysteriaMasqueradeSchema covers the inbound-only masquerade wire
shape: type ('proxy'|'file'|'string'), dir, url, rewriteHost,
insecure, content, headers, statusCode. The three masquerade types
cover the spectrum: reverse-proxy upstream, serve static files, or
return a fixed string body.
- HysteriaStreamSettingsSchema gains 3 inbound-side optional fields:
protocol, udpIdleTimeout, masquerade. Outbound side is untouched
(the legacy class accepted both wire shapes via the same struct).
InboundFormModal.tsx:
- New hysteria stream sub-form section in streamTab, gated by
protocol === HYSTERIA. Fields: version (disabled, locked to 2),
auth, udpIdleTimeout, masquerade Switch + nested type-Select with
three conditional sub-blocks (proxy URL+rewriteHost+insecure,
file dir, string statusCode+body+headers).
- onValuesChange cascade: switching TO hysteria seeds streamSettings
with the hysteria branch (forcing network='hysteria' + TLS); switching
AWAY from hysteria snaps back to TCP so the standard network
selector has a valid starting point.
masquerade headers use the HeaderMapEditor v1 component.
2026-05-26 11:44:00 +00:00
|
|
|
// Inbound-only masquerade sub-object. Xray's hysteria inbound can disguise
|
|
|
|
|
// itself as an HTTP server by serving static files (`type: 'file'`),
|
|
|
|
|
// reverse-proxying upstream traffic (`type: 'proxy'`), or returning a
|
|
|
|
|
// fixed string body (`type: 'string'`). Fields are loose-typed strings
|
|
|
|
|
// because the panel writes them as free-form input.
|
|
|
|
|
export const HysteriaMasqueradeSchema = z.object({
|
|
|
|
|
type: z.enum(['proxy', 'file', 'string']).default('proxy'),
|
|
|
|
|
dir: z.string().default(''),
|
|
|
|
|
url: z.string().default(''),
|
|
|
|
|
rewriteHost: z.boolean().default(false),
|
|
|
|
|
insecure: z.boolean().default(false),
|
|
|
|
|
content: z.string().default(''),
|
|
|
|
|
headers: z.record(z.string(), z.string()).default({}),
|
|
|
|
|
statusCode: z.number().int().min(0).default(0),
|
|
|
|
|
});
|
|
|
|
|
export type HysteriaMasquerade = z.infer<typeof HysteriaMasqueradeSchema>;
|
|
|
|
|
|
feat(frontend): Hysteria stream sub-form (schema branch + outbound UI)
Add the 7th branch to NetworkSettingsSchema for Hysteria transport.
schemas/protocols/stream/hysteria.ts:
- HysteriaStreamSettingsSchema covers the full wire shape: version=2,
auth, congestion (''|'brutal'), up/down bandwidth strings, optional
udphop sub-object for port-hopping, receive-window tuning fields,
maxIdleTimeout, keepAlivePeriod, disablePathMTUDiscovery.
schemas/protocols/stream/index.ts:
- NetworkSchema gains 'hysteria'.
- NetworkSettingsSchema gains the 7th branch
{ network: 'hysteria', hysteriaSettings: HysteriaStreamSettingsSchema }.
OutboundFormModal.tsx:
- NETWORK_OPTIONS keeps the 6 standard transports for non-hysteria
protocols; when protocol === 'hysteria', a 7th option is appended
(matches the legacy [...NETWORKS, 'hysteria'] gate).
- newStreamSlice handles the 'hysteria' case with sensible defaults
matching the legacy HysteriaStreamSettings constructor.
- New sub-form when network === 'hysteria': 8 common fields (auth,
congestion, up, down, udphop Switch + 3 nested fields when on,
maxIdleTimeout, keepAlivePeriod, disablePathMTUDiscovery).
- Receive-window tuning fields are still edit-via-JSON (rarely
touched + would clutter the form).
2026-05-26 11:10:37 +00:00
|
|
|
export const HysteriaStreamSettingsSchema = z.object({
|
feat(frontend): inbound Hysteria stream sub-form (auth + udpIdleTimeout + masquerade)
Restore the inbound side of Hysteria stream configuration that was
previously hidden — the legacy modal exposed these knobs but the
Pattern A rewrite gated them out.
schemas/protocols/stream/hysteria.ts:
- HysteriaMasqueradeSchema covers the inbound-only masquerade wire
shape: type ('proxy'|'file'|'string'), dir, url, rewriteHost,
insecure, content, headers, statusCode. The three masquerade types
cover the spectrum: reverse-proxy upstream, serve static files, or
return a fixed string body.
- HysteriaStreamSettingsSchema gains 3 inbound-side optional fields:
protocol, udpIdleTimeout, masquerade. Outbound side is untouched
(the legacy class accepted both wire shapes via the same struct).
InboundFormModal.tsx:
- New hysteria stream sub-form section in streamTab, gated by
protocol === HYSTERIA. Fields: version (disabled, locked to 2),
auth, udpIdleTimeout, masquerade Switch + nested type-Select with
three conditional sub-blocks (proxy URL+rewriteHost+insecure,
file dir, string statusCode+body+headers).
- onValuesChange cascade: switching TO hysteria seeds streamSettings
with the hysteria branch (forcing network='hysteria' + TLS); switching
AWAY from hysteria snaps back to TCP so the standard network
selector has a valid starting point.
masquerade headers use the HeaderMapEditor v1 component.
2026-05-26 11:44:00 +00:00
|
|
|
// Outbound-side fields. The version field is shared with inbound and
|
|
|
|
|
// typically locked to 2.
|
feat(frontend): Hysteria stream sub-form (schema branch + outbound UI)
Add the 7th branch to NetworkSettingsSchema for Hysteria transport.
schemas/protocols/stream/hysteria.ts:
- HysteriaStreamSettingsSchema covers the full wire shape: version=2,
auth, congestion (''|'brutal'), up/down bandwidth strings, optional
udphop sub-object for port-hopping, receive-window tuning fields,
maxIdleTimeout, keepAlivePeriod, disablePathMTUDiscovery.
schemas/protocols/stream/index.ts:
- NetworkSchema gains 'hysteria'.
- NetworkSettingsSchema gains the 7th branch
{ network: 'hysteria', hysteriaSettings: HysteriaStreamSettingsSchema }.
OutboundFormModal.tsx:
- NETWORK_OPTIONS keeps the 6 standard transports for non-hysteria
protocols; when protocol === 'hysteria', a 7th option is appended
(matches the legacy [...NETWORKS, 'hysteria'] gate).
- newStreamSlice handles the 'hysteria' case with sensible defaults
matching the legacy HysteriaStreamSettings constructor.
- New sub-form when network === 'hysteria': 8 common fields (auth,
congestion, up, down, udphop Switch + 3 nested fields when on,
maxIdleTimeout, keepAlivePeriod, disablePathMTUDiscovery).
- Receive-window tuning fields are still edit-via-JSON (rarely
touched + would clutter the form).
2026-05-26 11:10:37 +00:00
|
|
|
version: z.literal(2).default(2),
|
|
|
|
|
auth: z.string().default(''),
|
|
|
|
|
congestion: HysteriaCongestionSchema.default(''),
|
|
|
|
|
// up / down are dash-separated bandwidth strings like '100 mbps' / '1 gbps'.
|
|
|
|
|
// The panel stores them as free-form strings and Xray parses on the
|
|
|
|
|
// server side; no client-side validation.
|
|
|
|
|
up: z.string().default('0'),
|
|
|
|
|
down: z.string().default('0'),
|
|
|
|
|
udphop: HysteriaUdphopSchema.optional(),
|
|
|
|
|
initStreamReceiveWindow: z.number().int().min(0).default(8388608),
|
|
|
|
|
maxStreamReceiveWindow: z.number().int().min(0).default(8388608),
|
|
|
|
|
initConnectionReceiveWindow: z.number().int().min(0).default(20971520),
|
|
|
|
|
maxConnectionReceiveWindow: z.number().int().min(0).default(20971520),
|
|
|
|
|
maxIdleTimeout: z.number().int().min(1).default(30),
|
|
|
|
|
keepAlivePeriod: z.number().int().min(1).default(2),
|
|
|
|
|
disablePathMTUDiscovery: z.boolean().default(false),
|
feat(frontend): inbound Hysteria stream sub-form (auth + udpIdleTimeout + masquerade)
Restore the inbound side of Hysteria stream configuration that was
previously hidden — the legacy modal exposed these knobs but the
Pattern A rewrite gated them out.
schemas/protocols/stream/hysteria.ts:
- HysteriaMasqueradeSchema covers the inbound-only masquerade wire
shape: type ('proxy'|'file'|'string'), dir, url, rewriteHost,
insecure, content, headers, statusCode. The three masquerade types
cover the spectrum: reverse-proxy upstream, serve static files, or
return a fixed string body.
- HysteriaStreamSettingsSchema gains 3 inbound-side optional fields:
protocol, udpIdleTimeout, masquerade. Outbound side is untouched
(the legacy class accepted both wire shapes via the same struct).
InboundFormModal.tsx:
- New hysteria stream sub-form section in streamTab, gated by
protocol === HYSTERIA. Fields: version (disabled, locked to 2),
auth, udpIdleTimeout, masquerade Switch + nested type-Select with
three conditional sub-blocks (proxy URL+rewriteHost+insecure,
file dir, string statusCode+body+headers).
- onValuesChange cascade: switching TO hysteria seeds streamSettings
with the hysteria branch (forcing network='hysteria' + TLS); switching
AWAY from hysteria snaps back to TCP so the standard network
selector has a valid starting point.
masquerade headers use the HeaderMapEditor v1 component.
2026-05-26 11:44:00 +00:00
|
|
|
// Inbound-side fields. xray-core's HysteriaConfig accepts both sets in
|
|
|
|
|
// the same struct; outbound emits the bandwidth/udphop block, inbound
|
|
|
|
|
// emits the protocol/udpIdleTimeout/masquerade block. The panel can
|
|
|
|
|
// round-trip both shapes through this single schema.
|
|
|
|
|
protocol: z.string().optional(),
|
|
|
|
|
udpIdleTimeout: z.number().int().min(1).optional(),
|
|
|
|
|
masquerade: HysteriaMasqueradeSchema.optional(),
|
feat(frontend): Hysteria stream sub-form (schema branch + outbound UI)
Add the 7th branch to NetworkSettingsSchema for Hysteria transport.
schemas/protocols/stream/hysteria.ts:
- HysteriaStreamSettingsSchema covers the full wire shape: version=2,
auth, congestion (''|'brutal'), up/down bandwidth strings, optional
udphop sub-object for port-hopping, receive-window tuning fields,
maxIdleTimeout, keepAlivePeriod, disablePathMTUDiscovery.
schemas/protocols/stream/index.ts:
- NetworkSchema gains 'hysteria'.
- NetworkSettingsSchema gains the 7th branch
{ network: 'hysteria', hysteriaSettings: HysteriaStreamSettingsSchema }.
OutboundFormModal.tsx:
- NETWORK_OPTIONS keeps the 6 standard transports for non-hysteria
protocols; when protocol === 'hysteria', a 7th option is appended
(matches the legacy [...NETWORKS, 'hysteria'] gate).
- newStreamSlice handles the 'hysteria' case with sensible defaults
matching the legacy HysteriaStreamSettings constructor.
- New sub-form when network === 'hysteria': 8 common fields (auth,
congestion, up, down, udphop Switch + 3 nested fields when on,
maxIdleTimeout, keepAlivePeriod, disablePathMTUDiscovery).
- Receive-window tuning fields are still edit-via-JSON (rarely
touched + would clutter the form).
2026-05-26 11:10:37 +00:00
|
|
|
});
|
|
|
|
|
export type HysteriaStreamSettings = z.infer<typeof HysteriaStreamSettingsSchema>;
|