refactor(frontend): add createDefault*InboundSettings factories for all 10 protocols

Round out Step 3d's settings factory set. Ten plain-object factories
(vless / vmess / trojan / shadowsocks / hysteria / hysteria2 / http /
mixed / tunnel / wireguard) replace the legacy
`new Inbound.<X>Settings(protocol)` constructors. Each returns a Zod-
parsable wire shape with schema defaults applied — no class instance.
Forms (Step 4) and InboundsPage clone (Step 5) call these factories
directly once the swap lands.

Three factories take a seed for random fields:
  - shadowsocks: method-dependent password length via
    RandomUtil.randomShadowsocksPassword(method)
  - hysteria: explicit `version` override (defaults to 2, matching
    the legacy panel constructor — v1 is opt-in)
  - wireguard: secretKey from Wireguard.generateKeypair().privateKey

Tests double-verify each factory the same way as the client factories:
snapshot the shape, then Zod parse round-trip to confirm no missing
defaults or stray fields.

Suite: 59 tests across 6 files; typecheck + lint clean. Outbound
factories and the toShareLink extraction follow next.
This commit is contained in:
MHSanaei
2026-05-25 23:46:16 +02:00
parent 8d5d11cafc
commit e79ca42407
3 changed files with 274 additions and 11 deletions

View File

@@ -1,10 +1,15 @@
import { RandomUtil } from '@/utils';
import { RandomUtil, Wireguard } from '@/utils';
import type { HysteriaClient } from '@/schemas/protocols/inbound/hysteria';
import type { ShadowsocksClient } from '@/schemas/protocols/inbound/shadowsocks';
import type { TrojanClient } from '@/schemas/protocols/inbound/trojan';
import type { VlessClient } from '@/schemas/protocols/inbound/vless';
import type { VmessClient } from '@/schemas/protocols/inbound/vmess';
import type { HttpInboundSettings } from '@/schemas/protocols/inbound/http';
import type { Hysteria2InboundSettings } from '@/schemas/protocols/inbound/hysteria2';
import type { HysteriaClient, HysteriaInboundSettings } from '@/schemas/protocols/inbound/hysteria';
import type { MixedInboundSettings } from '@/schemas/protocols/inbound/mixed';
import type { ShadowsocksClient, ShadowsocksInboundSettings } from '@/schemas/protocols/inbound/shadowsocks';
import type { TrojanClient, TrojanInboundSettings } from '@/schemas/protocols/inbound/trojan';
import type { TunnelInboundSettings } from '@/schemas/protocols/inbound/tunnel';
import type { VlessClient, VlessInboundSettings } from '@/schemas/protocols/inbound/vless';
import type { VmessClient, VmessInboundSettings } from '@/schemas/protocols/inbound/vmess';
import type { WireguardInboundSettings } from '@/schemas/protocols/inbound/wireguard';
// Plain-object factories for protocol clients. Each returns a Zod-parsable
// object matching the wire shape. Random fields (id, password, auth,
@@ -120,3 +125,103 @@ export function createDefaultHysteriaClient(seed: HysteriaClientSeed = {}): Hyst
...clientBase(seed),
};
}
// Inbound-settings factories. Each returns a Zod-parsable wire-shape with
// schema defaults already applied — no class instance, no XrayCommonClass.
// Callers (form modals via Step 4, InboundsPage clone via Step 5) call
// these instead of the legacy `Inbound.Settings.getSettings(protocol)`.
export function createDefaultVlessInboundSettings(): VlessInboundSettings {
return {
clients: [],
decryption: 'none',
encryption: 'none',
fallbacks: [],
};
}
export function createDefaultVmessInboundSettings(): VmessInboundSettings {
return { clients: [] };
}
export function createDefaultTrojanInboundSettings(): TrojanInboundSettings {
return { clients: [], fallbacks: [] };
}
export interface ShadowsocksInboundSeed {
method?: ShadowsocksInboundSettings['method'];
password?: string;
network?: ShadowsocksInboundSettings['network'];
ivCheck?: boolean;
}
export function createDefaultShadowsocksInboundSettings(
seed: ShadowsocksInboundSeed = {},
): ShadowsocksInboundSettings {
const method = seed.method ?? '2022-blake3-aes-256-gcm';
return {
method,
password: seed.password ?? RandomUtil.randomShadowsocksPassword(method),
network: seed.network ?? 'tcp',
clients: [],
ivCheck: seed.ivCheck ?? false,
};
}
// Hysteria v1 defaults still emit `version: 2` to match the legacy panel
// constructor — the field discriminates v1 vs v2 inside the same settings
// shape. Callers that explicitly want v1 pass `{ version: 1 }`.
export interface HysteriaInboundSeed {
version?: number;
}
export function createDefaultHysteriaInboundSettings(
seed: HysteriaInboundSeed = {},
): HysteriaInboundSettings {
return {
version: seed.version ?? 2,
clients: [],
};
}
export function createDefaultHysteria2InboundSettings(): Hysteria2InboundSettings {
return { version: 2, clients: [] };
}
export function createDefaultHttpInboundSettings(): HttpInboundSettings {
return { accounts: [], allowTransparent: false };
}
export function createDefaultMixedInboundSettings(): MixedInboundSettings {
return {
auth: 'password',
accounts: [],
udp: false,
ip: '127.0.0.1',
};
}
export function createDefaultTunnelInboundSettings(): TunnelInboundSettings {
return {
portMap: {},
allowedNetwork: 'tcp,udp',
followRedirect: false,
};
}
export interface WireguardInboundSeed {
mtu?: number;
secretKey?: string;
noKernelTun?: boolean;
}
export function createDefaultWireguardInboundSettings(
seed: WireguardInboundSeed = {},
): WireguardInboundSettings {
return {
mtu: seed.mtu ?? 1420,
secretKey: seed.secretKey ?? Wireguard.generateKeypair().privateKey,
peers: [],
noKernelTun: seed.noKernelTun ?? false,
};
}

