mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-05-28 16:09:36 +00:00
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:
@@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user