162 lines
6.6 KiB
JavaScript
162 lines
6.6 KiB
JavaScript
import assert from 'node:assert/strict';
|
|
import test from 'node:test';
|
|
|
|
import {
|
|
parseSubscriptionBody,
|
|
selectRefreshedServer,
|
|
} from '../../dist/server/subscription.js';
|
|
import { createServerId } from '../../dist/shared/serverIdentity.js';
|
|
|
|
const parse = (outbounds) => parseSubscriptionBody(JSON.stringify({ outbounds }));
|
|
const outbound = (tag, server, server_port = 443) => ({
|
|
type: 'vless',
|
|
tag,
|
|
server,
|
|
server_port,
|
|
});
|
|
|
|
test('duplicate labels remain independently addressable by stable ID', () => {
|
|
const { config, servers } = parse([
|
|
outbound('Amsterdam', 'nl-1.example'),
|
|
outbound('Amsterdam', 'nl-2.example'),
|
|
]);
|
|
|
|
assert.equal(servers.length, 2);
|
|
assert.equal(servers[0].label, 'Amsterdam');
|
|
assert.equal(servers[1].label, 'Amsterdam');
|
|
assert.notEqual(servers[0].id, servers[1].id);
|
|
assert.deepEqual(config.outbounds.map((item) => item.tag), servers.map((server) => server.id));
|
|
});
|
|
|
|
test('provider reorder does not change server IDs or selection', () => {
|
|
const before = parse([outbound('DE', 'de.example'), outbound('NL', 'nl.example')]).servers;
|
|
const after = parse([outbound('NL', 'nl.example'), outbound('DE', 'de.example')]).servers;
|
|
const selectedId = before[1].id;
|
|
|
|
assert.deepEqual(
|
|
new Map(after.map((server) => [server.host, server.id])),
|
|
new Map(before.map((server) => [server.host, server.id])),
|
|
);
|
|
assert.equal(selectRefreshedServer(selectedId, before, after), selectedId);
|
|
});
|
|
|
|
test('cosmetic rename keeps selection for the same endpoint', () => {
|
|
const before = parse([outbound('Old name', 'nl.example')]).servers;
|
|
const after = parse([outbound('New name', 'nl.example')]).servers;
|
|
|
|
assert.equal(after[0].id, before[0].id);
|
|
assert.equal(selectRefreshedServer(before[0].id, before, after), before[0].id);
|
|
});
|
|
|
|
test('removed selected server requires an explicit new choice', () => {
|
|
const before = parse([outbound('DE', 'de.example'), outbound('NL', 'nl.example')]).servers;
|
|
const after = parse([outbound('DE', 'de.example')]).servers;
|
|
|
|
assert.equal(selectRefreshedServer(before[1].id, before, after), '');
|
|
assert.equal(selectRefreshedServer('', before, after), '');
|
|
});
|
|
|
|
test('provider placeholders never become selectable servers', () => {
|
|
const disabled = outbound('🚫 Subscription disabled', '0.0.0.0', 1);
|
|
const trafficExhausted = outbound('🚫 Traffic limit exceeded', '0.0.0.0', 1);
|
|
|
|
assert.throws(
|
|
() => parse([disabled]),
|
|
(error) => error.code === 'SUBSCRIPTION_DISABLED',
|
|
);
|
|
assert.throws(
|
|
() => parse([outbound('Account error', '::', 1)]),
|
|
(error) => error.code === 'SUBSCRIPTION_REJECTED',
|
|
);
|
|
assert.throws(
|
|
() => parse([trafficExhausted]),
|
|
(error) => error.code === 'SUBSCRIPTION_TRAFFIC_EXHAUSTED',
|
|
);
|
|
|
|
const parsed = parse([disabled, outbound('Amsterdam', 'nl.example')]);
|
|
assert.deepEqual(parsed.servers.map((server) => server.label), ['Amsterdam']);
|
|
assert.equal(parsed.config.outbounds.length, 1);
|
|
});
|
|
|
|
test('subscription parser rejects JSON primitives and arrays', () => {
|
|
for (const value of [null, [], 42, true, 'provider rejected']) {
|
|
assert.throws(
|
|
() => parseSubscriptionBody(JSON.stringify(value)),
|
|
(error) => error.code === 'SUBSCRIPTION_INVALID',
|
|
`expected ${JSON.stringify(value)} to be rejected`,
|
|
);
|
|
}
|
|
});
|
|
|
|
test('mixed Base64 VLESS feed keeps Reality TCP and TLS WebSocket servers', () => {
|
|
const reality = 'vless://00000000-0000-4000-8000-000000000001@reality.example.test:443?security=reality&type=tcp&pbk=public-key&sid=short-id&sni=cover.example.test&fp=chrome#Reality';
|
|
const websocket = 'vless://00000000-0000-4000-8000-000000000002@ws.example.test:8443?encryption=none&security=tls&sni=edge.example.test&fp=firefox&alpn=h2%2Chttp%2F1.1&type=ws&host=edge.example.test&path=%2Fsocket%3Fed%3D2048#TLS%20WS';
|
|
const websocketVariant = 'vless://00000000-0000-4000-8000-000000000002@ws.example.test:8443?encryption=none&security=tls&sni=edge.example.test&fp=firefox&alpn=h2%2Chttp%2F1.1&type=ws&host=edge.example.test&path=%2Fsecond#TLS%20WS%202';
|
|
const { config, servers } = parseSubscriptionBody(
|
|
Buffer.from(`${reality}\n${websocket}\n${websocketVariant}`).toString('base64'),
|
|
);
|
|
const [realityOutbound, websocketOutbound, websocketVariantOutbound] = config.outbounds;
|
|
const singleton = parseSubscriptionBody(Buffer.from(websocket).toString('base64'));
|
|
const reordered = parseSubscriptionBody(
|
|
Buffer.from(`${websocketVariant.replace('TLS%20WS%202', 'Renamed')}\n${websocket.replace('TLS%20WS', 'Also%20renamed')}\n${reality}`).toString('base64'),
|
|
);
|
|
const idsByPath = (outbounds) => Object.fromEntries(outbounds
|
|
.filter((outbound) => outbound.transport?.type === 'ws')
|
|
.map((outbound) => [outbound.transport.path, outbound.tag]));
|
|
|
|
assert.equal(servers.length, 3);
|
|
assert.notEqual(websocketOutbound.tag, websocketVariantOutbound.tag);
|
|
assert.equal(singleton.servers[0].id, websocketOutbound.tag);
|
|
assert.notEqual(singleton.servers[0].id, createServerId(singleton.config.outbounds[0]));
|
|
assert.deepEqual(idsByPath(reordered.config.outbounds), idsByPath(config.outbounds));
|
|
assert.deepEqual(realityOutbound.tls.reality, {
|
|
enabled: true,
|
|
public_key: 'public-key',
|
|
short_id: 'short-id',
|
|
});
|
|
assert.equal(realityOutbound.transport, undefined);
|
|
assert.deepEqual(websocketOutbound.tls, {
|
|
enabled: true,
|
|
server_name: 'edge.example.test',
|
|
alpn: ['h2', 'http/1.1'],
|
|
utls: { enabled: true, fingerprint: 'firefox' },
|
|
});
|
|
assert.deepEqual(websocketOutbound.transport, {
|
|
type: 'ws',
|
|
path: '/socket?ed=2048',
|
|
headers: { Host: 'edge.example.test' },
|
|
});
|
|
assert.equal(websocketOutbound.packet_encoding, 'xudp');
|
|
assert.equal(websocketVariantOutbound.transport.path, '/second');
|
|
const legacyWebsocketId = createServerId(singleton.config.outbounds[0]);
|
|
const legacyWebsocket = [{ ...singleton.servers[0], id: legacyWebsocketId }];
|
|
assert.equal(selectRefreshedServer(
|
|
legacyWebsocketId,
|
|
legacyWebsocket,
|
|
singleton.servers,
|
|
), singleton.servers[0].id);
|
|
assert.equal(selectRefreshedServer(
|
|
legacyWebsocketId,
|
|
legacyWebsocket,
|
|
servers.slice(1),
|
|
), '');
|
|
assert.equal(selectRefreshedServer(
|
|
legacyWebsocketId,
|
|
legacyWebsocket,
|
|
[legacyWebsocket[0], singleton.servers[0]],
|
|
), '');
|
|
assert.equal(selectRefreshedServer(
|
|
websocketOutbound.tag,
|
|
servers,
|
|
[servers[0], servers[2]],
|
|
), '');
|
|
assert.throws(
|
|
() => parseSubscriptionBody('vless://00000000-0000-4000-8000-000000000003@grpc.example.test:443?security=tls&type=grpc&pbk=public-key&sid=short-id'),
|
|
(error) => error.code === 'SUBSCRIPTION_INVALID',
|
|
);
|
|
assert.equal(
|
|
parseSubscriptionBody(reality.replace('type=tcp', 'type=raw')).config.outbounds[0].transport,
|
|
undefined,
|
|
);
|
|
});
|