View File

@@ -1,5 +1,84 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`createDefault*InboundSettings factories > http 1`] = `
{
"accounts": [],
"allowTransparent": false,
}
`;
exports[`createDefault*InboundSettings factories > hysteria (v1, defaults to v2 wire version) 1`] = `
{
"clients": [],
"version": 2,
}
`;
exports[`createDefault*InboundSettings factories > hysteria2 1`] = `
{
"clients": [],
"version": 2,
}
`;
exports[`createDefault*InboundSettings factories > mixed 1`] = `
{
"accounts": [],
"auth": "password",
"ip": "127.0.0.1",
"udp": false,
}
`;
exports[`createDefault*InboundSettings factories > shadowsocks 1`] = `
{
"clients": [],
"ivCheck": false,
"method": "2022-blake3-aes-256-gcm",
"network": "tcp",
"password": "ZmFrZS1zcy1zZWVk",
}
`;
exports[`createDefault*InboundSettings factories > trojan 1`] = `
{
"clients": [],
"fallbacks": [],
}
`;
exports[`createDefault*InboundSettings factories > tunnel 1`] = `
{
"allowedNetwork": "tcp,udp",
"followRedirect": false,
"portMap": {},
}
`;
exports[`createDefault*InboundSettings factories > vless 1`] = `
{
"clients": [],
"decryption": "none",
"encryption": "none",
"fallbacks": [],
}
`;
exports[`createDefault*InboundSettings factories > vmess 1`] = `
{
"clients": [],
}
`;
exports[`createDefault*InboundSettings factories > wireguard 1`] = `
{
"mtu": 1420,
"noKernelTun": false,
"peers": [],
"secretKey": "QGVlb2dXc1ZTWGw0ZXBzZndsWmtMaUM5MUlNYjBHWFdYbz0=",
}
`;
exports[`createDefaultHysteriaClient > produces a Zod-valid client 1`] = `
{
"auth": "fixed-hyst-auth",

View File

@@ -1,17 +1,32 @@
import { describe, expect, it } from 'vitest';
import {
createDefaultHttpInboundSettings,
createDefaultHysteria2InboundSettings,
createDefaultHysteriaClient,
createDefaultHysteriaInboundSettings,
createDefaultMixedInboundSettings,
createDefaultShadowsocksClient,
createDefaultShadowsocksInboundSettings,
createDefaultTrojanClient,
createDefaultTrojanInboundSettings,
createDefaultTunnelInboundSettings,
createDefaultVlessClient,
createDefaultVlessInboundSettings,
createDefaultVmessClient,
createDefaultVmessInboundSettings,
createDefaultWireguardInboundSettings,
} from '@/lib/xray/inbound-defaults';
import { HysteriaClientSchema } from '@/schemas/protocols/inbound/hysteria';
import { ShadowsocksClientSchema } from '@/schemas/protocols/inbound/shadowsocks';
import { TrojanClientSchema } from '@/schemas/protocols/inbound/trojan';
import { VlessClientSchema } from '@/schemas/protocols/inbound/vless';
import { VmessClientSchema } from '@/schemas/protocols/inbound/vmess';
import { HttpInboundSettingsSchema } from '@/schemas/protocols/inbound/http';
import { Hysteria2InboundSettingsSchema } from '@/schemas/protocols/inbound/hysteria2';
import { HysteriaClientSchema, HysteriaInboundSettingsSchema } from '@/schemas/protocols/inbound/hysteria';
import { MixedInboundSettingsSchema } from '@/schemas/protocols/inbound/mixed';
import { ShadowsocksClientSchema, ShadowsocksInboundSettingsSchema } from '@/schemas/protocols/inbound/shadowsocks';
import { TrojanClientSchema, TrojanInboundSettingsSchema } from '@/schemas/protocols/inbound/trojan';
import { TunnelInboundSettingsSchema } from '@/schemas/protocols/inbound/tunnel';
import { VlessClientSchema, VlessInboundSettingsSchema } from '@/schemas/protocols/inbound/vless';
import { VmessClientSchema, VmessInboundSettingsSchema } from '@/schemas/protocols/inbound/vmess';
import { WireguardInboundSettingsSchema } from '@/schemas/protocols/inbound/wireguard';
// Tests pass explicit seeds for every random field so the assertions don't
// depend on window.crypto (the node test env has no crypto.randomUUID).
@@ -65,3 +80,67 @@ describe('createDefaultHysteriaClient', () => {
expect(HysteriaClientSchema.parse(c)).toEqual(c);
});
});
describe('createDefault*InboundSettings factories', () => {
it('vless', () => {
const s = createDefaultVlessInboundSettings();
expect(s).toMatchSnapshot();
expect(VlessInboundSettingsSchema.parse(s)).toEqual(s);
});
it('vmess', () => {
const s = createDefaultVmessInboundSettings();
expect(s).toMatchSnapshot();
expect(VmessInboundSettingsSchema.parse(s)).toEqual(s);
});
it('trojan', () => {
const s = createDefaultTrojanInboundSettings();
expect(s).toMatchSnapshot();
expect(TrojanInboundSettingsSchema.parse(s)).toEqual(s);
});
it('shadowsocks', () => {
const s = createDefaultShadowsocksInboundSettings({ password: 'ZmFrZS1zcy1zZWVk' });
expect(s).toMatchSnapshot();
expect(ShadowsocksInboundSettingsSchema.parse(s)).toEqual(s);
});
it('hysteria (v1, defaults to v2 wire version)', () => {
const s = createDefaultHysteriaInboundSettings();
expect(s).toMatchSnapshot();
expect(HysteriaInboundSettingsSchema.parse(s)).toEqual(s);
});
it('hysteria2', () => {
const s = createDefaultHysteria2InboundSettings();
expect(s).toMatchSnapshot();
expect(Hysteria2InboundSettingsSchema.parse(s)).toEqual(s);
});
it('http', () => {
const s = createDefaultHttpInboundSettings();
expect(s).toMatchSnapshot();
expect(HttpInboundSettingsSchema.parse(s)).toEqual(s);
});
it('mixed', () => {
const s = createDefaultMixedInboundSettings();
expect(s).toMatchSnapshot();
expect(MixedInboundSettingsSchema.parse(s)).toEqual(s);
});
it('tunnel', () => {
const s = createDefaultTunnelInboundSettings();
expect(s).toMatchSnapshot();
expect(TunnelInboundSettingsSchema.parse(s)).toEqual(s);
});
it('wireguard', () => {
const s = createDefaultWireguardInboundSettings({
secretKey: 'QGVlb2dXc1ZTWGw0ZXBzZndsWmtMaUM5MUlNYjBHWFdYbz0=',
});
expect(s).toMatchSnapshot();
expect(WireguardInboundSettingsSchema.parse(s)).toEqual(s);
});
});