onelobby 0.3.1 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/blueprint.schema.json +237 -0
- package/dist/blueprint.d.ts +166 -0
- package/dist/blueprint.js +281 -0
- package/dist/blueprint.js.map +1 -0
- package/dist/chunking.d.ts +4 -0
- package/dist/chunking.js +23 -2
- package/dist/chunking.js.map +1 -1
- package/dist/deflate.d.ts +2 -1
- package/dist/deflate.js +10 -4
- package/dist/deflate.js.map +1 -1
- package/dist/direct/hub-host.d.ts +16 -0
- package/dist/direct/hub-host.js +45 -1
- package/dist/direct/hub-host.js.map +1 -1
- package/dist/direct/hub.d.ts +3 -0
- package/dist/direct/hub.js +32 -6
- package/dist/direct/hub.js.map +1 -1
- package/dist/direct/index.d.ts +8 -2
- package/dist/direct/index.js +26 -4
- package/dist/direct/index.js.map +1 -1
- package/dist/friendly.d.ts +36 -0
- package/dist/friendly.js +44 -0
- package/dist/friendly.js.map +1 -1
- package/dist/index.d.ts +5 -3
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/lockstep.d.ts +5 -0
- package/dist/lockstep.js +17 -2
- package/dist/lockstep.js.map +1 -1
- package/dist/onelobby.bundle.mjs +5 -5
- package/dist/onelobby.bundle.mjs.map +4 -4
- package/dist/onelobby.d.ts +34 -2
- package/dist/onelobby.global.js +445 -30
- package/dist/onelobby.global.js.map +3 -3
- package/dist/onelobby.global.min.js +5 -5
- package/dist/onelobby.global.min.js.map +4 -4
- package/dist/onelobby.js +136 -25
- package/dist/onelobby.js.map +1 -1
- package/package.json +4 -2
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
/*! onelobby 0.
|
|
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=`
|
|
1
|
+
/*! onelobby 0.5.0 | MIT license */
|
|
2
|
+
"use strict";var OneLobby=(()=>{var me=Object.defineProperty;var gt=Object.getOwnPropertyDescriptor;var kt=Object.getOwnPropertyNames;var vt=Object.prototype.hasOwnProperty;var wt=(r,e)=>{for(var t in e)me(r,t,{get:e[t],enumerable:!0})},xt=(r,e,t,s)=>{if(e&&typeof e=="object"||typeof e=="function")for(let n of kt(e))!vt.call(r,n)&&n!==t&&me(r,n,{get:()=>e[n],enumerable:!(s=gt(e,n))||s.enumerable});return r};var St=r=>xt(me({},"__esModule",{value:!0}),r);var ws={};wt(ws,{BlueprintError:()=>T,CHAT_MAX_BYTES:()=>q,Chat:()=>C,DEFAULT_READY_TIMEOUT_MS:()=>Me,ErrorCodes:()=>ut,Keepalive:()=>mt,KeepaliveError:()=>bt,LIB_NAME:()=>_e,LIB_VERSION:()=>Le,LOBBY_TAG:()=>Re,Lobby:()=>M,LobbyController:()=>I,Lockstep:()=>_,MIN_PEER_TIMEOUT_MS:()=>Se,OneLobby:()=>S,OneLobbyError:()=>d,SharedState:()=>U,Voice:()=>L,closeCodeFor:()=>j,codeFromUrl:()=>de,decodeJoinCode:()=>re,defineBlueprint:()=>lt,defineLobbyElement:()=>pe,describeError:()=>ce,formatCode:()=>P,lobbyServerUrl:()=>D,looksLikeJoinCode:()=>ie,normalizeCode:()=>g,parseBlueprint:()=>ae,shareUrl:()=>he,webCodecsOpusAvailable:()=>se,webRtcLinkFactoryFor:()=>et});function Pt(r,e){if(typeof Worker>"u"||typeof Blob>"u"||typeof URL>"u"||!URL.createObjectURL)return null;try{let t="let h=0;onmessage=e=>{if(e.data&&e.data.start){h=setInterval(()=>postMessage(0),e.data.ms)}else{clearInterval(h);close()}}",s=URL.createObjectURL(new Blob([t],{type:"text/javascript"})),n=new Worker(s);return URL.revokeObjectURL(s),n.onmessage=()=>r(),n.postMessage({start:!0,ms:e}),()=>{n.postMessage({stop:!0}),n.terminate()}}catch{return null}}var Ae={now:()=>typeof performance<"u"?performance.now():Date.now(),setInterval(r,e){let t=Pt(r,e);if(t)return t;let s=setInterval(r,e);return()=>clearInterval(s)},setTimeout(r,e){let t=setTimeout(r,e);return()=>clearTimeout(t)}};var f=class{handlers=new Map;on(e,t){let s=this.handlers.get(e);return s||this.handlers.set(e,s=[]),s.push(t),()=>this.off(e,t)}off(e,t){let s=this.handlers.get(e);if(!s)return;let n=s.indexOf(t);n>=0&&s.splice(n,1)}emit(e,...t){let s=this.handlers.get(e);if(s)for(let n of[...s])try{n(...t)}catch(i){queueMicrotask(()=>{throw i})}}};var O="0123456789ABCDEFGHJKMNPQRSTVWXYZ";function Tt(r){return r.replace(/[a-z]/g,e=>e.toUpperCase())}function g(r){let e=Tt(r).replace(/[ \-]/g,"").replace(/[IL]/g,"1").replace(/O/g,"0");if(e.length!==6)return null;for(let t of e)if(O.indexOf(t)<0)return null;return e}function P(r){let e=g(r)??r.toUpperCase();return e.length===6?`${e.slice(0,3)} ${e.slice(3)}`:e}var be={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},He=1,Fe=2,m=class extends Error{constructor(t,s=""){super(s?`${t}: ${s}`:t);this.code=t;this.name="CodecError"}code},Ie=new TextEncoder,Et=new TextDecoder("utf-8",{fatal:!0});function Oe(r){try{return Et.decode(r)}catch{throw new m("bad_utf8")}}var fe=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)}},ye=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 m("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 H(r,e=0,t=!1){let s=new fe(64);switch(s.u8(be[r.type]),s.u16(e),r.type){case"hi":s.u8(r.proto),s.u16(r.peerId),s.i64(r.lastTick);break;case"welcome_dc":s.i64(r.tick),s.u8(r.tickRate),s.u8(r.mode),s.u16(r.hostId);break;case"cmd":s.u32(r.seq),s.u16(r.bytes.length),s.bytes(r.bytes);break;case"frame":s.i64(r.tick),s.u16(r.commands.length);for(let i of r.commands)s.u16(i.peerId),s.u16(i.bytes.length),s.bytes(i.bytes);break;case"hash":s.i64(r.tick),s.u64(r.hash);break;case"desync":s.i64(r.tick),s.u64(r.hostHash),s.u64(r.yourHash);break;case"snapshot_req":s.i64(r.sinceTick);break;case"snapshot":s.i64(r.tick),s.u32(r.total),s.u8(r.flags);break;case"chunk":s.u32(r.id),s.u16(r.index),s.u16(r.count),s.bytes(r.bytes);break;case"raw":s.bytes(r.bytes);break;case"ping":case"pong":s.u32(r.n),s.u32(r.t);break;case"host_claim":s.u16(r.epoch),s.u16(r.hostId),s.i64(r.tick);break;case"voice":s.u16(r.seq),s.u8(r.flags),s.u8(r.frames.length);for(let i of r.frames)s.u16(i.length),s.bytes(i);break;case"chat":{let i=Ie.encode(r.text);s.u32(r.id),s.i64(r.ts),s.u16(i.length),s.bytes(i);break}case"chat_history":s.u16(r.lines.length);for(let i of r.lines){let o=Ie.encode(i.text);s.u16(i.peerId),s.u32(i.id),s.i64(i.ts),s.u16(o.length),s.bytes(o)}break;case"chat_reject":s.u32(r.id),s.u8(r.reason);break}let n=s.done();if(!t&&n.length>16384)throw new m("too_long",`${n.length} > 16384`);return n}var Ct=new Map(Object.keys(be).map(r=>[be[r],r]));function je(r,e=!1){let t=e?67108864:16384;if(r.length>t)throw new m("too_long",`${r.length} > ${t}`);if(r.length<3)throw new m("too_short","header");let s=new ye(r),n=Ct.get(s.u8()),i=s.u16();if(!n)return null;let o;switch(n){case"hi":o={type:n,proto:s.u8(),peerId:s.u16(),lastTick:s.i64()};break;case"welcome_dc":o={type:n,tick:s.i64(),tickRate:s.u8(),mode:s.u8(),hostId:s.u16()};break;case"cmd":{let a=s.u32();o={type:n,seq:a,bytes:s.bytes(s.u16())};break}case"frame":{let a=s.i64(),l=s.u16(),c=[];for(let h=0;h<l;h++){let p=s.u16();c.push({peerId:p,bytes:s.bytes(s.u16())})}o={type:n,tick:a,commands:c};break}case"hash":o={type:n,tick:s.i64(),hash:s.u64()};break;case"desync":o={type:n,tick:s.i64(),hostHash:s.u64(),yourHash:s.u64()};break;case"snapshot_req":o={type:n,sinceTick:s.i64()};break;case"snapshot":o={type:n,tick:s.i64(),total:s.u32(),flags:s.u8()};break;case"chunk":o={type:n,id:s.u32(),index:s.u16(),count:s.u16(),bytes:s.rest()};break;case"raw":o={type:n,bytes:s.rest()};break;case"ping":case"pong":o={type:n,n:s.u32(),t:s.u32()};break;case"host_claim":o={type:n,epoch:s.u16(),hostId:s.u16(),tick:s.i64()};break;case"voice":{let a=s.u16(),l=s.u8(),c=s.u8(),h=[];for(let p=0;p<c;p++)h.push(s.bytes(s.u16()));o={type:n,seq:a,flags:l,frames:h};break}case"chat":{let a=s.u32(),l=s.i64();o={type:n,id:a,ts:l,text:Oe(s.bytes(s.u16()))};break}case"chat_history":{let a=s.u16(),l=[];for(let c=0;c<a;c++){let h=s.u16(),p=s.u32(),E=s.i64();l.push({peerId:h,id:p,ts:E,text:Oe(s.bytes(s.u16()))})}o={type:n,lines:l};break}case"chat_reject":o={type:n,id:s.u32(),reason:s.u8()};break}if(s.pos!==r.length)throw new m("trailing",`${r.length-s.pos} extra bytes`);return{route:i,msg:o}}var B={rate:1,length:2,mutedByHost:3};function ge(r){return r===1?"rate":r===2?"length":r===3?"muted_by_host":"unknown"}var q=256,Ne=3,_t=5e3,Lt=5,$=100,Mt=/[\u0000-\u001f\u007f-\u009f\u202a-\u202e\u2066-\u2069]/g;function Rt(r){return r.replace(Mt,"")}function Ue(r){return new TextEncoder().encode(r).length}var ke=class{buckets=new Map;refillMs=_t/Lt;tryTake(e,t){let s=this.buckets.get(e);return s||this.buckets.set(e,s={tokens:Ne,at:t}),s.tokens=Math.min(Ne,s.tokens+(t-s.at)/this.refillMs),s.at=t,s.tokens<1?!1:(s.tokens-=1,!0)}forget(e){this.buckets.delete(e)}},C=class extends f{constructor(t,s,n){super();this.hub=t;this.wall=s;this.isLocallyMuted=n}hub;wall;isLocallyMuted;filter=t=>t;lines=[];limiter=new ke;hostMuted=new Set;localId=0;stampId=0;get history(){return this.lines}send(t){let s=++this.localId>>>0;return Ue(t)>q*4?(queueMicrotask(()=>this.emit("rejected",s,"length")),s):(this.hub.isHost?this.hostProcess(this.hub.localPeerId,s,t):this.hub.sendToHost({type:"chat",id:s,ts:0,text:t}),s)}hostMute(t,s){if(!this.hub.isHost)throw new Error("only the host can mute chat");s?this.hostMuted.add(t):this.hostMuted.delete(t)}isHostMuted(t){return this.hostMuted.has(t)}onPeerLeft(t){this.limiter.forget(t),this.hostMuted.delete(t)}sendHistory(t){let s=this.lines.slice(-$).map(n=>({...n}));this.hub.sendTo(t,{type:"chat_history",lines:s},0)}handle(t,s,n){switch(n.type){case"chat":this.hub.isHost?this.hostProcess(t,n.id,n.text):this.deliver({peerId:s,id:n.id,ts:n.ts,text:n.text});break;case"chat_history":if(!this.hub.isHost){this.lines=[];for(let i of n.lines.slice(-$))this.push({...i,text:this.safeFilter(i.text)});this.emit("history",this.lines)}break;case"chat_reject":this.emit("rejected",n.id,ge(n.reason));break}}hostProcess(t,s,n){let i=c=>{t===this.hub.localPeerId?this.emit("rejected",s,ge(c)):this.hub.sendTo(t,{type:"chat_reject",id:s,reason:c},0)};if(this.hostMuted.has(t))return i(B.mutedByHost);if(Ue(n)>q)return i(B.length);if(!this.limiter.tryTake(t,this.hub.clock.now()))return i(B.rate);let o=Rt(n);if(o.length===0)return;let a={peerId:t,id:++this.stampId>>>0,ts:this.wall(),text:o},l={type:"chat",id:a.id,ts:a.ts,text:a.text};for(let c of this.hub.connectedPeers())this.hub.sendTo(c,l,t);this.deliver(a)}deliver(t){let s={...t,text:this.safeFilter(t.text)};this.push(s),!this.isLocallyMuted(t.peerId)&&this.emit("message",s)}push(t){this.isLocallyMuted(t.peerId)||(this.lines.push(t),this.lines.length>$&&this.lines.splice(0,this.lines.length-$))}safeFilter(t){try{return this.filter(t)}catch{return t}}};var ve=16e3;function J(r,e,t=ve){let s=Math.max(1,Math.ceil(e.length/t));if(s>65535)throw new RangeError("payload needs more than 65535 chunks");let n=[];for(let i=0;i<s;i++)n.push({type:"chunk",id:r,index:i,count:s,bytes:e.subarray(i*t,Math.min(e.length,(i+1)*t))});return n}var At=8,V=class{parts=new Map;push(e){if(e.count===0||e.index>=e.count)return null;let t=this.parts.get(e.id);if(!t||t.count!==e.count){for(this.parts.delete(e.id);this.parts.size>=At;)this.parts.delete(this.parts.keys().next().value);t={count:e.count,got:0,parts:new Array(e.count),bytes:0},this.parts.set(e.id,t)}if(t.parts[e.index])return null;if(t.parts[e.index]=e.bytes,t.got++,t.bytes+=e.bytes.length,t.bytes>67108864)throw this.parts.delete(e.id),new m("too_long",`chunk set over ${67108864} bytes`);if(t.got<t.count){for(;this.pendingBytes>67108864&&this.parts.size>1;)for(let i of this.parts.keys())if(i!==e.id){this.parts.delete(i);break}return null}this.parts.delete(e.id);let s=new Uint8Array(t.bytes),n=0;for(let i of t.parts)s.set(i,n),n+=i.length;return s}get pending(){return this.parts.size}get pendingBytes(){let e=0;for(let t of this.parts.values())e+=t.bytes;return e}clear(){this.parts.clear()}};function De(){return typeof CompressionStream<"u"&&typeof DecompressionStream<"u"}async function Be(r,e,t=1/0){let s=e.writable.getWriter();s.write(r).then(()=>s.close()).catch(()=>{});let n=[],i=e.readable.getReader(),o=0;for(;;){let{done:c,value:h}=await i.read();if(c)break;if(n.push(h),o+=h.length,o>t)throw i.cancel().catch(()=>{}),new m("too_long",`snapshot inflates to more than ${t} bytes`)}let a=new Uint8Array(o),l=0;for(let c of n)a.set(c,l),l+=c.length;return a}function $e(r){return Be(r,new CompressionStream("deflate"))}function qe(r,e=67108864){return Be(r,new DecompressionStream("deflate"),e)}var Ot=3,Ht=3,Ft=3e3,jt=1e4,Nt=256,Ut=1024,_=class extends f{constructor(t,s){super();this.hub=t;this.hashEveryTicks=Math.max(1,s.hashEveryTicks),this.historyMax=s.historyFrames??Math.max(60,t.tickRate*30),this.lastFrameRx=t.clock.now()}hub;snapshot=null;restore=null;hashEveryTicks;chunkSize=ve;historyMax;queue=[];history=[];lastFrameTick=-1;consumedTick=0;cmdSeq=1;awaitingSnapshot=!1;lastFrameRx=0;lastSnapshotReq=-1/0;restoreFailures=0;hosting=!1;_started=!1;pending=[];_droppedCommands=0;ready=new Set;snapshotting=new Set;cancelTicker=null;baseTime=0;emitted=0;ownHashes=new Map;peerHashes=new Map;desyncs=new Map;lastHashRx=new Map;staleUpTo=new Map;pendingSnapshot=null;get droppedCommands(){return this._droppedCommands}get started(){return this._started}get framesBuffered(){return this.queue.length}get catchingUp(){return this.queue.length>2}get currentTick(){return this.consumedTick}get lastTick(){return Math.max(0,this.lastFrameTick)}get hiTick(){return this.lastFrameTick}submit(t){this.hosting?this.pending.push({peerId:this.hub.localPeerId,bytes:t.slice()}):this.hub.sendToHost({type:"cmd",seq:this.cmdSeq++>>>0,bytes:t})}nextFrame(){let t=this.queue.shift()??null;return t&&(this.consumedTick=t.tick),t}reportHash(t,s){if(!(this.awaitingSnapshot||t<=0||t%this.hashEveryTicks!==0))if(this.hosting){this.ownHashes.set(t,s),this.prune(this.ownHashes);let n=this.peerHashes.get(t);if(n){this.peerHashes.delete(t);for(let[i,o]of[...n].sort((a,l)=>a[0]-l[0]))this.compare(i,t,s,o)}}else this.hub.sendToHost({type:"hash",tick:t,hash:s})}hostIdle(){this.hosting=!0,this.lastFrameTick<0&&(this.lastFrameTick=0)}hostStart(){this.hostIdle(),this._started=!0,this.emitted=0,this.baseTime=this.hub.clock.now(),this.cancelTicker?.();let t=1e3/this.hub.tickRate;this.cancelTicker=this.hub.clock.setInterval(()=>this.pump(t),Math.max(1,Math.floor(t/2)))}becomeHost(){this.pending=[],this.ready.clear(),this.snapshotting.clear(),this.ownHashes.clear(),this.peerHashes.clear(),this.desyncs.clear(),this.awaitingSnapshot=!1,this.hostIdle(),this._started&&this.hostStart()}becomeJoiner(){this.hosting=!1,this.cancelTicker?.(),this.cancelTicker=null,this.pending=[],this.ready.clear(),this.lastFrameRx=this.hub.clock.now()}markStarted(){this._started||(this._started=!0,this.emit("started"))}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(),n=Math.floor((s-this.baseTime+1)/t)-this.emitted;n>10&&(this.baseTime=s-this.emitted*t,n=1);for(let i=0;i<n;i++)this.emitFrame()}emitFrame(){let t=this.pending.splice(0).sort((i,o)=>i.peerId-o.peerId),s={tick:this.lastFrameTick+1,commands:t};this.emitted++,this.lastFrameTick=s.tick,this.record(s),this.queue.push(s);let n={type:"frame",tick:s.tick,commands:t};for(let i of this.ready)this.hub.sendTo(i,n,0);this.checkPeerSilence()}record(t){this.history.push(t),this.history.length>this.historyMax&&this.history.splice(0,this.history.length-this.historyMax)}checkPeerSilence(){if(this.ownHashes.size===0)return;let t=this.hub.clock.now(),s=Math.max(jt,this.hashEveryTicks/this.hub.tickRate*1e3*3);for(let n of[...this.ready]){let i=this.lastHashRx.get(n)??t;t-i>s&&this.hub.dropPeer(n,"timeout")}}handle(t,s){switch(s.type){case"cmd":if(this.hosting){let n=0;for(let i of this.pending)i.peerId===t&&n++;n>=Nt?this._droppedCommands++:this.pending.push({peerId:t,bytes:s.bytes})}break;case"frame":this.hosting||this.onFrame(s.tick,s.commands);break;case"hash":this.hosting&&this.onPeerHash(t,s.tick,s.hash);break;case"desync":this.hosting||(this.awaitingSnapshot=!0,this.queue=[],this.emit("desync",{tick:s.tick,hostHash:s.hostHash,yourHash:s.yourHash}));break;case"snapshot_req":this.hosting&&this.sendSnapshot(t);break;case"snapshot":this.hosting||(this.markStarted(),this.pendingSnapshot={tick:s.tick,total:s.total,flags:s.flags});break}}get snapshotPending(){return this.pendingSnapshot!==null}onSnapshotPayload(t){let s=this.pendingSnapshot;if(this.pendingSnapshot=null,!!s){if(t.length!==s.total){this.awaitingSnapshot=!1,this.requestSnapshot();return}this.applySnapshot(s,t)}}onFrame(t,s){if(this.lastFrameRx=this.hub.clock.now(),this.markStarted(),this.awaitingSnapshot)return;let n=Math.max(0,this.lastFrameTick)+1;if(t<n)return;if(t>n){this.requestSnapshot();return}let i={tick:t,commands:s};this.lastFrameTick=t,this.record(i),this.queue.push(i),this.queue.length>this.hub.tickRate*3&&this.requestSnapshot()}requestSnapshot(){let t=this.hub.clock.now();this.awaitingSnapshot&&t-this.lastSnapshotReq<2e3||(this.lastSnapshotReq=t,this.awaitingSnapshot=!0,this.queue=[],this.hub.sendToHost({type:"snapshot_req",sinceTick:this.lastFrameTick}))}pokeHost(){this.hub.sendToHost({type:"snapshot_req",sinceTick:this.lastFrameTick})}async applySnapshot(t,s){try{let n=s;if(t.flags&1&&(n=await qe(s)),!this.restore)throw new Error("lockstep.restore is not set; cannot apply a snapshot");this.restore(t.tick,n),this.queue=[],this.history=[],this.lastFrameTick=t.tick,this.consumedTick=t.tick,this.awaitingSnapshot=!1,this.restoreFailures=0,this.markStarted(),this.lastFrameRx=this.hub.clock.now(),this.emit("restored",t.tick)}catch(n){this.hub.reportError(n instanceof Error?n:new Error(String(n))),++this.restoreFailures<=Ht?(this.awaitingSnapshot=!1,this.lastSnapshotReq=-1/0,this.requestSnapshot()):this.hub.reportError(new Error(`snapshot restore failed ${this.restoreFailures} times; giving up`))}}onPeerHi(t,s){this.lastHashRx.set(t,this.hub.clock.now());let n=this.lastFrameTick,i=this.history.length?this.history[0].tick:n+1;if(s<0){this._started&&n>0?this.sendSnapshot(t):this.ready.add(t);return}if(s<=n&&s+1>=i){for(let o of this.history)o.tick>s&&this.hub.sendTo(t,{type:"frame",tick:o.tick,commands:o.commands},0);this.ready.add(t)}else this.sendSnapshot(t)}onPeerHash(t,s,n){if(this.lastHashRx.set(t,this.hub.clock.now()),this.isStale(t,s))return;let i=this.ownHashes.get(s);if(i===void 0){let o=this.peerHashes.get(s);o||this.peerHashes.set(s,o=new Map),o.set(t,n),this.prune(this.peerHashes);return}this.compare(t,s,i,n)}compare(t,s,n,i){if(n===i||this.isStale(t,s)||!this.ready.has(t)||this.snapshotting.has(t))return;let o=(this.desyncs.get(t)??0)+1;if(this.desyncs.set(t,o),o>=Ot){this.hub.dropPeer(t,"desync");return}this.hub.sendTo(t,{type:"desync",tick:s,hostHash:n,yourHash:i},0),this.sendSnapshot(t,s)}isStale(t,s){let n=this.staleUpTo.get(t);return!!n&&s>n.from&&s<=n.to}async sendSnapshot(t,s=1/0){if(!this.snapshotting.has(t)){if(!this.snapshot){this.hub.reportError(new Error("lockstep.snapshot is not set; cannot serve a snapshot"));return}this.snapshotting.add(t),this.ready.delete(t);try{let n=this.consumedTick,i=this.snapshot(),o=i,a=0;if(i.length>=Ut&&De()&&(o=await $e(i),a=1),!this.snapshotting.has(t))return;this.hub.sendTo(t,{type:"snapshot",tick:n,total:o.length,flags:a},0);for(let l of J(this.hub.nextChunkId(),o,this.chunkSize))this.hub.sendTo(t,l,0);for(let l of this.history)l.tick>n&&this.hub.sendTo(t,{type:"frame",tick:l.tick,commands:l.commands},0);s!==1/0?this.staleUpTo.set(t,{from:s,to:this.lastFrameTick}):this.staleUpTo.delete(t);for(let l of this.peerHashes.values())l.delete(t);this.ready.add(t),this.lastHashRx.set(t,this.hub.clock.now())}catch(n){this.hub.reportError(n instanceof Error?n:new Error(String(n)))}finally{this.snapshotting.delete(t)}}}prune(t){if(t.size<=64)return;let s=[...t.keys()].sort((n,i)=>n-i);for(let n of s.slice(0,s.length-64))t.delete(n)}get msSinceFrame(){return this.hub.clock.now()-this.lastFrameRx}hostSilent(){return!this.hosting&&this._started&&this.msSinceFrame>Ft}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 Ve(r,e){let t=null;for(let s of r)s!==e&&(t===null||s<t)&&(t=s);return t}function Dt(r,e){let t=r-e&65535;return t!==0&&t<32768}function Je(r,e){return r.epoch!==e.epoch?Dt(r.epoch,e.epoch):r.hostId<e.hostId}var d=class extends Error{constructor(t,s,n){super(s?`${t}: ${s}`:t);this.code=t;this.data=n;this.name="OneLobbyError"}code;data},Ke="lk1",Bt=1e4,$t=3e4,K=class{constructor(e,t,s){this.url=e;this.factory=t;this.clock=s}url;factory;clock;socket=null;waiters=[];lastRx=0;cancelPing=null;pingN=0;closed=!1;handler=null;backlog=[];onClose=null;get onMessage(){return this.handler}set onMessage(e){for(this.handler=e;e&&this.handler===e&&this.backlog.length;)e(this.backlog.shift())}connect(){return new Promise((e,t)=>{let s;try{s=this.factory(this.url)}catch(n){t(new d("connect_failed",String(n)));return}this.socket=s,s.onopen=()=>{this.lastRx=this.clock.now(),this.cancelPing=this.clock.setInterval(()=>this.tick(),Bt),e()},s.onerror=()=>{this.lastRx||t(new d("connect_failed",`cannot reach ${this.url}`))},s.onmessage=n=>{this.lastRx=this.clock.now();let i;try{i=JSON.parse(String(n.data))}catch{return}if(!i||typeof i.t!="string")return;if(i.t==="ping"){this.send({t:"pong",n:i.n});return}if(i.t==="pong")return;let o=this.waiters.findIndex(a=>a.types.includes(i.t)||i.t==="error");if(o>=0){let[a]=this.waiters.splice(o,1);i.t==="error"?a.reject(qt(i)):a.resolve(i);return}this.handler?this.handler(i):this.backlog.length<256&&this.backlog.push(i)},s.onclose=()=>{this.lastRx||t(new d("connect_failed",`cannot reach ${this.url}`)),this.shutdown()}})}tick(){if(this.clock.now()-this.lastRx>$t){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 qt(r){return new d(String(r.code??"error"),r.msg===void 0?void 0:String(r.msg),r.data)}var z=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 ze(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 W=class{mode="pushToTalk";ptt=!1;vadThreshold=.02;lastLoud=-1/0;open=!1;update(e,t){let s;this.mode==="pushToTalk"?s=this.ptt:(e>=this.vadThreshold&&(this.lastLoud=t),s=t-this.lastLoud<=300);let n=s===this.open?null:s?"open":"close";return this.open=s,{send:s,edge:n}}get isOpen(){return this.open}};function we(r,e){let t=r-e&65535;return t>=32768?t-65536:t}var Y=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(we(e.seq,this.expected)<0)return;this.packets.set(e.seq,e),e.flags&1?this.endSeq=e.seq:this.endSeq!==null&&we(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&&we(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}},G=class{speakers=new Map;windows=new Map;burstTimeoutMs=500;accept(e,t,s,n){if(t>1024)return!1;let i=this.windows.get(e);for(i||this.windows.set(e,i=[]);i.length&&n-i[0]>=1e3;)i.shift();if(i.length>=30)return!1;for(let[o,a]of this.speakers)n-a>this.burstTimeoutMs&&this.speakers.delete(o);return!this.speakers.has(e)&&this.speakers.size>=8?!1:(i.push(n),s&1?this.speakers.delete(e):this.speakers.set(e,n),!0)}get activeSpeakers(){return this.speakers.size}forget(e){this.speakers.delete(e),this.windows.delete(e)}};var Vt=300,L=class extends f{constructor(t,s){super();this.hub=t;this.deps=s}hub;deps;mode="pushToTalk";muted=!1;inputLevel=0;bitrate=24e3;_enabled=!1;gate=new W;packetizer=new z;relay=new G;speakers=new Map;peerMuted=new Set;gains=new Map;encoder=null;source=null;sink=null;cancelPlayout=null;endTimer=null;sending=!1;get enabled(){return this._enabled}set enabled(t){t!==this._enabled&&(this._enabled=t,t?this.start():this.stop())}pushToTalk(t){this.gate.ptt=t}isMutedPeer(t){return this.peerMuted.has(t)}setMuted(t,s){s?this.peerMuted.add(t):this.peerMuted.delete(t)}setVolume(t,s){this.gains.set(t,Math.min(2,Math.max(0,s)))}volumeOf(t){return this.gains.get(t)??1}async start(){try{if(!this.deps.codec)throw new Error("no Opus codec available: supply one through the voice.codec option (WASM fallback)");if(!this.deps.createSink)throw new Error("no audio output available");this.sink=this.deps.createSink(),this.cancelPlayout=this.hub.clock.setInterval(()=>this.playoutTick(),20),this.deps.createSource&&(this.encoder=this.deps.codec.createEncoder({bitrate:this.bitrate},t=>this.onEncoded(t)),this.source=this.deps.createSource(),await this.source.start(t=>this.onCapturedFrame(t,this.hub.clock.now())))}catch(t){this._enabled=!1,this.stop(),this.emit("error",{message:t instanceof Error?t.message:String(t),cause:t})}}stop(){this.sending&&this.closeBurst(),this.source?.stop(),this.source=null,this.encoder?.close(),this.encoder=null,this.cancelPlayout?.(),this.cancelPlayout=null,this.sink?.close(),this.sink=null;for(let[t,s]of this.speakers)s.decoder?.close(),s.talking&&this.emit("speaking",t,!1);this.speakers.clear()}onCapturedFrame(t,s){let n=ze(t);if(this.inputLevel=n,!this._enabled||this.muted){this.sending&&this.closeBurst();return}let i=this.gate.update(n,s);i.send?(this.sending=!0,this.endTimer?.(),this.endTimer=null,this.encoder?.encode(t)):i.edge==="close"&&this.sending&&this.closeBurst()}closeBurst(){this.sending=!1,this.endTimer?.(),this.endTimer=this.hub.clock.setTimeout(()=>{this.endTimer=null,this.sendPacket(this.packetizer.endBurst())},40)}sendEncoded(t,s=!1){this.sendPacket(this.packetizer.packet(t,s?1:0))}onEncoded(t){let s=this.packetizer.push(t);s&&this.sendPacket(s)}sendPacket(t){let s={type:"voice",seq:t.seq,flags:t.flags,frames:t.frames};if(this.hub.isHost)for(let n of this.hub.connectedPeers())this.hub.sendTo(n,s,this.hub.localPeerId,"voice");else this.hub.sendToHost(s,0,"voice")}handle(t,s,n){if(n.type!=="voice")return;let i=n.frames.reduce((l,c)=>l+c.length+2,0)+7,o=this.hub.clock.now(),a=s;if(this.hub.isHost){if(a=t,!this.relay.accept(t,i,n.flags,o))return;for(let l of this.hub.connectedPeers())l!==t&&this.hub.sendTo(l,n,t,"voice")}this.emit("packet",a,n.seq,n.flags,n.frames),!(!this._enabled||a===this.hub.localPeerId||this.peerMuted.has(a))&&this.receivePacket(a,{seq:n.seq,flags:n.flags,frames:n.frames},o)}receivePacket(t,s,n){let i=this.speakers.get(t);i||(i={jitter:new Y,decoder:null,talking:!1,lastHeard:n},this.speakers.set(t,i)),i.lastHeard=n,i.jitter.push(s),!i.talking&&s.frames.length>0&&(i.talking=!0,this.emit("speaking",t,!0))}playoutTick(){let t=this.hub.clock.now();for(let[s,n]of this.speakers){let i=n.jitter.pull();i.kind==="frame"||i.kind==="plc"?(!n.decoder&&this.deps.codec&&(n.decoder=this.deps.codec.createDecoder(o=>this.sink?.play(s,o,this.volumeOf(s)))),n.decoder?.decode(i.kind==="frame"?i.opus:null)):i.kind==="idle"&&n.talking?(n.talking=!1,this.emit("speaking",s,!1)):n.talking&&t-n.lastHeard>Vt+100&&!n.jitter.active&&(n.talking=!1,this.emit("speaking",s,!1))}}onPeerLeft(t){this.relay.forget(t);let s=this.speakers.get(t);s&&(s.decoder?.close(),s.talking&&this.emit("speaking",t,!1),this.speakers.delete(t))}close(){this._enabled=!1,this.stop(),this.endTimer?.()}static END=1};function j(r){return r==="left"||r==="disconnected"?1e3:r==="host_left"||r==="closed"?1001:r==="kicked"?4e3:1006}var Ye=globalThis.setImmediate??(r=>{setTimeout(r,0)}),Jt=new TextEncoder,Kt=new TextDecoder,X=class{constructor(e,t,s,n){this.peerId=e;this.out=t;this.binary=s;this.onLocalClose=n}peerId;out;binary;onLocalClose;readyState=0;bufferedAmount=0;openHandler=null;openPending=!1;onmessage=null;onclose=null;onerror=null;detach=()=>{};send(e){this.readyState===1&&this.out(typeof e=="string"?Jt.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&&Ye(()=>this.fireOpen())}open(e=!1){this.readyState===0&&(this.readyState=1,this.openPending=!0,e?Ye(()=>this.fireOpen()):this.fireOpen())}fireOpen(){this.readyState!==1||!this.openPending||!this.openHandler||(this.openPending=!1,this.openHandler({type:"open"}))}deliver(e){this.readyState===1&&this.onmessage?.({type:"message",data:this.binary?e:Kt.decode(e)})}closed(e,t){this.readyState!==3&&(this.readyState=3,this.detach(),this.onclose?.({type:"close",code:e,reason:t,wasClean:e===1e3||e===1001}))}};function Ge(r,e={}){if(r.isHost)throw new Error("asWebSocket() is for joiners; the host uses lobby.onConnection()");let t=e.channel??"reliable",s=new X(r.hostPeerId,i=>r.send(r.hostPeerId,i,{channel:t}),!!e.binary,()=>{r.leave()}),n=[r.on("message",(i,o)=>{i===r.hostPeerId&&s.deliver(o)}),r.on("sessionEnded",(i,o)=>s.closed(o?.code??j(i),o?.reason??i)),r.on("hostChanged",()=>s.closed(1001,"host_changed"))];return s.detach=()=>n.forEach(i=>i()),r.connectedPeers.includes(r.hostPeerId)?s.open(!0):r.ready.then(()=>s.open(!0),()=>{}),s}function Xe(r,e,t={}){if(!r.isHost)throw new Error("onConnection() is for the host; a joiner uses lobby.asWebSocket()");let s=t.channel??"reliable",n=new Map,i=!1,o=l=>{if(l===r.localPeerId)return;let c=n.get(l);if(c||i)return c;c=new X(l,p=>r.send(l,p,{channel:s}),!!t.binary,()=>{});let h=c;return h.detach=()=>{n.get(l)===h&&n.delete(l)},n.set(l,h),h.open(),e(h),h},a=[r.on("peerReady",l=>{o(l.peerId)}),r.on("message",(l,c)=>o(l)?.deliver(c)),r.on("peerLeft",(l,c,h)=>n.get(l.peerId)?.closed(h?.code??(c==="left"?1001:j(c)),h?.reason??c)),r.on("sessionEnded",l=>{for(let c of[...n.values()])c.closed(l==="left"?1001:j(l),l);a.forEach(c=>c())})];for(let l of r.connectedPeers)o(l);return()=>{i=!0}}var Se=6e3,Wt=3e3;function Z(r){let e=typeof r.name=="string"?r.name.trim():"";return e&&e!=="Player"?r:{...r,name:`Player ${r.peerId}`}}function Yt(r){return r.some(e=>(Array.isArray(e.urls)?e.urls:[e.urls]).some(t=>/^turns?:/i.test(String(t))))}var Gt=250,Xt=2e3,M=class extends f{code;mode;tickRate;lockstep;chat;voice;localPeerId;hostPeerId;phase;meta;resumeToken;rtt=0;joinCode="";joinCodeKind="none";directAddresses=[];portMapping="";signaling;cfg;roster=new Map;links=new Map;earlyIce=new Map;connected=new Set;hiSentTo=new Set;epoch=0;ended=!1;serverUp=!0;cancelWatchdog=null;cancelPing=null;pingN=0;chunkId=1;reassemblers=new Map;hub;outbox=new Map;markReady=()=>{};failReady=()=>{};isReady=!1;cancelReadyTimer=null;relayRetry=!1;lastHeard=new Map;lingering=new Set;rate=new Map;ready=new Promise((e,t)=>{this.markReady=()=>{this.isReady||(this.isReady=!0,this.cancelReadyTimer?.(),this.cancelReadyTimer=null,e())},this.failReady=t});constructor(e){super(),this.cfg=e,this.signaling=e.signaling;let t=e.state;this.code=t.code,this.mode=t.mode,this.tickRate=t.tickRate,this.localPeerId=t.you,this.hostPeerId=t.hostId,this.phase=t.phase,this.meta={...t.meta},this.resumeToken=t.resumeToken;for(let n of t.peers)this.roster.set(n.peerId,Z(n));this.ready.catch(()=>{});let s=this;this.hub={get clock(){return s.cfg.clock},get localPeerId(){return s.localPeerId},get hostPeerId(){return s.hostPeerId},get isHost(){return s.isHost},get tickRate(){return s.tickRate},connectedPeers:()=>s.connectedPeerIds(),sendTo:(n,i,o,a)=>s.sendTo(n,i,o??0,a),sendToHost:(n,i,o)=>{s.isHost||s.sendTo(s.hostPeerId,n,i??0,o)},dropPeer:(n,i)=>s.dropPeer(n,i),reportError:n=>s.reportError(n),nextChunkId:()=>s.chunkId++>>>0},this.lockstep=new _(this.hub,{hashEveryTicks:e.hashEveryTicks}),this.chat=new C(this.hub,e.wall,n=>this.voice.isMutedPeer(n)),this.voice=new L(this.hub,e.voice),this.lockstep.on("started",()=>this.setPhase("in_game")),this.lockstep.on("error",n=>this.reportError(n)),this.signaling.onMessage=n=>this.onSignaling(n),this.signaling.onClose=()=>this.onServerClosed(),this.isHost?this.markReady():this.armReadyTimer(),this.isHost&&this.mode==="lockstep"&&(this.phase==="in_game"?this.lockstep.hostStart():this.lockstep.hostIdle()),this.cancelWatchdog=e.clock.setInterval(()=>this.watchdog(),Gt),this.cancelPing=e.clock.setInterval(()=>this.pingHost(),Xt)}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 Ge(this,e)}onConnection(e,t){return Xe(this,e,t)}set snapshotChunkSize(e){this.lockstep.chunkSize=e}get hostEpoch(){return this.epoch}_offer(e){return this.offerTo(e),this.links.get(e)}_answer(e){return this.makeLink(e)}_begin(){}async start(){if(!this.isHost)throw new Error("only the host can start the game");this.phase!=="in_game"&&(this.signaling.send({t:"phase",phase:"in_game"}),this.setPhase("in_game"),this.mode==="lockstep"&&this.lockstep.hostStart())}async setMeta(e){if(!this.isHost)throw new Error("only the host can set metadata");this.meta={...e},this.signaling.send({t:"meta",meta:e}),this.emit("metaChanged",this.meta)}async kick(e,t={}){if(!this.isHost)throw new Error("only the host can kick");let{ban:s=!0,code:n,reason:i}=t;if(n!==void 0&&!(n===1e3||Number.isInteger(n)&&n>=3e3&&n<=4999))throw new RangeError("close code must be 1000 or 3000-4999");if(i!==void 0&&i.length>120)throw new RangeError("close reason must be at most 120 characters");let o={t:"kick",peerId:e};s||(o.ban=!1),n!==void 0&&(o.closeCode=n),i!==void 0&&(o.closeReason=i),this.signaling.send(o);let a=n!==void 0||i!==void 0?{code:n,reason:i}:void 0;this.removePeer(e,s?"kicked":"disconnected",a,!0)}disconnect(e,t,s){return this.kick(e,{ban:!1,code:t,reason:s})}async approve(e,t){if(!this.isHost)throw new Error("only the host can approve joins");this.signaling.send({t:"approve",peerId:e,ok:t})}async leave(){this.ended||(this.signaling.send({t:"leave"}),this.teardown("left"))}send(e,t,s={}){let n=s.channel??"reliable";this.isHost?this.sendTo(e,{type:"raw",bytes:t},this.localPeerId,n):this.sendTo(this.hostPeerId,{type:"raw",bytes:t},e,n)}broadcast(e,t={}){let s=t.channel??"reliable";if(this.isHost)for(let n of this.roster.keys())n!==this.localPeerId&&this.sendTo(n,{type:"raw",bytes:e},this.localPeerId,s);else this.sendTo(this.hostPeerId,{type:"raw",bytes:e},0,s)}channelFor(e){return e.type==="voice"?"voice":e.type==="ping"||e.type==="pong"?"fast":"reliable"}sendTo(e,t,s,n){let i=this.links.get(e),o=n??this.channelFor(t),a=t.type==="raw"||t.type==="chat"||t.type==="cmd";if(!(!!i?.isOpen&&(!a||!this.isHost||this.connected.has(e)))){let c=this.roster.has(e)||e===this.hostPeerId;if(a&&o==="reliable"&&c&&!this.ended){let h=this.outbox.get(e);h||this.outbox.set(e,h=[]),h.length>=1e3&&h.shift(),h.push({msg:t,route:s,channel:n})}return}if(i)try{let c=o,h;try{h=H(t,s)}catch(p){if(!(p instanceof m)||p.code!=="too_long")throw p;h=H(t,s,!0)}if(h.length>16384)for(let p of J(this.chunkId++>>>0,h))i.send(c,H(p,s));else i.send(c,h)}catch(c){this.reportError(c instanceof Error?c:new Error(String(c)))}}connectedPeerIds(){return this.isHost?[...this.connected].filter(t=>this.links.get(t)?.isOpen).sort((t,s)=>t-s):this.links.get(this.hostPeerId)?.isOpen?[this.hostPeerId]:[]}reportError(e){this.errorSink?.(e)}errorSink=null;seatSink=null;armReadyTimer(){let e=this.cfg.readyTimeoutMs??0;if(!(e>0))return;let t=!this.cfg.relayOnly&&Yt(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 n=this.cfg.links.create({remoteId:e,iceServers:this.cfg.ice,relayOnly:t});return this.links.set(e,n),n.onSignal=i=>this.signaling.send({t:"signal",to:e,...i}),n.onOpen=()=>this.onLinkOpen(e,n),n.onClose=i=>this.onLinkClosed(e,n,i),n.onMessage=(i,o)=>this.onData(e,i,o),n.onPath=i=>this.signaling.send({t:"path",path:i}),n}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",H(this.lockstep.hiMessage(this.localPeerId))),this.markReady(),this.emit("ready"),this.emit("peerReady",this.roster.get(e.remoteId)??{peerId:e.remoteId,name:""}),this.flush(e.remoteId))}onLinkClosed(e,t,s){if(this.links.get(e)===t&&(this.links.delete(e),this.hiSentTo.delete(t),!this.ended)){if(this.isHost)this.connected.delete(e),this.lockstep.onPeerLeft(e),this.voice.onPeerLeft(e),!this.serverUp&&this.roster.has(e)&&this.removePeer(e,"timeout");else if(e===this.hostPeerId){if(!this.isReady&&this.cancelReadyTimer)return;this.handleHostLost("link closed")}}}onData(e,t,s,n=!1){let i;try{i=je(s,n)}catch(l){this.reportError(l instanceof Error?l:new Error(String(l)));return}if(!i)return;n||this.lastHeard.set(e,this.cfg.clock.now());let{msg:o,route:a}=i;switch(o.type){case"hi":this.isHost&&this.onHi(e,o.lastTick);break;case"welcome_dc":this.isHost||(o.hostId!==this.hostPeerId&&this.setHost(o.hostId),this.lockstep.touch());break;case"cmd":case"frame":case"hash":case"desync":case"snapshot_req":case"snapshot":(e===this.hostPeerId||this.isHost)&&this.lockstep.handle(e,o);break;case"chunk":{if(n)break;let l=this.reassemblers.get(e);l||this.reassemblers.set(e,l=new V);let c;try{c=l.push(o)}catch(h){this.reportError(h instanceof Error?h:new Error(String(h)));break}if(!c)break;this.lockstep.snapshotPending&&e===this.hostPeerId?this.lockstep.onSnapshotPayload(c):this.onData(e,t,c,!0);break}case"chat":case"chat_history":case"chat_reject":this.chat.handle(e,a,o);break;case"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"?He:Fe,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,n){if(!this.isHost){this.emit("message",t,s,n);return}if(this.allowRaw(e))if((t===0||t===this.localPeerId)&&this.emit("message",e,s,n),t===0)for(let i of this.connectedPeerIds())i!==e&&this.sendTo(i,{type:"raw",bytes:s},e,n);else t!==this.localPeerId&&this.sendTo(t,{type:"raw",bytes:s},e,n)}allowRaw(e){let t=this.cfg.rateLimit;if(!t||!(t.messages>0)||!(t.perMs>0))return!0;let s=this.cfg.clock.now(),n=this.rate.get(e);return(!n||s-n.start>=t.perMs)&&this.rate.set(e,n={start:s,n:0}),++n.n<=t.messages}setHost(e){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 n=this.links.get(t);n&&(n.onClose=null,n.onMessage=null,n.close(),this.links.delete(t),this.hiSentTo.delete(n)),(Ve([...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 n={epoch:this.epoch,hostId:this.hostPeerId},i={epoch:t,hostId:s};if(s===this.hostPeerId&&t===this.epoch&&!this.isHost){let l=this.links.get(e);l&&this.sendHi(l);return}if(!Je(i,n))return;let o=this.isHost;if(this.epoch=t,o){for(let[l,c]of this.links)l!==e&&(c.onClose=null,c.onMessage=null,c.close(),this.links.delete(l));this.connected.clear(),this.lockstep.becomeJoiner()}this.setHost(s);let a=this.links.get(e);a&&e===s&&this.sendHi(a)}onSignaling(e){switch(e.t){case"peer_joined":{if(!e.peer||this.roster.has(e.peer.peerId))return;let t=Z(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",Z(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,Z(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),n={};if(t.sdp&&(n.sdp=t.sdp),t.ice&&(n.ice=t.ice),t.sdp?.type==="offer"){s=this.makeLink(e);let i=this.earlyIce.get(e)??[];this.earlyIce.delete(e);let o=s.handleSignal(n);for(let a of i)s.handleSignal(a);await o;return}if(!s){if(!n.ice||this.ended||!this.roster.has(e))return;let i=this.earlyIce.get(e)??[];i.length<64&&i.push(n),this.earlyIce.set(e,i);return}await s.handleSignal(n)}removePeer(e,t,s,n=!1){let i=this.roster.get(e);if(e===this.localPeerId){this.teardown(t==="kicked"?"kicked":t);return}i&&this.roster.delete(e),this.earlyIce.delete(e);let o=this.links.get(e);if(o&&e!==this.hostPeerId)if(o.onMessage=null,this.links.delete(e),this.hiSentTo.delete(o),n){this.lingering.add(o);let a=()=>{this.lingering.delete(o)&&(l(),o.onClose=null,o.close())},l=this.cfg.clock.setTimeout(a,Wt);o.onClose=a}else o.onClose=null,o.close();this.connected.delete(e),this.outbox.delete(e),this.lastHeard.delete(e),this.rate.delete(e),this.lockstep.onPeerLeft(e),this.chat.onPeerLeft(e),this.voice.onPeerLeft(e),i&&this.emit("peerLeft",i,t,s),e===this.hostPeerId&&!this.isHost&&this.handleHostLost(t)}dropPeer(e,t){this.signaling.send({t:"kick",peerId:e,ban:!1,closeReason:t}),this.removePeer(e,t,void 0,!0)}setPhase(e){e!=="lobby"&&e!=="in_game"||this.phase!==e&&(this.phase=e,e==="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,Se)),!(this.ended||this.isHost)&&this.mode==="lockstep"&&this.lockstep.hostSilent()&&(this.lockstep.touch(),this.lockstep.pokeHost(),this.handleHostLost("timeout"))}dropSilent(e){let t=this.cfg.clock.now();if(this.isHost)for(let s of[...this.connected]){let n=this.lastHeard.get(s);n!==void 0&&t-n>e&&this.removePeer(s,"timeout")}else if(this.isReady){let s=this.lastHeard.get(this.hostPeerId);s!==void 0&&t-s>e&&(this.lastHeard.delete(this.hostPeerId),this.handleHostLost("timeout"))}}pingHost(){this.ended||this.isHost||this.sendTo(this.hostPeerId,{type:"ping",n:++this.pingN,t:this.cfg.clock.now()>>>0},0,"fast")}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},Pe=r=>new WebSocket(r);function se(){return typeof AudioEncoder<"u"&&typeof AudioDecoder<"u"&&typeof AudioData<"u"}var Ze={createEncoder(r,e){let t=0,s=new AudioEncoder({output:n=>{let i=new Uint8Array(n.byteLength);n.copyTo(i),e(i)},error:n=>console.error("onelobby: AudioEncoder",n)});return s.configure({codec:"opus",sampleRate:48e3,numberOfChannels:1,bitrate:r.bitrate,opus:{frameDuration:2e4,application:"voip",useinbandfec:!0,usedtx:!1,packetlossperc:10}}),{encode(n){let i=new AudioData({format:"f32",sampleRate:48e3,numberOfFrames:n.length,numberOfChannels:1,timestamp:t,data:n});t+=2e4,s.encode(i),i.close()},close(){s.state!=="closed"&&s.close()}}},createDecoder(r){let e=0,t=new AudioDecoder({output:s=>{let n=new Float32Array(s.numberOfFrames);s.copyTo(n,{planeIndex:0,format:"f32-planar"}),s.close(),r(n)},error:s=>console.error("onelobby: AudioDecoder",s)});return t.configure({codec:"opus",sampleRate:48e3,numberOfChannels:1}),{decode(s){if(s===null){r(new Float32Array(960));return}t.decode(new EncodedAudioChunk({type:"key",timestamp:e,data:s})),e+=2e4},close(){t.state!=="closed"&&t.close()}}}},Zt=`
|
|
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(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.3.1",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=`
|
|
20
|
+
`,ee=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([Zt],{type:"text/javascript"}));try{await this.ctx.audioWorklet.addModule(t)}finally{URL.revokeObjectURL(t)}let s=this.ctx.createMediaStreamSource(this.stream),n=new AudioWorkletNode(this.ctx,"onelobby-capture",{numberOfInputs:1,numberOfOutputs:0});n.port.onmessage=i=>e(i.data),s.connect(n),this.ctx.state==="suspended"&&await this.ctx.resume()}stop(){for(let e of this.stream?.getTracks()??[])e.stop();this.ctx?.close(),this.stream=null,this.ctx=null}},te=class{ctx=new AudioContext({sampleRate:48e3});next=new Map;constructor(){this.ctx.resume()}play(e,t,s){if(t.length===0)return;let n=this.ctx.createBuffer(1,t.length,48e3);n.copyToChannel(t,0);let i=this.ctx.createBufferSource();i.buffer=n;let o=this.ctx.createGain();o.gain.value=s,i.connect(o).connect(this.ctx.destination);let a=this.ctx.currentTime,l=this.next.get(e)??0;l<a+.01&&(l=a+.02),i.start(l),this.next.set(e,l+t.length/48e3)}close(){this.ctx.close()}};var Qt={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}},ne=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=n=>{n.candidate&&this.onSignal?.({ice:n.candidate.toJSON()})},this.pc.onconnectionstatechange=()=>{let n=this.pc.connectionState;(n==="failed"||n==="closed"||n==="disconnected")&&(n==="disconnected"?setTimeout(()=>this.pc.connectionState==="disconnected"&&this.fail("disconnected"),4e3):this.fail(n))}}openChannels(){if(!this.channels.reliable)for(let e of["reliable","fast","voice"]){let t=this.pc.createDataChannel(e,Qt[e]);t.binaryType="arraybuffer",t.onopen=()=>{if(++this.opened===3&&!this.closed){this.isOpen=!0,this.onOpen?.();for(let[s,n]of this.early.splice(0))this.closed||this.onMessage?.(s,n);this.reportPath()}},t.onclose=()=>this.fail(`channel ${e} closed`),t.onmessage=s=>{let n=s.data,i=n instanceof ArrayBuffer?new Uint8Array(n):ArrayBuffer.isView(n)?new Uint8Array(n.buffer,n.byteOffset,n.byteLength):new Uint8Array(n);if(!this.isOpen){this.early.length<1e3&&this.early.push([e,i]);return}this.onMessage?.(e,i)},this.channels[e]=t}}startOffer(){(async()=>{try{this.openChannels();let e=await this.pc.createOffer();await this.pc.setLocalDescription(e),this.onSignal?.({sdp:{type:"offer",sdp:e.sdp??""}})}catch(e){this.fail(`offer failed: ${String(e)}`)}})()}async handleSignal(e){try{if(e.sdp){await this.pc.setRemoteDescription({type:e.sdp.type,sdp:e.sdp.sdp}),this.haveRemote=!0;for(let t of this.pendingIce.splice(0))await this.pc.addIceCandidate(t);if(this.extra.length){let t=/^a=mid:(\S+)/m.exec(e.sdp.sdp)?.[1]??"0";for(let s of this.extra.splice(0))await this.pc.addIceCandidate({candidate:s,sdpMid:t,sdpMLineIndex:0})}if(e.sdp.type==="offer"){this.openChannels();let t=await this.pc.createAnswer();await this.pc.setLocalDescription(t),this.onSignal?.({sdp:{type:"answer",sdp:t.sdp??""}})}}e.ice&&(this.haveRemote?await this.pc.addIceCandidate(e.ice):this.pendingIce.push(e.ice))}catch(t){this.fail(`signal failed: ${String(t)}`)}}async reportPath(e=0){if(!(this.closed||e>10)){try{let t=await this.pc.getStats(),s=new Map;t.forEach(a=>s.set(a.id,a));let n;t.forEach(a=>{a.type==="transport"&&a.selectedCandidatePairId&&(n=s.get(a.selectedCandidatePairId))}),n||t.forEach(a=>{a.type==="candidate-pair"&&(a.selected||a.nominated&&a.state==="succeeded")&&(n=a)});let i=n&&s.get(n.localCandidateId),o=n&&s.get(n.remoteCandidateId);if(i&&o){this.onPath?.(i.candidateType==="relay"||o.candidateType==="relay"?"relayed":"direct");return}}catch{}setTimeout(()=>{this.reportPath(e+1)},300)}}send(e,t){let s=this.channels[e];s&&s.readyState==="open"&&s.send(t)}close(){this.fail("closed locally")}fail(e){if(this.closed)return;this.closed=!0;let t=this.isOpen;this.isOpen=!1;try{this.pc.close()}catch{}this.onClose?.(e)}},Qe={create:r=>new ne(r)};function et(r){return{create:e=>new ne(e,r)}}var tt=11;function st(r,e){let t=0;for(let s=e-1;s>=0;s--){let n=Number(r>>BigInt(s)&1n),i=t>>4&1;t=t<<1&31,i^n&&(t^=5)}return t}function es(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 nt(r,e){let t=es(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),n=s<<5n|BigInt(st(s,50)),i="";for(let o=0;o<tt;o++)i=O[Number(n&31n)]+i,n>>=5n;return`${i.slice(0,4)}-${i.slice(4,8)}-${i.slice(8)}`}function rt(r){let e=r.replace(/[ \-]/g,"").replace(/[a-z]/g,t=>t.toUpperCase()).replace(/[IL]/g,"1").replace(/O/g,"0");if(e.length!==tt)return null;for(let t of e)if(O.indexOf(t)<0)return null;return e}function re(r){let e=rt(r);if(!e)return{error:"bad_code"};let t=0n;for(let o of e)t=t<<5n|BigInt(O.indexOf(o));let s=t>>5n;if(st(s,50)!==Number(t&31n))return{error:"bad_code"};if(s>>48n!==0n)return{error:"unknown_version"};let n=Number(s&0xffffn);if(n===0)return{error:"bad_code"};let i=Number(s>>16n&0xffffffffn);return{ip:[i>>>24,i>>>16&255,i>>>8&255,i&255].join("."),port:n}}function ie(r){return!/[.:]/.test(r)&&rt(r)!==null}var ts=/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/,ss=/^(?=.{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 it(r){let e=ts.exec(r);return!!e&&e.slice(1).every(t=>Number(t)<=255)}function ns(r){if(!/^[0-9A-Fa-f:.]+$/.test(r)||r.split("::").length>2||!r.includes(":"))return!1;let e=r.split(":").pop()??"",t=r.split(":").filter((s,n,i)=>s!==""||n>0&&n<i.length-1);return e.includes(".")&&!it(e)?!1:t.every(s=>s===""||s.includes(".")||/^[0-9A-Fa-f]{1,4}$/.test(s))}function ot(r,e=47810){let t=r.trim();if(/^ws:\/\//i.test(t)&&(t=t.slice(5)),t=t.replace(/\/+$/,""),!t)return null;let s,n=null;if(t[0]==="["){let l=t.indexOf("]");if(l<0)return null;s=t.slice(1,l);let c=t.slice(l+1);if(c){if(c[0]!==":")return null;n=c.slice(1)}}else{let l=t.indexOf(":");l>=0&&l===t.lastIndexOf(":")?(s=t.slice(0,l),n=t.slice(l+1)):s=t}let i=e;if(n!==null&&(!/^\d{1,5}$/.test(n)||(i=Number(n),i<1||i>65535)))return null;let o=ns(s);if(!o&&!it(s)&&!ss.test(s))return null;let a=(o?`[${s}]`:s)+`:${i}`;return{host:s,port:i,isIPv6:o,display:a,url:`ws://${a}/`,candidate:`candidate:1 1 UDP 2122317823 ${s} ${i} typ host`}}function lt(r){return r}var T=class extends d{constructor(t,s,n){super("bad_config",`onelobby.json: ${t?t+": ":""}${n}`);this.path=t;this.rule=s}path;rule},y=(r,e)=>({k:"obj",props:r,req:e}),b={k:"bool"},N=(r={})=>({k:"str",...r}),k=(r,e,t)=>({k:"int",min:r,max:e,zeroOr:t}),w=(...r)=>({k:"enum",vals:r}),rs=y({version:{k:"const",val:1},game:y({id:N({pattern:/^[A-Za-z0-9._-]{1,64}$/}),netVersion:N({min:1,max:32}),title:N({max:64})},["id"]),engine:w("web","node-browser","godot-csharp","unity","dotnet"),hosting:y({direct:b,server:b,lobbyServer:N(),directPort:k(1024,65535),openPort:b,relayOnly:b,allowedOrigins:{k:"strarr"}}),lobby:y({maxPlayers:k(2,64),listed:b,approveJoins:b,password:b,lateJoin:b,rejoin:b,joinLink:b,meta:{k:"meta"}}),sync:y({mode:w("raw","lockstep","replication"),tickRate:k(1,120),hashEveryTicks:k(1,1e5)}),players:y({identity:w("name","anonymous","platform"),maxNameLength:k(1,24)}),chat:y({text:b,voice:b,voiceMode:w("pushToTalk","voiceActivity")}),session:y({hostLeaves:w("end","migrate"),pause:w("off","host","anyone"),readyTimeoutMs:k(0,6e5),peerTimeoutMs:k(6e3,6e5,!0),rateLimit:{k:"rate"},runInBackground:b}),platforms:y({steam:{k:"steam"},epic:{k:"epic"},discord:b}),ui:y({kind:w("drop-in","generated","none"),entry:N(),theme:w("light","dark","auto")})},["game"]),at=new Set(["$schema","$comment"]),u=class{constructor(e,t,s){this.path=e;this.rule=t;this.detail=s}path;rule;detail},x=(r,e)=>r?`${r}.${e}`:String(e),A=r=>typeof r=="object"&&r!==null&&!Array.isArray(r);function oe(r,e,t){switch(r.k){case"obj":{if(!A(e))throw new u(t,"type","must be an object");for(let s of r.req??[])if(!(s in e))throw new u(x(t,s),"required","is required");for(let s of Object.keys(e))if(!at.has(s)&&!(s in r.props))throw new u(x(t,s),"unknown_key","unknown key");for(let[s,n]of Object.entries(r.props))s in e&&oe(n,e[s],x(t,s));return}case"str":{if(typeof e!="string")throw new u(t,"type","must be a string");if(r.min!==void 0&&e.length<r.min||r.max!==void 0&&e.length>r.max)throw new u(t,"length",`must be ${r.min??0} to ${r.max??"any"} characters`);if(r.pattern&&!r.pattern.test(e))throw new u(t,"pattern",`must match ${r.pattern.source}`);return}case"int":{if(typeof e!="number"||!Number.isInteger(e))throw new u(t,"type","must be an integer");if(r.zeroOr&&e===0)return;if(r.min!==void 0&&e<r.min||r.max!==void 0&&e>r.max)throw new u(t,"range",`must be ${r.zeroOr?"0 or ":""}${r.min??"any"} to ${r.max??"any"}`);return}case"bool":if(typeof e!="boolean")throw new u(t,"type","must be true or false");return;case"enum":if(typeof e!="string"||!r.vals.includes(e))throw new u(t,"enum",`must be one of ${r.vals.join(", ")}`);return;case"const":if(e!==r.val)throw new u(t,"const",`must be ${r.val}`);return;case"strarr":{if(!Array.isArray(e))throw new u(t,"type","must be an array of strings");e.forEach((s,n)=>{if(typeof s!="string")throw new u(x(t,n),"type","must be a string")});return}case"meta":{if(!A(e))throw new u(t,"type","must be an object of strings");let s=Object.keys(e);if(s.length>16)throw new u(t,"length","at most 16 keys");for(let n of s){if(n.length>64)throw new u(x(t,n),"length","key is longer than 64 characters");let i=e[n];if(typeof i!="string")throw new u(x(t,n),"type","must be a string");if(i.length>64)throw new u(x(t,n),"length","value is longer than 64 characters")}return}case"rate":oe(y({messages:k(1),perMs:k(1)},["messages","perMs"]),e,t);return;case"steam":if(typeof e=="boolean")return;if(!A(e))throw new u(t,"type","must be true, false or { appId }");oe(y({appId:k(1)}),e,t);return;case"epic":{if(typeof e=="boolean")return;if(!A(e))throw new u(t,"type","must be true, false or { productId }");for(let s of Object.keys(e))if(!at.has(s)&&typeof e[s]!="string")throw new u(x(t,s),"type","must be a string");return}}}var v=r=>A(r)?r:{};function Ee(r){if(Array.isArray(r))return r.map(Ee);if(!A(r))return r;let e={};for(let[t,s]of Object.entries(r))t!=="$comment"&&t!=="$schema"&&(e[t]=Ee(s));return e}function ct(r){if(typeof r=="object"&&r!==null&&!Object.isFrozen(r)){Object.freeze(r);for(let e of Object.values(r))ct(e)}return r}function is(r){let e=v(r.game),t=r.engine,s=v(r.hosting),n=v(r.lobby),i=v(r.sync),o=v(r.players),a=v(r.chat),l=v(r.session),c=v(r.platforms),h=v(r.ui),p=i.tickRate??20,E=t==="web";return{version:1,game:{id:e.id,netVersion:e.netVersion??"1",...e.title!==void 0?{title:e.title}:{}},...t!==void 0?{engine:t}:{},hosting:{direct:s.direct??!E,server:s.server??E,lobbyServer:s.lobbyServer??"",directPort:s.directPort??47810,openPort:s.openPort??!0,relayOnly:s.relayOnly??!1,allowedOrigins:s.allowedOrigins??[]},lobby:{maxPlayers:n.maxPlayers??4,listed:n.listed??!1,approveJoins:n.approveJoins??!1,password:n.password??!1,lateJoin:n.lateJoin??!0,rejoin:n.rejoin??!0,joinLink:n.joinLink??(E||t==="node-browser"),meta:n.meta??{}},sync:{mode:i.mode??"raw",tickRate:p,hashEveryTicks:i.hashEveryTicks??p},players:{identity:o.identity??"name",maxNameLength:o.maxNameLength??24},chat:{text:a.text??!0,voice:a.voice??!1,voiceMode:a.voiceMode??"pushToTalk"},session:{hostLeaves:l.hostLeaves??"end",pause:l.pause??"off",readyTimeoutMs:l.readyTimeoutMs??15e3,peerTimeoutMs:l.peerTimeoutMs??0,...l.rateLimit!==void 0?{rateLimit:l.rateLimit}:{},runInBackground:l.runInBackground??!0},platforms:{steam:c.steam??!1,epic:c.epic??!1,discord:c.discord??!1},ui:{kind:h.kind??(E?"drop-in":"generated"),...h.entry!==void 0?{entry:h.entry}:{},theme:h.theme??"auto"}}}function os(r){if(!r.hosting.direct&&!r.hosting.server)throw new u("hosting","hosting_none","at least one of direct, server must be true");if(r.lobby.listed&&r.lobby.approveJoins)throw new u("lobby","listed_and_approve","listed and approveJoins cannot both be true");if(r.engine==="web"&&r.hosting.direct)throw new u("hosting.direct","direct_needs_listener","engine web cannot host direct (a browser cannot listen on a port)");let e=r.engine==="godot-csharp"||r.engine==="unity"||r.engine==="dotnet";if(r.sync.mode==="replication"&&!e)throw new u("sync.mode","replication_needs_csharp","replication needs engine godot-csharp, unity or dotnet");if(r.sync.mode==="replication"&&r.session.hostLeaves==="migrate")throw new u("session.hostLeaves","migrate_with_replication","replication has no host migration; use end");if(r.session.hostLeaves==="migrate"&&!r.hosting.server)throw new u("session.hostLeaves","migrate_needs_server","migrate needs hosting.server (direct lobbies always end)")}function ae(r){let e;try{oe(rs,r,""),e=is(Ee(r)),os(e)}catch(t){throw t instanceof u?new T(t.path,t.rule,t.detail):t}return ct(e)}var _e="onelobby",Le="0.5.0",Me=15e3;function ls(){return typeof navigator<"u"&&!!navigator.mediaDevices?.getUserMedia&&typeof AudioWorkletNode<"u"}function cs(){try{return globalThis.sessionStorage??null}catch{return null}}var Ce=new Set;function ht(r){for(let e of Ce)if(e.resumeToken===r)return!0;return!1}var S=class r extends f{options;socketFactory;linkFactory;clock;wall;voiceCodec;createMic;createSink;storage;blueprint;static fromConfig(e,t={}){let s=ae(e),n=(t.displayName??"").trim();if(n){if(s.players.identity==="anonymous")throw new d("identity_anonymous","this game uses anonymous players: no display names");if(n.length>s.players.maxNameLength)throw new T("displayName","length",`must be at most ${s.players.maxNameLength} characters`)}let i=new r({gameId:s.game.id,netVersion:s.game.netVersion,lobbyServer:t.lobbyServer??s.hosting.lobbyServer,mode:s.sync.mode==="lockstep"?"lockstep":"raw",tickRate:s.sync.tickRate,hashEveryTicks:s.sync.hashEveryTicks,displayName:n,relayOnly:s.hosting.relayOnly,readyTimeoutMs:s.session.readyTimeoutMs,peerTimeoutMs:s.session.peerTimeoutMs,rememberSeat:t.rememberSeat??s.lobby.rejoin,advanced:t.advanced});return i.blueprint=s,i}blueprintHostDefaults(){let e=this.blueprint;if(!e)return{};let t=this.options.displayName;return{name:t?`${t}'s game`:"Game",maxPlayers:e.lobby.maxPlayers,visibility:e.lobby.listed?"public":e.lobby.approveJoins?"private":"unlisted",lateJoin:e.lobby.lateJoin,meta:{...e.lobby.meta},...e.session.rateLimit?{rateLimit:{...e.session.rateLimit}}:{}}}constructor(e){super();let t=e.tickRate??20;this.options={gameId:e.gameId,netVersion:e.netVersion,lobbyServer:e.lobbyServer,mode:e.mode??"lockstep",tickRate:t,hashEveryTicks:e.hashEveryTicks??t,displayName:(e.displayName??"").trim(),relayOnly:e.relayOnly??!1,readyTimeoutMs:e.readyTimeoutMs??Me,peerTimeoutMs:e.peerTimeoutMs??0};let s=e.advanced??{},n=l=>s[l]!==void 0?s[l]:e[l];this.socketFactory=n("socketFactory")??Pe,this.linkFactory=n("linkFactory")??Qe,this.clock=n("clock")??Ae,this.wall=n("wallClock")??(()=>Date.now());let i=n("voiceCodec");this.voiceCodec=i!==void 0?i:se()?Ze:null;let o=n("createMic");this.createMic=o!==void 0?o:ls()?()=>new ee:null;let a=n("createSink");this.createSink=a!==void 0?a:typeof AudioContext<"u"?()=>new te:null,this.storage=e.rememberSeat===!1?null:s.storage!==void 0?s.storage:cs()}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||ht(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:Ke,gameId:this.options.gameId,netVersion:this.options.netVersion,client:{lib:_e,ver:Le,platform:typeof navigator<"u"?"browser":"node"},name:this.options.displayName.slice(0,24),...e?{resumeToken:e}:{}}}async connect(e=this.options.lobbyServer,t=this.socketFactory,s=0){let n=await this.open(e,t,s);try{let i=await n.request(this.helloMsg(),"welcome");return{sig:n,welcome:i}}catch(i){throw n.close(),i}}async open(e,t,s){if(!e.trim()&&t===Pe)throw new d("no_server","no lobby server address (set hosting.lobbyServer or pass lobbyServer)");let n=new K(e,t,this.clock);if(s>0){let i=()=>{},o=new Promise((a,l)=>{i=this.clock.setTimeout(()=>l(new d("direct_unreachable",`no answer from ${e} within ${s} ms`)),s)});try{await Promise.race([n.connect(),o])}catch(a){throw n.close(),a instanceof d&&a.code==="connect_failed"?new d("direct_unreachable",`no answer from ${e}`):a}finally{i()}}else await n.connect();return n}makeLobby(e,t,s,n,i){let o=new M({signaling:e,state:s,ice:t.ice??[],links:n?.links??this.linkFactory,direct:!!n,clock:this.clock,wall:this.wall,hashEveryTicks:this.options.hashEveryTicks,relayOnly:s.relayOnly??this.options.relayOnly,displayName:this.options.displayName,voice:{codec:this.voiceCodec,createSource:this.createMic,createSink:this.createSink},readyTimeoutMs:this.options.readyTimeoutMs,peerTimeoutMs:this.options.peerTimeoutMs,rateLimit:i});return o.errorSink=a=>this.emit("error",a),this.blueprint&&ds(o,this.blueprint),this.storage&&!n&&(Ce.add(o),this.saveSeat(o.code,o.resumeToken),o.seatSink=a=>this.saveSeat(o.code,a),o.on("sessionEnded",()=>{Ce.delete(o),this.clearSeat(o.code)})),o}async host(e){let t={name:"Game",maxPlayers:4,...this.blueprintHostDefaults(),...hs(e)},{sig:s,welcome:n}=await this.connect();try{let i=await s.request({t:"create",name:t.name,maxPlayers:t.maxPlayers,visibility:t.visibility??"unlisted",mode:this.options.mode,tickRate:this.options.tickRate,lateJoin:t.lateJoin??!0,meta:t.meta??{},...this.options.relayOnly?{relayOnly:!0}:{}},"lobby");return this.makeLobby(s,n,i,void 0,t.rateLimit)}catch(i){throw s.close(),i}}async join(e){let t=g(e);if(!t)throw new d("bad_code",`"${e}" is not a valid join code`);let s=this.loadSeat();if(s&&s.code===t&&!ht(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:n,welcome:i}=await this.connect();try{let o=await n.request({t:"join",code:t},"lobby");return this.makeLobby(n,i,o)}catch(o){throw n.close(),o}}async joinDirect(e,t={}){let{addr:s,links:n}=this.directTarget(e,t),{sig:i,welcome:o}=await this.connect(s.url,this.socketFactory,t.connectTimeoutMs??5e3);try{let a=await i.request({t:"join",...t.password?{password:t.password}:{}},"lobby"),l=this.makeLobby(i,o,a,{links:n});return l.joinCode=dt(s),l}catch(a){throw i.close(),a}}async rejoinDirect(e,t,s={}){let{addr:n,links:i}=this.directTarget(e,s),o=await this.resumeAt(t,n.url,s.connectTimeoutMs??5e3,{links:i});return o.joinCode=dt(n),o}directTarget(e,t){let s=e.trim();if(ie(s)){let a=re(s);if("error"in a)throw new d(a.error,a.error==="unknown_version"?`"${s}" is a join code from a newer version of the game`:`"${s}" is not a valid join code: check for a typo`);s=`${a.ip}:${a.port}`}let n=ot(s,t.defaultPort??47810);if(!n)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 i=this.linkFactory;return{addr:n,links:{create:a=>i.create({...a,iceServers:[],extraRemoteCandidates:[n.candidate]})}}}async hostDirectWith(e,t){let{sig:s,welcome:n}=await this.connect("onelobby-direct://host/",t.socketFactory);try{let i=await s.request({t:"create",name:e.name,maxPlayers:e.maxPlayers,visibility:"unlisted",mode:this.options.mode,tickRate:this.options.tickRate,lateJoin:e.lateJoin??!0,meta:e.meta??{}},"lobby");return this.makeLobby(s,n,i,{links:t.wrapLinks(this.linkFactory)},e.rateLimit)}catch(i){throw s.close(),i}}async rejoin(e){return this.resumeAt(e,this.options.lobbyServer,0)}async resumeAt(e,t,s,n){let i=await this.open(t,this.socketFactory,s);try{let o=i.expect("lobby"),a=i.request(this.helloMsg(e),"welcome");o.catch(()=>{});let l=await a;if(l.peerId===void 0)throw new d("invalid_token","resume token unknown or expired");let c=await o;return this.makeLobby(i,l,c,n)}catch(o){throw i.close(),o}}async list(e={}){if(this.blueprint&&!this.blueprint.hosting.server)throw new d("listing_disabled","this game has no lobby server (hosting.server is false)");let{sig:t}=await this.connect();try{return(await t.request({t:"list",visibility:"public",meta:e.meta??{},limit:Math.min(100,e.limit??50),...e.cursor?{cursor:e.cursor}:{}},"lobbies")).items??[]}finally{t.close()}}};function dt(r){if(r.isIPv6)return r.display;try{return nt(r.host,r.port)}catch{return r.display}}function hs(r){let e={};if(r)for(let[t,s]of Object.entries(r))s!==void 0&&(e[t]=s);return e}function ds(r,e){if(r.voice.mode=e.chat.voiceMode,!e.chat.voice){let t=r.voice,s=Object.getOwnPropertyDescriptor(Object.getPrototypeOf(t),"enabled");s?.get&&s.set&&Object.defineProperty(t,"enabled",{configurable:!0,get:()=>s.get.call(t),set:n=>{if(n)throw new d("voice_disabled","this game has voice chat turned off (chat.voice is false)");s.set.call(t,n)}})}e.chat.text||(r.chat.send=()=>{throw new d("chat_disabled","this game has text chat turned off (chat.text is false)")})}var ut={UnknownCode:"unknown_code",BadCode:"bad_code",LobbyFull:"lobby_full",VersionMismatch:"version_mismatch",InGame:"in_game",Kicked:"kicked",BadPassword:"bad_password",NotHost:"not_host",RateLimited:"rate_limited",ServerFull:"server_full",GameNotAllowed:"game_not_allowed",BadRequest:"bad_request",ServerClosed:"server_closed",IceFailed:"ice_failed",Timeout:"timeout",DirectUnreachable:"direct_unreachable",BadAddress:"bad_address",PortInUse:"port_in_use",ConnectFailed:"connect_failed",Closed:"closed",ServerLost:"server_lost",InvalidToken:"invalid_token",NoServer:"no_server",BadConfig:"bad_config",VoiceDisabled:"voice_disabled",ChatDisabled:"chat_disabled",ListingDisabled:"listing_disabled",IdentityAnonymous:"identity_anonymous"},le={unknown_code:{message:"We couldn't find a lobby with that code.",hint:"Check the code with the host. Codes stop working once everyone leaves."},bad_code:{message:"That doesn't look like a lobby code.",hint:"Codes are 6 letters and numbers, like 7HQ 4K2."},lobby_full:{message:"That lobby is full.",hint:"Ask the host to make room, or start your own lobby."},version_mismatch:{message:"You and the host are running different versions of the game.",hint:"Reload the page (or update the game) on both sides, then try again."},in_game:{message:"That game has already started.",hint:"Ask the host to let people join mid-game, or wait for the next round."},kicked:{message:"The host removed you from the lobby."},denied:{message:"The host didn't let you in this time.",hint:"Ask the host to approve you, then try again."},not_host:{message:"Only the host can do that."},rate_limited:{message:"Slow down a little: too many tries in a short time.",hint:"Wait a moment, then try again."},max_lobbies:{message:"You already have the most lobbies open that this server allows.",hint:"Close one of your other lobbies, then try again."},server_full:{message:"The game server is full right now.",hint:"Try again in a few minutes."},game_not_allowed:{message:"This game server doesn't host this game.",hint:"The game's server setting may be wrong; let the game's owner know."},bad_request:{message:"The game sent something the server didn't understand.",hint:"Reload the page and try again. If it keeps happening, the game may need an update."},connect_failed:{message:"Couldn't reach the game server.",hint:"Check your internet connection, then try again."},closed:{message:"Lost the connection to the game server.",hint:"Check your internet connection, then try again."},server_lost:{message:"Lost the connection to the game server.",hint:"Your game may keep going; new players can't join until it's back."},invalid_token:{message:"Your old seat in that lobby is no longer available.",hint:"Join again with the lobby code."},ice_failed:{message:"Couldn't connect to the other players.",hint:"Some networks (school, work, public Wi-Fi) block game connections. Try another network or a phone hotspot."},no_server:{message:"This game has no lobby server address yet.",hint:"Enter one in the multiplayer settings, or host from your own computer with a join code."},timeout:{message:"The connection took too long.",hint:"Check your internet connection, then try again."},voice_disabled:{message:"Voice chat is turned off in this game."},chat_disabled:{message:"Text chat is turned off in this game."},listing_disabled:{message:"This game has no public lobby list.",hint:"Join with a code or a link from the host."},identity_anonymous:{message:"This game doesn't use player names.",hint:"Everyone shows up as Player 1, Player 2, and so on."},bad_config:{message:"The game's multiplayer settings file is not valid.",hint:"Let the game's owner know; the details are in the error message."},left:{message:"You left the lobby."},unknown:{message:"Something went wrong.",hint:"Try again. If it keeps happening, reload the page."}},us=/^[a-z][a-z0-9_]*$/;function ps(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"&&us.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&&le[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 ce(r){let{code:e,data:t}=ps(r);if(!e)return{code:"unknown",...le.unknown};let s=e;e==="kicked"&&t?.reason==="denied"&&(s="denied"),e==="rate_limited"&&t?.reason==="max_lobbies"&&(s="max_lobbies");let n=le[s];if(!n)return{code:e,...le.unknown};let i={code:e,message:n.message};if(n.hint&&(i.hint=n.hint),e==="rate_limited"&&typeof t?.retryAfterMs=="number"&&s==="rate_limited"){let o=Math.max(1,Math.ceil(t.retryAfterMs/1e3));i.hint=`Wait ${o} second${o===1?"":"s"}, then try again.`}return i}function pt(){try{return typeof location<"u"?location.href:void 0}catch{return}}function he(r,e,t="join"){let s=g(r);if(!s)throw new Error(`"${r}" is not a valid lobby code`);let n=e??pt();if(!n)throw new Error("shareUrl needs a base URL outside the browser");let i=new URL(n);return i.searchParams.delete(t),i.searchParams.set(t,P(s).replace(" ","-")),i.toString()}function de(r,e="join"){let t=r??pt();if(!t)return null;let s;try{s=new URL(t,"http://localhost/")}catch{return null}let n=s.searchParams.get(e)??new URLSearchParams(s.hash.replace(/^#/,"")).get(e);return n?g(n):null}var mt=S,bt=d;var ms=new TextEncoder,bs=new TextDecoder,ue=r=>ms.encode(JSON.stringify(r)),U=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()]}},I=class{constructor(e){this.opts=e}opts;state="idle";lobby=null;error=null;linking=!1;name="";shared=new U(this);onChange=()=>{};onEvent=()=>{};lastAction=null;unsub=[];gen=0;seq=0;get isHost(){return!!this.lobby&&this.lobby.isHost}get code(){return this.lobby?.code??null}get players(){let e=this.lobby;if(!e)return[];let t=e.peers.map(s=>this.toPlayer(s));return t.some(s=>s.isMe)||t.push({id:e.localPeerId,name:this.name||`Player ${e.localPeerId}`,isHost:e.isHost,isMe:!0}),t.sort((s,n)=>s.id-n.id)}get statusText(){switch(this.state){case"idle":return"Host a game, or type a code to join one.";case"connecting":return this.lastAction?.kind==="join"?"Joining\u2026":this.lastAction?.kind==="resume"?"Getting your seat back\u2026":"Creating a game\u2026";case"error":return this.error?.message??"Something went wrong.";case"lobby":{if(this.linking)return`Connecting to ${this.hostName}\u2026`;if(this.isHost){let e=this.players.length;return e<=1?"Waiting for players\u2026 Share the code with your friends.":`${e} players here. Press Start when everyone's in.`}return`Connected! Waiting for ${this.hostName} to start.`}case"playing":return this.linking?`Connecting to ${this.hostName}\u2026`:"Game on!"}}get hostName(){let e=this.lobby;return e&&e.peer(e.hostPeerId)?.name||"the host"}toPlayer(e,t){let s=this.lobby,n=e?.peerId??t??0;return{id:n,name:e?.name||n===s.localPeerId&&this.name||`Player ${n}`,isHost:n===s.hostPeerId,isMe:n===s.localPeerId}}host(e){return this.lastAction={kind:"host"},this.run(e,t=>t.host({name:this.opts.lobbyName??"game",maxPlayers:typeof this.opts.maxPlayers=="function"?this.opts.maxPlayers():this.opts.maxPlayers,visibility:"unlisted",lateJoin:!0}))}join(e,t){let s=g(e);return this.lastAction={kind:"join",code:e},s?this.run(t,n=>n.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(ue({k:"m",d:e===void 0?null:e}))}async run(e,t,s=!1){this.detach();let n=++this.gen;this.name=(e||"").trim().slice(0,24),this.state="connecting",this.error=null,this.onChange();let i;try{i=await t(this.opts.makeKit(this.name))}catch(o){if(n!==this.gen)return;s?this.reset():this.fail(o);return}if(n!==this.gen){i?.leave();return}i?this.attach(i):this.reset()}attach(e){this.lobby=e,e.isHost||this.shared.values.clear(),this.seq=0,this.state=e.phase==="in_game"?"playing":"lobby",this.linking=!e.isHost;let t=this.unsub;t.push(e.on("ready",()=>this.linked())),t.push(e.on("peerJoined",s=>{this.onEvent("player-joined",{player:this.toPlayer(s)}),this.onChange()})),t.push(e.on("peerLeft",(s,n)=>{this.onEvent("player-left",{player:this.toPlayer(s),reason:n}),this.onChange()})),t.push(e.on("peerReady",s=>{e.isHost&&s.peerId!==e.localPeerId&&e.send(s.peerId,ue({k:"SS",all:this.shared.all(),q:this.seq}))})),t.push(e.on("hostChanged",()=>this.onChange())),t.push(e.on("phase",s=>{s==="in_game"&&this.state!=="playing"&&(this.state="playing",this.onChange(),this.onEvent("start",{}))})),t.push(e.on("message",(s,n)=>this.onRaw(s,n))),t.push(e.on("sessionEnded",s=>{this.lobby===e&&s!=="left"&&this.fail(new 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=ce(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(bs.decode(t))}catch{return}if(!s||typeof s!="object")return;let n=this.lobby;switch(s.k){case"m":this.onEvent("message",{from:this.playerById(e),data:s.d});break;case"set":case"add":n.isHost&&this.applyHost(s,e);break;case"S":!n.isHost&&e===n.hostPeerId&&!(s.q<=this.seq)&&(this.seq=s.q,this.applyValue(s.key,s.v,s.by));break;case"SS":if(!n.isHost&&e===n.hostPeerId&&s.all&&typeof s.all=="object"&&!(s.q<this.seq)){this.seq=s.q;for(let i of[...this.shared.values.keys()])i in s.all||this.applyValue(i,null,0);for(let[i,o]of Object.entries(s.all))this.applyValue(i,o,0)}break}}_sharedOp(e){let t=this.lobby;if(!t){let s=e.k==="set"?e.v:(Number(this.shared.values.get(e.key))||0)+e.n;this.applyValue(e.key,s,0);return}t.isHost?this.applyHost(e,t.localPeerId):t.send(t.hostPeerId,ue(e))}applyHost(e,t){let s=this.lobby;if(typeof e.key!="string")return;let n=e.k==="set"?e.v:(Number(this.shared.values.get(e.key))||0)+(Number(e.n)||0);this.applyValue(e.key,n,t),s.broadcast(ue({k:"S",key:e.key,v:n,by:t,q:++this.seq}))}applyValue(e,t,s){t===null?this.shared.values.delete(e):this.shared.values.set(e,t),this.onEvent("shared-change",{key:e,value:t,from:this.playerById(s)})}};var ft=`
|
|
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 D(r){let e=(r??"").trim();if(!e||/\s/.test(e)||/your[-_ ]?(lobby[-_ ]?)?server|replace[-_ ]?me|<|>/i.test(e))return null;let t=e;/^https?:\/\//i.test(t)?t=t.replace(/^http/i,"ws"):/^wss?:\/\//i.test(t)||(t="wss://"+t);let s;try{s=new URL(t)}catch{return null}let n=s.hostname.toLowerCase();return!n||/(^|\.)example\.(com|net|org)$/.test(n)||/\.example$/.test(n)||n==="example"||!n.includes(".")&&!n.includes(":")&&n!=="localhost"?null:t}var Re="one-lobby",yt="onelobby-name",fs=`
|
|
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 ys(){try{return localStorage.getItem(yt)??""}catch{return""}}function gs(r){try{localStorage.setItem(yt,r)}catch{}}async function ks(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 vs(){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>${ft}</style>${fs}`,this._controller=new I({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??ys()),this.$("nameLabel").hidden=!!t,this.render(),!this.autoJoined){this.autoJoined=!0;let n=de();n&&this._controller.state==="idle"?(this.$("code").value=P(n),s.value.trim()?this.join(n):(this.$("status").textContent="You're invited! Type your name, then press Join.",s.focus())):!n&&this._controller.state==="idle"&&D(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&&gs(t),t}makeKit(t){let s=D(this.getAttribute("server"));if(!s)throw new d("no_server","the <one-lobby> tag has no usable server address");return new S({gameId:this.getAttribute("game")||"onelobby-drop-in",netVersion:this.getAttribute("version")||"1",lobbyServer:s,mode:"raw",displayName:t,readyTimeoutMs:15e3,advanced:{createMic:null,createSink:null,voiceCodec:null}})}wire(){let t=(s,n,i)=>this.$(s).addEventListener(n,i);t("host","click",()=>{this.host()}),t("joinForm","submit",s=>{s.preventDefault(),this.join(this.$("code").value)}),t("cancel","click",()=>this._controller.reset()),t("start","click",()=>{this.start()}),t("leave","click",()=>{this.leave()}),t("retry","click",()=>{this._controller.retry()}),t("back","click",()=>this._controller.reset()),t("copyCode","click",()=>{this.copy(this.code??"","copyCode","Copied!")}),t("copyLink","click",()=>{this.copy(this.code?he(this.code):"","copyLink","Link copied!")})}async copy(t,s,n){if(!t)return;let i=await ks(t),o=this.$(s),a=s==="copyCode"?"Copy code":"Copy invite link";o.textContent=i?n:"Couldn't copy, select it by hand",this.$("copied").textContent=i?n:"",clearTimeout(this.copiedTimer),this.copiedTimer=setTimeout(()=>{o.textContent=a,this.$("copied").textContent=""},2e3)}render(){let t=this._controller,s=t.state,n=this.$("status");n.textContent=s==="error"&&t.error?.code==="no_server"?"This page is missing its lobby server address.":t.statusText,n.classList.toggle("error",s==="error");let i=this.$("hint"),o=s==="error"?t.error?.code==="no_server"?'Put your lobby server address in server="\u2026" on the <one-lobby> tag (it looks like https://your-name.onrender.com). The guide shows how to get one.':t.error?.hint??"":"";if(i.textContent=o,i.hidden=!o,this.$("v-idle").hidden=s!=="idle",this.$("v-busy").hidden=s!=="connecting",this.$("v-lobby").hidden=s!=="lobby"&&s!=="playing",this.$("v-error").hidden=s!=="error",this.setAttribute("state",s),s==="lobby"||s==="playing"){this.$("codeText").textContent=P(t.code??""),this.$("players").replaceChildren(...t.players.map(c=>{let h=document.createElement("li"),p=document.createElement("span");return p.textContent=c.name,h.append(p),c.isHost&&h.append(Object.assign(document.createElement("span"),{className:"tag",textContent:"host"})),c.isMe&&h.append(Object.assign(document.createElement("span"),{className:"tag",textContent:"you"})),h}));let l=this.$("start");l.hidden=!t.isHost||s==="playing",l.disabled=t.players.length<2,l.title=l.disabled?"Wait for at least one more player":""}}}return r}function pe(r=Re){typeof customElements>"u"||customElements.get(r)||customElements.define(r,vs())}pe();return St(ws);})();
|
|
140
140
|
//# sourceMappingURL=onelobby.global.min.js.map
|