chore: initial commit

This commit is contained in:
2026-10-22 17:07:08 +08:00
commit 6ccb102d38
20 changed files with 5949 additions and 0 deletions
+284
View File
@@ -0,0 +1,284 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import {createServer} from 'node:http';
import {EventEmitter, once} from 'node:events';
import {randomUUID} from 'node:crypto';
import {connect as connect_tcp} from 'node:net';
import {Server} from 'socket.io';
import {io as client} from 'socket.io-client';
import type {Socket as ClientSocket} from 'socket.io-client';
import {start_forward_server, stop_forward_server} from '../index.ts';
import {DEFAULT_LIMITS, resolve_options} from '../utils/relay_config.ts';
import {encode_token} from '../utils/token_utils.ts';
import type {BotLogRecord, ForwardAck, RelayClaims, SessionScope} from '../types.ts';
const key = 'lifecycle-test-secret-no-production-value';
const options = () => ({forward_id: 0, key, port: 0, allowed_origins: ['https://console.example']});
const address = (server: ReturnType<typeof createServer>) => {
const value = server.address();
assert.ok(value && typeof value === 'object');
return `http://127.0.0.1:${value.port}`;
};
async function connect(socket: ClientSocket) {
await new Promise<void>((resolve, reject) => {
socket.once('connect', resolve);
socket.once('connect_error', reject);
socket.connect();
});
}
function control_request(control: EventEmitter, event: string, value: unknown): ForwardAck<unknown> {
let result: ForwardAck<unknown> | undefined;
control.emit(event, value, (ack: ForwardAck<unknown>) => { result = ack; });
assert.ok(result, `Missing control ACK for ${event}`);
return result;
}
test('configuration validates explicit origins, IDs, limits and required secret', () => {
assert.throws(() => resolve_options({...options(), key: ''}));
assert.throws(() => resolve_options({...options(), forward_id: -1}));
assert.throws(() => resolve_options({...options(), allowed_origins: ['*']}));
assert.throws(() => resolve_options({...options(), limits: {rpc_timeout_ms: 0}}));
assert.equal(DEFAULT_LIMITS.rpc_timeout_ms, 5_000);
});
test('concurrent startup shares one listening service, stop is idempotent and restart works', async t => {
t.after(stop_forward_server);
const input = options();
const [a, b] = await Promise.all([start_forward_server(input), start_forward_server(input)]);
assert.equal(a, b);
assert.ok(a.http_server.listening);
await assert.rejects(start_forward_server({...input, forward_id: 2}), /different options/);
await Promise.all([a.stop(), a.stop()]);
assert.equal(a.http_server.listening, false);
const restarted = await start_forward_server(options());
assert.notEqual(restarted, a);
assert.ok(restarted.http_server.listening);
});
test('port collision rejects startup and resets initialization for retry', async t => {
const occupied = createServer();
occupied.listen(0, '127.0.0.1');
await once(occupied, 'listening');
t.after(async () => {
await stop_forward_server();
await new Promise<void>(resolve => occupied.close(() => resolve()));
});
const bound = occupied.address();
assert.ok(bound && typeof bound === 'object');
await assert.rejects(start_forward_server({...options(), port: bound.port, host: '127.0.0.1'}), /EADDRINUSE/);
const relay = await start_forward_server(options());
assert.ok(relay.http_server.listening);
});
test('shutdown deadline terminates owned stalled HTTP connections and allows restart', async t => {
t.after(stop_forward_server);
const relay = await start_forward_server({...options(), limits: {close_timeout_ms: 30}});
const bound = relay.http_server.address();
assert.ok(bound && typeof bound === 'object');
const stalled = connect_tcp({host: '127.0.0.1', port: bound.port});
t.after(() => stalled.destroy());
await once(stalled, 'connect');
stalled.write('GET / HTTP/1.1\r\nHost: localhost\r\n');
const closed = once(stalled, 'close');
await assert.rejects(relay.stop(), /shutdown timed out/);
await closed;
const restarted = await start_forward_server(options());
assert.ok(restarted.http_server.listening);
});
test('borrowed host and unrelated Socket.IO stay alive through relay stop and restart', async t => {
const host = createServer((_request, response) => response.end('main-alive'));
const control = new EventEmitter();
const input = {forward_id: 0, key, http_server: host, allowed_origins: [] as string[], control};
let relay = await start_forward_server(input);
// Main installs its own Socket.IO service after the embedded relay.
const main_io = new Server(host, {path: '/api/node', serveClient: false});
host.listen(0, '127.0.0.1');
await once(host, 'listening');
const main_client = client(address(host), {path: '/api/node', autoConnect: false, reconnection: false});
t.after(async () => {
main_client.disconnect();
await stop_forward_server();
await main_io.close();
});
await connect(main_client);
for (let count = 0; count < 3; count++) {
await relay.stop();
assert.equal(host.listening, true);
assert.equal(main_client.connected, true);
assert.equal(await (await fetch(`${address(host)}/health`)).text(), 'main-alive');
assert.equal((await fetch(`${address(host)}/api/forward_server/?EIO=4&transport=polling`)).status, 503);
relay = await start_forward_server(input);
const response = await fetch(`${address(host)}/api/forward_server/?EIO=4&transport=polling`);
assert.equal(response.status, 200);
assert.match(await response.text(), /^0\{/);
assert.equal(control.listenerCount('forward_open_session'), 1);
}
assert.ok(host.listenerCount('request') <= 3);
});
test('polling and websocket enforce origins and use authenticated control scope', async t => {
const control = new EventEmitter();
const relay = await start_forward_server({...options(), control});
const sockets: ClientSocket[] = [];
t.after(async () => {
for (const socket of sockets) socket.disconnect();
await stop_forward_server();
});
const scope: SessionScope = {
forward_id: 0, forward_session_id: randomUUID(), node_session_id: 'authenticated-main-node-session',
node_id: 10, bot_id: 20,
};
const now = Date.now();
assert.equal(control_request(control, 'forward_open_session', {...scope, lease_expires_at: now + 60_000}).status, 'success');
const grant_id = randomUUID();
assert.equal(control_request(control, 'forward_authorize_grant', {
forward_session_id: scope.forward_session_id, grant_id, user_id: 30,
permissions: ['bot.snapshot'], expires_at: now + 60_000,
}).status, 'success');
const claims: RelayClaims = {
...scope, v: 1, role: 'web', user_id: 30, grant_id, permissions: ['bot.snapshot'],
issued_at: now, join_before: now + 30_000, expires_at: now + 60_000, jti: randomUUID(),
};
for (const transport of ['polling', 'websocket'] as const) {
const socket = client(address(relay.http_server), {
path: relay.path, transports: [transport], autoConnect: false, reconnection: false,
extraHeaders: {Origin: 'https://console.example'},
});
sockets.push(socket);
await connect(socket);
const ack = await socket.timeout(2_000).emitWithAck('join_room', {token: encode_token(key, claims)});
assert.equal(ack.status, 'success');
assert.equal(ack.data.node_ready, false);
socket.disconnect();
const forbidden = client(address(relay.http_server), {
path: relay.path, transports: [transport], autoConnect: false, reconnection: false,
extraHeaders: {Origin: 'https://attacker.example'},
});
sockets.push(forbidden);
await assert.rejects(connect(forbidden));
}
});
test('default transport and validators relay large log history over polling and websocket', async t => {
const control = new EventEmitter();
const relay = await start_forward_server({...options(), control});
const sockets: ClientSocket[] = [];
t.after(async () => {
for (const socket of sockets) socket.disconnect();
await stop_forward_server();
});
const scope: SessionScope = {forward_id: 0, forward_session_id: randomUUID(),
node_session_id: 'large-history-node-session', node_id: 10, bot_id: 20};
const now = Date.now();
assert.equal(control_request(control, 'forward_open_session', {...scope,
lease_expires_at: now + 60_000}).status, 'success');
const records: BotLogRecord[] = Array.from({length: 20}, (_, index): BotLogRecord => ({id: randomUUID(),
timestamp: now, context_generation: null, seq: index + 1,
segments: Array.from({length: 32}, () => ({message: '\u754c'.repeat(1024),
font_color: '#ffffff', font_style: ['normal']}))}));
const result = {status: 'success', data: {stream_id: randomUUID(), seq: 20, records, truncated: false}};
assert.ok(Buffer.byteLength(JSON.stringify(result)) > 1024 * 1024);
for (const transport of ['polling', 'websocket'] as const) {
const grant_id = randomUUID();
assert.equal(control_request(control, 'forward_authorize_grant', {
forward_session_id: scope.forward_session_id, grant_id, user_id: 30,
permissions: ['bot.logs'], expires_at: now + 60_000,
}).status, 'success');
const base: RelayClaims = {...scope, v: 1, role: 'node', permissions: ['bot.logs'],
issued_at: now, join_before: now + 30_000, expires_at: now + 60_000, jti: randomUUID()};
const node = client(address(relay.http_server), {path: relay.path, transports: [transport],
autoConnect: false, reconnection: false});
sockets.push(node);
node.on('relay_request', (_request, reply) => reply(result));
await connect(node);
assert.equal((await node.timeout(2_000).emitWithAck('join_room', {token: encode_token(key, base)})).status, 'success');
assert.equal((await node.timeout(2_000).emitWithAck('forward_node_ready', {
forward_session_id: scope.forward_session_id})).status, 'success');
const web = client(address(relay.http_server), {path: relay.path, transports: [transport],
autoConnect: false, reconnection: false});
sockets.push(web);
await connect(web);
assert.equal((await web.timeout(2_000).emitWithAck('join_room', {token: encode_token(key, {
...base, role: 'web', grant_id, user_id: 30, jti: randomUUID(),
})})).status, 'success');
const history = await web.timeout(5_000).emitWithAck('relay_request', {
request_id: randomUUID(), method: 'get_log_history', payload: {},
});
assert.deepEqual(history, result);
assert.equal(node.connected, true);
assert.equal(web.connected, true);
web.disconnect();
node.disconnect();
}
});
test('control rebinding removes only owned listeners and stale disconnect cannot disable new control', async t => {
const first = new EventEmitter();
const second = new EventEmitter();
let unrelated = 0;
first.on('forward_open_session', () => { unrelated++; });
const relay = await start_forward_server({...options(), control: first});
t.after(stop_forward_server);
const dispose = relay.bind_control(second);
assert.equal(first.listenerCount('forward_open_session'), 1);
first.emit('disconnect');
assert.equal(relay.store.control_available, true);
first.emit('forward_open_session');
assert.equal(unrelated, 1);
second.emit('disconnect');
assert.equal(relay.store.control_available, false);
dispose();
assert.equal(second.listenerCount('forward_open_session'), 0);
});
test('borrowed host dispatches custom namespace WebSocket without closing an existing Main connection', async t => {
const host = createServer((_request, response) => response.end('main'));
const main_io = new Server(host, {path: '/api/node', serveClient: false});
host.listen(0, '127.0.0.1');
await once(host, 'listening');
const main_client = client(address(host), {path: '/api/node', autoConnect: false, reconnection: false});
await connect(main_client);
const relay = await start_forward_server({forward_id: 0, key, http_server: host,
allowed_origins: [], namespace: '/relay'});
const web = client(`${address(host)}/relay`, {path: relay.path,
transports: ['websocket'], autoConnect: false, reconnection: false});
t.after(async () => {
web.disconnect();
main_client.disconnect();
await stop_forward_server();
await main_io.close();
});
await connect(web);
const result = await web.timeout(1_000).emitWithAck('forward_open_session', {});
assert.equal(result.status, 'error');
assert.equal(result.data.code, 'FORBIDDEN');
await relay.stop();
assert.equal(main_client.connected, true);
assert.equal(host.listening, true);
});
test('closing and relistening the borrowed host reuses its relay dispatcher', async t => {
const host = createServer((_request, response) => response.end('main'));
const input = {forward_id: 0, key, http_server: host, allowed_origins: [] as string[]};
let relay = await start_forward_server(input);
host.listen(0, '127.0.0.1');
await once(host, 'listening');
t.after(async () => {
await stop_forward_server();
if (host.listening) await new Promise<void>(resolve => host.close(() => resolve()));
});
const listener_counts = {request: host.listenerCount('request'), upgrade: host.listenerCount('upgrade')};
await new Promise<void>(resolve => host.close(() => resolve()));
await relay.stop();
relay = await start_forward_server(input);
host.listen(0, '127.0.0.1');
await once(host, 'listening');
const web = client(address(host), {path: relay.path, transports: ['websocket'],
autoConnect: false, reconnection: false});
t.after(() => web.disconnect());
await connect(web);
assert.equal(host.listenerCount('request'), listener_counts.request);
assert.equal(host.listenerCount('upgrade'), listener_counts.upgrade);
assert.equal(await (await fetch(`${address(host)}/`)).text(), 'main');
});