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