mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-06-05 12:44:22 +00:00
refactor(frontend): extract genHysteriaLink + Wireguard link/config to lib/xray
Fifth and sixth link generators. genHysteriaLink builds the v1/v2 share URL (scheme picked from settings.version), copying TLS knobs into the query, surfacing the salamander obfs password from finalmask.udp[type=salamander] when present, and writing the broader finalmask payload under `fm` like the other links. Legacy parity note: the old genHysteriaLink read stream.tls.settings.allowInsecure, which isn't a field on TlsStreamSettings.Settings — the guard always evaluated false and the `insecure` param never made it into the URL. We omit it here to stay byte-stable. genWireguardLink and genWireguardConfig take a typed WireguardInboundSettings + peer index and: - link: wireguard://<peerPriv>@host:port?publickey=&address=&mtu=#remark - config: the .conf text WireGuard clients consume directly Both derive the server pubKey from settings.secretKey via Wireguard.generateKeypair at call time — Zod stores only secretKey on the wire (pubKey is computed). The Wireguard utility is pure JS (X25519 over Float64Array), so it runs fine under node + the window polyfill we added with the vmess extraction. Two new full-inbound fixtures (hysteria-v1-tls, wireguard-server) plus matching parity tests bring the suite to 78 tests across 8 files; typecheck + lint clean. Hysteria2 (protocol literal) parity stays deferred — the legacy class has no HYSTERIA2 dispatch case, so it can't round-trip a hysteria2 fixture without a protocol remap. Same trick the shadow harness uses; revisit in the orchestrator commit.
This commit is contained in:
parent
1e2845306c
commit
a7ca8c5b10
5 changed files with 431 additions and 2 deletions
|
|
@ -1,8 +1,12 @@
|
||||||
import { Base64 } from '@/utils';
|
import { Base64, Wireguard } from '@/utils';
|
||||||
|
|
||||||
import type { Inbound } from '@/schemas/api/inbound';
|
import type { Inbound } from '@/schemas/api/inbound';
|
||||||
import type { VlessClient } from '@/schemas/protocols/inbound/vless';
|
import type { VlessClient } from '@/schemas/protocols/inbound/vless';
|
||||||
import type { VmessSecurity } from '@/schemas/protocols/inbound/vmess';
|
import type { VmessSecurity } from '@/schemas/protocols/inbound/vmess';
|
||||||
|
import type {
|
||||||
|
WireguardInboundPeer,
|
||||||
|
WireguardInboundSettings,
|
||||||
|
} from '@/schemas/protocols/inbound/wireguard';
|
||||||
import type { ExternalProxyEntry } from '@/schemas/protocols/stream/external-proxy';
|
import type { ExternalProxyEntry } from '@/schemas/protocols/stream/external-proxy';
|
||||||
import type { FinalMaskStreamSettings } from '@/schemas/protocols/stream/finalmask';
|
import type { FinalMaskStreamSettings } from '@/schemas/protocols/stream/finalmask';
|
||||||
import type { XHttpStreamSettings } from '@/schemas/protocols/stream/xhttp';
|
import type { XHttpStreamSettings } from '@/schemas/protocols/stream/xhttp';
|
||||||
|
|
@ -541,3 +545,136 @@ export function genShadowsocksLink(input: GenShadowsocksLinkInput): string {
|
||||||
url.hash = encodeURIComponent(remark);
|
url.hash = encodeURIComponent(remark);
|
||||||
return url.toString();
|
return url.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface GenHysteriaLinkInput {
|
||||||
|
inbound: Inbound;
|
||||||
|
address: string;
|
||||||
|
port?: number;
|
||||||
|
remark?: string;
|
||||||
|
clientAuth: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hysteria share link: hysteria://<auth>@<host>:<port>?<query>#<remark>.
|
||||||
|
// The URL scheme is "hysteria2" when settings.version === 2 (hysteria v2
|
||||||
|
// AKA hysteria2), "hysteria" otherwise. Salamander obfuscation pulls its
|
||||||
|
// password from finalmask.udp[type=salamander] when present; the broader
|
||||||
|
// finalmask payload still rides under `fm` like the other links.
|
||||||
|
//
|
||||||
|
// Note: legacy genHysteriaLink reads stream.tls.settings.allowInsecure,
|
||||||
|
// which isn't a field on TlsStreamSettings.Settings — the guard is always
|
||||||
|
// false. We omit the `insecure` param here to stay byte-stable.
|
||||||
|
export function genHysteriaLink(input: GenHysteriaLinkInput): string {
|
||||||
|
const {
|
||||||
|
inbound,
|
||||||
|
address,
|
||||||
|
port = inbound.port,
|
||||||
|
remark = '',
|
||||||
|
clientAuth,
|
||||||
|
} = input;
|
||||||
|
|
||||||
|
if (inbound.protocol !== 'hysteria' && inbound.protocol !== 'hysteria2') return '';
|
||||||
|
const stream = inbound.streamSettings;
|
||||||
|
if (!stream || stream.security !== 'tls') return '';
|
||||||
|
|
||||||
|
const settings = inbound.settings;
|
||||||
|
const scheme = settings.version === 2 ? 'hysteria2' : 'hysteria';
|
||||||
|
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
params.set('security', 'tls');
|
||||||
|
const tls = stream.tlsSettings;
|
||||||
|
if (tls.settings.fingerprint.length > 0) params.set('fp', tls.settings.fingerprint);
|
||||||
|
if (tls.alpn.length > 0) params.set('alpn', tls.alpn.join(','));
|
||||||
|
if (tls.settings.echConfigList.length > 0) params.set('ech', tls.settings.echConfigList);
|
||||||
|
if (tls.serverName.length > 0) params.set('sni', tls.serverName);
|
||||||
|
|
||||||
|
const udpMasks = stream.finalmask?.udp;
|
||||||
|
if (Array.isArray(udpMasks)) {
|
||||||
|
const salamander = udpMasks.find((m) => m?.type === 'salamander');
|
||||||
|
const obfsPassword = salamander?.settings?.password;
|
||||||
|
if (typeof obfsPassword === 'string' && obfsPassword.length > 0) {
|
||||||
|
params.set('obfs', 'salamander');
|
||||||
|
params.set('obfs-password', obfsPassword);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
applyFinalMaskToParams(stream.finalmask, params);
|
||||||
|
|
||||||
|
const url = new URL(`${scheme}://${clientAuth}@${address}:${port}`);
|
||||||
|
for (const [key, value] of params) url.searchParams.set(key, value);
|
||||||
|
url.hash = encodeURIComponent(remark);
|
||||||
|
return url.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GenWireguardLinkInput {
|
||||||
|
settings: WireguardInboundSettings;
|
||||||
|
address: string;
|
||||||
|
port: number;
|
||||||
|
remark?: string;
|
||||||
|
peerIndex: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wireguard share link: wireguard://<peerPrivKey>@<host>:<port>
|
||||||
|
// ?publickey=<serverPub>&address=<peerAllowedIP>&mtu=<mtu>#<remark>
|
||||||
|
// pubKey is derived from the server's secretKey via Wireguard.generateKeypair
|
||||||
|
// at call time (Zod's schema stores secretKey only — pubKey isn't on the
|
||||||
|
// wire). Returns '' when the peer index is out of bounds.
|
||||||
|
export function genWireguardLink(input: GenWireguardLinkInput): string {
|
||||||
|
const { settings, address, port, remark = '', peerIndex } = input;
|
||||||
|
const peer = settings.peers[peerIndex];
|
||||||
|
if (!peer) return '';
|
||||||
|
|
||||||
|
const url = new URL(`wireguard://${address}:${port}`);
|
||||||
|
url.username = peer.privateKey ?? '';
|
||||||
|
|
||||||
|
const pubKey = settings.secretKey.length > 0
|
||||||
|
? Wireguard.generateKeypair(settings.secretKey).publicKey
|
||||||
|
: '';
|
||||||
|
if (pubKey.length > 0) url.searchParams.set('publickey', pubKey);
|
||||||
|
if (peer.allowedIPs.length > 0 && peer.allowedIPs[0]) {
|
||||||
|
url.searchParams.set('address', peer.allowedIPs[0]);
|
||||||
|
}
|
||||||
|
if (typeof settings.mtu === 'number' && settings.mtu > 0) {
|
||||||
|
url.searchParams.set('mtu', String(settings.mtu));
|
||||||
|
}
|
||||||
|
|
||||||
|
url.hash = encodeURIComponent(remark);
|
||||||
|
return url.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Plain-text WireGuard client config (.conf format). Mirrors the legacy
|
||||||
|
// getWireguardTxt — same DNS defaults (1.1.1.1, 1.0.0.1), MTU optional,
|
||||||
|
// presharedKey + keepAlive only emitted when present on the peer. The
|
||||||
|
// final newline structure follows the legacy: no newline after Endpoint,
|
||||||
|
// optional preSharedKey appended with leading \n, keepAlive appended
|
||||||
|
// with leading \n AND trailing \n.
|
||||||
|
export function genWireguardConfig(input: GenWireguardLinkInput): string {
|
||||||
|
const { settings, address, port, remark = '', peerIndex } = input;
|
||||||
|
const peer = settings.peers[peerIndex];
|
||||||
|
if (!peer) return '';
|
||||||
|
|
||||||
|
const pubKey = settings.secretKey.length > 0
|
||||||
|
? Wireguard.generateKeypair(settings.secretKey).publicKey
|
||||||
|
: '';
|
||||||
|
|
||||||
|
let txt = `[Interface]\n`;
|
||||||
|
txt += `PrivateKey = ${peer.privateKey ?? ''}\n`;
|
||||||
|
txt += `Address = ${peer.allowedIPs[0] ?? ''}\n`;
|
||||||
|
txt += `DNS = 1.1.1.1, 1.0.0.1\n`;
|
||||||
|
if (typeof settings.mtu === 'number' && settings.mtu > 0) {
|
||||||
|
txt += `MTU = ${settings.mtu}\n`;
|
||||||
|
}
|
||||||
|
txt += `\n# ${remark}\n`;
|
||||||
|
txt += `[Peer]\n`;
|
||||||
|
txt += `PublicKey = ${pubKey}\n`;
|
||||||
|
txt += `AllowedIPs = 0.0.0.0/0, ::/0\n`;
|
||||||
|
txt += `Endpoint = ${address}:${port}`;
|
||||||
|
if (peer.preSharedKey && peer.preSharedKey.length > 0) {
|
||||||
|
txt += `\nPresharedKey = ${peer.preSharedKey}`;
|
||||||
|
}
|
||||||
|
if (typeof peer.keepAlive === 'number' && peer.keepAlive > 0) {
|
||||||
|
txt += `\nPersistentKeepalive = ${peer.keepAlive}\n`;
|
||||||
|
}
|
||||||
|
return txt;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type { WireguardInboundPeer };
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,82 @@
|
||||||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||||
|
|
||||||
|
exports[`InboundSchema (full) fixtures > parses hysteria-v1-tls byte-stably 1`] = `
|
||||||
|
{
|
||||||
|
"down": 0,
|
||||||
|
"enable": true,
|
||||||
|
"expiryTime": 0,
|
||||||
|
"id": 21,
|
||||||
|
"listen": "",
|
||||||
|
"port": 36715,
|
||||||
|
"protocol": "hysteria",
|
||||||
|
"remark": "gina-hysteria-v1",
|
||||||
|
"settings": {
|
||||||
|
"clients": [
|
||||||
|
{
|
||||||
|
"auth": "hyst-v1-auth-XYZ",
|
||||||
|
"comment": "",
|
||||||
|
"email": "gina@example.test",
|
||||||
|
"enable": true,
|
||||||
|
"expiryTime": 0,
|
||||||
|
"limitIp": 0,
|
||||||
|
"reset": 0,
|
||||||
|
"subId": "hy1-001",
|
||||||
|
"tgId": 0,
|
||||||
|
"totalGB": 0,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"version": 1,
|
||||||
|
},
|
||||||
|
"sniffing": {
|
||||||
|
"destOverride": [
|
||||||
|
"http",
|
||||||
|
"tls",
|
||||||
|
"quic",
|
||||||
|
"fakedns",
|
||||||
|
],
|
||||||
|
"domainsExcluded": [],
|
||||||
|
"enabled": false,
|
||||||
|
"ipsExcluded": [],
|
||||||
|
"metadataOnly": false,
|
||||||
|
"routeOnly": false,
|
||||||
|
},
|
||||||
|
"streamSettings": {
|
||||||
|
"network": "tcp",
|
||||||
|
"security": "tls",
|
||||||
|
"tcpSettings": {},
|
||||||
|
"tlsSettings": {
|
||||||
|
"alpn": [
|
||||||
|
"h3",
|
||||||
|
],
|
||||||
|
"certificates": [
|
||||||
|
{
|
||||||
|
"buildChain": false,
|
||||||
|
"certificateFile": "/etc/ssl/certs/hysteria.crt",
|
||||||
|
"keyFile": "/etc/ssl/private/hysteria.key",
|
||||||
|
"oneTimeLoading": false,
|
||||||
|
"usage": "encipherment",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"cipherSuites": "",
|
||||||
|
"disableSystemRoot": false,
|
||||||
|
"echServerKeys": "",
|
||||||
|
"enableSessionResumption": false,
|
||||||
|
"maxVersion": "1.3",
|
||||||
|
"minVersion": "1.2",
|
||||||
|
"rejectUnknownSni": false,
|
||||||
|
"serverName": "hysteria.example.test",
|
||||||
|
"settings": {
|
||||||
|
"echConfigList": "",
|
||||||
|
"fingerprint": "chrome",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"tag": "inbound-hysteria-v1",
|
||||||
|
"total": 0,
|
||||||
|
"up": 0,
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`InboundSchema (full) fixtures > parses shadowsocks-tcp-2022 byte-stably 1`] = `
|
exports[`InboundSchema (full) fixtures > parses shadowsocks-tcp-2022 byte-stably 1`] = `
|
||||||
{
|
{
|
||||||
"down": 0,
|
"down": 0,
|
||||||
|
|
@ -394,3 +471,47 @@ exports[`InboundSchema (full) fixtures > parses vmess-tcp-tls byte-stably 1`] =
|
||||||
"up": 0,
|
"up": 0,
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`InboundSchema (full) fixtures > parses wireguard-server byte-stably 1`] = `
|
||||||
|
{
|
||||||
|
"down": 0,
|
||||||
|
"enable": true,
|
||||||
|
"expiryTime": 0,
|
||||||
|
"id": 25,
|
||||||
|
"listen": "",
|
||||||
|
"port": 51820,
|
||||||
|
"protocol": "wireguard",
|
||||||
|
"remark": "wg-server",
|
||||||
|
"settings": {
|
||||||
|
"mtu": 1420,
|
||||||
|
"noKernelTun": false,
|
||||||
|
"peers": [
|
||||||
|
{
|
||||||
|
"allowedIPs": [
|
||||||
|
"10.0.0.2/32",
|
||||||
|
],
|
||||||
|
"keepAlive": 25,
|
||||||
|
"privateKey": "QGVlb2dXc1ZTWGw0ZXBzZndsWmtMaUM5MUlNYjBHWFdYbz0=",
|
||||||
|
"publicKey": "DGSYIcEKAUkA7HhzGSjxLZuV67BR3LeyU0BMLJzNVHQ=",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"secretKey": "iJ2cBkrSGqRwIfYIDIxk7hr5RXfdR93MfJUL7yqkkH8=",
|
||||||
|
},
|
||||||
|
"sniffing": {
|
||||||
|
"destOverride": [
|
||||||
|
"http",
|
||||||
|
"tls",
|
||||||
|
"quic",
|
||||||
|
"fakedns",
|
||||||
|
],
|
||||||
|
"domainsExcluded": [],
|
||||||
|
"enabled": false,
|
||||||
|
"ipsExcluded": [],
|
||||||
|
"metadataOnly": false,
|
||||||
|
"routeOnly": false,
|
||||||
|
},
|
||||||
|
"tag": "inbound-wg-1",
|
||||||
|
"total": 0,
|
||||||
|
"up": 0,
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,67 @@
|
||||||
|
{
|
||||||
|
"id": 21,
|
||||||
|
"up": 0,
|
||||||
|
"down": 0,
|
||||||
|
"total": 0,
|
||||||
|
"remark": "gina-hysteria-v1",
|
||||||
|
"enable": true,
|
||||||
|
"expiryTime": 0,
|
||||||
|
"listen": "",
|
||||||
|
"port": 36715,
|
||||||
|
"tag": "inbound-hysteria-v1",
|
||||||
|
"sniffing": {
|
||||||
|
"enabled": false,
|
||||||
|
"destOverride": ["http", "tls", "quic", "fakedns"],
|
||||||
|
"metadataOnly": false,
|
||||||
|
"routeOnly": false,
|
||||||
|
"ipsExcluded": [],
|
||||||
|
"domainsExcluded": []
|
||||||
|
},
|
||||||
|
"protocol": "hysteria",
|
||||||
|
"settings": {
|
||||||
|
"version": 1,
|
||||||
|
"clients": [
|
||||||
|
{
|
||||||
|
"auth": "hyst-v1-auth-XYZ",
|
||||||
|
"email": "gina@example.test",
|
||||||
|
"limitIp": 0,
|
||||||
|
"totalGB": 0,
|
||||||
|
"expiryTime": 0,
|
||||||
|
"enable": true,
|
||||||
|
"tgId": 0,
|
||||||
|
"subId": "hy1-001",
|
||||||
|
"comment": "",
|
||||||
|
"reset": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"streamSettings": {
|
||||||
|
"network": "tcp",
|
||||||
|
"tcpSettings": {},
|
||||||
|
"security": "tls",
|
||||||
|
"tlsSettings": {
|
||||||
|
"serverName": "hysteria.example.test",
|
||||||
|
"minVersion": "1.2",
|
||||||
|
"maxVersion": "1.3",
|
||||||
|
"cipherSuites": "",
|
||||||
|
"rejectUnknownSni": false,
|
||||||
|
"disableSystemRoot": false,
|
||||||
|
"enableSessionResumption": false,
|
||||||
|
"certificates": [
|
||||||
|
{
|
||||||
|
"certificateFile": "/etc/ssl/certs/hysteria.crt",
|
||||||
|
"keyFile": "/etc/ssl/private/hysteria.key",
|
||||||
|
"oneTimeLoading": false,
|
||||||
|
"usage": "encipherment",
|
||||||
|
"buildChain": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"alpn": ["h3"],
|
||||||
|
"echServerKeys": "",
|
||||||
|
"settings": {
|
||||||
|
"fingerprint": "chrome",
|
||||||
|
"echConfigList": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,34 @@
|
||||||
|
{
|
||||||
|
"id": 25,
|
||||||
|
"up": 0,
|
||||||
|
"down": 0,
|
||||||
|
"total": 0,
|
||||||
|
"remark": "wg-server",
|
||||||
|
"enable": true,
|
||||||
|
"expiryTime": 0,
|
||||||
|
"listen": "",
|
||||||
|
"port": 51820,
|
||||||
|
"tag": "inbound-wg-1",
|
||||||
|
"sniffing": {
|
||||||
|
"enabled": false,
|
||||||
|
"destOverride": ["http", "tls", "quic", "fakedns"],
|
||||||
|
"metadataOnly": false,
|
||||||
|
"routeOnly": false,
|
||||||
|
"ipsExcluded": [],
|
||||||
|
"domainsExcluded": []
|
||||||
|
},
|
||||||
|
"protocol": "wireguard",
|
||||||
|
"settings": {
|
||||||
|
"mtu": 1420,
|
||||||
|
"secretKey": "iJ2cBkrSGqRwIfYIDIxk7hr5RXfdR93MfJUL7yqkkH8=",
|
||||||
|
"peers": [
|
||||||
|
{
|
||||||
|
"privateKey": "QGVlb2dXc1ZTWGw0ZXBzZndsWmtMaUM5MUlNYjBHWFdYbz0=",
|
||||||
|
"publicKey": "DGSYIcEKAUkA7HhzGSjxLZuV67BR3LeyU0BMLJzNVHQ=",
|
||||||
|
"allowedIPs": ["10.0.0.2/32"],
|
||||||
|
"keepAlive": 25
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"noKernelTun": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,9 +1,18 @@
|
||||||
/// <reference types="vite/client" />
|
/// <reference types="vite/client" />
|
||||||
import { describe, expect, it } from 'vitest';
|
import { describe, expect, it } from 'vitest';
|
||||||
|
|
||||||
import { genShadowsocksLink, genTrojanLink, genVlessLink, genVmessLink } from '@/lib/xray/inbound-link';
|
import {
|
||||||
|
genHysteriaLink,
|
||||||
|
genShadowsocksLink,
|
||||||
|
genTrojanLink,
|
||||||
|
genVlessLink,
|
||||||
|
genVmessLink,
|
||||||
|
genWireguardConfig,
|
||||||
|
genWireguardLink,
|
||||||
|
} from '@/lib/xray/inbound-link';
|
||||||
import { Inbound as LegacyInbound } from '@/models/inbound';
|
import { Inbound as LegacyInbound } from '@/models/inbound';
|
||||||
import { InboundSchema } from '@/schemas/api/inbound';
|
import { InboundSchema } from '@/schemas/api/inbound';
|
||||||
|
import type { WireguardInboundSettings } from '@/schemas/protocols/inbound/wireguard';
|
||||||
|
|
||||||
// Parity harness for the share-link extraction. For each full inbound
|
// Parity harness for the share-link extraction. For each full inbound
|
||||||
// fixture matching the protocol under test, we:
|
// fixture matching the protocol under test, we:
|
||||||
|
|
@ -129,6 +138,67 @@ describe('genTrojanLink parity', () => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('genHysteriaLink parity', () => {
|
||||||
|
const fixtures = fixturesForProtocol('hysteria');
|
||||||
|
expect(fixtures.length, 'need at least one hysteria full-inbound fixture').toBeGreaterThan(0);
|
||||||
|
|
||||||
|
for (const [name, raw] of fixtures) {
|
||||||
|
it(`${name}: matches legacy Inbound.genHysteriaLink`, () => {
|
||||||
|
const typed = InboundSchema.parse(raw);
|
||||||
|
const settings = (raw as { settings: { clients: Array<{ auth: string }> } }).settings;
|
||||||
|
const client = settings.clients[0];
|
||||||
|
|
||||||
|
const address = 'example.test';
|
||||||
|
const port = typed.port;
|
||||||
|
const remark = 'parity-test';
|
||||||
|
|
||||||
|
const newLink = genHysteriaLink({
|
||||||
|
inbound: typed,
|
||||||
|
address,
|
||||||
|
port,
|
||||||
|
remark,
|
||||||
|
clientAuth: client.auth,
|
||||||
|
});
|
||||||
|
|
||||||
|
const legacy = LegacyInbound.fromJson(raw);
|
||||||
|
const legacyLink = legacy.genHysteriaLink(address, port, remark, client.auth);
|
||||||
|
|
||||||
|
expect(newLink).toBe(legacyLink);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('genWireguardLink + genWireguardConfig parity', () => {
|
||||||
|
const fixtures = fixturesForProtocol('wireguard');
|
||||||
|
expect(fixtures.length, 'need at least one wireguard full-inbound fixture').toBeGreaterThan(0);
|
||||||
|
|
||||||
|
for (const [name, raw] of fixtures) {
|
||||||
|
it(`${name}: matches legacy getWireguardLink + getWireguardTxt`, () => {
|
||||||
|
const typed = InboundSchema.parse(raw);
|
||||||
|
if (typed.protocol !== 'wireguard') throw new Error('not a wireguard fixture');
|
||||||
|
// InboundSchema is an intersection of two DUs, so TS can't auto-narrow
|
||||||
|
// `settings` from `protocol`. The runtime guard above is the real
|
||||||
|
// check; this cast just helps the type checker.
|
||||||
|
const settings = typed.settings as WireguardInboundSettings;
|
||||||
|
|
||||||
|
const address = 'wg.example.test';
|
||||||
|
const port = typed.port;
|
||||||
|
const remark = 'wg-peer-1';
|
||||||
|
const peerIndex = 0;
|
||||||
|
|
||||||
|
const newLink = genWireguardLink({ settings, address, port, remark, peerIndex });
|
||||||
|
const newConfig = genWireguardConfig({ settings, address, port, remark, peerIndex });
|
||||||
|
|
||||||
|
const legacy = LegacyInbound.fromJson(raw);
|
||||||
|
const legacyLink = legacy.getWireguardLink(address, port, remark, peerIndex);
|
||||||
|
const legacyConfig = legacy.getWireguardTxt(address, port, remark, peerIndex);
|
||||||
|
|
||||||
|
expect(newLink).toBe(legacyLink);
|
||||||
|
expect(newConfig).toBe(legacyConfig);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
describe('genShadowsocksLink parity', () => {
|
describe('genShadowsocksLink parity', () => {
|
||||||
const fixtures = fixturesForProtocol('shadowsocks');
|
const fixtures = fixturesForProtocol('shadowsocks');
|
||||||
expect(fixtures.length, 'need at least one shadowsocks full-inbound fixture').toBeGreaterThan(0);
|
expect(fixtures.length, 'need at least one shadowsocks full-inbound fixture').toBeGreaterThan(0);
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue