onelobby 0.2.0 → 0.2.2
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/dist/direct/hub-host.js +23 -0
- package/dist/direct/hub-host.js.map +1 -1
- package/dist/direct/hub.js +23 -5
- package/dist/direct/hub.js.map +1 -1
- package/dist/direct/index.d.ts +8 -1
- package/dist/direct/index.js +17 -8
- package/dist/direct/index.js.map +1 -1
- package/dist/direct/portmap.d.ts +12 -1
- package/dist/direct/portmap.js +61 -14
- package/dist/direct/portmap.js.map +1 -1
- package/dist/lobby.d.ts +43 -4
- package/dist/lobby.js +98 -21
- package/dist/lobby.js.map +1 -1
- package/dist/onelobby.bundle.mjs +5 -5
- package/dist/onelobby.bundle.mjs.map +3 -3
- package/dist/onelobby.d.ts +1 -1
- package/dist/onelobby.global.js +186 -32
- package/dist/onelobby.global.js.map +2 -2
- package/dist/onelobby.global.min.js +5 -5
- package/dist/onelobby.global.min.js.map +3 -3
- package/dist/onelobby.js +17 -3
- package/dist/onelobby.js.map +1 -1
- package/dist/signaling.d.ts +5 -1
- package/dist/signaling.js +17 -2
- package/dist/signaling.js.map +1 -1
- package/dist/socket.d.ts +11 -5
- package/dist/socket.js +34 -10
- package/dist/socket.js.map +1 -1
- package/dist/testing.js +13 -3
- package/dist/testing.js.map +1 -1
- package/dist/webrtc.d.ts +12 -0
- package/dist/webrtc.js +30 -1
- package/dist/webrtc.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
/*! onelobby 0.2.
|
|
2
|
-
"use strict";var OneLobby=(()=>{var ee=Object.defineProperty;var Ge=Object.getOwnPropertyDescriptor;var Xe=Object.getOwnPropertyNames;var Ze=Object.prototype.hasOwnProperty;var Qe=(r,t)=>{for(var e in t)ee(r,e,{get:t[e],enumerable:!0})},et=(r,t,e,s)=>{if(t&&typeof t=="object"||typeof t=="function")for(let i of Xe(t))!Ze.call(r,i)&&i!==e&&ee(r,i,{get:()=>t[i],enumerable:!(s=Ge(t,i))||s.enumerable});return r};var tt=r=>et(ee({},"__esModule",{value:!0}),r);var Jt={};Qe(Jt,{CHAT_MAX_BYTES:()=>A,Chat:()=>g,DEFAULT_READY_TIMEOUT_MS:()=>me,Keepalive:()=>Ke,KeepaliveError:()=>Je,LIB_NAME:()=>ue,LIB_VERSION:()=>pe,LOBBY_TAG:()=>fe,Lobby:()=>w,LobbyController:()=>S,Lockstep:()=>k,MIN_PEER_TIMEOUT_MS:()=>le,OneLobby:()=>b,OneLobbyError:()=>d,SharedState:()=>C,Voice:()=>v,closeCodeFor:()=>T,codeFromUrl:()=>X,decodeJoinCode:()=>J,defineLobbyElement:()=>Q,describeError:()=>Y,formatCode:()=>y,lobbyServerUrl:()=>L,looksLikeJoinCode:()=>W,normalizeCode:()=>f,shareUrl:()=>G,webCodecsOpusAvailable:()=>V,webRtcLinkFactoryFor:()=>Ne});function st(r,t){if(typeof Worker>"u"||typeof Blob>"u"||typeof URL>"u"||!URL.createObjectURL)return null;try{let e="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([e],{type:"text/javascript"})),i=new Worker(s);return URL.revokeObjectURL(s),i.onmessage=()=>r(),i.postMessage({start:!0,ms:t}),()=>{i.postMessage({stop:!0}),i.terminate()}}catch{return null}}var be={now:()=>typeof performance<"u"?performance.now():Date.now(),setInterval(r,t){let e=st(r,t);if(e)return e;let s=setInterval(r,t);return()=>clearInterval(s)},setTimeout(r,t){let e=setTimeout(r,t);return()=>clearTimeout(e)}};var p=class{handlers=new Map;on(t,e){let s=this.handlers.get(t);return s||this.handlers.set(t,s=[]),s.push(e),()=>this.off(t,e)}off(t,e){let s=this.handlers.get(t);if(!s)return;let i=s.indexOf(e);i>=0&&s.splice(i,1)}emit(t,...e){let s=this.handlers.get(t);if(s)for(let i of[...s])try{i(...e)}catch(n){queueMicrotask(()=>{throw n})}}};var _="0123456789ABCDEFGHJKMNPQRSTVWXYZ";function it(r){return r.replace(/[a-z]/g,t=>t.toUpperCase())}function f(r){let t=it(r).replace(/[ \-]/g,"").replace(/[IL]/g,"1").replace(/O/g,"0");if(t.length!==6)return null;for(let e of t)if(_.indexOf(e)<0)return null;return t}function y(r){let t=f(r)??r.toUpperCase();return t.length===6?`${t.slice(0,3)} ${t.slice(3)}`:t}var te={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},ke=1,ve=2,m=class extends Error{constructor(e,s=""){super(s?`${e}: ${s}`:e);this.code=e;this.name="CodecError"}code},ye=new TextEncoder,rt=new TextDecoder("utf-8",{fatal:!0});function ge(r){try{return rt.decode(r)}catch{throw new m("bad_utf8")}}var se=class{buf;dv;pos=0;constructor(t){this.buf=new Uint8Array(t),this.dv=new DataView(this.buf.buffer)}ensure(t){if(this.pos+t<=this.buf.length)return;let e=new Uint8Array(Math.max(this.buf.length*2,this.pos+t));e.set(this.buf),this.buf=e,this.dv=new DataView(e.buffer)}u8(t){this.ensure(1),this.dv.setUint8(this.pos,t),this.pos+=1}u16(t){this.ensure(2),this.dv.setUint16(this.pos,t,!0),this.pos+=2}u32(t){this.ensure(4),this.dv.setUint32(this.pos,t>>>0,!0),this.pos+=4}i64(t){this.ensure(8),this.dv.setBigInt64(this.pos,BigInt(t),!0),this.pos+=8}u64(t){this.ensure(8),this.dv.setBigUint64(this.pos,BigInt.asUintN(64,t),!0),this.pos+=8}bytes(t){this.ensure(t.length),this.buf.set(t,this.pos),this.pos+=t.length}done(){return this.buf.slice(0,this.pos)}},ie=class{constructor(t){this.buf=t;this.dv=new DataView(t.buffer,t.byteOffset,t.byteLength)}buf;dv;pos=0;need(t){if(this.pos+t>this.buf.length)throw new m("too_short","truncated frame")}u8(){this.need(1);let t=this.dv.getUint8(this.pos);return this.pos+=1,t}u16(){this.need(2);let t=this.dv.getUint16(this.pos,!0);return this.pos+=2,t}u32(){this.need(4);let t=this.dv.getUint32(this.pos,!0);return this.pos+=4,t}i64(){this.need(8);let t=this.dv.getBigInt64(this.pos,!0);return this.pos+=8,Number(t)}u64(){this.need(8);let t=this.dv.getBigUint64(this.pos,!0);return this.pos+=8,t}bytes(t){this.need(t);let e=this.buf.slice(this.pos,this.pos+t);return this.pos+=t,e}rest(){let t=this.buf.slice(this.pos);return this.pos=this.buf.length,t}};function P(r,t=0,e=!1){let s=new se(64);switch(s.u8(te[r.type]),s.u16(t),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=ye.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=ye.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(!e&&i.length>16384)throw new m("too_long",`${i.length} > 16384`);return i}var nt=new Map(Object.keys(te).map(r=>[te[r],r]));function we(r,t=!1){let e=t?67108864:16384;if(r.length>e)throw new m("too_long",`${r.length} > ${e}`);if(r.length<3)throw new m("too_short","header");let s=new ie(r),i=nt.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:ge(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(),Ye=s.i64();c.push({peerId:h,id:u,ts:Ye,text:ge(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 m("trailing",`${r.length-s.pos} extra bytes`);return{route:n,msg:o}}var M={rate:1,length:2,mutedByHost:3};function re(r){return r===1?"rate":r===2?"length":r===3?"muted_by_host":"unknown"}var A=256,xe=3,ot=5e3,at=5,R=100,ct=/[\u0000-\u001f\u007f-\u009f\u202a-\u202e\u2066-\u2069]/g;function lt(r){return r.replace(ct,"")}function Se(r){return new TextEncoder().encode(r).length}var ne=class{buckets=new Map;refillMs=ot/at;tryTake(t,e){let s=this.buckets.get(t);return s||this.buckets.set(t,s={tokens:xe,at:e}),s.tokens=Math.min(xe,s.tokens+(e-s.at)/this.refillMs),s.at=e,s.tokens<1?!1:(s.tokens-=1,!0)}forget(t){this.buckets.delete(t)}},g=class extends p{constructor(e,s,i){super();this.hub=e;this.wall=s;this.isLocallyMuted=i}hub;wall;isLocallyMuted;filter=e=>e;lines=[];limiter=new ne;hostMuted=new Set;localId=0;stampId=0;get history(){return this.lines}send(e){let s=++this.localId>>>0;return Se(e)>A*4?(queueMicrotask(()=>this.emit("rejected",s,"length")),s):(this.hub.isHost?this.hostProcess(this.hub.localPeerId,s,e):this.hub.sendToHost({type:"chat",id:s,ts:0,text:e}),s)}hostMute(e,s){if(!this.hub.isHost)throw new Error("only the host can mute chat");s?this.hostMuted.add(e):this.hostMuted.delete(e)}isHostMuted(e){return this.hostMuted.has(e)}onPeerLeft(e){this.limiter.forget(e),this.hostMuted.delete(e)}sendHistory(e){let s=this.lines.slice(-R).map(i=>({...i}));this.hub.sendTo(e,{type:"chat_history",lines:s},0)}handle(e,s,i){switch(i.type){case"chat":this.hub.isHost?this.hostProcess(e,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(-R))this.push({...n,text:this.safeFilter(n.text)});this.emit("history",this.lines)}break;case"chat_reject":this.emit("rejected",i.id,re(i.reason));break}}hostProcess(e,s,i){let n=l=>{e===this.hub.localPeerId?this.emit("rejected",s,re(l)):this.hub.sendTo(e,{type:"chat_reject",id:s,reason:l},0)};if(this.hostMuted.has(e))return n(M.mutedByHost);if(Se(i)>A)return n(M.length);if(!this.limiter.tryTake(e,this.hub.clock.now()))return n(M.rate);let o=lt(i);if(o.length===0)return;let a={peerId:e,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,e);this.deliver(a)}deliver(e){let s={...e,text:this.safeFilter(e.text)};this.push(s),!this.isLocallyMuted(e.peerId)&&this.emit("message",s)}push(e){this.isLocallyMuted(e.peerId)||(this.lines.push(e),this.lines.length>R&&this.lines.splice(0,this.lines.length-R))}safeFilter(e){try{return this.filter(e)}catch{return e}}};var oe=16e3;function H(r,t,e=oe){let s=Math.max(1,Math.ceil(t.length/e));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:t.subarray(n*e,Math.min(t.length,(n+1)*e))});return i}var I=class{parts=new Map;push(t){if(t.count===0||t.index>=t.count)return null;let e=this.parts.get(t.id);if((!e||e.count!==t.count)&&(e={count:t.count,got:0,parts:new Array(t.count),bytes:0},this.parts.set(t.id,e)),e.parts[t.index])return null;if(e.parts[t.index]=t.bytes,e.got++,e.bytes+=t.bytes.length,e.bytes>67108864)throw this.parts.delete(t.id),new m("too_long",`chunk set over ${67108864} bytes`);if(e.got<e.count)return null;this.parts.delete(t.id);let s=new Uint8Array(e.bytes),i=0;for(let n of e.parts)s.set(n,i),i+=n.length;return s}get pending(){return this.parts.size}clear(){this.parts.clear()}};function Ee(){return typeof CompressionStream<"u"&&typeof DecompressionStream<"u"}async function Te(r,t){let e=t.writable.getWriter();e.write(r).then(()=>e.close());let s=[],i=t.readable.getReader(),n=0;for(;;){let{done:c,value:l}=await i.read();if(c)break;s.push(l),n+=l.length}let o=new Uint8Array(n),a=0;for(let c of s)o.set(c,a),a+=c.length;return o}function Ce(r){return Te(r,new CompressionStream("deflate"))}function Le(r){return Te(r,new DecompressionStream("deflate"))}var dt=3,ut=3,pt=3e3,mt=1e4,ft=1024,k=class extends p{constructor(e,s){super();this.hub=e;this.hashEveryTicks=Math.max(1,s.hashEveryTicks),this.historyMax=s.historyFrames??Math.max(60,e.tickRate*30),this.lastFrameRx=e.clock.now()}hub;snapshot=null;restore=null;hashEveryTicks;chunkSize=oe;historyMax;queue=[];history=[];lastFrameTick=-1;consumedTick=0;cmdSeq=1;awaitingSnapshot=!1;lastFrameRx=0;lastSnapshotReq=-1/0;restoreFailures=0;hosting=!1;_started=!1;pending=[];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 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(e){this.hosting?this.pending.push({peerId:this.hub.localPeerId,bytes:e.slice()}):this.hub.sendToHost({type:"cmd",seq:this.cmdSeq++>>>0,bytes:e})}nextFrame(){let e=this.queue.shift()??null;return e&&(this.consumedTick=e.tick),e}reportHash(e,s){if(!(this.awaitingSnapshot||e<=0||e%this.hashEveryTicks!==0))if(this.hosting){this.ownHashes.set(e,s),this.prune(this.ownHashes);let i=this.peerHashes.get(e);if(i){this.peerHashes.delete(e);for(let[n,o]of[...i].sort((a,c)=>a[0]-c[0]))this.compare(n,e,s,o)}}else this.hub.sendToHost({type:"hash",tick:e,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 e=1e3/this.hub.tickRate;this.cancelTicker=this.hub.clock.setInterval(()=>this.pump(e),Math.max(1,Math.floor(e/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(e){this.ready.delete(e),this.snapshotting.delete(e),this.desyncs.delete(e),this.lastHashRx.delete(e),this.staleUpTo.delete(e);for(let s of this.peerHashes.values())s.delete(e);this.pending=this.pending.filter(s=>s.peerId!==e)}pump(e){if(!this.hosting)return;let s=this.hub.clock.now(),i=Math.floor((s-this.baseTime+1)/e)-this.emitted;i>10&&(this.baseTime=s-this.emitted*e,i=1);for(let n=0;n<i;n++)this.emitFrame()}emitFrame(){let e=this.pending.splice(0).sort((n,o)=>n.peerId-o.peerId),s={tick:this.lastFrameTick+1,commands:e};this.emitted++,this.lastFrameTick=s.tick,this.record(s),this.queue.push(s);let i={type:"frame",tick:s.tick,commands:e};for(let n of this.ready)this.hub.sendTo(n,i,0);this.checkPeerSilence()}record(e){this.history.push(e),this.history.length>this.historyMax&&this.history.splice(0,this.history.length-this.historyMax)}checkPeerSilence(){if(this.ownHashes.size===0)return;let e=this.hub.clock.now(),s=Math.max(mt,this.hashEveryTicks/this.hub.tickRate*1e3*3);for(let i of[...this.ready]){let n=this.lastHashRx.get(i)??e;e-n>s&&this.hub.dropPeer(i,"timeout")}}handle(e,s){switch(s.type){case"cmd":this.hosting&&this.pending.push({peerId:e,bytes:s.bytes});break;case"frame":this.hosting||this.onFrame(s.tick,s.commands);break;case"hash":this.hosting&&this.onPeerHash(e,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(e);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(e){let s=this.pendingSnapshot;if(this.pendingSnapshot=null,!!s){if(e.length!==s.total){this.awaitingSnapshot=!1,this.requestSnapshot();return}this.applySnapshot(s,e)}}onFrame(e,s){if(this.lastFrameRx=this.hub.clock.now(),this.markStarted(),this.awaitingSnapshot)return;let i=Math.max(0,this.lastFrameTick)+1;if(e<i)return;if(e>i){this.requestSnapshot();return}let n={tick:e,commands:s};this.lastFrameTick=e,this.record(n),this.queue.push(n),this.queue.length>this.hub.tickRate*3&&this.requestSnapshot()}requestSnapshot(){let e=this.hub.clock.now();this.awaitingSnapshot&&e-this.lastSnapshotReq<2e3||(this.lastSnapshotReq=e,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(e,s){try{let i=s;if(e.flags&1&&(i=await Le(s)),!this.restore)throw new Error("lockstep.restore is not set; cannot apply a snapshot");this.restore(e.tick,i),this.queue=[],this.history=[],this.lastFrameTick=e.tick,this.consumedTick=e.tick,this.awaitingSnapshot=!1,this.restoreFailures=0,this.markStarted(),this.lastFrameRx=this.hub.clock.now(),this.emit("restored",e.tick)}catch(i){this.hub.reportError(i instanceof Error?i:new Error(String(i))),++this.restoreFailures<=ut?(this.awaitingSnapshot=!1,this.lastSnapshotReq=-1/0,this.requestSnapshot()):this.hub.reportError(new Error(`snapshot restore failed ${this.restoreFailures} times; giving up`))}}onPeerHi(e,s){this.lastHashRx.set(e,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(e):this.ready.add(e);return}if(s<=i&&s+1>=n){for(let o of this.history)o.tick>s&&this.hub.sendTo(e,{type:"frame",tick:o.tick,commands:o.commands},0);this.ready.add(e)}else this.sendSnapshot(e)}onPeerHash(e,s,i){if(this.lastHashRx.set(e,this.hub.clock.now()),this.isStale(e,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(e,i),this.prune(this.peerHashes);return}this.compare(e,s,n,i)}compare(e,s,i,n){if(i===n||this.isStale(e,s)||!this.ready.has(e)||this.snapshotting.has(e))return;let o=(this.desyncs.get(e)??0)+1;if(this.desyncs.set(e,o),o>=dt){this.hub.dropPeer(e,"desync");return}this.hub.sendTo(e,{type:"desync",tick:s,hostHash:i,yourHash:n},0),this.sendSnapshot(e,s)}isStale(e,s){let i=this.staleUpTo.get(e);return!!i&&s>i.from&&s<=i.to}async sendSnapshot(e,s=1/0){if(!this.snapshotting.has(e)){if(!this.snapshot){this.hub.reportError(new Error("lockstep.snapshot is not set; cannot serve a snapshot"));return}this.snapshotting.add(e),this.ready.delete(e);try{let i=this.consumedTick,n=this.snapshot(),o=n,a=0;if(n.length>=ft&&Ee()&&(o=await Ce(n),a=1),!this.snapshotting.has(e))return;this.hub.sendTo(e,{type:"snapshot",tick:i,total:o.length,flags:a},0);for(let c of H(this.hub.nextChunkId(),o,this.chunkSize))this.hub.sendTo(e,c,0);for(let c of this.history)c.tick>i&&this.hub.sendTo(e,{type:"frame",tick:c.tick,commands:c.commands},0);s!==1/0?this.staleUpTo.set(e,{from:s,to:this.lastFrameTick}):this.staleUpTo.delete(e);for(let c of this.peerHashes.values())c.delete(e);this.ready.add(e),this.lastHashRx.set(e,this.hub.clock.now())}catch(i){this.hub.reportError(i instanceof Error?i:new Error(String(i)))}finally{this.snapshotting.delete(e)}}}prune(e){if(e.size<=64)return;let s=[...e.keys()].sort((i,n)=>i-n);for(let i of s.slice(0,s.length-64))e.delete(i)}get msSinceFrame(){return this.hub.clock.now()-this.lastFrameRx}hostSilent(){return!this.hosting&&this._started&&this.msSinceFrame>pt}touch(){this.lastFrameRx=this.hub.clock.now()}hiMessage(e){return{type:"hi",proto:1,peerId:e,lastTick:this.lastFrameTick}}get resyncCount(){return this.desyncs}};function _e(r,t){let e=null;for(let s of r)s!==t&&(e===null||s<e)&&(e=s);return e}function bt(r,t){let e=r-t&65535;return e!==0&&e<32768}function Me(r,t){return r.epoch!==t.epoch?bt(r.epoch,t.epoch):r.hostId<t.hostId}var d=class extends Error{constructor(e,s,i){super(s?`${e}: ${s}`:e);this.code=e;this.data=i;this.name="OneLobbyError"}code;data},Re="lk1",yt=1e4,gt=3e4,E=class{constructor(t,e,s){this.url=t;this.factory=e;this.clock=s}url;factory;clock;socket=null;waiters=[];lastRx=0;cancelPing=null;pingN=0;closed=!1;onMessage=null;onClose=null;connect(){return new Promise((t,e)=>{let s;try{s=this.factory(this.url)}catch(i){e(new d("connect_failed",String(i)));return}this.socket=s,s.onopen=()=>{this.lastRx=this.clock.now(),this.cancelPing=this.clock.setInterval(()=>this.tick(),yt),t()},s.onerror=()=>{this.lastRx||e(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(kt(n)):a.resolve(n);return}this.onMessage?.(n)},s.onclose=()=>{this.lastRx||e(new d("connect_failed",`cannot reach ${this.url}`)),this.shutdown()}})}tick(){if(this.clock.now()-this.lastRx>gt){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 t=new d("closed","signaling connection closed");for(let e of this.waiters.splice(0))e.reject(t);this.onClose?.()}get isOpen(){return!this.closed&&this.socket!==null&&this.socket.readyState===1}send(t){this.isOpen&&this.socket.send(JSON.stringify(t))}expect(...t){return this.closed?Promise.reject(new d("closed","signaling connection closed")):new Promise((e,s)=>{this.waiters.push({types:t,resolve:e,reject:s})})}async request(t,...e){let s=this.expect(...e);return this.send(t),s}close(){this.socket?.close(1e3,"bye"),this.shutdown()}};function kt(r){return new d(String(r.code??"error"),r.msg===void 0?void 0:String(r.msg),r.data)}var O=class{constructor(t=2){this.framesPerPacket=t}framesPerPacket;seq=0;buf=[];push(t){return this.buf.push(t),this.buf.length<this.framesPerPacket?null:this.take(0)}packet(t,e){let s={seq:this.seq,flags:e,frames:t};return this.seq=this.seq+1&65535,s}endBurst(){return this.take(1)}get buffered(){return this.buf.length}take(t){let e={seq:this.seq,flags:t,frames:this.buf};return this.seq=this.seq+1&65535,this.buf=[],e}};function Ae(r){if(r.length===0)return 0;let t=0;for(let e=0;e<r.length;e++)t+=r[e]*r[e];return Math.sqrt(t/r.length)}var F=class{mode="pushToTalk";ptt=!1;vadThreshold=.02;lastLoud=-1/0;open=!1;update(t,e){let s;this.mode==="pushToTalk"?s=this.ptt:(t>=this.vadThreshold&&(this.lastLoud=e),s=e-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 ae(r,t){let e=r-t&65535;return e>=32768?e-65536:e}var U=class{constructor(t=60,e=2){this.framesPerPacket=e;this.prefillFrames=Math.max(1,Math.round(t/20))}framesPerPacket;packets=new Map;expected=null;ready=[];playing=!1;endSeq=null;waitTicks=0;prefillFrames;push(t){if(this.expected===null)this.expected=t.seq;else if(ae(t.seq,this.expected)<0)return;this.packets.set(t.seq,t),t.flags&1?this.endSeq=t.seq:this.endSeq!==null&&ae(t.seq,this.endSeq)>0&&(this.endSeq=null)}get ended(){return this.endSeq!==null}bufferedFrames(){let t=this.ready.length;for(let e of this.packets.values())t+=e.frames.length;return t}newerThanExpected(){for(let t of this.packets.keys())if(this.expected!==null&&ae(t,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 t=this.ready.shift();return t?(this.waitTicks=0,t.length===0?{kind:"plc"}:{kind:"frame",opus:t}):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 t=this.packets.get(this.expected);if(t){this.packets.delete(this.expected),this.expected=this.expected+1&65535;for(let e of t.frames)this.ready.push(e);return}if(this.newerThanExpected()){this.expected=this.expected+1&65535;for(let e=0;e<this.framesPerPacket;e++)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}},j=class{speakers=new Map;windows=new Map;burstTimeoutMs=500;accept(t,e,s,i){if(e>1024)return!1;let n=this.windows.get(t);for(n||this.windows.set(t,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(t)&&this.speakers.size>=8?!1:(n.push(i),s&1?this.speakers.delete(t):this.speakers.set(t,i),!0)}get activeSpeakers(){return this.speakers.size}forget(t){this.speakers.delete(t),this.windows.delete(t)}};var vt=300,v=class extends p{constructor(e,s){super();this.hub=e;this.deps=s}hub;deps;mode="pushToTalk";muted=!1;inputLevel=0;bitrate=24e3;_enabled=!1;gate=new F;packetizer=new O;relay=new j;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(e){e!==this._enabled&&(this._enabled=e,e?this.start():this.stop())}pushToTalk(e){this.gate.ptt=e}isMutedPeer(e){return this.peerMuted.has(e)}setMuted(e,s){s?this.peerMuted.add(e):this.peerMuted.delete(e)}setVolume(e,s){this.gains.set(e,Math.min(2,Math.max(0,s)))}volumeOf(e){return this.gains.get(e)??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},e=>this.onEncoded(e)),this.source=this.deps.createSource(),await this.source.start(e=>this.onCapturedFrame(e,this.hub.clock.now())))}catch(e){this._enabled=!1,this.stop(),this.emit("error",{message:e instanceof Error?e.message:String(e),cause:e})}}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[e,s]of this.speakers)s.decoder?.close(),s.talking&&this.emit("speaking",e,!1);this.speakers.clear()}onCapturedFrame(e,s){let i=Ae(e);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(e)):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(e,s=!1){this.sendPacket(this.packetizer.packet(e,s?1:0))}onEncoded(e){let s=this.packetizer.push(e);s&&this.sendPacket(s)}sendPacket(e){let s={type:"voice",seq:e.seq,flags:e.flags,frames:e.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(e,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=e,!this.relay.accept(e,n,i.flags,o))return;for(let c of this.hub.connectedPeers())c!==e&&this.hub.sendTo(c,i,e,"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(e,s,i){let n=this.speakers.get(e);n||(n={jitter:new U,decoder:null,talking:!1,lastHeard:i},this.speakers.set(e,n)),n.lastHeard=i,n.jitter.push(s),!n.talking&&s.frames.length>0&&(n.talking=!0,this.emit("speaking",e,!0))}playoutTick(){let e=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&&e-i.lastHeard>vt+100&&!i.jitter.active&&(i.talking=!1,this.emit("speaking",s,!1))}}onPeerLeft(e){this.relay.forget(e);let s=this.speakers.get(e);s&&(s.decoder?.close(),s.talking&&this.emit("speaking",e,!1),this.speakers.delete(e))}close(){this._enabled=!1,this.stop(),this.endTimer?.()}static END=1};function T(r){return r==="left"?1e3:r==="host_left"||r==="closed"?1001:r==="kicked"?4e3:1006}var wt=new TextEncoder,xt=new TextDecoder,N=class{constructor(t,e,s,i){this.peerId=t;this.out=e;this.binary=s;this.onLocalClose=i}peerId;out;binary;onLocalClose;readyState=0;bufferedAmount=0;onopen=null;onmessage=null;onclose=null;onerror=null;detach=()=>{};send(t){this.readyState===1&&this.out(typeof t=="string"?wt.encode(t):t)}close(t=1e3,e="left"){this.readyState!==3&&(this.closed(t,e),this.onLocalClose())}open(t=!1){this.readyState===0&&(this.readyState=1,t?queueMicrotask(()=>this.readyState===1&&this.onopen?.({type:"open"})):this.onopen?.({type:"open"}))}deliver(t){this.readyState===1&&this.onmessage?.({type:"message",data:this.binary?t:xt.decode(t)})}closed(t,e){this.readyState!==3&&(this.readyState=3,this.detach(),this.onclose?.({type:"close",code:t,reason:e,wasClean:t===1e3||t===1001}))}};function He(r,t={}){if(r.isHost)throw new Error("asWebSocket() is for joiners; the host uses lobby.onConnection()");let e=t.channel??"reliable",s=new N(r.hostPeerId,n=>r.send(r.hostPeerId,n,{channel:e}),!!t.binary,()=>{r.leave()}),i=[r.on("message",(n,o)=>{n===r.hostPeerId&&s.deliver(o)}),r.on("sessionEnded",n=>s.closed(T(n),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 Oe(r,t,e={}){if(!r.isHost)throw new Error("onConnection() is for the host; a joiner uses lobby.asWebSocket()");let s=e.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 N(c,u=>r.send(c,u,{channel:s}),!!e.binary,()=>{});let h=l;return h.detach=()=>{i.get(c)===h&&i.delete(c)},i.set(c,h),h.open(),t(h),h},a=[r.on("peerReady",c=>{o(c.peerId)}),r.on("message",(c,l)=>o(c)?.deliver(l)),r.on("peerLeft",(c,l)=>i.get(c.peerId)?.closed(l==="left"?1001:T(l),l)),r.on("sessionEnded",c=>{for(let l of[...i.values()])l.closed(c==="left"?1001:T(c),c);a.forEach(l=>l())})];for(let c of r.connectedPeers)o(c);return()=>{n=!0}}var le=6e3;function D(r){let t=typeof r.name=="string"?r.name.trim():"";return t&&t!=="Player"?r:{...r,name:`Player ${r.peerId}`}}function Pt(r){return r.some(t=>(Array.isArray(t.urls)?t.urls:[t.urls]).some(e=>/^turns?:/i.test(String(e))))}var Et=250,Tt=2e3,w=class extends p{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;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;rate=new Map;ready=new Promise((t,e)=>{this.markReady=()=>{this.isReady||(this.isReady=!0,this.cancelReadyTimer?.(),this.cancelReadyTimer=null,t())},this.failReady=e});constructor(t){super(),this.cfg=t,this.signaling=t.signaling;let e=t.state;this.code=e.code,this.mode=e.mode,this.tickRate=e.tickRate,this.localPeerId=e.you,this.hostPeerId=e.hostId,this.phase=e.phase,this.meta={...e.meta},this.resumeToken=e.resumeToken;for(let i of e.peers)this.roster.set(i.peerId,D(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 k(this.hub,{hashEveryTicks:t.hashEveryTicks}),this.chat=new g(this.hub,t.wall,i=>this.voice.isMutedPeer(i)),this.voice=new v(this.hub,t.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=t.clock.setInterval(()=>this.watchdog(),Et),this.cancelPing=t.clock.setInterval(()=>this.pingHost(),Tt)}get isHost(){return this.localPeerId===this.hostPeerId}get peers(){return[...this.roster.values()].sort((t,e)=>t.peerId-e.peerId)}peer(t){return this.roster.get(t)}get connectedPeers(){return this.connectedPeerIds()}asWebSocket(t){return He(this,t)}onConnection(t,e){return Oe(this,t,e)}set snapshotChunkSize(t){this.lockstep.chunkSize=t}get hostEpoch(){return this.epoch}_offer(t){return this.offerTo(t),this.links.get(t)}_answer(t){return this.makeLink(t)}_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(t){if(!this.isHost)throw new Error("only the host can set metadata");this.meta={...t},this.signaling.send({t:"meta",meta:t}),this.emit("metaChanged",this.meta)}async kick(t){if(!this.isHost)throw new Error("only the host can kick");this.signaling.send({t:"kick",peerId:t}),this.removePeer(t,"kicked")}async approve(t,e){if(!this.isHost)throw new Error("only the host can approve joins");this.signaling.send({t:"approve",peerId:t,ok:e})}async leave(){this.ended||(this.signaling.send({t:"leave"}),this.teardown("left"))}send(t,e,s={}){let i=s.channel??"reliable";this.isHost?this.sendTo(t,{type:"raw",bytes:e},this.localPeerId,i):this.sendTo(this.hostPeerId,{type:"raw",bytes:e},t,i)}broadcast(t,e={}){let s=e.channel??"reliable";if(this.isHost)for(let i of this.roster.keys())i!==this.localPeerId&&this.sendTo(i,{type:"raw",bytes:t},this.localPeerId,s);else this.sendTo(this.hostPeerId,{type:"raw",bytes:t},0,s)}channelFor(t){return t.type==="voice"?"voice":t.type==="ping"||t.type==="pong"?"fast":"reliable"}sendTo(t,e,s,i){let n=this.links.get(t),o=i??this.channelFor(e),a=e.type==="raw"||e.type==="chat"||e.type==="cmd";if(!(!!n?.isOpen&&(!a||!this.isHost||this.connected.has(t)))){let l=this.roster.has(t)||t===this.hostPeerId;if(a&&o==="reliable"&&l&&!this.ended){let h=this.outbox.get(t);h||this.outbox.set(t,h=[]),h.length>=1e3&&h.shift(),h.push({msg:e,route:s,channel:i})}return}if(n)try{let l=o,h;try{h=P(e,s)}catch(u){if(!(u instanceof m)||u.code!=="too_long")throw u;h=P(e,s,!0)}if(h.length>16384)for(let u of H(this.chunkId++>>>0,h))n.send(l,P(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(e=>this.links.get(e)?.isOpen).sort((e,s)=>e-s):this.links.get(this.hostPeerId)?.isOpen?[this.hostPeerId]:[]}reportError(t){this.errorSink?.(t)}errorSink=null;seatSink=null;armReadyTimer(){let t=this.cfg.readyTimeoutMs??0;if(!(t>0))return;let e=!this.cfg.relayOnly&&Pt(this.cfg.ice),s=e?Math.ceil(t/2):t;this.cancelReadyTimer=this.cfg.clock.setTimeout(()=>{this.isReady||this.ended||(e?this.retryRelayed(t-s):this.giveUpConnecting())},s)}retryRelayed(t){this.relayRetry=!0,this.makeLink(this.hostPeerId,!0).startOffer(),this.cancelReadyTimer=this.cfg.clock.setTimeout(()=>{!this.isReady&&!this.ended&&this.giveUpConnecting()},t)}giveUpConnecting(){let t=new d("ice_failed",`could not open a connection to the host${this.relayRetry?" (direct or relayed)":""}`);this.failReady(t),this.reportError(t),!this.ended&&(this.signaling.send({t:"leave"}),this.teardown("ice_failed"))}makeLink(t,e=this.cfg.relayOnly){let s=this.links.get(t);s&&(s.onClose=null,s.onMessage=null,s.close());let i=this.cfg.links.create({remoteId:t,iceServers:this.cfg.ice,relayOnly:e});return this.links.set(t,i),i.onSignal=n=>this.signaling.send({t:"signal",to:t,...n}),i.onOpen=()=>this.onLinkOpen(t,i),i.onClose=n=>this.onLinkClosed(t,i,n),i.onMessage=(n,o)=>this.onData(t,n,o),i.onPath=n=>this.signaling.send({t:"path",path:n}),i}offerTo(t){this.makeLink(t).startOffer()}onLinkOpen(t,e){if(this.isHost){this.epoch>0&&this.sendTo(t,{type:"host_claim",epoch:this.epoch,hostId:this.localPeerId,tick:this.lockstep.lastTick},0);return}t===this.hostPeerId&&this.sendHi(e)}sendHi(t){this.hiSentTo.has(t)||(this.hiSentTo.add(t),this.lockstep.touch(),t.send("reliable",P(this.lockstep.hiMessage(this.localPeerId))),this.markReady(),this.emit("ready"),this.emit("peerReady",this.roster.get(t.remoteId)??{peerId:t.remoteId,name:""}),this.flush(t.remoteId))}onLinkClosed(t,e,s){if(this.links.get(t)===e&&(this.links.delete(t),this.hiSentTo.delete(e),!this.ended)){if(this.isHost)this.connected.delete(t),this.lockstep.onPeerLeft(t),this.voice.onPeerLeft(t),!this.serverUp&&this.roster.has(t)&&this.removePeer(t,"timeout");else if(t===this.hostPeerId){if(!this.isReady&&this.cancelReadyTimer)return;this.handleHostLost("link closed")}}}onData(t,e,s,i=!1){let n;try{n=we(s,i)}catch(c){this.reportError(c instanceof Error?c:new Error(String(c)));return}if(!n)return;i||this.lastHeard.set(t,this.cfg.clock.now());let{msg:o,route:a}=n;switch(o.type){case"hi":this.isHost&&this.onHi(t,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":(t===this.hostPeerId||this.isHost)&&this.lockstep.handle(t,o);break;case"chunk":{if(i)break;let c=this.reassemblers.get(t);c||this.reassemblers.set(t,c=new I);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&&t===this.hostPeerId?this.lockstep.onSnapshotPayload(l):this.onData(t,e,l,!0);break}case"chat":case"chat_history":case"chat_reject":this.chat.handle(t,a,o);break;case"voice":e==="voice"&&this.voice.handle(t,a,o);break;case"raw":this.onRaw(t,a,o.bytes,e);break;case"ping":this.sendTo(t,{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(t,o.epoch,o.hostId);break}}onHi(t,e){this.connected.add(t),this.sendTo(t,{type:"welcome_dc",tick:this.mode==="lockstep"?this.lockstep.currentTick:0,tickRate:this.tickRate,mode:this.mode==="lockstep"?ke:ve,hostId:this.localPeerId},0),this.chat.sendHistory(t),this.mode==="lockstep"&&this.lockstep.onPeerHi(t,e),this.emit("peerReady",this.roster.get(t)??{peerId:t,name:""}),this.flush(t)}flush(t){let e=this.outbox.get(t);if(e){this.outbox.delete(t);for(let s of e)this.sendTo(t,s.msg,s.route,s.channel)}}onRaw(t,e,s,i){if(!this.isHost){this.emit("message",e,s,i);return}if(this.allowRaw(t))if((e===0||e===this.localPeerId)&&this.emit("message",t,s,i),e===0)for(let n of this.connectedPeerIds())n!==t&&this.sendTo(n,{type:"raw",bytes:s},t,i);else e!==this.localPeerId&&this.sendTo(e,{type:"raw",bytes:s},t,i)}allowRaw(t){let e=this.cfg.rateLimit;if(!e||!(e.messages>0)||!(e.perMs>0))return!0;let s=this.cfg.clock.now(),i=this.rate.get(t);return(!i||s-i.start>=e.perMs)&&this.rate.set(t,i={start:s,n:0}),++i.n<=e.messages}setHost(t){t!==this.hostPeerId&&(this.hostPeerId=t,this.emit("hostChanged",t))}handleHostLost(t){if(this.isHost||this.ended)return;if(this.cfg.direct){this.teardown("host_left");return}let e=this.hostPeerId,s=this.roster.get(e);s&&(this.roster.delete(e),this.emit("peerLeft",s,t==="link closed"?"timeout":t));let i=this.links.get(e);i&&(i.onClose=null,i.onMessage=null,i.close(),this.links.delete(e),this.hiSentTo.delete(i)),(_e([...this.roster.keys(),this.localPeerId],e)??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 t of this.roster.keys())t!==this.localPeerId&&this.offerTo(t)}onHostClaim(t,e,s){let i={epoch:this.epoch,hostId:this.hostPeerId},n={epoch:e,hostId:s};if(s===this.hostPeerId&&e===this.epoch&&!this.isHost){let c=this.links.get(t);c&&this.sendHi(c);return}if(!Me(n,i))return;let o=this.isHost;if(this.epoch=e,o){for(let[c,l]of this.links)c!==t&&(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(t);a&&t===s&&this.sendHi(a)}onSignaling(t){switch(t.t){case"peer_joined":{if(!t.peer||this.roster.has(t.peer.peerId))return;let e=D(t.peer);this.roster.set(e.peerId,e),this.emit("peerJoined",e),this.isHost&&this.offerTo(e.peerId);break}case"peer_left":this.removePeer(Number(t.peerId),String(t.reason??"left"));break;case"host_changed":{if(this.cfg.direct){this.isHost||this.teardown("host_left");break}let e=Number(t.hostId);if(typeof t.epoch=="number"&&t.epoch>this.epoch&&(this.epoch=t.epoch&65535),e===this.hostPeerId)break;if(e===this.localPeerId&&!this.isHost){let s=this.hostPeerId;this.roster.delete(s),this.becomeHost()}else this.setHost(e);break}case"join_request":this.emit("joinRequested",D(t.peer));break;case"lobby":{let e=t;this.meta={...e.meta},e.resumeToken&&e.resumeToken!==this.resumeToken&&(this.resumeToken=e.resumeToken,this.seatSink?.(this.resumeToken)),this.roster=new Map(e.peers.map(s=>[s.peerId,D(s)])),e.hostId!==this.hostPeerId&&!this.isHost&&this.setHost(e.hostId),this.setPhase(e.phase);break}case"meta":this.meta={...t.meta},this.emit("metaChanged",this.meta);break;case"phase":this.setPhase(t.phase);break;case"signal":this.onSignal(Number(t.from),t);break;case"error":t.code==="kicked"?this.teardown("kicked"):this.reportError(new d(String(t.code),String(t.msg??""),t.data));break}}async onSignal(t,e){let s=this.links.get(t);if(e.sdp?.type==="offer"&&(s=this.makeLink(t)),!s)return;let i={};e.sdp&&(i.sdp=e.sdp),e.ice&&(i.ice=e.ice),await s.handleSignal(i)}removePeer(t,e){let s=this.roster.get(t);if(t===this.localPeerId){this.teardown(e==="kicked"?"kicked":e);return}s&&this.roster.delete(t);let i=this.links.get(t);i&&t!==this.hostPeerId&&(i.onClose=null,i.onMessage=null,i.close(),this.links.delete(t),this.hiSentTo.delete(i)),this.connected.delete(t),this.outbox.delete(t),this.lastHeard.delete(t),this.rate.delete(t),this.lockstep.onPeerLeft(t),this.chat.onPeerLeft(t),this.voice.onPeerLeft(t),s&&this.emit("peerLeft",s,e),t===this.hostPeerId&&!this.isHost&&this.handleHostLost(e)}dropPeer(t,e){this.signaling.send({t:"kick",peerId:t}),this.removePeer(t,e)}setPhase(t){t!=="lobby"&&t!=="in_game"||this.phase!==t&&(this.phase=t,t==="in_game"&&!this.lockstep.started&&this.lockstep.markStarted(),this.emit("phase",t))}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 t=this.cfg.peerTimeoutMs??0;t>0&&this.dropSilent(Math.max(t,le)),!(this.ended||this.isHost)&&this.mode==="lockstep"&&this.lockstep.hostSilent()&&(this.lockstep.touch(),this.lockstep.pokeHost(),this.handleHostLost("timeout"))}dropSilent(t){let e=this.cfg.clock.now();if(this.isHost)for(let s of[...this.connected]){let i=this.lastHeard.get(s);i!==void 0&&e-i>t&&this.removePeer(s,"timeout")}else if(this.isReady){let s=this.lastHeard.get(this.hostPeerId);s!==void 0&&e-s>t&&(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(t){if(!this.ended){if(this.ended=!0,this.cancelReadyTimer?.(),!this.isReady){let e=t==="ice_failed"||t==="kicked"?t:"closed";this.failReady(new d(e,`the lobby ended before connecting (${t})`))}this.cancelWatchdog?.(),this.cancelPing?.(),this.lockstep.stop(),this.voice.close();for(let e of this.links.values())e.onClose=null,e.onMessage=null,e.close();this.links.clear(),this.signaling.onClose=null,this.signaling.onMessage=null,this.signaling.close(),this.emit("sessionEnded",t)}}get linkCount(){return this.links.size}};var q={reliable:0,fast:1,voice:2},Fe=r=>new WebSocket(r);function V(){return typeof AudioEncoder<"u"&&typeof AudioDecoder<"u"&&typeof AudioData<"u"}var Ue={createEncoder(r,t){let e=0,s=new AudioEncoder({output:i=>{let n=new Uint8Array(i.byteLength);i.copyTo(n),t(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:e,data:i});e+=2e4,s.encode(n),n.close()},close(){s.state!=="closed"&&s.close()}}},createDecoder(r){let t=0,e=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 e.configure({codec:"opus",sampleRate:48e3,numberOfChannels:1}),{decode(s){if(s===null){r(new Float32Array(960));return}e.decode(new EncodedAudioChunk({type:"key",timestamp:t,data:s})),t+=2e4},close(){e.state!=="closed"&&e.close()}}}},Ct=`
|
|
1
|
+
/*! onelobby 0.2.2 | MIT license */
|
|
2
|
+
"use strict";var OneLobby=(()=>{var ee=Object.defineProperty;var et=Object.getOwnPropertyDescriptor;var tt=Object.getOwnPropertyNames;var st=Object.prototype.hasOwnProperty;var it=(r,e)=>{for(var t in e)ee(r,t,{get:e[t],enumerable:!0})},nt=(r,e,t,s)=>{if(e&&typeof e=="object"||typeof e=="function")for(let i of tt(e))!st.call(r,i)&&i!==t&&ee(r,i,{get:()=>e[i],enumerable:!(s=et(e,i))||s.enumerable});return r};var rt=r=>nt(ee({},"__esModule",{value:!0}),r);var Xt={};it(Xt,{CHAT_MAX_BYTES:()=>A,Chat:()=>g,DEFAULT_READY_TIMEOUT_MS:()=>fe,Keepalive:()=>Ye,KeepaliveError:()=>Ge,LIB_NAME:()=>ue,LIB_VERSION:()=>pe,LOBBY_TAG:()=>me,Lobby:()=>w,LobbyController:()=>S,Lockstep:()=>k,MIN_PEER_TIMEOUT_MS:()=>le,OneLobby:()=>b,OneLobbyError:()=>d,SharedState:()=>L,Voice:()=>v,closeCodeFor:()=>E,codeFromUrl:()=>X,decodeJoinCode:()=>J,defineLobbyElement:()=>Q,describeError:()=>Y,formatCode:()=>y,lobbyServerUrl:()=>_,looksLikeJoinCode:()=>W,normalizeCode:()=>m,shareUrl:()=>G,webCodecsOpusAvailable:()=>V,webRtcLinkFactoryFor:()=>De});function ot(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 be={now:()=>typeof performance<"u"?performance.now():Date.now(),setInterval(r,e){let t=ot(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 p=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 P="0123456789ABCDEFGHJKMNPQRSTVWXYZ";function at(r){return r.replace(/[a-z]/g,e=>e.toUpperCase())}function m(r){let e=at(r).replace(/[ \-]/g,"").replace(/[IL]/g,"1").replace(/O/g,"0");if(e.length!==6)return null;for(let t of e)if(P.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 te={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},ke=1,ve=2,f=class extends Error{constructor(t,s=""){super(s?`${t}: ${s}`:t);this.code=t;this.name="CodecError"}code},ye=new TextEncoder,ct=new TextDecoder("utf-8",{fatal:!0});function ge(r){try{return ct.decode(r)}catch{throw new f("bad_utf8")}}var se=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)}},ie=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 f("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 C(r,e=0,t=!1){let s=new se(64);switch(s.u8(te[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=ye.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=ye.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 f("too_long",`${i.length} > 16384`);return i}var lt=new Map(Object.keys(te).map(r=>[te[r],r]));function we(r,e=!1){let t=e?67108864:16384;if(r.length>t)throw new f("too_long",`${r.length} > ${t}`);if(r.length<3)throw new f("too_short","header");let s=new ie(r),i=lt.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:ge(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(),Qe=s.i64();c.push({peerId:h,id:u,ts:Qe,text:ge(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 f("trailing",`${r.length-s.pos} extra bytes`);return{route:n,msg:o}}var M={rate:1,length:2,mutedByHost:3};function ne(r){return r===1?"rate":r===2?"length":r===3?"muted_by_host":"unknown"}var A=256,xe=3,ht=5e3,dt=5,R=100,ut=/[\u0000-\u001f\u007f-\u009f\u202a-\u202e\u2066-\u2069]/g;function pt(r){return r.replace(ut,"")}function Se(r){return new TextEncoder().encode(r).length}var re=class{buckets=new Map;refillMs=ht/dt;tryTake(e,t){let s=this.buckets.get(e);return s||this.buckets.set(e,s={tokens:xe,at:t}),s.tokens=Math.min(xe,s.tokens+(t-s.at)/this.refillMs),s.at=t,s.tokens<1?!1:(s.tokens-=1,!0)}forget(e){this.buckets.delete(e)}},g=class extends p{constructor(t,s,i){super();this.hub=t;this.wall=s;this.isLocallyMuted=i}hub;wall;isLocallyMuted;filter=t=>t;lines=[];limiter=new re;hostMuted=new Set;localId=0;stampId=0;get history(){return this.lines}send(t){let s=++this.localId>>>0;return Se(t)>A*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(-R).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(-R))this.push({...n,text:this.safeFilter(n.text)});this.emit("history",this.lines)}break;case"chat_reject":this.emit("rejected",i.id,ne(i.reason));break}}hostProcess(t,s,i){let n=l=>{t===this.hub.localPeerId?this.emit("rejected",s,ne(l)):this.hub.sendTo(t,{type:"chat_reject",id:s,reason:l},0)};if(this.hostMuted.has(t))return n(M.mutedByHost);if(Se(i)>A)return n(M.length);if(!this.limiter.tryTake(t,this.hub.clock.now()))return n(M.rate);let o=pt(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>R&&this.lines.splice(0,this.lines.length-R))}safeFilter(t){try{return this.filter(t)}catch{return t}}};var oe=16e3;function O(r,e,t=oe){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 I=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)&&(t={count:e.count,got:0,parts:new Array(e.count),bytes:0},this.parts.set(e.id,t)),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 f("too_long",`chunk set over ${67108864} bytes`);if(t.got<t.count)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}clear(){this.parts.clear()}};function Ce(){return typeof CompressionStream<"u"&&typeof DecompressionStream<"u"}async function Te(r,e){let t=e.writable.getWriter();t.write(r).then(()=>t.close());let s=[],i=e.readable.getReader(),n=0;for(;;){let{done:c,value:l}=await i.read();if(c)break;s.push(l),n+=l.length}let o=new Uint8Array(n),a=0;for(let c of s)o.set(c,a),a+=c.length;return o}function Ee(r){return Te(r,new CompressionStream("deflate"))}function Le(r){return Te(r,new DecompressionStream("deflate"))}var mt=3,bt=3,yt=3e3,gt=1e4,kt=1024,k=class extends p{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=oe;historyMax;queue=[];history=[];lastFrameTick=-1;consumedTick=0;cmdSeq=1;awaitingSnapshot=!1;lastFrameRx=0;lastSnapshotReq=-1/0;restoreFailures=0;hosting=!1;_started=!1;pending=[];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 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(gt,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":this.hosting&&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 Le(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<=bt?(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>=mt){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>=kt&&Ce()&&(o=await Ee(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 O(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>yt}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 _e(r,e){let t=null;for(let s of r)s!==e&&(t===null||s<t)&&(t=s);return t}function vt(r,e){let t=r-e&65535;return t!==0&&t<32768}function Me(r,e){return r.epoch!==e.epoch?vt(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},Re="lk1",wt=1e4,xt=3e4,T=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(),wt),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(St(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>xt){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 St(r){return new d(String(r.code??"error"),r.msg===void 0?void 0:String(r.msg),r.data)}var H=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 Ae(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 F=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 ae(r,e){let t=r-e&65535;return t>=32768?t-65536:t}var U=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(ae(e.seq,this.expected)<0)return;this.packets.set(e.seq,e),e.flags&1?this.endSeq=e.seq:this.endSeq!==null&&ae(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&&ae(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}},j=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 Pt=300,v=class extends p{constructor(t,s){super();this.hub=t;this.deps=s}hub;deps;mode="pushToTalk";muted=!1;inputLevel=0;bitrate=24e3;_enabled=!1;gate=new F;packetizer=new H;relay=new j;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=Ae(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 U,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>Pt+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 E(r){return r==="left"||r==="disconnected"?1e3:r==="host_left"||r==="closed"?1001:r==="kicked"?4e3:1006}var Oe=globalThis.setImmediate??(r=>{setTimeout(r,0)}),Ct=new TextEncoder,Tt=new TextDecoder,N=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"?Ct.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&&Oe(()=>this.fireOpen())}open(e=!1){this.readyState===0&&(this.readyState=1,this.openPending=!0,e?Oe(()=>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:Tt.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 He(r,e={}){if(r.isHost)throw new Error("asWebSocket() is for joiners; the host uses lobby.onConnection()");let t=e.channel??"reliable",s=new N(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??E(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 Fe(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 N(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:E(l)),h?.reason??l)),r.on("sessionEnded",c=>{for(let l of[...i.values()])l.closed(c==="left"?1001:E(c),c);a.forEach(l=>l())})];for(let c of r.connectedPeers)o(c);return()=>{n=!0}}var le=6e3,Lt=3e3;function D(r){let e=typeof r.name=="string"?r.name.trim():"";return e&&e!=="Player"?r:{...r,name:`Player ${r.peerId}`}}function _t(r){return r.some(e=>(Array.isArray(e.urls)?e.urls:[e.urls]).some(t=>/^turns?:/i.test(String(t))))}var Mt=250,Rt=2e3,w=class extends p{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,D(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 k(this.hub,{hashEveryTicks:e.hashEveryTicks}),this.chat=new g(this.hub,e.wall,i=>this.voice.isMutedPeer(i)),this.voice=new v(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(),Mt),this.cancelPing=e.clock.setInterval(()=>this.pingHost(),Rt)}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 He(this,e)}onConnection(e,t){return Fe(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=C(t,s)}catch(u){if(!(u instanceof f)||u.code!=="too_long")throw u;h=C(t,s,!0)}if(h.length>16384)for(let u of O(this.chunkId++>>>0,h))n.send(l,C(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&&_t(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",C(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=we(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 I);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"?ke:ve,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)),(_e([...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(!Me(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=D(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",D(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,D(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,Lt);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,le)),!(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 q={reliable:0,fast:1,voice:2},Ue=r=>new WebSocket(r);function V(){return typeof AudioEncoder<"u"&&typeof AudioDecoder<"u"&&typeof AudioData<"u"}var je={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()}}}},At=`
|
|
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,7 @@ class OneLobbyCapture extends AudioWorkletProcessor {
|
|
|
17
17
|
}
|
|
18
18
|
}
|
|
19
19
|
registerProcessor('onelobby-capture', OneLobbyCapture);
|
|
20
|
-
`,$=class{ctx=null;stream=null;async start(t){this.stream=await navigator.mediaDevices.getUserMedia({audio:{channelCount:1,echoCancellation:!0,noiseSuppression:!0,autoGainControl:!0}}),this.ctx=new AudioContext({sampleRate:48e3});let e=URL.createObjectURL(new Blob([Ct],{type:"text/javascript"}));try{await this.ctx.audioWorklet.addModule(e)}finally{URL.revokeObjectURL(e)}let s=this.ctx.createMediaStreamSource(this.stream),i=new AudioWorkletNode(this.ctx,"onelobby-capture",{numberOfInputs:1,numberOfOutputs:0});i.port.onmessage=n=>t(n.data),s.connect(i),this.ctx.state==="suspended"&&await this.ctx.resume()}stop(){for(let t of this.stream?.getTracks()??[])t.stop();this.ctx?.close(),this.stream=null,this.ctx=null}},B=class{ctx=new AudioContext({sampleRate:48e3});next=new Map;constructor(){this.ctx.resume()}play(t,e,s){if(e.length===0)return;let i=this.ctx.createBuffer(1,e.length,48e3);i.copyToChannel(e,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(t)??0;c<a+.01&&(c=a+.02),n.start(c),this.next.set(t,c+e.length/48e3)}close(){this.ctx.close()}};var Lt={reliable:{negotiated:!0,id:q.reliable,ordered:!0},fast:{negotiated:!0,id:q.fast,ordered:!1,maxRetransmits:0},voice:{negotiated:!0,id:q.voice,ordered:!1,maxRetransmits:0}},K=class{remoteId;isOpen=!1;onSignal=null;onOpen=null;onClose=null;onMessage=null;onPath=null;pc;channels={};pendingIce=[];closed=!1;opened=0;extra;constructor(t,e=globalThis.RTCPeerConnection){this.remoteId=t.remoteId,this.extra=t.extraRemoteCandidates??[];let s={iceServers:t.iceServers,iceTransportPolicy:t.relayOnly?"relay":"all"};t.portRange&&(s.portRangeBegin=t.portRange.begin,s.portRangeEnd=t.portRange.end,t.portRange.begin===t.portRange.end&&(s.enableIceUdpMux=!0)),this.pc=new e(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))};for(let i of["reliable","fast","voice"]){let n=this.pc.createDataChannel(i,Lt[i]);n.binaryType="arraybuffer",n.onopen=()=>{++this.opened===3&&!this.closed&&(this.isOpen=!0,this.onOpen?.(),this.reportPath())},n.onclose=()=>this.fail(`channel ${i} closed`),n.onmessage=o=>{let a=o.data,c=a instanceof ArrayBuffer?new Uint8Array(a):ArrayBuffer.isView(a)?new Uint8Array(a.buffer,a.byteOffset,a.byteLength):new Uint8Array(a);this.onMessage?.(i,c)},this.channels[i]=n}}startOffer(){(async()=>{try{let t=await this.pc.createOffer();await this.pc.setLocalDescription(t),this.onSignal?.({sdp:{type:"offer",sdp:t.sdp??""}})}catch(t){this.fail(`offer failed: ${String(t)}`)}})()}async handleSignal(t){try{if(t.sdp){await this.pc.setRemoteDescription({type:t.sdp.type,sdp:t.sdp.sdp});for(let e of this.pendingIce.splice(0))await this.pc.addIceCandidate(e);if(this.extra.length){let e=/^a=mid:(\S+)/m.exec(t.sdp.sdp)?.[1]??"0";for(let s of this.extra.splice(0))await this.pc.addIceCandidate({candidate:s,sdpMid:e,sdpMLineIndex:0})}if(t.sdp.type==="offer"){let e=await this.pc.createAnswer();await this.pc.setLocalDescription(e),this.onSignal?.({sdp:{type:"answer",sdp:e.sdp??""}})}}t.ice&&(this.pc.remoteDescription?await this.pc.addIceCandidate(t.ice):this.pendingIce.push(t.ice))}catch(e){this.fail(`signal failed: ${String(e)}`)}}async reportPath(t=0){if(!(this.closed||t>10)){try{let e=await this.pc.getStats(),s=new Map;e.forEach(a=>s.set(a.id,a));let i;e.forEach(a=>{a.type==="transport"&&a.selectedCandidatePairId&&(i=s.get(a.selectedCandidatePairId))}),i||e.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(t+1)},300)}}send(t,e){let s=this.channels[t];s&&s.readyState==="open"&&s.send(e)}close(){this.fail("closed locally")}fail(t){if(this.closed)return;this.closed=!0;let e=this.isOpen;this.isOpen=!1;try{this.pc.close()}catch{}this.onClose?.(t)}},je={create:r=>new K(r)};function Ne(r){return{create:t=>new K(t,r)}}var _t=11;function Mt(r,t){let e=0;for(let s=t-1;s>=0;s--){let i=Number(r>>BigInt(s)&1n),n=e>>4&1;e=e<<1&31,n^i&&(e^=5)}return e}function De(r){let t=r.replace(/[ \-]/g,"").replace(/[a-z]/g,e=>e.toUpperCase()).replace(/[IL]/g,"1").replace(/O/g,"0");if(t.length!==_t)return null;for(let e of t)if(_.indexOf(e)<0)return null;return t}function J(r){let t=De(r);if(!t)return{error:"bad_code"};let e=0n;for(let o of t)e=e<<5n|BigInt(_.indexOf(o));let s=e>>5n;if(Mt(s,50)!==Number(e&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 W(r){return!/[.:]/.test(r)&&De(r)!==null}var Rt=/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/,At=/^(?=.{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 qe(r){let t=Rt.exec(r);return!!t&&t.slice(1).every(e=>Number(e)<=255)}function It(r){if(!/^[0-9A-Fa-f:.]+$/.test(r)||r.split("::").length>2||!r.includes(":"))return!1;let t=r.split(":").pop()??"",e=r.split(":").filter((s,i,n)=>s!==""||i>0&&i<n.length-1);return t.includes(".")&&!qe(t)?!1:e.every(s=>s===""||s.includes(".")||/^[0-9A-Fa-f]{1,4}$/.test(s))}function $e(r,t=47810){let e=r.trim();if(/^ws:\/\//i.test(e)&&(e=e.slice(5)),e=e.replace(/\/+$/,""),!e)return null;let s,i=null;if(e[0]==="["){let c=e.indexOf("]");if(c<0)return null;s=e.slice(1,c);let l=e.slice(c+1);if(l){if(l[0]!==":")return null;i=l.slice(1)}}else{let c=e.indexOf(":");c>=0&&c===e.lastIndexOf(":")?(s=e.slice(0,c),i=e.slice(c+1)):s=e}let n=t;if(i!==null&&(!/^\d{1,5}$/.test(i)||(n=Number(i),n<1||n>65535)))return null;let o=It(s);if(!o&&!qe(s)&&!At.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 ue="onelobby",pe="0.2.0",me=15e3;function Ot(){return typeof navigator<"u"&&!!navigator.mediaDevices?.getUserMedia&&typeof AudioWorkletNode<"u"}function Ft(){try{return globalThis.sessionStorage??null}catch{return null}}var de=new Set;function Be(r){for(let t of de)if(t.resumeToken===r)return!0;return!1}var b=class extends p{options;socketFactory;linkFactory;clock;wall;voiceCodec;createMic;createSink;storage;constructor(t){super();let e=t.tickRate??20;this.options={gameId:t.gameId,netVersion:t.netVersion,lobbyServer:t.lobbyServer,mode:t.mode??"lockstep",tickRate:e,hashEveryTicks:t.hashEveryTicks??e,displayName:(t.displayName??"").trim(),relayOnly:t.relayOnly??!1,readyTimeoutMs:t.readyTimeoutMs??me,peerTimeoutMs:t.peerTimeoutMs??0};let s=t.advanced??{},i=c=>s[c]!==void 0?s[c]:t[c];this.socketFactory=i("socketFactory")??Fe,this.linkFactory=i("linkFactory")??je,this.clock=i("clock")??be,this.wall=i("wallClock")??(()=>Date.now());let n=i("voiceCodec");this.voiceCodec=n!==void 0?n:V()?Ue:null;let o=i("createMic");this.createMic=o!==void 0?o:Ot()?()=>new $:null;let a=i("createSink");this.createSink=a!==void 0?a:typeof AudioContext<"u"?()=>new B:null,this.storage=t.rememberSeat===!1?null:s.storage!==void 0?s.storage:Ft()}get seatKey(){return`onelobby.seat.${this.options.gameId}`}loadSeat(){if(!this.storage)return null;try{let t=this.storage.getItem(this.seatKey);if(!t)return null;let e=JSON.parse(t);return typeof e.code!="string"||typeof e.token!="string"||e.server!==this.options.lobbyServer?null:e}catch{return null}}saveSeat(t,e){if(!(!this.storage||!e))try{this.storage.setItem(this.seatKey,JSON.stringify({code:t,token:e,server:this.options.lobbyServer}))}catch{}}clearSeat(t){if(this.storage)try{if(t!==void 0){let e=this.loadSeat();if(e&&e.code!==t)return}this.storage.removeItem(this.seatKey)}catch{}}get rememberedCode(){return this.loadSeat()?.code??null}async resume(){let t=this.loadSeat();if(!t||Be(t.token))return null;try{return await this.rejoin(t.token)}catch(e){if(e instanceof d&&e.code==="invalid_token")return this.clearSeat(),null;throw e}}helloMsg(t){return{t:"hello",proto:Re,gameId:this.options.gameId,netVersion:this.options.netVersion,client:{lib:ue,ver:pe,platform:typeof navigator<"u"?"browser":"node"},name:this.options.displayName.slice(0,24),...t?{resumeToken:t}:{}}}async connect(t=this.options.lobbyServer,e=this.socketFactory,s=0){let i=new E(t,e,this.clock);if(s>0){let n=()=>{},o=new Promise((a,c)=>{n=this.clock.setTimeout(()=>c(new d("direct_unreachable",`no answer from ${t} 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 ${t}`):a}finally{n()}}else await i.connect();try{let n=await i.request(this.helloMsg(),"welcome");return{sig:i,welcome:n}}catch(n){throw i.close(),n}}makeLobby(t,e,s,i,n){let o=new w({signaling:t,state:s,ice:e.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&&(de.add(o),this.saveSeat(o.code,o.resumeToken),o.seatSink=a=>this.saveSeat(o.code,a),o.on("sessionEnded",()=>{de.delete(o),this.clearSeat(o.code)})),o}async host(t){let{sig:e,welcome:s}=await this.connect();try{let i=await e.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(e,s,i,void 0,t.rateLimit)}catch(i){throw e.close(),i}}async join(t){let e=f(t);if(!e)throw new d("bad_code",`"${t}" is not a valid join code`);let s=this.loadSeat();if(s&&s.code===e&&!Be(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:e},"lobby");return this.makeLobby(i,n,o)}catch(o){throw i.close(),o}}async joinDirect(t,e={}){let s=t.trim();if(W(s)){let l=J(s);if("error"in l)throw new d(l.error,l.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=`${l.ip}:${l.port}`}let i=$e(s,e.defaultPort??47810);if(!i)throw new d("bad_address",`"${t}" is not a join code or an address (try 6B01-RGFE-P2B or 192.168.1.20:47810)`);let n=this.linkFactory,o={create:l=>n.create({...l,iceServers:[],extraRemoteCandidates:[i.candidate]})},{sig:a,welcome:c}=await this.connect(i.url,this.socketFactory,e.connectTimeoutMs??5e3);try{let l=await a.request({t:"join",...e.password?{password:e.password}:{}},"lobby");return this.makeLobby(a,c,l,{links:o})}catch(l){throw a.close(),l}}async hostDirectWith(t,e){let{sig:s,welcome:i}=await this.connect("onelobby-direct://host/",e.socketFactory);try{let n=await s.request({t:"create",name:t.name,maxPlayers:t.maxPlayers,visibility:"unlisted",mode:this.options.mode,tickRate:this.options.tickRate,lateJoin:t.lateJoin??!0,meta:t.meta??{}},"lobby");return this.makeLobby(s,i,n,{links:e.wrapLinks(this.linkFactory)},t.rateLimit)}catch(n){throw s.close(),n}}async rejoin(t){let e=new E(this.options.lobbyServer,this.socketFactory,this.clock);await e.connect();try{let s=e.expect("lobby"),i=e.request(this.helloMsg(t),"welcome");s.catch(()=>{});let n=await i;if(n.peerId===void 0)throw new d("invalid_token","resume token unknown or expired");let o=await s;return this.makeLobby(e,n,o)}catch(s){throw e.close(),s}}async list(t={}){let{sig:e}=await this.connect();try{return(await e.request({t:"list",visibility:"public",meta:t.meta??{},limit:Math.min(100,t.limit??50),...t.cursor?{cursor:t.cursor}:{}},"lobbies")).items??[]}finally{e.close()}}};var z={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."}},Ut=/^[a-z][a-z0-9_]*$/;function jt(r){if(typeof r=="string"){let t=/^([a-z][a-z0-9_]*)(?::|$)/.exec(r.trim());return{code:t?t[1]:null}}if(r&&typeof r=="object"){let t=r,e=t.data&&typeof t.data=="object"?t.data:void 0;if(typeof t.code=="string"&&Ut.test(t.code))return{code:t.code,data:e};if(typeof t.message=="string"){let s=/^([a-z][a-z0-9_]*):/.exec(t.message);if(s&&z[s[1]])return{code:s[1],data:e};if(t.name==="NetworkError"||/network|failed to fetch|websocket/i.test(t.message))return{code:"connect_failed"};if(t.name==="TimeoutError"||/timed? ?out/i.test(t.message))return{code:"timeout"}}}return{code:null}}function Y(r){let{code:t,data:e}=jt(r);if(!t)return{code:"unknown",...z.unknown};let s=t;t==="kicked"&&e?.reason==="denied"&&(s="denied"),t==="rate_limited"&&e?.reason==="max_lobbies"&&(s="max_lobbies");let i=z[s];if(!i)return{code:t,...z.unknown};let n={code:t,message:i.message};if(i.hint&&(n.hint=i.hint),t==="rate_limited"&&typeof e?.retryAfterMs=="number"&&s==="rate_limited"){let o=Math.max(1,Math.ceil(e.retryAfterMs/1e3));n.hint=`Wait ${o} second${o===1?"":"s"}, then try again.`}return n}function Ve(){try{return typeof location<"u"?location.href:void 0}catch{return}}function G(r,t,e="join"){let s=f(r);if(!s)throw new Error(`"${r}" is not a valid lobby code`);let i=t??Ve();if(!i)throw new Error("shareUrl needs a base URL outside the browser");let n=new URL(i);return n.searchParams.delete(e),n.searchParams.set(e,y(s).replace(" ","-")),n.toString()}function X(r,t="join"){let e=r??Ve();if(!e)return null;let s;try{s=new URL(e,"http://localhost/")}catch{return null}let i=s.searchParams.get(t)??new URLSearchParams(s.hash.replace(/^#/,"")).get(t);return i?f(i):null}var Ke=b,Je=d;var Nt=new TextEncoder,Dt=new TextDecoder,Z=r=>Nt.encode(JSON.stringify(r)),C=class{constructor(t){this.c=t}c;values=new Map;get(t){return this.values.get(t)}set(t,e){this.c._sharedOp({k:"set",key:String(t),v:e===void 0?null:e})}add(t,e=1){this.c._sharedOp({k:"add",key:String(t),n:Number(e)||0})}all(){return Object.fromEntries(this.values)}keys(){return[...this.values.keys()]}},S=class{constructor(t){this.opts=t}opts;state="idle";lobby=null;error=null;linking=!1;name="";shared=new C(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 t=this.lobby;if(!t)return[];let e=t.peers.map(s=>this.toPlayer(s));return e.some(s=>s.isMe)||e.push({id:t.localPeerId,name:this.name||`Player ${t.localPeerId}`,isHost:t.isHost,isMe:!0}),e.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 t=this.players.length;return t<=1?"Waiting for players\u2026 Share the code with your friends.":`${t} 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 t=this.lobby;return t&&t.peer(t.hostPeerId)?.name||"the host"}toPlayer(t,e){let s=this.lobby,i=t?.peerId??e??0;return{id:i,name:t?.name||i===s.localPeerId&&this.name||`Player ${i}`,isHost:i===s.hostPeerId,isMe:i===s.localPeerId}}host(t){return this.lastAction={kind:"host"},this.run(t,e=>e.host({name:this.opts.lobbyName??"game",maxPlayers:typeof this.opts.maxPlayers=="function"?this.opts.maxPlayers():this.opts.maxPlayers,visibility:"unlisted",lateJoin:!0}))}join(t,e){let s=f(t);return this.lastAction={kind:"join",code:t},s?this.run(e,i=>i.join(s)):(this.fail(new d("bad_code")),Promise.resolve())}async resume(t){let e=this.opts.makeKit(t);return!e.resume||!e.rememberedCode?!1:(this.lastAction={kind:"resume"},await this.run(t,()=>e.resume(),!0),!!this.lobby)}retry(){let t=this.lastAction;return!t||t.kind==="resume"?(this.reset(),Promise.resolve()):t.kind==="host"?this.host(this.name):this.join(t.code,this.name)}async start(){!this.lobby||!this.isHost||this.state==="playing"||await this.lobby.start()}async leave(){let t=this.lobby;this.detach(),await t?.leave(),this.reset()}reset(){this.detach(),this.state="idle",this.error=null,this.onChange()}send(t){this.lobby&&this.lobby.broadcast(Z({k:"m",d:t===void 0?null:t}))}async run(t,e,s=!1){this.detach();let i=++this.gen;this.name=(t||"").trim().slice(0,24),this.state="connecting",this.error=null,this.onChange();let n;try{n=await e(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(t){this.lobby=t,t.isHost||this.shared.values.clear(),this.seq=0,this.state=t.phase==="in_game"?"playing":"lobby",this.linking=!t.isHost;let e=this.unsub;e.push(t.on("ready",()=>this.linked())),e.push(t.on("peerJoined",s=>{this.onEvent("player-joined",{player:this.toPlayer(s)}),this.onChange()})),e.push(t.on("peerLeft",(s,i)=>{this.onEvent("player-left",{player:this.toPlayer(s),reason:i}),this.onChange()})),e.push(t.on("peerReady",s=>{t.isHost&&s.peerId!==t.localPeerId&&t.send(s.peerId,Z({k:"SS",all:this.shared.all(),q:this.seq}))})),e.push(t.on("hostChanged",()=>this.onChange())),e.push(t.on("phase",s=>{s==="in_game"&&this.state!=="playing"&&(this.state="playing",this.onChange(),this.onEvent("start",{}))})),e.push(t.on("message",(s,i)=>this.onRaw(s,i))),e.push(t.on("sessionEnded",s=>{this.lobby===t&&s!=="left"&&this.fail(new d(s))})),t.ready.then(()=>{this.lobby===t&&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 t of this.unsub.splice(0))t();this.lobby=null,this.linking=!1}fail(t){let e=this.lobby;this.detach(),e&&e.leave(),this.state="error",this.error=Y(t),this.onChange(),this.onEvent("error",this.error)}playerById(t){let e=this.lobby;return!e||!t?null:this.toPlayer(e.peer(t),t)}onRaw(t,e){let s;try{s=JSON.parse(Dt.decode(e))}catch{return}if(!s||typeof s!="object")return;let i=this.lobby;switch(s.k){case"m":this.onEvent("message",{from:this.playerById(t),data:s.d});break;case"set":case"add":i.isHost&&this.applyHost(s,t);break;case"S":!i.isHost&&t===i.hostPeerId&&!(s.q<=this.seq)&&(this.seq=s.q,this.applyValue(s.key,s.v,s.by));break;case"SS":if(!i.isHost&&t===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(t){let e=this.lobby;if(!e){let s=t.k==="set"?t.v:(Number(this.shared.values.get(t.key))||0)+t.n;this.applyValue(t.key,s,0);return}e.isHost?this.applyHost(t,e.localPeerId):e.send(e.hostPeerId,Z(t))}applyHost(t,e){let s=this.lobby;if(typeof t.key!="string")return;let i=t.k==="set"?t.v:(Number(this.shared.values.get(t.key))||0)+(Number(t.n)||0);this.applyValue(t.key,i,e),s.broadcast(Z({k:"S",key:t.key,v:i,by:e,q:++this.seq}))}applyValue(t,e,s){e===null?this.shared.values.delete(t):this.shared.values.set(t,e),this.onEvent("shared-change",{key:t,value:e,from:this.playerById(s)})}};var We=`
|
|
20
|
+
`,$=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([At],{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}},B=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 It={reliable:{negotiated:!0,id:q.reliable,ordered:!0},fast:{negotiated:!0,id:q.fast,ordered:!1,maxRetransmits:0},voice:{negotiated:!0,id:q.voice,ordered:!1,maxRetransmits:0}},K=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,It[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)}},Ne={create:r=>new K(r)};function De(r){return{create:e=>new K(e,r)}}var qe=11;function $e(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 Ot(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 Be(r,e){let t=Ot(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($e(s,50)),n="";for(let o=0;o<qe;o++)n=P[Number(i&31n)]+n,i>>=5n;return`${n.slice(0,4)}-${n.slice(4,8)}-${n.slice(8)}`}function Ve(r){let e=r.replace(/[ \-]/g,"").replace(/[a-z]/g,t=>t.toUpperCase()).replace(/[IL]/g,"1").replace(/O/g,"0");if(e.length!==qe)return null;for(let t of e)if(P.indexOf(t)<0)return null;return e}function J(r){let e=Ve(r);if(!e)return{error:"bad_code"};let t=0n;for(let o of e)t=t<<5n|BigInt(P.indexOf(o));let s=t>>5n;if($e(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 W(r){return!/[.:]/.test(r)&&Ve(r)!==null}var Ht=/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/,Ft=/^(?=.{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 Ke(r){let e=Ht.exec(r);return!!e&&e.slice(1).every(t=>Number(t)<=255)}function Ut(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(".")&&!Ke(e)?!1:t.every(s=>s===""||s.includes(".")||/^[0-9A-Fa-f]{1,4}$/.test(s))}function Je(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=Ut(s);if(!o&&!Ke(s)&&!Ft.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 ue="onelobby",pe="0.2.2",fe=15e3;function Nt(){return typeof navigator<"u"&&!!navigator.mediaDevices?.getUserMedia&&typeof AudioWorkletNode<"u"}function Dt(){try{return globalThis.sessionStorage??null}catch{return null}}var de=new Set;function We(r){for(let e of de)if(e.resumeToken===r)return!0;return!1}var b=class extends p{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??fe,peerTimeoutMs:e.peerTimeoutMs??0};let s=e.advanced??{},i=c=>s[c]!==void 0?s[c]:e[c];this.socketFactory=i("socketFactory")??Ue,this.linkFactory=i("linkFactory")??Ne,this.clock=i("clock")??be,this.wall=i("wallClock")??(()=>Date.now());let n=i("voiceCodec");this.voiceCodec=n!==void 0?n:V()?je:null;let o=i("createMic");this.createMic=o!==void 0?o:Nt()?()=>new $:null;let a=i("createSink");this.createSink=a!==void 0?a:typeof AudioContext<"u"?()=>new B:null,this.storage=e.rememberSeat===!1?null:s.storage!==void 0?s.storage:Dt()}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||We(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:Re,gameId:this.options.gameId,netVersion:this.options.netVersion,client:{lib:ue,ver:pe,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=new T(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();try{let n=await i.request(this.helloMsg(),"welcome");return{sig:i,welcome:n}}catch(n){throw i.close(),n}}makeLobby(e,t,s,i,n){let o=new w({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&&(de.add(o),this.saveSeat(o.code,o.resumeToken),o.seatSink=a=>this.saveSeat(o.code,a),o.on("sessionEnded",()=>{de.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&&!We(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 s=e.trim();if(W(s)){let l=J(s);if("error"in l)throw new d(l.error,l.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=`${l.ip}:${l.port}`}let i=Je(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,o={create:l=>n.create({...l,iceServers:[],extraRemoteCandidates:[i.candidate]})},{sig:a,welcome:c}=await this.connect(i.url,this.socketFactory,t.connectTimeoutMs??5e3);try{let l=await a.request({t:"join",...t.password?{password:t.password}:{}},"lobby"),h=this.makeLobby(a,c,l,{links:o});return h.joinCode=qt(i),h}catch(l){throw a.close(),l}}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){let t=new T(this.options.lobbyServer,this.socketFactory,this.clock);await t.connect();try{let s=t.expect("lobby"),i=t.request(this.helloMsg(e),"welcome");s.catch(()=>{});let n=await i;if(n.peerId===void 0)throw new d("invalid_token","resume token unknown or expired");let o=await s;return this.makeLobby(t,n,o)}catch(s){throw t.close(),s}}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 qt(r){if(r.isIPv6)return r.display;try{return Be(r.host,r.port)}catch{return r.display}}var z={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."}},$t=/^[a-z][a-z0-9_]*$/;function Bt(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"&&$t.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&&z[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 Y(r){let{code:e,data:t}=Bt(r);if(!e)return{code:"unknown",...z.unknown};let s=e;e==="kicked"&&t?.reason==="denied"&&(s="denied"),e==="rate_limited"&&t?.reason==="max_lobbies"&&(s="max_lobbies");let i=z[s];if(!i)return{code:e,...z.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 ze(){try{return typeof location<"u"?location.href:void 0}catch{return}}function G(r,e,t="join"){let s=m(r);if(!s)throw new Error(`"${r}" is not a valid lobby code`);let i=e??ze();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 X(r,e="join"){let t=r??ze();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 Ye=b,Ge=d;var Vt=new TextEncoder,Kt=new TextDecoder,Z=r=>Vt.encode(JSON.stringify(r)),L=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()]}},S=class{constructor(e){this.opts=e}opts;state="idle";lobby=null;error=null;linking=!1;name="";shared=new L(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(Z({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,Z({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=Y(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(Kt.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,Z(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(Z({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 Xe=`
|
|
21
21
|
:host {
|
|
22
22
|
--onelobby-bg: #ffffff;
|
|
23
23
|
--onelobby-fg: #1b1f24;
|
|
@@ -84,7 +84,7 @@ li { display: flex; gap: 0.5rem; align-items: center; }
|
|
|
84
84
|
.tag { font-size: 0.75rem; border: 1px solid var(--onelobby-border); border-radius: 999px; padding: 0 0.5rem; color: var(--onelobby-muted); }
|
|
85
85
|
.bar { display: flex; gap: 0.5rem; flex-wrap: wrap; align-items: center; justify-content: space-between; }
|
|
86
86
|
.sr { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
|
|
87
|
-
`;function
|
|
87
|
+
`;function _(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 i=s.hostname.toLowerCase();return!i||/(^|\.)example\.(com|net|org)$/.test(i)||/\.example$/.test(i)||i==="example"||!i.includes(".")&&!i.includes(":")&&i!=="localhost"?null:t}var me="one-lobby",Ze="onelobby-name",Jt=`
|
|
88
88
|
<div class="card" part="card">
|
|
89
89
|
<p class="status" id="status" role="status" aria-live="polite"></p>
|
|
90
90
|
<p class="hint" id="hint" hidden></p>
|
|
@@ -136,5 +136,5 @@ li { display: flex; gap: 0.5rem; align-items: center; }
|
|
|
136
136
|
</div>
|
|
137
137
|
</div>
|
|
138
138
|
</div>
|
|
139
|
-
`;function
|
|
139
|
+
`;function Wt(){try{return localStorage.getItem(Ze)??""}catch{return""}}function zt(r){try{localStorage.setItem(Ze,r)}catch{}}async function Yt(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 Gt(){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>${Xe}</style>${Jt}`,this._controller=new S({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??Wt()),this.$("nameLabel").hidden=!!t,this.render(),!this.autoJoined){this.autoJoined=!0;let i=X();i&&this._controller.state==="idle"?(this.$("code").value=y(i),s.value.trim()?this.join(i):(this.$("status").textContent="You're invited! Type your name, then press Join.",s.focus())):!i&&this._controller.state==="idle"&&_(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&&zt(t),t}makeKit(t){let s=_(this.getAttribute("server"));if(!s)throw new d("no_server","the <one-lobby> tag has no usable server address");return new b({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,i,n)=>this.$(s).addEventListener(i,n);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?G(this.code):"","copyLink","Link copied!")})}async copy(t,s,i){if(!t)return;let n=await Yt(t),o=this.$(s),a=s==="copyCode"?"Copy code":"Copy invite link";o.textContent=n?i:"Couldn't copy, select it by hand",this.$("copied").textContent=n?i:"",clearTimeout(this.copiedTimer),this.copiedTimer=setTimeout(()=>{o.textContent=a,this.$("copied").textContent=""},2e3)}render(){let t=this._controller,s=t.state,i=this.$("status");i.textContent=s==="error"&&t.error?.code==="no_server"?"This page is missing its lobby server address.":t.statusText,i.classList.toggle("error",s==="error");let n=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(n.textContent=o,n.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=y(t.code??""),this.$("players").replaceChildren(...t.players.map(l=>{let h=document.createElement("li"),u=document.createElement("span");return u.textContent=l.name,h.append(u),l.isHost&&h.append(Object.assign(document.createElement("span"),{className:"tag",textContent:"host"})),l.isMe&&h.append(Object.assign(document.createElement("span"),{className:"tag",textContent:"you"})),h}));let c=this.$("start");c.hidden=!t.isHost||s==="playing",c.disabled=t.players.length<2,c.title=c.disabled?"Wait for at least one more player":""}}}return r}function Q(r=me){typeof customElements>"u"||customElements.get(r)||customElements.define(r,Gt())}Q();return rt(Xt);})();
|
|
140
140
|
//# sourceMappingURL=onelobby.global.min.js.map
|