fix(chat): preserve mixed-version compatibility

This commit is contained in:
Timo
2026-07-26 01:02:20 +02:00
parent 17c3ea295a
commit 77790a279c
26 changed files with 236 additions and 35 deletions
+37 -2
View File
@@ -23,7 +23,10 @@ async function c() {
function s(ws, evt, d={}) { ws.send(`42${JSON.stringify([evt,d])}`); }
function a(ws) { if (ws._m.length) { const r=ws._m.shift(); return r.startsWith('42') ? JSON.parse(r.substring(2)) : r; } return new Promise((resolve, reject) => { const t=setTimeout(()=>reject(Error('timeout')),3e3); const h=(d)=>{clearTimeout(t);ws.removeListener('message',h);const r=d.toString();resolve(r.startsWith('42')?JSON.parse(r.substring(2)):r);};ws.on('message',h);}); }
async function w(ws, evt, ms=3000) { const st=Date.now(); while(Date.now()-st<ms) { for(let i=0;i<ws._m.length;i++){const r=ws._m[i];ws._m.splice(i,1);if(r.startsWith('42')){try{const[e]=JSON.parse(r.substring(2));if(e===evt)return e}catch{/* skip */}}} await new Promise(r=>setTimeout(r,50));} throw Error(`wait:${evt}`); }
async function j(ws, rid, pid, pw=null) { s(ws,'join_room',{roomId:rid,peerId:pid,password:pw,protocolVersion:'1.0.0'}); assert.equal((await a(ws))[0],'room_data'); }
async function j(ws, rid, pid, pw=null, clientCapabilities=undefined) {
s(ws,'join_room',{roomId:rid,peerId:pid,password:pw,protocolVersion:'1.0.0',clientCapabilities});
assert.equal((await a(ws))[0],'room_data');
}
function close() { clients.forEach(w=>{try{w.close()}catch{/* ignore */}}); clients.length=0; }
// Test suite opens >10 connections/min — clear the IP connection counter so the
// connection rate limiter doesn't mask test failures (test-only, never at runtime).
@@ -72,6 +75,7 @@ try {
assert.ok(Array.isArray(capData.capabilities) && capData.capabilities.includes('host-control'),
'ROOM_DATA advertises the host-control capability');
assert.ok(capData.capabilities.includes('chat'), 'ROOM_DATA advertises the chat capability');
assert.ok(capData.capabilities.includes('chat-v1'), 'ROOM_DATA advertises the versioned chat capability');
assert.equal(capData.chatHistory, undefined, 'ROOM_DATA never contains chat history');
close();
resetConnectionRate();
@@ -79,7 +83,8 @@ try {
// --- Encrypted chat is a live-only canonical relay ---
const chatRoom = 'chat-'+Date.now();
const chat1 = await c(), chat2 = await c();
await j(chat1, chatRoom, 'alice'); await j(chat2, chatRoom, 'bob');
await j(chat1, chatRoom, 'alice', null, ['chat-v1']);
await j(chat2, chatRoom, 'bob', null, ['chat-v1']);
chat1._m.length = chat2._m.length = 0;
const ciphertext = Buffer.alloc(64, 7).toString('base64url');
s(chat1, 'chat_message', {
@@ -106,6 +111,36 @@ try {
close();
resetConnectionRate();
// --- Mixed rollout: old non-chat clients never receive unknown chat events ---
const mixedChatRoom = 'chat-mixed-'+Date.now();
const newChat = await c(), oldNoChat = await c();
await j(newChat, mixedChatRoom, 'new-chat', null, ['chat-v1', 'chat-v1', 'future-chat', 42]);
await j(oldNoChat, mixedChatRoom, 'old-no-chat', null, ['chat-v2']);
newChat._m.length = oldNoChat._m.length = 0;
s(newChat, 'chat_message', { ciphertext });
await w(newChat, 'chat_message');
let oldReceivedChat = false;
try { await w(oldNoChat, 'chat_message', 500); oldReceivedChat = true; } catch { /* expected */ }
assert.equal(oldReceivedChat, false, 'old client receives no unknown chat event');
// The same old client remains fully usable for the pre-chat protocol.
newChat._m.length = oldNoChat._m.length = 0;
s(oldNoChat, 'play', { currentTime: 12 });
await w(newChat, 'play');
s(newChat, 'pause', { currentTime: 13 });
await w(oldNoChat, 'pause');
// First chat beta compatibility: sending a valid chat frame dynamically
// proves receive support even when that beta omitted clientCapabilities.
const firstBeta = await c();
await j(firstBeta, mixedChatRoom, 'first-beta');
firstBeta._m.length = newChat._m.length = 0;
s(firstBeta, 'chat_message', { ciphertext });
await w(firstBeta, 'chat_message');
await w(newChat, 'chat_message');
close();
resetConnectionRate();
// --- Default 'everyone' mode does NOT gate anyone (host-control OFF = unchanged) ---
// Confirms that with host-only off, a non-host guest can still drive every
// room-moving event exactly like before the feature existed.