mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-07-14 16:46:07 +00:00
fix(inbounds): heal legacy client data and TLS cert form hydration
- Detach preserves client traffic stats. DelInboundClient, DelInboundClientByEmail, and bulkDelInboundClients now take a keepTraffic flag; Detach passes true, delete-paths keep prior behavior. Runtime user removal still runs so xray drops the session. - Two startup seeders normalize legacy inbound settings JSON: clients:null -> [] and any non-numeric tgId -> 0 (string, bool, NaN, Inf, non-integer floats). Each records itself once in history_of_seeders. - MigrationRequirements no longer rewrites empty clients arrays back to null: newClients is initialized as a non-nil slice and incoming clients:null is coerced before the type assertion. - TLS cert form: rawInboundToFormValues synthesizes a useFile discriminator per cert from whichever side carries data, so the edit modal can show file-mode paths again. formValuesToWirePayload strips useFile so saved JSON stays in wire shape.
This commit is contained in:
@@ -112,10 +112,26 @@ function healStreamNetworkKey(stream: Record<string, unknown>): void {
|
||||
}
|
||||
}
|
||||
|
||||
// Map a raw DB row (settings/streamSettings/sniffing as string OR object)
|
||||
// into the typed InboundFormValues. Does NOT validate against the schema —
|
||||
// callers that want a hard guarantee should follow up with
|
||||
// InboundFormSchema.safeParse(...).
|
||||
function tlsCerts(stream: Record<string, unknown>): Record<string, unknown>[] {
|
||||
const tls = stream.tlsSettings as { certificates?: unknown } | undefined;
|
||||
return Array.isArray(tls?.certificates) ? tls.certificates as Record<string, unknown>[] : [];
|
||||
}
|
||||
|
||||
function synthesizeTlsCertUseFile(stream: Record<string, unknown>): void {
|
||||
for (const c of tlsCerts(stream)) {
|
||||
if (typeof c.useFile === 'boolean') continue;
|
||||
const hasFile = !!c.certificateFile || !!c.keyFile;
|
||||
const hasInline =
|
||||
(Array.isArray(c.certificate) && c.certificate.length > 0) ||
|
||||
(Array.isArray(c.key) && c.key.length > 0);
|
||||
c.useFile = hasFile || !hasInline;
|
||||
}
|
||||
}
|
||||
|
||||
function stripTlsCertUseFile(stream: Record<string, unknown>): void {
|
||||
for (const c of tlsCerts(stream)) delete c.useFile;
|
||||
}
|
||||
|
||||
export function rawInboundToFormValues(row: RawInboundRow): InboundFormValues {
|
||||
const protocol = (row.protocol || 'vless') as InboundSettings['protocol'];
|
||||
const settings = coerceJsonObject(row.settings) as InboundSettings['settings'];
|
||||
@@ -125,6 +141,7 @@ export function rawInboundToFormValues(row: RawInboundRow): InboundFormValues {
|
||||
: undefined;
|
||||
if (streamSettings) {
|
||||
healStreamNetworkKey(streamSettings as unknown as Record<string, unknown>);
|
||||
synthesizeTlsCertUseFile(streamSettings as unknown as Record<string, unknown>);
|
||||
}
|
||||
const sniffing = coerceJsonObject(row.sniffing) as unknown as Sniffing;
|
||||
|
||||
@@ -181,12 +198,12 @@ export function pruneEmpty(value: unknown): unknown {
|
||||
// gives us the canonical projection.
|
||||
function clientSchemaForProtocol(protocol: string): z.ZodType | null {
|
||||
switch (protocol) {
|
||||
case 'vless': return VlessClientSchema;
|
||||
case 'vmess': return VmessClientSchema;
|
||||
case 'trojan': return TrojanClientSchema;
|
||||
case 'vless': return VlessClientSchema;
|
||||
case 'vmess': return VmessClientSchema;
|
||||
case 'trojan': return TrojanClientSchema;
|
||||
case 'shadowsocks': return ShadowsocksClientSchema;
|
||||
case 'hysteria': return HysteriaClientSchema;
|
||||
default: return null;
|
||||
case 'hysteria': return HysteriaClientSchema;
|
||||
default: return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -265,6 +282,7 @@ export function formValuesToWirePayload(values: InboundFormValues): WireInboundP
|
||||
const streamPruned = values.streamSettings
|
||||
? ((pruneEmpty(values.streamSettings) ?? {}) as Record<string, unknown>)
|
||||
: undefined;
|
||||
if (streamPruned) stripTlsCertUseFile(streamPruned);
|
||||
dropLegacyOptionalEmpties(settingsPruned, streamPruned);
|
||||
const payload: WireInboundPayload = {
|
||||
up: values.up,
|
||||
|
||||
Reference in New Issue
Block a user