feat: relay world snapshot and voxel payloads
Allow world.* methods through the relay, validate their payloads, and track per-session world revisions in the session store.
This commit is contained in:
@@ -278,36 +278,75 @@ test('tab_complete dispatch rejects ranges beyond request text and accepts exact
|
||||
} finally {await f.close();}
|
||||
});
|
||||
|
||||
test('bot_event is rejected while bot_log checks permission, scope and sequence', async () => {
|
||||
test('status lifecycle events fan out to snapshot readers with scope, permission and sequence checks', async () => {
|
||||
const f = await fixture();
|
||||
try {
|
||||
const node = await f.node(); const web = await f.web();
|
||||
const limited = await f.web(randomUUID(), ['bot.subscribe']);
|
||||
await ack(web.socket, 'relay_request', request('bot.subscribe', {events: ['status_update', 'log']}));
|
||||
await ack(limited.socket, 'relay_request', request('bot.subscribe', {events: ['status_update']}));
|
||||
let limited_events = 0; limited.socket.on('bot_event', () => limited_events++);
|
||||
const node = await f.node();
|
||||
const web = await f.web(randomUUID(), ['bot.snapshot']);
|
||||
const log_reader = await f.web(randomUUID(), ['bot.logs']);
|
||||
let log_reader_events = 0;
|
||||
log_reader.socket.on('bot_event', () => log_reader_events++);
|
||||
const stream = randomUUID();
|
||||
const event = {forward_session_id: f.s.forward_session_id, bot_id: f.s.bot_id, context_generation: null as string | null,
|
||||
stream_id: stream, seq: 1, emitted_at: Date.now(), name: 'status_update', payload: {status: 'offline', context_exists: false}};
|
||||
let web_events = 0; web.socket.on('bot_event', () => web_events++);
|
||||
code(await ack(node, 'bot_event', event), 'FORBIDDEN');
|
||||
code(await ack(web.socket, 'bot_event', {...event, seq: 2}), 'FORBIDDEN');
|
||||
code(await ack(web.socket, 'forward_open_session', {}), 'FORBIDDEN');
|
||||
node.emit('bot_event', {...event, seq: 3});
|
||||
node.on('relay_request', (_value, reply) => reply(snapshot(f.s.bot_id)));
|
||||
assert.equal((await ack(web.socket, 'relay_request', request('bot.snapshot'))).status, 'success');
|
||||
assert.equal(web_events, 0);
|
||||
const log = {...event, name: 'log', payload: {records: [] as never[]}};
|
||||
let limited_logs = 0; limited.socket.on('bot_log', () => limited_logs++);
|
||||
const delivered = received(web.socket, 'bot_log');
|
||||
const context_generation = randomUUID();
|
||||
const event = (seq: number, status: string, context_exists: boolean) => ({
|
||||
forward_session_id: f.s.forward_session_id, bot_id: f.s.bot_id,
|
||||
context_generation: context_exists ? context_generation : null,
|
||||
stream_id: stream, seq, emitted_at: Date.now(), name: 'status_update',
|
||||
payload: {status, context_exists},
|
||||
});
|
||||
for (const [seq, status, context_exists] of [[1, 'online', true], [2, 'disconnecting', true],
|
||||
[3, 'offline', true], [4, 'offline', false]] as const) {
|
||||
const expected = event(seq, status, context_exists);
|
||||
const delivered = received(web.socket, 'bot_event');
|
||||
assert.deepEqual(await ack(node, 'bot_event', expected), {status: 'success', data: {accepted: true}});
|
||||
assert.deepEqual((await delivered)[0], expected);
|
||||
}
|
||||
assert.equal(log_reader_events, 0);
|
||||
|
||||
code(await ack(web.socket, 'bot_event', event(5, 'offline', false)), 'FORBIDDEN');
|
||||
for (const [name, payload] of [['bot_error', {message: 'unexpected'}], ['message', {text: 'hello', position: 'chat'}],
|
||||
['log', {records: []}], ['arbitrary', {}], ['status_update', {status: 'invalid', context_exists: false}],
|
||||
['status_update', {status: 'offline', context_exists: false, extra: true}]] as const) {
|
||||
code(await ack(node, 'bot_event', {...event(5, 'offline', false), name, payload}), 'INVALID_REQUEST');
|
||||
}
|
||||
code(await ack(node, 'bot_event', event(5, 'online', false)), 'INVALID_REQUEST');
|
||||
code(await ack(node, 'bot_event', {...event(5, 'offline', true), context_generation: null}), 'INVALID_REQUEST');
|
||||
code(await ack(node, 'bot_event', {...event(5, 'offline', false), seq: -1}), 'INVALID_REQUEST');
|
||||
code(await ack(node, 'bot_event', {...event(5, 'offline', false), extra: true}), 'INVALID_REQUEST');
|
||||
code(await ack(node, 'bot_event', event(4, 'offline', false)), 'INVALID_REQUEST');
|
||||
code(await ack(node, 'bot_event', {...event(5, 'offline', false), bot_id: f.s.bot_id + 1}), 'FORBIDDEN');
|
||||
code(await ack(node, 'bot_event', {...event(5, 'offline', false), forward_session_id: randomUUID()}), 'FORBIDDEN');
|
||||
const next = event(5, 'offline', false);
|
||||
const delivered = received(web.socket, 'bot_event');
|
||||
node.emit('bot_event', next);
|
||||
assert.deepEqual((await delivered)[0], next);
|
||||
|
||||
const log = {...next, seq: 6, name: 'log', payload: {records: [] as never[]}};
|
||||
let limited_logs = 0; web.socket.on('bot_log', () => limited_logs++);
|
||||
const log_delivered = received(log_reader.socket, 'bot_log');
|
||||
assert.equal((await ack(node, 'bot_log', log)).status, 'success');
|
||||
assert.deepEqual((await delivered)[0], log);
|
||||
code(await ack(node, 'bot_log', log), 'INVALID_REQUEST');
|
||||
code(await ack(web.socket, 'bot_log', {...log, seq: 2}), 'FORBIDDEN');
|
||||
code(await ack(node, 'bot_log', {...log, seq: 2, bot_id: f.s.bot_id + 1}), 'FORBIDDEN');
|
||||
code(await ack(node, 'bot_log', {...event, seq: 2}), 'INVALID_REQUEST');
|
||||
assert.deepEqual((await log_delivered)[0], log);
|
||||
code(await ack(node, 'bot_log', {...log, seq: 5}), 'INVALID_REQUEST');
|
||||
code(await ack(web.socket, 'bot_log', {...log, seq: 6}), 'FORBIDDEN');
|
||||
code(await ack(node, 'bot_log', {...log, seq: 6, bot_id: f.s.bot_id + 1}), 'FORBIDDEN');
|
||||
code(await ack(node, 'bot_log', {...next, seq: 6}), 'INVALID_REQUEST');
|
||||
code(await ack(node, 'bot_event', event(6, 'offline', false)), 'INVALID_REQUEST');
|
||||
code(await ack(node, 'bot_event', event(5, 'offline', false)), 'INVALID_REQUEST');
|
||||
const update = {forward_session_id: next.forward_session_id, bot_id: next.bot_id,
|
||||
stream_id: stream, context_generation, emitted_at: next.emitted_at, seq: 7,
|
||||
data: {name: 'health', value: 20}};
|
||||
assert.equal((await ack(node, 'info_update', update)).status, 'success');
|
||||
code(await ack(node, 'bot_event', event(7, 'offline', false)), 'INVALID_REQUEST');
|
||||
const replacement = {...event(0, 'offline', false), stream_id: randomUUID()};
|
||||
assert.equal((await ack(node, 'bot_event', replacement)).status, 'success');
|
||||
code(await ack(node, 'bot_event', event(8, 'offline', false)), 'RESYNC_REQUIRED');
|
||||
assert.equal(limited_logs, 0);
|
||||
assert.equal(limited_events, 0);
|
||||
assert.equal(log_reader_events, 0);
|
||||
|
||||
const restricted = await f.node(['bot.logs']);
|
||||
code(await ack(restricted, 'bot_event', event(8, 'offline', false)), 'FORBIDDEN');
|
||||
const not_ready = await f.node(['bot.snapshot'], false);
|
||||
code(await ack(not_ready, 'bot_event', event(8, 'offline', false)), 'NODE_OFFLINE');
|
||||
} finally {await f.close();}
|
||||
});
|
||||
|
||||
@@ -434,6 +473,29 @@ test('info_update auto-pushes exact envelopes and shares log stream and sequence
|
||||
} finally {await f.close();}
|
||||
});
|
||||
|
||||
test('lifecycle packets use reliable bounded buffering and share the Node event rate', async () => {
|
||||
const f = await fixture({max_buffered_packets: 2, events_per_second: 3});
|
||||
try {
|
||||
const node = await f.node(['bot.snapshot', 'bot.logs']);
|
||||
const web = await f.web(randomUUID(), ['bot.snapshot']);
|
||||
const server_web = f.namespace.sockets.get(web.socket.id!)!;
|
||||
const transport = server_web.conn as unknown as {writeBuffer: unknown[]};
|
||||
server_web.conn.transport.writable = false;
|
||||
const event = {forward_session_id: f.s.forward_session_id, bot_id: f.s.bot_id,
|
||||
context_generation: null as string | null, stream_id: randomUUID(), seq: 1, emitted_at: Date.now(),
|
||||
name: 'status_update', payload: {status: 'offline', context_exists: false}};
|
||||
for (let seq = 1; seq <= 3; seq++) {
|
||||
assert.equal((await ack(node, 'bot_event', {...event, seq})).status, 'success');
|
||||
if (seq === 1) assert.equal(transport.writeBuffer.length, 1);
|
||||
}
|
||||
assert.equal(server_web.connected, false);
|
||||
assert.ok(transport.writeBuffer.length <= 4);
|
||||
const closed = received(node, 'forward_session_closed');
|
||||
node.emit('bot_log', {...event, seq: 4, name: 'log', payload: {records: []}});
|
||||
assert.equal((await closed)[0].code, 'RATE_LIMITED');
|
||||
} finally {await f.close();}
|
||||
});
|
||||
|
||||
test('info_update enforces readiness and Node and Web token permissions independently of subscriptions', async () => {
|
||||
const f = await fixture();
|
||||
try {
|
||||
@@ -619,9 +681,9 @@ test('slow-client status buffer overflow and forbidden event floods require expl
|
||||
// Notification is emitted before disconnect; packet delivery depends on Engine.IO's buffer flush.
|
||||
assert.equal(server_web.connected, false);
|
||||
await Promise.race([closed, delay(30)]);
|
||||
code(await ack(replacement, 'bot_event', event), 'FORBIDDEN');
|
||||
code(await ack(replacement, 'bot_event', event), 'NODE_OFFLINE');
|
||||
const node_closed = received(replacement, 'forward_session_closed');
|
||||
for (let seq = 2; seq <= 10; seq++) replacement.emit('bot_event', {...event, seq});
|
||||
for (let seq = 2; seq <= 10; seq++) replacement.emit('unknown', {...event, seq});
|
||||
assert.equal((await node_closed)[0].code, 'RATE_LIMITED');
|
||||
} finally {await f.close();}
|
||||
});
|
||||
@@ -660,9 +722,11 @@ test('reauth extends only the same identity, reconnect restores subscriptions, r
|
||||
const replacement = await f.connect();
|
||||
await ack(replacement, 'join_room', {token: token(f.s, 'web', web.grant_id, expiry)});
|
||||
await ack(replacement, 'relay_request', subscribe);
|
||||
code(await ack(node, 'bot_event', {forward_session_id: f.s.forward_session_id, bot_id: f.s.bot_id,
|
||||
const pushed = received(replacement, 'bot_event');
|
||||
assert.equal((await ack(node, 'bot_event', {forward_session_id: f.s.forward_session_id, bot_id: f.s.bot_id,
|
||||
context_generation: null, stream_id: randomUUID(), seq: 1, emitted_at: Date.now(),
|
||||
name: 'status_update', payload: {status: 'offline', context_exists: false}}), 'FORBIDDEN');
|
||||
name: 'status_update', payload: {status: 'offline', context_exists: false}})).status, 'success');
|
||||
assert.equal((await pushed)[0].payload.status, 'offline');
|
||||
node.on('relay_request', (_value, reply) => reply(snapshot(f.s.bot_id)));
|
||||
assert.equal((await ack(replacement, 'relay_request', request('bot.snapshot'))).status, 'success');
|
||||
f.store.revoke_grant({forward_session_id: f.s.forward_session_id, grant_id: web.grant_id, reason: 'permission_removed'});
|
||||
|
||||
Reference in New Issue
Block a user