mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-06-05 12:44:22 +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:
parent
8d5d11cafc
commit
e79ca42407
3 changed files with 274 additions and 11 deletions
|
|
@ -1,10 +1,15 @@
|
||||||
import { RandomUtil } from '@/utils';
|
import { RandomUtil, Wireguard } from '@/utils';
|
||||||
|
|
||||||
import type { HysteriaClient } from '@/schemas/protocols/inbound/hysteria';
|
import type { HttpInboundSettings } from '@/schemas/protocols/inbound/http';
|
||||||
import type { ShadowsocksClient } from '@/schemas/protocols/inbound/shadowsocks';
|
import type { Hysteria2InboundSettings } from '@/schemas/protocols/inbound/hysteria2';
|
||||||
import type { TrojanClient } from '@/schemas/protocols/inbound/trojan';
|
import type { HysteriaClient, HysteriaInboundSettings } from '@/schemas/protocols/inbound/hysteria';
|
||||||
import type { VlessClient } from '@/schemas/protocols/inbound/vless';
|
import type { MixedInboundSettings } from '@/schemas/protocols/inbound/mixed';
|
||||||
import type { VmessClient } from '@/schemas/protocols/inbound/vmess';
|
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
|
// Plain-object factories for protocol clients. Each returns a Zod-parsable
|
||||||
// object matching the wire shape. Random fields (id, password, auth,
|
// object matching the wire shape. Random fields (id, password, auth,
|
||||||
|
|
@ -120,3 +125,103 @@ export function createDefaultHysteriaClient(seed: HysteriaClientSeed = {}): Hyst
|
||||||
...clientBase(seed),
|
...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
|
// 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`] = `
|
exports[`createDefaultHysteriaClient > produces a Zod-valid client 1`] = `
|
||||||
{
|
{
|
||||||
"auth": "fixed-hyst-auth",
|
"auth": "fixed-hyst-auth",
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,32 @@
|
||||||
import { describe, expect, it } from 'vitest';
|
import { describe, expect, it } from 'vitest';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
createDefaultHttpInboundSettings,
|
||||||
|
createDefaultHysteria2InboundSettings,
|
||||||
createDefaultHysteriaClient,
|
createDefaultHysteriaClient,
|
||||||
|
createDefaultHysteriaInboundSettings,
|
||||||
|
createDefaultMixedInboundSettings,
|
||||||
createDefaultShadowsocksClient,
|
createDefaultShadowsocksClient,
|
||||||
|
createDefaultShadowsocksInboundSettings,
|
||||||
createDefaultTrojanClient,
|
createDefaultTrojanClient,
|
||||||
|
createDefaultTrojanInboundSettings,
|
||||||
|
createDefaultTunnelInboundSettings,
|
||||||
createDefaultVlessClient,
|
createDefaultVlessClient,
|
||||||
|
createDefaultVlessInboundSettings,
|
||||||
createDefaultVmessClient,
|
createDefaultVmessClient,
|
||||||
|
createDefaultVmessInboundSettings,
|
||||||
|
createDefaultWireguardInboundSettings,
|
||||||
} from '@/lib/xray/inbound-defaults';
|
} from '@/lib/xray/inbound-defaults';
|
||||||
import { HysteriaClientSchema } from '@/schemas/protocols/inbound/hysteria';
|
import { HttpInboundSettingsSchema } from '@/schemas/protocols/inbound/http';
|
||||||
import { ShadowsocksClientSchema } from '@/schemas/protocols/inbound/shadowsocks';
|
import { Hysteria2InboundSettingsSchema } from '@/schemas/protocols/inbound/hysteria2';
|
||||||
import { TrojanClientSchema } from '@/schemas/protocols/inbound/trojan';
|
import { HysteriaClientSchema, HysteriaInboundSettingsSchema } from '@/schemas/protocols/inbound/hysteria';
|
||||||
import { VlessClientSchema } from '@/schemas/protocols/inbound/vless';
|
import { MixedInboundSettingsSchema } from '@/schemas/protocols/inbound/mixed';
|
||||||
import { VmessClientSchema } from '@/schemas/protocols/inbound/vmess';
|
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
|
// 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).
|
// depend on window.crypto (the node test env has no crypto.randomUUID).
|
||||||
|
|
@ -65,3 +80,67 @@ describe('createDefaultHysteriaClient', () => {
|
||||||
expect(HysteriaClientSchema.parse(c)).toEqual(c);
|
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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue