'use strict'; const assert=require('assert/strict'); async function connectRealtime(base,session,{timeout=4000}={}){ assert(session?.playerId&&session?.token,'Realtime session is required'); const url=base.replace(/^http/,'ws')+'/api/realtime'; const socket=new WebSocket(url); const queue=[]; const waiters=[]; let closed=false; function dispatch(message){ for(let index=0;index{ try{dispatch(JSON.parse(String(event.data)))}catch(error){dispatch({type:'test-parse-error',error})} }); socket.addEventListener('close',()=>{closed=true;for(const waiter of waiters.splice(0)){clearTimeout(waiter.timer);waiter.reject(new Error('Realtime socket closed'))}}); await new Promise((resolve,reject)=>{ const timer=setTimeout(()=>reject(new Error('Realtime socket open timed out')),timeout); socket.addEventListener('open',()=>{clearTimeout(timer);resolve()},{once:true}); socket.addEventListener('error',()=>{clearTimeout(timer);reject(new Error('Realtime socket failed'))},{once:true}); }); socket.send(JSON.stringify({type:'hello',playerId:session.playerId,token:session.token})); async function waitFor(predicateOrType,waitTimeout=timeout){ const predicate=typeof predicateOrType==='function'?predicateOrType:message=>message?.type===predicateOrType; const found=queue.findIndex(predicate); if(found>=0)return queue.splice(found,1)[0]; if(closed)throw new Error('Realtime socket is closed'); return new Promise((resolve,reject)=>{ const waiter={predicate,resolve,reject,timer:null}; waiter.timer=setTimeout(()=>{const index=waiters.indexOf(waiter);if(index>=0)waiters.splice(index,1);reject(new Error('Realtime message timed out'))},waitTimeout); waiters.push(waiter); }); } const ready=await waitFor('ready'); return{ socket,ready, send(message){socket.send(JSON.stringify(message))}, waitFor, close(){try{socket.close()}catch(_){}}, queued:queue }; } module.exports={connectRealtime};