diff --git a/lith/walkieware-socket.test.mjs b/lith/walkieware-socket.test.mjs index 8b5a419ec7..2de4490daf 100644 --- a/lith/walkieware-socket.test.mjs +++ b/lith/walkieware-socket.test.mjs @@ -66,3 +66,14 @@ test('phone client persists identity, coalesces ledger sync without echo loop an phone.sync();await new Promise(r=>setTimeout(r,30));assert.equal((await f.store.read('owner','wwRuboh')).revision,1); phone.suspend();await new Promise(r=>setTimeout(r,30));await phone.resume();await new Promise(r=>setTimeout(r,80));assert.equal(phone.ready,true);assert.equal((await f.store.read('owner','wwRuboh')).revision,1); }); +test('silent socket stalls reconnect even when close never emits; concurrent resume opens one socket',{timeout:2000},async()=>{ + const values=new Map(),storage={getItem:k=>values.get(k)||null,setItem:(k,v)=>values.set(k,v)}; + let count=0,offline=0; + class SilentSocket { + constructor(){count++;this.readyState=1;queueMicrotask(()=>this.onopen?.());} + send(text){const m=JSON.parse(text);if(m.type==='authenticate')queueMicrotask(()=>this.onmessage?.({data:JSON.stringify({type:'ready',thread:{code:'wwRuboh',revision:1,ledger}})}));} + close(){this.readyState=3;} + } + const phone=new WalkiewareThread({storage,key:'stall',token:()=> 'owner',ledger:()=>ledger,state:()=>({}),onStatus:(_,status)=>{if(status==='Offline')offline++;},onCommand:async()=>({ok:true}),WebSocketImpl:SilentSocket,heartbeatMs:5,maxIdleMs:15,reconnectMs:1}); + try{await Promise.all([phone.resume(),phone.resume()]);assert.equal(count,1);await new Promise(r=>setTimeout(r,55));assert.ok(count>=2);assert.ok(offline>=1);}finally{phone.suspend();} +}); diff --git a/system/public/aesthetic.computer/disks/ww.mjs b/system/public/aesthetic.computer/disks/ww.mjs index 76471ae3ea..66153127db 100644 --- a/system/public/aesthetic.computer/disks/ww.mjs +++ b/system/public/aesthetic.computer/disks/ww.mjs @@ -1,10 +1,12 @@ // Walkieware, 26.09.30 // Open your running Walkieware piece by its ww code. let socket,child,api,problem='Connecting…',generation=0,stopped=false; -export async function boot($) { +export function boot($) {api=$;void connect($).catch(error=>problem=error.message);} +async function connect($) { api=$;const code=$.params[0]; if(!/^ww[a-z]{5,12}$/i.test(code||'')){problem='Enter a ww code';return;} - const token=await $.authorize(); + let token;try{token=await $.authorize?.();}catch{} + if(stopped)return; if(!token){problem='Sign in to open your Walkieware piece';return;} socket=new WebSocket('wss://aesthetic.computer/api/walkieware-stream'); socket.onopen=()=>socket.send(JSON.stringify({type:'authenticate',role:'agent',token,code}));