diff --git a/frontend/src/lib/xray/inbound-defaults.ts b/frontend/src/lib/xray/inbound-defaults.ts index a3ec5df2..00b1b72d 100644 --- a/frontend/src/lib/xray/inbound-defaults.ts +++ b/frontend/src/lib/xray/inbound-defaults.ts @@ -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, + }; +} diff --git a/frontend/src/test/__snapshots__/inbound-defaults.test.ts.snap b/frontend/src/test/__snapshots__/inbound-defaults.test.ts.snap index 95a44adf..6a973a42 100644 --- a/frontend/src/test/__snapshots__/inbound-defaults.test.ts.snap +++ b/frontend/src/test/__snapshots__/inbound-defaults.test.ts.snap @@ -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", diff --git a/frontend/src/test/inbound-defaults.test.ts b/frontend/src/test/inbound-defaults.test.ts index f99e9a17..5501e7c4 100644 --- a/frontend/src/test/inbound-defaults.test.ts +++ b/frontend/src/test/inbound-defaults.test.ts @@ -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); + }); +});