Files
3x-ui/frontend/src/schemas/api/inbound.ts
T
MHSanaei ccd0853b6c fix(inbounds): allow port 0 for UDS inbounds (#4783)
Unix Domain Socket inbounds (listen path starting with /) use port 0, which xray-core ignores. Validation was hard-locked to a minimum of 1 in three places: the shared Zod PortSchema, the AntD InputNumber, and the Go Inbound model tag. Adds an InboundPortSchema (min 0) for the inbound form/API schemas, makes the port InputNumber min UDS-aware, and relaxes the Inbound model validate tag to gte=0. PortSchema and the Node model stay min 1.
2026-06-01 23:26:20 +02:00

65 lines
2.7 KiB
TypeScript

import { z } from 'zod';
import { InboundPortSchema, SniffingSchema } from '@/schemas/primitives';
import { InboundSettingsSchema } from '@/schemas/protocols/inbound';
import { SecuritySettingsSchema } from '@/schemas/protocols/security';
import { NetworkSettingsSchema, StreamExtrasSchema } from '@/schemas/protocols/stream';
// Top-level inbound shape on the wire. Composes:
// - Per-protocol settings via the InboundSettingsSchema discriminated
// union (10 protocols, tagged-wrapper {protocol, settings}).
// - StreamSettings as an intersection of the network DU (6 branches),
// security DU (3 branches), and the orthogonal extras (finalmask,
// sockopt, externalProxy). Zod 4 supports DU intersection — each
// branch validates its slice of the same input object.
//
// The id/up/down/total/expiryTime fields are int64 on the Go side but
// the panel ships them as JS numbers. Numbers above Number.MAX_SAFE_INTEGER
// (~9e15) lose precision; the panel works around this for the traffic
// counters by stringifying them at the API edge. Not modeled here.
export const StreamSettingsSchema = NetworkSettingsSchema
.and(SecuritySettingsSchema)
.and(StreamExtrasSchema);
export type StreamSettings = z.infer<typeof StreamSettingsSchema>;
export const InboundCoreSchema = z.object({
id: z.number().int().optional(),
up: z.number().int().min(0).default(0),
down: z.number().int().min(0).default(0),
total: z.number().int().min(0).default(0),
remark: z.string().default(''),
enable: z.boolean().default(true),
expiryTime: z.number().int().default(0),
listen: z.string().default(''),
port: InboundPortSchema,
tag: z.string().default(''),
sniffing: SniffingSchema.default({
enabled: false,
destOverride: ['http', 'tls', 'quic', 'fakedns'],
metadataOnly: false,
routeOnly: false,
ipsExcluded: [],
domainsExcluded: [],
}),
streamSettings: StreamSettingsSchema.optional(),
clientStats: z.string().optional(),
});
export type InboundCore = z.infer<typeof InboundCoreSchema>;
// Full Inbound = core fields + the protocol/settings discriminated union.
// Consumers narrow on `.protocol` to access the matching `.settings`
// branch with full type safety.
export const InboundSchema = InboundCoreSchema.and(InboundSettingsSchema);
export type Inbound = z.infer<typeof InboundSchema>;
// SlimInbound is the list-view projection — same shape minus settings
// and streamSettings (the list endpoint omits both to keep payload
// small). Used by InboundsPage list rendering.
export const SlimInboundSchema = InboundCoreSchema.omit({
streamSettings: true,
}).extend({
protocol: z.string(),
});
export type SlimInbound = z.infer<typeof SlimInboundSchema>;