onelobby 0.4.1 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/blueprint.schema.json +244 -0
- package/dist/blueprint.d.ts +170 -0
- package/dist/blueprint.js +286 -0
- package/dist/blueprint.js.map +1 -0
- package/dist/codec.d.ts +23 -0
- package/dist/codec.js +28 -0
- package/dist/codec.js.map +1 -1
- package/dist/direct/browser-host.d.ts +46 -0
- package/dist/direct/browser-host.js +93 -0
- package/dist/direct/browser-host.js.map +1 -0
- package/dist/direct/hub-host.d.ts +34 -23
- package/dist/direct/hub-host.js +75 -59
- package/dist/direct/hub-host.js.map +1 -1
- package/dist/direct/hub-local.d.ts +22 -0
- package/dist/direct/hub-local.js +46 -0
- package/dist/direct/hub-local.js.map +1 -0
- package/dist/direct/hub.d.ts +37 -10
- package/dist/direct/hub.js +114 -23
- package/dist/direct/hub.js.map +1 -1
- package/dist/direct/index.d.ts +37 -4
- package/dist/direct/index.js +162 -21
- package/dist/direct/index.js.map +1 -1
- package/dist/direct/metrics.d.ts +12 -0
- package/dist/direct/metrics.js +45 -0
- package/dist/direct/metrics.js.map +1 -0
- package/dist/direct/portmap.d.ts +9 -2
- package/dist/direct/portmap.js +7 -2
- package/dist/direct/portmap.js.map +1 -1
- package/dist/direct/sha1.d.ts +16 -0
- package/dist/direct/sha1.js +111 -0
- package/dist/direct/sha1.js.map +1 -0
- package/dist/direct-address.d.ts +2 -2
- package/dist/direct-address.js.map +1 -1
- package/dist/friendly.d.ts +28 -0
- package/dist/friendly.js +40 -0
- package/dist/friendly.js.map +1 -1
- package/dist/index.d.ts +11 -3
- package/dist/index.js +8 -2
- package/dist/index.js.map +1 -1
- package/dist/internals.d.ts +9 -0
- package/dist/internals.js +9 -0
- package/dist/internals.js.map +1 -1
- package/dist/join-code-kind.d.ts +10 -0
- package/dist/join-code-kind.js +10 -0
- package/dist/join-code-kind.js.map +1 -0
- package/dist/joincode.d.ts +10 -1
- package/dist/joincode.js +23 -3
- package/dist/joincode.js.map +1 -1
- package/dist/lobby.d.ts +65 -4
- package/dist/lobby.js +151 -12
- package/dist/lobby.js.map +1 -1
- package/dist/lockstep.d.ts +8 -0
- package/dist/lockstep.js +23 -2
- package/dist/lockstep.js.map +1 -1
- package/dist/nostr/crypto.d.ts +20 -0
- package/dist/nostr/crypto.js +78 -0
- package/dist/nostr/crypto.js.map +1 -0
- package/dist/nostr/host.d.ts +66 -0
- package/dist/nostr/host.js +196 -0
- package/dist/nostr/host.js.map +1 -0
- package/dist/nostr/limits.d.ts +31 -0
- package/dist/nostr/limits.js +36 -0
- package/dist/nostr/limits.js.map +1 -0
- package/dist/nostr/link.d.ts +70 -0
- package/dist/nostr/link.js +180 -0
- package/dist/nostr/link.js.map +1 -0
- package/dist/nostr/relays.d.ts +79 -0
- package/dist/nostr/relays.js +315 -0
- package/dist/nostr/relays.js.map +1 -0
- package/dist/nostr/room.d.ts +43 -0
- package/dist/nostr/room.js +104 -0
- package/dist/nostr/room.js.map +1 -0
- package/dist/nostr/secp256k1.d.ts +10 -0
- package/dist/nostr/secp256k1.js +171 -0
- package/dist/nostr/secp256k1.js.map +1 -0
- package/dist/nostr/socket.d.ts +48 -0
- package/dist/nostr/socket.js +141 -0
- package/dist/nostr/socket.js.map +1 -0
- package/dist/onelobby.bundle.js +146 -0
- package/dist/onelobby.bundle.js.map +7 -0
- package/dist/onelobby.bundle.mjs +11 -5
- package/dist/onelobby.bundle.mjs.map +4 -4
- package/dist/onelobby.d.ts +54 -4
- package/dist/onelobby.global.js +2932 -49
- package/dist/onelobby.global.js.map +4 -4
- package/dist/onelobby.global.min.js +11 -5
- package/dist/onelobby.global.min.js.map +4 -4
- package/dist/onelobby.js +158 -15
- package/dist/onelobby.js.map +1 -1
- package/dist/pause.d.ts +85 -0
- package/dist/pause.js +160 -0
- package/dist/pause.js.map +1 -0
- package/dist/transport.d.ts +3 -1
- package/dist/transport.js.map +1 -1
- package/dist/webrtc.d.ts +1 -0
- package/dist/webrtc.js +6 -1
- package/dist/webrtc.js.map +1 -1
- package/package.json +4 -2
package/dist/onelobby.bundle.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
/*! onelobby 0.
|
|
2
|
-
function Xe(r,e){if(typeof Worker>"u"||typeof Blob>"u"||typeof URL>"u"||!URL.createObjectURL)return null;try{let t="let h=0;onmessage=e=>{if(e.data&&e.data.start){h=setInterval(()=>postMessage(0),e.data.ms)}else{clearInterval(h);close()}}",s=URL.createObjectURL(new Blob([t],{type:"text/javascript"})),i=new Worker(s);return URL.revokeObjectURL(s),i.onmessage=()=>r(),i.postMessage({start:!0,ms:e}),()=>{i.postMessage({stop:!0}),i.terminate()}}catch{return null}}var de={now:()=>typeof performance<"u"?performance.now():Date.now(),setInterval(r,e){let t=Xe(r,e);if(t)return t;let s=setInterval(r,e);return()=>clearInterval(s)},setTimeout(r,e){let t=setTimeout(r,e);return()=>clearTimeout(t)}};var f=class{handlers=new Map;on(e,t){let s=this.handlers.get(e);return s||this.handlers.set(e,s=[]),s.push(t),()=>this.off(e,t)}off(e,t){let s=this.handlers.get(e);if(!s)return;let i=s.indexOf(t);i>=0&&s.splice(i,1)}emit(e,...t){let s=this.handlers.get(e);if(s)for(let i of[...s])try{i(...t)}catch(n){queueMicrotask(()=>{throw n})}}};var k="0123456789ABCDEFGHJKMNPQRSTVWXYZ";function Ze(r){return r.replace(/[a-z]/g,e=>e.toUpperCase())}function m(r){let e=Ze(r).replace(/[ \-]/g,"").replace(/[IL]/g,"1").replace(/O/g,"0");if(e.length!==6)return null;for(let t of e)if(k.indexOf(t)<0)return null;return e}function y(r){let e=m(r)??r.toUpperCase();return e.length===6?`${e.slice(0,3)} ${e.slice(3)}`:e}var W={hi:1,welcome_dc:2,cmd:16,frame:17,hash:18,desync:19,snapshot_req:20,snapshot:21,chunk:22,raw:32,ping:48,pong:49,host_claim:64,voice:80,chat:96,chat_history:97,chat_reject:98},fe=1,me=2,p=class extends Error{constructor(t,s=""){super(s?`${t}: ${s}`:t);this.code=t;this.name="CodecError"}code},ue=new TextEncoder,Qe=new TextDecoder("utf-8",{fatal:!0});function pe(r){try{return Qe.decode(r)}catch{throw new p("bad_utf8")}}var z=class{buf;dv;pos=0;constructor(e){this.buf=new Uint8Array(e),this.dv=new DataView(this.buf.buffer)}ensure(e){if(this.pos+e<=this.buf.length)return;let t=new Uint8Array(Math.max(this.buf.length*2,this.pos+e));t.set(this.buf),this.buf=t,this.dv=new DataView(t.buffer)}u8(e){this.ensure(1),this.dv.setUint8(this.pos,e),this.pos+=1}u16(e){this.ensure(2),this.dv.setUint16(this.pos,e,!0),this.pos+=2}u32(e){this.ensure(4),this.dv.setUint32(this.pos,e>>>0,!0),this.pos+=4}i64(e){this.ensure(8),this.dv.setBigInt64(this.pos,BigInt(e),!0),this.pos+=8}u64(e){this.ensure(8),this.dv.setBigUint64(this.pos,BigInt.asUintN(64,e),!0),this.pos+=8}bytes(e){this.ensure(e.length),this.buf.set(e,this.pos),this.pos+=e.length}done(){return this.buf.slice(0,this.pos)}},Y=class{constructor(e){this.buf=e;this.dv=new DataView(e.buffer,e.byteOffset,e.byteLength)}buf;dv;pos=0;need(e){if(this.pos+e>this.buf.length)throw new p("too_short","truncated frame")}u8(){this.need(1);let e=this.dv.getUint8(this.pos);return this.pos+=1,e}u16(){this.need(2);let e=this.dv.getUint16(this.pos,!0);return this.pos+=2,e}u32(){this.need(4);let e=this.dv.getUint32(this.pos,!0);return this.pos+=4,e}i64(){this.need(8);let e=this.dv.getBigInt64(this.pos,!0);return this.pos+=8,Number(e)}u64(){this.need(8);let e=this.dv.getBigUint64(this.pos,!0);return this.pos+=8,e}bytes(e){this.need(e);let t=this.buf.slice(this.pos,this.pos+e);return this.pos+=e,t}rest(){let e=this.buf.slice(this.pos);return this.pos=this.buf.length,e}};function v(r,e=0,t=!1){let s=new z(64);switch(s.u8(W[r.type]),s.u16(e),r.type){case"hi":s.u8(r.proto),s.u16(r.peerId),s.i64(r.lastTick);break;case"welcome_dc":s.i64(r.tick),s.u8(r.tickRate),s.u8(r.mode),s.u16(r.hostId);break;case"cmd":s.u32(r.seq),s.u16(r.bytes.length),s.bytes(r.bytes);break;case"frame":s.i64(r.tick),s.u16(r.commands.length);for(let n of r.commands)s.u16(n.peerId),s.u16(n.bytes.length),s.bytes(n.bytes);break;case"hash":s.i64(r.tick),s.u64(r.hash);break;case"desync":s.i64(r.tick),s.u64(r.hostHash),s.u64(r.yourHash);break;case"snapshot_req":s.i64(r.sinceTick);break;case"snapshot":s.i64(r.tick),s.u32(r.total),s.u8(r.flags);break;case"chunk":s.u32(r.id),s.u16(r.index),s.u16(r.count),s.bytes(r.bytes);break;case"raw":s.bytes(r.bytes);break;case"ping":case"pong":s.u32(r.n),s.u32(r.t);break;case"host_claim":s.u16(r.epoch),s.u16(r.hostId),s.i64(r.tick);break;case"voice":s.u16(r.seq),s.u8(r.flags),s.u8(r.frames.length);for(let n of r.frames)s.u16(n.length),s.bytes(n);break;case"chat":{let n=ue.encode(r.text);s.u32(r.id),s.i64(r.ts),s.u16(n.length),s.bytes(n);break}case"chat_history":s.u16(r.lines.length);for(let n of r.lines){let o=ue.encode(n.text);s.u16(n.peerId),s.u32(n.id),s.i64(n.ts),s.u16(o.length),s.bytes(o)}break;case"chat_reject":s.u32(r.id),s.u8(r.reason);break}let i=s.done();if(!t&&i.length>16384)throw new p("too_long",`${i.length} > 16384`);return i}var et=new Map(Object.keys(W).map(r=>[W[r],r]));function be(r,e=!1){let t=e?67108864:16384;if(r.length>t)throw new p("too_long",`${r.length} > ${t}`);if(r.length<3)throw new p("too_short","header");let s=new Y(r),i=et.get(s.u8()),n=s.u16();if(!i)return null;let o;switch(i){case"hi":o={type:i,proto:s.u8(),peerId:s.u16(),lastTick:s.i64()};break;case"welcome_dc":o={type:i,tick:s.i64(),tickRate:s.u8(),mode:s.u8(),hostId:s.u16()};break;case"cmd":{let a=s.u32();o={type:i,seq:a,bytes:s.bytes(s.u16())};break}case"frame":{let a=s.i64(),c=s.u16(),l=[];for(let h=0;h<c;h++){let u=s.u16();l.push({peerId:u,bytes:s.bytes(s.u16())})}o={type:i,tick:a,commands:l};break}case"hash":o={type:i,tick:s.i64(),hash:s.u64()};break;case"desync":o={type:i,tick:s.i64(),hostHash:s.u64(),yourHash:s.u64()};break;case"snapshot_req":o={type:i,sinceTick:s.i64()};break;case"snapshot":o={type:i,tick:s.i64(),total:s.u32(),flags:s.u8()};break;case"chunk":o={type:i,id:s.u32(),index:s.u16(),count:s.u16(),bytes:s.rest()};break;case"raw":o={type:i,bytes:s.rest()};break;case"ping":case"pong":o={type:i,n:s.u32(),t:s.u32()};break;case"host_claim":o={type:i,epoch:s.u16(),hostId:s.u16(),tick:s.i64()};break;case"voice":{let a=s.u16(),c=s.u8(),l=s.u8(),h=[];for(let u=0;u<l;u++)h.push(s.bytes(s.u16()));o={type:i,seq:a,flags:c,frames:h};break}case"chat":{let a=s.u32(),c=s.i64();o={type:i,id:a,ts:c,text:pe(s.bytes(s.u16()))};break}case"chat_history":{let a=s.u16(),c=[];for(let l=0;l<a;l++){let h=s.u16(),u=s.u32(),Ge=s.i64();c.push({peerId:h,id:u,ts:Ge,text:pe(s.bytes(s.u16()))})}o={type:i,lines:c};break}case"chat_reject":o={type:i,id:s.u32(),reason:s.u8()};break}if(s.pos!==r.length)throw new p("trailing",`${r.length-s.pos} extra bytes`);return{route:n,msg:o}}var T={rate:1,length:2,mutedByHost:3};function G(r){return r===1?"rate":r===2?"length":r===3?"muted_by_host":"unknown"}var X=256,ye=3,tt=5e3,st=5,_=100,it=/[\u0000-\u001f\u007f-\u009f\u202a-\u202e\u2066-\u2069]/g;function nt(r){return r.replace(it,"")}function ge(r){return new TextEncoder().encode(r).length}var Z=class{buckets=new Map;refillMs=tt/st;tryTake(e,t){let s=this.buckets.get(e);return s||this.buckets.set(e,s={tokens:ye,at:t}),s.tokens=Math.min(ye,s.tokens+(t-s.at)/this.refillMs),s.at=t,s.tokens<1?!1:(s.tokens-=1,!0)}forget(e){this.buckets.delete(e)}},w=class extends f{constructor(t,s,i){super();this.hub=t;this.wall=s;this.isLocallyMuted=i}hub;wall;isLocallyMuted;filter=t=>t;lines=[];limiter=new Z;hostMuted=new Set;localId=0;stampId=0;get history(){return this.lines}send(t){let s=++this.localId>>>0;return ge(t)>X*4?(queueMicrotask(()=>this.emit("rejected",s,"length")),s):(this.hub.isHost?this.hostProcess(this.hub.localPeerId,s,t):this.hub.sendToHost({type:"chat",id:s,ts:0,text:t}),s)}hostMute(t,s){if(!this.hub.isHost)throw new Error("only the host can mute chat");s?this.hostMuted.add(t):this.hostMuted.delete(t)}isHostMuted(t){return this.hostMuted.has(t)}onPeerLeft(t){this.limiter.forget(t),this.hostMuted.delete(t)}sendHistory(t){let s=this.lines.slice(-_).map(i=>({...i}));this.hub.sendTo(t,{type:"chat_history",lines:s},0)}handle(t,s,i){switch(i.type){case"chat":this.hub.isHost?this.hostProcess(t,i.id,i.text):this.deliver({peerId:s,id:i.id,ts:i.ts,text:i.text});break;case"chat_history":if(!this.hub.isHost){this.lines=[];for(let n of i.lines.slice(-_))this.push({...n,text:this.safeFilter(n.text)});this.emit("history",this.lines)}break;case"chat_reject":this.emit("rejected",i.id,G(i.reason));break}}hostProcess(t,s,i){let n=l=>{t===this.hub.localPeerId?this.emit("rejected",s,G(l)):this.hub.sendTo(t,{type:"chat_reject",id:s,reason:l},0)};if(this.hostMuted.has(t))return n(T.mutedByHost);if(ge(i)>X)return n(T.length);if(!this.limiter.tryTake(t,this.hub.clock.now()))return n(T.rate);let o=nt(i);if(o.length===0)return;let a={peerId:t,id:++this.stampId>>>0,ts:this.wall(),text:o},c={type:"chat",id:a.id,ts:a.ts,text:a.text};for(let l of this.hub.connectedPeers())this.hub.sendTo(l,c,t);this.deliver(a)}deliver(t){let s={...t,text:this.safeFilter(t.text)};this.push(s),!this.isLocallyMuted(t.peerId)&&this.emit("message",s)}push(t){this.isLocallyMuted(t.peerId)||(this.lines.push(t),this.lines.length>_&&this.lines.splice(0,this.lines.length-_))}safeFilter(t){try{return this.filter(t)}catch{return t}}};var Q=16e3;function M(r,e,t=Q){let s=Math.max(1,Math.ceil(e.length/t));if(s>65535)throw new RangeError("payload needs more than 65535 chunks");let i=[];for(let n=0;n<s;n++)i.push({type:"chunk",id:r,index:n,count:s,bytes:e.subarray(n*t,Math.min(e.length,(n+1)*t))});return i}var rt=8,L=class{parts=new Map;push(e){if(e.count===0||e.index>=e.count)return null;let t=this.parts.get(e.id);if(!t||t.count!==e.count){for(this.parts.delete(e.id);this.parts.size>=rt;)this.parts.delete(this.parts.keys().next().value);t={count:e.count,got:0,parts:new Array(e.count),bytes:0},this.parts.set(e.id,t)}if(t.parts[e.index])return null;if(t.parts[e.index]=e.bytes,t.got++,t.bytes+=e.bytes.length,t.bytes>67108864)throw this.parts.delete(e.id),new p("too_long",`chunk set over ${67108864} bytes`);if(t.got<t.count){for(;this.pendingBytes>67108864&&this.parts.size>1;)for(let n of this.parts.keys())if(n!==e.id){this.parts.delete(n);break}return null}this.parts.delete(e.id);let s=new Uint8Array(t.bytes),i=0;for(let n of t.parts)s.set(n,i),i+=n.length;return s}get pending(){return this.parts.size}get pendingBytes(){let e=0;for(let t of this.parts.values())e+=t.bytes;return e}clear(){this.parts.clear()}};function ke(){return typeof CompressionStream<"u"&&typeof DecompressionStream<"u"}async function ve(r,e,t=1/0){let s=e.writable.getWriter();s.write(r).then(()=>s.close()).catch(()=>{});let i=[],n=e.readable.getReader(),o=0;for(;;){let{done:l,value:h}=await n.read();if(l)break;if(i.push(h),o+=h.length,o>t)throw n.cancel().catch(()=>{}),new p("too_long",`snapshot inflates to more than ${t} bytes`)}let a=new Uint8Array(o),c=0;for(let l of i)a.set(l,c),c+=l.length;return a}function we(r){return ve(r,new CompressionStream("deflate"))}function Se(r,e=67108864){return ve(r,new DecompressionStream("deflate"),e)}var at=3,ct=3,lt=3e3,ht=1e4,dt=256,ut=1024,x=class extends f{constructor(t,s){super();this.hub=t;this.hashEveryTicks=Math.max(1,s.hashEveryTicks),this.historyMax=s.historyFrames??Math.max(60,t.tickRate*30),this.lastFrameRx=t.clock.now()}hub;snapshot=null;restore=null;hashEveryTicks;chunkSize=Q;historyMax;queue=[];history=[];lastFrameTick=-1;consumedTick=0;cmdSeq=1;awaitingSnapshot=!1;lastFrameRx=0;lastSnapshotReq=-1/0;restoreFailures=0;hosting=!1;_started=!1;pending=[];_droppedCommands=0;ready=new Set;snapshotting=new Set;cancelTicker=null;baseTime=0;emitted=0;ownHashes=new Map;peerHashes=new Map;desyncs=new Map;lastHashRx=new Map;staleUpTo=new Map;pendingSnapshot=null;get droppedCommands(){return this._droppedCommands}get started(){return this._started}get framesBuffered(){return this.queue.length}get catchingUp(){return this.queue.length>2}get currentTick(){return this.consumedTick}get lastTick(){return Math.max(0,this.lastFrameTick)}get hiTick(){return this.lastFrameTick}submit(t){this.hosting?this.pending.push({peerId:this.hub.localPeerId,bytes:t.slice()}):this.hub.sendToHost({type:"cmd",seq:this.cmdSeq++>>>0,bytes:t})}nextFrame(){let t=this.queue.shift()??null;return t&&(this.consumedTick=t.tick),t}reportHash(t,s){if(!(this.awaitingSnapshot||t<=0||t%this.hashEveryTicks!==0))if(this.hosting){this.ownHashes.set(t,s),this.prune(this.ownHashes);let i=this.peerHashes.get(t);if(i){this.peerHashes.delete(t);for(let[n,o]of[...i].sort((a,c)=>a[0]-c[0]))this.compare(n,t,s,o)}}else this.hub.sendToHost({type:"hash",tick:t,hash:s})}hostIdle(){this.hosting=!0,this.lastFrameTick<0&&(this.lastFrameTick=0)}hostStart(){this.hostIdle(),this._started=!0,this.emitted=0,this.baseTime=this.hub.clock.now(),this.cancelTicker?.();let t=1e3/this.hub.tickRate;this.cancelTicker=this.hub.clock.setInterval(()=>this.pump(t),Math.max(1,Math.floor(t/2)))}becomeHost(){this.pending=[],this.ready.clear(),this.snapshotting.clear(),this.ownHashes.clear(),this.peerHashes.clear(),this.desyncs.clear(),this.awaitingSnapshot=!1,this.hostIdle(),this._started&&this.hostStart()}becomeJoiner(){this.hosting=!1,this.cancelTicker?.(),this.cancelTicker=null,this.pending=[],this.ready.clear(),this.lastFrameRx=this.hub.clock.now()}markStarted(){this._started||(this._started=!0,this.emit("started"))}stop(){this.cancelTicker?.(),this.cancelTicker=null}onPeerLeft(t){this.ready.delete(t),this.snapshotting.delete(t),this.desyncs.delete(t),this.lastHashRx.delete(t),this.staleUpTo.delete(t);for(let s of this.peerHashes.values())s.delete(t);this.pending=this.pending.filter(s=>s.peerId!==t)}pump(t){if(!this.hosting)return;let s=this.hub.clock.now(),i=Math.floor((s-this.baseTime+1)/t)-this.emitted;i>10&&(this.baseTime=s-this.emitted*t,i=1);for(let n=0;n<i;n++)this.emitFrame()}emitFrame(){let t=this.pending.splice(0).sort((n,o)=>n.peerId-o.peerId),s={tick:this.lastFrameTick+1,commands:t};this.emitted++,this.lastFrameTick=s.tick,this.record(s),this.queue.push(s);let i={type:"frame",tick:s.tick,commands:t};for(let n of this.ready)this.hub.sendTo(n,i,0);this.checkPeerSilence()}record(t){this.history.push(t),this.history.length>this.historyMax&&this.history.splice(0,this.history.length-this.historyMax)}checkPeerSilence(){if(this.ownHashes.size===0)return;let t=this.hub.clock.now(),s=Math.max(ht,this.hashEveryTicks/this.hub.tickRate*1e3*3);for(let i of[...this.ready]){let n=this.lastHashRx.get(i)??t;t-n>s&&this.hub.dropPeer(i,"timeout")}}handle(t,s){switch(s.type){case"cmd":if(this.hosting){let i=0;for(let n of this.pending)n.peerId===t&&i++;i>=dt?this._droppedCommands++:this.pending.push({peerId:t,bytes:s.bytes})}break;case"frame":this.hosting||this.onFrame(s.tick,s.commands);break;case"hash":this.hosting&&this.onPeerHash(t,s.tick,s.hash);break;case"desync":this.hosting||(this.awaitingSnapshot=!0,this.queue=[],this.emit("desync",{tick:s.tick,hostHash:s.hostHash,yourHash:s.yourHash}));break;case"snapshot_req":this.hosting&&this.sendSnapshot(t);break;case"snapshot":this.hosting||(this.markStarted(),this.pendingSnapshot={tick:s.tick,total:s.total,flags:s.flags});break}}get snapshotPending(){return this.pendingSnapshot!==null}onSnapshotPayload(t){let s=this.pendingSnapshot;if(this.pendingSnapshot=null,!!s){if(t.length!==s.total){this.awaitingSnapshot=!1,this.requestSnapshot();return}this.applySnapshot(s,t)}}onFrame(t,s){if(this.lastFrameRx=this.hub.clock.now(),this.markStarted(),this.awaitingSnapshot)return;let i=Math.max(0,this.lastFrameTick)+1;if(t<i)return;if(t>i){this.requestSnapshot();return}let n={tick:t,commands:s};this.lastFrameTick=t,this.record(n),this.queue.push(n),this.queue.length>this.hub.tickRate*3&&this.requestSnapshot()}requestSnapshot(){let t=this.hub.clock.now();this.awaitingSnapshot&&t-this.lastSnapshotReq<2e3||(this.lastSnapshotReq=t,this.awaitingSnapshot=!0,this.queue=[],this.hub.sendToHost({type:"snapshot_req",sinceTick:this.lastFrameTick}))}pokeHost(){this.hub.sendToHost({type:"snapshot_req",sinceTick:this.lastFrameTick})}async applySnapshot(t,s){try{let i=s;if(t.flags&1&&(i=await Se(s)),!this.restore)throw new Error("lockstep.restore is not set; cannot apply a snapshot");this.restore(t.tick,i),this.queue=[],this.history=[],this.lastFrameTick=t.tick,this.consumedTick=t.tick,this.awaitingSnapshot=!1,this.restoreFailures=0,this.markStarted(),this.lastFrameRx=this.hub.clock.now(),this.emit("restored",t.tick)}catch(i){this.hub.reportError(i instanceof Error?i:new Error(String(i))),++this.restoreFailures<=ct?(this.awaitingSnapshot=!1,this.lastSnapshotReq=-1/0,this.requestSnapshot()):this.hub.reportError(new Error(`snapshot restore failed ${this.restoreFailures} times; giving up`))}}onPeerHi(t,s){this.lastHashRx.set(t,this.hub.clock.now());let i=this.lastFrameTick,n=this.history.length?this.history[0].tick:i+1;if(s<0){this._started&&i>0?this.sendSnapshot(t):this.ready.add(t);return}if(s<=i&&s+1>=n){for(let o of this.history)o.tick>s&&this.hub.sendTo(t,{type:"frame",tick:o.tick,commands:o.commands},0);this.ready.add(t)}else this.sendSnapshot(t)}onPeerHash(t,s,i){if(this.lastHashRx.set(t,this.hub.clock.now()),this.isStale(t,s))return;let n=this.ownHashes.get(s);if(n===void 0){let o=this.peerHashes.get(s);o||this.peerHashes.set(s,o=new Map),o.set(t,i),this.prune(this.peerHashes);return}this.compare(t,s,n,i)}compare(t,s,i,n){if(i===n||this.isStale(t,s)||!this.ready.has(t)||this.snapshotting.has(t))return;let o=(this.desyncs.get(t)??0)+1;if(this.desyncs.set(t,o),o>=at){this.hub.dropPeer(t,"desync");return}this.hub.sendTo(t,{type:"desync",tick:s,hostHash:i,yourHash:n},0),this.sendSnapshot(t,s)}isStale(t,s){let i=this.staleUpTo.get(t);return!!i&&s>i.from&&s<=i.to}async sendSnapshot(t,s=1/0){if(!this.snapshotting.has(t)){if(!this.snapshot){this.hub.reportError(new Error("lockstep.snapshot is not set; cannot serve a snapshot"));return}this.snapshotting.add(t),this.ready.delete(t);try{let i=this.consumedTick,n=this.snapshot(),o=n,a=0;if(n.length>=ut&&ke()&&(o=await we(n),a=1),!this.snapshotting.has(t))return;this.hub.sendTo(t,{type:"snapshot",tick:i,total:o.length,flags:a},0);for(let c of M(this.hub.nextChunkId(),o,this.chunkSize))this.hub.sendTo(t,c,0);for(let c of this.history)c.tick>i&&this.hub.sendTo(t,{type:"frame",tick:c.tick,commands:c.commands},0);s!==1/0?this.staleUpTo.set(t,{from:s,to:this.lastFrameTick}):this.staleUpTo.delete(t);for(let c of this.peerHashes.values())c.delete(t);this.ready.add(t),this.lastHashRx.set(t,this.hub.clock.now())}catch(i){this.hub.reportError(i instanceof Error?i:new Error(String(i)))}finally{this.snapshotting.delete(t)}}}prune(t){if(t.size<=64)return;let s=[...t.keys()].sort((i,n)=>i-n);for(let i of s.slice(0,s.length-64))t.delete(i)}get msSinceFrame(){return this.hub.clock.now()-this.lastFrameRx}hostSilent(){return!this.hosting&&this._started&&this.msSinceFrame>lt}touch(){this.lastFrameRx=this.hub.clock.now()}hiMessage(t){return{type:"hi",proto:1,peerId:t,lastTick:this.lastFrameTick}}get resyncCount(){return this.desyncs}};function xe(r,e){let t=null;for(let s of r)s!==e&&(t===null||s<t)&&(t=s);return t}function pt(r,e){let t=r-e&65535;return t!==0&&t<32768}function Pe(r,e){return r.epoch!==e.epoch?pt(r.epoch,e.epoch):r.hostId<e.hostId}var d=class extends Error{constructor(t,s,i){super(s?`${t}: ${s}`:t);this.code=t;this.data=i;this.name="OneLobbyError"}code;data},Ce="lk1",ft=1e4,mt=3e4,R=class{constructor(e,t,s){this.url=e;this.factory=t;this.clock=s}url;factory;clock;socket=null;waiters=[];lastRx=0;cancelPing=null;pingN=0;closed=!1;handler=null;backlog=[];onClose=null;get onMessage(){return this.handler}set onMessage(e){for(this.handler=e;e&&this.handler===e&&this.backlog.length;)e(this.backlog.shift())}connect(){return new Promise((e,t)=>{let s;try{s=this.factory(this.url)}catch(i){t(new d("connect_failed",String(i)));return}this.socket=s,s.onopen=()=>{this.lastRx=this.clock.now(),this.cancelPing=this.clock.setInterval(()=>this.tick(),ft),e()},s.onerror=()=>{this.lastRx||t(new d("connect_failed",`cannot reach ${this.url}`))},s.onmessage=i=>{this.lastRx=this.clock.now();let n;try{n=JSON.parse(String(i.data))}catch{return}if(!n||typeof n.t!="string")return;if(n.t==="ping"){this.send({t:"pong",n:n.n});return}if(n.t==="pong")return;let o=this.waiters.findIndex(a=>a.types.includes(n.t)||n.t==="error");if(o>=0){let[a]=this.waiters.splice(o,1);n.t==="error"?a.reject(bt(n)):a.resolve(n);return}this.handler?this.handler(n):this.backlog.length<256&&this.backlog.push(n)},s.onclose=()=>{this.lastRx||t(new d("connect_failed",`cannot reach ${this.url}`)),this.shutdown()}})}tick(){if(this.clock.now()-this.lastRx>mt){this.socket?.close(4e3,"silence"),this.shutdown();return}this.send({t:"ping",n:++this.pingN})}shutdown(){if(this.closed)return;this.closed=!0,this.cancelPing?.();let e=new d("closed","signaling connection closed");for(let t of this.waiters.splice(0))t.reject(e);this.onClose?.()}get isOpen(){return!this.closed&&this.socket!==null&&this.socket.readyState===1}send(e){this.isOpen&&this.socket.send(JSON.stringify(e))}expect(...e){return this.closed?Promise.reject(new d("closed","signaling connection closed")):new Promise((t,s)=>{this.waiters.push({types:e,resolve:t,reject:s})})}async request(e,...t){let s=this.expect(...t);return this.send(e),s}close(){this.socket?.close(1e3,"bye"),this.shutdown()}};function bt(r){return new d(String(r.code??"error"),r.msg===void 0?void 0:String(r.msg),r.data)}var A=class{constructor(e=2){this.framesPerPacket=e}framesPerPacket;seq=0;buf=[];push(e){return this.buf.push(e),this.buf.length<this.framesPerPacket?null:this.take(0)}packet(e,t){let s={seq:this.seq,flags:t,frames:e};return this.seq=this.seq+1&65535,s}endBurst(){return this.take(1)}get buffered(){return this.buf.length}take(e){let t={seq:this.seq,flags:e,frames:this.buf};return this.seq=this.seq+1&65535,this.buf=[],t}};function Ee(r){if(r.length===0)return 0;let e=0;for(let t=0;t<r.length;t++)e+=r[t]*r[t];return Math.sqrt(e/r.length)}var I=class{mode="pushToTalk";ptt=!1;vadThreshold=.02;lastLoud=-1/0;open=!1;update(e,t){let s;this.mode==="pushToTalk"?s=this.ptt:(e>=this.vadThreshold&&(this.lastLoud=t),s=t-this.lastLoud<=300);let i=s===this.open?null:s?"open":"close";return this.open=s,{send:s,edge:i}}get isOpen(){return this.open}};function ee(r,e){let t=r-e&65535;return t>=32768?t-65536:t}var O=class{constructor(e=60,t=2){this.framesPerPacket=t;this.prefillFrames=Math.max(1,Math.round(e/20))}framesPerPacket;packets=new Map;expected=null;ready=[];playing=!1;endSeq=null;waitTicks=0;prefillFrames;push(e){if(this.expected===null)this.expected=e.seq;else if(ee(e.seq,this.expected)<0)return;this.packets.set(e.seq,e),e.flags&1?this.endSeq=e.seq:this.endSeq!==null&&ee(e.seq,this.endSeq)>0&&(this.endSeq=null)}get ended(){return this.endSeq!==null}bufferedFrames(){let e=this.ready.length;for(let t of this.packets.values())e+=t.frames.length;return e}newerThanExpected(){for(let e of this.packets.keys())if(this.expected!==null&&ee(e,this.expected)>0)return!0;return!1}pull(){if(this.expected===null)return{kind:"idle"};if(!this.playing)if(this.bufferedFrames()>=this.prefillFrames||this.ended&&this.packets.size>0)this.playing=!0;else return{kind:"wait"};this.ready.length===0&&this.refill();let e=this.ready.shift();return e?(this.waitTicks=0,e.length===0?{kind:"plc"}:{kind:"frame",opus:e}):this.ended&&this.packets.size===0?(this.reset(),{kind:"idle"}):++this.waitTicks>12?(this.reset(),{kind:"idle"}):{kind:"wait"}}refill(){if(this.expected===null)return;let e=this.packets.get(this.expected);if(e){this.packets.delete(this.expected),this.expected=this.expected+1&65535;for(let t of e.frames)this.ready.push(t);return}if(this.newerThanExpected()){this.expected=this.expected+1&65535;for(let t=0;t<this.framesPerPacket;t++)this.ready.push(new Uint8Array(0))}}reset(){this.packets.clear(),this.ready=[],this.expected=null,this.playing=!1,this.endSeq=null,this.waitTicks=0}get active(){return this.expected!==null}},H=class{speakers=new Map;windows=new Map;burstTimeoutMs=500;accept(e,t,s,i){if(t>1024)return!1;let n=this.windows.get(e);for(n||this.windows.set(e,n=[]);n.length&&i-n[0]>=1e3;)n.shift();if(n.length>=30)return!1;for(let[o,a]of this.speakers)i-a>this.burstTimeoutMs&&this.speakers.delete(o);return!this.speakers.has(e)&&this.speakers.size>=8?!1:(n.push(i),s&1?this.speakers.delete(e):this.speakers.set(e,i),!0)}get activeSpeakers(){return this.speakers.size}forget(e){this.speakers.delete(e),this.windows.delete(e)}};var yt=300,P=class extends f{constructor(t,s){super();this.hub=t;this.deps=s}hub;deps;mode="pushToTalk";muted=!1;inputLevel=0;bitrate=24e3;_enabled=!1;gate=new I;packetizer=new A;relay=new H;speakers=new Map;peerMuted=new Set;gains=new Map;encoder=null;source=null;sink=null;cancelPlayout=null;endTimer=null;sending=!1;get enabled(){return this._enabled}set enabled(t){t!==this._enabled&&(this._enabled=t,t?this.start():this.stop())}pushToTalk(t){this.gate.ptt=t}isMutedPeer(t){return this.peerMuted.has(t)}setMuted(t,s){s?this.peerMuted.add(t):this.peerMuted.delete(t)}setVolume(t,s){this.gains.set(t,Math.min(2,Math.max(0,s)))}volumeOf(t){return this.gains.get(t)??1}async start(){try{if(!this.deps.codec)throw new Error("no Opus codec available: supply one through the voice.codec option (WASM fallback)");if(!this.deps.createSink)throw new Error("no audio output available");this.sink=this.deps.createSink(),this.cancelPlayout=this.hub.clock.setInterval(()=>this.playoutTick(),20),this.deps.createSource&&(this.encoder=this.deps.codec.createEncoder({bitrate:this.bitrate},t=>this.onEncoded(t)),this.source=this.deps.createSource(),await this.source.start(t=>this.onCapturedFrame(t,this.hub.clock.now())))}catch(t){this._enabled=!1,this.stop(),this.emit("error",{message:t instanceof Error?t.message:String(t),cause:t})}}stop(){this.sending&&this.closeBurst(),this.source?.stop(),this.source=null,this.encoder?.close(),this.encoder=null,this.cancelPlayout?.(),this.cancelPlayout=null,this.sink?.close(),this.sink=null;for(let[t,s]of this.speakers)s.decoder?.close(),s.talking&&this.emit("speaking",t,!1);this.speakers.clear()}onCapturedFrame(t,s){let i=Ee(t);if(this.inputLevel=i,!this._enabled||this.muted){this.sending&&this.closeBurst();return}let n=this.gate.update(i,s);n.send?(this.sending=!0,this.endTimer?.(),this.endTimer=null,this.encoder?.encode(t)):n.edge==="close"&&this.sending&&this.closeBurst()}closeBurst(){this.sending=!1,this.endTimer?.(),this.endTimer=this.hub.clock.setTimeout(()=>{this.endTimer=null,this.sendPacket(this.packetizer.endBurst())},40)}sendEncoded(t,s=!1){this.sendPacket(this.packetizer.packet(t,s?1:0))}onEncoded(t){let s=this.packetizer.push(t);s&&this.sendPacket(s)}sendPacket(t){let s={type:"voice",seq:t.seq,flags:t.flags,frames:t.frames};if(this.hub.isHost)for(let i of this.hub.connectedPeers())this.hub.sendTo(i,s,this.hub.localPeerId,"voice");else this.hub.sendToHost(s,0,"voice")}handle(t,s,i){if(i.type!=="voice")return;let n=i.frames.reduce((c,l)=>c+l.length+2,0)+7,o=this.hub.clock.now(),a=s;if(this.hub.isHost){if(a=t,!this.relay.accept(t,n,i.flags,o))return;for(let c of this.hub.connectedPeers())c!==t&&this.hub.sendTo(c,i,t,"voice")}this.emit("packet",a,i.seq,i.flags,i.frames),!(!this._enabled||a===this.hub.localPeerId||this.peerMuted.has(a))&&this.receivePacket(a,{seq:i.seq,flags:i.flags,frames:i.frames},o)}receivePacket(t,s,i){let n=this.speakers.get(t);n||(n={jitter:new O,decoder:null,talking:!1,lastHeard:i},this.speakers.set(t,n)),n.lastHeard=i,n.jitter.push(s),!n.talking&&s.frames.length>0&&(n.talking=!0,this.emit("speaking",t,!0))}playoutTick(){let t=this.hub.clock.now();for(let[s,i]of this.speakers){let n=i.jitter.pull();n.kind==="frame"||n.kind==="plc"?(!i.decoder&&this.deps.codec&&(i.decoder=this.deps.codec.createDecoder(o=>this.sink?.play(s,o,this.volumeOf(s)))),i.decoder?.decode(n.kind==="frame"?n.opus:null)):n.kind==="idle"&&i.talking?(i.talking=!1,this.emit("speaking",s,!1)):i.talking&&t-i.lastHeard>yt+100&&!i.jitter.active&&(i.talking=!1,this.emit("speaking",s,!1))}}onPeerLeft(t){this.relay.forget(t);let s=this.speakers.get(t);s&&(s.decoder?.close(),s.talking&&this.emit("speaking",t,!1),this.speakers.delete(t))}close(){this._enabled=!1,this.stop(),this.endTimer?.()}static END=1};function F(r){return r==="left"||r==="disconnected"?1e3:r==="host_left"||r==="closed"?1001:r==="kicked"?4e3:1006}var _e=globalThis.setImmediate??(r=>{setTimeout(r,0)}),gt=new TextEncoder,kt=new TextDecoder,U=class{constructor(e,t,s,i){this.peerId=e;this.out=t;this.binary=s;this.onLocalClose=i}peerId;out;binary;onLocalClose;readyState=0;bufferedAmount=0;openHandler=null;openPending=!1;onmessage=null;onclose=null;onerror=null;detach=()=>{};send(e){this.readyState===1&&this.out(typeof e=="string"?gt.encode(e):e)}close(e=1e3,t="left"){this.readyState!==3&&(this.closed(e,t),this.onLocalClose())}get onopen(){return this.openHandler}set onopen(e){this.openHandler=e,e&&this.openPending&&_e(()=>this.fireOpen())}open(e=!1){this.readyState===0&&(this.readyState=1,this.openPending=!0,e?_e(()=>this.fireOpen()):this.fireOpen())}fireOpen(){this.readyState!==1||!this.openPending||!this.openHandler||(this.openPending=!1,this.openHandler({type:"open"}))}deliver(e){this.readyState===1&&this.onmessage?.({type:"message",data:this.binary?e:kt.decode(e)})}closed(e,t){this.readyState!==3&&(this.readyState=3,this.detach(),this.onclose?.({type:"close",code:e,reason:t,wasClean:e===1e3||e===1001}))}};function Le(r,e={}){if(r.isHost)throw new Error("asWebSocket() is for joiners; the host uses lobby.onConnection()");let t=e.channel??"reliable",s=new U(r.hostPeerId,n=>r.send(r.hostPeerId,n,{channel:t}),!!e.binary,()=>{r.leave()}),i=[r.on("message",(n,o)=>{n===r.hostPeerId&&s.deliver(o)}),r.on("sessionEnded",(n,o)=>s.closed(o?.code??F(n),o?.reason??n)),r.on("hostChanged",()=>s.closed(1001,"host_changed"))];return s.detach=()=>i.forEach(n=>n()),r.connectedPeers.includes(r.hostPeerId)?s.open(!0):r.ready.then(()=>s.open(!0),()=>{}),s}function Me(r,e,t={}){if(!r.isHost)throw new Error("onConnection() is for the host; a joiner uses lobby.asWebSocket()");let s=t.channel??"reliable",i=new Map,n=!1,o=c=>{if(c===r.localPeerId)return;let l=i.get(c);if(l||n)return l;l=new U(c,u=>r.send(c,u,{channel:s}),!!t.binary,()=>{});let h=l;return h.detach=()=>{i.get(c)===h&&i.delete(c)},i.set(c,h),h.open(),e(h),h},a=[r.on("peerReady",c=>{o(c.peerId)}),r.on("message",(c,l)=>o(c)?.deliver(l)),r.on("peerLeft",(c,l,h)=>i.get(c.peerId)?.closed(h?.code??(l==="left"?1001:F(l)),h?.reason??l)),r.on("sessionEnded",c=>{for(let l of[...i.values()])l.closed(c==="left"?1001:F(c),c);a.forEach(l=>l())})];for(let c of r.connectedPeers)o(c);return()=>{n=!0}}var Re=6e3,wt=3e3;function N(r){let e=typeof r.name=="string"?r.name.trim():"";return e&&e!=="Player"?r:{...r,name:`Player ${r.peerId}`}}function St(r){return r.some(e=>(Array.isArray(e.urls)?e.urls:[e.urls]).some(t=>/^turns?:/i.test(String(t))))}var xt=250,Pt=2e3,C=class extends f{code;mode;tickRate;lockstep;chat;voice;localPeerId;hostPeerId;phase;meta;resumeToken;rtt=0;joinCode="";joinCodeKind="none";directAddresses=[];portMapping="";signaling;cfg;roster=new Map;links=new Map;earlyIce=new Map;connected=new Set;hiSentTo=new Set;epoch=0;ended=!1;serverUp=!0;cancelWatchdog=null;cancelPing=null;pingN=0;chunkId=1;reassemblers=new Map;hub;outbox=new Map;markReady=()=>{};failReady=()=>{};isReady=!1;cancelReadyTimer=null;relayRetry=!1;lastHeard=new Map;lingering=new Set;rate=new Map;ready=new Promise((e,t)=>{this.markReady=()=>{this.isReady||(this.isReady=!0,this.cancelReadyTimer?.(),this.cancelReadyTimer=null,e())},this.failReady=t});constructor(e){super(),this.cfg=e,this.signaling=e.signaling;let t=e.state;this.code=t.code,this.mode=t.mode,this.tickRate=t.tickRate,this.localPeerId=t.you,this.hostPeerId=t.hostId,this.phase=t.phase,this.meta={...t.meta},this.resumeToken=t.resumeToken;for(let i of t.peers)this.roster.set(i.peerId,N(i));this.ready.catch(()=>{});let s=this;this.hub={get clock(){return s.cfg.clock},get localPeerId(){return s.localPeerId},get hostPeerId(){return s.hostPeerId},get isHost(){return s.isHost},get tickRate(){return s.tickRate},connectedPeers:()=>s.connectedPeerIds(),sendTo:(i,n,o,a)=>s.sendTo(i,n,o??0,a),sendToHost:(i,n,o)=>{s.isHost||s.sendTo(s.hostPeerId,i,n??0,o)},dropPeer:(i,n)=>s.dropPeer(i,n),reportError:i=>s.reportError(i),nextChunkId:()=>s.chunkId++>>>0},this.lockstep=new x(this.hub,{hashEveryTicks:e.hashEveryTicks}),this.chat=new w(this.hub,e.wall,i=>this.voice.isMutedPeer(i)),this.voice=new P(this.hub,e.voice),this.lockstep.on("started",()=>this.setPhase("in_game")),this.lockstep.on("error",i=>this.reportError(i)),this.signaling.onMessage=i=>this.onSignaling(i),this.signaling.onClose=()=>this.onServerClosed(),this.isHost?this.markReady():this.armReadyTimer(),this.isHost&&this.mode==="lockstep"&&(this.phase==="in_game"?this.lockstep.hostStart():this.lockstep.hostIdle()),this.cancelWatchdog=e.clock.setInterval(()=>this.watchdog(),xt),this.cancelPing=e.clock.setInterval(()=>this.pingHost(),Pt)}get isHost(){return this.localPeerId===this.hostPeerId}get peers(){return[...this.roster.values()].sort((e,t)=>e.peerId-t.peerId)}peer(e){return this.roster.get(e)}get connectedPeers(){return this.connectedPeerIds()}asWebSocket(e){return Le(this,e)}onConnection(e,t){return Me(this,e,t)}set snapshotChunkSize(e){this.lockstep.chunkSize=e}get hostEpoch(){return this.epoch}_offer(e){return this.offerTo(e),this.links.get(e)}_answer(e){return this.makeLink(e)}_begin(){}async start(){if(!this.isHost)throw new Error("only the host can start the game");this.phase!=="in_game"&&(this.signaling.send({t:"phase",phase:"in_game"}),this.setPhase("in_game"),this.mode==="lockstep"&&this.lockstep.hostStart())}async setMeta(e){if(!this.isHost)throw new Error("only the host can set metadata");this.meta={...e},this.signaling.send({t:"meta",meta:e}),this.emit("metaChanged",this.meta)}async kick(e,t={}){if(!this.isHost)throw new Error("only the host can kick");let{ban:s=!0,code:i,reason:n}=t;if(i!==void 0&&!(i===1e3||Number.isInteger(i)&&i>=3e3&&i<=4999))throw new RangeError("close code must be 1000 or 3000-4999");if(n!==void 0&&n.length>120)throw new RangeError("close reason must be at most 120 characters");let o={t:"kick",peerId:e};s||(o.ban=!1),i!==void 0&&(o.closeCode=i),n!==void 0&&(o.closeReason=n),this.signaling.send(o);let a=i!==void 0||n!==void 0?{code:i,reason:n}:void 0;this.removePeer(e,s?"kicked":"disconnected",a,!0)}disconnect(e,t,s){return this.kick(e,{ban:!1,code:t,reason:s})}async approve(e,t){if(!this.isHost)throw new Error("only the host can approve joins");this.signaling.send({t:"approve",peerId:e,ok:t})}async leave(){this.ended||(this.signaling.send({t:"leave"}),this.teardown("left"))}send(e,t,s={}){let i=s.channel??"reliable";this.isHost?this.sendTo(e,{type:"raw",bytes:t},this.localPeerId,i):this.sendTo(this.hostPeerId,{type:"raw",bytes:t},e,i)}broadcast(e,t={}){let s=t.channel??"reliable";if(this.isHost)for(let i of this.roster.keys())i!==this.localPeerId&&this.sendTo(i,{type:"raw",bytes:e},this.localPeerId,s);else this.sendTo(this.hostPeerId,{type:"raw",bytes:e},0,s)}channelFor(e){return e.type==="voice"?"voice":e.type==="ping"||e.type==="pong"?"fast":"reliable"}sendTo(e,t,s,i){let n=this.links.get(e),o=i??this.channelFor(t),a=t.type==="raw"||t.type==="chat"||t.type==="cmd";if(!(!!n?.isOpen&&(!a||!this.isHost||this.connected.has(e)))){let l=this.roster.has(e)||e===this.hostPeerId;if(a&&o==="reliable"&&l&&!this.ended){let h=this.outbox.get(e);h||this.outbox.set(e,h=[]),h.length>=1e3&&h.shift(),h.push({msg:t,route:s,channel:i})}return}if(n)try{let l=o,h;try{h=v(t,s)}catch(u){if(!(u instanceof p)||u.code!=="too_long")throw u;h=v(t,s,!0)}if(h.length>16384)for(let u of M(this.chunkId++>>>0,h))n.send(l,v(u,s));else n.send(l,h)}catch(l){this.reportError(l instanceof Error?l:new Error(String(l)))}}connectedPeerIds(){return this.isHost?[...this.connected].filter(t=>this.links.get(t)?.isOpen).sort((t,s)=>t-s):this.links.get(this.hostPeerId)?.isOpen?[this.hostPeerId]:[]}reportError(e){this.errorSink?.(e)}errorSink=null;seatSink=null;armReadyTimer(){let e=this.cfg.readyTimeoutMs??0;if(!(e>0))return;let t=!this.cfg.relayOnly&&St(this.cfg.ice),s=t?Math.ceil(e/2):e;this.cancelReadyTimer=this.cfg.clock.setTimeout(()=>{this.isReady||this.ended||(t?this.retryRelayed(e-s):this.giveUpConnecting())},s)}retryRelayed(e){this.relayRetry=!0,this.makeLink(this.hostPeerId,!0).startOffer(),this.cancelReadyTimer=this.cfg.clock.setTimeout(()=>{!this.isReady&&!this.ended&&this.giveUpConnecting()},e)}giveUpConnecting(){let e=new d("ice_failed",`could not open a connection to the host${this.relayRetry?" (direct or relayed)":""}`);this.failReady(e),this.reportError(e),!this.ended&&(this.signaling.send({t:"leave"}),this.teardown("ice_failed"))}makeLink(e,t=this.cfg.relayOnly){let s=this.links.get(e);s&&(s.onClose=null,s.onMessage=null,s.close());let i=this.cfg.links.create({remoteId:e,iceServers:this.cfg.ice,relayOnly:t});return this.links.set(e,i),i.onSignal=n=>this.signaling.send({t:"signal",to:e,...n}),i.onOpen=()=>this.onLinkOpen(e,i),i.onClose=n=>this.onLinkClosed(e,i,n),i.onMessage=(n,o)=>this.onData(e,n,o),i.onPath=n=>this.signaling.send({t:"path",path:n}),i}offerTo(e){this.makeLink(e).startOffer()}onLinkOpen(e,t){if(this.isHost){this.epoch>0&&this.sendTo(e,{type:"host_claim",epoch:this.epoch,hostId:this.localPeerId,tick:this.lockstep.lastTick},0);return}e===this.hostPeerId&&this.sendHi(t)}sendHi(e){this.hiSentTo.has(e)||(this.hiSentTo.add(e),this.lockstep.touch(),e.send("reliable",v(this.lockstep.hiMessage(this.localPeerId))),this.markReady(),this.emit("ready"),this.emit("peerReady",this.roster.get(e.remoteId)??{peerId:e.remoteId,name:""}),this.flush(e.remoteId))}onLinkClosed(e,t,s){if(this.links.get(e)===t&&(this.links.delete(e),this.hiSentTo.delete(t),!this.ended)){if(this.isHost)this.connected.delete(e),this.lockstep.onPeerLeft(e),this.voice.onPeerLeft(e),!this.serverUp&&this.roster.has(e)&&this.removePeer(e,"timeout");else if(e===this.hostPeerId){if(!this.isReady&&this.cancelReadyTimer)return;this.handleHostLost("link closed")}}}onData(e,t,s,i=!1){let n;try{n=be(s,i)}catch(c){this.reportError(c instanceof Error?c:new Error(String(c)));return}if(!n)return;i||this.lastHeard.set(e,this.cfg.clock.now());let{msg:o,route:a}=n;switch(o.type){case"hi":this.isHost&&this.onHi(e,o.lastTick);break;case"welcome_dc":this.isHost||(o.hostId!==this.hostPeerId&&this.setHost(o.hostId),this.lockstep.touch());break;case"cmd":case"frame":case"hash":case"desync":case"snapshot_req":case"snapshot":(e===this.hostPeerId||this.isHost)&&this.lockstep.handle(e,o);break;case"chunk":{if(i)break;let c=this.reassemblers.get(e);c||this.reassemblers.set(e,c=new L);let l;try{l=c.push(o)}catch(h){this.reportError(h instanceof Error?h:new Error(String(h)));break}if(!l)break;this.lockstep.snapshotPending&&e===this.hostPeerId?this.lockstep.onSnapshotPayload(l):this.onData(e,t,l,!0);break}case"chat":case"chat_history":case"chat_reject":this.chat.handle(e,a,o);break;case"voice":t==="voice"&&this.voice.handle(e,a,o);break;case"raw":this.onRaw(e,a,o.bytes,t);break;case"ping":this.sendTo(e,{type:"pong",n:o.n,t:o.t},0,"fast");break;case"pong":this.rtt=Math.max(0,(this.cfg.clock.now()>>>0)-o.t);break;case"host_claim":this.onHostClaim(e,o.epoch,o.hostId);break}}onHi(e,t){this.connected.add(e),this.sendTo(e,{type:"welcome_dc",tick:this.mode==="lockstep"?this.lockstep.currentTick:0,tickRate:this.tickRate,mode:this.mode==="lockstep"?fe:me,hostId:this.localPeerId},0),this.chat.sendHistory(e),this.mode==="lockstep"&&this.lockstep.onPeerHi(e,t),this.emit("peerReady",this.roster.get(e)??{peerId:e,name:""}),this.flush(e)}flush(e){let t=this.outbox.get(e);if(t){this.outbox.delete(e);for(let s of t)this.sendTo(e,s.msg,s.route,s.channel)}}onRaw(e,t,s,i){if(!this.isHost){this.emit("message",t,s,i);return}if(this.allowRaw(e))if((t===0||t===this.localPeerId)&&this.emit("message",e,s,i),t===0)for(let n of this.connectedPeerIds())n!==e&&this.sendTo(n,{type:"raw",bytes:s},e,i);else t!==this.localPeerId&&this.sendTo(t,{type:"raw",bytes:s},e,i)}allowRaw(e){let t=this.cfg.rateLimit;if(!t||!(t.messages>0)||!(t.perMs>0))return!0;let s=this.cfg.clock.now(),i=this.rate.get(e);return(!i||s-i.start>=t.perMs)&&this.rate.set(e,i={start:s,n:0}),++i.n<=t.messages}setHost(e){e!==this.hostPeerId&&(this.hostPeerId=e,this.emit("hostChanged",e))}handleHostLost(e){if(this.isHost||this.ended)return;if(this.cfg.direct){this.teardown("host_left");return}let t=this.hostPeerId,s=this.roster.get(t);s&&(this.roster.delete(t),this.emit("peerLeft",s,e==="link closed"?"timeout":e));let i=this.links.get(t);i&&(i.onClose=null,i.onMessage=null,i.close(),this.links.delete(t),this.hiSentTo.delete(i)),(xe([...this.roster.keys(),this.localPeerId],t)??this.localPeerId)===this.localPeerId&&this.becomeHost()}becomeHost(){this.epoch=this.epoch+1&65535,this.hostPeerId=this.localPeerId,this.markReady(),this.connected.clear(),this.hiSentTo.clear(),this.lastHeard.clear(),this.lockstep.becomeHost(),this.emit("hostChanged",this.localPeerId),this.serverUp&&this.signaling.send({t:"host_changed",hostId:this.localPeerId,epoch:this.epoch});for(let e of this.roster.keys())e!==this.localPeerId&&this.offerTo(e)}onHostClaim(e,t,s){let i={epoch:this.epoch,hostId:this.hostPeerId},n={epoch:t,hostId:s};if(s===this.hostPeerId&&t===this.epoch&&!this.isHost){let c=this.links.get(e);c&&this.sendHi(c);return}if(!Pe(n,i))return;let o=this.isHost;if(this.epoch=t,o){for(let[c,l]of this.links)c!==e&&(l.onClose=null,l.onMessage=null,l.close(),this.links.delete(c));this.connected.clear(),this.lockstep.becomeJoiner()}this.setHost(s);let a=this.links.get(e);a&&e===s&&this.sendHi(a)}onSignaling(e){switch(e.t){case"peer_joined":{if(!e.peer||this.roster.has(e.peer.peerId))return;let t=N(e.peer);this.roster.set(t.peerId,t),this.emit("peerJoined",t),this.isHost&&this.offerTo(t.peerId);break}case"peer_left":this.removePeer(Number(e.peerId),String(e.reason??"left"));break;case"host_changed":{if(this.cfg.direct){this.isHost||this.teardown("host_left");break}let t=Number(e.hostId);if(typeof e.epoch=="number"&&e.epoch>this.epoch&&(this.epoch=e.epoch&65535),t===this.hostPeerId)break;if(t===this.localPeerId&&!this.isHost){let s=this.hostPeerId;this.roster.delete(s),this.becomeHost()}else this.setHost(t);break}case"join_request":this.emit("joinRequested",N(e.peer));break;case"lobby":{let t=e;this.meta={...t.meta},t.resumeToken&&t.resumeToken!==this.resumeToken&&(this.resumeToken=t.resumeToken,this.seatSink?.(this.resumeToken)),this.roster=new Map(t.peers.map(s=>[s.peerId,N(s)])),t.hostId!==this.hostPeerId&&!this.isHost&&this.setHost(t.hostId),this.setPhase(t.phase);break}case"meta":this.meta={...e.meta},this.emit("metaChanged",this.meta);break;case"phase":this.setPhase(e.phase);break;case"signal":this.onSignal(Number(e.from),e);break;case"error":if(e.code==="kicked"){let t=e.data??{},s={};typeof t.closeCode=="number"&&(s.code=t.closeCode),typeof t.closeReason=="string"&&(s.reason=t.closeReason),this.teardown(t.ban===!1?"disconnected":"kicked",s.code!==void 0||s.reason!==void 0?s:void 0)}else this.reportError(new d(String(e.code),String(e.msg??""),e.data));break}}async onSignal(e,t){let s=this.links.get(e),i={};if(t.sdp&&(i.sdp=t.sdp),t.ice&&(i.ice=t.ice),t.sdp?.type==="offer"){s=this.makeLink(e);let n=this.earlyIce.get(e)??[];this.earlyIce.delete(e);let o=s.handleSignal(i);for(let a of n)s.handleSignal(a);await o;return}if(!s){if(!i.ice||this.ended||!this.roster.has(e))return;let n=this.earlyIce.get(e)??[];n.length<64&&n.push(i),this.earlyIce.set(e,n);return}await s.handleSignal(i)}removePeer(e,t,s,i=!1){let n=this.roster.get(e);if(e===this.localPeerId){this.teardown(t==="kicked"?"kicked":t);return}n&&this.roster.delete(e),this.earlyIce.delete(e);let o=this.links.get(e);if(o&&e!==this.hostPeerId)if(o.onMessage=null,this.links.delete(e),this.hiSentTo.delete(o),i){this.lingering.add(o);let a=()=>{this.lingering.delete(o)&&(c(),o.onClose=null,o.close())},c=this.cfg.clock.setTimeout(a,wt);o.onClose=a}else o.onClose=null,o.close();this.connected.delete(e),this.outbox.delete(e),this.lastHeard.delete(e),this.rate.delete(e),this.lockstep.onPeerLeft(e),this.chat.onPeerLeft(e),this.voice.onPeerLeft(e),n&&this.emit("peerLeft",n,t,s),e===this.hostPeerId&&!this.isHost&&this.handleHostLost(t)}dropPeer(e,t){this.signaling.send({t:"kick",peerId:e,ban:!1,closeReason:t}),this.removePeer(e,t,void 0,!0)}setPhase(e){e!=="lobby"&&e!=="in_game"||this.phase!==e&&(this.phase=e,e==="in_game"&&!this.lockstep.started&&this.lockstep.markStarted(),this.emit("phase",e))}onServerClosed(){if(!this.ended){if(this.cfg.direct&&!this.isHost){this.teardown("host_left");return}this.serverUp=!1,this.emit("serverLost"),this.links.size===0&&this.roster.size<=1&&this.teardown("server_lost")}}watchdog(){if(this.ended)return;let e=this.cfg.peerTimeoutMs??0;e>0&&this.dropSilent(Math.max(e,Re)),!(this.ended||this.isHost)&&this.mode==="lockstep"&&this.lockstep.hostSilent()&&(this.lockstep.touch(),this.lockstep.pokeHost(),this.handleHostLost("timeout"))}dropSilent(e){let t=this.cfg.clock.now();if(this.isHost)for(let s of[...this.connected]){let i=this.lastHeard.get(s);i!==void 0&&t-i>e&&this.removePeer(s,"timeout")}else if(this.isReady){let s=this.lastHeard.get(this.hostPeerId);s!==void 0&&t-s>e&&(this.lastHeard.delete(this.hostPeerId),this.handleHostLost("timeout"))}}pingHost(){this.ended||this.isHost||this.sendTo(this.hostPeerId,{type:"ping",n:++this.pingN,t:this.cfg.clock.now()>>>0},0,"fast")}teardown(e,t){if(!this.ended){if(this.ended=!0,this.cancelReadyTimer?.(),!this.isReady){let s=e==="ice_failed"||e==="kicked"?e:"closed";this.failReady(new d(s,`the lobby ended before connecting (${e})`))}this.cancelWatchdog?.(),this.cancelPing?.(),this.lockstep.stop(),this.voice.close();for(let s of[...this.links.values(),...this.lingering])s.onClose=null,s.onMessage=null,s.close();this.links.clear(),this.earlyIce.clear(),this.lingering.clear(),this.signaling.onClose=null,this.signaling.onMessage=null,this.signaling.close(),this.emit("sessionEnded",e,t)}}get linkCount(){return this.links.size}};var j={reliable:0,fast:1,voice:2},Ae=r=>new WebSocket(r);function ie(){return typeof AudioEncoder<"u"&&typeof AudioDecoder<"u"&&typeof AudioData<"u"}var Ie={createEncoder(r,e){let t=0,s=new AudioEncoder({output:i=>{let n=new Uint8Array(i.byteLength);i.copyTo(n),e(n)},error:i=>console.error("onelobby: AudioEncoder",i)});return s.configure({codec:"opus",sampleRate:48e3,numberOfChannels:1,bitrate:r.bitrate,opus:{frameDuration:2e4,application:"voip",useinbandfec:!0,usedtx:!1,packetlossperc:10}}),{encode(i){let n=new AudioData({format:"f32",sampleRate:48e3,numberOfFrames:i.length,numberOfChannels:1,timestamp:t,data:i});t+=2e4,s.encode(n),n.close()},close(){s.state!=="closed"&&s.close()}}},createDecoder(r){let e=0,t=new AudioDecoder({output:s=>{let i=new Float32Array(s.numberOfFrames);s.copyTo(i,{planeIndex:0,format:"f32-planar"}),s.close(),r(i)},error:s=>console.error("onelobby: AudioDecoder",s)});return t.configure({codec:"opus",sampleRate:48e3,numberOfChannels:1}),{decode(s){if(s===null){r(new Float32Array(960));return}t.decode(new EncodedAudioChunk({type:"key",timestamp:e,data:s})),e+=2e4},close(){t.state!=="closed"&&t.close()}}}},Ct=`
|
|
1
|
+
/*! onelobby 0.6.0 | MIT license */
|
|
2
|
+
function $s(r,e){if(typeof Worker>"u"||typeof Blob>"u"||typeof URL>"u"||!URL.createObjectURL)return null;try{let t="let h=0;onmessage=e=>{if(e.data&&e.data.start){h=setInterval(()=>postMessage(0),e.data.ms)}else{clearInterval(h);close()}}",s=URL.createObjectURL(new Blob([t],{type:"text/javascript"})),n=new Worker(s);return URL.revokeObjectURL(s),n.onmessage=()=>r(),n.postMessage({start:!0,ms:e}),()=>{n.postMessage({stop:!0}),n.terminate()}}catch{return null}}var Mt={now:()=>typeof performance<"u"?performance.now():Date.now(),setInterval(r,e){let t=$s(r,e);if(t)return t;let s=setInterval(r,e);return()=>clearInterval(s)},setTimeout(r,e){let t=setTimeout(r,e);return()=>clearTimeout(t)}};var E=class{handlers=new Map;on(e,t){let s=this.handlers.get(e);return s||this.handlers.set(e,s=[]),s.push(t),()=>this.off(e,t)}off(e,t){let s=this.handlers.get(e);if(!s)return;let n=s.indexOf(t);n>=0&&s.splice(n,1)}emit(e,...t){let s=this.handlers.get(e);if(s)for(let n of[...s])try{n(...t)}catch(i){queueMicrotask(()=>{throw i})}}};var X="0123456789ABCDEFGHJKMNPQRSTVWXYZ";function Vs(r){return r.replace(/[a-z]/g,e=>e.toUpperCase())}function I(r){let e=Vs(r).replace(/[ \-]/g,"").replace(/[IL]/g,"1").replace(/O/g,"0");if(e.length!==6)return null;for(let t of e)if(X.indexOf(t)<0)return null;return e}function $(r){let e=I(r)??r.toUpperCase();return e.length===6?`${e.slice(0,3)} ${e.slice(3)}`:e}var Ke={hi:1,welcome_dc:2,cmd:16,frame:17,hash:18,desync:19,snapshot_req:20,snapshot:21,chunk:22,raw:32,ping:48,pong:49,host_claim:64,voice:80,chat:96,chat_history:97,chat_reject:98,pause_request:112,pause_state:113,pause_refuse:114},It=1,Ot=2,C=class extends Error{constructor(t,s=""){super(s?`${t}: ${s}`:t);this.code=t;this.name="CodecError"}code},Rt=new TextEncoder,Ks=new TextDecoder("utf-8",{fatal:!0});function At(r){try{return Ks.decode(r)}catch{throw new C("bad_utf8")}}var We=class{buf;dv;pos=0;constructor(e){this.buf=new Uint8Array(e),this.dv=new DataView(this.buf.buffer)}ensure(e){if(this.pos+e<=this.buf.length)return;let t=new Uint8Array(Math.max(this.buf.length*2,this.pos+e));t.set(this.buf),this.buf=t,this.dv=new DataView(t.buffer)}u8(e){this.ensure(1),this.dv.setUint8(this.pos,e),this.pos+=1}u16(e){this.ensure(2),this.dv.setUint16(this.pos,e,!0),this.pos+=2}u32(e){this.ensure(4),this.dv.setUint32(this.pos,e>>>0,!0),this.pos+=4}i64(e){this.ensure(8),this.dv.setBigInt64(this.pos,BigInt(e),!0),this.pos+=8}u64(e){this.ensure(8),this.dv.setBigUint64(this.pos,BigInt.asUintN(64,e),!0),this.pos+=8}bytes(e){this.ensure(e.length),this.buf.set(e,this.pos),this.pos+=e.length}done(){return this.buf.slice(0,this.pos)}},ze=class{constructor(e){this.buf=e;this.dv=new DataView(e.buffer,e.byteOffset,e.byteLength)}buf;dv;pos=0;need(e){if(this.pos+e>this.buf.length)throw new C("too_short","truncated frame")}u8(){this.need(1);let e=this.dv.getUint8(this.pos);return this.pos+=1,e}u16(){this.need(2);let e=this.dv.getUint16(this.pos,!0);return this.pos+=2,e}u32(){this.need(4);let e=this.dv.getUint32(this.pos,!0);return this.pos+=4,e}i64(){this.need(8);let e=this.dv.getBigInt64(this.pos,!0);return this.pos+=8,Number(e)}u64(){this.need(8);let e=this.dv.getBigUint64(this.pos,!0);return this.pos+=8,e}bytes(e){this.need(e);let t=this.buf.slice(this.pos,this.pos+e);return this.pos+=e,t}rest(){let e=this.buf.slice(this.pos);return this.pos=this.buf.length,e}};function Z(r,e=0,t=!1){let s=new We(64);switch(s.u8(Ke[r.type]),s.u16(e),r.type){case"hi":s.u8(r.proto),s.u16(r.peerId),s.i64(r.lastTick);break;case"welcome_dc":s.i64(r.tick),s.u8(r.tickRate),s.u8(r.mode),s.u16(r.hostId);break;case"cmd":s.u32(r.seq),s.u16(r.bytes.length),s.bytes(r.bytes);break;case"frame":s.i64(r.tick),s.u16(r.commands.length);for(let i of r.commands)s.u16(i.peerId),s.u16(i.bytes.length),s.bytes(i.bytes);break;case"hash":s.i64(r.tick),s.u64(r.hash);break;case"desync":s.i64(r.tick),s.u64(r.hostHash),s.u64(r.yourHash);break;case"snapshot_req":s.i64(r.sinceTick);break;case"snapshot":s.i64(r.tick),s.u32(r.total),s.u8(r.flags);break;case"chunk":s.u32(r.id),s.u16(r.index),s.u16(r.count),s.bytes(r.bytes);break;case"raw":s.bytes(r.bytes);break;case"ping":case"pong":s.u32(r.n),s.u32(r.t);break;case"host_claim":s.u16(r.epoch),s.u16(r.hostId),s.i64(r.tick);break;case"voice":s.u16(r.seq),s.u8(r.flags),s.u8(r.frames.length);for(let i of r.frames)s.u16(i.length),s.bytes(i);break;case"chat":{let i=Rt.encode(r.text);s.u32(r.id),s.i64(r.ts),s.u16(i.length),s.bytes(i);break}case"chat_history":s.u16(r.lines.length);for(let i of r.lines){let o=Rt.encode(i.text);s.u16(i.peerId),s.u32(i.id),s.i64(i.ts),s.u16(o.length),s.bytes(o)}break;case"chat_reject":s.u32(r.id),s.u8(r.reason);break;case"pause_request":s.u8(r.paused);break;case"pause_state":s.u8(r.paused),s.u16(r.byPeer),s.u8(r.flags);break;case"pause_refuse":s.u8(r.paused),s.u8(r.reason);break}let n=s.done();if(!t&&n.length>16384)throw new C("too_long",`${n.length} > 16384`);return n}var Ws=new Map(Object.keys(Ke).map(r=>[Ke[r],r]));function Ht(r,e=!1){let t=e?67108864:16384;if(r.length>t)throw new C("too_long",`${r.length} > ${t}`);if(r.length<3)throw new C("too_short","header");let s=new ze(r),n=Ws.get(s.u8()),i=s.u16();if(!n)return null;let o;switch(n){case"hi":o={type:n,proto:s.u8(),peerId:s.u16(),lastTick:s.i64()};break;case"welcome_dc":o={type:n,tick:s.i64(),tickRate:s.u8(),mode:s.u8(),hostId:s.u16()};break;case"cmd":{let a=s.u32();o={type:n,seq:a,bytes:s.bytes(s.u16())};break}case"frame":{let a=s.i64(),l=s.u16(),c=[];for(let h=0;h<l;h++){let d=s.u16();c.push({peerId:d,bytes:s.bytes(s.u16())})}o={type:n,tick:a,commands:c};break}case"hash":o={type:n,tick:s.i64(),hash:s.u64()};break;case"desync":o={type:n,tick:s.i64(),hostHash:s.u64(),yourHash:s.u64()};break;case"snapshot_req":o={type:n,sinceTick:s.i64()};break;case"snapshot":o={type:n,tick:s.i64(),total:s.u32(),flags:s.u8()};break;case"chunk":o={type:n,id:s.u32(),index:s.u16(),count:s.u16(),bytes:s.rest()};break;case"raw":o={type:n,bytes:s.rest()};break;case"ping":case"pong":o={type:n,n:s.u32(),t:s.u32()};break;case"host_claim":o={type:n,epoch:s.u16(),hostId:s.u16(),tick:s.i64()};break;case"voice":{let a=s.u16(),l=s.u8(),c=s.u8(),h=[];for(let d=0;d<c;d++)h.push(s.bytes(s.u16()));o={type:n,seq:a,flags:l,frames:h};break}case"chat":{let a=s.u32(),l=s.i64();o={type:n,id:a,ts:l,text:At(s.bytes(s.u16()))};break}case"chat_history":{let a=s.u16(),l=[];for(let c=0;c<a;c++){let h=s.u16(),d=s.u32(),u=s.i64();l.push({peerId:h,id:d,ts:u,text:At(s.bytes(s.u16()))})}o={type:n,lines:l};break}case"chat_reject":o={type:n,id:s.u32(),reason:s.u8()};break;case"pause_request":o={type:n,paused:s.u8()};break;case"pause_state":o={type:n,paused:s.u8(),byPeer:s.u16(),flags:s.u8()};break;case"pause_refuse":o={type:n,paused:s.u8(),reason:s.u8()};break}if(s.pos!==r.length)throw new C("trailing",`${r.length-s.pos} extra bytes`);return{route:i,msg:o}}var ue={rate:1,length:2,mutedByHost:3};function Ye(r){return r===1?"rate":r===2?"length":r===3?"muted_by_host":"unknown"}var Ge=1,H={notInGame:1,rateLimited:2,notAllowed:3};var Xe=256,Nt=3,zs=5e3,Ys=5,pe=100,Gs=/[\u0000-\u001f\u007f-\u009f\u202a-\u202e\u2066-\u2069]/g;function Xs(r){return r.replace(Gs,"")}function Ft(r){return new TextEncoder().encode(r).length}var Ze=class{buckets=new Map;refillMs=zs/Ys;tryTake(e,t){let s=this.buckets.get(e);return s||this.buckets.set(e,s={tokens:Nt,at:t}),s.tokens=Math.min(Nt,s.tokens+(t-s.at)/this.refillMs),s.at=t,s.tokens<1?!1:(s.tokens-=1,!0)}forget(e){this.buckets.delete(e)}},Q=class extends E{constructor(t,s,n){super();this.hub=t;this.wall=s;this.isLocallyMuted=n}hub;wall;isLocallyMuted;filter=t=>t;lines=[];limiter=new Ze;hostMuted=new Set;localId=0;stampId=0;get history(){return this.lines}send(t){let s=++this.localId>>>0;return Ft(t)>Xe*4?(queueMicrotask(()=>this.emit("rejected",s,"length")),s):(this.hub.isHost?this.hostProcess(this.hub.localPeerId,s,t):this.hub.sendToHost({type:"chat",id:s,ts:0,text:t}),s)}hostMute(t,s){if(!this.hub.isHost)throw new Error("only the host can mute chat");s?this.hostMuted.add(t):this.hostMuted.delete(t)}isHostMuted(t){return this.hostMuted.has(t)}onPeerLeft(t){this.limiter.forget(t),this.hostMuted.delete(t)}sendHistory(t){let s=this.lines.slice(-pe).map(n=>({...n}));this.hub.sendTo(t,{type:"chat_history",lines:s},0)}handle(t,s,n){switch(n.type){case"chat":this.hub.isHost?this.hostProcess(t,n.id,n.text):this.deliver({peerId:s,id:n.id,ts:n.ts,text:n.text});break;case"chat_history":if(!this.hub.isHost){this.lines=[];for(let i of n.lines.slice(-pe))this.push({...i,text:this.safeFilter(i.text)});this.emit("history",this.lines)}break;case"chat_reject":this.emit("rejected",n.id,Ye(n.reason));break}}hostProcess(t,s,n){let i=c=>{t===this.hub.localPeerId?this.emit("rejected",s,Ye(c)):this.hub.sendTo(t,{type:"chat_reject",id:s,reason:c},0)};if(this.hostMuted.has(t))return i(ue.mutedByHost);if(Ft(n)>Xe)return i(ue.length);if(!this.limiter.tryTake(t,this.hub.clock.now()))return i(ue.rate);let o=Xs(n);if(o.length===0)return;let a={peerId:t,id:++this.stampId>>>0,ts:this.wall(),text:o},l={type:"chat",id:a.id,ts:a.ts,text:a.text};for(let c of this.hub.connectedPeers())this.hub.sendTo(c,l,t);this.deliver(a)}deliver(t){let s={...t,text:this.safeFilter(t.text)};this.push(s),!this.isLocallyMuted(t.peerId)&&this.emit("message",s)}push(t){this.isLocallyMuted(t.peerId)||(this.lines.push(t),this.lines.length>pe&&this.lines.splice(0,this.lines.length-pe))}safeFilter(t){try{return this.filter(t)}catch{return t}}};var Qe=16e3;function fe(r,e,t=Qe){let s=Math.max(1,Math.ceil(e.length/t));if(s>65535)throw new RangeError("payload needs more than 65535 chunks");let n=[];for(let i=0;i<s;i++)n.push({type:"chunk",id:r,index:i,count:s,bytes:e.subarray(i*t,Math.min(e.length,(i+1)*t))});return n}var Zs=8,me=class{parts=new Map;push(e){if(e.count===0||e.index>=e.count)return null;let t=this.parts.get(e.id);if(!t||t.count!==e.count){for(this.parts.delete(e.id);this.parts.size>=Zs;)this.parts.delete(this.parts.keys().next().value);t={count:e.count,got:0,parts:new Array(e.count),bytes:0},this.parts.set(e.id,t)}if(t.parts[e.index])return null;if(t.parts[e.index]=e.bytes,t.got++,t.bytes+=e.bytes.length,t.bytes>67108864)throw this.parts.delete(e.id),new C("too_long",`chunk set over ${67108864} bytes`);if(t.got<t.count){for(;this.pendingBytes>67108864&&this.parts.size>1;)for(let i of this.parts.keys())if(i!==e.id){this.parts.delete(i);break}return null}this.parts.delete(e.id);let s=new Uint8Array(t.bytes),n=0;for(let i of t.parts)s.set(i,n),n+=i.length;return s}get pending(){return this.parts.size}get pendingBytes(){let e=0;for(let t of this.parts.values())e+=t.bytes;return e}clear(){this.parts.clear()}};function Ut(){return typeof CompressionStream<"u"&&typeof DecompressionStream<"u"}async function jt(r,e,t=1/0){let s=e.writable.getWriter();s.write(r).then(()=>s.close()).catch(()=>{});let n=[],i=e.readable.getReader(),o=0;for(;;){let{done:c,value:h}=await i.read();if(c)break;if(n.push(h),o+=h.length,o>t)throw i.cancel().catch(()=>{}),new C("too_long",`snapshot inflates to more than ${t} bytes`)}let a=new Uint8Array(o),l=0;for(let c of n)a.set(c,l),l+=c.length;return a}function qt(r){return jt(r,new CompressionStream("deflate"))}function Dt(r,e=67108864){return jt(r,new DecompressionStream("deflate"),e)}var en=3,tn=3,sn=3e3,nn=1e4,rn=256,on=1024,te=class extends E{constructor(t,s){super();this.hub=t;this.hashEveryTicks=Math.max(1,s.hashEveryTicks),this.historyMax=s.historyFrames??Math.max(60,t.tickRate*30),this.lastFrameRx=t.clock.now()}hub;snapshot=null;restore=null;hashEveryTicks;chunkSize=Qe;historyMax;queue=[];history=[];lastFrameTick=-1;consumedTick=0;cmdSeq=1;awaitingSnapshot=!1;lastFrameRx=0;lastSnapshotReq=-1/0;restoreFailures=0;hosting=!1;_started=!1;pending=[];_droppedCommands=0;ready=new Set;snapshotting=new Set;cancelTicker=null;baseTime=0;emitted=0;held=!1;ownHashes=new Map;peerHashes=new Map;desyncs=new Map;lastHashRx=new Map;staleUpTo=new Map;pendingSnapshot=null;get droppedCommands(){return this._droppedCommands}get started(){return this._started}get framesBuffered(){return this.queue.length}get catchingUp(){return this.queue.length>2}get currentTick(){return this.consumedTick}get lastTick(){return Math.max(0,this.lastFrameTick)}get hiTick(){return this.lastFrameTick}submit(t){this.hosting?this.pending.push({peerId:this.hub.localPeerId,bytes:t.slice()}):this.hub.sendToHost({type:"cmd",seq:this.cmdSeq++>>>0,bytes:t})}nextFrame(){let t=this.queue.shift()??null;return t&&(this.consumedTick=t.tick),t}reportHash(t,s){if(!(this.awaitingSnapshot||t<=0||t%this.hashEveryTicks!==0))if(this.hosting){this.ownHashes.set(t,s),this.prune(this.ownHashes);let n=this.peerHashes.get(t);if(n){this.peerHashes.delete(t);for(let[i,o]of[...n].sort((a,l)=>a[0]-l[0]))this.compare(i,t,s,o)}}else this.hub.sendToHost({type:"hash",tick:t,hash:s})}hostIdle(){this.hosting=!0,this.lastFrameTick<0&&(this.lastFrameTick=0)}hostStart(){this.hostIdle(),this._started=!0,this.emitted=0,this.baseTime=this.hub.clock.now(),this.cancelTicker?.();let t=1e3/this.hub.tickRate;this.cancelTicker=this.hub.clock.setInterval(()=>this.pump(t),Math.max(1,Math.floor(t/2)))}becomeHost(){this.pending=[],this.ready.clear(),this.snapshotting.clear(),this.ownHashes.clear(),this.peerHashes.clear(),this.desyncs.clear(),this.awaitingSnapshot=!1,this.hostIdle(),this._started&&this.hostStart()}becomeJoiner(){this.hosting=!1,this.cancelTicker?.(),this.cancelTicker=null,this.pending=[],this.ready.clear(),this.lastFrameRx=this.hub.clock.now()}markStarted(){this._started||(this._started=!0,this.emit("started"))}get isHeld(){return this.held}hold(t){if(this.held===t||(this.held=t,t))return;let s=this.hub.clock.now();this.baseTime=s-this.emitted*(1e3/this.hub.tickRate),this.lastFrameRx=s;for(let n of this.ready)this.lastHashRx.set(n,s)}stop(){this.cancelTicker?.(),this.cancelTicker=null}onPeerLeft(t){this.ready.delete(t),this.snapshotting.delete(t),this.desyncs.delete(t),this.lastHashRx.delete(t),this.staleUpTo.delete(t);for(let s of this.peerHashes.values())s.delete(t);this.pending=this.pending.filter(s=>s.peerId!==t)}pump(t){if(!this.hosting||this.held)return;let s=this.hub.clock.now(),n=Math.floor((s-this.baseTime+1)/t)-this.emitted;n>10&&(this.baseTime=s-this.emitted*t,n=1);for(let i=0;i<n;i++)this.emitFrame()}emitFrame(){let t=this.pending.splice(0).sort((i,o)=>i.peerId-o.peerId),s={tick:this.lastFrameTick+1,commands:t};this.emitted++,this.lastFrameTick=s.tick,this.record(s),this.queue.push(s);let n={type:"frame",tick:s.tick,commands:t};for(let i of this.ready)this.hub.sendTo(i,n,0);this.checkPeerSilence()}record(t){this.history.push(t),this.history.length>this.historyMax&&this.history.splice(0,this.history.length-this.historyMax)}checkPeerSilence(){if(this.ownHashes.size===0)return;let t=this.hub.clock.now(),s=Math.max(nn,this.hashEveryTicks/this.hub.tickRate*1e3*3);for(let n of[...this.ready]){let i=this.lastHashRx.get(n)??t;t-i>s&&this.hub.dropPeer(n,"timeout")}}handle(t,s){switch(s.type){case"cmd":if(this.hosting){let n=0;for(let i of this.pending)i.peerId===t&&n++;n>=rn?this._droppedCommands++:this.pending.push({peerId:t,bytes:s.bytes})}break;case"frame":this.hosting||this.onFrame(s.tick,s.commands);break;case"hash":this.hosting&&this.onPeerHash(t,s.tick,s.hash);break;case"desync":this.hosting||(this.awaitingSnapshot=!0,this.queue=[],this.emit("desync",{tick:s.tick,hostHash:s.hostHash,yourHash:s.yourHash}));break;case"snapshot_req":this.hosting&&this.sendSnapshot(t);break;case"snapshot":this.hosting||(this.markStarted(),this.pendingSnapshot={tick:s.tick,total:s.total,flags:s.flags});break}}get snapshotPending(){return this.pendingSnapshot!==null}onSnapshotPayload(t){let s=this.pendingSnapshot;if(this.pendingSnapshot=null,!!s){if(t.length!==s.total){this.awaitingSnapshot=!1,this.requestSnapshot();return}this.applySnapshot(s,t)}}onFrame(t,s){if(this.lastFrameRx=this.hub.clock.now(),this.markStarted(),this.awaitingSnapshot)return;let n=Math.max(0,this.lastFrameTick)+1;if(t<n)return;if(t>n){this.requestSnapshot();return}let i={tick:t,commands:s};this.lastFrameTick=t,this.record(i),this.queue.push(i),this.queue.length>this.hub.tickRate*3&&this.requestSnapshot()}requestSnapshot(){let t=this.hub.clock.now();this.awaitingSnapshot&&t-this.lastSnapshotReq<2e3||(this.lastSnapshotReq=t,this.awaitingSnapshot=!0,this.queue=[],this.hub.sendToHost({type:"snapshot_req",sinceTick:this.lastFrameTick}))}pokeHost(){this.hub.sendToHost({type:"snapshot_req",sinceTick:this.lastFrameTick})}async applySnapshot(t,s){try{let n=s;if(t.flags&1&&(n=await Dt(s)),!this.restore)throw new Error("lockstep.restore is not set; cannot apply a snapshot");this.restore(t.tick,n),this.queue=[],this.history=[],this.lastFrameTick=t.tick,this.consumedTick=t.tick,this.awaitingSnapshot=!1,this.restoreFailures=0,this.markStarted(),this.lastFrameRx=this.hub.clock.now(),this.emit("restored",t.tick)}catch(n){this.hub.reportError(n instanceof Error?n:new Error(String(n))),++this.restoreFailures<=tn?(this.awaitingSnapshot=!1,this.lastSnapshotReq=-1/0,this.requestSnapshot()):this.hub.reportError(new Error(`snapshot restore failed ${this.restoreFailures} times; giving up`))}}onPeerHi(t,s){this.lastHashRx.set(t,this.hub.clock.now());let n=this.lastFrameTick,i=this.history.length?this.history[0].tick:n+1;if(s<0){this._started&&n>0?this.sendSnapshot(t):this.ready.add(t);return}if(s<=n&&s+1>=i){for(let o of this.history)o.tick>s&&this.hub.sendTo(t,{type:"frame",tick:o.tick,commands:o.commands},0);this.ready.add(t)}else this.sendSnapshot(t)}onPeerHash(t,s,n){if(this.lastHashRx.set(t,this.hub.clock.now()),this.isStale(t,s))return;let i=this.ownHashes.get(s);if(i===void 0){let o=this.peerHashes.get(s);o||this.peerHashes.set(s,o=new Map),o.set(t,n),this.prune(this.peerHashes);return}this.compare(t,s,i,n)}compare(t,s,n,i){if(n===i||this.isStale(t,s)||!this.ready.has(t)||this.snapshotting.has(t))return;let o=(this.desyncs.get(t)??0)+1;if(this.desyncs.set(t,o),o>=en){this.hub.dropPeer(t,"desync");return}this.hub.sendTo(t,{type:"desync",tick:s,hostHash:n,yourHash:i},0),this.sendSnapshot(t,s)}isStale(t,s){let n=this.staleUpTo.get(t);return!!n&&s>n.from&&s<=n.to}async sendSnapshot(t,s=1/0){if(!this.snapshotting.has(t)){if(!this.snapshot){this.hub.reportError(new Error("lockstep.snapshot is not set; cannot serve a snapshot"));return}this.snapshotting.add(t),this.ready.delete(t);try{let n=this.consumedTick,i=this.snapshot(),o=i,a=0;if(i.length>=on&&Ut()&&(o=await qt(i),a=1),!this.snapshotting.has(t))return;this.hub.sendTo(t,{type:"snapshot",tick:n,total:o.length,flags:a},0);for(let l of fe(this.hub.nextChunkId(),o,this.chunkSize))this.hub.sendTo(t,l,0);for(let l of this.history)l.tick>n&&this.hub.sendTo(t,{type:"frame",tick:l.tick,commands:l.commands},0);s!==1/0?this.staleUpTo.set(t,{from:s,to:this.lastFrameTick}):this.staleUpTo.delete(t);for(let l of this.peerHashes.values())l.delete(t);this.ready.add(t),this.lastHashRx.set(t,this.hub.clock.now())}catch(n){this.hub.reportError(n instanceof Error?n:new Error(String(n)))}finally{this.snapshotting.delete(t)}}}prune(t){if(t.size<=64)return;let s=[...t.keys()].sort((n,i)=>n-i);for(let n of s.slice(0,s.length-64))t.delete(n)}get msSinceFrame(){return this.hub.clock.now()-this.lastFrameRx}hostSilent(){return!this.hosting&&!this.held&&this._started&&this.msSinceFrame>sn}touch(){this.lastFrameRx=this.hub.clock.now()}hiMessage(t){return{type:"hi",proto:1,peerId:t,lastTick:this.lastFrameTick}}get resyncCount(){return this.desyncs}};function Bt(r,e){let t=null;for(let s of r)s!==e&&(t===null||s<t)&&(t=s);return t}function an(r,e){let t=r-e&65535;return t!==0&&t<32768}function Jt(r,e){return r.epoch!==e.epoch?an(r.epoch,e.epoch):r.hostId<e.hostId}var p=class extends Error{constructor(t,s,n){super(s?`${t}: ${s}`:t);this.code=t;this.data=n;this.name="OneLobbyError"}code;data},$t="lk1",ln=1e4,cn=3e4,be=class{constructor(e,t,s){this.url=e;this.factory=t;this.clock=s}url;factory;clock;socket=null;waiters=[];lastRx=0;cancelPing=null;pingN=0;closed=!1;handler=null;backlog=[];onClose=null;get onMessage(){return this.handler}set onMessage(e){for(this.handler=e;e&&this.handler===e&&this.backlog.length;)e(this.backlog.shift())}connect(){return new Promise((e,t)=>{let s;try{s=this.factory(this.url)}catch(n){t(new p("connect_failed",String(n)));return}this.socket=s,s.onopen=()=>{this.lastRx=this.clock.now(),this.cancelPing=this.clock.setInterval(()=>this.tick(),ln),e()},s.onerror=()=>{this.lastRx||t(new p("connect_failed",`cannot reach ${this.url}`))},s.onmessage=n=>{this.lastRx=this.clock.now();let i;try{i=JSON.parse(String(n.data))}catch{return}if(!i||typeof i.t!="string")return;if(i.t==="ping"){this.send({t:"pong",n:i.n});return}if(i.t==="pong")return;let o=this.waiters.findIndex(a=>a.types.includes(i.t)||i.t==="error");if(o>=0){let[a]=this.waiters.splice(o,1);i.t==="error"?a.reject(hn(i)):a.resolve(i);return}this.handler?this.handler(i):this.backlog.length<256&&this.backlog.push(i)},s.onclose=()=>{this.lastRx||t(new p("connect_failed",`cannot reach ${this.url}`)),this.shutdown()}})}tick(){if(this.clock.now()-this.lastRx>cn){this.socket?.close(4e3,"silence"),this.shutdown();return}this.send({t:"ping",n:++this.pingN})}shutdown(){if(this.closed)return;this.closed=!0,this.cancelPing?.();let e=new p("closed","signaling connection closed");for(let t of this.waiters.splice(0))t.reject(e);this.onClose?.()}get isOpen(){return!this.closed&&this.socket!==null&&this.socket.readyState===1}send(e){this.isOpen&&this.socket.send(JSON.stringify(e))}expect(...e){return this.closed?Promise.reject(new p("closed","signaling connection closed")):new Promise((t,s)=>{this.waiters.push({types:e,resolve:t,reject:s})})}async request(e,...t){let s=this.expect(...t);return this.send(e),s}close(){this.socket?.close(1e3,"bye"),this.shutdown()}};function hn(r){return new p(String(r.code??"error"),r.msg===void 0?void 0:String(r.msg),r.data)}var dn=500,un=Object.freeze({paused:!1,byPeer:0,pauserLeft:!1});function Vt(r){return r===H.notInGame?"not_in_game":r===H.rateLimited?"rate_limited":r===H.notAllowed?"not_allowed":"unknown"}var se=class extends E{constructor(t,s){super();this.hub=t;this.deps=s}hub;deps;mode="anyone";policy=null;current=un;lastAccepted=new Map;get state(){return this.current}get canResume(){return this.current.paused&&this.defaultAllows(this.hub.localPeerId,!1)}request(t){if(this.mode==="off")throw new p("pause_disabled",'pause is off in onelobby.json (session.pause is "off"); set session.pause to "anyone" or "host" to turn it on');this.hub.isHost?this.hostHandle(this.hub.localPeerId,t):this.hub.sendToHost({type:"pause_request",paused:t?1:0})}clear(){if(!this.hub.isHost)throw new Error("only the host can clear the pause");this.current.paused&&this.set({paused:!1,byPeer:this.hub.localPeerId,pauserLeft:!1},!0)}handle(t,s){switch(s.type){case"pause_request":this.hub.isHost&&this.hub.connectedPeers().includes(t)&&this.hostHandle(t,s.paused!==0);break;case"pause_state":!this.hub.isHost&&t===this.hub.hostPeerId&&this.set({paused:s.paused!==0,byPeer:s.byPeer,pauserLeft:(s.flags&Ge)!==0},!1);break;case"pause_refuse":!this.hub.isHost&&t===this.hub.hostPeerId&&this.emit("refused",Vt(s.reason));break}}sendState(t){this.current.paused&&this.hub.sendTo(t,this.stateMessage(),0)}onPeerLeft(t){this.lastAccepted.delete(t);let s=this.current;this.hub.isHost&&s.paused&&!s.pauserLeft&&s.byPeer===t&&this.set({paused:!0,byPeer:this.hub.localPeerId,pauserLeft:!0},!0)}onBecameHost(){this.deps.hold(this.current.paused);let t=this.current;t.paused&&!t.pauserLeft&&!this.deps.hasPeer(t.byPeer)&&this.set({paused:!0,byPeer:this.hub.localPeerId,pauserLeft:!0},!0)}onLobbyPhase(){this.current.paused&&this.set({paused:!1,byPeer:this.hub.hostPeerId,pauserLeft:!1},this.hub.isHost)}defaultAllows(t,s){return this.mode==="host"?t===this.hub.hostPeerId:s||t===this.current.byPeer||t===this.hub.hostPeerId}hostHandle(t,s){if(this.mode==="off")return this.refuse(t,s,H.notAllowed);if(this.deps.phase()!=="in_game")return this.refuse(t,s,H.notInGame);if(s===this.current.paused)return;let n=this.hub.clock.now(),i=this.lastAccepted.get(t);if(i!==void 0&&n-i<dn)return this.refuse(t,s,H.rateLimited);if(!(this.policy?this.policy(t,s):this.defaultAllows(t,s)))return this.refuse(t,s,H.notAllowed);this.lastAccepted.set(t,n),this.set({paused:s,byPeer:t,pauserLeft:!1},!0)}refuse(t,s,n){t===this.hub.localPeerId?this.emit("refused",Vt(n)):this.hub.sendTo(t,{type:"pause_refuse",paused:s?1:0,reason:n},0)}stateMessage(){let t=this.current;return{type:"pause_state",paused:t.paused?1:0,byPeer:t.byPeer,flags:t.pauserLeft?Ge:0}}set(t,s){let n=this.current;if(this.current=Object.freeze(t),this.deps.hold(t.paused),s){let i=this.stateMessage();for(let o of this.hub.connectedPeers())this.hub.sendTo(o,i,0)}(n.paused!==t.paused||n.byPeer!==t.byPeer||n.pauserLeft!==t.pauserLeft)&&this.emit("changed",t)}};var ye=class{constructor(e=2){this.framesPerPacket=e}framesPerPacket;seq=0;buf=[];push(e){return this.buf.push(e),this.buf.length<this.framesPerPacket?null:this.take(0)}packet(e,t){let s={seq:this.seq,flags:t,frames:e};return this.seq=this.seq+1&65535,s}endBurst(){return this.take(1)}get buffered(){return this.buf.length}take(e){let t={seq:this.seq,flags:e,frames:this.buf};return this.seq=this.seq+1&65535,this.buf=[],t}};function Kt(r){if(r.length===0)return 0;let e=0;for(let t=0;t<r.length;t++)e+=r[t]*r[t];return Math.sqrt(e/r.length)}var ge=class{mode="pushToTalk";ptt=!1;vadThreshold=.02;lastLoud=-1/0;open=!1;update(e,t){let s;this.mode==="pushToTalk"?s=this.ptt:(e>=this.vadThreshold&&(this.lastLoud=t),s=t-this.lastLoud<=300);let n=s===this.open?null:s?"open":"close";return this.open=s,{send:s,edge:n}}get isOpen(){return this.open}};function et(r,e){let t=r-e&65535;return t>=32768?t-65536:t}var ve=class{constructor(e=60,t=2){this.framesPerPacket=t;this.prefillFrames=Math.max(1,Math.round(e/20))}framesPerPacket;packets=new Map;expected=null;ready=[];playing=!1;endSeq=null;waitTicks=0;prefillFrames;push(e){if(this.expected===null)this.expected=e.seq;else if(et(e.seq,this.expected)<0)return;this.packets.set(e.seq,e),e.flags&1?this.endSeq=e.seq:this.endSeq!==null&&et(e.seq,this.endSeq)>0&&(this.endSeq=null)}get ended(){return this.endSeq!==null}bufferedFrames(){let e=this.ready.length;for(let t of this.packets.values())e+=t.frames.length;return e}newerThanExpected(){for(let e of this.packets.keys())if(this.expected!==null&&et(e,this.expected)>0)return!0;return!1}pull(){if(this.expected===null)return{kind:"idle"};if(!this.playing)if(this.bufferedFrames()>=this.prefillFrames||this.ended&&this.packets.size>0)this.playing=!0;else return{kind:"wait"};this.ready.length===0&&this.refill();let e=this.ready.shift();return e?(this.waitTicks=0,e.length===0?{kind:"plc"}:{kind:"frame",opus:e}):this.ended&&this.packets.size===0?(this.reset(),{kind:"idle"}):++this.waitTicks>12?(this.reset(),{kind:"idle"}):{kind:"wait"}}refill(){if(this.expected===null)return;let e=this.packets.get(this.expected);if(e){this.packets.delete(this.expected),this.expected=this.expected+1&65535;for(let t of e.frames)this.ready.push(t);return}if(this.newerThanExpected()){this.expected=this.expected+1&65535;for(let t=0;t<this.framesPerPacket;t++)this.ready.push(new Uint8Array(0))}}reset(){this.packets.clear(),this.ready=[],this.expected=null,this.playing=!1,this.endSeq=null,this.waitTicks=0}get active(){return this.expected!==null}},ke=class{speakers=new Map;windows=new Map;burstTimeoutMs=500;accept(e,t,s,n){if(t>1024)return!1;let i=this.windows.get(e);for(i||this.windows.set(e,i=[]);i.length&&n-i[0]>=1e3;)i.shift();if(i.length>=30)return!1;for(let[o,a]of this.speakers)n-a>this.burstTimeoutMs&&this.speakers.delete(o);return!this.speakers.has(e)&&this.speakers.size>=8?!1:(i.push(n),s&1?this.speakers.delete(e):this.speakers.set(e,n),!0)}get activeSpeakers(){return this.speakers.size}forget(e){this.speakers.delete(e),this.windows.delete(e)}};var pn=300,ne=class extends E{constructor(t,s){super();this.hub=t;this.deps=s}hub;deps;mode="pushToTalk";muted=!1;inputLevel=0;bitrate=24e3;_enabled=!1;gate=new ge;packetizer=new ye;relay=new ke;speakers=new Map;peerMuted=new Set;gains=new Map;encoder=null;source=null;sink=null;cancelPlayout=null;endTimer=null;sending=!1;get enabled(){return this._enabled}set enabled(t){t!==this._enabled&&(this._enabled=t,t?this.start():this.stop())}pushToTalk(t){this.gate.ptt=t}isMutedPeer(t){return this.peerMuted.has(t)}setMuted(t,s){s?this.peerMuted.add(t):this.peerMuted.delete(t)}setVolume(t,s){this.gains.set(t,Math.min(2,Math.max(0,s)))}volumeOf(t){return this.gains.get(t)??1}async start(){try{if(!this.deps.codec)throw new Error("no Opus codec available: supply one through the voice.codec option (WASM fallback)");if(!this.deps.createSink)throw new Error("no audio output available");this.sink=this.deps.createSink(),this.cancelPlayout=this.hub.clock.setInterval(()=>this.playoutTick(),20),this.deps.createSource&&(this.encoder=this.deps.codec.createEncoder({bitrate:this.bitrate},t=>this.onEncoded(t)),this.source=this.deps.createSource(),await this.source.start(t=>this.onCapturedFrame(t,this.hub.clock.now())))}catch(t){this._enabled=!1,this.stop(),this.emit("error",{message:t instanceof Error?t.message:String(t),cause:t})}}stop(){this.sending&&this.closeBurst(),this.source?.stop(),this.source=null,this.encoder?.close(),this.encoder=null,this.cancelPlayout?.(),this.cancelPlayout=null,this.sink?.close(),this.sink=null;for(let[t,s]of this.speakers)s.decoder?.close(),s.talking&&this.emit("speaking",t,!1);this.speakers.clear()}onCapturedFrame(t,s){let n=Kt(t);if(this.inputLevel=n,!this._enabled||this.muted){this.sending&&this.closeBurst();return}let i=this.gate.update(n,s);i.send?(this.sending=!0,this.endTimer?.(),this.endTimer=null,this.encoder?.encode(t)):i.edge==="close"&&this.sending&&this.closeBurst()}closeBurst(){this.sending=!1,this.endTimer?.(),this.endTimer=this.hub.clock.setTimeout(()=>{this.endTimer=null,this.sendPacket(this.packetizer.endBurst())},40)}sendEncoded(t,s=!1){this.sendPacket(this.packetizer.packet(t,s?1:0))}onEncoded(t){let s=this.packetizer.push(t);s&&this.sendPacket(s)}sendPacket(t){let s={type:"voice",seq:t.seq,flags:t.flags,frames:t.frames};if(this.hub.isHost)for(let n of this.hub.connectedPeers())this.hub.sendTo(n,s,this.hub.localPeerId,"voice");else this.hub.sendToHost(s,0,"voice")}handle(t,s,n){if(n.type!=="voice")return;let i=n.frames.reduce((l,c)=>l+c.length+2,0)+7,o=this.hub.clock.now(),a=s;if(this.hub.isHost){if(a=t,!this.relay.accept(t,i,n.flags,o))return;for(let l of this.hub.connectedPeers())l!==t&&this.hub.sendTo(l,n,t,"voice")}this.emit("packet",a,n.seq,n.flags,n.frames),!(!this._enabled||a===this.hub.localPeerId||this.peerMuted.has(a))&&this.receivePacket(a,{seq:n.seq,flags:n.flags,frames:n.frames},o)}receivePacket(t,s,n){let i=this.speakers.get(t);i||(i={jitter:new ve,decoder:null,talking:!1,lastHeard:n},this.speakers.set(t,i)),i.lastHeard=n,i.jitter.push(s),!i.talking&&s.frames.length>0&&(i.talking=!0,this.emit("speaking",t,!0))}playoutTick(){let t=this.hub.clock.now();for(let[s,n]of this.speakers){let i=n.jitter.pull();i.kind==="frame"||i.kind==="plc"?(!n.decoder&&this.deps.codec&&(n.decoder=this.deps.codec.createDecoder(o=>this.sink?.play(s,o,this.volumeOf(s)))),n.decoder?.decode(i.kind==="frame"?i.opus:null)):i.kind==="idle"&&n.talking?(n.talking=!1,this.emit("speaking",s,!1)):n.talking&&t-n.lastHeard>pn+100&&!n.jitter.active&&(n.talking=!1,this.emit("speaking",s,!1))}}onPeerLeft(t){this.relay.forget(t);let s=this.speakers.get(t);s&&(s.decoder?.close(),s.talking&&this.emit("speaking",t,!1),this.speakers.delete(t))}close(){this._enabled=!1,this.stop(),this.endTimer?.()}static END=1};function we(r){return r==="left"||r==="disconnected"?1e3:r==="host_left"||r==="closed"?1001:r==="kicked"?4e3:1006}var zt=globalThis.setImmediate??(r=>{setTimeout(r,0)}),mn=new TextEncoder,fn=new TextDecoder,Se=class{constructor(e,t,s,n){this.peerId=e;this.out=t;this.binary=s;this.onLocalClose=n}peerId;out;binary;onLocalClose;readyState=0;bufferedAmount=0;openHandler=null;openPending=!1;onmessage=null;onclose=null;onerror=null;detach=()=>{};send(e){this.readyState===1&&this.out(typeof e=="string"?mn.encode(e):e)}close(e=1e3,t="left"){this.readyState!==3&&(this.closed(e,t),this.onLocalClose())}get onopen(){return this.openHandler}set onopen(e){this.openHandler=e,e&&this.openPending&&zt(()=>this.fireOpen())}open(e=!1){this.readyState===0&&(this.readyState=1,this.openPending=!0,e?zt(()=>this.fireOpen()):this.fireOpen())}fireOpen(){this.readyState!==1||!this.openPending||!this.openHandler||(this.openPending=!1,this.openHandler({type:"open"}))}deliver(e){this.readyState===1&&this.onmessage?.({type:"message",data:this.binary?e:fn.decode(e)})}closed(e,t){this.readyState!==3&&(this.readyState=3,this.detach(),this.onclose?.({type:"close",code:e,reason:t,wasClean:e===1e3||e===1001}))}};function Yt(r,e={}){if(r.isHost)throw new Error("asWebSocket() is for joiners; the host uses lobby.onConnection()");let t=e.channel??"reliable",s=new Se(r.hostPeerId,i=>r.send(r.hostPeerId,i,{channel:t}),!!e.binary,()=>{r.leave()}),n=[r.on("message",(i,o)=>{i===r.hostPeerId&&s.deliver(o)}),r.on("sessionEnded",(i,o)=>s.closed(o?.code??we(i),o?.reason??i)),r.on("hostChanged",()=>s.closed(1001,"host_changed"))];return s.detach=()=>n.forEach(i=>i()),r.connectedPeers.includes(r.hostPeerId)?s.open(!0):r.ready.then(()=>s.open(!0),()=>{}),s}function Gt(r,e,t={}){if(!r.isHost)throw new Error("onConnection() is for the host; a joiner uses lobby.asWebSocket()");let s=t.channel??"reliable",n=new Map,i=!1,o=l=>{if(l===r.localPeerId)return;let c=n.get(l);if(c||i)return c;c=new Se(l,d=>r.send(l,d,{channel:s}),!!t.binary,()=>{});let h=c;return h.detach=()=>{n.get(l)===h&&n.delete(l)},n.set(l,h),h.open(),e(h),h},a=[r.on("peerReady",l=>{o(l.peerId)}),r.on("message",(l,c)=>o(l)?.deliver(c)),r.on("peerLeft",(l,c,h)=>n.get(l.peerId)?.closed(h?.code??(c==="left"?1001:we(c)),h?.reason??c)),r.on("sessionEnded",l=>{for(let c of[...n.values()])c.closed(l==="left"?1001:we(l),l);a.forEach(c=>c())})];for(let l of r.connectedPeers)o(l);return()=>{i=!0}}var Zt=6e3,yn=3e3,Xt=2e4;function xe(r){let e=typeof r.name=="string"?r.name.trim():"";return e&&e!=="Player"?r:{...r,name:`Player ${r.peerId}`}}function gn(r){return r.some(e=>(Array.isArray(e.urls)?e.urls:[e.urls]).some(t=>/^turns?:/i.test(String(t))))}var vn=250,kn=2e3,re=class extends E{code;mode;tickRate;lockstep;chat;pause;voice;localPeerId;hostPeerId;phase;meta;name;resumeToken;rtt=0;joinCode="";joinCodeKind="none";directCode="";directCodeKind="none";directAddresses=[];portMapping="";directHostLog="";hostHidden=!1;setHostHidden(e){this.hostHidden!==e&&(this.hostHidden=e,this.emit("hostHiddenChanged",e))}notifyJoinCodeChanged(){this.emit("joinCodeChanged",this.joinCode,this.joinCodeKind)}signaling;cfg;roster=new Map;links=new Map;earlyIce=new Map;heldOffers=new Map;connected=new Set;hiSentTo=new Set;epoch=0;ended=!1;serverUp=!0;cancelWatchdog=null;cancelPing=null;pingN=0;chunkId=1;reassemblers=new Map;hub;outbox=new Map;markReady=()=>{};failReady=()=>{};isReady=!1;cancelReadyTimer=null;relayRetry=!1;lastHeard=new Map;lingering=new Set;notReady=new Map;rate=new Map;ready=new Promise((e,t)=>{this.markReady=()=>{this.isReady||(this.isReady=!0,this.cancelReadyTimer?.(),this.cancelReadyTimer=null,e())},this.failReady=t});constructor(e){super(),this.cfg=e,this.signaling=e.signaling;let t=e.state;this.code=t.code,this.mode=t.mode,this.tickRate=t.tickRate,this.localPeerId=t.you,this.hostPeerId=t.hostId,this.phase=t.phase,this.meta={...t.meta},this.name=t.name??"",this.resumeToken=t.resumeToken;for(let n of t.peers)this.roster.set(n.peerId,xe(n));this.ready.catch(()=>{});let s=this;this.hub={get clock(){return s.cfg.clock},get localPeerId(){return s.localPeerId},get hostPeerId(){return s.hostPeerId},get isHost(){return s.isHost},get tickRate(){return s.tickRate},connectedPeers:()=>s.connectedPeerIds(),sendTo:(n,i,o,a)=>s.sendTo(n,i,o??0,a),sendToHost:(n,i,o)=>{s.isHost||s.sendTo(s.hostPeerId,n,i??0,o)},dropPeer:(n,i)=>s.dropPeer(n,i),reportError:n=>s.reportError(n),nextChunkId:()=>s.chunkId++>>>0},this.lockstep=new te(this.hub,{hashEveryTicks:e.hashEveryTicks}),this.chat=new Q(this.hub,e.wall,n=>this.voice.isMutedPeer(n)),this.voice=new ne(this.hub,e.voice),this.pause=new se(this.hub,{phase:()=>this.phase,hold:n=>this.lockstep.hold(n),hasPeer:n=>this.roster.has(n)}),this.lockstep.on("started",()=>this.setPhase("in_game")),this.lockstep.on("error",n=>this.reportError(n)),this.signaling.onMessage=n=>this.onSignaling(n),this.signaling.onClose=()=>this.onServerClosed(),this.isHost?this.markReady():this.armReadyTimer(),this.isHost&&this.mode==="lockstep"&&(this.phase==="in_game"?this.lockstep.hostStart():this.lockstep.hostIdle()),this.cancelWatchdog=e.clock.setInterval(()=>this.watchdog(),vn),this.cancelPing=e.clock.setInterval(()=>this.pingHost(),kn)}get isHost(){return this.localPeerId===this.hostPeerId}get peers(){return[...this.roster.values()].sort((e,t)=>e.peerId-t.peerId)}peer(e){return this.roster.get(e)}get connectedPeers(){return this.connectedPeerIds()}asWebSocket(e){return Yt(this,e)}onConnection(e,t){return Gt(this,e,t)}set snapshotChunkSize(e){this.lockstep.chunkSize=e}get hostEpoch(){return this.epoch}_offer(e){return this.offerTo(e),this.links.get(e)}_answer(e){return this.makeLink(e)}_begin(){}async start(){if(!this.isHost)throw new Error("only the host can start the game");this.phase!=="in_game"&&(this.signaling.send({t:"phase",phase:"in_game"}),this.setPhase("in_game"),this.mode==="lockstep"&&this.lockstep.hostStart())}async setMeta(e){if(!this.isHost)throw new Error("only the host can set metadata");this.meta={...e},this.signaling.send({t:"meta",meta:e}),this.emit("metaChanged",this.meta)}async kick(e,t={}){if(!this.isHost)throw new Error("only the host can kick");let{ban:s=!0,code:n,reason:i}=t;if(n!==void 0&&!(n===1e3||Number.isInteger(n)&&n>=3e3&&n<=4999))throw new RangeError("close code must be 1000 or 3000-4999");if(i!==void 0&&i.length>120)throw new RangeError("close reason must be at most 120 characters");let o={t:"kick",peerId:e};s||(o.ban=!1),n!==void 0&&(o.closeCode=n),i!==void 0&&(o.closeReason=i),this.signaling.send(o);let a=n!==void 0||i!==void 0?{code:n,reason:i}:void 0;this.removePeer(e,s?"kicked":"disconnected",a,!0)}disconnect(e,t,s){return this.kick(e,{ban:!1,code:t,reason:s})}async approve(e,t){if(!this.isHost)throw new Error("only the host can approve joins");this.signaling.send({t:"approve",peerId:e,ok:t})}async leave(){this.ended||(this.signaling.send({t:"leave"}),this.teardown("left"))}send(e,t,s={}){let n=s.channel??"reliable";this.isHost?this.sendTo(e,{type:"raw",bytes:t},this.localPeerId,n):this.sendTo(this.hostPeerId,{type:"raw",bytes:t},e,n)}broadcast(e,t={}){let s=t.channel??"reliable";if(this.isHost)for(let n of this.roster.keys())n!==this.localPeerId&&this.sendTo(n,{type:"raw",bytes:e},this.localPeerId,s);else this.sendTo(this.hostPeerId,{type:"raw",bytes:e},0,s)}channelFor(e){return e.type==="voice"?"voice":e.type==="ping"||e.type==="pong"?"fast":"reliable"}sendTo(e,t,s,n){let i=this.links.get(e),o=n??this.channelFor(t),a=t.type==="raw"||t.type==="chat"||t.type==="cmd";if(!(!!i?.isOpen&&(!a||!this.isHost||this.connected.has(e)))){let c=this.roster.has(e)||e===this.hostPeerId;if(a&&o==="reliable"&&c&&!this.ended){let h=this.outbox.get(e);h||this.outbox.set(e,h=[]),h.length>=1e3&&h.shift(),h.push({msg:t,route:s,channel:n})}return}if(i)try{let c=o,h;try{h=Z(t,s)}catch(d){if(!(d instanceof C)||d.code!=="too_long")throw d;h=Z(t,s,!0)}if(h.length>16384)for(let d of fe(this.chunkId++>>>0,h))i.send(c,Z(d,s));else i.send(c,h)}catch(c){this.reportError(c instanceof Error?c:new Error(String(c)))}}connectedPeerIds(){return this.isHost?[...this.connected].filter(t=>this.links.get(t)?.isOpen).sort((t,s)=>t-s):this.links.get(this.hostPeerId)?.isOpen?[this.hostPeerId]:[]}reportError(e){this.errorSink?.(e)}errorSink=null;seatSink=null;armReadyTimer(){let e=this.cfg.readyTimeoutMs??0;if(!(e>0))return;let t=!this.cfg.relayOnly&&gn(this.cfg.ice),s=t?Math.ceil(e/2):e;this.cancelReadyTimer=this.cfg.clock.setTimeout(()=>{this.isReady||this.ended||(t?this.retryRelayed(e-s):this.giveUpConnecting())},s)}retryRelayed(e){this.relayRetry=!0,this.makeLink(this.hostPeerId,!0).startOffer(),this.cancelReadyTimer=this.cfg.clock.setTimeout(()=>{!this.isReady&&!this.ended&&this.giveUpConnecting()},e)}giveUpConnecting(){let e=new p("ice_failed",`could not open a connection to the host${this.relayRetry?" (direct or relayed)":""}`);this.failReady(e),this.reportError(e),!this.ended&&(this.signaling.send({t:"leave"}),this.teardown("ice_failed"))}linkAddressSink=null;makeLink(e,t=this.cfg.relayOnly){let s=this.links.get(e);s&&(s.onClose=null,s.onMessage=null,s.close());let n=this.cfg.links.create({remoteId:e,iceServers:this.cfg.ice,relayOnly:t});return this.links.set(e,n),n.onSignal=i=>this.signaling.send({t:"signal",to:e,...i}),n.onOpen=()=>this.onLinkOpen(e,n),n.onClose=i=>this.onLinkClosed(e,n,i),n.onMessage=(i,o)=>this.onData(e,i,o),n.onPath=i=>this.signaling.send({t:"path",path:i}),n.onRemoteAddress=i=>this.linkAddressSink?.(e,i),n}offerTo(e){this.makeLink(e).startOffer()}onLinkOpen(e,t){if(this.isHost){this.epoch>0&&this.sendTo(e,{type:"host_claim",epoch:this.epoch,hostId:this.localPeerId,tick:this.lockstep.lastTick},0);return}e===this.hostPeerId&&this.sendHi(t)}sendHi(e){this.hiSentTo.has(e)||(this.hiSentTo.add(e),this.lockstep.touch(),e.send("reliable",Z(this.lockstep.hiMessage(this.localPeerId))),this.markReady(),this.emit("ready"),this.emit("peerReady",this.roster.get(e.remoteId)??{peerId:e.remoteId,name:""}),this.flush(e.remoteId))}onLinkClosed(e,t,s){if(this.links.get(e)===t&&(this.links.delete(e),this.hiSentTo.delete(t),!this.ended)){if(this.isHost)this.connected.delete(e),this.lockstep.onPeerLeft(e),this.voice.onPeerLeft(e),!this.serverUp&&this.roster.has(e)&&this.removePeer(e,"timeout");else if(e===this.hostPeerId){if(!this.isReady&&this.cancelReadyTimer)return;this.handleHostLost("link closed")}}}onData(e,t,s,n=!1){let i;try{i=Ht(s,n)}catch(l){this.reportError(l instanceof Error?l:new Error(String(l)));return}if(!i||!this.isHost&&e!==this.hostPeerId&&i.msg.type!=="host_claim")return;n||this.lastHeard.set(e,this.cfg.clock.now());let{msg:o,route:a}=i;switch(o.type){case"hi":this.isHost&&this.onHi(e,o.lastTick);break;case"welcome_dc":this.isHost||(o.hostId!==this.hostPeerId&&this.setHost(o.hostId),this.lockstep.touch());break;case"cmd":case"frame":case"hash":case"desync":case"snapshot_req":case"snapshot":(e===this.hostPeerId||this.isHost)&&this.lockstep.handle(e,o);break;case"chunk":{if(n)break;let l=this.reassemblers.get(e);l||this.reassemblers.set(e,l=new me);let c;try{c=l.push(o)}catch(h){this.reportError(h instanceof Error?h:new Error(String(h)));break}if(!c)break;this.lockstep.snapshotPending&&e===this.hostPeerId?this.lockstep.onSnapshotPayload(c):this.onData(e,t,c,!0);break}case"chat":case"chat_history":case"chat_reject":this.chat.handle(e,a,o);break;case"pause_request":case"pause_state":case"pause_refuse":this.pause.handle(e,o);break;case"voice":t==="voice"&&this.voice.handle(e,a,o);break;case"raw":this.onRaw(e,a,o.bytes,t);break;case"ping":this.sendTo(e,{type:"pong",n:o.n,t:o.t},0,"fast");break;case"pong":this.rtt=Math.max(0,(this.cfg.clock.now()>>>0)-o.t);break;case"host_claim":this.onHostClaim(e,o.epoch,o.hostId);break}}onHi(e,t){this.connected.add(e),this.notReady.get(e)?.(),this.notReady.delete(e),this.sendTo(e,{type:"welcome_dc",tick:this.mode==="lockstep"?this.lockstep.currentTick:0,tickRate:this.tickRate,mode:this.mode==="lockstep"?It:Ot,hostId:this.localPeerId},0),this.chat.sendHistory(e),this.pause.sendState(e),this.mode==="lockstep"&&this.lockstep.onPeerHi(e,t),this.emit("peerReady",this.roster.get(e)??{peerId:e,name:""}),this.flush(e)}flush(e){let t=this.outbox.get(e);if(t){this.outbox.delete(e);for(let s of t)this.sendTo(e,s.msg,s.route,s.channel)}}onRaw(e,t,s,n){if(!this.isHost){this.emit("message",t,s,n);return}if(this.allowRaw(e))if((t===0||t===this.localPeerId)&&this.emit("message",e,s,n),t===0)for(let i of this.connectedPeerIds())i!==e&&this.sendTo(i,{type:"raw",bytes:s},e,n);else t!==this.localPeerId&&this.sendTo(t,{type:"raw",bytes:s},e,n)}allowRaw(e){let t=this.cfg.rateLimit;if(!t||!(t.messages>0)||!(t.perMs>0))return!0;let s=this.cfg.clock.now(),n=this.rate.get(e);return(!n||s-n.start>=t.perMs)&&this.rate.set(e,n={start:s,n:0}),++n.n<=t.messages}setHost(e){if(e===this.hostPeerId)return;this.hostPeerId=e,this.emit("hostChanged",e);let t=this.heldOffers.get(e);t&&(this.heldOffers.delete(e),this.cfg.clock.now()-t.at<=Xt&&this.acceptOffer(e,t.payload))}handleHostLost(e){if(this.isHost||this.ended)return;if(this.cfg.direct){this.teardown("host_left");return}let t=this.hostPeerId,s=this.roster.get(t);s&&(this.roster.delete(t),this.emit("peerLeft",s,e==="link closed"?"timeout":e));let n=this.links.get(t);n&&(n.onClose=null,n.onMessage=null,n.close(),this.links.delete(t),this.hiSentTo.delete(n)),(Bt([...this.roster.keys(),this.localPeerId],t)??this.localPeerId)===this.localPeerId&&this.becomeHost(),this.replayHeldOffers()}replayHeldOffers(){let e=this.cfg.clock.now();for(let[t,s]of[...this.heldOffers])this.heldOffers.delete(t),e-s.at<=Xt&&this.roster.has(t)&&this.acceptOffer(t,s.payload)}becomeHost(){this.epoch=this.epoch+1&65535,this.hostPeerId=this.localPeerId,this.markReady(),this.connected.clear(),this.hiSentTo.clear(),this.lastHeard.clear(),this.lockstep.becomeHost(),this.pause.onBecameHost(),this.emit("hostChanged",this.localPeerId),this.serverUp&&this.signaling.send({t:"host_changed",hostId:this.localPeerId,epoch:this.epoch});for(let e of this.roster.keys())e!==this.localPeerId&&this.offerTo(e)}onHostClaim(e,t,s){if(s!==e)return;let n={epoch:this.epoch,hostId:this.hostPeerId},i={epoch:t,hostId:s};if(s===this.hostPeerId&&t===this.epoch&&!this.isHost){let l=this.links.get(e);l&&this.sendHi(l);return}if(!Jt(i,n))return;let o=this.isHost;if(this.epoch=t,o){for(let[l,c]of this.links)l!==e&&(c.onClose=null,c.onMessage=null,c.close(),this.links.delete(l));this.connected.clear(),this.lockstep.becomeJoiner()}this.setHost(s);let a=this.links.get(e);a&&e===s&&this.sendHi(a)}onSignaling(e){switch(e.t){case"peer_joined":{if(!e.peer||this.roster.has(e.peer.peerId))return;let t=xe(e.peer);this.roster.set(t.peerId,t),this.emit("peerJoined",t),this.isHost&&(this.watchReady(t.peerId),this.offerTo(t.peerId));break}case"peer_left":this.removePeer(Number(e.peerId),String(e.reason??"left"));break;case"host_changed":{if(this.cfg.direct){this.isHost||this.teardown("host_left");break}let t=Number(e.hostId);if(typeof e.epoch=="number"&&e.epoch>this.epoch&&(this.epoch=e.epoch&65535),t===this.hostPeerId)break;if(t===this.localPeerId&&!this.isHost){let s=this.hostPeerId;this.roster.delete(s),this.becomeHost()}else this.setHost(t);break}case"join_request":this.emit("joinRequested",xe(e.peer));break;case"lobby":{let t=e;this.meta={...t.meta},t.name&&(this.name=t.name),t.resumeToken&&t.resumeToken!==this.resumeToken&&(this.resumeToken=t.resumeToken,this.seatSink?.(this.resumeToken)),this.roster=new Map(t.peers.map(s=>[s.peerId,xe(s)])),t.hostId!==this.hostPeerId&&!this.isHost&&this.setHost(t.hostId),this.setPhase(t.phase);break}case"meta":this.meta={...e.meta},this.emit("metaChanged",this.meta);break;case"phase":this.setPhase(e.phase);break;case"signal":this.onSignal(Number(e.from),e);break;case"error":if(e.code==="kicked"){let t=e.data??{},s={};typeof t.closeCode=="number"&&(s.code=t.closeCode),typeof t.closeReason=="string"&&(s.reason=t.closeReason),this.teardown(t.ban===!1?"disconnected":"kicked",s.code!==void 0||s.reason!==void 0?s:void 0)}else this.reportError(new p(String(e.code),String(e.msg??""),e.data));break}}async onSignal(e,t){let s=this.links.get(e),n={};if(t.sdp&&(n.sdp=t.sdp),t.ice&&(n.ice=t.ice),t.sdp?.type==="offer"){let i=this.offerVerdict(e);i==="hold"?(this.heldOffers.size<64||this.heldOffers.has(e))&&this.heldOffers.set(e,{payload:n,at:this.cfg.clock.now()}):i==="accept"&&await this.acceptOffer(e,n);return}if(!s){if(!n.ice||this.ended||!this.roster.has(e)||this.cfg.direct&&e!==this.hostPeerId)return;let i=this.earlyIce.get(e)??[];i.length<64&&i.push(n),this.earlyIce.set(e,i);return}await s.handleSignal(n)}offerVerdict(e){return this.ended||e===this.localPeerId?"drop":!this.isHost&&e===this.hostPeerId?"accept":this.cfg.direct||!this.roster.has(e)?"drop":this.isHost?"accept":this.isReady?this.links.get(this.hostPeerId)?.isOpen?"hold":"accept":"drop"}async acceptOffer(e,t){let s=this.makeLink(e),n=this.earlyIce.get(e)??[];this.earlyIce.delete(e);let i=s.handleSignal(t);for(let o of n)s.handleSignal(o);await i}removePeer(e,t,s,n=!1){let i=this.roster.get(e);if(e===this.localPeerId){this.teardown(t==="kicked"?"kicked":t);return}i&&this.roster.delete(e),this.earlyIce.delete(e);let o=this.notReady.get(e);o&&(o(),this.notReady.delete(e),i&&t!=="kicked"&&t!=="disconnected"&&this.emit("peerFailed",i,t));let a=this.links.get(e);if(a&&e!==this.hostPeerId)if(a.onMessage=null,this.links.delete(e),this.hiSentTo.delete(a),n){this.lingering.add(a);let l=()=>{this.lingering.delete(a)&&(c(),a.onClose=null,a.close())},c=this.cfg.clock.setTimeout(l,yn);a.onClose=l}else a.onClose=null,a.close();this.connected.delete(e),this.outbox.delete(e),this.lastHeard.delete(e),this.rate.delete(e),this.lockstep.onPeerLeft(e),this.chat.onPeerLeft(e),this.voice.onPeerLeft(e),this.pause.onPeerLeft(e),i&&this.emit("peerLeft",i,t,s),e===this.hostPeerId&&!this.isHost&&this.handleHostLost(t)}dropPeer(e,t){this.signaling.send({t:"kick",peerId:e,ban:!1,closeReason:t}),this.removePeer(e,t,void 0,!0)}setPhase(e){e!=="lobby"&&e!=="in_game"||this.phase!==e&&(this.phase=e,e==="lobby"&&this.pause.onLobbyPhase(),e==="in_game"&&!this.lockstep.started&&this.lockstep.markStarted(),this.emit("phase",e))}onServerClosed(){if(!this.ended){if(this.cfg.direct&&!this.isHost){this.teardown("host_left");return}this.serverUp=!1,this.emit("serverLost"),this.links.size===0&&this.roster.size<=1&&this.teardown("server_lost")}}watchdog(){if(this.ended)return;let e=this.cfg.peerTimeoutMs??0;e>0&&this.dropSilent(Math.max(e,Zt)),!(this.ended||this.isHost)&&this.mode==="lockstep"&&this.lockstep.hostSilent()&&(this.lockstep.touch(),this.lockstep.pokeHost(),this.handleHostLost("timeout"))}dropSilent(e){let t=this.cfg.clock.now();if(this.isHost)for(let s of[...this.connected]){let n=this.lastHeard.get(s);n!==void 0&&t-n>e&&this.removePeer(s,"timeout")}else if(this.isReady){let s=this.lastHeard.get(this.hostPeerId);s!==void 0&&t-s>e&&(this.lastHeard.delete(this.hostPeerId),this.handleHostLost("timeout"))}}pingHost(){this.ended||this.isHost||this.sendTo(this.hostPeerId,{type:"ping",n:++this.pingN,t:this.cfg.clock.now()>>>0},0,"fast")}watchReady(e){let t=this.cfg.readyTimeoutMs??0,s=()=>{};t>0&&(s=this.cfg.clock.setTimeout(()=>{if(!this.notReady.delete(e))return;let n=this.roster.get(e);n&&this.emit("peerFailed",n,"timeout")},t)),this.notReady.set(e,s)}teardown(e,t){if(!this.ended){this.ended=!0,this.cancelReadyTimer?.();for(let s of this.notReady.values())s();if(this.notReady.clear(),!this.isReady){let s=e==="ice_failed"||e==="kicked"?e:"closed";this.failReady(new p(s,`the lobby ended before connecting (${e})`))}this.cancelWatchdog?.(),this.cancelPing?.(),this.lockstep.stop(),this.voice.close();for(let s of[...this.links.values(),...this.lingering])s.onClose=null,s.onMessage=null,s.close();this.links.clear(),this.earlyIce.clear(),this.heldOffers.clear(),this.lingering.clear(),this.signaling.onClose=null,this.signaling.onMessage=null,this.signaling.close(),this.emit("sessionEnded",e,t)}}get linkCount(){return this.links.size}};var _e={reliable:0,fast:1,voice:2},st=r=>new WebSocket(r);function rt(){return typeof AudioEncoder<"u"&&typeof AudioDecoder<"u"&&typeof AudioData<"u"}var Qt={createEncoder(r,e){let t=0,s=new AudioEncoder({output:n=>{let i=new Uint8Array(n.byteLength);n.copyTo(i),e(i)},error:n=>console.error("onelobby: AudioEncoder",n)});return s.configure({codec:"opus",sampleRate:48e3,numberOfChannels:1,bitrate:r.bitrate,opus:{frameDuration:2e4,application:"voip",useinbandfec:!0,usedtx:!1,packetlossperc:10}}),{encode(n){let i=new AudioData({format:"f32",sampleRate:48e3,numberOfFrames:n.length,numberOfChannels:1,timestamp:t,data:n});t+=2e4,s.encode(i),i.close()},close(){s.state!=="closed"&&s.close()}}},createDecoder(r){let e=0,t=new AudioDecoder({output:s=>{let n=new Float32Array(s.numberOfFrames);s.copyTo(n,{planeIndex:0,format:"f32-planar"}),s.close(),r(n)},error:s=>console.error("onelobby: AudioDecoder",s)});return t.configure({codec:"opus",sampleRate:48e3,numberOfChannels:1}),{decode(s){if(s===null){r(new Float32Array(960));return}t.decode(new EncodedAudioChunk({type:"key",timestamp:e,data:s})),e+=2e4},close(){t.state!=="closed"&&t.close()}}}},wn=`
|
|
3
3
|
class OneLobbyCapture extends AudioWorkletProcessor {
|
|
4
4
|
constructor() { super(); this.buf = new Float32Array(${960}); this.n = 0; }
|
|
5
5
|
process(inputs) {
|
|
@@ -17,7 +17,13 @@ class OneLobbyCapture extends AudioWorkletProcessor {
|
|
|
17
17
|
}
|
|
18
18
|
}
|
|
19
19
|
registerProcessor('onelobby-capture', OneLobbyCapture);
|
|
20
|
-
`,D=class{ctx=null;stream=null;async start(e){this.stream=await navigator.mediaDevices.getUserMedia({audio:{channelCount:1,echoCancellation:!0,noiseSuppression:!0,autoGainControl:!0}}),this.ctx=new AudioContext({sampleRate:48e3});let t=URL.createObjectURL(new Blob([Ct],{type:"text/javascript"}));try{await this.ctx.audioWorklet.addModule(t)}finally{URL.revokeObjectURL(t)}let s=this.ctx.createMediaStreamSource(this.stream),i=new AudioWorkletNode(this.ctx,"onelobby-capture",{numberOfInputs:1,numberOfOutputs:0});i.port.onmessage=n=>e(n.data),s.connect(i),this.ctx.state==="suspended"&&await this.ctx.resume()}stop(){for(let e of this.stream?.getTracks()??[])e.stop();this.ctx?.close(),this.stream=null,this.ctx=null}},q=class{ctx=new AudioContext({sampleRate:48e3});next=new Map;constructor(){this.ctx.resume()}play(e,t,s){if(t.length===0)return;let i=this.ctx.createBuffer(1,t.length,48e3);i.copyToChannel(t,0);let n=this.ctx.createBufferSource();n.buffer=i;let o=this.ctx.createGain();o.gain.value=s,n.connect(o).connect(this.ctx.destination);let a=this.ctx.currentTime,c=this.next.get(e)??0;c<a+.01&&(c=a+.02),n.start(c),this.next.set(e,c+t.length/48e3)}close(){this.ctx.close()}};var Et={reliable:{negotiated:!0,id:j.reliable,ordered:!0},fast:{negotiated:!0,id:j.fast,ordered:!1,maxRetransmits:0},voice:{negotiated:!0,id:j.voice,ordered:!1,maxRetransmits:0}},B=class{remoteId;isOpen=!1;onSignal=null;onOpen=null;onClose=null;onMessage=null;onPath=null;pc;channels={};pendingIce=[];haveRemote=!1;closed=!1;opened=0;early=[];extra;constructor(e,t=globalThis.RTCPeerConnection){this.remoteId=e.remoteId,this.extra=e.extraRemoteCandidates??[];let s={iceServers:e.iceServers,iceTransportPolicy:e.relayOnly?"relay":"all"};e.portRange&&(s.portRangeBegin=e.portRange.begin,s.portRangeEnd=e.portRange.end,e.portRange.begin===e.portRange.end&&(s.enableIceUdpMux=!0)),this.pc=new t(s),this.pc.onicecandidate=i=>{i.candidate&&this.onSignal?.({ice:i.candidate.toJSON()})},this.pc.onconnectionstatechange=()=>{let i=this.pc.connectionState;(i==="failed"||i==="closed"||i==="disconnected")&&(i==="disconnected"?setTimeout(()=>this.pc.connectionState==="disconnected"&&this.fail("disconnected"),4e3):this.fail(i))}}openChannels(){if(!this.channels.reliable)for(let e of["reliable","fast","voice"]){let t=this.pc.createDataChannel(e,Et[e]);t.binaryType="arraybuffer",t.onopen=()=>{if(++this.opened===3&&!this.closed){this.isOpen=!0,this.onOpen?.();for(let[s,i]of this.early.splice(0))this.closed||this.onMessage?.(s,i);this.reportPath()}},t.onclose=()=>this.fail(`channel ${e} closed`),t.onmessage=s=>{let i=s.data,n=i instanceof ArrayBuffer?new Uint8Array(i):ArrayBuffer.isView(i)?new Uint8Array(i.buffer,i.byteOffset,i.byteLength):new Uint8Array(i);if(!this.isOpen){this.early.length<1e3&&this.early.push([e,n]);return}this.onMessage?.(e,n)},this.channels[e]=t}}startOffer(){(async()=>{try{this.openChannels();let e=await this.pc.createOffer();await this.pc.setLocalDescription(e),this.onSignal?.({sdp:{type:"offer",sdp:e.sdp??""}})}catch(e){this.fail(`offer failed: ${String(e)}`)}})()}async handleSignal(e){try{if(e.sdp){await this.pc.setRemoteDescription({type:e.sdp.type,sdp:e.sdp.sdp}),this.haveRemote=!0;for(let t of this.pendingIce.splice(0))await this.pc.addIceCandidate(t);if(this.extra.length){let t=/^a=mid:(\S+)/m.exec(e.sdp.sdp)?.[1]??"0";for(let s of this.extra.splice(0))await this.pc.addIceCandidate({candidate:s,sdpMid:t,sdpMLineIndex:0})}if(e.sdp.type==="offer"){this.openChannels();let t=await this.pc.createAnswer();await this.pc.setLocalDescription(t),this.onSignal?.({sdp:{type:"answer",sdp:t.sdp??""}})}}e.ice&&(this.haveRemote?await this.pc.addIceCandidate(e.ice):this.pendingIce.push(e.ice))}catch(t){this.fail(`signal failed: ${String(t)}`)}}async reportPath(e=0){if(!(this.closed||e>10)){try{let t=await this.pc.getStats(),s=new Map;t.forEach(a=>s.set(a.id,a));let i;t.forEach(a=>{a.type==="transport"&&a.selectedCandidatePairId&&(i=s.get(a.selectedCandidatePairId))}),i||t.forEach(a=>{a.type==="candidate-pair"&&(a.selected||a.nominated&&a.state==="succeeded")&&(i=a)});let n=i&&s.get(i.localCandidateId),o=i&&s.get(i.remoteCandidateId);if(n&&o){this.onPath?.(n.candidateType==="relay"||o.candidateType==="relay"?"relayed":"direct");return}}catch{}setTimeout(()=>{this.reportPath(e+1)},300)}}send(e,t){let s=this.channels[e];s&&s.readyState==="open"&&s.send(t)}close(){this.fail("closed locally")}fail(e){if(this.closed)return;this.closed=!0;let t=this.isOpen;this.isOpen=!1;try{this.pc.close()}catch{}this.onClose?.(e)}},Oe={create:r=>new B(r)};function Tt(r){return{create:e=>new B(e,r)}}var He=11;function Fe(r,e){let t=0;for(let s=e-1;s>=0;s--){let i=Number(r>>BigInt(s)&1n),n=t>>4&1;t=t<<1&31,n^i&&(t^=5)}return t}function _t(r){let e=r.split(".");if(e.length!==4)return null;let t=0;for(let s of e){if(!/^\d{1,3}$/.test(s)||Number(s)>255)return null;t=t*256+Number(s)}return t}function Ue(r,e){let t=_t(r);if(t===null)throw new Error(`not an IPv4 address: ${r}`);if(!Number.isInteger(e)||e<1||e>65535)throw new Error(`bad port ${e}`);let s=BigInt(t)<<16n|BigInt(e),i=s<<5n|BigInt(Fe(s,50)),n="";for(let o=0;o<He;o++)n=k[Number(i&31n)]+n,i>>=5n;return`${n.slice(0,4)}-${n.slice(4,8)}-${n.slice(8)}`}function Ne(r){let e=r.replace(/[ \-]/g,"").replace(/[a-z]/g,t=>t.toUpperCase()).replace(/[IL]/g,"1").replace(/O/g,"0");if(e.length!==He)return null;for(let t of e)if(k.indexOf(t)<0)return null;return e}function ne(r){let e=Ne(r);if(!e)return{error:"bad_code"};let t=0n;for(let o of e)t=t<<5n|BigInt(k.indexOf(o));let s=t>>5n;if(Fe(s,50)!==Number(t&31n))return{error:"bad_code"};if(s>>48n!==0n)return{error:"unknown_version"};let i=Number(s&0xffffn);if(i===0)return{error:"bad_code"};let n=Number(s>>16n&0xffffffffn);return{ip:[n>>>24,n>>>16&255,n>>>8&255,n&255].join("."),port:i}}function re(r){return!/[.:]/.test(r)&&Ne(r)!==null}var Lt=/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/,Mt=/^(?=.{1,253}$)([A-Za-z0-9]([A-Za-z0-9-]{0,61}[A-Za-z0-9])?)(\.[A-Za-z0-9]([A-Za-z0-9-]{0,61}[A-Za-z0-9])?)*$/;function je(r){let e=Lt.exec(r);return!!e&&e.slice(1).every(t=>Number(t)<=255)}function Rt(r){if(!/^[0-9A-Fa-f:.]+$/.test(r)||r.split("::").length>2||!r.includes(":"))return!1;let e=r.split(":").pop()??"",t=r.split(":").filter((s,i,n)=>s!==""||i>0&&i<n.length-1);return e.includes(".")&&!je(e)?!1:t.every(s=>s===""||s.includes(".")||/^[0-9A-Fa-f]{1,4}$/.test(s))}function De(r,e=47810){let t=r.trim();if(/^ws:\/\//i.test(t)&&(t=t.slice(5)),t=t.replace(/\/+$/,""),!t)return null;let s,i=null;if(t[0]==="["){let c=t.indexOf("]");if(c<0)return null;s=t.slice(1,c);let l=t.slice(c+1);if(l){if(l[0]!==":")return null;i=l.slice(1)}}else{let c=t.indexOf(":");c>=0&&c===t.lastIndexOf(":")?(s=t.slice(0,c),i=t.slice(c+1)):s=t}let n=e;if(i!==null&&(!/^\d{1,5}$/.test(i)||(n=Number(i),n<1||n>65535)))return null;let o=Rt(s);if(!o&&!je(s)&&!Mt.test(s))return null;let a=(o?`[${s}]`:s)+`:${n}`;return{host:s,port:n,isIPv6:o,display:a,url:`ws://${a}/`,candidate:`candidate:1 1 UDP 2122317823 ${s} ${n} typ host`}}var $e="onelobby",Ve="0.4.1",Ke=15e3;function It(){return typeof navigator<"u"&&!!navigator.mediaDevices?.getUserMedia&&typeof AudioWorkletNode<"u"}function Ot(){try{return globalThis.sessionStorage??null}catch{return null}}var oe=new Set;function qe(r){for(let e of oe)if(e.resumeToken===r)return!0;return!1}var b=class extends f{options;socketFactory;linkFactory;clock;wall;voiceCodec;createMic;createSink;storage;constructor(e){super();let t=e.tickRate??20;this.options={gameId:e.gameId,netVersion:e.netVersion,lobbyServer:e.lobbyServer,mode:e.mode??"lockstep",tickRate:t,hashEveryTicks:e.hashEveryTicks??t,displayName:(e.displayName??"").trim(),relayOnly:e.relayOnly??!1,readyTimeoutMs:e.readyTimeoutMs??Ke,peerTimeoutMs:e.peerTimeoutMs??0};let s=e.advanced??{},i=c=>s[c]!==void 0?s[c]:e[c];this.socketFactory=i("socketFactory")??Ae,this.linkFactory=i("linkFactory")??Oe,this.clock=i("clock")??de,this.wall=i("wallClock")??(()=>Date.now());let n=i("voiceCodec");this.voiceCodec=n!==void 0?n:ie()?Ie:null;let o=i("createMic");this.createMic=o!==void 0?o:It()?()=>new D:null;let a=i("createSink");this.createSink=a!==void 0?a:typeof AudioContext<"u"?()=>new q:null,this.storage=e.rememberSeat===!1?null:s.storage!==void 0?s.storage:Ot()}get seatKey(){return`onelobby.seat.${this.options.gameId}`}loadSeat(){if(!this.storage)return null;try{let e=this.storage.getItem(this.seatKey);if(!e)return null;let t=JSON.parse(e);return typeof t.code!="string"||typeof t.token!="string"||t.server!==this.options.lobbyServer?null:t}catch{return null}}saveSeat(e,t){if(!(!this.storage||!t))try{this.storage.setItem(this.seatKey,JSON.stringify({code:e,token:t,server:this.options.lobbyServer}))}catch{}}clearSeat(e){if(this.storage)try{if(e!==void 0){let t=this.loadSeat();if(t&&t.code!==e)return}this.storage.removeItem(this.seatKey)}catch{}}get rememberedCode(){return this.loadSeat()?.code??null}async resume(){let e=this.loadSeat();if(!e||qe(e.token))return null;try{return await this.rejoin(e.token)}catch(t){if(t instanceof d&&t.code==="invalid_token")return this.clearSeat(),null;throw t}}helloMsg(e){return{t:"hello",proto:Ce,gameId:this.options.gameId,netVersion:this.options.netVersion,client:{lib:$e,ver:Ve,platform:typeof navigator<"u"?"browser":"node"},name:this.options.displayName.slice(0,24),...e?{resumeToken:e}:{}}}async connect(e=this.options.lobbyServer,t=this.socketFactory,s=0){let i=await this.open(e,t,s);try{let n=await i.request(this.helloMsg(),"welcome");return{sig:i,welcome:n}}catch(n){throw i.close(),n}}async open(e,t,s){let i=new R(e,t,this.clock);if(s>0){let n=()=>{},o=new Promise((a,c)=>{n=this.clock.setTimeout(()=>c(new d("direct_unreachable",`no answer from ${e} within ${s} ms`)),s)});try{await Promise.race([i.connect(),o])}catch(a){throw i.close(),a instanceof d&&a.code==="connect_failed"?new d("direct_unreachable",`no answer from ${e}`):a}finally{n()}}else await i.connect();return i}makeLobby(e,t,s,i,n){let o=new C({signaling:e,state:s,ice:t.ice??[],links:i?.links??this.linkFactory,direct:!!i,clock:this.clock,wall:this.wall,hashEveryTicks:this.options.hashEveryTicks,relayOnly:s.relayOnly??this.options.relayOnly,displayName:this.options.displayName,voice:{codec:this.voiceCodec,createSource:this.createMic,createSink:this.createSink},readyTimeoutMs:this.options.readyTimeoutMs,peerTimeoutMs:this.options.peerTimeoutMs,rateLimit:n});return o.errorSink=a=>this.emit("error",a),this.storage&&!i&&(oe.add(o),this.saveSeat(o.code,o.resumeToken),o.seatSink=a=>this.saveSeat(o.code,a),o.on("sessionEnded",()=>{oe.delete(o),this.clearSeat(o.code)})),o}async host(e){let{sig:t,welcome:s}=await this.connect();try{let i=await t.request({t:"create",name:e.name,maxPlayers:e.maxPlayers,visibility:e.visibility??"unlisted",mode:this.options.mode,tickRate:this.options.tickRate,lateJoin:e.lateJoin??!0,meta:e.meta??{},...this.options.relayOnly?{relayOnly:!0}:{}},"lobby");return this.makeLobby(t,s,i,void 0,e.rateLimit)}catch(i){throw t.close(),i}}async join(e){let t=m(e);if(!t)throw new d("bad_code",`"${e}" is not a valid join code`);let s=this.loadSeat();if(s&&s.code===t&&!qe(s.token))try{return await this.rejoin(s.token)}catch(o){if(this.clearSeat(),!(o instanceof d&&o.code==="invalid_token"))throw o}let{sig:i,welcome:n}=await this.connect();try{let o=await i.request({t:"join",code:t},"lobby");return this.makeLobby(i,n,o)}catch(o){throw i.close(),o}}async joinDirect(e,t={}){let{addr:s,links:i}=this.directTarget(e,t),{sig:n,welcome:o}=await this.connect(s.url,this.socketFactory,t.connectTimeoutMs??5e3);try{let a=await n.request({t:"join",...t.password?{password:t.password}:{}},"lobby"),c=this.makeLobby(n,o,a,{links:i});return c.joinCode=Be(s),c}catch(a){throw n.close(),a}}async rejoinDirect(e,t,s={}){let{addr:i,links:n}=this.directTarget(e,s),o=await this.resumeAt(t,i.url,s.connectTimeoutMs??5e3,{links:n});return o.joinCode=Be(i),o}directTarget(e,t){let s=e.trim();if(re(s)){let a=ne(s);if("error"in a)throw new d(a.error,a.error==="unknown_version"?`"${s}" is a join code from a newer version of the game`:`"${s}" is not a valid join code: check for a typo`);s=`${a.ip}:${a.port}`}let i=De(s,t.defaultPort??47810);if(!i)throw new d("bad_address",`"${e}" is not a join code or an address (try 6B01-RGFE-P2B or 192.168.1.20:47810)`);let n=this.linkFactory;return{addr:i,links:{create:a=>n.create({...a,iceServers:[],extraRemoteCandidates:[i.candidate]})}}}async hostDirectWith(e,t){let{sig:s,welcome:i}=await this.connect("onelobby-direct://host/",t.socketFactory);try{let n=await s.request({t:"create",name:e.name,maxPlayers:e.maxPlayers,visibility:"unlisted",mode:this.options.mode,tickRate:this.options.tickRate,lateJoin:e.lateJoin??!0,meta:e.meta??{}},"lobby");return this.makeLobby(s,i,n,{links:t.wrapLinks(this.linkFactory)},e.rateLimit)}catch(n){throw s.close(),n}}async rejoin(e){return this.resumeAt(e,this.options.lobbyServer,0)}async resumeAt(e,t,s,i){let n=await this.open(t,this.socketFactory,s);try{let o=n.expect("lobby"),a=n.request(this.helloMsg(e),"welcome");o.catch(()=>{});let c=await a;if(c.peerId===void 0)throw new d("invalid_token","resume token unknown or expired");let l=await o;return this.makeLobby(n,c,l,i)}catch(o){throw n.close(),o}}async list(e={}){let{sig:t}=await this.connect();try{return(await t.request({t:"list",visibility:"public",meta:e.meta??{},limit:Math.min(100,e.limit??50),...e.cursor?{cursor:e.cursor}:{}},"lobbies")).items??[]}finally{t.close()}}};function Be(r){if(r.isIPv6)return r.display;try{return Ue(r.host,r.port)}catch{return r.display}}var Ht={UnknownCode:"unknown_code",BadCode:"bad_code",LobbyFull:"lobby_full",VersionMismatch:"version_mismatch",InGame:"in_game",Kicked:"kicked",BadPassword:"bad_password",NotHost:"not_host",RateLimited:"rate_limited",ServerFull:"server_full",GameNotAllowed:"game_not_allowed",BadRequest:"bad_request",ServerClosed:"server_closed",IceFailed:"ice_failed",Timeout:"timeout",DirectUnreachable:"direct_unreachable",BadAddress:"bad_address",PortInUse:"port_in_use",ConnectFailed:"connect_failed",Closed:"closed",ServerLost:"server_lost",InvalidToken:"invalid_token",NoServer:"no_server"},$={unknown_code:{message:"We couldn't find a lobby with that code.",hint:"Check the code with the host. Codes stop working once everyone leaves."},bad_code:{message:"That doesn't look like a lobby code.",hint:"Codes are 6 letters and numbers, like 7HQ 4K2."},lobby_full:{message:"That lobby is full.",hint:"Ask the host to make room, or start your own lobby."},version_mismatch:{message:"You and the host are running different versions of the game.",hint:"Reload the page (or update the game) on both sides, then try again."},in_game:{message:"That game has already started.",hint:"Ask the host to let people join mid-game, or wait for the next round."},kicked:{message:"The host removed you from the lobby."},denied:{message:"The host didn't let you in this time.",hint:"Ask the host to approve you, then try again."},not_host:{message:"Only the host can do that."},rate_limited:{message:"Slow down a little: too many tries in a short time.",hint:"Wait a moment, then try again."},max_lobbies:{message:"You already have the most lobbies open that this server allows.",hint:"Close one of your other lobbies, then try again."},server_full:{message:"The game server is full right now.",hint:"Try again in a few minutes."},game_not_allowed:{message:"This game server doesn't host this game.",hint:"The game's server setting may be wrong; let the game's owner know."},bad_request:{message:"The game sent something the server didn't understand.",hint:"Reload the page and try again. If it keeps happening, the game may need an update."},connect_failed:{message:"Couldn't reach the game server.",hint:"Check your internet connection, then try again."},closed:{message:"Lost the connection to the game server.",hint:"Check your internet connection, then try again."},server_lost:{message:"Lost the connection to the game server.",hint:"Your game may keep going; new players can't join until it's back."},invalid_token:{message:"Your old seat in that lobby is no longer available.",hint:"Join again with the lobby code."},ice_failed:{message:"Couldn't connect to the other players.",hint:"Some networks (school, work, public Wi-Fi) block game connections. Try another network or a phone hotspot."},timeout:{message:"The connection took too long.",hint:"Check your internet connection, then try again."},left:{message:"You left the lobby."},unknown:{message:"Something went wrong.",hint:"Try again. If it keeps happening, reload the page."}},Ft=/^[a-z][a-z0-9_]*$/;function Ut(r){if(typeof r=="string"){let e=/^([a-z][a-z0-9_]*)(?::|$)/.exec(r.trim());return{code:e?e[1]:null}}if(r&&typeof r=="object"){let e=r,t=e.data&&typeof e.data=="object"?e.data:void 0;if(typeof e.code=="string"&&Ft.test(e.code))return{code:e.code,data:t};if(typeof e.message=="string"){let s=/^([a-z][a-z0-9_]*):/.exec(e.message);if(s&&$[s[1]])return{code:s[1],data:t};if(e.name==="NetworkError"||/network|failed to fetch|websocket/i.test(e.message))return{code:"connect_failed"};if(e.name==="TimeoutError"||/timed? ?out/i.test(e.message))return{code:"timeout"}}}return{code:null}}function ae(r){let{code:e,data:t}=Ut(r);if(!e)return{code:"unknown",...$.unknown};let s=e;e==="kicked"&&t?.reason==="denied"&&(s="denied"),e==="rate_limited"&&t?.reason==="max_lobbies"&&(s="max_lobbies");let i=$[s];if(!i)return{code:e,...$.unknown};let n={code:e,message:i.message};if(i.hint&&(n.hint=i.hint),e==="rate_limited"&&typeof t?.retryAfterMs=="number"&&s==="rate_limited"){let o=Math.max(1,Math.ceil(t.retryAfterMs/1e3));n.hint=`Wait ${o} second${o===1?"":"s"}, then try again.`}return n}function Je(){try{return typeof location<"u"?location.href:void 0}catch{return}}function ce(r,e,t="join"){let s=m(r);if(!s)throw new Error(`"${r}" is not a valid lobby code`);let i=e??Je();if(!i)throw new Error("shareUrl needs a base URL outside the browser");let n=new URL(i);return n.searchParams.delete(t),n.searchParams.set(t,y(s).replace(" ","-")),n.toString()}function le(r,e="join"){let t=r??Je();if(!t)return null;let s;try{s=new URL(t,"http://localhost/")}catch{return null}let i=s.searchParams.get(e)??new URLSearchParams(s.hash.replace(/^#/,"")).get(e);return i?m(i):null}var Nt=b,jt=d;var Dt=new TextEncoder,qt=new TextDecoder,V=r=>Dt.encode(JSON.stringify(r)),K=class{constructor(e){this.c=e}c;values=new Map;get(e){return this.values.get(e)}set(e,t){this.c._sharedOp({k:"set",key:String(e),v:t===void 0?null:t})}add(e,t=1){this.c._sharedOp({k:"add",key:String(e),n:Number(t)||0})}all(){return Object.fromEntries(this.values)}keys(){return[...this.values.keys()]}},E=class{constructor(e){this.opts=e}opts;state="idle";lobby=null;error=null;linking=!1;name="";shared=new K(this);onChange=()=>{};onEvent=()=>{};lastAction=null;unsub=[];gen=0;seq=0;get isHost(){return!!this.lobby&&this.lobby.isHost}get code(){return this.lobby?.code??null}get players(){let e=this.lobby;if(!e)return[];let t=e.peers.map(s=>this.toPlayer(s));return t.some(s=>s.isMe)||t.push({id:e.localPeerId,name:this.name||`Player ${e.localPeerId}`,isHost:e.isHost,isMe:!0}),t.sort((s,i)=>s.id-i.id)}get statusText(){switch(this.state){case"idle":return"Host a game, or type a code to join one.";case"connecting":return this.lastAction?.kind==="join"?"Joining\u2026":this.lastAction?.kind==="resume"?"Getting your seat back\u2026":"Creating a game\u2026";case"error":return this.error?.message??"Something went wrong.";case"lobby":{if(this.linking)return`Connecting to ${this.hostName}\u2026`;if(this.isHost){let e=this.players.length;return e<=1?"Waiting for players\u2026 Share the code with your friends.":`${e} players here. Press Start when everyone's in.`}return`Connected! Waiting for ${this.hostName} to start.`}case"playing":return this.linking?`Connecting to ${this.hostName}\u2026`:"Game on!"}}get hostName(){let e=this.lobby;return e&&e.peer(e.hostPeerId)?.name||"the host"}toPlayer(e,t){let s=this.lobby,i=e?.peerId??t??0;return{id:i,name:e?.name||i===s.localPeerId&&this.name||`Player ${i}`,isHost:i===s.hostPeerId,isMe:i===s.localPeerId}}host(e){return this.lastAction={kind:"host"},this.run(e,t=>t.host({name:this.opts.lobbyName??"game",maxPlayers:typeof this.opts.maxPlayers=="function"?this.opts.maxPlayers():this.opts.maxPlayers,visibility:"unlisted",lateJoin:!0}))}join(e,t){let s=m(e);return this.lastAction={kind:"join",code:e},s?this.run(t,i=>i.join(s)):(this.fail(new d("bad_code")),Promise.resolve())}async resume(e){let t=this.opts.makeKit(e);return!t.resume||!t.rememberedCode?!1:(this.lastAction={kind:"resume"},await this.run(e,()=>t.resume(),!0),!!this.lobby)}retry(){let e=this.lastAction;return!e||e.kind==="resume"?(this.reset(),Promise.resolve()):e.kind==="host"?this.host(this.name):this.join(e.code,this.name)}async start(){!this.lobby||!this.isHost||this.state==="playing"||await this.lobby.start()}async leave(){let e=this.lobby;this.detach(),await e?.leave(),this.reset()}reset(){this.detach(),this.state="idle",this.error=null,this.onChange()}send(e){this.lobby&&this.lobby.broadcast(V({k:"m",d:e===void 0?null:e}))}async run(e,t,s=!1){this.detach();let i=++this.gen;this.name=(e||"").trim().slice(0,24),this.state="connecting",this.error=null,this.onChange();let n;try{n=await t(this.opts.makeKit(this.name))}catch(o){if(i!==this.gen)return;s?this.reset():this.fail(o);return}if(i!==this.gen){n?.leave();return}n?this.attach(n):this.reset()}attach(e){this.lobby=e,e.isHost||this.shared.values.clear(),this.seq=0,this.state=e.phase==="in_game"?"playing":"lobby",this.linking=!e.isHost;let t=this.unsub;t.push(e.on("ready",()=>this.linked())),t.push(e.on("peerJoined",s=>{this.onEvent("player-joined",{player:this.toPlayer(s)}),this.onChange()})),t.push(e.on("peerLeft",(s,i)=>{this.onEvent("player-left",{player:this.toPlayer(s),reason:i}),this.onChange()})),t.push(e.on("peerReady",s=>{e.isHost&&s.peerId!==e.localPeerId&&e.send(s.peerId,V({k:"SS",all:this.shared.all(),q:this.seq}))})),t.push(e.on("hostChanged",()=>this.onChange())),t.push(e.on("phase",s=>{s==="in_game"&&this.state!=="playing"&&(this.state="playing",this.onChange(),this.onEvent("start",{}))})),t.push(e.on("message",(s,i)=>this.onRaw(s,i))),t.push(e.on("sessionEnded",s=>{this.lobby===e&&s!=="left"&&this.fail(new d(s))})),e.ready.then(()=>{this.lobby===e&&this.linked()},()=>{}),this.onChange(),this.state==="playing"&&!this.linking&&this.onEvent("start",{})}linked(){this.linking&&(this.linking=!1,this.onChange(),this.state==="playing"&&this.onEvent("start",{}))}detach(){this.gen++;for(let e of this.unsub.splice(0))e();this.lobby=null,this.linking=!1}fail(e){let t=this.lobby;this.detach(),t&&t.leave(),this.state="error",this.error=ae(e),this.onChange(),this.onEvent("error",this.error)}playerById(e){let t=this.lobby;return!t||!e?null:this.toPlayer(t.peer(e),e)}onRaw(e,t){let s;try{s=JSON.parse(qt.decode(t))}catch{return}if(!s||typeof s!="object")return;let i=this.lobby;switch(s.k){case"m":this.onEvent("message",{from:this.playerById(e),data:s.d});break;case"set":case"add":i.isHost&&this.applyHost(s,e);break;case"S":!i.isHost&&e===i.hostPeerId&&!(s.q<=this.seq)&&(this.seq=s.q,this.applyValue(s.key,s.v,s.by));break;case"SS":if(!i.isHost&&e===i.hostPeerId&&s.all&&typeof s.all=="object"&&!(s.q<this.seq)){this.seq=s.q;for(let n of[...this.shared.values.keys()])n in s.all||this.applyValue(n,null,0);for(let[n,o]of Object.entries(s.all))this.applyValue(n,o,0)}break}}_sharedOp(e){let t=this.lobby;if(!t){let s=e.k==="set"?e.v:(Number(this.shared.values.get(e.key))||0)+e.n;this.applyValue(e.key,s,0);return}t.isHost?this.applyHost(e,t.localPeerId):t.send(t.hostPeerId,V(e))}applyHost(e,t){let s=this.lobby;if(typeof e.key!="string")return;let i=e.k==="set"?e.v:(Number(this.shared.values.get(e.key))||0)+(Number(e.n)||0);this.applyValue(e.key,i,t),s.broadcast(V({k:"S",key:e.key,v:i,by:t,q:++this.seq}))}applyValue(e,t,s){t===null?this.shared.values.delete(e):this.shared.values.set(e,t),this.onEvent("shared-change",{key:e,value:t,from:this.playerById(s)})}};var We=`
|
|
20
|
+
`,Pe=class{ctx=null;stream=null;async start(e){this.stream=await navigator.mediaDevices.getUserMedia({audio:{channelCount:1,echoCancellation:!0,noiseSuppression:!0,autoGainControl:!0}}),this.ctx=new AudioContext({sampleRate:48e3});let t=URL.createObjectURL(new Blob([wn],{type:"text/javascript"}));try{await this.ctx.audioWorklet.addModule(t)}finally{URL.revokeObjectURL(t)}let s=this.ctx.createMediaStreamSource(this.stream),n=new AudioWorkletNode(this.ctx,"onelobby-capture",{numberOfInputs:1,numberOfOutputs:0});n.port.onmessage=i=>e(i.data),s.connect(n),this.ctx.state==="suspended"&&await this.ctx.resume()}stop(){for(let e of this.stream?.getTracks()??[])e.stop();this.ctx?.close(),this.stream=null,this.ctx=null}},Ee=class{ctx=new AudioContext({sampleRate:48e3});next=new Map;constructor(){this.ctx.resume()}play(e,t,s){if(t.length===0)return;let n=this.ctx.createBuffer(1,t.length,48e3);n.copyToChannel(t,0);let i=this.ctx.createBufferSource();i.buffer=n;let o=this.ctx.createGain();o.gain.value=s,i.connect(o).connect(this.ctx.destination);let a=this.ctx.currentTime,l=this.next.get(e)??0;l<a+.01&&(l=a+.02),i.start(l),this.next.set(e,l+t.length/48e3)}close(){this.ctx.close()}};var Sn={reliable:{negotiated:!0,id:_e.reliable,ordered:!0},fast:{negotiated:!0,id:_e.fast,ordered:!1,maxRetransmits:0},voice:{negotiated:!0,id:_e.voice,ordered:!1,maxRetransmits:0}},Ce=class{remoteId;isOpen=!1;onSignal=null;onOpen=null;onClose=null;onMessage=null;onPath=null;onRemoteAddress=null;pc;channels={};pendingIce=[];haveRemote=!1;closed=!1;opened=0;early=[];extra;constructor(e,t=globalThis.RTCPeerConnection){this.remoteId=e.remoteId,this.extra=e.extraRemoteCandidates??[];let s={iceServers:e.iceServers,iceTransportPolicy:e.relayOnly?"relay":"all"};e.portRange&&(s.portRangeBegin=e.portRange.begin,s.portRangeEnd=e.portRange.end,e.portRange.begin===e.portRange.end&&(s.enableIceUdpMux=!0)),this.pc=new t(s),this.pc.onicecandidate=n=>{n.candidate&&this.onSignal?.({ice:n.candidate.toJSON()})},this.pc.onconnectionstatechange=()=>{let n=this.pc.connectionState;(n==="failed"||n==="closed"||n==="disconnected")&&(n==="disconnected"?setTimeout(()=>this.pc.connectionState==="disconnected"&&this.fail("disconnected"),4e3):this.fail(n))}}openChannels(){if(!this.channels.reliable)for(let e of["reliable","fast","voice"]){let t=this.pc.createDataChannel(e,Sn[e]);t.binaryType="arraybuffer",t.onopen=()=>{if(++this.opened===3&&!this.closed){this.isOpen=!0,this.onOpen?.();for(let[s,n]of this.early.splice(0))this.closed||this.onMessage?.(s,n);this.reportPath()}},t.onclose=()=>this.fail(`channel ${e} closed`),t.onmessage=s=>{let n=s.data,i=n instanceof ArrayBuffer?new Uint8Array(n):ArrayBuffer.isView(n)?new Uint8Array(n.buffer,n.byteOffset,n.byteLength):new Uint8Array(n);if(!this.isOpen){this.early.length<1e3&&this.early.push([e,i]);return}this.onMessage?.(e,i)},this.channels[e]=t}}startOffer(){(async()=>{try{this.openChannels();let e=await this.pc.createOffer();await this.pc.setLocalDescription(e),this.onSignal?.({sdp:{type:"offer",sdp:e.sdp??""}})}catch(e){this.fail(`offer failed: ${String(e)}`)}})()}async handleSignal(e){try{if(e.sdp){await this.pc.setRemoteDescription({type:e.sdp.type,sdp:e.sdp.sdp}),this.haveRemote=!0;for(let t of this.pendingIce.splice(0))await this.pc.addIceCandidate(t);if(this.extra.length){let t=/^a=mid:(\S+)/m.exec(e.sdp.sdp)?.[1]??"0";for(let s of this.extra.splice(0))await this.pc.addIceCandidate({candidate:s,sdpMid:t,sdpMLineIndex:0})}if(e.sdp.type==="offer"){this.openChannels();let t=await this.pc.createAnswer();await this.pc.setLocalDescription(t),this.onSignal?.({sdp:{type:"answer",sdp:t.sdp??""}})}}e.ice&&(this.haveRemote?await this.pc.addIceCandidate(e.ice):this.pendingIce.push(e.ice))}catch(t){this.fail(`signal failed: ${String(t)}`)}}async reportPath(e=0){if(!(this.closed||e>10)){try{let t=await this.pc.getStats(),s=new Map;t.forEach(a=>s.set(a.id,a));let n;t.forEach(a=>{a.type==="transport"&&a.selectedCandidatePairId&&(n=s.get(a.selectedCandidatePairId))}),n||t.forEach(a=>{a.type==="candidate-pair"&&(a.selected||a.nominated&&a.state==="succeeded")&&(n=a)});let i=n&&s.get(n.localCandidateId),o=n&&s.get(n.remoteCandidateId);if(i&&o){let a=String(o.address??o.ip??"");o.candidateType!=="relay"&&i.candidateType!=="relay"&&a&&!a.endsWith(".local")&&this.onRemoteAddress?.(a.replace(/^::ffff:/i,"")),this.onPath?.(i.candidateType==="relay"||o.candidateType==="relay"?"relayed":"direct");return}}catch{}setTimeout(()=>{this.reportPath(e+1)},300)}}send(e,t){let s=this.channels[e];s&&s.readyState==="open"&&s.send(t)}close(){this.fail("closed locally")}fail(e){if(this.closed)return;this.closed=!0;let t=this.isOpen;this.isOpen=!1;try{this.pc.close()}catch{}this.onClose?.(e)}},es={create:r=>new Ce(r)};function xn(r){return{create:e=>new Ce(e,r)}}var ts=11;function ss(r,e){let t=0;for(let s=e-1;s>=0;s--){let n=Number(r>>BigInt(s)&1n),i=t>>4&1;t=t<<1&31,i^n&&(t^=5)}return t}function _n(r){let e=r.split(".");if(e.length!==4)return null;let t=0;for(let s of e){if(!/^\d{1,3}$/.test(s)||Number(s)>255)return null;t=t*256+Number(s)}return t}function ns(r,e){let t=_n(r);if(t===null)throw new Error(`not an IPv4 address: ${r}`);if(!Number.isInteger(e)||e<1||e>65535)throw new Error(`bad port ${e}`);return rs(BigInt(t)<<16n|BigInt(e))}function rs(r){let e=r<<5n|BigInt(ss(r,50)),t="";for(let s=0;s<ts;s++)t=X[Number(e&31n)]+t,e>>=5n;return`${t.slice(0,4)}-${t.slice(4,8)}-${t.slice(8)}`}function it(r){if(!/^[0-9a-fA-F]{12}$/.test(r))throw new Error(`room must be 12 hex digits: ${r}`);return rs(1n<<48n|BigInt("0x"+r))}function ot(){let r=new Uint8Array(6);return globalThis.crypto.getRandomValues(r),it([...r].map(e=>e.toString(16).padStart(2,"0")).join(""))}function Te(r){let e=r.replace(/[ \-]/g,"").replace(/[a-z]/g,t=>t.toUpperCase()).replace(/[IL]/g,"1").replace(/O/g,"0");if(e.length!==ts)return null;for(let t of e)if(X.indexOf(t)<0)return null;return e}function at(r){let e=Te(r);if(!e)return{error:"bad_code"};let t=0n;for(let a of e)t=t<<5n|BigInt(X.indexOf(a));let s=t>>5n;if(ss(s,50)!==Number(t&31n))return{error:"bad_code"};let n=s>>48n;if(n===1n)return{room:(s&0xffffffffffffn).toString(16).padStart(12,"0")};if(n!==0n)return{error:"unknown_version"};let i=Number(s&0xffffn);if(i===0)return{error:"bad_code"};let o=Number(s>>16n&0xffffffffn);return{ip:[o>>>24,o>>>16&255,o>>>8&255,o&255].join("."),port:i}}function lt(r){return!/[.:]/.test(r)&&Te(r)!==null}var Pn=new TextEncoder,is=r=>Pn.encode(r).length,K=class{o;queue=[];queueBytes=0;unackedBytes=0;nextSeq=1;unacked=new Map;rcvNext=1;held=new Map;ackOwed=!1;lastSent;lastHeard;cancelFlush=null;cancelTick=null;done=!1;constructor(e){this.o=e,this.lastSent=this.lastHeard=e.clock.now(),this.cancelTick=e.clock.setInterval(()=>this.tick(),250)}get closed(){return this.done}send(e){if(this.done)return;let t=is(e);this.queue.length+this.unacked.size>=128||this.queueBytes+this.unackedBytes+t>262144||(this.queue.push(e),this.queueBytes+=t,this.scheduleFlush())}heard(){this.lastHeard=this.o.clock.now()}receive(e){if(this.done)return;if(this.heard(),e.type==="close"){let s=typeof e.reason=="string"?e.reason:"";this.stop(),this.o.onPeerClose(s);return}if(e.type!=="data"&&e.type!=="beat")return;if(Number.isInteger(e.ack))for(let s of[...this.unacked.keys()])s<=e.ack&&this.dropUnacked(s);if(e.type==="beat")return;let t=Array.isArray(e.msgs)?e.msgs.filter(s=>typeof s=="string"):[];if(!(!Number.isInteger(e.seq)||e.seq<1)){if(this.ackOwed=!0,e.seq===this.rcvNext){this.rcvNext++;for(let s of t)this.o.deliver(s);for(let s=this.held.get(this.rcvNext);s&&!this.done;s=this.held.get(this.rcvNext)){this.held.delete(this.rcvNext),this.rcvNext++;for(let n of s)this.o.deliver(n)}}else e.seq>this.rcvNext&&e.seq<=this.rcvNext+64&&this.held.set(e.seq,t);this.scheduleFlush()}}close(e=""){this.done||(this.flush(),this.o.send({type:"close",seq:0,ack:this.rcvNext-1,...e?{reason:e}:{}}),this.stop())}stop(){this.done=!0,this.cancelFlush?.(),this.cancelTick?.(),this.cancelFlush=this.cancelTick=null,this.unacked.clear(),this.unackedBytes=0,this.held.clear(),this.queue=[],this.queueBytes=0}scheduleFlush(){this.cancelFlush||this.done||(this.cancelFlush=this.o.clock.setTimeout(()=>{this.cancelFlush=null,this.flush()},30))}flush(){if(this.done)return;this.cancelFlush?.(),this.cancelFlush=null;let e=this.o.clock.now();if(this.queue.length===0){this.ackOwed&&this.emit({type:"beat",seq:0,ack:this.rcvNext-1},e);return}let t=[],s=0,n=()=>{if(t.length===0)return;let i={type:"data",seq:this.nextSeq++,ack:this.rcvNext-1,msgs:t};this.unacked.set(i.seq,{bytes:s,frame:i,lastSent:e,resends:0}),this.unackedBytes+=s,this.emit(i,e),t=[],s=0};this.queueBytes=0;for(let i of this.queue.splice(0)){let o=is(i);t.length>0&&s+o>3e4&&n(),t.push(i),s+=o}n()}dropUnacked(e){let t=this.unacked.get(e);t&&(this.unackedBytes-=t.bytes,this.unacked.delete(e))}emit(e,t){this.lastSent=t,this.ackOwed=!1,this.o.send(e)}tick(){if(this.done)return;let e=this.o.clock.now();if(e-this.lastHeard>=3e4)return this.die();for(let t of this.unacked.values())if(!(e-t.lastSent<3e3)){if(t.resends>=5)return this.die();t.resends++,t.lastSent=e,this.o.send({...t.frame,ack:this.rcvNext-1}),this.lastSent=e}e-this.lastSent>=1e4&&this.emit({type:"beat",seq:0,ack:this.rcvNext-1},e)}die(){this.stop(),this.o.onDead("timeout")}};var q=()=>{let r=globalThis.crypto?.subtle;if(!r)throw new Error("WebCrypto (crypto.subtle) is not available; Nostr signaling needs a secure context or Node 18+");return r},M=r=>r,Le=new TextEncoder;function Me(...r){let e=new Uint8Array(r.reduce((s,n)=>s+n.length,0)),t=0;for(let s of r)e.set(s,t),t+=s.length;return e}var N=r=>Array.from(r,e=>e.toString(16).padStart(2,"0")).join("");function as(r){if(r.length%2||/[^0-9a-fA-F]/.test(r))throw new Error("bad hex");let e=new Uint8Array(r.length/2);for(let t=0;t<e.length;t++)e[t]=parseInt(r.slice(t*2,t*2+2),16);return e}function En(r){let e="";for(let t=0;t<r.length;t+=32768)e+=String.fromCharCode(...r.subarray(t,t+32768));return btoa(e)}function Cn(r){let e=atob(r),t=new Uint8Array(e.length);for(let s=0;s<e.length;s++)t[s]=e.charCodeAt(s);return t}async function ie(r){return new Uint8Array(await q().digest("SHA-256",M(r)))}async function os(r,e,t,s=32){let n=await q().importKey("raw",M(r),"HKDF",!1,["deriveBits"]),i=await q().deriveBits({name:"HKDF",hash:"SHA-256",salt:M(Le.encode(e)),info:M(Le.encode(t))},n,s*8);return new Uint8Array(i)}async function ls(r){let e=Le.encode(r),t="onelobby-nostr-v1",[s,n]=await Promise.all([os(e,t,"key"),os(e,t,"topic")]);return{key:s,topic:N(n)}}async function cs(r,e,t){let s=await q().importKey("raw",M(r),"AES-GCM",!1,["encrypt"]),n=t??globalThis.crypto.getRandomValues(new Uint8Array(12)),i=new Uint8Array(await q().encrypt({name:"AES-GCM",iv:M(n)},s,M(Le.encode(e))));return En(Me(n,i))}async function hs(r,e){try{let t=Cn(e);if(t.length<28)return null;let s=await q().importKey("raw",M(r),"AES-GCM",!1,["decrypt"]),n=await q().decrypt({name:"AES-GCM",iv:M(t.subarray(0,12))},s,M(t.subarray(12)));return new TextDecoder().decode(n)}catch{return null}}var x=0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2fn,F=0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141n,Tn=0x79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798n,Ln=0x483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8n,pt=[0n,1n,0n],ht=[Tn,Ln,1n],R=(r,e=x)=>{let t=r%e;return t>=0n?t:t+e};function Mn(r,e,t){let s=1n;for(r=R(r,t);e>0n;)e&1n&&(s=s*r%t),r=r*r%t,e>>=1n;return s}var Rn=(r,e=x)=>Mn(r,e-2n,e);function ds(r){let[e,t,s]=r;if(s===0n||t===0n)return pt;let n=e*e%x,i=t*t%x,o=i*i%x,a=R(2n*((e+i)*(e+i)-n-o)),l=3n*n,c=l*l%x,h=R(c-2n*a);return[h,R(l*(a-h)-8n*o),R(2n*t*s)]}function An(r,e){if(r[2]===0n)return e;if(e[2]===0n)return r;let[t,s,n]=r,[i,o,a]=e,l=n*n%x,c=a*a%x,h=t*c%x,d=i*l%x,u=s*a*c%x,k=o*n*l%x;if(h===d)return u===k?ds(r):pt;let f=R(d-h),S=4n*f*f%x,y=f*S%x,g=R(2n*(k-u)),v=h*S%x,Lt=R(g*g-y-2n*v),Bs=R(g*(v-Lt)-2n*u*y),Js=R(((n+a)*(n+a)-l-c)*f);return[Lt,Bs,Js]}function dt(r,e){let t=pt,s=r;for(;e>0n;)e&1n&&(t=An(t,s)),s=ds(s),e>>=1n;return t}function ut(r){if(r[2]===0n)return null;let e=Rn(r[2]),t=e*e%x;return[r[0]*t%x,r[1]*t*e%x]}var ae=r=>{let e=0n;for(let t of r)e=e<<8n|BigInt(t);return e};function oe(r,e=32){let t=new Uint8Array(e);for(let s=e-1;s>=0;s--)t[s]=Number(r&0xffn),r>>=8n;return t}async function ct(r,...e){let t=await ie(new TextEncoder().encode(r));return ie(Me(t,t,...e))}function Re(r){let e=ae(r);if(e<1n||e>=F)throw new Error("secret key out of range");let t=ut(dt(ht,e));return oe(t[0])}function us(){for(;;){let r=new Uint8Array(32);globalThis.crypto.getRandomValues(r);let e=ae(r);if(e>=1n&&e<F)return r}}async function ps(r,e,t){let s=ae(e);if(s<1n||s>=F)throw new Error("secret key out of range");let n=ut(dt(ht,s)),i=n[1]&1n?F-s:s,o=oe(n[0]),a=t??globalThis.crypto.getRandomValues(new Uint8Array(32)),l=await ct("BIP0340/aux",a),h=oe(i).map((g,v)=>g^l[v]),d=await ct("BIP0340/nonce",h,o,r),u=ae(d)%F;if(u===0n)throw new Error("bad nonce");let k=ut(dt(ht,u)),f=k[1]&1n?F-u:u,S=oe(k[0]),y=ae(await ct("BIP0340/challenge",S,o,r))%F;return Me(S,oe((f+y*i)%F))}var le=25050;async function In(r,e,t,s,n){return N(await ie(new TextEncoder().encode(JSON.stringify([0,r,e,t,s,n]))))}async function mt(r,e,t,s){let n=N(Re(r)),i=[["t",e]],o=await In(n,s,le,i,t),a=N(await ps(as(o),r));return{id:o,pubkey:n,created_at:s,kind:le,tags:i,content:t,sig:a}}var ft=r=>{let e=globalThis.WebSocket;if(!e)throw new Error("no WebSocket in this runtime (Node 22+ has one; older Node needs advanced.nostrConnect)");return new e(r)},On=4096,Hn=30,Ae=class{constructor(e){this.o=e;this.relays=e.urls.map(t=>({url:t,ws:null,open:!1,attempted:!1,skipped:!1,backoff:1e3,cancelRetry:null}))}o;relays;seen=new Set;sub="ol"+Math.random().toString(16).slice(2,10);closed=!1;waiters=[];get total(){return this.relays.length}get connected(){return this.relays.filter(e=>e.open&&!e.skipped).length}get settled(){return this.relays.every(e=>e.attempted||e.skipped)}start(){for(let e of this.relays)this.dial(e)}whenUp(e){return this.connected>0?Promise.resolve(!0):new Promise(t=>{let s=this.o.clock.setTimeout(()=>n(!1),e),n=o=>{s(),this.waiters=this.waiters.filter(a=>a!==i),t(o)},i=()=>{this.connected>0?n(!0):this.closed&&n(!1)};this.waiters.push(i)})}publish(e){let t=JSON.stringify(["EVENT",e]);for(let s of this.relays)s.open&&!s.skipped&&this.trySend(s,t)}close(){this.closed=!0;for(let e of this.relays)e.cancelRetry?.(),e.cancelRetry=null,this.drop(e,!1);this.changed()}changed(){for(let e of[...this.waiters])e();this.o.onChange?.()}trySend(e,t){try{e.ws?.send(t)}catch{}}dial(e){if(this.closed||e.skipped)return;let t;try{t=this.o.connect(e.url)}catch{e.attempted=!0,this.retryLater(e);return}e.ws=t,t.onopen=()=>{if(e.ws!==t)return;e.open=!0,e.attempted=!0,e.backoff=1e3;let n=Math.floor(this.o.wall()/1e3)-Hn;this.trySend(e,JSON.stringify(["REQ",this.sub,{kinds:[le],"#t":[this.o.topic],since:n}])),this.changed()},t.onmessage=n=>{e.ws===t&&this.onMessage(e,String(n.data))};let s=()=>{if(e.ws!==t)return;let n=e.open;e.ws=null,e.open=!1,e.attempted=!0,n&&this.changed(),this.retryLater(e)};t.onclose=s,t.onerror=()=>{s()}}drop(e,t){let s=e.ws;e.ws=null,e.open=!1;try{s?.close()}catch{}t&&this.retryLater(e)}retryLater(e){if(this.closed||e.skipped||e.cancelRetry)return;let t=e.backoff;e.backoff=Math.min(3e4,e.backoff*2),e.cancelRetry=this.o.clock.setTimeout(()=>{e.cancelRetry=null,this.dial(e)},t)}skip(e){e.skipped||(e.skipped=!0,e.cancelRetry?.(),e.cancelRetry=null,this.drop(e,!1),this.changed())}onMessage(e,t){let s;try{s=JSON.parse(t)}catch{return}if(Array.isArray(s))switch(s[0]){case"EVENT":{let n=s[2];if(!n||typeof n.id!="string"||typeof n.pubkey!="string"||typeof n.content!="string"||typeof n.created_at!="number"||n.kind!==le||!Array.isArray(n.tags)||!n.tags.some(i=>i[0]==="t"&&i[1]===this.o.topic)||this.seen.has(n.id))return;this.seen.add(n.id),this.seen.size>On&&this.seen.delete(this.seen.values().next().value),this.o.onEvent(n);return}case"OK":s[2]===!1&&!String(s[3]??"").startsWith("duplicate")&&this.skip(e);return;case"CLOSED":this.skip(e);return}}},D=["wss://nos.lol","wss://relay.primal.net","wss://nostr-pub.wellorder.net","wss://nostr.oxtr.dev","wss://nostr.bitcoiner.social","wss://relay.nostr.net","wss://nostr.sathoarder.com"],ce=["stun:stun.l.google.com:19302","stun:stun.cloudflare.com:3478"];async function Nn(r,e=8e3,t={}){let s=t.connect??ft,n=(i,o)=>{let a=setTimeout(i,o);return()=>clearTimeout(a)};return Promise.all(r.map(i=>Fn(i,e,s,t.clock?.setTimeout.bind(t.clock)??n)))}async function Fn(r,e,t,s){let n=Date.now(),i=new Uint8Array(32);globalThis.crypto.getRandomValues(i),i[0]&=127,i.every(l=>l===0)&&(i[31]=1);let o=N(globalThis.crypto.getRandomValues(new Uint8Array(32))),a;try{a=await mt(i,o,"probe",Math.floor(Date.now()/1e3))}catch(l){return{url:r,ok:!1,ms:0,error:String(l)}}return new Promise(l=>{let c=null,h=!1,d=!1,u=(g,v)=>{if(!h){h=!0,k();try{c?.close()}catch{}l({url:r,ok:g,ms:Date.now()-n,...v?{error:v}:{}})}},k=s(()=>u(!1,d?"accepted but not echoed":"timeout"),e);try{c=t(r)}catch(g){u(!1,String(g));return}let f="chk"+o.slice(0,8),S=!1,y=()=>{S||(S=!0,c.send(JSON.stringify(["EVENT",a])))};c.onopen=()=>{c.send(JSON.stringify(["REQ",f,{kinds:[le],"#t":[o],since:Math.floor(Date.now()/1e3)-30}])),s(y,1500)},c.onerror=()=>u(!1,"connection failed"),c.onclose=()=>u(!1,"closed"),c.onmessage=g=>{let v;try{v=JSON.parse(String(g.data))}catch{return}Array.isArray(v)&&(v[0]==="EOSE"?y():v[0]==="OK"&&v[1]===a.id?v[2]===!0?d=!0:u(!1,`refused: ${String(v[3]??"")}`.slice(0,120)):v[0]==="CLOSED"?u(!1,`closed: ${String(v[2]??"")}`.slice(0,120)):v[0]==="EVENT"&&v[2]?.id===a.id&&u(!0))}})}var Un=120,W=class r{constructor(e,t,s,n){this.secret=e;this.keys=t;this.wall=n;this.pub=N(Re(e)),this.pool=new Ae({urls:s.relays&&s.relays.length>0?s.relays:D,topic:t.topic,connect:s.connect??ft,clock:s.clock,wall:n,onEvent:i=>{this.onEvent(i)},onChange:s.onChange})}secret;keys;wall;pub;onFrame=null;pool;closed=!1;pending=new Set;static async open(e){let t=Te(e.code);if(!t)throw new Error(`not a join code: ${e.code}`);let s=await ls(t),n=new r(us(),s,e,e.wall??(()=>Date.now()));return n.pool.start(),n}get relaysConnected(){return this.pool.connected}get relaysTotal(){return this.pool.total}get relaysSettled(){return this.pool.settled}whenUp(e){return this.pool.whenUp(e)}send(e,t){let s=this.sendNow(e,t);return this.pending.add(s),s.finally(()=>this.pending.delete(s)),s}async sendNow(e,t){if(this.closed)return;let s={v:1,type:t.type,from:this.pub,to:e,seq:t.seq,ack:t.ack,...t.msgs?{msgs:t.msgs}:{},...t.reason?{reason:t.reason}:{}},n=await cs(this.keys.key,JSON.stringify(s)),i=await mt(this.secret,this.keys.topic,n,Math.floor(this.wall()/1e3));this.closed||this.pool.publish(i)}close(e=!1){if(e&&this.pending.size>0){Promise.allSettled([...this.pending]).then(()=>this.close());return}this.closed=!0,this.onFrame=null,this.pool.close()}async onEvent(e){if(this.closed||e.pubkey===this.pub||this.wall()/1e3-e.created_at>Un)return;let t=await hs(this.keys.key,e.content);if(t===null||this.closed)return;let s;try{s=JSON.parse(t)}catch{return}!s||s.v!==1||s.from!==e.pubkey||s.from===this.pub||s.type!=="open"&&s.type!=="data"&&s.type!=="beat"&&s.type!=="close"||typeof s.to!="string"||s.to!==""&&s.to!==this.pub||this.onFrame?.(s)}};var Ie=class{constructor(e){this.o=e;this.start()}o;readyState=0;onopen=null;onmessage=null;onclose=null;onerror=null;failure="";room=null;link=null;hostPub="";cancelOpen=null;cancelDeadline=null;sendOpen=null;relaysSeen=0;get relaysConnected(){return this.room?.relaysConnected??0}get relaysTotal(){return this.room?.relaysTotal??0}send(e){if(!(this.readyState!==1||!this.link)){if(e.length<64&&e.startsWith('{"t":"ping"'))try{let t=JSON.parse(e);queueMicrotask(()=>this.readyState===1&&this.onmessage?.({data:JSON.stringify({t:"pong",n:t.n})}));return}catch{}this.link.send(e)}}close(e,t){if(this.readyState===3)return;this.readyState===1&&this.link?.close(t??""),this.finish(1e3,t??"",!1)}async start(){let{clock:e}=this.o;try{this.room=await W.open({...this.o,onChange:()=>this.relaysChanged()})}catch(n){this.fail(String(n));return}let t=this.room;if(this.readyState===3){t.close();return}if(t.onFrame=n=>this.onFrame(n),this.cancelDeadline=e.setTimeout(()=>this.fail("the host did not answer through the Nostr relays"),this.o.openTimeoutMs??15e3),!await t.whenUp(this.o.relayTimeoutMs??1e4)){this.fail("no Nostr relay reachable");return}if(this.readyState!==0)return;let s=()=>{t.send("",{type:"open",seq:0,ack:0})};this.relaysSeen=t.relaysConnected,this.sendOpen=s,s(),this.cancelOpen=e.setInterval(s,this.o.openEveryMs??2e3)}relaysChanged(){let e=this.room?.relaysConnected??0;e>this.relaysSeen&&this.readyState===0&&this.sendOpen?.(),this.relaysSeen=e}onFrame(e){if(!(this.readyState===3||e.to!==this.room?.pub)){if(this.readyState===0){if(e.type!=="open")return;this.hostPub=e.from,this.sendOpen=null,this.cancelOpen?.(),this.cancelDeadline?.(),this.cancelOpen=this.cancelDeadline=null;let t=this.room;this.link=new K({clock:this.o.clock,send:s=>{t.send(this.hostPub,s)},deliver:s=>this.readyState===1&&this.onmessage?.({data:s}),onPeerClose:s=>this.finish(1e3,s,!0),onDead:s=>this.finish(1006,s,!1)}),this.readyState=1,this.onopen?.();return}if(e.from===this.hostPub){if(e.type==="open"){this.link?.heard();return}this.link?.receive(e)}}}fail(e){this.readyState!==3&&(this.failure=e,this.readyState===0&&this.onerror?.({message:e}),this.finish(1006,e,!1))}finish(e,t,s){this.readyState!==3&&(this.readyState=3,this.cancelOpen?.(),this.cancelDeadline?.(),this.cancelOpen=this.cancelDeadline=null,this.link?.stop(),this.room?.close(!0),queueMicrotask(()=>this.onclose?.({code:e,reason:t,wasClean:s})))}};var jn=/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/,qn=/^(?=.{1,253}$)([A-Za-z0-9]([A-Za-z0-9-]{0,61}[A-Za-z0-9])?)(\.[A-Za-z0-9]([A-Za-z0-9-]{0,61}[A-Za-z0-9])?)*$/;function ms(r){let e=jn.exec(r);return!!e&&e.slice(1).every(t=>Number(t)<=255)}function Dn(r){if(!/^[0-9A-Fa-f:.]+$/.test(r)||r.split("::").length>2||!r.includes(":"))return!1;let e=r.split(":").pop()??"",t=r.split(":").filter((s,n,i)=>s!==""||n>0&&n<i.length-1);return e.includes(".")&&!ms(e)?!1:t.every(s=>s===""||s.includes(".")||/^[0-9A-Fa-f]{1,4}$/.test(s))}function fs(r,e=47810){let t=r.trim();if(/^ws:\/\//i.test(t)&&(t=t.slice(5)),t=t.replace(/\/+$/,""),!t)return null;let s,n=null;if(t[0]==="["){let l=t.indexOf("]");if(l<0)return null;s=t.slice(1,l);let c=t.slice(l+1);if(c){if(c[0]!==":")return null;n=c.slice(1)}}else{let l=t.indexOf(":");l>=0&&l===t.lastIndexOf(":")?(s=t.slice(0,l),n=t.slice(l+1)):s=t}let i=e;if(n!==null&&(!/^\d{1,5}$/.test(n)||(i=Number(n),i<1||i>65535)))return null;let o=Dn(s);if(!o&&!ms(s)&&!qn.test(s))return null;let a=(o?`[${s}]`:s)+`:${i}`;return{host:s,port:i,isIPv6:o,display:a,url:`ws://${a}/`,candidate:`candidate:1 1 UDP 2122317823 ${s} ${i} typ host`}}function Bn(r){return r}var Y=class extends p{constructor(t,s,n){super("bad_config",`onelobby.json: ${t?t+": ":""}${n}`);this.path=t;this.rule=s}path;rule},T=(r,e)=>({k:"obj",props:r,req:e}),P={k:"bool"},he=(r={})=>({k:"str",...r}),A=(r,e,t)=>({k:"int",min:r,max:e,zeroOr:t}),U=(...r)=>({k:"enum",vals:r}),Jn=T({version:{k:"const",val:1},game:T({id:he({pattern:/^[A-Za-z0-9._-]{1,64}$/}),netVersion:he({min:1,max:32}),title:he({max:64})},["id"]),engine:U("web","node-browser","godot-csharp","unity","dotnet"),hosting:T({direct:P,server:P,lobbyServer:he(),directPort:A(1024,65535),openPort:P,nostr:P,nostrRelays:{k:"strarr"},relayOnly:P,allowedOrigins:{k:"strarr"}}),lobby:T({maxPlayers:A(2,64),listed:P,approveJoins:P,password:P,lateJoin:P,rejoin:P,joinLink:P,meta:{k:"meta"}}),sync:T({mode:U("raw","lockstep","replication"),tickRate:A(1,120),hashEveryTicks:A(1,1e5)}),players:T({identity:U("name","anonymous","platform"),maxNameLength:A(1,24)}),chat:T({text:P,voice:P,voiceMode:U("pushToTalk","voiceActivity")}),session:T({hostLeaves:U("end","migrate"),pause:U("off","host","anyone"),readyTimeoutMs:A(0,6e5),peerTimeoutMs:A(6e3,6e5,!0),rateLimit:{k:"rate"},runInBackground:P}),platforms:T({steam:{k:"steam"},epic:{k:"epic"},discord:P}),ui:T({kind:U("drop-in","generated","none"),entry:he(),theme:U("light","dark","auto")})},["game"]),bs=new Set(["$schema","$comment"]),b=class{constructor(e,t,s){this.path=e;this.rule=t;this.detail=s}path;rule;detail},j=(r,e)=>r?`${r}.${e}`:String(e),z=r=>typeof r=="object"&&r!==null&&!Array.isArray(r);function Oe(r,e,t){switch(r.k){case"obj":{if(!z(e))throw new b(t,"type","must be an object");for(let s of r.req??[])if(!(s in e))throw new b(j(t,s),"required","is required");for(let s of Object.keys(e))if(!bs.has(s)&&!(s in r.props))throw new b(j(t,s),"unknown_key","unknown key");for(let[s,n]of Object.entries(r.props))s in e&&Oe(n,e[s],j(t,s));return}case"str":{if(typeof e!="string")throw new b(t,"type","must be a string");if(r.min!==void 0&&e.length<r.min||r.max!==void 0&&e.length>r.max)throw new b(t,"length",`must be ${r.min??0} to ${r.max??"any"} characters`);if(r.pattern&&!r.pattern.test(e))throw new b(t,"pattern",`must match ${r.pattern.source}`);return}case"int":{if(typeof e!="number"||!Number.isInteger(e))throw new b(t,"type","must be an integer");if(r.zeroOr&&e===0)return;if(r.min!==void 0&&e<r.min||r.max!==void 0&&e>r.max)throw new b(t,"range",`must be ${r.zeroOr?"0 or ":""}${r.min??"any"} to ${r.max??"any"}`);return}case"bool":if(typeof e!="boolean")throw new b(t,"type","must be true or false");return;case"enum":if(typeof e!="string"||!r.vals.includes(e))throw new b(t,"enum",`must be one of ${r.vals.join(", ")}`);return;case"const":if(e!==r.val)throw new b(t,"const",`must be ${r.val}`);return;case"strarr":{if(!Array.isArray(e))throw new b(t,"type","must be an array of strings");e.forEach((s,n)=>{if(typeof s!="string")throw new b(j(t,n),"type","must be a string")});return}case"meta":{if(!z(e))throw new b(t,"type","must be an object of strings");let s=Object.keys(e);if(s.length>16)throw new b(t,"length","at most 16 keys");for(let n of s){if(n.length>64)throw new b(j(t,n),"length","key is longer than 64 characters");let i=e[n];if(typeof i!="string")throw new b(j(t,n),"type","must be a string");if(i.length>64)throw new b(j(t,n),"length","value is longer than 64 characters")}return}case"rate":Oe(T({messages:A(1),perMs:A(1)},["messages","perMs"]),e,t);return;case"steam":if(typeof e=="boolean")return;if(!z(e))throw new b(t,"type","must be true, false or { appId }");Oe(T({appId:A(1)}),e,t);return;case"epic":{if(typeof e=="boolean")return;if(!z(e))throw new b(t,"type","must be true, false or { productId }");for(let s of Object.keys(e))if(!bs.has(s)&&typeof e[s]!="string")throw new b(j(t,s),"type","must be a string");return}}}var O=r=>z(r)?r:{};function bt(r){if(Array.isArray(r))return r.map(bt);if(!z(r))return r;let e={};for(let[t,s]of Object.entries(r))t!=="$comment"&&t!=="$schema"&&(e[t]=bt(s));return e}function ys(r){if(typeof r=="object"&&r!==null&&!Object.isFrozen(r)){Object.freeze(r);for(let e of Object.values(r))ys(e)}return r}function $n(r){let e=O(r.game),t=r.engine,s=O(r.hosting),n=O(r.lobby),i=O(r.sync),o=O(r.players),a=O(r.chat),l=O(r.session),c=O(r.platforms),h=O(r.ui),d=i.tickRate??20,u=t==="web";return{version:1,game:{id:e.id,netVersion:e.netVersion??"1",...e.title!==void 0?{title:e.title}:{}},...t!==void 0?{engine:t}:{},hosting:{direct:s.direct??!u,server:s.server??u,lobbyServer:s.lobbyServer??"",directPort:s.directPort??47810,openPort:s.openPort??!0,nostr:s.nostr??!0,nostrRelays:s.nostrRelays??[...D],relayOnly:s.relayOnly??!1,allowedOrigins:s.allowedOrigins??[]},lobby:{maxPlayers:n.maxPlayers??4,listed:n.listed??!1,approveJoins:n.approveJoins??!1,password:n.password??!1,lateJoin:n.lateJoin??!0,rejoin:n.rejoin??!0,joinLink:n.joinLink??(u||t==="node-browser"),meta:n.meta??{}},sync:{mode:i.mode??"raw",tickRate:d,hashEveryTicks:i.hashEveryTicks??d},players:{identity:o.identity??"name",maxNameLength:o.maxNameLength??24},chat:{text:a.text??!0,voice:a.voice??!1,voiceMode:a.voiceMode??"pushToTalk"},session:{hostLeaves:l.hostLeaves??"end",pause:l.pause??"off",readyTimeoutMs:l.readyTimeoutMs??15e3,peerTimeoutMs:l.peerTimeoutMs??0,...l.rateLimit!==void 0?{rateLimit:l.rateLimit}:{},runInBackground:l.runInBackground??!0},platforms:{steam:c.steam??!1,epic:c.epic??!1,discord:c.discord??!1},ui:{kind:h.kind??(u?"drop-in":"generated"),...h.entry!==void 0?{entry:h.entry}:{},theme:h.theme??"auto"}}}function Vn(r){if(!r.hosting.direct&&!r.hosting.server)throw new b("hosting","hosting_none","at least one of direct, server must be true");if(r.lobby.listed&&r.lobby.approveJoins)throw new b("lobby","listed_and_approve","listed and approveJoins cannot both be true");if(r.engine==="web"&&r.hosting.direct)throw new b("hosting.direct","direct_needs_listener","engine web cannot host direct (a browser cannot listen on a port)");let e=r.engine==="godot-csharp"||r.engine==="unity"||r.engine==="dotnet";if(r.sync.mode==="replication"&&!e)throw new b("sync.mode","replication_needs_csharp","replication needs engine godot-csharp, unity or dotnet");if(r.sync.mode==="replication"&&r.session.hostLeaves==="migrate")throw new b("session.hostLeaves","migrate_with_replication","replication has no host migration; use end");if(r.session.hostLeaves==="migrate"&&!r.hosting.server)throw new b("session.hostLeaves","migrate_needs_server","migrate needs hosting.server (direct lobbies always end)")}function yt(r){let e;try{Oe(Jn,r,""),e=$n(bt(r)),Vn(e)}catch(t){throw t instanceof b?new Y(t.path,t.rule,t.detail):t}return ys(e)}function zn(r){return Math.max(16,Math.min(64,r??0))}var Yn='{"t":"ping","n":0}',Gn=512;var He=class r{constructor(e,t,s,n){this.hub=e;this.room=t;this.clock=s;this.burst=this.newTokens=zn(n),t.onFrame=i=>this.onFrame(i)}hub;room;clock;peers=new Map;gone=new Set;stopped=!1;burst;newTokens;newTokensAt=null;waiting=new Map;cancelWaiting=null;static async start(e,t){return new r(e,await W.open(t),t.clock,t.maxPlayers)}get relaysConnected(){return this.room.relaysConnected}get relaysTotal(){return this.room.relaysTotal}whenUp(e){return this.room.whenUp(e)}get relaysSettled(){return this.room.relaysSettled}async settle(e){if(!await this.whenUp(e))return!1;for(let t=0;t<1500/50&&!this.relaysSettled;t++)await new Promise(s=>setTimeout(s,50));return!0}get peerCount(){return this.peers.size}close(){if(!this.stopped){this.stopped=!0,this.cancelWaiting?.(),this.cancelWaiting=null,this.waiting.clear();for(let[e,t]of this.peers)t.link.close("host left"),t.session&&this.hub.disconnect(t.session),this.peers.delete(e);this.room.close(!0)}}onFrame(e){if(this.stopped)return;let t=this.peers.get(e.from);if(e.type==="open"){if(this.gone.has(e.from))return;t?(t.link.heard(),this.answer(e.from)):this.waiting.has(e.from)?(this.waiting.set(e.from,this.clock.now()),this.admitWaiting()):this.waiting.size===0&&this.admitNew()?this.accept(e.from):this.waiting.size<64&&(this.waiting.set(e.from,this.clock.now()),this.scheduleWaiting());return}t&&(t.link.receive(e),e.type==="beat"&&t.session&&this.hub.receive(t.session,Yn))}scheduleWaiting(){if(this.cancelWaiting||this.waiting.size===0||this.stopped)return;let e=Math.max(0,1-this.newTokens);this.cancelWaiting=this.clock.setTimeout(()=>{this.cancelWaiting=null,this.admitWaiting()},Math.max(1,Math.ceil(e*1e3)))}admitWaiting(){if(this.stopped)return;let e=this.clock.now();for(let[t,s]of this.waiting){if(e-s>5e3||this.peers.has(t)||this.gone.has(t)){this.waiting.delete(t);continue}if(!this.admitNew())break;this.waiting.delete(t),this.accept(t)}this.scheduleWaiting()}admitNew(){let e=this.clock.now();return this.newTokensAt!==null&&(this.newTokens=Math.min(this.burst,this.newTokens+(e-this.newTokensAt)/1e3)),this.newTokensAt=e,this.newTokens<1-1e-6?!1:(this.newTokens=Math.max(0,this.newTokens-1),!0)}answer(e){this.room.send(e,{type:"open",seq:0,ack:0})}accept(e){let t={session:null,link:null};t.link=new K({clock:this.clock,send:n=>{this.room.send(e,n)},deliver:n=>t.session&&this.hub.receive(t.session,n),onPeerClose:()=>this.detach(e,t),onDead:()=>this.detach(e,t)}),this.peers.set(e,t),this.answer(e);let s=this.hub.connect(`nostr:${e.slice(0,16)}`,n=>{n.startsWith('{"t":"pong"')||t.link.send(n)},()=>{t.link.close(),this.forget(e,t)});if(!t.link.closed){if(!s){t.link.close("closed"),this.forget(e,t);return}t.session=s}}detach(e,t){t.link.stop(),t.session&&this.hub.disconnect(t.session),t.session=null,this.forget(e,t)}forget(e,t){this.peers.get(e)===t&&this.peers.delete(e),this.gone.add(e),this.gone.size>Gn&&this.gone.delete(this.gone.values().next().value)}};var Ne=class{counters=new Map;gauges=new Map;help=new Map;describe(e,t){this.help.set(e,t)}gauge(e,t,s){this.gauges.set(e,{help:t,read:s})}inc(e,t,s,n=1){let i=vs(e,t,s);this.counters.set(i,(this.counters.get(i)??0)+n)}get(e,t,s){return this.counters.get(vs(e,t,s))??0}render(){let e="";for(let s of[...this.gauges.keys()].sort(gs)){let n=this.gauges.get(s);e+=`# HELP ${s} ${n.help}
|
|
21
|
+
# TYPE ${s} gauge
|
|
22
|
+
${s} ${n.read()}
|
|
23
|
+
`}let t=null;for(let s of[...this.counters.keys()].sort(gs)){let n=s.indexOf("{"),i=n<0?s:s.slice(0,n);if(i!==t){let o=this.help.get(i);o!==void 0&&(e+=`# HELP ${i} ${o}
|
|
24
|
+
`),e+=`# TYPE ${i} counter
|
|
25
|
+
`,t=i}e+=`${s} ${this.counters.get(s)}
|
|
26
|
+
`}return e}},gs=(r,e)=>r<e?-1:r>e?1:0,Xn=r=>r.replace(/\\/g,"\\\\").replace(/"/g,'\\"').replace(/\n/g,"\\n"),vs=(r,e,t)=>e===void 0?r:`${r}{${e}="${Xn(t??"")}"}`;var gt=new TextEncoder;function Fe(r){let e=[1732584193,4023233417,2562383102,271733878,3285377520],t=r.length,s=new Uint8Array((t+8>>6<<6)+64);s.set(r),s[t]=128;let n=new DataView(s.buffer);n.setUint32(s.length-8,Math.floor(t*8/4294967296)),n.setUint32(s.length-4,t*8>>>0);let i=new Uint32Array(80);for(let l=0;l<s.length;l+=64){for(let f=0;f<16;f++)i[f]=n.getUint32(l+f*4);for(let f=16;f<80;f++){let S=i[f-3]^i[f-8]^i[f-14]^i[f-16];i[f]=S<<1|S>>>31}let[c,h,d,u,k]=e;for(let f=0;f<80;f++){let S,y;f<20?(S=h&d|~h&u,y=1518500249):f<40?(S=h^d^u,y=1859775393):f<60?(S=h&d|h&u|d&u,y=2400959708):(S=h^d^u,y=3395469782);let g=(c<<5|c>>>27)+S+k+y+i[f]|0;k=u,u=d,d=h<<30|h>>>2,h=c,c=g}e[0]=e[0]+c|0,e[1]=e[1]+h|0,e[2]=e[2]+d|0,e[3]=e[3]+u|0,e[4]=e[4]+k|0}let o=new Uint8Array(20),a=new DataView(o.buffer);for(let l=0;l<5;l++)a.setUint32(l*4,e[l]>>>0);return o}function Zn(r,e){let t=r.length>64?Fe(r):r,s=new Uint8Array(64+e.length),n=new Uint8Array(84);for(let i=0;i<64;i++){let o=i<t.length?t[i]:0;s[i]=o^54,n[i]=o^92}return s.set(e,64),n.set(Fe(s),64),Fe(n)}function ks(r){let e="";for(let t of r)e+=t.toString(16).padStart(2,"0");return e}function Qn(r){let e="";for(let t of r)e+=String.fromCharCode(t);return btoa(e)}function ws(r,e){return Qn(Zn(gt.encode(r),gt.encode(e)))}function Ss(r){return ks(Fe(gt.encode(r))).slice(0,8)}function xs(r){return ks(globalThis.crypto.getRandomValues(new Uint8Array(r)))}function _s(r){let t=4294967296-4294967296%r,s=new Uint32Array(1);for(;;)if(globalThis.crypto.getRandomValues(s),s[0]<t)return s[0]%r}var Ps="lk1",er=24,tr=48,Es=16,vt=64,sr=8,Ms="0123456789ABCDEFGHJKMNPQRSTVWXYZ",nr=/^[A-Za-z0-9._-]{1,64}$/;function Rs(){return{turnUrls:[],turnSecret:"",stunUrls:["stun:stun.l.google.com:19302"],region:"",allowedGames:[],gameQuotas:{},maxLobbies:1e3,maxPeers:1e4,hardMaxPlayers:64,rateScale:1,turnTtlSeconds:3600,maxMessageBytes:16*1024,disableIpLimits:!1,createPerMin:10,joinPerMin:60,signalPerSec:20,socketsPerIp:32,openLobbiesPerIp:8,idleTimeoutMs:3e4,resumeWindowMs:6e4,codeReuseMs:24*3600*1e3,listLimitMax:100,singleLobby:!1,password:null,issueCodes:!0}}function As(r,e){let t=Math.min(250,Math.max(2,r));return{...Rs(),singleLobby:!0,issueCodes:!1,password:e||null,stunUrls:[],turnUrls:[],maxLobbies:1,hardMaxPlayers:t,maxPeers:t+8}}var rr=8,kt="nostr:",ir=10,G=class{constructor(e,t){this.limit=e;this.windowMs=t}limit;windowMs;hits=[];tryHit(e){return this.trim(e),this.hits.length>=this.limit?!1:(this.hits.push(e),!0)}retryAfterMs(e){return this.trim(e),this.hits.length<this.limit||this.hits.length===0?0:Math.max(1,this.windowMs-(e-this.hits[0]))}isIdle(e){return this.trim(e),this.hits.length===0}trim(e){for(;this.hits.length>0&&e-this.hits[0]>=this.windowMs;)this.hits.shift()}};function Cs(r,e,t=""){if(!r)return t;let s="";for(let n of r){let i=n.charCodeAt(0);n.length===1&&(i<=31||i>=127&&i<=159||i>=8234&&i<=8238||i>=8294&&i<=8297)||(s+=n)}if(s.length>e){s=s.slice(0,e);let n=s.charCodeAt(s.length-1);n>=55296&&n<=56319&&(s=s.slice(0,-1))}return s.length===0?t:s}function or(r){if(typeof r!="string")return null;let e="";for(let t of r){if(t===" "||t==="-")continue;let s=t>="a"&&t<="z"?t.toUpperCase():t;if(s==="I"||s==="L"?s="1":s==="O"&&(s="0"),Ms.indexOf(s)<0||e.length>=6)return null;e+=s}return e.length===6?e:null}var wt=class{constructor(e,t,s,n,i){this.id=e;this.ip=t;this.connId=s;this.send=n;this.close=i}id;ip;connId;send;close;closed=!1;lastSeen=0;helloDone=!1;gameId="";netVersion="";name="";lobby=null;member=null;pendingIn=null;pendingMember=null;linkIp=null;signalRates=new Map;signalTotal=null},je=class{peerId=0;name="";joinedAt=0;session=null;detachedAt=0;token="";ip=""},St=class{code="";gameId="";netVersion="";name="";visibility="public";mode="lockstep";tickRate=20;lateJoin=!1;relayOnly=!1;phase="lobby";maxPlayers=0;createdAt=0;ownerIp="";meta={};members=[];pending=[];kicked=new Set;hostId=0;epoch=0;pendingClaimPeer=0;pendingClaimEpoch=0;nextPeerId=1;find(e){return this.members.find(t=>t.peerId===e)}},Ts=new Set(["hello","create","join","list","leave","signal","meta","phase","kick","approve","ping","host_changed","path"]),_=(r,e)=>typeof r[e]=="string"?r[e]:null,L=(r,e)=>{let t=r[e];return typeof t=="number"&&Number.isInteger(t)&&Math.abs(t)<2147483647?t:null},B=(r,e)=>typeof r[e]=="boolean"?r[e]:null,w=(r,e)=>r[e]!==void 0&&r[e]!==null,m=r=>({field:r}),Ue=r=>xs(r),Is=(r,e)=>r<e?-1:r>e?1:0;function Ls(r){if(r==null)return{meta:{}};if(typeof r!="object"||Array.isArray(r))return{err:"meta must be an object of strings"};let e=Object.entries(r);if(e.length>Es)return{err:`meta allows at most ${Es} keys`};let t={};for(let[s,n]of e.sort(([i],[o])=>Is(i,o))){if(typeof n!="string")return{err:"meta values must be strings"};if(s.length>vt||n.length>vt)return{err:`meta keys and values are limited to ${vt} chars`};t[s]=n}return{meta:t}}var qe=class{o;now;rand;log;lobbies=new Map;retired=new Map;tokens=new Map;sessions=new Map;gameLobbies=new Map;ips=new Map;nextSession=0;createdOnce=!1;members=0;logSalt=Ue(8);metrics=new Ne;constructor(e={},t={}){this.o={...Rs(),...e},this.now=t.now??(()=>Date.now()),this.rand=t.randomInt??(n=>_s(n)),this.log=t.log??(()=>{});let s=this.metrics;s.gauge("onelobby_lobbies","Live lobbies.",()=>this.lobbyCount),s.gauge("onelobby_sockets","Open signaling sockets.",()=>this.socketCount),s.gauge("onelobby_lobbies_retired","Join codes held back from reuse.",()=>this.retired.size),s.describe("onelobby_messages_total","Client messages received, by type."),s.describe("onelobby_errors_total","Error replies sent, by code."),s.describe("onelobby_path_total","ICE path reports from peers: direct vs relayed (DESIGN s.10)."),s.describe("onelobby_connections_total","Sockets accepted since start."),s.describe("onelobby_lobbies_created_total","Lobbies created since start."),s.describe("onelobby_rate_limit_hits_total","Requests refused by a rate limit, by kind.")}get lobbyCount(){return this.lobbies.size}get socketCount(){return this.sessions.size}get scale(){return Math.max(1,this.o.rateScale)}connect(e,t,s){let n=new wt(++this.nextSession,e,Ue(6),t,s);if(n.lastSeen=this.now(),this.metrics.inc("onelobby_connections_total"),this.sessions.size>=this.o.maxPeers)return this.error(n,"server_full","server is at its peer limit"),n.close(),null;let i=this.ipOf(e);return!this.o.disableIpLimits&&i.sockets>=this.o.socketsPerIp*this.scale?(this.rateLimited(n,"sockets",5e3,"max_sockets"),n.close(1008,"too_many_sockets"),null):(this.sessions.set(n.id,n),i.sockets++,n)}disconnect(e){if(!this.sessions.delete(e.id))return;e.closed=!0;let t=this.ips.get(e.ip);t&&t.sockets--,e.pendingIn&&(e.pendingIn.pending=e.pendingIn.pending.filter(i=>i!==e.pendingMember),e.pendingIn=null,e.pendingMember=null);let s=e.member,n=e.lobby;s&&n&&(s.session=null,s.detachedAt=this.now(),e.member=null,e.lobby=null,this.logEvent("detach",n,e),this.broadcast(n,{t:"peer_left",peerId:s.peerId,reason:"timeout"}),this.metrics.inc("onelobby_peers_left_total","reason","timeout"),n.hostId===s.peerId&&this.promoteHost(n,s.peerId))}reportLinkAddress(e,t){for(let s of this.lobbies.values()){let n=s.find(e),i=n?.session;if(!(!n||!i||!i.ip.startsWith(kt))){i.linkIp=t,s.kicked.has(t)&&(this.removeMember(s,n,"kicked"),this.closeSession(i));return}}}closeSession(e){e.close(),this.disconnect(e)}tick(){let e=this.now();for(let t of[...this.sessions.values()])e-t.lastSeen>this.o.idleTimeoutMs&&(this.metrics.inc("onelobby_idle_timeouts_total"),this.closeSession(t));for(let t of[...this.lobbies.values()])for(let s of[...t.members])s.detachedAt>0&&e-s.detachedAt>=this.o.resumeWindowMs&&this.removeMember(t,s,"timeout");for(let[t,s]of[...this.retired])e-s>=this.o.codeReuseMs&&this.retired.delete(t);for(let[t,s]of[...this.ips])s.sockets<=0&&s.open<=0&&s.create.isIdle(e)&&s.join.isIdle(e)&&this.ips.delete(t)}receive(e,t){if(e.closed)return;if(e.lastSeen=this.now(),t.length>this.o.maxMessageBytes){this.error(e,"bad_request","message too large"),this.closeSession(e);return}let s=null;try{let i=JSON.parse(t);s=i&&typeof i=="object"&&!Array.isArray(i)?i:null}catch{s=null}let n=s?_(s,"t"):null;if(!s||n===null){this.error(e,"bad_request","expected a JSON object with a string t",m("t"));return}if(this.metrics.inc("onelobby_messages_total","type",Ts.has(n)?n:"unknown"),n==="hello")return this.hello(e,s);if(!e.helloDone){Ts.has(n)&&this.error(e,"bad_request","send hello first");return}switch(n){case"create":return this.create(e,s);case"join":return this.join(e,s);case"list":return this.list(e,s);case"leave":return this.leave(e);case"signal":return this.signal(e,s);case"meta":return this.hostMeta(e,s);case"phase":return this.hostPhase(e,s);case"kick":return this.kick(e,s);case"approve":return this.approve(e,s);case"ping":{let i=L(s,"n");return i===null||i<0?this.error(e,"bad_request","ping needs an integer n",m("n")):this.send(e,{t:"pong",n:i})}case"host_changed":return this.claimHost(e,s);case"path":return this.pathReport(e,s);default:return}}hello(e,t){if(e.helloDone)return this.error(e,"bad_request","hello already sent");if(_(t,"proto")!==Ps){this.error(e,"bad_request","unsupported proto, expected lk1",{supported:[Ps]}),this.closeSession(e);return}if(w(t,"name")&&_(t,"name")===null)return this.error(e,"bad_request","name must be a string",m("name"));if(w(t,"client")&&(typeof t.client!="object"||Array.isArray(t.client)))return this.error(e,"bad_request","client must be an object",m("client"));let s=_(t,"gameId"),n=_(t,"netVersion");if(s===null||!nr.test(s))return this.error(e,"bad_request","gameId must match [A-Za-z0-9._-]{1,64}",m("gameId"));if(!n||n.length>32)return this.error(e,"bad_request","netVersion must be 1-32 characters",m("netVersion"));if(this.o.allowedGames.length>0&&!this.o.allowedGames.includes(s)){this.error(e,"game_not_allowed","this server does not host that game"),this.closeSession(e);return}e.gameId=s,e.netVersion=n,e.name=Cs(_(t,"name"),er,"Player"),e.helloDone=!0;let i=null,o=null,a=_(t,"resumeToken"),l=a!==null?this.tokens.get(a):void 0;if(l&&l.lobby.gameId===s&&l.lobby.netVersion===n){let h=l.member.session,d=h!=null&&h!==e,u=l.member.detachedAt>0&&this.now()-l.member.detachedAt<this.o.resumeWindowMs;(d||u)&&(d&&this.closeSession(h),i=l.member,o=l.lobby)}let c={t:"welcome",ice:this.iceList(e),serverTime:this.now(),limits:{maxMsg:this.o.maxMessageBytes,maxPlayers:this.o.hardMaxPlayers}};if(i&&(c.peerId=i.peerId),this.send(e,c),i&&o){this.tokens.delete(i.token),i.token=Ue(16),this.tokens.set(i.token,{lobby:o,member:i}),i.session=e,i.detachedAt=0,e.lobby=o,e.member=i,this.logEvent("resume",o,e),this.metrics.inc("onelobby_resumes_total");for(let h of o.members)h!==i&&this.sendTo(h,{t:"peer_joined",peer:this.peerObj(i)});this.send(e,this.lobbyMsg(o,i))}}iceList(e){let t=[];if(this.o.stunUrls.length>0&&t.push({urls:[...this.o.stunUrls]}),this.o.turnUrls.length>0&&this.o.turnSecret.length>0){let n=`${Math.floor(this.now()/1e3)+this.o.turnTtlSeconds}:${e.connId}`,i=ws(this.o.turnSecret,n);t.push({urls:[...this.o.turnUrls],username:n,credential:i,ttl:this.o.turnTtlSeconds})}return t}ipOf(e){let t=this.ips.get(e);return t||(t={create:new G(this.o.createPerMin*this.scale,6e4),join:new G(this.o.joinPerMin*this.scale,6e4),open:0,sockets:0},this.ips.set(e,t)),t}create(e,t){if(e.lobby||e.pendingIn)return this.error(e,"bad_request","already in a lobby");let s=this.ipOf(e.ip),n=this.now();if(!this.o.disableIpLimits){if(!s.create.tryHit(n))return this.rateLimited(e,"create",s.create.retryAfterMs(n),null);if(s.open>=this.o.openLobbiesPerIp*this.scale)return this.rateLimited(e,"open_lobbies",5e3,"max_lobbies")}if(this.o.singleLobby&&this.createdOnce)return this.error(e,"server_full","this host runs one lobby");if(this.lobbies.size>=this.o.maxLobbies||this.members>=this.o.maxPeers)return this.error(e,"server_full","server is at its lobby limit");let i=this.o.gameQuotas[e.gameId];if(i!==void 0&&(this.gameLobbies.get(e.gameId)??0)>=i)return this.error(e,"server_full","lobby quota for this game is reached");if(w(t,"name")&&_(t,"name")===null)return this.error(e,"bad_request","name must be a string",m("name"));let o=_(t,"visibility")??"public",a=_(t,"mode")??"lockstep";if(o!=="public"&&o!=="unlisted"&&o!=="private")return this.error(e,"bad_request","visibility must be public, unlisted or private",m("visibility"));if(a!=="lockstep"&&a!=="raw")return this.error(e,"bad_request","mode must be lockstep or raw",m("mode"));if(w(t,"maxPlayers")&&L(t,"maxPlayers")===null)return this.error(e,"bad_request","maxPlayers must be an integer",m("maxPlayers"));let l=L(t,"maxPlayers")??8;if(l<2||l>this.o.hardMaxPlayers)return this.error(e,"bad_request",`maxPlayers must be 2-${this.o.hardMaxPlayers}`,m("maxPlayers"));if(w(t,"tickRate")&&L(t,"tickRate")===null)return this.error(e,"bad_request","tickRate must be an integer",m("tickRate"));let c=L(t,"tickRate")??20;if(c<1||c>255)return this.error(e,"bad_request","tickRate must be 1-255",m("tickRate"));if(w(t,"lateJoin")&&B(t,"lateJoin")===null)return this.error(e,"bad_request","lateJoin must be a boolean",m("lateJoin"));if(w(t,"relayOnly")&&B(t,"relayOnly")===null)return this.error(e,"bad_request","relayOnly must be a boolean",m("relayOnly"));let h=Ls(t.meta);if("err"in h)return this.error(e,"bad_request",h.err,m("meta"));let d=this.newCode();if(d===null)return this.error(e,"server_full","could not allocate a join code");let u=new St;Object.assign(u,{code:d,gameId:e.gameId,netVersion:e.netVersion,name:Cs(_(t,"name"),tr,`${e.name}'s lobby`),visibility:o,mode:a,tickRate:c,lateJoin:B(t,"lateJoin")??!1,relayOnly:B(t,"relayOnly")??!1,maxPlayers:l,createdAt:n,ownerIp:e.ip,meta:h.meta}),this.lobbies.set(d,u),this.createdOnce=!0,this.gameLobbies.set(e.gameId,(this.gameLobbies.get(e.gameId)??0)+1),s.open++,this.metrics.inc("onelobby_lobbies_created_total");let k=this.addMember(u,e,null);u.hostId=k.peerId,this.logEvent("create",u,e),this.send(e,this.lobbyMsg(u,k))}join(e,t){if(e.lobby||e.pendingIn)return this.error(e,"bad_request","already in a lobby");let s=this.ipOf(e.ip),n=this.now();if(!this.o.disableIpLimits&&!s.join.tryHit(n))return this.rateLimited(e,"join",s.join.retryAfterMs(n),null);let i;if(this.o.singleLobby&&!w(t,"code")){if(i=this.lobbies.values().next().value,!i||i.gameId!==e.gameId)return this.error(e,"unknown_code","this host has no open lobby for that game")}else{let o=or(t.code);if(o===null)return this.error(e,"bad_request","malformed join code",m("code"));if(i=this.lobbies.get(o),!i||i.gameId!==e.gameId)return this.error(e,"unknown_code","no lobby with that code")}if(this.o.password!==null&&_(t,"password")!==this.o.password)return this.error(e,"bad_password","wrong password for this lobby");if(i.netVersion!==e.netVersion)return this.error(e,"version_mismatch",`host is on ${i.netVersion}, you are on ${e.netVersion}`,{hostNetVersion:i.netVersion,yourNetVersion:e.netVersion});if(i.kicked.has(e.ip))return this.error(e,"kicked","you were removed from this lobby");if(i.phase==="in_game"&&!i.lateJoin)return this.error(e,"in_game","the game has already started");if(i.members.length>=i.maxPlayers)return this.error(e,"lobby_full","lobby is full");if(this.members>=this.o.maxPeers)return this.error(e,"server_full","server is at its peer limit");if(i.visibility==="private"){if(i.pending.length>=sr)return this.error(e,"lobby_full","too many pending requests");let o=new je;o.peerId=i.nextPeerId++,o.name=e.name,o.session=e,o.ip=e.ip,i.pending.push(o),e.pendingIn=i,e.pendingMember=o;let a=i.find(i.hostId)?.session;a&&this.send(a,{t:"join_request",peer:this.peerObj(o)}),this.logEvent("join_request",i,e);return}this.admit(i,e,null)}admit(e,t,s){let n=this.addMember(e,t,s);this.logEvent("join",e,t);for(let i of e.members)i!==n&&this.sendTo(i,{t:"peer_joined",peer:this.peerObj(n)});e.find(e.hostId)?.session||this.promoteHost(e,e.hostId),this.send(t,this.lobbyMsg(e,n))}addMember(e,t,s){let n=s??new je;return s||(n.peerId=e.nextPeerId++),n.name=t.name,n.session=t,n.ip=t.ip,n.joinedAt=this.now(),n.detachedAt=0,n.token=Ue(16),e.members.push(n),this.members++,this.tokens.set(n.token,{lobby:e,member:n}),t.lobby=e,t.member=n,t.pendingIn=null,t.pendingMember=null,n}list(e,t){let s=_(t,"visibility");if(!this.o.issueCodes)return this.send(e,{t:"lobbies",items:[]});if(s!=="public")return this.error(e,"bad_request","only public lobbies can be listed",m("visibility"));let n=50;if(w(t,"limit")){let d=L(t,"limit");if(d===null||d<1||d>this.o.listLimitMax)return this.error(e,"bad_request",`limit must be 1-${this.o.listLimitMax}`,m("limit"));n=d}let i=0;if(w(t,"cursor")){let d=_(t,"cursor");if(d===null||!/^\d+$/.test(d))return this.error(e,"bad_request","bad cursor",m("cursor"));i=Number(d)}let o=null;if(w(t,"meta")){if(typeof t.meta!="object"||Array.isArray(t.meta))return this.error(e,"bad_request","meta must be an object",m("meta"));o={};for(let[d,u]of Object.entries(t.meta)){if(typeof u!="string")return this.error(e,"bad_request","meta values must be strings",m("meta"));o[d]=u}}let a=[...this.lobbies.values()].filter(d=>d.gameId===e.gameId&&d.netVersion===e.netVersion&&d.visibility==="public"&&(d.phase!=="in_game"||d.lateJoin)&&(o===null||Object.entries(o).every(([u,k])=>d.meta[u]===k))).sort((d,u)=>d.createdAt-u.createdAt||Is(d.code,u.code)),l=a.slice(i,i+n),h={t:"lobbies",items:l.map(d=>({code:d.code,gameId:d.gameId,netVersion:d.netVersion,name:d.name,hostName:d.find(d.hostId)?.name??"",players:d.members.length,maxPlayers:d.maxPlayers,phase:d.phase,meta:{...d.meta},region:this.o.region,createdAt:d.createdAt}))};i+l.length<a.length&&(h.cursor=String(i+l.length)),this.send(e,h)}leave(e){if(e.pendingIn){e.pendingIn.pending=e.pendingIn.pending.filter(t=>t!==e.pendingMember),e.pendingIn=null,e.pendingMember=null;return}!e.lobby||!e.member||this.removeMember(e.lobby,e.member,"left")}signal(e,t){let s=e.lobby,n=e.member;if(!s||!n)return this.error(e,"bad_request","not in a lobby");let i=L(t,"to"),o=i===null?void 0:s.find(i);if(!o||!o.session||o===n)return this.error(e,"bad_request","unknown signal target",m("to"));if(this.o.singleLobby&&n.peerId!==s.hostId&&o.peerId!==s.hostId)return this.error(e,"bad_request","signals go to or from the host only",m("to"));let a=this.now(),l=this.signalRate(e,o,a);if(!l.tryHit(a))return this.rateLimited(e,"signal",l.retryAfterMs(a),null);let c=e.signalTotal??=new G(this.o.signalPerSec*rr*this.scale,1e3);if(!c.tryHit(a))return this.rateLimited(e,"signal",c.retryAfterMs(a),null);if(!w(t,"sdp")&&!w(t,"ice"))return this.error(e,"bad_request","signal needs sdp or ice",m("sdp"));let h={t:"signal",from:n.peerId};w(t,"sdp")&&(h.sdp=t.sdp),w(t,"ice")&&(h.ice=t.ice),this.send(o.session,h),this.metrics.inc("onelobby_signals_relayed_total")}signalRate(e,t,s){let n=e.signalRates.get(t);if(!n){for(let[i,o]of e.signalRates)o.isIdle(s)&&e.signalRates.delete(i);n=new G((t.session?.ip.startsWith(kt)?ir:this.o.signalPerSec)*this.scale,1e3),e.signalRates.set(t,n)}return n}requireHost(e){return!e.lobby||!e.member?(this.error(e,"bad_request","not in a lobby"),null):e.lobby.hostId!==e.member.peerId?(this.error(e,"not_host","only the host can do that"),null):e.lobby}hostMeta(e,t){let s=this.requireHost(e);if(!s)return;let n=Ls(t.meta);if("err"in n||!w(t,"meta"))return this.error(e,"bad_request","err"in n?n.err:"meta is required",m("meta"));s.meta=n.meta,this.broadcastLobby(s)}hostPhase(e,t){let s=this.requireHost(e);if(!s)return;let n=_(t,"phase");if(n!=="in_game"&&n!=="lobby")return this.error(e,"bad_request","phase must be in_game or lobby",m("phase"));s.phase=n,this.logEvent(`phase:${n}`,s,e),this.broadcastLobby(s)}kick(e,t){let s=this.requireHost(e);if(!s)return;let n=L(t,"peerId"),i=n===null?void 0:s.find(n);if(n===null||n===e.member.peerId||!i)return this.error(e,"bad_request","unknown peerId",m("peerId"));let o=B(t,"ban");if(w(t,"ban")&&o===null)return this.error(e,"bad_request","ban must be a boolean",m("ban"));let a=L(t,"closeCode");if(w(t,"closeCode")&&(a===null||!(a===1e3||a>=3e3&&a<=4999)))return this.error(e,"bad_request","closeCode must be 1000 or 3000-4999",m("closeCode"));let l=_(t,"closeReason");if(w(t,"closeReason")&&(l===null||l.length>120))return this.error(e,"bad_request","closeReason must be a string of at most 120 characters",m("closeReason"));let c={};if(o===!1&&(c.ban=!1),a!==null&&(c.closeCode=a),l!==null&&(c.closeReason=l),o!==!1){s.kicked.add(i.ip);let h=i.session?.linkIp;h&&i.ip.startsWith(kt)&&s.kicked.add(h)}this.removeMember(s,i,o===!1?"disconnected":"kicked",Object.keys(c).length?c:void 0)}approve(e,t){let s=this.requireHost(e);if(!s)return;let n=L(t,"peerId"),i=n===null?void 0:s.pending.find(a=>a.peerId===n),o=i?.session;if(!i||!o)return this.error(e,"bad_request","no such pending request",m("peerId"));if(w(t,"ok")&&B(t,"ok")===null)return this.error(e,"bad_request","ok must be a boolean",m("ok"));if(s.pending=s.pending.filter(a=>a!==i),o.pendingIn=null,o.pendingMember=null,B(t,"ok")!==!0)return this.error(o,"kicked","the host declined your request",{reason:"denied"});if(s.members.length>=s.maxPlayers)return this.error(o,"lobby_full","lobby is full");if(s.phase==="in_game"&&!s.lateJoin)return this.error(o,"in_game","the game has already started");this.admit(s,o,i)}claimHost(e,t){let s=e.lobby,n=e.member;if(!s||!n)return;let i=L(t,"hostId"),o=L(t,"epoch");if(!(i!==n.peerId||o===null||o<=s.epoch)&&!this.o.singleLobby){if(s.hostId!==n.peerId&&s.find(s.hostId)?.session){o>s.pendingClaimEpoch&&(s.pendingClaimPeer=n.peerId,s.pendingClaimEpoch=o);return}s.hostId=n.peerId,s.epoch=o,s.pendingClaimPeer=s.pendingClaimEpoch=0,this.broadcast(s,{t:"host_changed",hostId:s.hostId,epoch:s.epoch}),this.metrics.inc("onelobby_host_changes_total"),this.logEvent("host_claim",s,e)}}pathReport(e,t){let s=_(t,"path");s==="relayed"||s==="direct"?this.metrics.inc("onelobby_path_total","path",s):this.error(e,"bad_request","path must be relayed or direct",m("path"))}removeMember(e,t,s,n){let i=e.members.indexOf(t);if(i<0)return;e.members.splice(i,1),this.members--,this.tokens.delete(t.token);let o=t.session,a=t.detachedAt===0;if(o&&(o.lobby=null,o.member=null),a&&(this.broadcast(e,{t:"peer_left",peerId:t.peerId,reason:s}),this.metrics.inc("onelobby_peers_left_total","reason",s),(s==="kicked"||s==="disconnected")&&o&&this.error(o,"kicked",s==="kicked"?"the host removed you from the lobby":"the host disconnected you",n)),this.logEvent(s,e,o),e.members.length===0)return this.closeLobby(e);e.hostId===t.peerId&&this.promoteHost(e,t.peerId)}promoteHost(e,t){let s=e.pendingClaimEpoch>e.epoch&&e.pendingClaimPeer!==t?e.members.find(a=>a.peerId===e.pendingClaimPeer&&a.session):void 0,n=e.pendingClaimEpoch;e.pendingClaimPeer=e.pendingClaimEpoch=0;let i=s??e.members.filter(a=>a.session&&a.peerId!==t).sort((a,l)=>a.peerId-l.peerId)[0];if(!i&&(e.find(t)||(i=[...e.members].sort((a,l)=>a.peerId-l.peerId)[0],!i)))return;e.hostId=i.peerId,s?e.epoch=n:e.epoch++,this.broadcast(e,{t:"host_changed",hostId:i.peerId,epoch:e.epoch}),this.metrics.inc("onelobby_host_changes_total");let o=i.session;if(o)for(let a of e.pending)this.send(o,{t:"join_request",peer:this.peerObj(a)});this.logEvent("host_changed",e,i.session)}closeLobby(e){this.lobbies.delete(e.code),this.retired.set(e.code,this.now());let t=this.gameLobbies.get(e.gameId);t!==void 0&&(t<=1?this.gameLobbies.delete(e.gameId):this.gameLobbies.set(e.gameId,t-1));let s=this.ips.get(e.ownerIp);s&&s.open>0&&s.open--;for(let n of[...e.pending]){let i=n.session;i&&(i.pendingIn=null,i.pendingMember=null,this.error(i,"unknown_code","the lobby closed"))}e.pending=[],this.logEvent("close",e,null)}newCode(){for(let e=0;e<100;e++){let t="";for(let s=0;s<6;s++)t+=Ms[this.rand(32)];if(!this.lobbies.has(t)&&!this.retired.has(t))return t}return null}lobbyMsg(e,t){let s=e.members.filter(n=>n.detachedAt===0).sort((n,i)=>n.peerId-i.peerId).map(n=>({peerId:n.peerId,name:n.name,joinedAt:n.joinedAt}));return{t:"lobby",code:this.o.issueCodes?e.code:"",gameId:e.gameId,netVersion:e.netVersion,hostId:e.hostId,you:t.peerId,mode:e.mode,tickRate:e.tickRate,lateJoin:e.lateJoin,phase:e.phase,meta:{...e.meta},peers:s,resumeToken:t.token,name:e.name,maxPlayers:e.maxPlayers,visibility:e.visibility,relayOnly:e.relayOnly}}broadcastLobby(e){for(let t of e.members)t.session&&this.sendTo(t,this.lobbyMsg(e,t))}broadcast(e,t){for(let s of e.members)this.sendTo(s,t)}peerObj(e){return{peerId:e.peerId,name:e.name}}send(e,t){e.closed||e.send(JSON.stringify(t))}sendTo(e,t){e.session&&this.send(e.session,t)}error(e,t,s,n){this.metrics.inc("onelobby_errors_total","code",t);let i={t:"error",code:t,msg:s};n!==void 0&&(i.data=n),e.send(JSON.stringify(i))}rateLimited(e,t,s,n){this.metrics.inc("onelobby_rate_limit_hits_total","kind",t),this.log(`rate_limit kind=${t} ip=${this.hashIp(e.ip)}`);let i={retryAfterMs:s};n!==null&&(i.reason=n),this.error(e,"rate_limited",`too many ${t} requests`,i)}logEvent(e,t,s){this.log(`lobby ${e} code=${t.code} game=${t.gameId} players=${t.members.length} ip=${s?this.hashIp(s.ip):"-"}`)}hashIp(e){return Ss(this.logSalt+e)}};var De=class r{constructor(e){this.hub=e;this.session=e.connect(r.ADDRESS,t=>queueMicrotask(()=>this.readyState===1&&this.onmessage?.({data:t})),()=>this.finish()),queueMicrotask(()=>{if(!this.session){this.finish();return}this.readyState=1,this.onopen?.()})}hub;static ADDRESS="local";readyState=0;onopen=null;onmessage=null;onclose=null;onerror=null;session;send(e){this.readyState===1&&this.session&&this.hub.receive(this.session,e)}close(){let e=this.session,t=this.readyState===1;this.finish(),t&&e&&this.hub.disconnect(e)}finish(){this.readyState!==3&&(this.readyState=3,queueMicrotask(()=>this.onclose?.()))}};function ar(r,e){return`OneLobby: browser host (no port); code ${r} (Nostr); nostr ${e} relays`}async function Os(r,e={}){let t=r.blueprint;if(t&&!t.hosting.nostr)throw new p("nostr_disabled","this game has Nostr signaling turned off (hosting.nostr is false), and a browser host has no other way to be reached");let s=Object.fromEntries(Object.entries(e).filter(([,f])=>f!==void 0)),n=t?{maxPlayers:t.lobby.maxPlayers,lateJoin:t.lobby.lateJoin,meta:{...t.lobby.meta},...t.session.rateLimit?{rateLimit:{...t.session.rateLimit}}:{},...s,nostr:{...s.nostr,relays:s.nostr?.relays??[...t.hosting.nostrRelays]}}:s,i=n.maxPlayers??4,o=n.nostr?.relays&&n.nostr.relays.length>0?n.nostr.relays:[...D],a=n.nostr?.stun??[...ce],l=new qe({...As(i,n.password),stunUrls:a}),c=setInterval(()=>l.tick(),1e3);c.unref?.();let h=ot(),d=r.nostrHostEnv(),u,k=()=>{clearInterval(c),u?.close()};try{try{u=await He.start(l,{code:h,relays:o,connect:d.connect,clock:d.clock,wall:d.wall,maxPlayers:i})}catch(g){throw new p("direct_unreachable",`cannot reach any Nostr relay (${g instanceof Error?g.message:String(g)}); a browser host has no other way to be reached`)}if(!await u.settle(n.relayTimeoutMs??1e4))throw new p("direct_unreachable",`no Nostr relay reachable (tried ${u.relaysTotal}); a browser host has no IP fallback, check the connection or pass other relays`);let S={name:n.name??`${r.options.displayName||"Player"}'s game`,maxPlayers:i,lateJoin:n.lateJoin??!0,meta:n.meta,rateLimit:n.rateLimit},y=await r.hostDirectWith(S,{socketFactory:()=>new De(l),wrapLinks:g=>g});return y.linkAddressSink=(g,v)=>l.reportLinkAddress(g,v),y.on("sessionEnded",k),y.joinCode=h,y.joinCodeKind="nostr",y.directCode="",y.directCodeKind="none",y.portMapping="off",y.directHostLog=ar(h,`${u.relaysConnected}/${u.relaysTotal}`),lr(y,n),n.log!==!1&&(n.log??console.log)(y.directHostLog),y}catch(f){throw k(),f}}function lr(r,e){let t=e.document!==void 0?e.document:globalThis.document;if(!t||typeof t.addEventListener!="function")return;let s=e.warn===!1?null:e.warn??(i=>console.warn(i)),n=()=>{let i=t.visibilityState==="hidden",o=i!==r.hostHidden;r.setHostHidden(i),o&&i&&s?.("OneLobby: the host tab is hidden; the browser slows its timers, so players may lag or drop. Keep this tab visible while hosting.")};t.addEventListener("visibilitychange",n),r.on("sessionEnded",()=>t.removeEventListener("visibilitychange",n)),n()}var Ns="onelobby",Fs="0.6.0",Us=15e3;function hr(){return typeof navigator<"u"&&!!navigator.mediaDevices?.getUserMedia&&typeof AudioWorkletNode<"u"}function dr(){try{return globalThis.sessionStorage??null}catch{return null}}var xt=new Set;function Hs(r){for(let e of xt)if(e.resumeToken===r)return!0;return!1}var J=class r extends E{options;socketFactory;linkFactory;clock;wall;voiceCodec;createMic;createSink;storage;nostrConnectFn;blueprint;static fromConfig(e,t={}){let s=yt(e),n=(t.displayName??"").trim();if(n){if(s.players.identity==="anonymous")throw new p("identity_anonymous","this game uses anonymous players: no display names");if(n.length>s.players.maxNameLength)throw new Y("displayName","length",`must be at most ${s.players.maxNameLength} characters`)}let i=new r({gameId:s.game.id,netVersion:s.game.netVersion,lobbyServer:t.lobbyServer??s.hosting.lobbyServer,mode:s.sync.mode==="lockstep"?"lockstep":"raw",tickRate:s.sync.tickRate,hashEveryTicks:s.sync.hashEveryTicks,displayName:n,relayOnly:s.hosting.relayOnly,readyTimeoutMs:s.session.readyTimeoutMs,peerTimeoutMs:s.session.peerTimeoutMs,rememberSeat:t.rememberSeat??s.lobby.rejoin,advanced:t.advanced});return i.blueprint=s,i}blueprintHostDefaults(){let e=this.blueprint;if(!e)return{};let t=this.options.displayName;return{name:t?`${t}'s game`:"Game",maxPlayers:e.lobby.maxPlayers,visibility:e.lobby.listed?"public":e.lobby.approveJoins?"private":"unlisted",lateJoin:e.lobby.lateJoin,meta:{...e.lobby.meta},...e.session.rateLimit?{rateLimit:{...e.session.rateLimit}}:{}}}constructor(e){super();let t=e.tickRate??20;this.options={gameId:e.gameId,netVersion:e.netVersion,lobbyServer:e.lobbyServer,mode:e.mode??"lockstep",tickRate:t,hashEveryTicks:e.hashEveryTicks??t,displayName:(e.displayName??"").trim(),relayOnly:e.relayOnly??!1,readyTimeoutMs:e.readyTimeoutMs??Us,peerTimeoutMs:e.peerTimeoutMs??0};let s=e.advanced??{},n=l=>s[l]!==void 0?s[l]:e[l];this.socketFactory=n("socketFactory")??st,this.linkFactory=n("linkFactory")??es,this.clock=n("clock")??Mt,this.wall=n("wallClock")??(()=>Date.now());let i=n("voiceCodec");this.voiceCodec=i!==void 0?i:rt()?Qt:null;let o=n("createMic");this.createMic=o!==void 0?o:hr()?()=>new Pe:null;let a=n("createSink");this.createSink=a!==void 0?a:typeof AudioContext<"u"?()=>new Ee:null,this.nostrConnectFn=s.nostrConnect,this.storage=e.rememberSeat===!1?null:s.storage!==void 0?s.storage:dr()}get seatKey(){return`onelobby.seat.${this.options.gameId}`}loadSeat(){if(!this.storage)return null;try{let e=this.storage.getItem(this.seatKey);if(!e)return null;let t=JSON.parse(e);return typeof t.code!="string"||typeof t.token!="string"||t.server!==this.options.lobbyServer?null:t}catch{return null}}saveSeat(e,t){if(!(!this.storage||!t))try{this.storage.setItem(this.seatKey,JSON.stringify({code:e,token:t,server:this.options.lobbyServer}))}catch{}}clearSeat(e){if(this.storage)try{if(e!==void 0){let t=this.loadSeat();if(t&&t.code!==e)return}this.storage.removeItem(this.seatKey)}catch{}}get rememberedCode(){return this.loadSeat()?.code??null}async resume(){let e=this.loadSeat();if(!e||Hs(e.token))return null;try{return await this.rejoin(e.token)}catch(t){if(t instanceof p&&t.code==="invalid_token")return this.clearSeat(),null;throw t}}helloMsg(e){return{t:"hello",proto:$t,gameId:this.options.gameId,netVersion:this.options.netVersion,client:{lib:Ns,ver:Fs,platform:typeof navigator<"u"?"browser":"node"},name:this.options.displayName.slice(0,24),...e?{resumeToken:e}:{}}}async connect(e=this.options.lobbyServer,t=this.socketFactory,s=0){let n=await this.open(e,t,s);try{let i=await n.request(this.helloMsg(),"welcome");return{sig:n,welcome:i}}catch(i){throw n.close(),i}}async open(e,t,s){if(!e.trim()&&t===st)throw new p("no_server","no lobby server address (set hosting.lobbyServer or pass lobbyServer)");let n=new be(e,t,this.clock);if(s>0){let i=()=>{},o=new Promise((a,l)=>{i=this.clock.setTimeout(()=>l(new p("direct_unreachable",`no answer from ${e} within ${s} ms`)),s)});try{await Promise.race([n.connect(),o])}catch(a){throw n.close(),a instanceof p&&a.code==="connect_failed"?new p("direct_unreachable",`no answer from ${e}`):a}finally{i()}}else await n.connect();return n}makeLobby(e,t,s,n,i){let o=new re({signaling:e,state:s,ice:t.ice??[],links:n?.links??this.linkFactory,direct:!!n,clock:this.clock,wall:this.wall,hashEveryTicks:this.options.hashEveryTicks,relayOnly:s.relayOnly??this.options.relayOnly,displayName:this.options.displayName,voice:{codec:this.voiceCodec,createSource:this.createMic,createSink:this.createSink},readyTimeoutMs:this.options.readyTimeoutMs,peerTimeoutMs:this.options.peerTimeoutMs,rateLimit:i});return o.errorSink=a=>this.emit("error",a),this.blueprint&&mr(o,this.blueprint),this.storage&&!n&&(xt.add(o),this.saveSeat(o.code,o.resumeToken),o.seatSink=a=>this.saveSeat(o.code,a),o.on("sessionEnded",()=>{xt.delete(o),this.clearSeat(o.code)})),o}async host(e){let t={name:"Game",maxPlayers:4,...this.blueprintHostDefaults(),...pr(e)},{sig:s,welcome:n}=await this.connect();try{let i=await s.request({t:"create",name:t.name,maxPlayers:t.maxPlayers,visibility:t.visibility??"unlisted",mode:this.options.mode,tickRate:this.options.tickRate,lateJoin:t.lateJoin??!0,meta:t.meta??{},...this.options.relayOnly?{relayOnly:!0}:{}},"lobby");return this.makeLobby(s,n,i,void 0,t.rateLimit)}catch(i){throw s.close(),i}}async join(e){let t=I(e);if(!t)throw new p("bad_code",`"${e}" is not a valid join code`);let s=this.loadSeat();if(s&&s.code===t&&!Hs(s.token))try{return await this.rejoin(s.token)}catch(o){if(this.clearSeat(),!(o instanceof p&&o.code==="invalid_token"))throw o}let{sig:n,welcome:i}=await this.connect();try{let o=await n.request({t:"join",code:t},"lobby");return this.makeLobby(n,i,o)}catch(o){throw n.close(),o}}async joinDirect(e,t={}){let s=this.directTarget(e,t),{sig:n,welcome:i}=await this.connectDirect(s,t);try{let o=await n.request({t:"join",...t.password?{password:t.password}:{}},"lobby"),a=this.makeLobby(n,this.withStun(i,s,t),o,{links:s.links});return a.joinCode=s.joinedWith,a}catch(o){throw n.close(),o}}async rejoinDirect(e,t,s={}){let n=this.directTarget(e,s),i=await this.resumeAt(t,n.url,n.nostr?0:s.connectTimeoutMs??5e3,{links:n.links},n.nostr?this.nostrFactory(n,s):void 0,o=>this.withStun(o,n,s));return i.joinCode=n.joinedWith,i}nostrFactory(e,t){let s=t.nostrRelays??(this.blueprint?[...this.blueprint.hosting.nostrRelays]:void 0);return()=>new Ie({code:e.nostr,relays:s&&s.length>0?s:void 0,connect:this.nostrConnectFn,clock:this.clock,wall:this.wall})}async connectDirect(e,t){if(!e.nostr)return this.connect(e.url,this.socketFactory,t.connectTimeoutMs??5e3);let s=null,n=this.nostrFactory(e,t);try{return await this.connect(e.url,i=>s=n(i),0)}catch(i){let o=s?.failure;throw i instanceof p&&i.code==="connect_failed"?new p("direct_unreachable",o||"no answer through the Nostr relays"):i}}withStun(e,t,s){if(!t.nostr||e.ice&&e.ice.length>0)return e;let n=s.stun??[...ce];return n.length>0?{...e,ice:[{urls:n}]}:e}directTarget(e,t){let s=e.trim();if(lt(s)){let a=at(s);if("error"in a)throw new p(a.error,a.error==="unknown_version"?`"${s}" is a join code from a newer version of the game`:`"${s}" is not a valid join code: check for a typo`);if("room"in a){let l=it(a.room);return{nostr:l,url:`nostr:${l}`,joinedWith:l,links:this.linkFactory}}s=`${a.ip}:${a.port}`}let n=fs(s,t.defaultPort??47810);if(!n)throw new p("bad_address",`"${e}" is not a join code or an address (try 6B01-RGFE-P2B or 192.168.1.20:47810)`);let i=this.linkFactory,o={create:a=>i.create({...a,iceServers:[],extraRemoteCandidates:[n.candidate]})};return{nostr:null,url:n.url,joinedWith:ur(n),links:o}}nostrHostEnv(){return{connect:this.nostrConnectFn,clock:this.clock,wall:this.wall}}async hostDirectWith(e,t){let{sig:s,welcome:n}=await this.connect("onelobby-direct://host/",t.socketFactory);try{let i=await s.request({t:"create",name:e.name,maxPlayers:e.maxPlayers,visibility:"unlisted",mode:this.options.mode,tickRate:this.options.tickRate,lateJoin:e.lateJoin??!0,meta:e.meta??{}},"lobby");return this.makeLobby(s,n,i,{links:t.wrapLinks(this.linkFactory)},e.rateLimit)}catch(i){throw s.close(),i}}hostNostr(e={}){return Os(this,e)}async rejoin(e){return this.resumeAt(e,this.options.lobbyServer,0)}async resumeAt(e,t,s,n,i,o){let a=await this.open(t,i??this.socketFactory,s);try{let l=a.expect("lobby"),c=a.request(this.helloMsg(e),"welcome");l.catch(()=>{});let h=o?o(await c):await c;if(h.peerId===void 0)throw new p("invalid_token","resume token unknown or expired");let d=await l;return this.makeLobby(a,h,d,n)}catch(l){throw a.close(),l}}async list(e={}){if(this.blueprint&&!this.blueprint.hosting.server)throw new p("listing_disabled","this game has no lobby server (hosting.server is false)");let{sig:t}=await this.connect();try{return(await t.request({t:"list",visibility:"public",meta:e.meta??{},limit:Math.min(100,e.limit??50),...e.cursor?{cursor:e.cursor}:{}},"lobbies")).items??[]}finally{t.close()}}};function ur(r){if(r.isIPv6)return r.display;try{return ns(r.host,r.port)}catch{return r.display}}function pr(r){let e={};if(r)for(let[t,s]of Object.entries(r))s!==void 0&&(e[t]=s);return e}function mr(r,e){if(r.voice.mode=e.chat.voiceMode,!e.chat.voice){let t=r.voice,s=Object.getOwnPropertyDescriptor(Object.getPrototypeOf(t),"enabled");s?.get&&s.set&&Object.defineProperty(t,"enabled",{configurable:!0,get:()=>s.get.call(t),set:n=>{if(n)throw new p("voice_disabled","this game has voice chat turned off (chat.voice is false)");s.set.call(t,n)}})}r.pause.mode=e.session.pause,e.chat.text||(r.chat.send=()=>{throw new p("chat_disabled","this game has text chat turned off (chat.text is false)")})}var fr={UnknownCode:"unknown_code",BadCode:"bad_code",LobbyFull:"lobby_full",VersionMismatch:"version_mismatch",InGame:"in_game",Kicked:"kicked",BadPassword:"bad_password",NotHost:"not_host",RateLimited:"rate_limited",ServerFull:"server_full",GameNotAllowed:"game_not_allowed",BadRequest:"bad_request",ServerClosed:"server_closed",IceFailed:"ice_failed",Timeout:"timeout",DirectUnreachable:"direct_unreachable",BadAddress:"bad_address",PortInUse:"port_in_use",ConnectFailed:"connect_failed",Closed:"closed",ServerLost:"server_lost",InvalidToken:"invalid_token",NoServer:"no_server",NotInGame:"not_in_game",NotAllowed:"not_allowed",BadConfig:"bad_config",VoiceDisabled:"voice_disabled",ChatDisabled:"chat_disabled",PauseDisabled:"pause_disabled",ListingDisabled:"listing_disabled",NostrDisabled:"nostr_disabled",IdentityAnonymous:"identity_anonymous"},Be={unknown_code:{message:"We couldn't find a lobby with that code.",hint:"Check the code with the host. Codes stop working once everyone leaves."},bad_code:{message:"That doesn't look like a lobby code.",hint:"Codes are 6 letters and numbers, like 7HQ 4K2."},lobby_full:{message:"That lobby is full.",hint:"Ask the host to make room, or start your own lobby."},version_mismatch:{message:"You and the host are running different versions of the game.",hint:"Reload the page (or update the game) on both sides, then try again."},in_game:{message:"That game has already started.",hint:"Ask the host to let people join mid-game, or wait for the next round."},kicked:{message:"The host removed you from the lobby."},denied:{message:"The host didn't let you in this time.",hint:"Ask the host to approve you, then try again."},not_host:{message:"Only the host can do that."},rate_limited:{message:"Slow down a little: too many tries in a short time.",hint:"Wait a moment, then try again."},max_lobbies:{message:"You already have the most lobbies open that this server allows.",hint:"Close one of your other lobbies, then try again."},server_full:{message:"The game server is full right now.",hint:"Try again in a few minutes."},game_not_allowed:{message:"This game server doesn't host this game.",hint:"The game's server setting may be wrong; let the game's owner know."},bad_request:{message:"The game sent something the server didn't understand.",hint:"Reload the page and try again. If it keeps happening, the game may need an update."},connect_failed:{message:"Couldn't reach the game server.",hint:"Check your internet connection, then try again."},closed:{message:"Lost the connection to the game server.",hint:"Check your internet connection, then try again."},server_lost:{message:"Lost the connection to the game server.",hint:"Your game may keep going; new players can't join until it's back."},invalid_token:{message:"Your old seat in that lobby is no longer available.",hint:"Join again with the lobby code."},ice_failed:{message:"Couldn't connect to the other players.",hint:"Some networks (school, work, public Wi-Fi) block game connections. Try another network or a phone hotspot."},no_server:{message:"This game has no lobby server address yet.",hint:"Enter one in the multiplayer settings, or host from your own computer with a join code."},timeout:{message:"The connection took too long.",hint:"Check your internet connection, then try again."},voice_disabled:{message:"Voice chat is turned off in this game."},chat_disabled:{message:"Text chat is turned off in this game."},pause_disabled:{message:"Pausing is turned off in this game."},not_in_game:{message:"You can only pause while a game is running.",hint:"Start the game first."},not_allowed:{message:"You can't do that right now.",hint:"The player who paused, or the host, can resume."},listing_disabled:{message:"This game has no public lobby list.",hint:"Join with a code or a link from the host."},direct_unreachable:{message:"Couldn't reach the host.",hint:"Check the code, and that the host is still online. A host in a browser needs its tab open and visible."},nostr_disabled:{message:"This game can't be hosted from a browser.",hint:"Host from the desktop version, or let the game's owner know."},identity_anonymous:{message:"This game doesn't use player names.",hint:"Everyone shows up as Player 1, Player 2, and so on."},bad_config:{message:"The game's multiplayer settings file is not valid.",hint:"Let the game's owner know; the details are in the error message."},left:{message:"You left the lobby."},unknown:{message:"Something went wrong.",hint:"Try again. If it keeps happening, reload the page."}},br=/^[a-z][a-z0-9_]*$/;function yr(r){if(typeof r=="string"){let e=/^([a-z][a-z0-9_]*)(?::|$)/.exec(r.trim());return{code:e?e[1]:null}}if(r&&typeof r=="object"){let e=r,t=e.data&&typeof e.data=="object"?e.data:void 0;if(typeof e.code=="string"&&br.test(e.code))return{code:e.code,data:t};if(typeof e.message=="string"){let s=/^([a-z][a-z0-9_]*):/.exec(e.message);if(s&&Be[s[1]])return{code:s[1],data:t};if(e.name==="NetworkError"||/network|failed to fetch|websocket/i.test(e.message))return{code:"connect_failed"};if(e.name==="TimeoutError"||/timed? ?out/i.test(e.message))return{code:"timeout"}}}return{code:null}}function _t(r){let{code:e,data:t}=yr(r);if(!e)return{code:"unknown",...Be.unknown};let s=e;e==="kicked"&&t?.reason==="denied"&&(s="denied"),e==="rate_limited"&&t?.reason==="max_lobbies"&&(s="max_lobbies");let n=Be[s];if(!n)return{code:e,...Be.unknown};let i={code:e,message:n.message};if(n.hint&&(i.hint=n.hint),e==="rate_limited"&&typeof t?.retryAfterMs=="number"&&s==="rate_limited"){let o=Math.max(1,Math.ceil(t.retryAfterMs/1e3));i.hint=`Wait ${o} second${o===1?"":"s"}, then try again.`}return i}function Pt(){try{return typeof location<"u"?location.href:void 0}catch{return}}function Et(r,e,t="join"){let s=I(r);if(!s)throw new Error(`"${r}" is not a valid lobby code`);let n=e??Pt();if(!n)throw new Error("shareUrl needs a base URL outside the browser");let i=new URL(n);return i.searchParams.delete(t),i.searchParams.set(t,$(s).replace(" ","-")),i.toString()}async function gr(r){let e=r??Pt();if(!e)return null;try{let t=await fetch(new URL("/joininfo",e),{cache:"no-store"});return t.ok?await t.json():null}catch{return null}}function Ct(r,e="join"){let t=r??Pt();if(!t)return null;let s;try{s=new URL(t,"http://localhost/")}catch{return null}let n=s.searchParams.get(e)??new URLSearchParams(s.hash.replace(/^#/,"")).get(e);return n?I(n):null}var vr=J,kr=p;var wr=new TextEncoder,Sr=new TextDecoder,Je=r=>wr.encode(JSON.stringify(r)),$e=class{constructor(e){this.c=e}c;values=new Map;get(e){return this.values.get(e)}set(e,t){this.c._sharedOp({k:"set",key:String(e),v:t===void 0?null:t})}add(e,t=1){this.c._sharedOp({k:"add",key:String(e),n:Number(t)||0})}all(){return Object.fromEntries(this.values)}keys(){return[...this.values.keys()]}},de=class{constructor(e){this.opts=e}opts;state="idle";lobby=null;error=null;linking=!1;name="";shared=new $e(this);onChange=()=>{};onEvent=()=>{};lastAction=null;unsub=[];gen=0;seq=0;get isHost(){return!!this.lobby&&this.lobby.isHost}get code(){return this.lobby?.code??null}get players(){let e=this.lobby;if(!e)return[];let t=e.peers.map(s=>this.toPlayer(s));return t.some(s=>s.isMe)||t.push({id:e.localPeerId,name:this.name||`Player ${e.localPeerId}`,isHost:e.isHost,isMe:!0}),t.sort((s,n)=>s.id-n.id)}get statusText(){switch(this.state){case"idle":return"Host a game, or type a code to join one.";case"connecting":return this.lastAction?.kind==="join"?"Joining\u2026":this.lastAction?.kind==="resume"?"Getting your seat back\u2026":"Creating a game\u2026";case"error":return this.error?.message??"Something went wrong.";case"lobby":{if(this.linking)return`Connecting to ${this.hostName}\u2026`;if(this.isHost){let e=this.players.length;return e<=1?"Waiting for players\u2026 Share the code with your friends.":`${e} players here. Press Start when everyone's in.`}return`Connected! Waiting for ${this.hostName} to start.`}case"playing":return this.linking?`Connecting to ${this.hostName}\u2026`:"Game on!"}}get hostName(){let e=this.lobby;return e&&e.peer(e.hostPeerId)?.name||"the host"}toPlayer(e,t){let s=this.lobby,n=e?.peerId??t??0;return{id:n,name:e?.name||n===s.localPeerId&&this.name||`Player ${n}`,isHost:n===s.hostPeerId,isMe:n===s.localPeerId}}host(e){return this.lastAction={kind:"host"},this.run(e,t=>t.host({name:this.opts.lobbyName??"game",maxPlayers:typeof this.opts.maxPlayers=="function"?this.opts.maxPlayers():this.opts.maxPlayers,visibility:"unlisted",lateJoin:!0}))}join(e,t){let s=I(e);return this.lastAction={kind:"join",code:e},s?this.run(t,n=>n.join(s)):(this.fail(new p("bad_code")),Promise.resolve())}async resume(e){let t=this.opts.makeKit(e);return!t.resume||!t.rememberedCode?!1:(this.lastAction={kind:"resume"},await this.run(e,()=>t.resume(),!0),!!this.lobby)}retry(){let e=this.lastAction;return!e||e.kind==="resume"?(this.reset(),Promise.resolve()):e.kind==="host"?this.host(this.name):this.join(e.code,this.name)}async start(){!this.lobby||!this.isHost||this.state==="playing"||await this.lobby.start()}async leave(){let e=this.lobby;this.detach(),await e?.leave(),this.reset()}reset(){this.detach(),this.state="idle",this.error=null,this.onChange()}send(e){this.lobby&&this.lobby.broadcast(Je({k:"m",d:e===void 0?null:e}))}async run(e,t,s=!1){this.detach();let n=++this.gen;this.name=(e||"").trim().slice(0,24),this.state="connecting",this.error=null,this.onChange();let i;try{i=await t(this.opts.makeKit(this.name))}catch(o){if(n!==this.gen)return;s?this.reset():this.fail(o);return}if(n!==this.gen){i?.leave();return}i?this.attach(i):this.reset()}attach(e){this.lobby=e,e.isHost||this.shared.values.clear(),this.seq=0,this.state=e.phase==="in_game"?"playing":"lobby",this.linking=!e.isHost;let t=this.unsub;t.push(e.on("ready",()=>this.linked())),t.push(e.on("peerJoined",s=>{this.onEvent("player-joined",{player:this.toPlayer(s)}),this.onChange()})),t.push(e.on("peerLeft",(s,n)=>{this.onEvent("player-left",{player:this.toPlayer(s),reason:n}),this.onChange()})),t.push(e.on("peerReady",s=>{e.isHost&&s.peerId!==e.localPeerId&&e.send(s.peerId,Je({k:"SS",all:this.shared.all(),q:this.seq}))})),t.push(e.on("hostChanged",()=>this.onChange())),t.push(e.on("phase",s=>{s==="in_game"&&this.state!=="playing"&&(this.state="playing",this.onChange(),this.onEvent("start",{}))})),t.push(e.on("message",(s,n)=>this.onRaw(s,n))),t.push(e.on("sessionEnded",s=>{this.lobby===e&&s!=="left"&&this.fail(new p(s))})),e.ready.then(()=>{this.lobby===e&&this.linked()},()=>{}),this.onChange(),this.state==="playing"&&!this.linking&&this.onEvent("start",{})}linked(){this.linking&&(this.linking=!1,this.onChange(),this.state==="playing"&&this.onEvent("start",{}))}detach(){this.gen++;for(let e of this.unsub.splice(0))e();this.lobby=null,this.linking=!1}fail(e){let t=this.lobby;this.detach(),t&&t.leave(),this.state="error",this.error=_t(e),this.onChange(),this.onEvent("error",this.error)}playerById(e){let t=this.lobby;return!t||!e?null:this.toPlayer(t.peer(e),e)}onRaw(e,t){let s;try{s=JSON.parse(Sr.decode(t))}catch{return}if(!s||typeof s!="object")return;let n=this.lobby;switch(s.k){case"m":this.onEvent("message",{from:this.playerById(e),data:s.d});break;case"set":case"add":n.isHost&&this.applyHost(s,e);break;case"S":!n.isHost&&e===n.hostPeerId&&!(s.q<=this.seq)&&(this.seq=s.q,this.applyValue(s.key,s.v,s.by));break;case"SS":if(!n.isHost&&e===n.hostPeerId&&s.all&&typeof s.all=="object"&&!(s.q<this.seq)){this.seq=s.q;for(let i of[...this.shared.values.keys()])i in s.all||this.applyValue(i,null,0);for(let[i,o]of Object.entries(s.all))this.applyValue(i,o,0)}break}}_sharedOp(e){let t=this.lobby;if(!t){let s=e.k==="set"?e.v:(Number(this.shared.values.get(e.key))||0)+e.n;this.applyValue(e.key,s,0);return}t.isHost?this.applyHost(e,t.localPeerId):t.send(t.hostPeerId,Je(e))}applyHost(e,t){let s=this.lobby;if(typeof e.key!="string")return;let n=e.k==="set"?e.v:(Number(this.shared.values.get(e.key))||0)+(Number(e.n)||0);this.applyValue(e.key,n,t),s.broadcast(Je({k:"S",key:e.key,v:n,by:t,q:++this.seq}))}applyValue(e,t,s){t===null?this.shared.values.delete(e):this.shared.values.set(e,t),this.onEvent("shared-change",{key:e,value:t,from:this.playerById(s)})}};var js=`
|
|
21
27
|
:host {
|
|
22
28
|
--onelobby-bg: #ffffff;
|
|
23
29
|
--onelobby-fg: #1b1f24;
|
|
@@ -84,7 +90,7 @@ li { display: flex; gap: 0.5rem; align-items: center; }
|
|
|
84
90
|
.tag { font-size: 0.75rem; border: 1px solid var(--onelobby-border); border-radius: 999px; padding: 0 0.5rem; color: var(--onelobby-muted); }
|
|
85
91
|
.bar { display: flex; gap: 0.5rem; flex-wrap: wrap; align-items: center; justify-content: space-between; }
|
|
86
92
|
.sr { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
|
|
87
|
-
`;function
|
|
93
|
+
`;function Ve(r){let e=(r??"").trim();if(!e||/\s/.test(e)||/your[-_ ]?(lobby[-_ ]?)?server|replace[-_ ]?me|<|>/i.test(e))return null;let t=e;/^https?:\/\//i.test(t)?t=t.replace(/^http/i,"ws"):/^wss?:\/\//i.test(t)||(t="wss://"+t);let s;try{s=new URL(t)}catch{return null}let n=s.hostname.toLowerCase();return!n||/(^|\.)example\.(com|net|org)$/.test(n)||/\.example$/.test(n)||n==="example"||!n.includes(".")&&!n.includes(":")&&n!=="localhost"?null:t}var qs="one-lobby",Ds="onelobby-name",xr=`
|
|
88
94
|
<div class="card" part="card">
|
|
89
95
|
<p class="status" id="status" role="status" aria-live="polite"></p>
|
|
90
96
|
<p class="hint" id="hint" hidden></p>
|
|
@@ -136,5 +142,5 @@ li { display: flex; gap: 0.5rem; align-items: center; }
|
|
|
136
142
|
</div>
|
|
137
143
|
</div>
|
|
138
144
|
</div>
|
|
139
|
-
`;function
|
|
145
|
+
`;function _r(){try{return localStorage.getItem(Ds)??""}catch{return""}}function Pr(r){try{localStorage.setItem(Ds,r)}catch{}}async function Er(r){try{return await navigator.clipboard.writeText(r),!0}catch{let e=document.createElement("textarea");e.value=r,e.style.position="fixed",e.style.opacity="0",document.body.appendChild(e),e.select();let t=!1;try{t=document.execCommand("copy")}catch{t=!1}return e.remove(),t}}function Cr(){class r extends HTMLElement{static observedAttributes=["name","theme"];_controller;root;$=t=>this.root.getElementById(t);autoJoined=!1;copiedTimer;constructor(){super(),this.root=this.attachShadow({mode:"open"}),this.root.innerHTML=`<style>${js}</style>${xr}`,this._controller=new de({makeKit:t=>this.makeKit(t),maxPlayers:()=>this.maxPlayers}),this._controller.onChange=()=>this.render(),this._controller.onEvent=(t,s)=>this.dispatchEvent(new CustomEvent(t,{detail:s,bubbles:!0,composed:!0})),this.wire()}get isHost(){return this._controller.isHost}get players(){return this._controller.players}get code(){return this._controller.code}get state(){return this._controller.state}get shared(){return this._controller.shared}send(t){this._controller.send(t)}host(){return this._controller.host(this.playerName())}join(t){return this._controller.join(t,this.playerName())}start(){return this._controller.start()}leave(){return this._controller.leave()}connectedCallback(){let t=this.getAttribute("name"),s=this.$("name");if(s.value||(s.value=t??_r()),this.$("nameLabel").hidden=!!t,this.render(),!this.autoJoined){this.autoJoined=!0;let n=Ct();n&&this._controller.state==="idle"?(this.$("code").value=$(n),s.value.trim()?this.join(n):(this.$("status").textContent="You're invited! Type your name, then press Join.",s.focus())):!n&&this._controller.state==="idle"&&Ve(this.getAttribute("server"))&&this._controller.resume(s.value.trim())}}disconnectedCallback(){this._controller.lobby&&this._controller.leave()}attributeChangedCallback(t){if(t==="name"&&this.isConnected){let s=this.getAttribute("name");s&&(this.$("name").value=s),this.$("nameLabel").hidden=!!s}}get maxPlayers(){let t=parseInt(this.getAttribute("max-players")??"",10);return Number.isFinite(t)&&t>=2?Math.min(t,250):8}playerName(){let t=this.$("name").value.trim()||this.getAttribute("name")||"";return!this.getAttribute("name")&&t&&Pr(t),t}makeKit(t){let s=Ve(this.getAttribute("server"));if(!s)throw new p("no_server","the <one-lobby> tag has no usable server address");return new J({gameId:this.getAttribute("game")||"onelobby-drop-in",netVersion:this.getAttribute("version")||"1",lobbyServer:s,mode:"raw",displayName:t,readyTimeoutMs:15e3,advanced:{createMic:null,createSink:null,voiceCodec:null}})}wire(){let t=(s,n,i)=>this.$(s).addEventListener(n,i);t("host","click",()=>{this.host()}),t("joinForm","submit",s=>{s.preventDefault(),this.join(this.$("code").value)}),t("cancel","click",()=>this._controller.reset()),t("start","click",()=>{this.start()}),t("leave","click",()=>{this.leave()}),t("retry","click",()=>{this._controller.retry()}),t("back","click",()=>this._controller.reset()),t("copyCode","click",()=>{this.copy(this.code??"","copyCode","Copied!")}),t("copyLink","click",()=>{this.copy(this.code?Et(this.code):"","copyLink","Link copied!")})}async copy(t,s,n){if(!t)return;let i=await Er(t),o=this.$(s),a=s==="copyCode"?"Copy code":"Copy invite link";o.textContent=i?n:"Couldn't copy, select it by hand",this.$("copied").textContent=i?n:"",clearTimeout(this.copiedTimer),this.copiedTimer=setTimeout(()=>{o.textContent=a,this.$("copied").textContent=""},2e3)}render(){let t=this._controller,s=t.state,n=this.$("status");n.textContent=s==="error"&&t.error?.code==="no_server"?"This page is missing its lobby server address.":t.statusText,n.classList.toggle("error",s==="error");let i=this.$("hint"),o=s==="error"?t.error?.code==="no_server"?'Put your lobby server address in server="\u2026" on the <one-lobby> tag (it looks like https://your-name.onrender.com). The guide shows how to get one.':t.error?.hint??"":"";if(i.textContent=o,i.hidden=!o,this.$("v-idle").hidden=s!=="idle",this.$("v-busy").hidden=s!=="connecting",this.$("v-lobby").hidden=s!=="lobby"&&s!=="playing",this.$("v-error").hidden=s!=="error",this.setAttribute("state",s),s==="lobby"||s==="playing"){this.$("codeText").textContent=$(t.code??""),this.$("players").replaceChildren(...t.players.map(c=>{let h=document.createElement("li"),d=document.createElement("span");return d.textContent=c.name,h.append(d),c.isHost&&h.append(Object.assign(document.createElement("span"),{className:"tag",textContent:"host"})),c.isMe&&h.append(Object.assign(document.createElement("span"),{className:"tag",textContent:"you"})),h}));let l=this.$("start");l.hidden=!t.isHost||s==="playing",l.disabled=t.players.length<2,l.title=l.disabled?"Wait for at least one more player":""}}}return r}function Tt(r=qs){typeof customElements>"u"||customElements.get(r)||customElements.define(r,Cr())}Tt();export{Y as BlueprintError,Xe as CHAT_MAX_BYTES,Q as Chat,D as DEFAULT_NOSTR_RELAYS,ce as DEFAULT_NOSTR_STUN,Us as DEFAULT_READY_TIMEOUT_MS,fr as ErrorCodes,vr as Keepalive,kr as KeepaliveError,Ns as LIB_NAME,Fs as LIB_VERSION,qs as LOBBY_TAG,re as Lobby,de as LobbyController,te as Lockstep,Zt as MIN_PEER_TIMEOUT_MS,J as OneLobby,p as OneLobbyError,se as Pause,$e as SharedState,ne as Voice,Nn as checkNostrRelays,we as closeCodeFor,Ct as codeFromUrl,at as decodeJoinCode,Bn as defineBlueprint,Tt as defineLobbyElement,_t as describeError,gr as directJoinInfo,ot as encodeRoomCode,$ as formatCode,Ve as lobbyServerUrl,lt as looksLikeJoinCode,I as normalizeCode,yt as parseBlueprint,Et as shareUrl,rt as webCodecsOpusAvailable,xn as webRtcLinkFactoryFor};
|
|
140
146
|
//# sourceMappingURL=onelobby.bundle.mjs.map
|