mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-06-05 12:44:22 +00:00
refactor(frontend): extract genTrojanLink + genShadowsocksLink to lib/xray
Third and fourth link generators. genTrojanLink mirrors genVlessLink's
shape (URLSearchParams + network/security branches + remark hash) minus
the encryption/flow VLESS-isms. genShadowsocksLink shares the same query
construction but base64-encodes the userinfo portion as method:password
or method:settingsPw:clientPw depending on whether SS-2022 is in
single-user or multi-user mode.
Three reusable helpers move out of the per-protocol functions:
- writeNetworkParams: the per-network switch that all param-style
links share (tcp http header / kcp mtu+tti / ws path+host /
grpc serviceName+authority / httpupgrade / xhttp extras)
- writeTlsParams: fingerprint/alpn/ech/sni
- writeRealityParams: pbk/sid/spx/pqv (preserves the SNI-omission
legacy parity quirk noted in the genVlessLink commit)
genVmessLink stays with its inline switch — it builds a JSON obj instead
of URLSearchParams and has per-network quirks (kcp emits mtu+tti at
the obj root, grpc maps multiMode to obj.type='multi') that don't
factor cleanly through the shared writer.
Two new full-inbound fixtures (trojan-ws-tls, shadowsocks-tcp-2022)
plus matching parity tests bring the suite to 74 tests across 8 files;
typecheck + lint clean.
This commit is contained in:
parent
79c076ee11
commit
1e2845306c
5 changed files with 501 additions and 1 deletions
|
|
@ -371,3 +371,173 @@ export function genVlessLink(input: GenVlessLinkInput): string {
|
||||||
url.hash = encodeURIComponent(remark);
|
url.hash = encodeURIComponent(remark);
|
||||||
return url.toString();
|
return url.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Shared network-branch writer used by trojan + shadowsocks links.
|
||||||
|
// VLESS and VMess don't call this because they have minor per-protocol
|
||||||
|
// quirks inline (vmess maps `multi` differently into obj.type; vless sets
|
||||||
|
// encryption=none up-front).
|
||||||
|
function writeNetworkParams(stream: NonNullable<Inbound['streamSettings']>, params: URLSearchParams): void {
|
||||||
|
if (stream.network === 'tcp') {
|
||||||
|
const tcp = stream.tcpSettings;
|
||||||
|
if (tcp.header?.type === 'http') {
|
||||||
|
const request = tcp.header.request;
|
||||||
|
if (request) {
|
||||||
|
params.set('path', request.path.join(','));
|
||||||
|
const host = getHeaderValue(request.headers, 'host');
|
||||||
|
if (host) params.set('host', host);
|
||||||
|
params.set('headerType', 'http');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (stream.network === 'kcp') {
|
||||||
|
const kcp = stream.kcpSettings;
|
||||||
|
params.set('mtu', String(kcp.mtu));
|
||||||
|
params.set('tti', String(kcp.tti));
|
||||||
|
} else if (stream.network === 'ws') {
|
||||||
|
const ws = stream.wsSettings;
|
||||||
|
params.set('path', ws.path);
|
||||||
|
params.set('host', ws.host.length > 0 ? ws.host : getHeaderValue(ws.headers, 'host'));
|
||||||
|
} else if (stream.network === 'grpc') {
|
||||||
|
const grpc = stream.grpcSettings;
|
||||||
|
params.set('serviceName', grpc.serviceName);
|
||||||
|
params.set('authority', grpc.authority);
|
||||||
|
if (grpc.multiMode) params.set('mode', 'multi');
|
||||||
|
} else if (stream.network === 'httpupgrade') {
|
||||||
|
const hu = stream.httpupgradeSettings;
|
||||||
|
params.set('path', hu.path);
|
||||||
|
params.set('host', hu.host.length > 0 ? hu.host : getHeaderValue(hu.headers, 'host'));
|
||||||
|
} else if (stream.network === 'xhttp') {
|
||||||
|
applyXhttpExtraToParams(stream.xhttpSettings, params);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function writeTlsParams(stream: NonNullable<Inbound['streamSettings']>, params: URLSearchParams): void {
|
||||||
|
if (stream.security !== 'tls') return;
|
||||||
|
const tls = stream.tlsSettings;
|
||||||
|
params.set('fp', tls.settings.fingerprint);
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reality query-string writer shared by VLESS and Trojan. Preserves the
|
||||||
|
// legacy SNI-omission quirk (see genVlessLink for the full story).
|
||||||
|
function writeRealityParams(stream: NonNullable<Inbound['streamSettings']>, params: URLSearchParams): void {
|
||||||
|
if (stream.security !== 'reality') return;
|
||||||
|
const reality = stream.realitySettings;
|
||||||
|
params.set('pbk', reality.settings.publicKey);
|
||||||
|
params.set('fp', reality.settings.fingerprint);
|
||||||
|
if (reality.shortIds.length > 0) params.set('sid', reality.shortIds[0]);
|
||||||
|
if (reality.settings.spiderX.length > 0) params.set('spx', reality.settings.spiderX);
|
||||||
|
if (reality.settings.mldsa65Verify.length > 0) params.set('pqv', reality.settings.mldsa65Verify);
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GenTrojanLinkInput {
|
||||||
|
inbound: Inbound;
|
||||||
|
address: string;
|
||||||
|
port?: number;
|
||||||
|
forceTls?: ForceTls;
|
||||||
|
remark?: string;
|
||||||
|
clientPassword: string;
|
||||||
|
externalProxy?: ExternalProxyEntry | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trojan share link: trojan://<password>@<host>:<port>?<query>#<remark>.
|
||||||
|
// Same query-string shape as VLESS minus the `encryption` and `flow`
|
||||||
|
// fields. Returns '' if the inbound isn't trojan.
|
||||||
|
export function genTrojanLink(input: GenTrojanLinkInput): string {
|
||||||
|
const {
|
||||||
|
inbound,
|
||||||
|
address,
|
||||||
|
port = inbound.port,
|
||||||
|
forceTls = 'same',
|
||||||
|
remark = '',
|
||||||
|
clientPassword,
|
||||||
|
externalProxy = null,
|
||||||
|
} = input;
|
||||||
|
|
||||||
|
if (inbound.protocol !== 'trojan') return '';
|
||||||
|
const stream = inbound.streamSettings;
|
||||||
|
if (!stream) return '';
|
||||||
|
|
||||||
|
const security = forceTls === 'same' ? stream.security : forceTls;
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
params.set('type', stream.network);
|
||||||
|
|
||||||
|
writeNetworkParams(stream, params);
|
||||||
|
applyFinalMaskToParams(stream.finalmask, params);
|
||||||
|
|
||||||
|
if (security === 'tls') {
|
||||||
|
params.set('security', 'tls');
|
||||||
|
writeTlsParams(stream, params);
|
||||||
|
applyExternalProxyTLSParams(externalProxy, params, security);
|
||||||
|
} else if (security === 'reality') {
|
||||||
|
params.set('security', 'reality');
|
||||||
|
writeRealityParams(stream, params);
|
||||||
|
} else {
|
||||||
|
params.set('security', 'none');
|
||||||
|
}
|
||||||
|
|
||||||
|
const url = new URL(`trojan://${clientPassword}@${address}:${port}`);
|
||||||
|
for (const [key, value] of params) url.searchParams.set(key, value);
|
||||||
|
url.hash = encodeURIComponent(remark);
|
||||||
|
return url.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GenShadowsocksLinkInput {
|
||||||
|
inbound: Inbound;
|
||||||
|
address: string;
|
||||||
|
port?: number;
|
||||||
|
forceTls?: ForceTls;
|
||||||
|
remark?: string;
|
||||||
|
clientPassword?: string;
|
||||||
|
externalProxy?: ExternalProxyEntry | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shadowsocks 2022 share link. The userinfo portion is base64(method:pw)
|
||||||
|
// for single-user and base64(method:settingsPw:clientPw) for multi-user
|
||||||
|
// 2022-blake3. Legacy SS (non-2022) leaves the password out of the
|
||||||
|
// userinfo entirely — matches the legacy class's password-array logic.
|
||||||
|
// Note: legacy `isSSMultiUser` returns true for everything except
|
||||||
|
// 2022-blake3-chacha20-poly1305 (a curious classification, but we
|
||||||
|
// preserve it for byte-stable parity).
|
||||||
|
export function genShadowsocksLink(input: GenShadowsocksLinkInput): string {
|
||||||
|
const {
|
||||||
|
inbound,
|
||||||
|
address,
|
||||||
|
port = inbound.port,
|
||||||
|
forceTls = 'same',
|
||||||
|
remark = '',
|
||||||
|
clientPassword = '',
|
||||||
|
externalProxy = null,
|
||||||
|
} = input;
|
||||||
|
|
||||||
|
if (inbound.protocol !== 'shadowsocks') return '';
|
||||||
|
const stream = inbound.streamSettings;
|
||||||
|
if (!stream) return '';
|
||||||
|
const settings = inbound.settings;
|
||||||
|
|
||||||
|
const security = forceTls === 'same' ? stream.security : forceTls;
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
params.set('type', stream.network);
|
||||||
|
|
||||||
|
writeNetworkParams(stream, params);
|
||||||
|
applyFinalMaskToParams(stream.finalmask, params);
|
||||||
|
|
||||||
|
if (security === 'tls') {
|
||||||
|
params.set('security', 'tls');
|
||||||
|
writeTlsParams(stream, params);
|
||||||
|
applyExternalProxyTLSParams(externalProxy, params, security);
|
||||||
|
}
|
||||||
|
|
||||||
|
const isSS2022 = settings.method.substring(0, 4) === '2022';
|
||||||
|
const isSSMultiUser = settings.method !== '2022-blake3-chacha20-poly1305';
|
||||||
|
const passwords: string[] = [];
|
||||||
|
if (isSS2022) passwords.push(settings.password);
|
||||||
|
if (isSSMultiUser) passwords.push(clientPassword);
|
||||||
|
|
||||||
|
const userinfo = Base64.encode(`${settings.method}:${passwords.join(':')}`, true);
|
||||||
|
const url = new URL(`ss://${userinfo}@${address}:${port}`);
|
||||||
|
for (const [key, value] of params) url.searchParams.set(key, value);
|
||||||
|
url.hash = encodeURIComponent(remark);
|
||||||
|
return url.toString();
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,148 @@
|
||||||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||||
|
|
||||||
|
exports[`InboundSchema (full) fixtures > parses shadowsocks-tcp-2022 byte-stably 1`] = `
|
||||||
|
{
|
||||||
|
"down": 0,
|
||||||
|
"enable": true,
|
||||||
|
"expiryTime": 0,
|
||||||
|
"id": 17,
|
||||||
|
"listen": "",
|
||||||
|
"port": 8388,
|
||||||
|
"protocol": "shadowsocks",
|
||||||
|
"remark": "frank-ss-tcp-2022",
|
||||||
|
"settings": {
|
||||||
|
"clients": [
|
||||||
|
{
|
||||||
|
"comment": "",
|
||||||
|
"email": "frank@example.test",
|
||||||
|
"enable": true,
|
||||||
|
"expiryTime": 0,
|
||||||
|
"limitIp": 0,
|
||||||
|
"method": "",
|
||||||
|
"password": "dGVzdC1jbGllbnQtcGFzc3dvcmQtMQ==",
|
||||||
|
"reset": 0,
|
||||||
|
"subId": "ss-001",
|
||||||
|
"tgId": 0,
|
||||||
|
"totalGB": 0,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"ivCheck": false,
|
||||||
|
"method": "2022-blake3-aes-256-gcm",
|
||||||
|
"network": "tcp,udp",
|
||||||
|
"password": "ZmFrZS1zZXJ2ZXItcGFzc3dvcmQtMDAwMQ==",
|
||||||
|
},
|
||||||
|
"sniffing": {
|
||||||
|
"destOverride": [
|
||||||
|
"http",
|
||||||
|
"tls",
|
||||||
|
"quic",
|
||||||
|
"fakedns",
|
||||||
|
],
|
||||||
|
"domainsExcluded": [],
|
||||||
|
"enabled": true,
|
||||||
|
"ipsExcluded": [],
|
||||||
|
"metadataOnly": false,
|
||||||
|
"routeOnly": false,
|
||||||
|
},
|
||||||
|
"streamSettings": {
|
||||||
|
"network": "tcp",
|
||||||
|
"security": "none",
|
||||||
|
"tcpSettings": {
|
||||||
|
"header": {
|
||||||
|
"type": "none",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"tag": "inbound-ss-2022",
|
||||||
|
"total": 0,
|
||||||
|
"up": 0,
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`InboundSchema (full) fixtures > parses trojan-ws-tls byte-stably 1`] = `
|
||||||
|
{
|
||||||
|
"down": 0,
|
||||||
|
"enable": true,
|
||||||
|
"expiryTime": 0,
|
||||||
|
"id": 13,
|
||||||
|
"listen": "",
|
||||||
|
"port": 443,
|
||||||
|
"protocol": "trojan",
|
||||||
|
"remark": "eve-trojan-ws-tls",
|
||||||
|
"settings": {
|
||||||
|
"clients": [
|
||||||
|
{
|
||||||
|
"comment": "",
|
||||||
|
"email": "eve@example.test",
|
||||||
|
"enable": true,
|
||||||
|
"expiryTime": 0,
|
||||||
|
"limitIp": 0,
|
||||||
|
"password": "trojan-test-pw-XYZ",
|
||||||
|
"reset": 0,
|
||||||
|
"subId": "trj-001",
|
||||||
|
"tgId": 0,
|
||||||
|
"totalGB": 0,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"fallbacks": [],
|
||||||
|
},
|
||||||
|
"sniffing": {
|
||||||
|
"destOverride": [
|
||||||
|
"http",
|
||||||
|
"tls",
|
||||||
|
"quic",
|
||||||
|
"fakedns",
|
||||||
|
],
|
||||||
|
"domainsExcluded": [],
|
||||||
|
"enabled": true,
|
||||||
|
"ipsExcluded": [],
|
||||||
|
"metadataOnly": false,
|
||||||
|
"routeOnly": false,
|
||||||
|
},
|
||||||
|
"streamSettings": {
|
||||||
|
"network": "ws",
|
||||||
|
"security": "tls",
|
||||||
|
"tlsSettings": {
|
||||||
|
"alpn": [
|
||||||
|
"h2",
|
||||||
|
"http/1.1",
|
||||||
|
],
|
||||||
|
"certificates": [
|
||||||
|
{
|
||||||
|
"buildChain": false,
|
||||||
|
"certificateFile": "/etc/ssl/certs/trojan.crt",
|
||||||
|
"keyFile": "/etc/ssl/private/trojan.key",
|
||||||
|
"oneTimeLoading": false,
|
||||||
|
"usage": "encipherment",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"cipherSuites": "",
|
||||||
|
"disableSystemRoot": false,
|
||||||
|
"echServerKeys": "",
|
||||||
|
"enableSessionResumption": false,
|
||||||
|
"maxVersion": "1.3",
|
||||||
|
"minVersion": "1.2",
|
||||||
|
"rejectUnknownSni": false,
|
||||||
|
"serverName": "trojan.example.test",
|
||||||
|
"settings": {
|
||||||
|
"echConfigList": "",
|
||||||
|
"fingerprint": "chrome",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"wsSettings": {
|
||||||
|
"acceptProxyProtocol": false,
|
||||||
|
"headers": {},
|
||||||
|
"heartbeatPeriod": 0,
|
||||||
|
"host": "trojan.example.test",
|
||||||
|
"path": "/trojan",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"tag": "inbound-trojan-ws",
|
||||||
|
"total": 0,
|
||||||
|
"up": 0,
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`InboundSchema (full) fixtures > parses vless-tcp-reality byte-stably 1`] = `
|
exports[`InboundSchema (full) fixtures > parses vless-tcp-reality byte-stably 1`] = `
|
||||||
{
|
{
|
||||||
"down": 0,
|
"down": 0,
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,49 @@
|
||||||
|
{
|
||||||
|
"id": 17,
|
||||||
|
"up": 0,
|
||||||
|
"down": 0,
|
||||||
|
"total": 0,
|
||||||
|
"remark": "frank-ss-tcp-2022",
|
||||||
|
"enable": true,
|
||||||
|
"expiryTime": 0,
|
||||||
|
"listen": "",
|
||||||
|
"port": 8388,
|
||||||
|
"tag": "inbound-ss-2022",
|
||||||
|
"sniffing": {
|
||||||
|
"enabled": true,
|
||||||
|
"destOverride": ["http", "tls", "quic", "fakedns"],
|
||||||
|
"metadataOnly": false,
|
||||||
|
"routeOnly": false,
|
||||||
|
"ipsExcluded": [],
|
||||||
|
"domainsExcluded": []
|
||||||
|
},
|
||||||
|
"protocol": "shadowsocks",
|
||||||
|
"settings": {
|
||||||
|
"method": "2022-blake3-aes-256-gcm",
|
||||||
|
"password": "ZmFrZS1zZXJ2ZXItcGFzc3dvcmQtMDAwMQ==",
|
||||||
|
"network": "tcp,udp",
|
||||||
|
"clients": [
|
||||||
|
{
|
||||||
|
"method": "",
|
||||||
|
"password": "dGVzdC1jbGllbnQtcGFzc3dvcmQtMQ==",
|
||||||
|
"email": "frank@example.test",
|
||||||
|
"limitIp": 0,
|
||||||
|
"totalGB": 0,
|
||||||
|
"expiryTime": 0,
|
||||||
|
"enable": true,
|
||||||
|
"tgId": 0,
|
||||||
|
"subId": "ss-001",
|
||||||
|
"comment": "",
|
||||||
|
"reset": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"ivCheck": false
|
||||||
|
},
|
||||||
|
"streamSettings": {
|
||||||
|
"network": "tcp",
|
||||||
|
"tcpSettings": {
|
||||||
|
"header": { "type": "none" }
|
||||||
|
},
|
||||||
|
"security": "none"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,73 @@
|
||||||
|
{
|
||||||
|
"id": 13,
|
||||||
|
"up": 0,
|
||||||
|
"down": 0,
|
||||||
|
"total": 0,
|
||||||
|
"remark": "eve-trojan-ws-tls",
|
||||||
|
"enable": true,
|
||||||
|
"expiryTime": 0,
|
||||||
|
"listen": "",
|
||||||
|
"port": 443,
|
||||||
|
"tag": "inbound-trojan-ws",
|
||||||
|
"sniffing": {
|
||||||
|
"enabled": true,
|
||||||
|
"destOverride": ["http", "tls", "quic", "fakedns"],
|
||||||
|
"metadataOnly": false,
|
||||||
|
"routeOnly": false,
|
||||||
|
"ipsExcluded": [],
|
||||||
|
"domainsExcluded": []
|
||||||
|
},
|
||||||
|
"protocol": "trojan",
|
||||||
|
"settings": {
|
||||||
|
"clients": [
|
||||||
|
{
|
||||||
|
"password": "trojan-test-pw-XYZ",
|
||||||
|
"email": "eve@example.test",
|
||||||
|
"limitIp": 0,
|
||||||
|
"totalGB": 0,
|
||||||
|
"expiryTime": 0,
|
||||||
|
"enable": true,
|
||||||
|
"tgId": 0,
|
||||||
|
"subId": "trj-001",
|
||||||
|
"comment": "",
|
||||||
|
"reset": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"fallbacks": []
|
||||||
|
},
|
||||||
|
"streamSettings": {
|
||||||
|
"network": "ws",
|
||||||
|
"wsSettings": {
|
||||||
|
"acceptProxyProtocol": false,
|
||||||
|
"path": "/trojan",
|
||||||
|
"host": "trojan.example.test",
|
||||||
|
"headers": {},
|
||||||
|
"heartbeatPeriod": 0
|
||||||
|
},
|
||||||
|
"security": "tls",
|
||||||
|
"tlsSettings": {
|
||||||
|
"serverName": "trojan.example.test",
|
||||||
|
"minVersion": "1.2",
|
||||||
|
"maxVersion": "1.3",
|
||||||
|
"cipherSuites": "",
|
||||||
|
"rejectUnknownSni": false,
|
||||||
|
"disableSystemRoot": false,
|
||||||
|
"enableSessionResumption": false,
|
||||||
|
"certificates": [
|
||||||
|
{
|
||||||
|
"certificateFile": "/etc/ssl/certs/trojan.crt",
|
||||||
|
"keyFile": "/etc/ssl/private/trojan.key",
|
||||||
|
"oneTimeLoading": false,
|
||||||
|
"usage": "encipherment",
|
||||||
|
"buildChain": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"alpn": ["h2", "http/1.1"],
|
||||||
|
"echServerKeys": "",
|
||||||
|
"settings": {
|
||||||
|
"fingerprint": "chrome",
|
||||||
|
"echConfigList": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
/// <reference types="vite/client" />
|
/// <reference types="vite/client" />
|
||||||
import { describe, expect, it } from 'vitest';
|
import { describe, expect, it } from 'vitest';
|
||||||
|
|
||||||
import { genVlessLink, genVmessLink } from '@/lib/xray/inbound-link';
|
import { genShadowsocksLink, genTrojanLink, genVlessLink, genVmessLink } 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';
|
||||||
|
|
||||||
|
|
@ -96,3 +96,68 @@ describe('genVlessLink parity', () => {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('genTrojanLink parity', () => {
|
||||||
|
const fixtures = fixturesForProtocol('trojan');
|
||||||
|
expect(fixtures.length, 'need at least one trojan full-inbound fixture').toBeGreaterThan(0);
|
||||||
|
|
||||||
|
for (const [name, raw] of fixtures) {
|
||||||
|
it(`${name}: matches legacy Inbound.genTrojanLink`, () => {
|
||||||
|
const typed = InboundSchema.parse(raw);
|
||||||
|
const settings = (raw as { settings: { clients: Array<{ password: string }> } }).settings;
|
||||||
|
const client = settings.clients[0];
|
||||||
|
|
||||||
|
const address = 'example.test';
|
||||||
|
const port = typed.port;
|
||||||
|
const remark = 'parity-test';
|
||||||
|
|
||||||
|
const newLink = genTrojanLink({
|
||||||
|
inbound: typed,
|
||||||
|
address,
|
||||||
|
port,
|
||||||
|
forceTls: 'same',
|
||||||
|
remark,
|
||||||
|
clientPassword: client.password,
|
||||||
|
externalProxy: null,
|
||||||
|
});
|
||||||
|
|
||||||
|
const legacy = LegacyInbound.fromJson(raw);
|
||||||
|
const legacyLink = legacy.genTrojanLink(address, port, 'same', remark, client.password, null);
|
||||||
|
|
||||||
|
expect(newLink).toBe(legacyLink);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('genShadowsocksLink parity', () => {
|
||||||
|
const fixtures = fixturesForProtocol('shadowsocks');
|
||||||
|
expect(fixtures.length, 'need at least one shadowsocks full-inbound fixture').toBeGreaterThan(0);
|
||||||
|
|
||||||
|
for (const [name, raw] of fixtures) {
|
||||||
|
it(`${name}: matches legacy Inbound.genSSLink`, () => {
|
||||||
|
const typed = InboundSchema.parse(raw);
|
||||||
|
const settings = (raw as { settings: { clients?: Array<{ password: string }> } }).settings;
|
||||||
|
const client = settings.clients?.[0];
|
||||||
|
|
||||||
|
const address = 'example.test';
|
||||||
|
const port = typed.port;
|
||||||
|
const remark = 'parity-test';
|
||||||
|
const clientPassword = client?.password ?? '';
|
||||||
|
|
||||||
|
const newLink = genShadowsocksLink({
|
||||||
|
inbound: typed,
|
||||||
|
address,
|
||||||
|
port,
|
||||||
|
forceTls: 'same',
|
||||||
|
remark,
|
||||||
|
clientPassword,
|
||||||
|
externalProxy: null,
|
||||||
|
});
|
||||||
|
|
||||||
|
const legacy = LegacyInbound.fromJson(raw);
|
||||||
|
const legacyLink = legacy.genSSLink(address, port, 'same', remark, clientPassword, null);
|
||||||
|
|
||||||
|
expect(newLink).toBe(legacyLink);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue