@dobuki/hello-worker 1.0.77 → 1.0.79

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.
@@ -1,4 +1,4 @@
1
- function i(J){let{userId:C,worldId:E,room:j,host:n,autoRejoin:y=!0,logLine:q}=J,L=!1,_=0,z,b,S=!0,x=new Map,F=`wss://${n}/room/${E}/${j}?userId=${encodeURIComponent(C)}`,W=[],M=0;function K(V,A,f){if(!z)return q?.("\uD83D\uDC64 ➡️ ❌","no ws available"),!1;let D={type:V,to:A,payload:f};if(W.push(D),q?.("\uD83D\uDC64 ➡️ \uD83D\uDDA5️",D),clearTimeout(M),L||z.readyState!==WebSocket.OPEN)return q?.("\uD83D\uDC64 ➡️ ❌","Not in opened state: "+z.readyState),!1;return M=setTimeout(()=>{z.send(JSON.stringify(W)),W.length=0}),!0}function k(){if(L)return;z=new WebSocket(F),z.onopen=()=>{if(S)J.onOpen?.(),S=!1;_=0},z.onmessage=(V)=>{try{let A=JSON.parse(V.data);(Array.isArray(A)?A:[A]).forEach((D)=>{if(q?.("\uD83D\uDDA5️ ➡️ \uD83D\uDC64",D),D.type==="peer-joined"||D.type==="peer-left")h(D.users,D);else if(D.type==="ice-server")J.onIceUrl?.(D.url,D.expiration);else if(D.userId)J.onMessage(D.type,D.payload,{userId:D.userId,receive:(Z,X)=>K(Z,D.userId,X)})})}catch{q?.("⚠️ ERROR",{error:"invalid-json"})}},z.onclose=(V)=>{let f=[1001,1006,1011,1012,1013].includes(V.code);if(y&&!L&&f){let D=Math.min(Math.pow(2,_)*1000,30000),Z=Math.random()*1000,X=D+Z;q?.("\uD83D\uDD04 RECONNECTING",{attempt:_+1,delayMs:Math.round(X)}),_++,b=setTimeout(k,X)}else J.onClose?.({code:V.code,reason:V.reason,wasClean:V.wasClean})},z.onerror=(V)=>{console.error("WS Error",V),J.onError?.()}}function h(V,A){let f=[],D=[],Z=new Set;V.forEach(({userId:X})=>{if(X===C)return;if(!x.has(X)||A.type==="peer-joined"&&X===A.userId){let N={userId:X,receive:(T,P)=>K(T,X,P)};x.set(X,N),f.push(N)}Z.add(X)});for(let X of x.keys())if(!Z.has(X)||A.type==="peer-left"&&X===A.userId)x.delete(X),D.push({userId:X});if(f.length)J.onPeerJoined(f);if(D.length)J.onPeerLeft(D)}return k(),{sendToServer(V,A){K(V,"server",A)},exitRoom:()=>{L=!0,clearTimeout(b),z.close()}}}function c({userId:J,worldId:C,room:E,host:j,autoRejoin:n=!0,onOpen:y,onClose:q,onError:L,onPeerJoined:_,onPeerLeft:z,onIceUrl:b,onMessage:S,logLine:x,workerUrl:F}){if(!F)return console.warn("Warning: enterRoom called without workerUrl; this may cause issues in some environments. You should pass workerUrl explicitly. Use:","https://cdn.jsdelivr.net/npm/@dobuki/hello-worker/dist/signal-room.worker.min.js"),i({userId:J,worldId:C,room:E,host:j,autoRejoin:n,onOpen:y,onClose:q,onError:L,onPeerJoined:_,onPeerLeft:z,onIceUrl:b,onMessage:S});let W=new Worker(F,{type:"module"}),M=!1;function K({userId:h}){return{userId:h,receive:(V,A)=>{if(M)return!1;return W.postMessage({cmd:"send",toUserId:h,host:j,room:E,type:V,payload:A}),!0}}}let k=(h)=>{let V=h.data;if(V.kind==="open")y?.();else if(V.kind==="close")W.terminate(),q?.(V.ev);else if(V.kind==="error")L?.();else if(V.kind==="peer-joined")_(V.users.map((A)=>K({userId:A.userId})));else if(V.kind==="peer-left")z(V.users);else if(V.kind==="ice-server")b?.(V.url,V.expiration);else if(V.kind==="message")S(V.type,V.payload,K({userId:V.fromUserId}));else if(V.kind==="log")x?.(V.direction,V.obj)};return W.addEventListener("message",k),W.postMessage({cmd:"enter",userId:J,worldId:C,room:E,host:j,autoRejoin:n}),{exitRoom:()=>{M=!0,W.removeEventListener("message",k),W.postMessage({cmd:"exit"})},sendToServer:(h,V)=>{W.postMessage({cmd:"send",toUserId:"server",host:j,room:E,type:h,payload:V})}}}var u=c;function I({userId:J,worldId:C,receivePeerConnection:E,peerlessUserExpiration:j=5000,fallbackRtcConfig:n={iceServers:[{urls:"stun:stun.l.google.com:19302"}]},enterRoomFunction:y=u,logLine:q,onLeaveUser:L,workerUrl:_,onRoomReady:z,onRoomClose:b,onBroadcastMessage:S}){let x=J??`user-${crypto.randomUUID()}`,F=new Map,W=void 0,M={...n,timestamp:Date.now()},K=new Map;async function k(D,Z){if(D){let X=3;for(let N=0;N<X;N++){try{let T=await fetch(D);if(!T.ok)throw Error(`ICE endpoint failed: ${T.status}`);return M=await T.json(),M}catch(T){console.warn("Failed fetching iceUrl")}D=(await Z()).url}}return M}function h(D){L?.(D);let Z=F.get(D);if(!Z)return;F.delete(D);try{Z.close()}catch{}}async function V(D){if(!D.pc?.remoteDescription)return;let Z=D.pendingRemoteIce;D.pendingRemoteIce=[];for(let X of Z)try{await D.pc.addIceCandidate(X)}catch(N){q?.("⚠️ ERROR",{error:"add-ice-failed",userId:D.peer.userId,detail:String(N)})}}function A({room:D,host:Z}){let X=`${Z}/room/${D}`,N=K.get(X);if(N)N.exitRoom(),K.delete(X)}function f({room:D,host:Z}){return new Promise(async(X,N)=>{async function T(G){let $=Date.now();if($-(M?.timestamp??0)>1e4){let Q=!W||W.expiration-$<2000?await O():W;M=await k(Q.url,O)}return G.pc=new RTCPeerConnection(M),G.pc.onicecandidate=(Q)=>{if(!Q.candidate)return;G.peer.receive("ice",Q.candidate.toJSON())},G.pc.onconnectionstatechange=async()=>{if(q?.("\uD83D\uDCAC",{event:"pc-state",userId:G.peer.userId,state:G.pc?.connectionState}),G.pc?.connectionState==="failed"){G.close();let Q=await P(G.peer,!0);if(Q.pc)E({pc:Q.pc,userId:Q.peer.userId,restart:()=>Q.close()});else q?.("\uD83D\uDC64ℹ️","no pc: "+Q.peer.userId);return}},G.pc}async function P(G,$){let Q=F.get(G.userId);if(!Q||$){let H={pendingRemoteIce:[],peer:G,close(){this.pc?.close(),this.pc=void 0,F.delete(G.userId)},async reset(){H.close();let v=await P(G,!0);console.log(">>",v.peer.userId),setTimeout(async()=>{if(!v.pc){console.log("no pc");return}E({pc:v.pc,userId:v.peer.userId,restart:()=>v.close()}),await B(v.peer)},3000)}};await T(H),Q=H,F.set(Q.peer.userId,Q)}else if(Q){if(clearTimeout(Q.expirationTimeout),Q.expirationTimeout=0,!Q.pc||Q.pc.signalingState==="closed")await T(Q)}return Q.peer=G,Q}async function B(G){let Q=(await P(G)).pc,H=await Q?.createOffer();await Q?.setLocalDescription(H),G.receive("offer",Q?.localDescription?.toJSON())}let Y;async function O(){let G=await new Promise(($)=>{Y=$,R("request-ice")});return Y=void 0,G}let{exitRoom:w,sendToServer:R}=y({userId:x,worldId:C,room:D,host:Z,logLine:q,workerUrl:_,autoRejoin:!0,onOpen(){z?.({room:D,host:Z}),X()},onError(){console.error("onError"),N()},onClose(G){b?.({room:D,host:Z,ev:G})},onPeerJoined(G){G.forEach(async($)=>{let Q=await P($,!0),H=Q.pc;if(!H){q?.("\uD83D\uDC64ℹ️","no pc: "+$.userId);return}E({pc:H,userId:$.userId,restart:()=>Q.close()}),await B($)})},onPeerLeft(G){G.forEach(({userId:$})=>{let Q=F.get($);if(!Q)return;Q.expirationTimeout=setTimeout(()=>h($),j??0)})},onIceUrl(G,$){W={url:G,expiration:$},Y?.(W)},async onMessage(G,$,Q){let H=await P(Q);if(G==="offer"){H.close();let U=await T(H);E({pc:U,userId:Q.userId,restart:()=>H.close()}),await U.setRemoteDescription($);let g=await U.createAnswer();await U.setLocalDescription(g),Q.receive("answer",U.localDescription?.toJSON()),await V(H);return}let v=H.pc??await T(H);if(G==="answer"){await v.setRemoteDescription($),await V(H);return}if(G==="ice"){let U=$;if(!v.remoteDescription){H.pendingRemoteIce.push(U);return}try{await H.pc?.addIceCandidate(U)}catch(g){q?.("⚠️ ERROR",{error:"add-ice-failed",userId:H.peer.userId,detail:String(g)})}return}if(G==="broadcast")S?.($,Q.userId)}});K.set(`${Z}/room/${D}`,{exitRoom:w,room:D,host:Z,broadcast:(G)=>R("broadcast",G)})})}return{userId:x,enterRoom:f,exitRoom:A,leaveUser:h,async reset(D){F.get(D)?.reset()},broadcast(D){K.forEach((Z)=>Z.broadcast(D))},end(){K.forEach(({exitRoom:D})=>D()),K.clear(),F.forEach(({peer:D})=>h(D.userId)),F.clear()}}}function BD({userId:J,worldId:C,logLine:E,enterRoomFunction:j=c,peerlessUserExpiration:n,workerUrl:y,onRoomReady:q,onRoomClose:L,dataChannelOptions:_}){let z=new Set,b=new Map,S=new Set,x=new Set;function F(B,Y,O){function w(G){let $=G.channel;M(Y,$,O),b.set(Y,$)}B.addEventListener("datachannel",w);let R=B.createDataChannel("data",_);return M(Y,R,O),b.set(Y,R),()=>{B.removeEventListener("datachannel",w)}}function W(B,Y){x.forEach((O)=>O(B,Y))}function M(B,Y,O){Y.onopen=()=>{E?.("\uD83D\uDCAC",{event:"dc-open",userId:B}),z.add(B),S.forEach((R)=>R(B,"join",[...z]))};let w=({data:R})=>{W(R,B)};Y.addEventListener("message",w),Y.onclose=()=>{E?.("\uD83D\uDCAC",{event:"dc-close",userId:B}),z.delete(B),S.forEach((R)=>R(B,"leave",[...z])),Y.removeEventListener("message",w),Y.onopen=null,Y.onclose=null,Y.onerror=null,Y.close(),O?.()},Y.onerror=()=>E?.("⚠️ ERROR",{error:"dc-error",userId:B})}let{userId:K,enterRoom:k,exitRoom:h,leaveUser:V,broadcast:A,end:f,reset:D}=I({userId:J,worldId:C,enterRoomFunction:j,logLine:E,workerUrl:y,peerlessUserExpiration:n,onRoomReady:q,onRoomClose:L,onLeaveUser(B){let Y=b.get(B);try{Y?.close()}catch{}b.delete(B)},receivePeerConnection({pc:B,userId:Y,restart:O}){F(B,Y,O)},onBroadcastMessage(B,Y){W(B,Y),E?.("\uD83D\uDCE2",{event:"broadcast",userId:K,data:B})}});function Z(B,Y){b.forEach((O,w)=>{if(Y&&w!==Y)return;if(O.readyState==="open")O.send(B)})}function X(B){x.delete(B)}function N(B){return x.add(B),()=>{X(B)}}function T(B){S.delete(B)}function P(B){return S.add(B),()=>{T(B)}}return{userId:K,send:Z,broadcast:A,enterRoom:k,exitRoom:h,leaveUser:V,getUsers:()=>[...z],addMessageListener:N,removeMessageListener:X,addUserListener:P,removeUserListener:T,reset(){z.forEach((B)=>{b.get(B)?.close(),b.delete(B),D(B)})},end(){b.forEach((B)=>{try{B.close()}catch{}}),b.clear(),f(),S.clear(),z.clear()}}}export{BD as enterWorld};
1
+ class k{sendToServerFunctions=[];icePromiseResolve;icePromise;receiveIce(T,H){this.icePromiseResolve?.({url:T,expiration:H}),this.icePromiseResolve=void 0,this.icePromise=void 0}addRequester(T){return this.sendToServerFunctions.push(T),()=>{this.removeRequester(T)}}removeRequester(T){this.sendToServerFunctions.splice(this.sendToServerFunctions.indexOf(T),1)}sendToServer(T){this.sendToServerFunctions[Math.floor(this.sendToServerFunctions.length*Math.random())](T)}async requestIce(){if(!this.icePromise)this.icePromise=new Promise((T)=>{this.icePromiseResolve=T,this.sendToServer("request-ice")});return await this.icePromise}}function L(T){let{userId:H,worldId:Q,room:S,host:J,autoRejoin:V=!0,logLine:Z}=T,P=!1,x=0,G,Y,E=!0,$=new Map,K=`wss://${J}/room/${Q}/${S}?userId=${encodeURIComponent(H)}`,X=[],N=0;function W(b,h,B){if(!G)return Z?.("\uD83D\uDC64 ➡️ ❌","no ws available"),!1;let F={type:b,to:h,payload:B};if(X.push(F),Z?.("\uD83D\uDC64 ➡️ \uD83D\uDDA5️",F),clearTimeout(N),P||G.readyState!==WebSocket.OPEN)return Z?.("\uD83D\uDC64 ➡️ ❌","Not in opened state: "+G.readyState),!1;return N=setTimeout(()=>{G.send(JSON.stringify(X)),X.length=0}),!0}function O(){if(P)return;G=new WebSocket(K),G.onopen=()=>{if(E)T.onOpen?.(),E=!1;x=0},G.onmessage=(b)=>{try{let h=JSON.parse(b.data);(Array.isArray(h)?h:[h]).forEach((F)=>{if(Z?.("\uD83D\uDDA5️ ➡️ \uD83D\uDC64",F),F.type==="peer-joined"||F.type==="peer-left")M(F.users,F);else if(F.type==="ice-server")T.onIceUrl?.(F.url,F.expiration);else if(F.userId)T.onMessage(F.type,F.payload,{userId:F.userId,receive:(z,A)=>W(z,F.userId,A)})})}catch{Z?.("⚠️ ERROR",{error:"invalid-json"})}},G.onclose=(b)=>{let B=[1001,1006,1011,1012,1013].includes(b.code);if(V&&!P&&B){let F=Math.min(Math.pow(2,x)*1000,30000),z=Math.random()*1000,A=F+z;Z?.("\uD83D\uDD04 RECONNECTING",{attempt:x+1,delayMs:Math.round(A)}),x++,Y=setTimeout(O,A)}else T.onClose?.({code:b.code,reason:b.reason,wasClean:b.wasClean})},G.onerror=(b)=>{console.error("WS Error",b),T.onError?.()}}function M(b,h){let B=[],F=[],z=new Set;b.forEach(({userId:A})=>{if(A===H)return;if(!$.has(A)||h.type==="peer-joined"&&A===h.userId){let c={userId:A,receive:(v,w)=>W(v,A,w)};$.set(A,c),B.push(c)}z.add(A)});for(let A of $.keys())if(!z.has(A)||h.type==="peer-left"&&A===h.userId)$.delete(A),F.push({userId:A});if(B.length)T.onPeerJoined(B);if(F.length)T.onPeerLeft(F)}return O(),{sendToServer(b,h){W(b,"server",h)},exitRoom:()=>{P=!0,clearTimeout(Y),G.close()}}}function j({userId:T,worldId:H,room:Q,host:S,autoRejoin:J=!0,onOpen:V,onClose:Z,onError:P,onPeerJoined:x,onPeerLeft:G,onIceUrl:Y,onMessage:E,logLine:$,workerUrl:K}){if(!K)return console.warn("Warning: enterRoom called without workerUrl; this may cause issues in some environments. You should pass workerUrl explicitly. Use:","https://cdn.jsdelivr.net/npm/@dobuki/hello-worker/dist/signal-room.worker.min.js"),L({userId:T,worldId:H,room:Q,host:S,autoRejoin:J,onOpen:V,onClose:Z,onError:P,onPeerJoined:x,onPeerLeft:G,onIceUrl:Y,onMessage:E});let X=new Worker(K,{type:"module"}),N=!1;function W({userId:M}){return{userId:M,receive:(b,h)=>{if(N)return!1;return X.postMessage({cmd:"send",toUserId:M,host:S,room:Q,type:b,payload:h}),!0}}}let O=(M)=>{let b=M.data;if(b.kind==="open")V?.();else if(b.kind==="close")X.terminate(),Z?.(b.ev);else if(b.kind==="error")P?.();else if(b.kind==="peer-joined")x(b.users.map((h)=>W({userId:h.userId})));else if(b.kind==="peer-left")G(b.users);else if(b.kind==="ice-server")Y?.(b.url,b.expiration);else if(b.kind==="message")E(b.type,b.payload,W({userId:b.fromUserId}));else if(b.kind==="log")$?.(b.direction,b.obj)};return X.addEventListener("message",O),X.postMessage({cmd:"enter",userId:T,worldId:H,room:Q,host:S,autoRejoin:J}),{exitRoom:()=>{N=!0,X.removeEventListener("message",O),X.postMessage({cmd:"exit"})},sendToServer:(M,b)=>{X.postMessage({cmd:"send",toUserId:"server",host:S,room:Q,type:M,payload:b})}}}var U={iceServers:[{urls:"stun:stun.l.google.com:19302"}]};class n{iceUrlProvider;constructor(T){this.iceUrlProvider=T}rtcConfig={...U,timestamp:Date.now()};rtcConfigPromise;async getRtcConfig(){if(Date.now()-(this.rtcConfig?.timestamp??0)<1e4)return this.rtcConfig;if(!this.rtcConfigPromise)this.rtcConfigPromise=new Promise(async(H)=>{let Q=3;for(let S=0;S<Q;S++)try{let J=(await this.iceUrlProvider.requestIce()).url,V=await fetch(J);if(!V.ok)throw Error(`ICE endpoint failed: ${V.status}`);let Z=await V.json();H(Z);return}catch(J){console.warn("Failed fetching iceUrl")}}),this.rtcConfig=await this.rtcConfigPromise,this.rtcConfigPromise=void 0;return this.rtcConfig}}var m=j;function g({userId:T,worldId:H,receivePeerConnection:Q,peerlessUserExpiration:S=5000,enterRoomFunction:J=m,logLine:V,onLeaveUser:Z,workerUrl:P,onRoomReady:x,onRoomClose:G,onBroadcastMessage:Y}){let E=T??`user-${crypto.randomUUID()}`,$=new Map,K=new Map;function X(h){Z?.(h);let B=$.get(h);if(!B)return;$.delete(h);try{B.close()}catch{}}async function N(h){if(!h.connection?.pc?.remoteDescription)return;let B=h.connection.pendingRemoteIce;h.connection.pendingRemoteIce=[];for(let F of B)try{await h.connection.pc.addIceCandidate(F)}catch(z){V?.("⚠️ ERROR",{error:"add-ice-failed",userId:h.peer.userId,detail:String(z)})}}let W=new k,O=new n(W);function M({room:h,host:B}){let F=`${B}/room/${h}`,z=K.get(F);if(z)z.exitRoom(),K.delete(F)}function b({room:h,host:B}){return new Promise(async(F,z)=>{async function A(i){if(i.connectionPromise)return i.connectionPromise;return i.connectionPromise=new Promise(async(q)=>{i.connection={id:`conn-${crypto.randomUUID()}`,pc:new RTCPeerConnection(await O.getRtcConfig()),pendingRemoteIce:[]},i.connection.pc.onicecandidate=(f)=>{if(!f.candidate)return;i.peer.receive("ice",{connectionId:i.connection?.id,ice:f.candidate.toJSON()})},i.connection.pc.onconnectionstatechange=async()=>{if(V?.("\uD83D\uDCAC",{event:"pc-state",userId:i.peer.userId,state:i.connection?.pc?.connectionState}),i.connection?.pc?.connectionState==="failed"){i.close();let f=await c(i.peer,!0);if(f.connection?.pc)Q({pc:f.connection?.pc,userId:f.peer.userId,restart:()=>f.close()});else V?.("\uD83D\uDC64ℹ️","no pc: "+f.peer.userId);return}},q(i.connection)})}async function c(i,q){let f=$.get(i.userId);if(!f||q){let D={peer:i,close(){if(this.connection)this.connection.pc.close(),this.connection=void 0;$.delete(i.userId)},async reset(){D.close(),setTimeout(async()=>{let R=await c(i,!0);if(!R.connection?.pc){V?.("⚠️","no pc");return}Q({pc:R.connection?.pc,userId:R.peer.userId,restart:()=>R.close()}),await v(R.peer)},500)}};f=D,await A(D),$.set(f.peer.userId,f)}else if(f){if(clearTimeout(f.expirationTimeout),f.expirationTimeout=0,!f.connection?.pc||f.connection?.pc.signalingState==="closed")await A(f)}return f.peer=i,f}async function v(i){let q=await c(i),f=q.connection?.pc,D=await f?.createOffer();await f?.setLocalDescription(D),i.receive("offer",{connectionId:q.connection?.id,offer:f.localDescription.toJSON()})}let{exitRoom:w,sendToServer:C}=J({userId:E,worldId:H,room:h,host:B,logLine:V,workerUrl:P,autoRejoin:!0,onOpen(){x?.({room:h,host:B}),F()},onError(){console.error("onError"),z()},onClose(i){G?.({room:h,host:B,ev:i})},onPeerJoined(i){i.forEach(async(q)=>{let f=await c(q,!0),D=f.connection?.pc;if(!D){V?.("\uD83D\uDC64ℹ️","no pc: "+q.userId);return}Q({pc:D,userId:q.userId,restart:()=>f.close()}),await v(q)})},onPeerLeft(i){i.forEach(({userId:q})=>{let f=$.get(q);if(!f)return;f.expirationTimeout=setTimeout(()=>X(q),S??0)})},onIceUrl(i,q){W.receiveIce(i,q)},async onMessage(i,q,f){if(i==="offer"&&q.offer){let D=await c(f,!1),R=!D.connection||D.connection.pc.signalingState==="stable"?await A(D):D.connection;V?.("\uD83D\uDCAC",{type:i,signalingState:R.pc.signalingState}),R.peerConnectionId=q.connectionId,Q({pc:R.pc,userId:f.userId,restart:()=>D.close()}),await R.pc.setRemoteDescription(q.offer);let _=await R.pc.createAnswer();await R.pc.setLocalDescription(_),f.receive("answer",{connectionId:R.id,answer:R.pc.localDescription?.toJSON()}),await N(D);return}if(i==="answer"&&q.answer){let D=await c(f,!1),R=D.connection&&D.connection.pc.signalingState!=="closed"?D.connection:await A(D);V?.("\uD83D\uDCAC",{type:i,signalingState:R.pc.signalingState}),await R.pc.setRemoteDescription(q.answer),R.peerConnectionId=q.connectionId,await N(D);return}if(i==="ice"&&q.ice){let D=await c(f,!1),R=D.connection??await D.connectionPromise;if(!R){V?.("⚠️","No connection");return}if(V?.("\uD83D\uDCAC",{type:i,signalingState:R.pc.signalingState}),R.peerConnectionId&&q.connectionId!==R.peerConnectionId){V?.("⚠️","Mismatch peerConnectionID"+q.connectionId+"vs"+R.peerConnectionId);return}if(!R.pc.remoteDescription||!R.peerConnectionId){R.peerConnectionId=q.connectionId,R.pendingRemoteIce.push(q.ice);return}try{await R.pc.addIceCandidate(q.ice)}catch(_){V?.("⚠️ ERROR",{error:"add-ice-failed",userId:D.peer.userId,detail:String(_)})}return}if(i==="broadcast")Y?.(q,f.userId)}});W.addRequester(C),K.set(`${B}/room/${h}`,{exitRoom:()=>{w(),W.removeRequester(C)},room:h,host:B,broadcast:(i)=>C("broadcast",i)})})}return{userId:E,enterRoom:b,exitRoom:M,leaveUser:X,async reset(h){$.get(h)?.reset()},broadcast(h){K.forEach((B)=>B.broadcast(h))},end(){K.forEach(({exitRoom:h})=>h()),K.clear(),$.forEach(({peer:h})=>X(h.userId)),$.clear()}}}function Ci({userId:T,worldId:H,logLine:Q,enterRoomFunction:S=j,peerlessUserExpiration:J,workerUrl:V,onRoomReady:Z,onRoomClose:P,dataChannelOptions:x}){let G=new Set,Y=new Map,E=new Set,$=new Set;function K(C,i,q){function f(R){let _=R.channel;N(i,_,q),Y.set(i,_)}C.addEventListener("datachannel",f);let D=C.createDataChannel("data",x);return N(i,D,q),Y.set(i,D),()=>{C.removeEventListener("datachannel",f)}}function X(C,i){$.forEach((q)=>q(C,i))}function N(C,i,q){i.onopen=()=>{Q?.("\uD83D\uDCAC",{event:"dc-open",userId:C}),G.add(C),E.forEach((D)=>D(C,"join",[...G]))};let f=({data:D})=>{X(D,C)};i.addEventListener("message",f),i.onclose=()=>{Q?.("\uD83D\uDCAC",{event:"dc-close",userId:C}),G.delete(C),E.forEach((D)=>D(C,"leave",[...G])),i.removeEventListener("message",f),i.onopen=null,i.onclose=null,i.onerror=null,i.close(),q?.()},i.onerror=()=>Q?.("⚠️ ERROR",{error:"dc-error",userId:C})}let{userId:W,enterRoom:O,exitRoom:M,leaveUser:b,broadcast:h,end:B,reset:F}=g({userId:T,worldId:H,enterRoomFunction:S,logLine:Q,workerUrl:V,peerlessUserExpiration:J,onRoomReady:Z,onRoomClose:P,onLeaveUser(C){let i=Y.get(C);try{i?.close()}catch{}Y.delete(C)},receivePeerConnection({pc:C,userId:i,restart:q}){K(C,i,q)},onBroadcastMessage(C,i){X(C,i),Q?.("\uD83D\uDCE2",{event:"broadcast",userId:W,data:C})}});function z(C,i){Y.forEach((q,f)=>{if(i&&f!==i)return;if(q.readyState==="open")q.send(C)})}function A(C){$.delete(C)}function c(C){return $.add(C),()=>{A(C)}}function v(C){E.delete(C)}function w(C){return E.add(C),()=>{v(C)}}return{userId:W,send:z,broadcast:h,enterRoom:O,exitRoom:M,leaveUser:b,getUsers:()=>[...G],addMessageListener:c,removeMessageListener:A,addUserListener:w,removeUserListener:v,reset(){G.forEach((C)=>{Y.get(C)?.close(),Y.delete(C),F(C)})},end(){Y.forEach((C)=>{try{C.close()}catch{}}),Y.clear(),B(),E.clear(),G.clear()}}}export{Ci as enterWorld};
2
2
 
3
- //# debugId=6F46100BAA9D39CE64756E2164756E21
3
+ //# debugId=4C800C9DE42E87E164756E2164756E21
4
4
  //# sourceMappingURL=enter-world.js.map
@@ -1,13 +1,15 @@
1
1
  {
2
2
  "version": 3,
3
- "sources": ["../src/browser/signal/impl/signal-room.ts", "../src/browser/signal/signal-room.ts", "../src/browser/webrtc-peer-collector.ts", "../src/browser/enter-world.ts"],
3
+ "sources": ["../src/browser/utils/ice-url-provider.ts", "../src/browser/signal/impl/signal-room.ts", "../src/browser/signal/signal-room.ts", "../src/browser/utils/rtc-config.ts", "../src/browser/webrtc-peer-collector.ts", "../src/browser/enter-world.ts"],
4
4
  "sourcesContent": [
5
+ "export class IceUrlProvider {\n private sendToServerFunctions = new Array<(command: \"request-ice\") => void>();\n private icePromiseResolve?: (url: {\n url: string;\n expiration: number;\n }) => void;\n private icePromise?: Promise<{ url: string; expiration: number }>;\n\n receiveIce(url: string, expiration: number) {\n this.icePromiseResolve?.({ url, expiration });\n this.icePromiseResolve = undefined;\n this.icePromise = undefined;\n }\n\n addRequester(requester: (command: \"request-ice\") => void) {\n this.sendToServerFunctions.push(requester);\n return () => {\n this.removeRequester(requester);\n };\n }\n\n removeRequester(requester: (command: \"request-ice\") => void) {\n this.sendToServerFunctions.splice(\n this.sendToServerFunctions.indexOf(requester),\n 1,\n );\n }\n\n sendToServer(command: \"request-ice\") {\n this.sendToServerFunctions[\n Math.floor(this.sendToServerFunctions.length * Math.random())\n ](command);\n }\n\n async requestIce() {\n if (!this.icePromise) {\n this.icePromise = new Promise<{ url: string; expiration: number }>(\n (resolve) => {\n this.icePromiseResolve = resolve;\n this.sendToServer(\"request-ice\");\n },\n );\n }\n return await this.icePromise;\n }\n}\n",
5
6
  "export interface IPeer<T extends string = string, P = any> {\n userId: string;\n receive(type: T, payload: P): boolean;\n}\n\ntype OutMessage = { type: string; to: string; payload: any };\n\n/**\n * enterRoom connects to the signaling room via WebSocket.\n */\nexport function enterRoom<T extends string, P = any>(params: {\n userId: string;\n worldId: string;\n room: string;\n host: string;\n onOpen?: () => void;\n onClose?: (ev: Pick<CloseEvent, \"code\" | \"reason\" | \"wasClean\">) => void;\n onError?: () => void;\n logLine?: (direction: string, obj?: any) => void;\n onPeerJoined(users: IPeer<T, P>[]): void;\n onPeerLeft(users: { userId: string }[]): void;\n onIceUrl?(url: string, expiration: number): void;\n onMessage(type: T, payload: P, from: IPeer<T, P>): void;\n autoRejoin?: boolean;\n}): {\n exitRoom: () => void;\n sendToServer: <P extends any>(type: T, payload?: P) => void;\n} {\n type Message = {\n type: \"peer-joined\" | \"peer-left\" | \"ice-server\" | T;\n userId: string;\n users: { userId: string }[];\n payload: P;\n url: string;\n expiration: number;\n };\n\n const { userId, worldId, room, host, autoRejoin = true, logLine } = params;\n\n let exited = false;\n let retryCount = 0;\n let ws: WebSocket;\n let timeoutId: ReturnType<typeof setTimeout>;\n let initialConnection = true;\n\n const peers = new Map<string, IPeer<T, P>>();\n const wsUrl = `wss://${host}/room/${worldId}/${room}?userId=${encodeURIComponent(\n userId,\n )}`;\n\n // Helper for sending (uses the current ws instance)\n const accumulatedMessages: OutMessage[] = [];\n let timeout: ReturnType<typeof setTimeout> = 0;\n function send(type: string, to: \"server\" | string, payload?: any) {\n if (!ws) {\n logLine?.(\"👤 ➡️ ❌\", \"no ws available\");\n return false;\n }\n const obj: OutMessage = { type, to, payload };\n accumulatedMessages.push(obj);\n logLine?.(\"👤 ➡️ 🖥️\", obj);\n clearTimeout(timeout);\n if (exited || ws.readyState !== WebSocket.OPEN) {\n logLine?.(\"👤 ➡️ ❌\", \"Not in opened state: \" + ws.readyState);\n return false;\n }\n timeout = setTimeout(() => {\n ws.send(JSON.stringify(accumulatedMessages));\n accumulatedMessages.length = 0;\n });\n return true;\n }\n\n function connect() {\n if (exited) return;\n\n ws = new WebSocket(wsUrl);\n\n ws.onopen = () => {\n if (initialConnection) {\n params.onOpen?.();\n initialConnection = false;\n }\n retryCount = 0; // Reset backoff on successful connection\n };\n\n ws.onmessage = (e: MessageEvent) => {\n try {\n const result = JSON.parse(e.data);\n const msgs: Message[] = Array.isArray(result) ? result : [result];\n msgs.forEach((msg) => {\n logLine?.(\"🖥️ ➡️ 👤\", msg);\n if (msg.type === \"peer-joined\" || msg.type === \"peer-left\") {\n updatePeers(msg.users, msg);\n } else if (msg.type === \"ice-server\") {\n params.onIceUrl?.(msg.url, msg.expiration);\n } else if (msg.userId) {\n params.onMessage(msg.type, msg.payload, {\n userId: msg.userId,\n receive: (type: T, payload: P) => send(type, msg.userId, payload),\n });\n }\n });\n } catch {\n logLine?.(\"⚠️ ERROR\", { error: \"invalid-json\" });\n }\n };\n\n ws.onclose = (ev: CloseEvent) => {\n // 1. Check if we should even try to reconnect\n const recoverableCodes = [1001, 1006, 1011, 1012, 1013];\n const isRecoverable = recoverableCodes.includes(ev.code);\n\n if (autoRejoin && !exited && isRecoverable) {\n // 2. Exponential Backoff: 1s, 2s, 4s, 8s... capped at 30s\n const backoff = Math.min(Math.pow(2, retryCount) * 1000, 30000);\n // 3. Add Jitter: +/- 1000ms randomness\n const jitter = Math.random() * 1000;\n const delay = backoff + jitter;\n\n logLine?.(\"🔄 RECONNECTING\", {\n attempt: retryCount + 1,\n delayMs: Math.round(delay),\n });\n\n retryCount++;\n timeoutId = setTimeout(connect, delay);\n } else {\n params.onClose?.({\n code: ev.code,\n reason: ev.reason,\n wasClean: ev.wasClean,\n });\n }\n };\n\n ws.onerror = (ev) => {\n console.error(\"WS Error\", ev);\n params.onError?.();\n };\n }\n\n // Helper for peer tracking (logic from your original code)\n function updatePeers(updatedUsers: { userId: string }[], msg: Message) {\n const joined: IPeer<T, P>[] = [];\n const left: { userId: string }[] = [];\n const updatedPeerSet = new Set<string>();\n\n updatedUsers.forEach(({ userId: pUserId }) => {\n if (pUserId === userId) return;\n if (\n !peers.has(pUserId) ||\n (msg.type === \"peer-joined\" && pUserId === msg.userId)\n ) {\n const newPeer = {\n userId: pUserId,\n receive: (t: T, p: P) => send(t, pUserId, p),\n };\n peers.set(pUserId, newPeer);\n joined.push(newPeer);\n }\n updatedPeerSet.add(pUserId);\n });\n\n for (const pUserId of peers.keys()) {\n if (\n !updatedPeerSet.has(pUserId) ||\n (msg.type === \"peer-left\" && pUserId === msg.userId)\n ) {\n peers.delete(pUserId);\n left.push({ userId: pUserId });\n }\n }\n\n // Notify peer joined first then peer left. (avoid disconnect in case the peer leaving / joining is on the same user).\n if (joined.length) params.onPeerJoined(joined);\n if (left.length) params.onPeerLeft(left);\n }\n\n // Start initial connection\n connect();\n\n return {\n sendToServer(type, payload) {\n send(type, \"server\", payload);\n },\n exitRoom: () => {\n exited = true;\n clearTimeout(timeoutId);\n ws.close();\n },\n };\n}\n",
6
7
  "import type { IPeer } from \"./impl/signal-room.js\";\nimport { enterRoom as baseEnterRoom } from \"./impl/signal-room.js\";\nimport { RoomEvent, WorkerCommand } from \"./signal-room.worker.js\";\n\nexport function enterRoom<T extends string, P = any>({\n userId,\n worldId,\n room,\n host,\n autoRejoin = true,\n onOpen,\n onClose,\n onError,\n onPeerJoined,\n onPeerLeft,\n onIceUrl,\n onMessage,\n logLine,\n workerUrl,\n}: {\n userId: string;\n worldId: string;\n room: string;\n host: string;\n autoRejoin?: boolean;\n onOpen?: () => void;\n onClose?: (ev: Pick<CloseEvent, \"code\" | \"reason\" | \"wasClean\">) => void;\n onError?: () => void;\n onPeerJoined: (users: IPeer<T, P>[]) => void;\n onPeerLeft: (users: { userId: string }[]) => void;\n onIceUrl?(url: string, expiration: number): void;\n onMessage: (type: T, payload: P, from: IPeer<T, P>) => void;\n logLine?: (direction: string, obj?: any) => void;\n\n // Pass the URL to your worker file (bundler will handle it)\n workerUrl?: URL;\n}): {\n exitRoom: () => void;\n sendToServer: <P extends any>(type: T, payload?: P) => void;\n} {\n if (!workerUrl) {\n const CDN_WORKER_URL = `https://cdn.jsdelivr.net/npm/@dobuki/hello-worker/dist/signal-room.worker.min.js`;\n\n console.warn(\n \"Warning: enterRoom called without workerUrl; this may cause issues in some environments. You should pass workerUrl explicitly. Use:\",\n CDN_WORKER_URL,\n );\n return baseEnterRoom<T, P>({\n userId,\n worldId,\n room,\n host,\n autoRejoin,\n onOpen,\n onClose,\n onError,\n onPeerJoined,\n onPeerLeft,\n onIceUrl,\n onMessage,\n });\n }\n const worker = new Worker(workerUrl, { type: \"module\" });\n let exited = false;\n\n function makeUser({ userId }: { userId: string }): IPeer<T, P> {\n return {\n userId,\n receive: (type: T, payload: P) => {\n if (exited) return false;\n worker.postMessage({\n cmd: \"send\",\n toUserId: userId,\n host,\n room,\n type,\n payload,\n } as WorkerCommand);\n return true;\n },\n };\n }\n\n const onWorkerMessage = (e: MessageEvent<RoomEvent<T, P>>) => {\n const ev = e.data;\n\n if (ev.kind === \"open\") onOpen?.();\n else if (ev.kind === \"close\") {\n worker.terminate();\n onClose?.(ev.ev);\n } else if (ev.kind === \"error\") onError?.();\n else if (ev.kind === \"peer-joined\")\n onPeerJoined(ev.users.map((ev) => makeUser({ userId: ev.userId })));\n else if (ev.kind === \"peer-left\") onPeerLeft(ev.users);\n else if (ev.kind === \"ice-server\") onIceUrl?.(ev.url, ev.expiration);\n else if (ev.kind === \"message\")\n onMessage(ev.type, ev.payload, makeUser({ userId: ev.fromUserId }));\n else if (ev.kind === \"log\") logLine?.(ev.direction, ev.obj);\n };\n\n worker.addEventListener(\"message\", onWorkerMessage);\n\n worker.postMessage({\n cmd: \"enter\",\n userId,\n worldId,\n room,\n host,\n autoRejoin,\n } as WorkerCommand);\n\n return {\n exitRoom: () => {\n exited = true;\n worker.removeEventListener(\"message\", onWorkerMessage);\n worker.postMessage({ cmd: \"exit\" } as WorkerCommand);\n },\n sendToServer: <P extends any>(type: T, payload?: P) => {\n worker.postMessage({\n cmd: \"send\",\n toUserId: \"server\",\n host,\n room,\n type,\n payload,\n } as WorkerCommand);\n },\n };\n}\n\nexport type EnterRoom<T extends string, P> = typeof enterRoom<T, P>;\n",
7
- "import { IPeer } from \"./signal/impl/signal-room\";\nimport { EnterRoom, enterRoom } from \"./signal/signal-room\";\n\nexport type SigType = \"offer\" | \"answer\" | \"ice\" | \"request-ice\" | \"broadcast\";\nexport type SigPayload = RTCSessionDescriptionInit | RTCIceCandidateInit;\n\ntype UserState = {\n pc?: RTCPeerConnection;\n\n // ICE that arrived before we had remoteDescription\n pendingRemoteIce: RTCIceCandidateInit[];\n\n // the signaling \"user\" handle so we can send messages\n peer: IPeer<SigType, SigPayload>;\n\n expirationTimeout?: number;\n close(): void;\n reset(): void;\n};\n\nconst DEFAULT_ENTER_ROOM = enterRoom;\n\nexport function collectPeerConnections({\n userId: passedUserId,\n worldId,\n receivePeerConnection,\n peerlessUserExpiration = 5000,\n fallbackRtcConfig = {\n iceServers: [{ urls: \"stun:stun.l.google.com:19302\" }],\n },\n enterRoomFunction: enterRoom = DEFAULT_ENTER_ROOM,\n logLine,\n onLeaveUser,\n workerUrl,\n onRoomReady,\n onRoomClose,\n onBroadcastMessage,\n}: {\n userId?: string;\n worldId: string;\n fallbackRtcConfig?: RTCConfiguration;\n enterRoomFunction?: EnterRoom<SigType, SigPayload>;\n onLeaveUser?: (userId: string) => void;\n logLine?: (direction: string, obj?: any) => void;\n workerUrl?: URL;\n peerlessUserExpiration?: number;\n receivePeerConnection(connection: {\n pc: RTCPeerConnection;\n userId: string;\n restart?: () => void;\n }): void;\n onRoomReady?(info: { host: string; room: string }): void;\n onRoomClose?(info: {\n host: string;\n room: string;\n ev: Pick<CloseEvent, \"reason\" | \"code\" | \"wasClean\">;\n }): void;\n onBroadcastMessage?<P extends any>(payload: P, from: string): void;\n}) {\n const userId = passedUserId ?? `user-${crypto.randomUUID()}`;\n const users: Map<string, UserState> = new Map();\n let iceUrl: { url: string; expiration: number } | undefined = undefined;\n let rtcConfig: RTCConfiguration & { timestamp: number } = {\n ...fallbackRtcConfig,\n timestamp: Date.now(),\n };\n\n const roomsEntered = new Map<\n string,\n {\n room: string;\n host: string;\n exitRoom: () => void;\n broadcast: <P extends any>(payload: P) => void;\n }\n >();\n\n async function getRtcConfig(\n iceUrl: string,\n retryIce: () => Promise<{ url: string }>,\n ): Promise<RTCConfiguration & { timestamp: number }> {\n if (iceUrl) {\n let retries = 3;\n for (let r = 0; r < retries; r++) {\n try {\n const r = await fetch(iceUrl);\n if (!r.ok) throw new Error(`ICE endpoint failed: ${r.status}`);\n rtcConfig = (await r.json()) as RTCConfiguration & {\n timestamp: number;\n };\n return rtcConfig;\n } catch (e) {\n console.warn(\"Failed fetching iceUrl\");\n }\n iceUrl = (await retryIce()).url;\n }\n }\n return rtcConfig;\n }\n\n function leaveUser(userId: string) {\n onLeaveUser?.(userId);\n const p = users.get(userId);\n if (!p) return;\n users.delete(userId);\n try {\n p.close();\n } catch {}\n }\n\n async function flushRemoteIce(state: UserState) {\n if (!state.pc?.remoteDescription) return;\n\n const queued = state.pendingRemoteIce;\n state.pendingRemoteIce = [];\n\n for (const ice of queued) {\n try {\n await state.pc.addIceCandidate(ice);\n } catch (e) {\n logLine?.(\"⚠️ ERROR\", {\n error: \"add-ice-failed\",\n userId: state.peer.userId,\n detail: String(e),\n });\n }\n }\n }\n\n function exit({ room, host }: { room: string; host: string }) {\n const key = `${host}/room/${room}`;\n const session = roomsEntered.get(key);\n if (session) {\n session.exitRoom();\n roomsEntered.delete(key);\n }\n }\n\n function enter({ room, host }: { room: string; host: string }) {\n return new Promise<void>(async (resolve, reject) => {\n async function setupPC(state: UserState) {\n const now = Date.now();\n if (now - (rtcConfig?.timestamp ?? 0) > 10000) {\n const ice =\n !iceUrl || iceUrl.expiration - now < 2000\n ? await requestIce()\n : iceUrl;\n rtcConfig = await getRtcConfig(ice.url, requestIce);\n }\n state.pc = new RTCPeerConnection(rtcConfig);\n // Send local ICE candidates to this peer\n state.pc.onicecandidate = (ev) => {\n if (!ev.candidate) return;\n state.peer.receive(\"ice\", ev.candidate.toJSON());\n };\n\n state.pc.onconnectionstatechange = async () => {\n logLine?.(\"💬\", {\n event: \"pc-state\",\n userId: state.peer.userId,\n state: state.pc?.connectionState,\n });\n if (state.pc?.connectionState === \"failed\") {\n // reset the connection\n state.close();\n const userState = await getPeer(state.peer, true);\n if (userState.pc) {\n receivePeerConnection({\n pc: userState.pc,\n userId: userState.peer.userId,\n restart: () => userState.close(),\n });\n } else {\n logLine?.(\"👤ℹ️\", \"no pc: \" + userState.peer.userId);\n }\n return;\n }\n };\n\n return state.pc;\n }\n\n async function getPeer(\n peer: IPeer<SigType, SigPayload>,\n forceReset?: boolean,\n ): Promise<UserState> {\n let state = users.get(peer.userId);\n if (!state || forceReset) {\n const newState: UserState = {\n pendingRemoteIce: [],\n peer,\n close() {\n this.pc?.close();\n this.pc = undefined;\n users.delete(peer.userId);\n },\n async reset() {\n newState.close();\n const userState = await getPeer(peer, true);\n console.log(\">>\", userState.peer.userId);\n\n setTimeout(async () => {\n if (!userState.pc) {\n console.log(\"no pc\");\n return;\n }\n receivePeerConnection({\n pc: userState.pc,\n userId: userState.peer.userId,\n restart: () => userState.close(),\n });\n await makeOffer(userState.peer);\n }, 3000);\n },\n };\n\n await setupPC(newState);\n state = newState;\n\n // New user\n users.set(state.peer.userId, state);\n } else if (state) {\n clearTimeout(state.expirationTimeout);\n state.expirationTimeout = 0;\n if (!state.pc || state.pc.signalingState === \"closed\") {\n await setupPC(state);\n }\n }\n state.peer = peer;\n return state;\n }\n\n async function makeOffer(user: IPeer) {\n // Offer flow: createOffer -> setLocalDescription -> send localDescription\n const state = await getPeer(user);\n const pc = state.pc;\n const offer = await pc?.createOffer();\n await pc?.setLocalDescription(offer);\n user.receive(\"offer\", pc?.localDescription?.toJSON()!);\n }\n\n let icePromiseResolve:\n | undefined\n | ((url: { url: string; expiration: number }) => void);\n async function requestIce() {\n const iceUrl = await new Promise<{ url: string; expiration: number }>(\n (resolve) => {\n icePromiseResolve = resolve;\n sendToServer(\"request-ice\");\n },\n );\n icePromiseResolve = undefined;\n return iceUrl;\n }\n\n const { exitRoom, sendToServer } = enterRoom({\n userId,\n worldId,\n room,\n host,\n logLine,\n workerUrl,\n autoRejoin: true,\n\n onOpen() {\n onRoomReady?.({ room, host });\n resolve();\n },\n onError() {\n console.error(\"onError\");\n reject();\n },\n onClose(ev: Pick<CloseEvent, \"reason\" | \"code\" | \"wasClean\">) {\n onRoomClose?.({ room, host, ev });\n },\n\n // Existing peers initiate to the newcomer\n onPeerJoined(joiningUsers: IPeer<SigType, SigPayload>[]) {\n joiningUsers.forEach(async (user) => {\n const state = await getPeer(user, true);\n const pc = state.pc;\n if (!pc) {\n logLine?.(\"👤ℹ️\", \"no pc: \" + user.userId);\n return;\n }\n\n receivePeerConnection({\n pc,\n userId: user.userId,\n restart: () => state.close(),\n });\n await makeOffer(user);\n });\n },\n\n onPeerLeft(leavingUsers: { userId: string }[]) {\n leavingUsers.forEach(({ userId }) => {\n const state = users.get(userId);\n if (!state) return;\n state.expirationTimeout = setTimeout(\n () => leaveUser(userId),\n peerlessUserExpiration ?? 0,\n );\n });\n },\n\n onIceUrl(url: string, expiration: number) {\n iceUrl = { url, expiration };\n icePromiseResolve?.(iceUrl);\n },\n\n async onMessage(type: SigType, payload: any, from: IPeer) {\n const state = await getPeer(from);\n\n if (type === \"offer\") {\n state.close(); // need new PC\n const pc = await setupPC(state);\n\n receivePeerConnection({\n pc,\n userId: from.userId,\n restart: () => state.close(),\n });\n // Responder: set remote offer\n await pc.setRemoteDescription(payload as RTCSessionDescriptionInit);\n\n // Create and send answer\n const answer = await pc.createAnswer();\n await pc.setLocalDescription(answer);\n\n from.receive(\"answer\", pc.localDescription?.toJSON()!);\n\n // Now safe to apply any queued ICE from this peer\n await flushRemoteIce(state);\n return;\n }\n\n const pc = state.pc ?? (await setupPC(state));\n\n if (type === \"answer\") {\n // Initiator: set remote answer\n await pc.setRemoteDescription(payload as RTCSessionDescriptionInit);\n await flushRemoteIce(state);\n return;\n }\n\n if (type === \"ice\") {\n const ice = payload as RTCIceCandidateInit;\n\n // If we don't have remoteDescription yet, queue it\n if (!pc.remoteDescription) {\n state.pendingRemoteIce.push(ice);\n return;\n }\n\n try {\n await state.pc?.addIceCandidate(ice);\n } catch (e) {\n logLine?.(\"⚠️ ERROR\", {\n error: \"add-ice-failed\",\n userId: state.peer.userId,\n detail: String(e),\n });\n }\n return;\n }\n\n if (type === \"broadcast\") {\n onBroadcastMessage?.(payload, from.userId);\n }\n },\n });\n roomsEntered.set(`${host}/room/${room}`, {\n exitRoom,\n room,\n host,\n broadcast: (payload) => sendToServer(\"broadcast\", payload),\n });\n });\n }\n\n return {\n userId,\n enterRoom: enter,\n exitRoom: exit,\n leaveUser,\n async reset(userId: string) {\n const userState = users.get(userId);\n userState?.reset();\n },\n broadcast<P extends any>(payload: P) {\n roomsEntered.forEach((room) => room.broadcast(payload));\n },\n end() {\n roomsEntered.forEach(({ exitRoom }) => exitRoom());\n roomsEntered.clear();\n users.forEach(({ peer }) => leaveUser(peer.userId));\n users.clear();\n },\n };\n}\n\n/*\nTurn Token ID\n<CF_TURN_TOKEN_ID>\n\nAPI Token\n<CF_RTC_API_TOKEN>\n\nCURL\ncurl \\\n\t-H \"Authorization: Bearer <CF_RTC_API_TOKEN>\" \\\n\t-H \"Content-Type: application/json\" -d '{\"ttl\": 86400}' \\\n\thttps://rtc.live.cloudflare.com/v1/turn/keys/<CF_TURN_TOKEN_ID>/credentials/generate-ice-servers\n\nJSON\n{\n\t\"iceServers\": [\n {\n \"urls\": [\n \"stun:stun.cloudflare.com:3478\",\n \"turn:turn.cloudflare.com:3478?transport=udp\",\n \"turn:turn.cloudflare.com:3478?transport=tcp\",\n \"turns:turn.cloudflare.com:5349?transport=tcp\"\n ],\n \"username\": \"xxxx\",\n \"credential\": \"yyyy\",\n }\n ]\n}\n\n*/\n",
8
+ "import { IceUrlProvider } from \"./ice-url-provider\";\n\nconst FALLBACK_RTC_CONFIG = {\n iceServers: [{ urls: \"stun:stun.l.google.com:19302\" }],\n};\n\nexport class RTCConfigProvider {\n constructor(private iceUrlProvider: IceUrlProvider) {}\n\n private rtcConfig: RTCConfiguration & { timestamp: number } = {\n ...FALLBACK_RTC_CONFIG,\n timestamp: Date.now(),\n };\n private rtcConfigPromise?: Promise<RTCConfiguration & { timestamp: number }>;\n\n async getRtcConfig(): Promise<RTCConfiguration & { timestamp: number }> {\n const now = Date.now();\n if (now - (this.rtcConfig?.timestamp ?? 0) < 10000) {\n return this.rtcConfig;\n }\n\n if (!this.rtcConfigPromise) {\n this.rtcConfigPromise = new Promise<\n RTCConfiguration & { timestamp: number }\n >(async (resolve) => {\n let retries = 3;\n for (let r = 0; r < retries; r++) {\n try {\n const iceUrl = (await this.iceUrlProvider.requestIce()).url;\n const r = await fetch(iceUrl);\n if (!r.ok) throw new Error(`ICE endpoint failed: ${r.status}`);\n const rtcConfig = (await r.json()) as RTCConfiguration & {\n timestamp: number;\n };\n resolve(rtcConfig);\n return;\n } catch (e) {\n console.warn(\"Failed fetching iceUrl\");\n }\n }\n });\n this.rtcConfig = await this.rtcConfigPromise;\n this.rtcConfigPromise = undefined;\n }\n return this.rtcConfig;\n }\n}\n",
9
+ "import { IceUrlProvider } from \"./utils/ice-url-provider\";\nimport { IPeer } from \"./signal/impl/signal-room\";\nimport { EnterRoom, enterRoom } from \"./signal/signal-room\";\nimport { RTCConfigProvider } from \"./utils/rtc-config\";\n\nexport type SigType = \"offer\" | \"answer\" | \"ice\" | \"request-ice\" | \"broadcast\";\nexport type SigPayload = {\n connectionId?: string;\n offer?: RTCSessionDescriptionInit;\n answer?: RTCSessionDescriptionInit;\n ice?: RTCIceCandidateInit;\n} & Record<string, any>;\n\ninterface Connection {\n id: string;\n peerConnectionId?: string;\n pc: RTCPeerConnection;\n // ICE that arrived before we had remoteDescription\n pendingRemoteIce: RTCIceCandidateInit[];\n}\n\ntype UserState = {\n connection?: Connection;\n\n // the signaling \"user\" handle so we can send messages\n peer: IPeer<SigType, SigPayload>;\n\n expirationTimeout?: number;\n close(): void;\n reset(): void;\n connectionPromise?: Promise<Connection>;\n};\n\nconst DEFAULT_ENTER_ROOM = enterRoom;\n\n/**\n * Collect peers\n */\nexport function collectPeerConnections({\n userId: passedUserId,\n worldId,\n receivePeerConnection,\n peerlessUserExpiration = 5000,\n enterRoomFunction: enterRoom = DEFAULT_ENTER_ROOM,\n logLine,\n onLeaveUser,\n workerUrl,\n onRoomReady,\n onRoomClose,\n onBroadcastMessage,\n}: {\n userId?: string;\n worldId: string;\n enterRoomFunction?: EnterRoom<SigType, SigPayload>;\n onLeaveUser?: (userId: string) => void;\n logLine?: (direction: string, obj?: any) => void;\n workerUrl?: URL;\n peerlessUserExpiration?: number;\n receivePeerConnection(connection: {\n pc: RTCPeerConnection;\n userId: string;\n restart?: () => void;\n }): void;\n onRoomReady?(info: { host: string; room: string }): void;\n onRoomClose?(info: {\n host: string;\n room: string;\n ev: Pick<CloseEvent, \"reason\" | \"code\" | \"wasClean\">;\n }): void;\n onBroadcastMessage?<P extends any>(payload: P, from: string): void;\n}) {\n const userId = passedUserId ?? `user-${crypto.randomUUID()}`;\n const users: Map<string, UserState> = new Map();\n\n const roomsEntered = new Map<\n string,\n {\n room: string;\n host: string;\n exitRoom: () => void;\n broadcast: <P extends any>(payload: P) => void;\n }\n >();\n\n function leaveUser(userId: string) {\n onLeaveUser?.(userId);\n const p = users.get(userId);\n if (!p) return;\n users.delete(userId);\n try {\n p.close();\n } catch {}\n }\n\n async function flushRemoteIce(state: UserState) {\n if (!state.connection?.pc?.remoteDescription) return;\n\n const queued = state.connection.pendingRemoteIce;\n state.connection.pendingRemoteIce = [];\n\n for (const ice of queued) {\n try {\n await state.connection.pc.addIceCandidate(ice);\n } catch (e) {\n logLine?.(\"⚠️ ERROR\", {\n error: \"add-ice-failed\",\n userId: state.peer.userId,\n detail: String(e),\n });\n }\n }\n }\n\n const iceUrlProvider = new IceUrlProvider();\n const rtcConfigProvider = new RTCConfigProvider(iceUrlProvider);\n\n function exit({ room, host }: { room: string; host: string }) {\n const key = `${host}/room/${room}`;\n const session = roomsEntered.get(key);\n if (session) {\n session.exitRoom();\n roomsEntered.delete(key);\n }\n }\n\n function enter({ room, host }: { room: string; host: string }) {\n return new Promise<void>(async (resolve, reject) => {\n async function setupConnection(state: UserState) {\n if (state.connectionPromise) {\n return state.connectionPromise;\n }\n return (state.connectionPromise = new Promise<Connection>(\n async (resolve) => {\n state.connection = {\n id: `conn-${crypto.randomUUID()}`,\n pc: new RTCPeerConnection(await rtcConfigProvider.getRtcConfig()),\n pendingRemoteIce: [],\n };\n\n // Send local ICE candidates to this peer\n state.connection.pc.onicecandidate = (ev) => {\n if (!ev.candidate) return;\n state.peer.receive(\"ice\", {\n connectionId: state.connection?.id,\n ice: ev.candidate.toJSON(),\n });\n };\n\n state.connection.pc.onconnectionstatechange = async () => {\n logLine?.(\"💬\", {\n event: \"pc-state\",\n userId: state.peer.userId,\n state: state.connection?.pc?.connectionState,\n });\n if (state.connection?.pc?.connectionState === \"failed\") {\n // reset the connection\n state.close();\n const userState = await getPeer(state.peer, true);\n if (userState.connection?.pc) {\n receivePeerConnection({\n pc: userState.connection?.pc,\n userId: userState.peer.userId,\n restart: () => userState.close(),\n });\n } else {\n logLine?.(\"👤ℹ️\", \"no pc: \" + userState.peer.userId);\n }\n return;\n }\n };\n\n resolve(state.connection);\n },\n ));\n }\n\n async function getPeer(\n peer: IPeer<SigType, SigPayload>,\n forceReset?: boolean,\n ): Promise<UserState> {\n let state = users.get(peer.userId);\n if (!state || forceReset) {\n const newState: UserState = {\n peer,\n close() {\n if (this.connection) {\n this.connection.pc.close();\n this.connection = undefined;\n }\n users.delete(peer.userId);\n },\n async reset() {\n newState.close();\n\n setTimeout(async () => {\n const userState = await getPeer(peer, true);\n if (!userState.connection?.pc) {\n logLine?.(\"⚠️\", \"no pc\");\n return;\n }\n receivePeerConnection({\n pc: userState.connection?.pc,\n userId: userState.peer.userId,\n restart: () => userState.close(),\n });\n await makeOffer(userState.peer);\n }, 500);\n },\n };\n state = newState;\n\n await setupConnection(newState);\n\n // New user\n users.set(state.peer.userId, state);\n } else if (state) {\n clearTimeout(state.expirationTimeout);\n state.expirationTimeout = 0;\n if (\n !state.connection?.pc ||\n state.connection?.pc.signalingState === \"closed\"\n ) {\n await setupConnection(state);\n }\n }\n state.peer = peer;\n return state;\n }\n\n async function makeOffer(user: IPeer<SigType, SigPayload>) {\n // Offer flow: createOffer -> setLocalDescription -> send localDescription\n const state = await getPeer(user);\n const pc = state.connection?.pc;\n const offer = await pc?.createOffer();\n await pc?.setLocalDescription(offer);\n user.receive(\"offer\", {\n connectionId: state.connection?.id,\n offer: pc!.localDescription!.toJSON(),\n });\n }\n\n const { exitRoom, sendToServer } = enterRoom({\n userId,\n worldId,\n room,\n host,\n logLine,\n workerUrl,\n autoRejoin: true,\n\n onOpen() {\n onRoomReady?.({ room, host });\n resolve();\n },\n onError() {\n console.error(\"onError\");\n reject();\n },\n onClose(ev: Pick<CloseEvent, \"reason\" | \"code\" | \"wasClean\">) {\n onRoomClose?.({ room, host, ev });\n },\n\n // Existing peers initiate to the newcomer\n onPeerJoined(joiningUsers: IPeer<SigType, SigPayload>[]) {\n joiningUsers.forEach(async (user) => {\n const state = await getPeer(user, true);\n const pc = state.connection?.pc;\n if (!pc) {\n logLine?.(\"👤ℹ️\", \"no pc: \" + user.userId);\n return;\n }\n\n receivePeerConnection({\n pc,\n userId: user.userId,\n restart: () => state.close(),\n });\n await makeOffer(user);\n });\n },\n\n onPeerLeft(leavingUsers: { userId: string }[]) {\n leavingUsers.forEach(({ userId }) => {\n const state = users.get(userId);\n if (!state) return;\n state.expirationTimeout = setTimeout(\n () => leaveUser(userId),\n peerlessUserExpiration ?? 0,\n );\n });\n },\n\n onIceUrl(url: string, expiration: number) {\n iceUrlProvider.receiveIce(url, expiration);\n },\n\n async onMessage(type, payload, from: IPeer<SigType, SigPayload>) {\n if (type === \"offer\" && payload.offer) {\n // Grab state and connection\n const state = await getPeer(from, false);\n const connection =\n !state.connection ||\n state.connection.pc.signalingState === \"stable\"\n ? await setupConnection(state)\n : state.connection; // reset\n logLine?.(\"💬\", {\n type,\n signalingState: connection.pc.signalingState,\n });\n\n connection.peerConnectionId = payload.connectionId;\n receivePeerConnection({\n pc: connection.pc,\n userId: from.userId,\n restart: () => state.close(),\n });\n // Responder: set remote offer\n await connection.pc.setRemoteDescription(payload.offer);\n\n // Create and send answer\n const answer = await connection.pc.createAnswer();\n await connection.pc.setLocalDescription(answer);\n\n from.receive(\"answer\", {\n connectionId: connection.id,\n answer: connection.pc.localDescription?.toJSON(),\n });\n\n // Now safe to apply any queued ICE from this peer\n await flushRemoteIce(state);\n return;\n }\n\n if (type === \"answer\" && payload.answer) {\n const state = await getPeer(from, false);\n const connection =\n state.connection &&\n state.connection.pc.signalingState !== \"closed\"\n ? state.connection\n : await setupConnection(state);\n logLine?.(\"💬\", {\n type,\n signalingState: connection.pc.signalingState,\n });\n\n // Initiator: set remote answer\n await connection.pc.setRemoteDescription(payload.answer);\n connection.peerConnectionId = payload.connectionId;\n await flushRemoteIce(state);\n return;\n }\n\n if (type === \"ice\" && payload.ice) {\n // Grab state and connection\n const state = await getPeer(from, false);\n const connection =\n state.connection ?? (await state.connectionPromise);\n if (!connection) {\n logLine?.(\"⚠️\", \"No connection\");\n return;\n }\n logLine?.(\"💬\", {\n type,\n signalingState: connection.pc.signalingState,\n });\n\n if (\n connection.peerConnectionId &&\n payload.connectionId !== connection.peerConnectionId\n ) {\n logLine?.(\n \"⚠️\",\n \"Mismatch peerConnectionID\" +\n payload.connectionId +\n \"vs\" +\n connection.peerConnectionId,\n );\n return;\n }\n\n // If we don't have remoteDescription yet (or if connectionId doesn't match), queue it\n if (\n !connection.pc.remoteDescription ||\n !connection.peerConnectionId\n ) {\n connection.peerConnectionId = payload.connectionId;\n connection.pendingRemoteIce.push(payload.ice);\n return;\n }\n\n try {\n await connection.pc.addIceCandidate(payload.ice);\n } catch (e) {\n logLine?.(\"⚠️ ERROR\", {\n error: \"add-ice-failed\",\n userId: state.peer.userId,\n detail: String(e),\n });\n }\n return;\n }\n\n if (type === \"broadcast\") {\n onBroadcastMessage?.(payload, from.userId);\n }\n },\n });\n\n iceUrlProvider.addRequester(sendToServer);\n\n roomsEntered.set(`${host}/room/${room}`, {\n exitRoom: () => {\n exitRoom();\n iceUrlProvider.removeRequester(sendToServer);\n },\n room,\n host,\n broadcast: (payload) => sendToServer(\"broadcast\", payload),\n });\n });\n }\n\n return {\n userId,\n enterRoom: enter,\n exitRoom: exit,\n leaveUser,\n async reset(userId: string) {\n const userState = users.get(userId);\n userState?.reset();\n },\n broadcast<P extends any>(payload: P) {\n roomsEntered.forEach((room) => room.broadcast(payload));\n },\n end() {\n roomsEntered.forEach(({ exitRoom }) => exitRoom());\n roomsEntered.clear();\n users.forEach(({ peer }) => leaveUser(peer.userId));\n users.clear();\n },\n };\n}\n\n/*\nTurn Token ID\n<CF_TURN_TOKEN_ID>\n\nAPI Token\n<CF_RTC_API_TOKEN>\n\nCURL\ncurl \\\n\t-H \"Authorization: Bearer <CF_RTC_API_TOKEN>\" \\\n\t-H \"Content-Type: application/json\" -d '{\"ttl\": 86400}' \\\n\thttps://rtc.live.cloudflare.com/v1/turn/keys/<CF_TURN_TOKEN_ID>/credentials/generate-ice-servers\n\nJSON\n{\n\t\"iceServers\": [\n {\n \"urls\": [\n \"stun:stun.cloudflare.com:3478\",\n \"turn:turn.cloudflare.com:3478?transport=udp\",\n \"turn:turn.cloudflare.com:3478?transport=tcp\",\n \"turns:turn.cloudflare.com:5349?transport=tcp\"\n ],\n \"username\": \"xxxx\",\n \"credential\": \"yyyy\",\n }\n ]\n}\n\n*/\n",
8
10
  "import { EnterRoom, enterRoom } from \"./signal/signal-room\";\nimport {\n SigType,\n SigPayload,\n collectPeerConnections,\n} from \"./webrtc-peer-collector\";\n\ntype UserListener = (\n user: string,\n action: \"join\" | \"leave\",\n users: string[],\n) => void;\n\nexport function enterWorld<\n S extends string | ArrayBufferView = string | ArrayBufferView,\n R extends string | ArrayBufferLike = string | ArrayBufferLike,\n>({\n userId: passedUserId,\n worldId,\n logLine,\n enterRoomFunction = enterRoom,\n peerlessUserExpiration,\n workerUrl,\n onRoomReady,\n onRoomClose,\n dataChannelOptions,\n}: {\n userId?: string;\n worldId: string;\n logLine?: (...obj: any[]) => void;\n enterRoomFunction?: EnterRoom<SigType, SigPayload>;\n peerlessUserExpiration?: number;\n workerUrl?: URL;\n onRoomReady?(info: { host: string; room: string }): void;\n onRoomClose?(info: {\n host: string;\n room: string;\n ev: Pick<CloseEvent, \"reason\" | \"code\" | \"wasClean\">;\n }): void;\n dataChannelOptions?: RTCDataChannelInit;\n}) {\n const userIds = new Set<string>();\n const dataChannels = new Map<string, RTCDataChannel>();\n const userListeners = new Set<UserListener>();\n const messagesListeners = new Set<(data: R, from: string) => void>();\n\n function createDataChannel(\n pc: RTCPeerConnection,\n peerUserId: string,\n restart?: () => void,\n ) {\n function listener(ev: RTCDataChannelEvent) {\n const dc = ev.channel;\n wireDataChannel(peerUserId, dc, restart);\n dataChannels.set(peerUserId, dc);\n }\n pc.addEventListener(\"datachannel\", listener);\n const dc = pc.createDataChannel(\"data\", dataChannelOptions);\n wireDataChannel(peerUserId, dc, restart);\n dataChannels.set(peerUserId, dc);\n return () => {\n pc.removeEventListener(\"datachannel\", listener);\n };\n }\n\n function conveyMessage(data: any, userId: string) {\n messagesListeners.forEach((listener) => listener(data, userId));\n }\n\n function wireDataChannel(\n userId: string,\n dc: RTCDataChannel,\n restart?: () => void,\n ) {\n dc.onopen = () => {\n logLine?.(\"💬\", { event: \"dc-open\", userId });\n userIds.add(userId);\n userListeners.forEach((listener) =>\n listener(userId, \"join\", [...userIds]),\n );\n };\n const onmessage = ({ data }: MessageEvent) => {\n conveyMessage(data, userId);\n };\n dc.addEventListener(\"message\", onmessage);\n dc.onclose = () => {\n logLine?.(\"💬\", { event: \"dc-close\", userId });\n userIds.delete(userId);\n userListeners.forEach((listener) =>\n listener(userId, \"leave\", [...userIds]),\n );\n dc.removeEventListener(\"message\", onmessage);\n dc.onopen = null;\n dc.onclose = null;\n dc.onerror = null;\n dc.close();\n restart?.();\n };\n dc.onerror = () => logLine?.(\"⚠️ ERROR\", { error: \"dc-error\", userId });\n }\n\n const {\n userId,\n enterRoom,\n exitRoom,\n leaveUser,\n broadcast,\n end: endPeerCollection,\n reset: resetPeerCollection,\n } = collectPeerConnections({\n userId: passedUserId,\n worldId,\n enterRoomFunction,\n logLine,\n workerUrl,\n peerlessUserExpiration,\n onRoomReady,\n onRoomClose,\n onLeaveUser(userId: string) {\n const dc = dataChannels.get(userId);\n try {\n dc?.close();\n } catch {}\n dataChannels.delete(userId);\n },\n receivePeerConnection({ pc, userId, restart }) {\n createDataChannel(pc, userId, restart);\n },\n onBroadcastMessage(payload, from) {\n conveyMessage(payload, from);\n logLine?.(\"📢\", { event: \"broadcast\", userId, data: payload });\n },\n });\n\n function send(data: S, userId?: string) {\n dataChannels.forEach((dataChannel, pUserId) => {\n if (userId && pUserId !== userId) return;\n if (dataChannel.readyState === \"open\") {\n dataChannel.send(data as any);\n }\n });\n }\n\n function removeMessageListener(listener: (data: R, from: string) => void) {\n messagesListeners.delete(listener);\n }\n\n function addMessageListener(listener: (data: R, from: string) => void) {\n messagesListeners.add(listener);\n return () => {\n removeMessageListener(listener);\n };\n }\n\n function removeUserListener(listener: UserListener) {\n userListeners.delete(listener);\n }\n\n function addUserListener(listener: UserListener) {\n userListeners.add(listener);\n return () => {\n removeUserListener(listener);\n };\n }\n\n return {\n userId,\n send,\n broadcast,\n enterRoom,\n exitRoom,\n leaveUser,\n getUsers: () => [...userIds],\n addMessageListener,\n removeMessageListener,\n addUserListener,\n removeUserListener,\n reset() {\n userIds.forEach((userId) => {\n dataChannels.get(userId)?.close();\n dataChannels.delete(userId);\n resetPeerCollection(userId);\n });\n },\n end() {\n dataChannels.forEach((dataChannel) => {\n try {\n dataChannel.close();\n } catch {}\n });\n dataChannels.clear();\n endPeerCollection();\n userListeners.clear();\n userIds.clear();\n },\n };\n}\n"
9
11
  ],
10
- "mappings": "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",
11
- "debugId": "6F46100BAA9D39CE64756E2164756E21",
12
+ "mappings": "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",
13
+ "debugId": "4C800C9DE42E87E164756E2164756E21",
12
14
  "names": []
13
15
  }
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- function i(J){let{userId:C,worldId:q,room:j,host:v,autoRejoin:y=!0,logLine:H}=J,L=!1,R=0,$,E,x=!0,O=new Map,F=`wss://${v}/room/${q}/${j}?userId=${encodeURIComponent(C)}`,z=[],M=0;function K(V,b,S){if(!$)return H?.("\uD83D\uDC64 ➡️ ❌","no ws available"),!1;let D={type:V,to:b,payload:S};if(z.push(D),H?.("\uD83D\uDC64 ➡️ \uD83D\uDDA5️",D),clearTimeout(M),L||$.readyState!==WebSocket.OPEN)return H?.("\uD83D\uDC64 ➡️ ❌","Not in opened state: "+$.readyState),!1;return M=setTimeout(()=>{$.send(JSON.stringify(z)),z.length=0}),!0}function k(){if(L)return;$=new WebSocket(F),$.onopen=()=>{if(x)J.onOpen?.(),x=!1;R=0},$.onmessage=(V)=>{try{let b=JSON.parse(V.data);(Array.isArray(b)?b:[b]).forEach((D)=>{if(H?.("\uD83D\uDDA5️ ➡️ \uD83D\uDC64",D),D.type==="peer-joined"||D.type==="peer-left")f(D.users,D);else if(D.type==="ice-server")J.onIceUrl?.(D.url,D.expiration);else if(D.userId)J.onMessage(D.type,D.payload,{userId:D.userId,receive:(Y,W)=>K(Y,D.userId,W)})})}catch{H?.("⚠️ ERROR",{error:"invalid-json"})}},$.onclose=(V)=>{let S=[1001,1006,1011,1012,1013].includes(V.code);if(y&&!L&&S){let D=Math.min(Math.pow(2,R)*1000,30000),Y=Math.random()*1000,W=D+Y;H?.("\uD83D\uDD04 RECONNECTING",{attempt:R+1,delayMs:Math.round(W)}),R++,E=setTimeout(k,W)}else J.onClose?.({code:V.code,reason:V.reason,wasClean:V.wasClean})},$.onerror=(V)=>{console.error("WS Error",V),J.onError?.()}}function f(V,b){let S=[],D=[],Y=new Set;V.forEach(({userId:W})=>{if(W===C)return;if(!O.has(W)||b.type==="peer-joined"&&W===b.userId){let h={userId:W,receive:(N,P)=>K(N,W,P)};O.set(W,h),S.push(h)}Y.add(W)});for(let W of O.keys())if(!Y.has(W)||b.type==="peer-left"&&W===b.userId)O.delete(W),D.push({userId:W});if(S.length)J.onPeerJoined(S);if(D.length)J.onPeerLeft(D)}return k(),{sendToServer(V,b){K(V,"server",b)},exitRoom:()=>{L=!0,clearTimeout(E),$.close()}}}function U({userId:J,worldId:C,room:q,host:j,autoRejoin:v=!0,onOpen:y,onClose:H,onError:L,onPeerJoined:R,onPeerLeft:$,onIceUrl:E,onMessage:x,logLine:O,workerUrl:F}){if(!F)return console.warn("Warning: enterRoom called without workerUrl; this may cause issues in some environments. You should pass workerUrl explicitly. Use:","https://cdn.jsdelivr.net/npm/@dobuki/hello-worker/dist/signal-room.worker.min.js"),i({userId:J,worldId:C,room:q,host:j,autoRejoin:v,onOpen:y,onClose:H,onError:L,onPeerJoined:R,onPeerLeft:$,onIceUrl:E,onMessage:x});let z=new Worker(F,{type:"module"}),M=!1;function K({userId:f}){return{userId:f,receive:(V,b)=>{if(M)return!1;return z.postMessage({cmd:"send",toUserId:f,host:j,room:q,type:V,payload:b}),!0}}}let k=(f)=>{let V=f.data;if(V.kind==="open")y?.();else if(V.kind==="close")z.terminate(),H?.(V.ev);else if(V.kind==="error")L?.();else if(V.kind==="peer-joined")R(V.users.map((b)=>K({userId:b.userId})));else if(V.kind==="peer-left")$(V.users);else if(V.kind==="ice-server")E?.(V.url,V.expiration);else if(V.kind==="message")x(V.type,V.payload,K({userId:V.fromUserId}));else if(V.kind==="log")O?.(V.direction,V.obj)};return z.addEventListener("message",k),z.postMessage({cmd:"enter",userId:J,worldId:C,room:q,host:j,autoRejoin:v}),{exitRoom:()=>{M=!0,z.removeEventListener("message",k),z.postMessage({cmd:"exit"})},sendToServer:(f,V)=>{z.postMessage({cmd:"send",toUserId:"server",host:j,room:q,type:f,payload:V})}}}var m=U;function I({userId:J,worldId:C,receivePeerConnection:q,peerlessUserExpiration:j=5000,fallbackRtcConfig:v={iceServers:[{urls:"stun:stun.l.google.com:19302"}]},enterRoomFunction:y=m,logLine:H,onLeaveUser:L,workerUrl:R,onRoomReady:$,onRoomClose:E,onBroadcastMessage:x}){let O=J??`user-${crypto.randomUUID()}`,F=new Map,z=void 0,M={...v,timestamp:Date.now()},K=new Map;async function k(D,Y){if(D){let W=3;for(let h=0;h<W;h++){try{let N=await fetch(D);if(!N.ok)throw Error(`ICE endpoint failed: ${N.status}`);return M=await N.json(),M}catch(N){console.warn("Failed fetching iceUrl")}D=(await Y()).url}}return M}function f(D){L?.(D);let Y=F.get(D);if(!Y)return;F.delete(D);try{Y.close()}catch{}}async function V(D){if(!D.pc?.remoteDescription)return;let Y=D.pendingRemoteIce;D.pendingRemoteIce=[];for(let W of Y)try{await D.pc.addIceCandidate(W)}catch(h){H?.("⚠️ ERROR",{error:"add-ice-failed",userId:D.peer.userId,detail:String(h)})}}function b({room:D,host:Y}){let W=`${Y}/room/${D}`,h=K.get(W);if(h)h.exitRoom(),K.delete(W)}function S({room:D,host:Y}){return new Promise(async(W,h)=>{async function N(G){let Z=Date.now();if(Z-(M?.timestamp??0)>1e4){let Q=!z||z.expiration-Z<2000?await T():z;M=await k(Q.url,T)}return G.pc=new RTCPeerConnection(M),G.pc.onicecandidate=(Q)=>{if(!Q.candidate)return;G.peer.receive("ice",Q.candidate.toJSON())},G.pc.onconnectionstatechange=async()=>{if(H?.("\uD83D\uDCAC",{event:"pc-state",userId:G.peer.userId,state:G.pc?.connectionState}),G.pc?.connectionState==="failed"){G.close();let Q=await P(G.peer,!0);if(Q.pc)q({pc:Q.pc,userId:Q.peer.userId,restart:()=>Q.close()});else H?.("\uD83D\uDC64ℹ️","no pc: "+Q.peer.userId);return}},G.pc}async function P(G,Z){let Q=F.get(G.userId);if(!Q||Z){let A={pendingRemoteIce:[],peer:G,close(){this.pc?.close(),this.pc=void 0,F.delete(G.userId)},async reset(){A.close();let n=await P(G,!0);console.log(">>",n.peer.userId),setTimeout(async()=>{if(!n.pc){console.log("no pc");return}q({pc:n.pc,userId:n.peer.userId,restart:()=>n.close()}),await B(n.peer)},3000)}};await N(A),Q=A,F.set(Q.peer.userId,Q)}else if(Q){if(clearTimeout(Q.expirationTimeout),Q.expirationTimeout=0,!Q.pc||Q.pc.signalingState==="closed")await N(Q)}return Q.peer=G,Q}async function B(G){let Q=(await P(G)).pc,A=await Q?.createOffer();await Q?.setLocalDescription(A),G.receive("offer",Q?.localDescription?.toJSON())}let X;async function T(){let G=await new Promise((Z)=>{X=Z,_("request-ice")});return X=void 0,G}let{exitRoom:w,sendToServer:_}=y({userId:O,worldId:C,room:D,host:Y,logLine:H,workerUrl:R,autoRejoin:!0,onOpen(){$?.({room:D,host:Y}),W()},onError(){console.error("onError"),h()},onClose(G){E?.({room:D,host:Y,ev:G})},onPeerJoined(G){G.forEach(async(Z)=>{let Q=await P(Z,!0),A=Q.pc;if(!A){H?.("\uD83D\uDC64ℹ️","no pc: "+Z.userId);return}q({pc:A,userId:Z.userId,restart:()=>Q.close()}),await B(Z)})},onPeerLeft(G){G.forEach(({userId:Z})=>{let Q=F.get(Z);if(!Q)return;Q.expirationTimeout=setTimeout(()=>f(Z),j??0)})},onIceUrl(G,Z){z={url:G,expiration:Z},X?.(z)},async onMessage(G,Z,Q){let A=await P(Q);if(G==="offer"){A.close();let c=await N(A);q({pc:c,userId:Q.userId,restart:()=>A.close()}),await c.setRemoteDescription(Z);let g=await c.createAnswer();await c.setLocalDescription(g),Q.receive("answer",c.localDescription?.toJSON()),await V(A);return}let n=A.pc??await N(A);if(G==="answer"){await n.setRemoteDescription(Z),await V(A);return}if(G==="ice"){let c=Z;if(!n.remoteDescription){A.pendingRemoteIce.push(c);return}try{await A.pc?.addIceCandidate(c)}catch(g){H?.("⚠️ ERROR",{error:"add-ice-failed",userId:A.peer.userId,detail:String(g)})}return}if(G==="broadcast")x?.(Z,Q.userId)}});K.set(`${Y}/room/${D}`,{exitRoom:w,room:D,host:Y,broadcast:(G)=>_("broadcast",G)})})}return{userId:O,enterRoom:S,exitRoom:b,leaveUser:f,async reset(D){F.get(D)?.reset()},broadcast(D){K.forEach((Y)=>Y.broadcast(D))},end(){K.forEach(({exitRoom:D})=>D()),K.clear(),F.forEach(({peer:D})=>f(D.userId)),F.clear()}}}function d({userId:J,worldId:C,logLine:q,enterRoomFunction:j=U,peerlessUserExpiration:v,workerUrl:y,onRoomReady:H,onRoomClose:L,dataChannelOptions:R}){let $=new Set,E=new Map,x=new Set,O=new Set;function F(B,X,T){function w(G){let Z=G.channel;M(X,Z,T),E.set(X,Z)}B.addEventListener("datachannel",w);let _=B.createDataChannel("data",R);return M(X,_,T),E.set(X,_),()=>{B.removeEventListener("datachannel",w)}}function z(B,X){O.forEach((T)=>T(B,X))}function M(B,X,T){X.onopen=()=>{q?.("\uD83D\uDCAC",{event:"dc-open",userId:B}),$.add(B),x.forEach((_)=>_(B,"join",[...$]))};let w=({data:_})=>{z(_,B)};X.addEventListener("message",w),X.onclose=()=>{q?.("\uD83D\uDCAC",{event:"dc-close",userId:B}),$.delete(B),x.forEach((_)=>_(B,"leave",[...$])),X.removeEventListener("message",w),X.onopen=null,X.onclose=null,X.onerror=null,X.close(),T?.()},X.onerror=()=>q?.("⚠️ ERROR",{error:"dc-error",userId:B})}let{userId:K,enterRoom:k,exitRoom:f,leaveUser:V,broadcast:b,end:S,reset:D}=I({userId:J,worldId:C,enterRoomFunction:j,logLine:q,workerUrl:y,peerlessUserExpiration:v,onRoomReady:H,onRoomClose:L,onLeaveUser(B){let X=E.get(B);try{X?.close()}catch{}E.delete(B)},receivePeerConnection({pc:B,userId:X,restart:T}){F(B,X,T)},onBroadcastMessage(B,X){z(B,X),q?.("\uD83D\uDCE2",{event:"broadcast",userId:K,data:B})}});function Y(B,X){E.forEach((T,w)=>{if(X&&w!==X)return;if(T.readyState==="open")T.send(B)})}function W(B){O.delete(B)}function h(B){return O.add(B),()=>{W(B)}}function N(B){x.delete(B)}function P(B){return x.add(B),()=>{N(B)}}return{userId:K,send:Y,broadcast:b,enterRoom:k,exitRoom:f,leaveUser:V,getUsers:()=>[...$],addMessageListener:h,removeMessageListener:W,addUserListener:P,removeUserListener:N,reset(){$.forEach((B)=>{E.get(B)?.close(),E.delete(B),D(B)})},end(){E.forEach((B)=>{try{B.close()}catch{}}),E.clear(),S(),x.clear(),$.clear()}}}export{d as enterWorld,i as enterRoom,I as collectPeerConnections};
1
+ class j{sendToServerFunctions=[];icePromiseResolve;icePromise;receiveIce(T,E){this.icePromiseResolve?.({url:T,expiration:E}),this.icePromiseResolve=void 0,this.icePromise=void 0}addRequester(T){return this.sendToServerFunctions.push(T),()=>{this.removeRequester(T)}}removeRequester(T){this.sendToServerFunctions.splice(this.sendToServerFunctions.indexOf(T),1)}sendToServer(T){this.sendToServerFunctions[Math.floor(this.sendToServerFunctions.length*Math.random())](T)}async requestIce(){if(!this.icePromise)this.icePromise=new Promise((T)=>{this.icePromiseResolve=T,this.sendToServer("request-ice")});return await this.icePromise}}function k(T){let{userId:E,worldId:c,room:H,host:M,autoRejoin:V=!0,logLine:X}=T,J=!1,P=0,G,W,Z=!0,$=new Map,S=`wss://${M}/room/${c}/${H}?userId=${encodeURIComponent(E)}`,Q=[],N=0;function z(q,h,B){if(!G)return X?.("\uD83D\uDC64 ➡️ ❌","no ws available"),!1;let D={type:q,to:h,payload:B};if(Q.push(D),X?.("\uD83D\uDC64 ➡️ \uD83D\uDDA5️",D),clearTimeout(N),J||G.readyState!==WebSocket.OPEN)return X?.("\uD83D\uDC64 ➡️ ❌","Not in opened state: "+G.readyState),!1;return N=setTimeout(()=>{G.send(JSON.stringify(Q)),Q.length=0}),!0}function O(){if(J)return;G=new WebSocket(S),G.onopen=()=>{if(Z)T.onOpen?.(),Z=!1;P=0},G.onmessage=(q)=>{try{let h=JSON.parse(q.data);(Array.isArray(h)?h:[h]).forEach((D)=>{if(X?.("\uD83D\uDDA5️ ➡️ \uD83D\uDC64",D),D.type==="peer-joined"||D.type==="peer-left")K(D.users,D);else if(D.type==="ice-server")T.onIceUrl?.(D.url,D.expiration);else if(D.userId)T.onMessage(D.type,D.payload,{userId:D.userId,receive:(Y,A)=>z(Y,D.userId,A)})})}catch{X?.("⚠️ ERROR",{error:"invalid-json"})}},G.onclose=(q)=>{let B=[1001,1006,1011,1012,1013].includes(q.code);if(V&&!J&&B){let D=Math.min(Math.pow(2,P)*1000,30000),Y=Math.random()*1000,A=D+Y;X?.("\uD83D\uDD04 RECONNECTING",{attempt:P+1,delayMs:Math.round(A)}),P++,W=setTimeout(O,A)}else T.onClose?.({code:q.code,reason:q.reason,wasClean:q.wasClean})},G.onerror=(q)=>{console.error("WS Error",q),T.onError?.()}}function K(q,h){let B=[],D=[],Y=new Set;q.forEach(({userId:A})=>{if(A===E)return;if(!$.has(A)||h.type==="peer-joined"&&A===h.userId){let x={userId:A,receive:(v,n)=>z(v,A,n)};$.set(A,x),B.push(x)}Y.add(A)});for(let A of $.keys())if(!Y.has(A)||h.type==="peer-left"&&A===h.userId)$.delete(A),D.push({userId:A});if(B.length)T.onPeerJoined(B);if(D.length)T.onPeerLeft(D)}return O(),{sendToServer(q,h){z(q,"server",h)},exitRoom:()=>{J=!0,clearTimeout(W),G.close()}}}function w({userId:T,worldId:E,room:c,host:H,autoRejoin:M=!0,onOpen:V,onClose:X,onError:J,onPeerJoined:P,onPeerLeft:G,onIceUrl:W,onMessage:Z,logLine:$,workerUrl:S}){if(!S)return console.warn("Warning: enterRoom called without workerUrl; this may cause issues in some environments. You should pass workerUrl explicitly. Use:","https://cdn.jsdelivr.net/npm/@dobuki/hello-worker/dist/signal-room.worker.min.js"),k({userId:T,worldId:E,room:c,host:H,autoRejoin:M,onOpen:V,onClose:X,onError:J,onPeerJoined:P,onPeerLeft:G,onIceUrl:W,onMessage:Z});let Q=new Worker(S,{type:"module"}),N=!1;function z({userId:K}){return{userId:K,receive:(q,h)=>{if(N)return!1;return Q.postMessage({cmd:"send",toUserId:K,host:H,room:c,type:q,payload:h}),!0}}}let O=(K)=>{let q=K.data;if(q.kind==="open")V?.();else if(q.kind==="close")Q.terminate(),X?.(q.ev);else if(q.kind==="error")J?.();else if(q.kind==="peer-joined")P(q.users.map((h)=>z({userId:h.userId})));else if(q.kind==="peer-left")G(q.users);else if(q.kind==="ice-server")W?.(q.url,q.expiration);else if(q.kind==="message")Z(q.type,q.payload,z({userId:q.fromUserId}));else if(q.kind==="log")$?.(q.direction,q.obj)};return Q.addEventListener("message",O),Q.postMessage({cmd:"enter",userId:T,worldId:E,room:c,host:H,autoRejoin:M}),{exitRoom:()=>{N=!0,Q.removeEventListener("message",O),Q.postMessage({cmd:"exit"})},sendToServer:(K,q)=>{Q.postMessage({cmd:"send",toUserId:"server",host:H,room:c,type:K,payload:q})}}}var U={iceServers:[{urls:"stun:stun.l.google.com:19302"}]};class L{iceUrlProvider;constructor(T){this.iceUrlProvider=T}rtcConfig={...U,timestamp:Date.now()};rtcConfigPromise;async getRtcConfig(){if(Date.now()-(this.rtcConfig?.timestamp??0)<1e4)return this.rtcConfig;if(!this.rtcConfigPromise)this.rtcConfigPromise=new Promise(async(E)=>{let c=3;for(let H=0;H<c;H++)try{let M=(await this.iceUrlProvider.requestIce()).url,V=await fetch(M);if(!V.ok)throw Error(`ICE endpoint failed: ${V.status}`);let X=await V.json();E(X);return}catch(M){console.warn("Failed fetching iceUrl")}}),this.rtcConfig=await this.rtcConfigPromise,this.rtcConfigPromise=void 0;return this.rtcConfig}}var m=w;function g({userId:T,worldId:E,receivePeerConnection:c,peerlessUserExpiration:H=5000,enterRoomFunction:M=m,logLine:V,onLeaveUser:X,workerUrl:J,onRoomReady:P,onRoomClose:G,onBroadcastMessage:W}){let Z=T??`user-${crypto.randomUUID()}`,$=new Map,S=new Map;function Q(h){X?.(h);let B=$.get(h);if(!B)return;$.delete(h);try{B.close()}catch{}}async function N(h){if(!h.connection?.pc?.remoteDescription)return;let B=h.connection.pendingRemoteIce;h.connection.pendingRemoteIce=[];for(let D of B)try{await h.connection.pc.addIceCandidate(D)}catch(Y){V?.("⚠️ ERROR",{error:"add-ice-failed",userId:h.peer.userId,detail:String(Y)})}}let z=new j,O=new L(z);function K({room:h,host:B}){let D=`${B}/room/${h}`,Y=S.get(D);if(Y)Y.exitRoom(),S.delete(D)}function q({room:h,host:B}){return new Promise(async(D,Y)=>{async function A(i){if(i.connectionPromise)return i.connectionPromise;return i.connectionPromise=new Promise(async(b)=>{i.connection={id:`conn-${crypto.randomUUID()}`,pc:new RTCPeerConnection(await O.getRtcConfig()),pendingRemoteIce:[]},i.connection.pc.onicecandidate=(f)=>{if(!f.candidate)return;i.peer.receive("ice",{connectionId:i.connection?.id,ice:f.candidate.toJSON()})},i.connection.pc.onconnectionstatechange=async()=>{if(V?.("\uD83D\uDCAC",{event:"pc-state",userId:i.peer.userId,state:i.connection?.pc?.connectionState}),i.connection?.pc?.connectionState==="failed"){i.close();let f=await x(i.peer,!0);if(f.connection?.pc)c({pc:f.connection?.pc,userId:f.peer.userId,restart:()=>f.close()});else V?.("\uD83D\uDC64ℹ️","no pc: "+f.peer.userId);return}},b(i.connection)})}async function x(i,b){let f=$.get(i.userId);if(!f||b){let R={peer:i,close(){if(this.connection)this.connection.pc.close(),this.connection=void 0;$.delete(i.userId)},async reset(){R.close(),setTimeout(async()=>{let F=await x(i,!0);if(!F.connection?.pc){V?.("⚠️","no pc");return}c({pc:F.connection?.pc,userId:F.peer.userId,restart:()=>F.close()}),await v(F.peer)},500)}};f=R,await A(R),$.set(f.peer.userId,f)}else if(f){if(clearTimeout(f.expirationTimeout),f.expirationTimeout=0,!f.connection?.pc||f.connection?.pc.signalingState==="closed")await A(f)}return f.peer=i,f}async function v(i){let b=await x(i),f=b.connection?.pc,R=await f?.createOffer();await f?.setLocalDescription(R),i.receive("offer",{connectionId:b.connection?.id,offer:f.localDescription.toJSON()})}let{exitRoom:n,sendToServer:C}=M({userId:Z,worldId:E,room:h,host:B,logLine:V,workerUrl:J,autoRejoin:!0,onOpen(){P?.({room:h,host:B}),D()},onError(){console.error("onError"),Y()},onClose(i){G?.({room:h,host:B,ev:i})},onPeerJoined(i){i.forEach(async(b)=>{let f=await x(b,!0),R=f.connection?.pc;if(!R){V?.("\uD83D\uDC64ℹ️","no pc: "+b.userId);return}c({pc:R,userId:b.userId,restart:()=>f.close()}),await v(b)})},onPeerLeft(i){i.forEach(({userId:b})=>{let f=$.get(b);if(!f)return;f.expirationTimeout=setTimeout(()=>Q(b),H??0)})},onIceUrl(i,b){z.receiveIce(i,b)},async onMessage(i,b,f){if(i==="offer"&&b.offer){let R=await x(f,!1),F=!R.connection||R.connection.pc.signalingState==="stable"?await A(R):R.connection;V?.("\uD83D\uDCAC",{type:i,signalingState:F.pc.signalingState}),F.peerConnectionId=b.connectionId,c({pc:F.pc,userId:f.userId,restart:()=>R.close()}),await F.pc.setRemoteDescription(b.offer);let _=await F.pc.createAnswer();await F.pc.setLocalDescription(_),f.receive("answer",{connectionId:F.id,answer:F.pc.localDescription?.toJSON()}),await N(R);return}if(i==="answer"&&b.answer){let R=await x(f,!1),F=R.connection&&R.connection.pc.signalingState!=="closed"?R.connection:await A(R);V?.("\uD83D\uDCAC",{type:i,signalingState:F.pc.signalingState}),await F.pc.setRemoteDescription(b.answer),F.peerConnectionId=b.connectionId,await N(R);return}if(i==="ice"&&b.ice){let R=await x(f,!1),F=R.connection??await R.connectionPromise;if(!F){V?.("⚠️","No connection");return}if(V?.("\uD83D\uDCAC",{type:i,signalingState:F.pc.signalingState}),F.peerConnectionId&&b.connectionId!==F.peerConnectionId){V?.("⚠️","Mismatch peerConnectionID"+b.connectionId+"vs"+F.peerConnectionId);return}if(!F.pc.remoteDescription||!F.peerConnectionId){F.peerConnectionId=b.connectionId,F.pendingRemoteIce.push(b.ice);return}try{await F.pc.addIceCandidate(b.ice)}catch(_){V?.("⚠️ ERROR",{error:"add-ice-failed",userId:R.peer.userId,detail:String(_)})}return}if(i==="broadcast")W?.(b,f.userId)}});z.addRequester(C),S.set(`${B}/room/${h}`,{exitRoom:()=>{n(),z.removeRequester(C)},room:h,host:B,broadcast:(i)=>C("broadcast",i)})})}return{userId:Z,enterRoom:q,exitRoom:K,leaveUser:Q,async reset(h){$.get(h)?.reset()},broadcast(h){S.forEach((B)=>B.broadcast(h))},end(){S.forEach(({exitRoom:h})=>h()),S.clear(),$.forEach(({peer:h})=>Q(h.userId)),$.clear()}}}function u({userId:T,worldId:E,logLine:c,enterRoomFunction:H=w,peerlessUserExpiration:M,workerUrl:V,onRoomReady:X,onRoomClose:J,dataChannelOptions:P}){let G=new Set,W=new Map,Z=new Set,$=new Set;function S(C,i,b){function f(F){let _=F.channel;N(i,_,b),W.set(i,_)}C.addEventListener("datachannel",f);let R=C.createDataChannel("data",P);return N(i,R,b),W.set(i,R),()=>{C.removeEventListener("datachannel",f)}}function Q(C,i){$.forEach((b)=>b(C,i))}function N(C,i,b){i.onopen=()=>{c?.("\uD83D\uDCAC",{event:"dc-open",userId:C}),G.add(C),Z.forEach((R)=>R(C,"join",[...G]))};let f=({data:R})=>{Q(R,C)};i.addEventListener("message",f),i.onclose=()=>{c?.("\uD83D\uDCAC",{event:"dc-close",userId:C}),G.delete(C),Z.forEach((R)=>R(C,"leave",[...G])),i.removeEventListener("message",f),i.onopen=null,i.onclose=null,i.onerror=null,i.close(),b?.()},i.onerror=()=>c?.("⚠️ ERROR",{error:"dc-error",userId:C})}let{userId:z,enterRoom:O,exitRoom:K,leaveUser:q,broadcast:h,end:B,reset:D}=g({userId:T,worldId:E,enterRoomFunction:H,logLine:c,workerUrl:V,peerlessUserExpiration:M,onRoomReady:X,onRoomClose:J,onLeaveUser(C){let i=W.get(C);try{i?.close()}catch{}W.delete(C)},receivePeerConnection({pc:C,userId:i,restart:b}){S(C,i,b)},onBroadcastMessage(C,i){Q(C,i),c?.("\uD83D\uDCE2",{event:"broadcast",userId:z,data:C})}});function Y(C,i){W.forEach((b,f)=>{if(i&&f!==i)return;if(b.readyState==="open")b.send(C)})}function A(C){$.delete(C)}function x(C){return $.add(C),()=>{A(C)}}function v(C){Z.delete(C)}function n(C){return Z.add(C),()=>{v(C)}}return{userId:z,send:Y,broadcast:h,enterRoom:O,exitRoom:K,leaveUser:q,getUsers:()=>[...G],addMessageListener:x,removeMessageListener:A,addUserListener:n,removeUserListener:v,reset(){G.forEach((C)=>{W.get(C)?.close(),W.delete(C),D(C)})},end(){W.forEach((C)=>{try{C.close()}catch{}}),W.clear(),B(),Z.clear(),G.clear()}}}export{u as enterWorld,k as enterRoom,g as collectPeerConnections};
2
2
 
3
- //# debugId=45D96A674130C1F164756E2164756E21
3
+ //# debugId=0AD885CEE565BC8564756E2164756E21
4
4
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1,13 +1,15 @@
1
1
  {
2
2
  "version": 3,
3
- "sources": ["../src/browser/signal/impl/signal-room.ts", "../src/browser/signal/signal-room.ts", "../src/browser/webrtc-peer-collector.ts", "../src/browser/enter-world.ts"],
3
+ "sources": ["../src/browser/utils/ice-url-provider.ts", "../src/browser/signal/impl/signal-room.ts", "../src/browser/signal/signal-room.ts", "../src/browser/utils/rtc-config.ts", "../src/browser/webrtc-peer-collector.ts", "../src/browser/enter-world.ts"],
4
4
  "sourcesContent": [
5
+ "export class IceUrlProvider {\n private sendToServerFunctions = new Array<(command: \"request-ice\") => void>();\n private icePromiseResolve?: (url: {\n url: string;\n expiration: number;\n }) => void;\n private icePromise?: Promise<{ url: string; expiration: number }>;\n\n receiveIce(url: string, expiration: number) {\n this.icePromiseResolve?.({ url, expiration });\n this.icePromiseResolve = undefined;\n this.icePromise = undefined;\n }\n\n addRequester(requester: (command: \"request-ice\") => void) {\n this.sendToServerFunctions.push(requester);\n return () => {\n this.removeRequester(requester);\n };\n }\n\n removeRequester(requester: (command: \"request-ice\") => void) {\n this.sendToServerFunctions.splice(\n this.sendToServerFunctions.indexOf(requester),\n 1,\n );\n }\n\n sendToServer(command: \"request-ice\") {\n this.sendToServerFunctions[\n Math.floor(this.sendToServerFunctions.length * Math.random())\n ](command);\n }\n\n async requestIce() {\n if (!this.icePromise) {\n this.icePromise = new Promise<{ url: string; expiration: number }>(\n (resolve) => {\n this.icePromiseResolve = resolve;\n this.sendToServer(\"request-ice\");\n },\n );\n }\n return await this.icePromise;\n }\n}\n",
5
6
  "export interface IPeer<T extends string = string, P = any> {\n userId: string;\n receive(type: T, payload: P): boolean;\n}\n\ntype OutMessage = { type: string; to: string; payload: any };\n\n/**\n * enterRoom connects to the signaling room via WebSocket.\n */\nexport function enterRoom<T extends string, P = any>(params: {\n userId: string;\n worldId: string;\n room: string;\n host: string;\n onOpen?: () => void;\n onClose?: (ev: Pick<CloseEvent, \"code\" | \"reason\" | \"wasClean\">) => void;\n onError?: () => void;\n logLine?: (direction: string, obj?: any) => void;\n onPeerJoined(users: IPeer<T, P>[]): void;\n onPeerLeft(users: { userId: string }[]): void;\n onIceUrl?(url: string, expiration: number): void;\n onMessage(type: T, payload: P, from: IPeer<T, P>): void;\n autoRejoin?: boolean;\n}): {\n exitRoom: () => void;\n sendToServer: <P extends any>(type: T, payload?: P) => void;\n} {\n type Message = {\n type: \"peer-joined\" | \"peer-left\" | \"ice-server\" | T;\n userId: string;\n users: { userId: string }[];\n payload: P;\n url: string;\n expiration: number;\n };\n\n const { userId, worldId, room, host, autoRejoin = true, logLine } = params;\n\n let exited = false;\n let retryCount = 0;\n let ws: WebSocket;\n let timeoutId: ReturnType<typeof setTimeout>;\n let initialConnection = true;\n\n const peers = new Map<string, IPeer<T, P>>();\n const wsUrl = `wss://${host}/room/${worldId}/${room}?userId=${encodeURIComponent(\n userId,\n )}`;\n\n // Helper for sending (uses the current ws instance)\n const accumulatedMessages: OutMessage[] = [];\n let timeout: ReturnType<typeof setTimeout> = 0;\n function send(type: string, to: \"server\" | string, payload?: any) {\n if (!ws) {\n logLine?.(\"👤 ➡️ ❌\", \"no ws available\");\n return false;\n }\n const obj: OutMessage = { type, to, payload };\n accumulatedMessages.push(obj);\n logLine?.(\"👤 ➡️ 🖥️\", obj);\n clearTimeout(timeout);\n if (exited || ws.readyState !== WebSocket.OPEN) {\n logLine?.(\"👤 ➡️ ❌\", \"Not in opened state: \" + ws.readyState);\n return false;\n }\n timeout = setTimeout(() => {\n ws.send(JSON.stringify(accumulatedMessages));\n accumulatedMessages.length = 0;\n });\n return true;\n }\n\n function connect() {\n if (exited) return;\n\n ws = new WebSocket(wsUrl);\n\n ws.onopen = () => {\n if (initialConnection) {\n params.onOpen?.();\n initialConnection = false;\n }\n retryCount = 0; // Reset backoff on successful connection\n };\n\n ws.onmessage = (e: MessageEvent) => {\n try {\n const result = JSON.parse(e.data);\n const msgs: Message[] = Array.isArray(result) ? result : [result];\n msgs.forEach((msg) => {\n logLine?.(\"🖥️ ➡️ 👤\", msg);\n if (msg.type === \"peer-joined\" || msg.type === \"peer-left\") {\n updatePeers(msg.users, msg);\n } else if (msg.type === \"ice-server\") {\n params.onIceUrl?.(msg.url, msg.expiration);\n } else if (msg.userId) {\n params.onMessage(msg.type, msg.payload, {\n userId: msg.userId,\n receive: (type: T, payload: P) => send(type, msg.userId, payload),\n });\n }\n });\n } catch {\n logLine?.(\"⚠️ ERROR\", { error: \"invalid-json\" });\n }\n };\n\n ws.onclose = (ev: CloseEvent) => {\n // 1. Check if we should even try to reconnect\n const recoverableCodes = [1001, 1006, 1011, 1012, 1013];\n const isRecoverable = recoverableCodes.includes(ev.code);\n\n if (autoRejoin && !exited && isRecoverable) {\n // 2. Exponential Backoff: 1s, 2s, 4s, 8s... capped at 30s\n const backoff = Math.min(Math.pow(2, retryCount) * 1000, 30000);\n // 3. Add Jitter: +/- 1000ms randomness\n const jitter = Math.random() * 1000;\n const delay = backoff + jitter;\n\n logLine?.(\"🔄 RECONNECTING\", {\n attempt: retryCount + 1,\n delayMs: Math.round(delay),\n });\n\n retryCount++;\n timeoutId = setTimeout(connect, delay);\n } else {\n params.onClose?.({\n code: ev.code,\n reason: ev.reason,\n wasClean: ev.wasClean,\n });\n }\n };\n\n ws.onerror = (ev) => {\n console.error(\"WS Error\", ev);\n params.onError?.();\n };\n }\n\n // Helper for peer tracking (logic from your original code)\n function updatePeers(updatedUsers: { userId: string }[], msg: Message) {\n const joined: IPeer<T, P>[] = [];\n const left: { userId: string }[] = [];\n const updatedPeerSet = new Set<string>();\n\n updatedUsers.forEach(({ userId: pUserId }) => {\n if (pUserId === userId) return;\n if (\n !peers.has(pUserId) ||\n (msg.type === \"peer-joined\" && pUserId === msg.userId)\n ) {\n const newPeer = {\n userId: pUserId,\n receive: (t: T, p: P) => send(t, pUserId, p),\n };\n peers.set(pUserId, newPeer);\n joined.push(newPeer);\n }\n updatedPeerSet.add(pUserId);\n });\n\n for (const pUserId of peers.keys()) {\n if (\n !updatedPeerSet.has(pUserId) ||\n (msg.type === \"peer-left\" && pUserId === msg.userId)\n ) {\n peers.delete(pUserId);\n left.push({ userId: pUserId });\n }\n }\n\n // Notify peer joined first then peer left. (avoid disconnect in case the peer leaving / joining is on the same user).\n if (joined.length) params.onPeerJoined(joined);\n if (left.length) params.onPeerLeft(left);\n }\n\n // Start initial connection\n connect();\n\n return {\n sendToServer(type, payload) {\n send(type, \"server\", payload);\n },\n exitRoom: () => {\n exited = true;\n clearTimeout(timeoutId);\n ws.close();\n },\n };\n}\n",
6
7
  "import type { IPeer } from \"./impl/signal-room.js\";\nimport { enterRoom as baseEnterRoom } from \"./impl/signal-room.js\";\nimport { RoomEvent, WorkerCommand } from \"./signal-room.worker.js\";\n\nexport function enterRoom<T extends string, P = any>({\n userId,\n worldId,\n room,\n host,\n autoRejoin = true,\n onOpen,\n onClose,\n onError,\n onPeerJoined,\n onPeerLeft,\n onIceUrl,\n onMessage,\n logLine,\n workerUrl,\n}: {\n userId: string;\n worldId: string;\n room: string;\n host: string;\n autoRejoin?: boolean;\n onOpen?: () => void;\n onClose?: (ev: Pick<CloseEvent, \"code\" | \"reason\" | \"wasClean\">) => void;\n onError?: () => void;\n onPeerJoined: (users: IPeer<T, P>[]) => void;\n onPeerLeft: (users: { userId: string }[]) => void;\n onIceUrl?(url: string, expiration: number): void;\n onMessage: (type: T, payload: P, from: IPeer<T, P>) => void;\n logLine?: (direction: string, obj?: any) => void;\n\n // Pass the URL to your worker file (bundler will handle it)\n workerUrl?: URL;\n}): {\n exitRoom: () => void;\n sendToServer: <P extends any>(type: T, payload?: P) => void;\n} {\n if (!workerUrl) {\n const CDN_WORKER_URL = `https://cdn.jsdelivr.net/npm/@dobuki/hello-worker/dist/signal-room.worker.min.js`;\n\n console.warn(\n \"Warning: enterRoom called without workerUrl; this may cause issues in some environments. You should pass workerUrl explicitly. Use:\",\n CDN_WORKER_URL,\n );\n return baseEnterRoom<T, P>({\n userId,\n worldId,\n room,\n host,\n autoRejoin,\n onOpen,\n onClose,\n onError,\n onPeerJoined,\n onPeerLeft,\n onIceUrl,\n onMessage,\n });\n }\n const worker = new Worker(workerUrl, { type: \"module\" });\n let exited = false;\n\n function makeUser({ userId }: { userId: string }): IPeer<T, P> {\n return {\n userId,\n receive: (type: T, payload: P) => {\n if (exited) return false;\n worker.postMessage({\n cmd: \"send\",\n toUserId: userId,\n host,\n room,\n type,\n payload,\n } as WorkerCommand);\n return true;\n },\n };\n }\n\n const onWorkerMessage = (e: MessageEvent<RoomEvent<T, P>>) => {\n const ev = e.data;\n\n if (ev.kind === \"open\") onOpen?.();\n else if (ev.kind === \"close\") {\n worker.terminate();\n onClose?.(ev.ev);\n } else if (ev.kind === \"error\") onError?.();\n else if (ev.kind === \"peer-joined\")\n onPeerJoined(ev.users.map((ev) => makeUser({ userId: ev.userId })));\n else if (ev.kind === \"peer-left\") onPeerLeft(ev.users);\n else if (ev.kind === \"ice-server\") onIceUrl?.(ev.url, ev.expiration);\n else if (ev.kind === \"message\")\n onMessage(ev.type, ev.payload, makeUser({ userId: ev.fromUserId }));\n else if (ev.kind === \"log\") logLine?.(ev.direction, ev.obj);\n };\n\n worker.addEventListener(\"message\", onWorkerMessage);\n\n worker.postMessage({\n cmd: \"enter\",\n userId,\n worldId,\n room,\n host,\n autoRejoin,\n } as WorkerCommand);\n\n return {\n exitRoom: () => {\n exited = true;\n worker.removeEventListener(\"message\", onWorkerMessage);\n worker.postMessage({ cmd: \"exit\" } as WorkerCommand);\n },\n sendToServer: <P extends any>(type: T, payload?: P) => {\n worker.postMessage({\n cmd: \"send\",\n toUserId: \"server\",\n host,\n room,\n type,\n payload,\n } as WorkerCommand);\n },\n };\n}\n\nexport type EnterRoom<T extends string, P> = typeof enterRoom<T, P>;\n",
7
- "import { IPeer } from \"./signal/impl/signal-room\";\nimport { EnterRoom, enterRoom } from \"./signal/signal-room\";\n\nexport type SigType = \"offer\" | \"answer\" | \"ice\" | \"request-ice\" | \"broadcast\";\nexport type SigPayload = RTCSessionDescriptionInit | RTCIceCandidateInit;\n\ntype UserState = {\n pc?: RTCPeerConnection;\n\n // ICE that arrived before we had remoteDescription\n pendingRemoteIce: RTCIceCandidateInit[];\n\n // the signaling \"user\" handle so we can send messages\n peer: IPeer<SigType, SigPayload>;\n\n expirationTimeout?: number;\n close(): void;\n reset(): void;\n};\n\nconst DEFAULT_ENTER_ROOM = enterRoom;\n\nexport function collectPeerConnections({\n userId: passedUserId,\n worldId,\n receivePeerConnection,\n peerlessUserExpiration = 5000,\n fallbackRtcConfig = {\n iceServers: [{ urls: \"stun:stun.l.google.com:19302\" }],\n },\n enterRoomFunction: enterRoom = DEFAULT_ENTER_ROOM,\n logLine,\n onLeaveUser,\n workerUrl,\n onRoomReady,\n onRoomClose,\n onBroadcastMessage,\n}: {\n userId?: string;\n worldId: string;\n fallbackRtcConfig?: RTCConfiguration;\n enterRoomFunction?: EnterRoom<SigType, SigPayload>;\n onLeaveUser?: (userId: string) => void;\n logLine?: (direction: string, obj?: any) => void;\n workerUrl?: URL;\n peerlessUserExpiration?: number;\n receivePeerConnection(connection: {\n pc: RTCPeerConnection;\n userId: string;\n restart?: () => void;\n }): void;\n onRoomReady?(info: { host: string; room: string }): void;\n onRoomClose?(info: {\n host: string;\n room: string;\n ev: Pick<CloseEvent, \"reason\" | \"code\" | \"wasClean\">;\n }): void;\n onBroadcastMessage?<P extends any>(payload: P, from: string): void;\n}) {\n const userId = passedUserId ?? `user-${crypto.randomUUID()}`;\n const users: Map<string, UserState> = new Map();\n let iceUrl: { url: string; expiration: number } | undefined = undefined;\n let rtcConfig: RTCConfiguration & { timestamp: number } = {\n ...fallbackRtcConfig,\n timestamp: Date.now(),\n };\n\n const roomsEntered = new Map<\n string,\n {\n room: string;\n host: string;\n exitRoom: () => void;\n broadcast: <P extends any>(payload: P) => void;\n }\n >();\n\n async function getRtcConfig(\n iceUrl: string,\n retryIce: () => Promise<{ url: string }>,\n ): Promise<RTCConfiguration & { timestamp: number }> {\n if (iceUrl) {\n let retries = 3;\n for (let r = 0; r < retries; r++) {\n try {\n const r = await fetch(iceUrl);\n if (!r.ok) throw new Error(`ICE endpoint failed: ${r.status}`);\n rtcConfig = (await r.json()) as RTCConfiguration & {\n timestamp: number;\n };\n return rtcConfig;\n } catch (e) {\n console.warn(\"Failed fetching iceUrl\");\n }\n iceUrl = (await retryIce()).url;\n }\n }\n return rtcConfig;\n }\n\n function leaveUser(userId: string) {\n onLeaveUser?.(userId);\n const p = users.get(userId);\n if (!p) return;\n users.delete(userId);\n try {\n p.close();\n } catch {}\n }\n\n async function flushRemoteIce(state: UserState) {\n if (!state.pc?.remoteDescription) return;\n\n const queued = state.pendingRemoteIce;\n state.pendingRemoteIce = [];\n\n for (const ice of queued) {\n try {\n await state.pc.addIceCandidate(ice);\n } catch (e) {\n logLine?.(\"⚠️ ERROR\", {\n error: \"add-ice-failed\",\n userId: state.peer.userId,\n detail: String(e),\n });\n }\n }\n }\n\n function exit({ room, host }: { room: string; host: string }) {\n const key = `${host}/room/${room}`;\n const session = roomsEntered.get(key);\n if (session) {\n session.exitRoom();\n roomsEntered.delete(key);\n }\n }\n\n function enter({ room, host }: { room: string; host: string }) {\n return new Promise<void>(async (resolve, reject) => {\n async function setupPC(state: UserState) {\n const now = Date.now();\n if (now - (rtcConfig?.timestamp ?? 0) > 10000) {\n const ice =\n !iceUrl || iceUrl.expiration - now < 2000\n ? await requestIce()\n : iceUrl;\n rtcConfig = await getRtcConfig(ice.url, requestIce);\n }\n state.pc = new RTCPeerConnection(rtcConfig);\n // Send local ICE candidates to this peer\n state.pc.onicecandidate = (ev) => {\n if (!ev.candidate) return;\n state.peer.receive(\"ice\", ev.candidate.toJSON());\n };\n\n state.pc.onconnectionstatechange = async () => {\n logLine?.(\"💬\", {\n event: \"pc-state\",\n userId: state.peer.userId,\n state: state.pc?.connectionState,\n });\n if (state.pc?.connectionState === \"failed\") {\n // reset the connection\n state.close();\n const userState = await getPeer(state.peer, true);\n if (userState.pc) {\n receivePeerConnection({\n pc: userState.pc,\n userId: userState.peer.userId,\n restart: () => userState.close(),\n });\n } else {\n logLine?.(\"👤ℹ️\", \"no pc: \" + userState.peer.userId);\n }\n return;\n }\n };\n\n return state.pc;\n }\n\n async function getPeer(\n peer: IPeer<SigType, SigPayload>,\n forceReset?: boolean,\n ): Promise<UserState> {\n let state = users.get(peer.userId);\n if (!state || forceReset) {\n const newState: UserState = {\n pendingRemoteIce: [],\n peer,\n close() {\n this.pc?.close();\n this.pc = undefined;\n users.delete(peer.userId);\n },\n async reset() {\n newState.close();\n const userState = await getPeer(peer, true);\n console.log(\">>\", userState.peer.userId);\n\n setTimeout(async () => {\n if (!userState.pc) {\n console.log(\"no pc\");\n return;\n }\n receivePeerConnection({\n pc: userState.pc,\n userId: userState.peer.userId,\n restart: () => userState.close(),\n });\n await makeOffer(userState.peer);\n }, 3000);\n },\n };\n\n await setupPC(newState);\n state = newState;\n\n // New user\n users.set(state.peer.userId, state);\n } else if (state) {\n clearTimeout(state.expirationTimeout);\n state.expirationTimeout = 0;\n if (!state.pc || state.pc.signalingState === \"closed\") {\n await setupPC(state);\n }\n }\n state.peer = peer;\n return state;\n }\n\n async function makeOffer(user: IPeer) {\n // Offer flow: createOffer -> setLocalDescription -> send localDescription\n const state = await getPeer(user);\n const pc = state.pc;\n const offer = await pc?.createOffer();\n await pc?.setLocalDescription(offer);\n user.receive(\"offer\", pc?.localDescription?.toJSON()!);\n }\n\n let icePromiseResolve:\n | undefined\n | ((url: { url: string; expiration: number }) => void);\n async function requestIce() {\n const iceUrl = await new Promise<{ url: string; expiration: number }>(\n (resolve) => {\n icePromiseResolve = resolve;\n sendToServer(\"request-ice\");\n },\n );\n icePromiseResolve = undefined;\n return iceUrl;\n }\n\n const { exitRoom, sendToServer } = enterRoom({\n userId,\n worldId,\n room,\n host,\n logLine,\n workerUrl,\n autoRejoin: true,\n\n onOpen() {\n onRoomReady?.({ room, host });\n resolve();\n },\n onError() {\n console.error(\"onError\");\n reject();\n },\n onClose(ev: Pick<CloseEvent, \"reason\" | \"code\" | \"wasClean\">) {\n onRoomClose?.({ room, host, ev });\n },\n\n // Existing peers initiate to the newcomer\n onPeerJoined(joiningUsers: IPeer<SigType, SigPayload>[]) {\n joiningUsers.forEach(async (user) => {\n const state = await getPeer(user, true);\n const pc = state.pc;\n if (!pc) {\n logLine?.(\"👤ℹ️\", \"no pc: \" + user.userId);\n return;\n }\n\n receivePeerConnection({\n pc,\n userId: user.userId,\n restart: () => state.close(),\n });\n await makeOffer(user);\n });\n },\n\n onPeerLeft(leavingUsers: { userId: string }[]) {\n leavingUsers.forEach(({ userId }) => {\n const state = users.get(userId);\n if (!state) return;\n state.expirationTimeout = setTimeout(\n () => leaveUser(userId),\n peerlessUserExpiration ?? 0,\n );\n });\n },\n\n onIceUrl(url: string, expiration: number) {\n iceUrl = { url, expiration };\n icePromiseResolve?.(iceUrl);\n },\n\n async onMessage(type: SigType, payload: any, from: IPeer) {\n const state = await getPeer(from);\n\n if (type === \"offer\") {\n state.close(); // need new PC\n const pc = await setupPC(state);\n\n receivePeerConnection({\n pc,\n userId: from.userId,\n restart: () => state.close(),\n });\n // Responder: set remote offer\n await pc.setRemoteDescription(payload as RTCSessionDescriptionInit);\n\n // Create and send answer\n const answer = await pc.createAnswer();\n await pc.setLocalDescription(answer);\n\n from.receive(\"answer\", pc.localDescription?.toJSON()!);\n\n // Now safe to apply any queued ICE from this peer\n await flushRemoteIce(state);\n return;\n }\n\n const pc = state.pc ?? (await setupPC(state));\n\n if (type === \"answer\") {\n // Initiator: set remote answer\n await pc.setRemoteDescription(payload as RTCSessionDescriptionInit);\n await flushRemoteIce(state);\n return;\n }\n\n if (type === \"ice\") {\n const ice = payload as RTCIceCandidateInit;\n\n // If we don't have remoteDescription yet, queue it\n if (!pc.remoteDescription) {\n state.pendingRemoteIce.push(ice);\n return;\n }\n\n try {\n await state.pc?.addIceCandidate(ice);\n } catch (e) {\n logLine?.(\"⚠️ ERROR\", {\n error: \"add-ice-failed\",\n userId: state.peer.userId,\n detail: String(e),\n });\n }\n return;\n }\n\n if (type === \"broadcast\") {\n onBroadcastMessage?.(payload, from.userId);\n }\n },\n });\n roomsEntered.set(`${host}/room/${room}`, {\n exitRoom,\n room,\n host,\n broadcast: (payload) => sendToServer(\"broadcast\", payload),\n });\n });\n }\n\n return {\n userId,\n enterRoom: enter,\n exitRoom: exit,\n leaveUser,\n async reset(userId: string) {\n const userState = users.get(userId);\n userState?.reset();\n },\n broadcast<P extends any>(payload: P) {\n roomsEntered.forEach((room) => room.broadcast(payload));\n },\n end() {\n roomsEntered.forEach(({ exitRoom }) => exitRoom());\n roomsEntered.clear();\n users.forEach(({ peer }) => leaveUser(peer.userId));\n users.clear();\n },\n };\n}\n\n/*\nTurn Token ID\n<CF_TURN_TOKEN_ID>\n\nAPI Token\n<CF_RTC_API_TOKEN>\n\nCURL\ncurl \\\n\t-H \"Authorization: Bearer <CF_RTC_API_TOKEN>\" \\\n\t-H \"Content-Type: application/json\" -d '{\"ttl\": 86400}' \\\n\thttps://rtc.live.cloudflare.com/v1/turn/keys/<CF_TURN_TOKEN_ID>/credentials/generate-ice-servers\n\nJSON\n{\n\t\"iceServers\": [\n {\n \"urls\": [\n \"stun:stun.cloudflare.com:3478\",\n \"turn:turn.cloudflare.com:3478?transport=udp\",\n \"turn:turn.cloudflare.com:3478?transport=tcp\",\n \"turns:turn.cloudflare.com:5349?transport=tcp\"\n ],\n \"username\": \"xxxx\",\n \"credential\": \"yyyy\",\n }\n ]\n}\n\n*/\n",
8
+ "import { IceUrlProvider } from \"./ice-url-provider\";\n\nconst FALLBACK_RTC_CONFIG = {\n iceServers: [{ urls: \"stun:stun.l.google.com:19302\" }],\n};\n\nexport class RTCConfigProvider {\n constructor(private iceUrlProvider: IceUrlProvider) {}\n\n private rtcConfig: RTCConfiguration & { timestamp: number } = {\n ...FALLBACK_RTC_CONFIG,\n timestamp: Date.now(),\n };\n private rtcConfigPromise?: Promise<RTCConfiguration & { timestamp: number }>;\n\n async getRtcConfig(): Promise<RTCConfiguration & { timestamp: number }> {\n const now = Date.now();\n if (now - (this.rtcConfig?.timestamp ?? 0) < 10000) {\n return this.rtcConfig;\n }\n\n if (!this.rtcConfigPromise) {\n this.rtcConfigPromise = new Promise<\n RTCConfiguration & { timestamp: number }\n >(async (resolve) => {\n let retries = 3;\n for (let r = 0; r < retries; r++) {\n try {\n const iceUrl = (await this.iceUrlProvider.requestIce()).url;\n const r = await fetch(iceUrl);\n if (!r.ok) throw new Error(`ICE endpoint failed: ${r.status}`);\n const rtcConfig = (await r.json()) as RTCConfiguration & {\n timestamp: number;\n };\n resolve(rtcConfig);\n return;\n } catch (e) {\n console.warn(\"Failed fetching iceUrl\");\n }\n }\n });\n this.rtcConfig = await this.rtcConfigPromise;\n this.rtcConfigPromise = undefined;\n }\n return this.rtcConfig;\n }\n}\n",
9
+ "import { IceUrlProvider } from \"./utils/ice-url-provider\";\nimport { IPeer } from \"./signal/impl/signal-room\";\nimport { EnterRoom, enterRoom } from \"./signal/signal-room\";\nimport { RTCConfigProvider } from \"./utils/rtc-config\";\n\nexport type SigType = \"offer\" | \"answer\" | \"ice\" | \"request-ice\" | \"broadcast\";\nexport type SigPayload = {\n connectionId?: string;\n offer?: RTCSessionDescriptionInit;\n answer?: RTCSessionDescriptionInit;\n ice?: RTCIceCandidateInit;\n} & Record<string, any>;\n\ninterface Connection {\n id: string;\n peerConnectionId?: string;\n pc: RTCPeerConnection;\n // ICE that arrived before we had remoteDescription\n pendingRemoteIce: RTCIceCandidateInit[];\n}\n\ntype UserState = {\n connection?: Connection;\n\n // the signaling \"user\" handle so we can send messages\n peer: IPeer<SigType, SigPayload>;\n\n expirationTimeout?: number;\n close(): void;\n reset(): void;\n connectionPromise?: Promise<Connection>;\n};\n\nconst DEFAULT_ENTER_ROOM = enterRoom;\n\n/**\n * Collect peers\n */\nexport function collectPeerConnections({\n userId: passedUserId,\n worldId,\n receivePeerConnection,\n peerlessUserExpiration = 5000,\n enterRoomFunction: enterRoom = DEFAULT_ENTER_ROOM,\n logLine,\n onLeaveUser,\n workerUrl,\n onRoomReady,\n onRoomClose,\n onBroadcastMessage,\n}: {\n userId?: string;\n worldId: string;\n enterRoomFunction?: EnterRoom<SigType, SigPayload>;\n onLeaveUser?: (userId: string) => void;\n logLine?: (direction: string, obj?: any) => void;\n workerUrl?: URL;\n peerlessUserExpiration?: number;\n receivePeerConnection(connection: {\n pc: RTCPeerConnection;\n userId: string;\n restart?: () => void;\n }): void;\n onRoomReady?(info: { host: string; room: string }): void;\n onRoomClose?(info: {\n host: string;\n room: string;\n ev: Pick<CloseEvent, \"reason\" | \"code\" | \"wasClean\">;\n }): void;\n onBroadcastMessage?<P extends any>(payload: P, from: string): void;\n}) {\n const userId = passedUserId ?? `user-${crypto.randomUUID()}`;\n const users: Map<string, UserState> = new Map();\n\n const roomsEntered = new Map<\n string,\n {\n room: string;\n host: string;\n exitRoom: () => void;\n broadcast: <P extends any>(payload: P) => void;\n }\n >();\n\n function leaveUser(userId: string) {\n onLeaveUser?.(userId);\n const p = users.get(userId);\n if (!p) return;\n users.delete(userId);\n try {\n p.close();\n } catch {}\n }\n\n async function flushRemoteIce(state: UserState) {\n if (!state.connection?.pc?.remoteDescription) return;\n\n const queued = state.connection.pendingRemoteIce;\n state.connection.pendingRemoteIce = [];\n\n for (const ice of queued) {\n try {\n await state.connection.pc.addIceCandidate(ice);\n } catch (e) {\n logLine?.(\"⚠️ ERROR\", {\n error: \"add-ice-failed\",\n userId: state.peer.userId,\n detail: String(e),\n });\n }\n }\n }\n\n const iceUrlProvider = new IceUrlProvider();\n const rtcConfigProvider = new RTCConfigProvider(iceUrlProvider);\n\n function exit({ room, host }: { room: string; host: string }) {\n const key = `${host}/room/${room}`;\n const session = roomsEntered.get(key);\n if (session) {\n session.exitRoom();\n roomsEntered.delete(key);\n }\n }\n\n function enter({ room, host }: { room: string; host: string }) {\n return new Promise<void>(async (resolve, reject) => {\n async function setupConnection(state: UserState) {\n if (state.connectionPromise) {\n return state.connectionPromise;\n }\n return (state.connectionPromise = new Promise<Connection>(\n async (resolve) => {\n state.connection = {\n id: `conn-${crypto.randomUUID()}`,\n pc: new RTCPeerConnection(await rtcConfigProvider.getRtcConfig()),\n pendingRemoteIce: [],\n };\n\n // Send local ICE candidates to this peer\n state.connection.pc.onicecandidate = (ev) => {\n if (!ev.candidate) return;\n state.peer.receive(\"ice\", {\n connectionId: state.connection?.id,\n ice: ev.candidate.toJSON(),\n });\n };\n\n state.connection.pc.onconnectionstatechange = async () => {\n logLine?.(\"💬\", {\n event: \"pc-state\",\n userId: state.peer.userId,\n state: state.connection?.pc?.connectionState,\n });\n if (state.connection?.pc?.connectionState === \"failed\") {\n // reset the connection\n state.close();\n const userState = await getPeer(state.peer, true);\n if (userState.connection?.pc) {\n receivePeerConnection({\n pc: userState.connection?.pc,\n userId: userState.peer.userId,\n restart: () => userState.close(),\n });\n } else {\n logLine?.(\"👤ℹ️\", \"no pc: \" + userState.peer.userId);\n }\n return;\n }\n };\n\n resolve(state.connection);\n },\n ));\n }\n\n async function getPeer(\n peer: IPeer<SigType, SigPayload>,\n forceReset?: boolean,\n ): Promise<UserState> {\n let state = users.get(peer.userId);\n if (!state || forceReset) {\n const newState: UserState = {\n peer,\n close() {\n if (this.connection) {\n this.connection.pc.close();\n this.connection = undefined;\n }\n users.delete(peer.userId);\n },\n async reset() {\n newState.close();\n\n setTimeout(async () => {\n const userState = await getPeer(peer, true);\n if (!userState.connection?.pc) {\n logLine?.(\"⚠️\", \"no pc\");\n return;\n }\n receivePeerConnection({\n pc: userState.connection?.pc,\n userId: userState.peer.userId,\n restart: () => userState.close(),\n });\n await makeOffer(userState.peer);\n }, 500);\n },\n };\n state = newState;\n\n await setupConnection(newState);\n\n // New user\n users.set(state.peer.userId, state);\n } else if (state) {\n clearTimeout(state.expirationTimeout);\n state.expirationTimeout = 0;\n if (\n !state.connection?.pc ||\n state.connection?.pc.signalingState === \"closed\"\n ) {\n await setupConnection(state);\n }\n }\n state.peer = peer;\n return state;\n }\n\n async function makeOffer(user: IPeer<SigType, SigPayload>) {\n // Offer flow: createOffer -> setLocalDescription -> send localDescription\n const state = await getPeer(user);\n const pc = state.connection?.pc;\n const offer = await pc?.createOffer();\n await pc?.setLocalDescription(offer);\n user.receive(\"offer\", {\n connectionId: state.connection?.id,\n offer: pc!.localDescription!.toJSON(),\n });\n }\n\n const { exitRoom, sendToServer } = enterRoom({\n userId,\n worldId,\n room,\n host,\n logLine,\n workerUrl,\n autoRejoin: true,\n\n onOpen() {\n onRoomReady?.({ room, host });\n resolve();\n },\n onError() {\n console.error(\"onError\");\n reject();\n },\n onClose(ev: Pick<CloseEvent, \"reason\" | \"code\" | \"wasClean\">) {\n onRoomClose?.({ room, host, ev });\n },\n\n // Existing peers initiate to the newcomer\n onPeerJoined(joiningUsers: IPeer<SigType, SigPayload>[]) {\n joiningUsers.forEach(async (user) => {\n const state = await getPeer(user, true);\n const pc = state.connection?.pc;\n if (!pc) {\n logLine?.(\"👤ℹ️\", \"no pc: \" + user.userId);\n return;\n }\n\n receivePeerConnection({\n pc,\n userId: user.userId,\n restart: () => state.close(),\n });\n await makeOffer(user);\n });\n },\n\n onPeerLeft(leavingUsers: { userId: string }[]) {\n leavingUsers.forEach(({ userId }) => {\n const state = users.get(userId);\n if (!state) return;\n state.expirationTimeout = setTimeout(\n () => leaveUser(userId),\n peerlessUserExpiration ?? 0,\n );\n });\n },\n\n onIceUrl(url: string, expiration: number) {\n iceUrlProvider.receiveIce(url, expiration);\n },\n\n async onMessage(type, payload, from: IPeer<SigType, SigPayload>) {\n if (type === \"offer\" && payload.offer) {\n // Grab state and connection\n const state = await getPeer(from, false);\n const connection =\n !state.connection ||\n state.connection.pc.signalingState === \"stable\"\n ? await setupConnection(state)\n : state.connection; // reset\n logLine?.(\"💬\", {\n type,\n signalingState: connection.pc.signalingState,\n });\n\n connection.peerConnectionId = payload.connectionId;\n receivePeerConnection({\n pc: connection.pc,\n userId: from.userId,\n restart: () => state.close(),\n });\n // Responder: set remote offer\n await connection.pc.setRemoteDescription(payload.offer);\n\n // Create and send answer\n const answer = await connection.pc.createAnswer();\n await connection.pc.setLocalDescription(answer);\n\n from.receive(\"answer\", {\n connectionId: connection.id,\n answer: connection.pc.localDescription?.toJSON(),\n });\n\n // Now safe to apply any queued ICE from this peer\n await flushRemoteIce(state);\n return;\n }\n\n if (type === \"answer\" && payload.answer) {\n const state = await getPeer(from, false);\n const connection =\n state.connection &&\n state.connection.pc.signalingState !== \"closed\"\n ? state.connection\n : await setupConnection(state);\n logLine?.(\"💬\", {\n type,\n signalingState: connection.pc.signalingState,\n });\n\n // Initiator: set remote answer\n await connection.pc.setRemoteDescription(payload.answer);\n connection.peerConnectionId = payload.connectionId;\n await flushRemoteIce(state);\n return;\n }\n\n if (type === \"ice\" && payload.ice) {\n // Grab state and connection\n const state = await getPeer(from, false);\n const connection =\n state.connection ?? (await state.connectionPromise);\n if (!connection) {\n logLine?.(\"⚠️\", \"No connection\");\n return;\n }\n logLine?.(\"💬\", {\n type,\n signalingState: connection.pc.signalingState,\n });\n\n if (\n connection.peerConnectionId &&\n payload.connectionId !== connection.peerConnectionId\n ) {\n logLine?.(\n \"⚠️\",\n \"Mismatch peerConnectionID\" +\n payload.connectionId +\n \"vs\" +\n connection.peerConnectionId,\n );\n return;\n }\n\n // If we don't have remoteDescription yet (or if connectionId doesn't match), queue it\n if (\n !connection.pc.remoteDescription ||\n !connection.peerConnectionId\n ) {\n connection.peerConnectionId = payload.connectionId;\n connection.pendingRemoteIce.push(payload.ice);\n return;\n }\n\n try {\n await connection.pc.addIceCandidate(payload.ice);\n } catch (e) {\n logLine?.(\"⚠️ ERROR\", {\n error: \"add-ice-failed\",\n userId: state.peer.userId,\n detail: String(e),\n });\n }\n return;\n }\n\n if (type === \"broadcast\") {\n onBroadcastMessage?.(payload, from.userId);\n }\n },\n });\n\n iceUrlProvider.addRequester(sendToServer);\n\n roomsEntered.set(`${host}/room/${room}`, {\n exitRoom: () => {\n exitRoom();\n iceUrlProvider.removeRequester(sendToServer);\n },\n room,\n host,\n broadcast: (payload) => sendToServer(\"broadcast\", payload),\n });\n });\n }\n\n return {\n userId,\n enterRoom: enter,\n exitRoom: exit,\n leaveUser,\n async reset(userId: string) {\n const userState = users.get(userId);\n userState?.reset();\n },\n broadcast<P extends any>(payload: P) {\n roomsEntered.forEach((room) => room.broadcast(payload));\n },\n end() {\n roomsEntered.forEach(({ exitRoom }) => exitRoom());\n roomsEntered.clear();\n users.forEach(({ peer }) => leaveUser(peer.userId));\n users.clear();\n },\n };\n}\n\n/*\nTurn Token ID\n<CF_TURN_TOKEN_ID>\n\nAPI Token\n<CF_RTC_API_TOKEN>\n\nCURL\ncurl \\\n\t-H \"Authorization: Bearer <CF_RTC_API_TOKEN>\" \\\n\t-H \"Content-Type: application/json\" -d '{\"ttl\": 86400}' \\\n\thttps://rtc.live.cloudflare.com/v1/turn/keys/<CF_TURN_TOKEN_ID>/credentials/generate-ice-servers\n\nJSON\n{\n\t\"iceServers\": [\n {\n \"urls\": [\n \"stun:stun.cloudflare.com:3478\",\n \"turn:turn.cloudflare.com:3478?transport=udp\",\n \"turn:turn.cloudflare.com:3478?transport=tcp\",\n \"turns:turn.cloudflare.com:5349?transport=tcp\"\n ],\n \"username\": \"xxxx\",\n \"credential\": \"yyyy\",\n }\n ]\n}\n\n*/\n",
8
10
  "import { EnterRoom, enterRoom } from \"./signal/signal-room\";\nimport {\n SigType,\n SigPayload,\n collectPeerConnections,\n} from \"./webrtc-peer-collector\";\n\ntype UserListener = (\n user: string,\n action: \"join\" | \"leave\",\n users: string[],\n) => void;\n\nexport function enterWorld<\n S extends string | ArrayBufferView = string | ArrayBufferView,\n R extends string | ArrayBufferLike = string | ArrayBufferLike,\n>({\n userId: passedUserId,\n worldId,\n logLine,\n enterRoomFunction = enterRoom,\n peerlessUserExpiration,\n workerUrl,\n onRoomReady,\n onRoomClose,\n dataChannelOptions,\n}: {\n userId?: string;\n worldId: string;\n logLine?: (...obj: any[]) => void;\n enterRoomFunction?: EnterRoom<SigType, SigPayload>;\n peerlessUserExpiration?: number;\n workerUrl?: URL;\n onRoomReady?(info: { host: string; room: string }): void;\n onRoomClose?(info: {\n host: string;\n room: string;\n ev: Pick<CloseEvent, \"reason\" | \"code\" | \"wasClean\">;\n }): void;\n dataChannelOptions?: RTCDataChannelInit;\n}) {\n const userIds = new Set<string>();\n const dataChannels = new Map<string, RTCDataChannel>();\n const userListeners = new Set<UserListener>();\n const messagesListeners = new Set<(data: R, from: string) => void>();\n\n function createDataChannel(\n pc: RTCPeerConnection,\n peerUserId: string,\n restart?: () => void,\n ) {\n function listener(ev: RTCDataChannelEvent) {\n const dc = ev.channel;\n wireDataChannel(peerUserId, dc, restart);\n dataChannels.set(peerUserId, dc);\n }\n pc.addEventListener(\"datachannel\", listener);\n const dc = pc.createDataChannel(\"data\", dataChannelOptions);\n wireDataChannel(peerUserId, dc, restart);\n dataChannels.set(peerUserId, dc);\n return () => {\n pc.removeEventListener(\"datachannel\", listener);\n };\n }\n\n function conveyMessage(data: any, userId: string) {\n messagesListeners.forEach((listener) => listener(data, userId));\n }\n\n function wireDataChannel(\n userId: string,\n dc: RTCDataChannel,\n restart?: () => void,\n ) {\n dc.onopen = () => {\n logLine?.(\"💬\", { event: \"dc-open\", userId });\n userIds.add(userId);\n userListeners.forEach((listener) =>\n listener(userId, \"join\", [...userIds]),\n );\n };\n const onmessage = ({ data }: MessageEvent) => {\n conveyMessage(data, userId);\n };\n dc.addEventListener(\"message\", onmessage);\n dc.onclose = () => {\n logLine?.(\"💬\", { event: \"dc-close\", userId });\n userIds.delete(userId);\n userListeners.forEach((listener) =>\n listener(userId, \"leave\", [...userIds]),\n );\n dc.removeEventListener(\"message\", onmessage);\n dc.onopen = null;\n dc.onclose = null;\n dc.onerror = null;\n dc.close();\n restart?.();\n };\n dc.onerror = () => logLine?.(\"⚠️ ERROR\", { error: \"dc-error\", userId });\n }\n\n const {\n userId,\n enterRoom,\n exitRoom,\n leaveUser,\n broadcast,\n end: endPeerCollection,\n reset: resetPeerCollection,\n } = collectPeerConnections({\n userId: passedUserId,\n worldId,\n enterRoomFunction,\n logLine,\n workerUrl,\n peerlessUserExpiration,\n onRoomReady,\n onRoomClose,\n onLeaveUser(userId: string) {\n const dc = dataChannels.get(userId);\n try {\n dc?.close();\n } catch {}\n dataChannels.delete(userId);\n },\n receivePeerConnection({ pc, userId, restart }) {\n createDataChannel(pc, userId, restart);\n },\n onBroadcastMessage(payload, from) {\n conveyMessage(payload, from);\n logLine?.(\"📢\", { event: \"broadcast\", userId, data: payload });\n },\n });\n\n function send(data: S, userId?: string) {\n dataChannels.forEach((dataChannel, pUserId) => {\n if (userId && pUserId !== userId) return;\n if (dataChannel.readyState === \"open\") {\n dataChannel.send(data as any);\n }\n });\n }\n\n function removeMessageListener(listener: (data: R, from: string) => void) {\n messagesListeners.delete(listener);\n }\n\n function addMessageListener(listener: (data: R, from: string) => void) {\n messagesListeners.add(listener);\n return () => {\n removeMessageListener(listener);\n };\n }\n\n function removeUserListener(listener: UserListener) {\n userListeners.delete(listener);\n }\n\n function addUserListener(listener: UserListener) {\n userListeners.add(listener);\n return () => {\n removeUserListener(listener);\n };\n }\n\n return {\n userId,\n send,\n broadcast,\n enterRoom,\n exitRoom,\n leaveUser,\n getUsers: () => [...userIds],\n addMessageListener,\n removeMessageListener,\n addUserListener,\n removeUserListener,\n reset() {\n userIds.forEach((userId) => {\n dataChannels.get(userId)?.close();\n dataChannels.delete(userId);\n resetPeerCollection(userId);\n });\n },\n end() {\n dataChannels.forEach((dataChannel) => {\n try {\n dataChannel.close();\n } catch {}\n });\n dataChannels.clear();\n endPeerCollection();\n userListeners.clear();\n userIds.clear();\n },\n };\n}\n"
9
11
  ],
10
- "mappings": "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",
11
- "debugId": "45D96A674130C1F164756E2164756E21",
12
+ "mappings": "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",
13
+ "debugId": "0AD885CEE565BC8564756E2164756E21",
12
14
  "names": []
13
15
  }
@@ -0,0 +1,10 @@
1
+ interface Message<T = string> {
2
+ type: T;
3
+ }
4
+ export declare class Listeners<M extends Message> {
5
+ private listenersByType;
6
+ addListener(type: M["type"], listener: (msg: M) => void): () => void;
7
+ trigger(msg: M): void;
8
+ }
9
+ export {};
10
+ //# sourceMappingURL=listeners.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"listeners.d.ts","sourceRoot":"","sources":["../src/browser/listeners.ts"],"names":[],"mappings":"AAAA,UAAU,OAAO,CAAC,CAAC,GAAG,MAAM;IAC1B,IAAI,EAAE,CAAC,CAAC;CACT;AAED,qBAAa,SAAS,CAAC,CAAC,SAAS,OAAO;IACtC,OAAO,CAAC,eAAe,CAA+C;IAEtE,WAAW,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,EAAE,QAAQ,EAAE,CAAC,GAAG,EAAE,CAAC,KAAK,IAAI;IAcvD,OAAO,CAAC,GAAG,EAAE,CAAC;CAGf"}
@@ -0,0 +1,4 @@
1
+ class i{listenersByType=new Map;addListener(e,s){let t=this.listenersByType.get(e)??(()=>{let r=new Set;return this.listenersByType.set(e,r),r})();return t.add(s),()=>{t.delete(s)}}trigger(e){this.listenersByType.get(e.type)?.forEach((s)=>s(e))}}export{i as Listeners};
2
+
3
+ //# debugId=0ECC54E6EECC2DD764756E2164756E21
4
+ //# sourceMappingURL=listeners.js.map
@@ -0,0 +1,10 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/browser/listeners.ts"],
4
+ "sourcesContent": [
5
+ "interface Message<T = string> {\n type: T;\n}\n\nexport class Listeners<M extends Message> {\n private listenersByType = new Map<M[\"type\"], Set<(msg: M) => void>>();\n\n addListener(type: M[\"type\"], listener: (msg: M) => void) {\n const listeners =\n this.listenersByType.get(type) ??\n (() => {\n const listeners = new Set<(msg: M) => void>();\n this.listenersByType.set(type, listeners);\n return listeners;\n })();\n listeners.add(listener);\n return () => {\n listeners.delete(listener);\n };\n }\n\n trigger(msg: M) {\n this.listenersByType.get(msg.type)?.forEach((listener) => listener(msg));\n }\n}\n"
6
+ ],
7
+ "mappings": "AAIO,MAAM,CAA6B,CAChC,gBAAkB,IAAI,IAE9B,WAAW,CAAC,EAAiB,EAA4B,CACvD,IAAM,EACJ,KAAK,gBAAgB,IAAI,CAAI,IAC5B,IAAM,CACL,IAAM,EAAY,IAAI,IAEtB,OADA,KAAK,gBAAgB,IAAI,EAAM,CAAS,EACjC,IACN,EAEL,OADA,EAAU,IAAI,CAAQ,EACf,IAAM,CACX,EAAU,OAAO,CAAQ,GAI7B,OAAO,CAAC,EAAQ,CACd,KAAK,gBAAgB,IAAI,EAAI,IAAI,GAAG,QAAQ,CAAC,IAAa,EAAS,CAAG,CAAC,EAE3E",
8
+ "debugId": "0ECC54E6EECC2DD764756E2164756E21",
9
+ "names": []
10
+ }
@@ -0,0 +1,14 @@
1
+ export declare class IceUrlProvider {
2
+ private sendToServerFunctions;
3
+ private icePromiseResolve?;
4
+ private icePromise?;
5
+ receiveIce(url: string, expiration: number): void;
6
+ addRequester(requester: (command: "request-ice") => void): () => void;
7
+ removeRequester(requester: (command: "request-ice") => void): void;
8
+ sendToServer(command: "request-ice"): void;
9
+ requestIce(): Promise<{
10
+ url: string;
11
+ expiration: number;
12
+ }>;
13
+ }
14
+ //# sourceMappingURL=ice-url-provider.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ice-url-provider.d.ts","sourceRoot":"","sources":["../../src/browser/utils/ice-url-provider.ts"],"names":[],"mappings":"AAAA,qBAAa,cAAc;IACzB,OAAO,CAAC,qBAAqB,CAAiD;IAC9E,OAAO,CAAC,iBAAiB,CAAC,CAGf;IACX,OAAO,CAAC,UAAU,CAAC,CAA+C;IAElE,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM;IAM1C,YAAY,CAAC,SAAS,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,IAAI;IAOxD,eAAe,CAAC,SAAS,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,IAAI;IAO3D,YAAY,CAAC,OAAO,EAAE,aAAa;IAM7B,UAAU;aA5BoB,MAAM;oBAAc,MAAM;;CAuC/D"}
@@ -0,0 +1,11 @@
1
+ import { IceUrlProvider } from "./ice-url-provider";
2
+ export declare class RTCConfigProvider {
3
+ private iceUrlProvider;
4
+ constructor(iceUrlProvider: IceUrlProvider);
5
+ private rtcConfig;
6
+ private rtcConfigPromise?;
7
+ getRtcConfig(): Promise<RTCConfiguration & {
8
+ timestamp: number;
9
+ }>;
10
+ }
11
+ //# sourceMappingURL=rtc-config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rtc-config.d.ts","sourceRoot":"","sources":["../../src/browser/utils/rtc-config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAMpD,qBAAa,iBAAiB;IAChB,OAAO,CAAC,cAAc;gBAAd,cAAc,EAAE,cAAc;IAElD,OAAO,CAAC,SAAS,CAGf;IACF,OAAO,CAAC,gBAAgB,CAAC,CAAoD;IAEvE,YAAY,IAAI,OAAO,CAAC,gBAAgB,GAAG;QAAE,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC;CA+BxE"}
@@ -1,10 +1,17 @@
1
1
  import { EnterRoom } from "./signal/signal-room";
2
2
  export type SigType = "offer" | "answer" | "ice" | "request-ice" | "broadcast";
3
- export type SigPayload = RTCSessionDescriptionInit | RTCIceCandidateInit;
4
- export declare function collectPeerConnections({ userId: passedUserId, worldId, receivePeerConnection, peerlessUserExpiration, fallbackRtcConfig, enterRoomFunction: enterRoom, logLine, onLeaveUser, workerUrl, onRoomReady, onRoomClose, onBroadcastMessage, }: {
3
+ export type SigPayload = {
4
+ connectionId?: string;
5
+ offer?: RTCSessionDescriptionInit;
6
+ answer?: RTCSessionDescriptionInit;
7
+ ice?: RTCIceCandidateInit;
8
+ } & Record<string, any>;
9
+ /**
10
+ * Collect peers
11
+ */
12
+ export declare function collectPeerConnections({ userId: passedUserId, worldId, receivePeerConnection, peerlessUserExpiration, enterRoomFunction: enterRoom, logLine, onLeaveUser, workerUrl, onRoomReady, onRoomClose, onBroadcastMessage, }: {
5
13
  userId?: string;
6
14
  worldId: string;
7
- fallbackRtcConfig?: RTCConfiguration;
8
15
  enterRoomFunction?: EnterRoom<SigType, SigPayload>;
9
16
  onLeaveUser?: (userId: string) => void;
10
17
  logLine?: (direction: string, obj?: any) => void;
@@ -1 +1 @@
1
- {"version":3,"file":"webrtc-peer-collector.d.ts","sourceRoot":"","sources":["../src/browser/webrtc-peer-collector.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAa,MAAM,sBAAsB,CAAC;AAE5D,MAAM,MAAM,OAAO,GAAG,OAAO,GAAG,QAAQ,GAAG,KAAK,GAAG,aAAa,GAAG,WAAW,CAAC;AAC/E,MAAM,MAAM,UAAU,GAAG,yBAAyB,GAAG,mBAAmB,CAAC;AAkBzE,wBAAgB,sBAAsB,CAAC,EACrC,MAAM,EAAE,YAAY,EACpB,OAAO,EACP,qBAAqB,EACrB,sBAA6B,EAC7B,iBAEC,EACD,iBAAiB,EAAE,SAA8B,EACjD,OAAO,EACP,WAAW,EACX,SAAS,EACT,WAAW,EACX,WAAW,EACX,kBAAkB,GACnB,EAAE;IACD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,iBAAiB,CAAC,EAAE,gBAAgB,CAAC;IACrC,iBAAiB,CAAC,EAAE,SAAS,CAAC,OAAO,EAAE,UAAU,CAAC,CAAC;IACnD,WAAW,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IACvC,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,GAAG,KAAK,IAAI,CAAC;IACjD,SAAS,CAAC,EAAE,GAAG,CAAC;IAChB,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,qBAAqB,CAAC,UAAU,EAAE;QAChC,EAAE,EAAE,iBAAiB,CAAC;QACtB,MAAM,EAAE,MAAM,CAAC;QACf,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;KACtB,GAAG,IAAI,CAAC;IACT,WAAW,CAAC,CAAC,IAAI,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IACzD,WAAW,CAAC,CAAC,IAAI,EAAE;QACjB,IAAI,EAAE,MAAM,CAAC;QACb,IAAI,EAAE,MAAM,CAAC;QACb,EAAE,EAAE,IAAI,CAAC,UAAU,EAAE,QAAQ,GAAG,MAAM,GAAG,UAAU,CAAC,CAAC;KACtD,GAAG,IAAI,CAAC;IACT,kBAAkB,CAAC,CAAC,CAAC,SAAS,GAAG,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;CACpE;;gCAgFgC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE;+BAT/B;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE;wBA7BjC,MAAM;kBA8RX,MAAM;cAIhB,CAAC,2BAAuB,CAAC;;EAUtC"}
1
+ {"version":3,"file":"webrtc-peer-collector.d.ts","sourceRoot":"","sources":["../src/browser/webrtc-peer-collector.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,SAAS,EAAa,MAAM,sBAAsB,CAAC;AAG5D,MAAM,MAAM,OAAO,GAAG,OAAO,GAAG,QAAQ,GAAG,KAAK,GAAG,aAAa,GAAG,WAAW,CAAC;AAC/E,MAAM,MAAM,UAAU,GAAG;IACvB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,KAAK,CAAC,EAAE,yBAAyB,CAAC;IAClC,MAAM,CAAC,EAAE,yBAAyB,CAAC;IACnC,GAAG,CAAC,EAAE,mBAAmB,CAAC;CAC3B,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;AAwBxB;;GAEG;AACH,wBAAgB,sBAAsB,CAAC,EACrC,MAAM,EAAE,YAAY,EACpB,OAAO,EACP,qBAAqB,EACrB,sBAA6B,EAC7B,iBAAiB,EAAE,SAA8B,EACjD,OAAO,EACP,WAAW,EACX,SAAS,EACT,WAAW,EACX,WAAW,EACX,kBAAkB,GACnB,EAAE;IACD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,iBAAiB,CAAC,EAAE,SAAS,CAAC,OAAO,EAAE,UAAU,CAAC,CAAC;IACnD,WAAW,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IACvC,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,GAAG,KAAK,IAAI,CAAC;IACjD,SAAS,CAAC,EAAE,GAAG,CAAC;IAChB,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,qBAAqB,CAAC,UAAU,EAAE;QAChC,EAAE,EAAE,iBAAiB,CAAC;QACtB,MAAM,EAAE,MAAM,CAAC;QACf,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;KACtB,GAAG,IAAI,CAAC;IACT,WAAW,CAAC,CAAC,IAAI,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IACzD,WAAW,CAAC,CAAC,IAAI,EAAE;QACjB,IAAI,EAAE,MAAM,CAAC;QACb,IAAI,EAAE,MAAM,CAAC;QACb,EAAE,EAAE,IAAI,CAAC,UAAU,EAAE,QAAQ,GAAG,MAAM,GAAG,UAAU,CAAC,CAAC;KACtD,GAAG,IAAI,CAAC;IACT,kBAAkB,CAAC,CAAC,CAAC,SAAS,GAAG,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;CACpE;;gCAuDgC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE;+BAT/B;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE;wBAhCjC,MAAM;kBAuVX,MAAM;cAIhB,CAAC,2BAAuB,CAAC;;EAUtC"}
@@ -1,4 +1,4 @@
1
- function g(K){let{userId:k,worldId:N,room:E,host:C,autoRejoin:w=!0,logLine:q}=K,_=!1,T=0,B,L,h=!0,x=new Map,D=`wss://${C}/room/${N}/${E}?userId=${encodeURIComponent(k)}`,W=[],J=0;function A(Q,z,O){if(!B)return q?.("\uD83D\uDC64 ➡️ ❌","no ws available"),!1;let G={type:Q,to:z,payload:O};if(W.push(G),q?.("\uD83D\uDC64 ➡️ \uD83D\uDDA5️",G),clearTimeout(J),_||B.readyState!==WebSocket.OPEN)return q?.("\uD83D\uDC64 ➡️ ❌","Not in opened state: "+B.readyState),!1;return J=setTimeout(()=>{B.send(JSON.stringify(W)),W.length=0}),!0}function P(){if(_)return;B=new WebSocket(D),B.onopen=()=>{if(h)K.onOpen?.(),h=!1;T=0},B.onmessage=(Q)=>{try{let z=JSON.parse(Q.data);(Array.isArray(z)?z:[z]).forEach((G)=>{if(q?.("\uD83D\uDDA5️ ➡️ \uD83D\uDC64",G),G.type==="peer-joined"||G.type==="peer-left")F(G.users,G);else if(G.type==="ice-server")K.onIceUrl?.(G.url,G.expiration);else if(G.userId)K.onMessage(G.type,G.payload,{userId:G.userId,receive:(Z,Y)=>A(Z,G.userId,Y)})})}catch{q?.("⚠️ ERROR",{error:"invalid-json"})}},B.onclose=(Q)=>{let O=[1001,1006,1011,1012,1013].includes(Q.code);if(w&&!_&&O){let G=Math.min(Math.pow(2,T)*1000,30000),Z=Math.random()*1000,Y=G+Z;q?.("\uD83D\uDD04 RECONNECTING",{attempt:T+1,delayMs:Math.round(Y)}),T++,L=setTimeout(P,Y)}else K.onClose?.({code:Q.code,reason:Q.reason,wasClean:Q.wasClean})},B.onerror=(Q)=>{console.error("WS Error",Q),K.onError?.()}}function F(Q,z){let O=[],G=[],Z=new Set;Q.forEach(({userId:Y})=>{if(Y===k)return;if(!x.has(Y)||z.type==="peer-joined"&&Y===z.userId){let b={userId:Y,receive:(M,R)=>A(M,Y,R)};x.set(Y,b),O.push(b)}Z.add(Y)});for(let Y of x.keys())if(!Z.has(Y)||z.type==="peer-left"&&Y===z.userId)x.delete(Y),G.push({userId:Y});if(O.length)K.onPeerJoined(O);if(G.length)K.onPeerLeft(G)}return P(),{sendToServer(Q,z){A(Q,"server",z)},exitRoom:()=>{_=!0,clearTimeout(L),B.close()}}}function c({userId:K,worldId:k,room:N,host:E,autoRejoin:C=!0,onOpen:w,onClose:q,onError:_,onPeerJoined:T,onPeerLeft:B,onIceUrl:L,onMessage:h,logLine:x,workerUrl:D}){if(!D)return console.warn("Warning: enterRoom called without workerUrl; this may cause issues in some environments. You should pass workerUrl explicitly. Use:","https://cdn.jsdelivr.net/npm/@dobuki/hello-worker/dist/signal-room.worker.min.js"),g({userId:K,worldId:k,room:N,host:E,autoRejoin:C,onOpen:w,onClose:q,onError:_,onPeerJoined:T,onPeerLeft:B,onIceUrl:L,onMessage:h});let W=new Worker(D,{type:"module"}),J=!1;function A({userId:F}){return{userId:F,receive:(Q,z)=>{if(J)return!1;return W.postMessage({cmd:"send",toUserId:F,host:E,room:N,type:Q,payload:z}),!0}}}let P=(F)=>{let Q=F.data;if(Q.kind==="open")w?.();else if(Q.kind==="close")W.terminate(),q?.(Q.ev);else if(Q.kind==="error")_?.();else if(Q.kind==="peer-joined")T(Q.users.map((z)=>A({userId:z.userId})));else if(Q.kind==="peer-left")B(Q.users);else if(Q.kind==="ice-server")L?.(Q.url,Q.expiration);else if(Q.kind==="message")h(Q.type,Q.payload,A({userId:Q.fromUserId}));else if(Q.kind==="log")x?.(Q.direction,Q.obj)};return W.addEventListener("message",P),W.postMessage({cmd:"enter",userId:K,worldId:k,room:N,host:E,autoRejoin:C}),{exitRoom:()=>{J=!0,W.removeEventListener("message",P),W.postMessage({cmd:"exit"})},sendToServer:(F,Q)=>{W.postMessage({cmd:"send",toUserId:"server",host:E,room:N,type:F,payload:Q})}}}var m=c;function a({userId:K,worldId:k,receivePeerConnection:N,peerlessUserExpiration:E=5000,fallbackRtcConfig:C={iceServers:[{urls:"stun:stun.l.google.com:19302"}]},enterRoomFunction:w=m,logLine:q,onLeaveUser:_,workerUrl:T,onRoomReady:B,onRoomClose:L,onBroadcastMessage:h}){let x=K??`user-${crypto.randomUUID()}`,D=new Map,W=void 0,J={...C,timestamp:Date.now()},A=new Map;async function P(G,Z){if(G){let Y=3;for(let b=0;b<Y;b++){try{let M=await fetch(G);if(!M.ok)throw Error(`ICE endpoint failed: ${M.status}`);return J=await M.json(),J}catch(M){console.warn("Failed fetching iceUrl")}G=(await Z()).url}}return J}function F(G){_?.(G);let Z=D.get(G);if(!Z)return;D.delete(G);try{Z.close()}catch{}}async function Q(G){if(!G.pc?.remoteDescription)return;let Z=G.pendingRemoteIce;G.pendingRemoteIce=[];for(let Y of Z)try{await G.pc.addIceCandidate(Y)}catch(b){q?.("⚠️ ERROR",{error:"add-ice-failed",userId:G.peer.userId,detail:String(b)})}}function z({room:G,host:Z}){let Y=`${Z}/room/${G}`,b=A.get(Y);if(b)b.exitRoom(),A.delete(Y)}function O({room:G,host:Z}){return new Promise(async(Y,b)=>{async function M(X){let $=Date.now();if($-(J?.timestamp??0)>1e4){let V=!W||W.expiration-$<2000?await v():W;J=await P(V.url,v)}return X.pc=new RTCPeerConnection(J),X.pc.onicecandidate=(V)=>{if(!V.candidate)return;X.peer.receive("ice",V.candidate.toJSON())},X.pc.onconnectionstatechange=async()=>{if(q?.("\uD83D\uDCAC",{event:"pc-state",userId:X.peer.userId,state:X.pc?.connectionState}),X.pc?.connectionState==="failed"){X.close();let V=await R(X.peer,!0);if(V.pc)N({pc:V.pc,userId:V.peer.userId,restart:()=>V.close()});else q?.("\uD83D\uDC64ℹ️","no pc: "+V.peer.userId);return}},X.pc}async function R(X,$){let V=D.get(X.userId);if(!V||$){let H={pendingRemoteIce:[],peer:X,close(){this.pc?.close(),this.pc=void 0,D.delete(X.userId)},async reset(){H.close();let j=await R(X,!0);console.log(">>",j.peer.userId),setTimeout(async()=>{if(!j.pc){console.log("no pc");return}N({pc:j.pc,userId:j.peer.userId,restart:()=>j.close()}),await U(j.peer)},3000)}};await M(H),V=H,D.set(V.peer.userId,V)}else if(V){if(clearTimeout(V.expirationTimeout),V.expirationTimeout=0,!V.pc||V.pc.signalingState==="closed")await M(V)}return V.peer=X,V}async function U(X){let V=(await R(X)).pc,H=await V?.createOffer();await V?.setLocalDescription(H),X.receive("offer",V?.localDescription?.toJSON())}let f;async function v(){let X=await new Promise(($)=>{f=$,I("request-ice")});return f=void 0,X}let{exitRoom:u,sendToServer:I}=w({userId:x,worldId:k,room:G,host:Z,logLine:q,workerUrl:T,autoRejoin:!0,onOpen(){B?.({room:G,host:Z}),Y()},onError(){console.error("onError"),b()},onClose(X){L?.({room:G,host:Z,ev:X})},onPeerJoined(X){X.forEach(async($)=>{let V=await R($,!0),H=V.pc;if(!H){q?.("\uD83D\uDC64ℹ️","no pc: "+$.userId);return}N({pc:H,userId:$.userId,restart:()=>V.close()}),await U($)})},onPeerLeft(X){X.forEach(({userId:$})=>{let V=D.get($);if(!V)return;V.expirationTimeout=setTimeout(()=>F($),E??0)})},onIceUrl(X,$){W={url:X,expiration:$},f?.(W)},async onMessage(X,$,V){let H=await R(V);if(X==="offer"){H.close();let S=await M(H);N({pc:S,userId:V.userId,restart:()=>H.close()}),await S.setRemoteDescription($);let y=await S.createAnswer();await S.setLocalDescription(y),V.receive("answer",S.localDescription?.toJSON()),await Q(H);return}let j=H.pc??await M(H);if(X==="answer"){await j.setRemoteDescription($),await Q(H);return}if(X==="ice"){let S=$;if(!j.remoteDescription){H.pendingRemoteIce.push(S);return}try{await H.pc?.addIceCandidate(S)}catch(y){q?.("⚠️ ERROR",{error:"add-ice-failed",userId:H.peer.userId,detail:String(y)})}return}if(X==="broadcast")h?.($,V.userId)}});A.set(`${Z}/room/${G}`,{exitRoom:u,room:G,host:Z,broadcast:(X)=>I("broadcast",X)})})}return{userId:x,enterRoom:O,exitRoom:z,leaveUser:F,async reset(G){D.get(G)?.reset()},broadcast(G){A.forEach((Z)=>Z.broadcast(G))},end(){A.forEach(({exitRoom:G})=>G()),A.clear(),D.forEach(({peer:G})=>F(G.userId)),D.clear()}}}export{a as collectPeerConnections};
1
+ class L{sendToServerFunctions=[];icePromiseResolve;icePromise;receiveIce(V,T){this.icePromiseResolve?.({url:V,expiration:T}),this.icePromiseResolve=void 0,this.icePromise=void 0}addRequester(V){return this.sendToServerFunctions.push(V),()=>{this.removeRequester(V)}}removeRequester(V){this.sendToServerFunctions.splice(this.sendToServerFunctions.indexOf(V),1)}sendToServer(V){this.sendToServerFunctions[Math.floor(this.sendToServerFunctions.length*Math.random())](V)}async requestIce(){if(!this.icePromise)this.icePromise=new Promise((V)=>{this.icePromiseResolve=V,this.sendToServer("request-ice")});return await this.icePromise}}function U(V){let{userId:T,worldId:H,room:J,host:O,autoRejoin:z=!0,logLine:W}=V,x=!1,S=0,B,R,_=!0,A=new Map,C=`wss://${O}/room/${H}/${J}?userId=${encodeURIComponent(T)}`,D=[],P=0;function N(b,q,Y){if(!B)return W?.("\uD83D\uDC64 ➡️ ❌","no ws available"),!1;let $={type:b,to:q,payload:Y};if(D.push($),W?.("\uD83D\uDC64 ➡️ \uD83D\uDDA5️",$),clearTimeout(P),x||B.readyState!==WebSocket.OPEN)return W?.("\uD83D\uDC64 ➡️ ❌","Not in opened state: "+B.readyState),!1;return P=setTimeout(()=>{B.send(JSON.stringify(D)),D.length=0}),!0}function E(){if(x)return;B=new WebSocket(C),B.onopen=()=>{if(_)V.onOpen?.(),_=!1;S=0},B.onmessage=(b)=>{try{let q=JSON.parse(b.data);(Array.isArray(q)?q:[q]).forEach(($)=>{if(W?.("\uD83D\uDDA5️ ➡️ \uD83D\uDC64",$),$.type==="peer-joined"||$.type==="peer-left")M($.users,$);else if($.type==="ice-server")V.onIceUrl?.($.url,$.expiration);else if($.userId)V.onMessage($.type,$.payload,{userId:$.userId,receive:(K,Z)=>N(K,$.userId,Z)})})}catch{W?.("⚠️ ERROR",{error:"invalid-json"})}},B.onclose=(b)=>{let Y=[1001,1006,1011,1012,1013].includes(b.code);if(z&&!x&&Y){let $=Math.min(Math.pow(2,S)*1000,30000),K=Math.random()*1000,Z=$+K;W?.("\uD83D\uDD04 RECONNECTING",{attempt:S+1,delayMs:Math.round(Z)}),S++,R=setTimeout(E,Z)}else V.onClose?.({code:b.code,reason:b.reason,wasClean:b.wasClean})},B.onerror=(b)=>{console.error("WS Error",b),V.onError?.()}}function M(b,q){let Y=[],$=[],K=new Set;b.forEach(({userId:Z})=>{if(Z===T)return;if(!A.has(Z)||q.type==="peer-joined"&&Z===q.userId){let f={userId:Z,receive:(j,w)=>N(j,Z,w)};A.set(Z,f),Y.push(f)}K.add(Z)});for(let Z of A.keys())if(!K.has(Z)||q.type==="peer-left"&&Z===q.userId)A.delete(Z),$.push({userId:Z});if(Y.length)V.onPeerJoined(Y);if($.length)V.onPeerLeft($)}return E(),{sendToServer(b,q){N(b,"server",q)},exitRoom:()=>{x=!0,clearTimeout(R),B.close()}}}function I({userId:V,worldId:T,room:H,host:J,autoRejoin:O=!0,onOpen:z,onClose:W,onError:x,onPeerJoined:S,onPeerLeft:B,onIceUrl:R,onMessage:_,logLine:A,workerUrl:C}){if(!C)return console.warn("Warning: enterRoom called without workerUrl; this may cause issues in some environments. You should pass workerUrl explicitly. Use:","https://cdn.jsdelivr.net/npm/@dobuki/hello-worker/dist/signal-room.worker.min.js"),U({userId:V,worldId:T,room:H,host:J,autoRejoin:O,onOpen:z,onClose:W,onError:x,onPeerJoined:S,onPeerLeft:B,onIceUrl:R,onMessage:_});let D=new Worker(C,{type:"module"}),P=!1;function N({userId:M}){return{userId:M,receive:(b,q)=>{if(P)return!1;return D.postMessage({cmd:"send",toUserId:M,host:J,room:H,type:b,payload:q}),!0}}}let E=(M)=>{let b=M.data;if(b.kind==="open")z?.();else if(b.kind==="close")D.terminate(),W?.(b.ev);else if(b.kind==="error")x?.();else if(b.kind==="peer-joined")S(b.users.map((q)=>N({userId:q.userId})));else if(b.kind==="peer-left")B(b.users);else if(b.kind==="ice-server")R?.(b.url,b.expiration);else if(b.kind==="message")_(b.type,b.payload,N({userId:b.fromUserId}));else if(b.kind==="log")A?.(b.direction,b.obj)};return D.addEventListener("message",E),D.postMessage({cmd:"enter",userId:V,worldId:T,room:H,host:J,autoRejoin:O}),{exitRoom:()=>{P=!0,D.removeEventListener("message",E),D.postMessage({cmd:"exit"})},sendToServer:(M,b)=>{D.postMessage({cmd:"send",toUserId:"server",host:J,room:H,type:M,payload:b})}}}var c={iceServers:[{urls:"stun:stun.l.google.com:19302"}]};class v{iceUrlProvider;constructor(V){this.iceUrlProvider=V}rtcConfig={...c,timestamp:Date.now()};rtcConfigPromise;async getRtcConfig(){if(Date.now()-(this.rtcConfig?.timestamp??0)<1e4)return this.rtcConfig;if(!this.rtcConfigPromise)this.rtcConfigPromise=new Promise(async(T)=>{let H=3;for(let J=0;J<H;J++)try{let O=(await this.iceUrlProvider.requestIce()).url,z=await fetch(O);if(!z.ok)throw Error(`ICE endpoint failed: ${z.status}`);let W=await z.json();T(W);return}catch(O){console.warn("Failed fetching iceUrl")}}),this.rtcConfig=await this.rtcConfigPromise,this.rtcConfigPromise=void 0;return this.rtcConfig}}var u=I;function s({userId:V,worldId:T,receivePeerConnection:H,peerlessUserExpiration:J=5000,enterRoomFunction:O=u,logLine:z,onLeaveUser:W,workerUrl:x,onRoomReady:S,onRoomClose:B,onBroadcastMessage:R}){let _=V??`user-${crypto.randomUUID()}`,A=new Map,C=new Map;function D(q){W?.(q);let Y=A.get(q);if(!Y)return;A.delete(q);try{Y.close()}catch{}}async function P(q){if(!q.connection?.pc?.remoteDescription)return;let Y=q.connection.pendingRemoteIce;q.connection.pendingRemoteIce=[];for(let $ of Y)try{await q.connection.pc.addIceCandidate($)}catch(K){z?.("⚠️ ERROR",{error:"add-ice-failed",userId:q.peer.userId,detail:String(K)})}}let N=new L,E=new v(N);function M({room:q,host:Y}){let $=`${Y}/room/${q}`,K=C.get($);if(K)K.exitRoom(),C.delete($)}function b({room:q,host:Y}){return new Promise(async($,K)=>{async function Z(F){if(F.connectionPromise)return F.connectionPromise;return F.connectionPromise=new Promise(async(G)=>{F.connection={id:`conn-${crypto.randomUUID()}`,pc:new RTCPeerConnection(await E.getRtcConfig()),pendingRemoteIce:[]},F.connection.pc.onicecandidate=(h)=>{if(!h.candidate)return;F.peer.receive("ice",{connectionId:F.connection?.id,ice:h.candidate.toJSON()})},F.connection.pc.onconnectionstatechange=async()=>{if(z?.("\uD83D\uDCAC",{event:"pc-state",userId:F.peer.userId,state:F.connection?.pc?.connectionState}),F.connection?.pc?.connectionState==="failed"){F.close();let h=await f(F.peer,!0);if(h.connection?.pc)H({pc:h.connection?.pc,userId:h.peer.userId,restart:()=>h.close()});else z?.("\uD83D\uDC64ℹ️","no pc: "+h.peer.userId);return}},G(F.connection)})}async function f(F,G){let h=A.get(F.userId);if(!h||G){let X={peer:F,close(){if(this.connection)this.connection.pc.close(),this.connection=void 0;A.delete(F.userId)},async reset(){X.close(),setTimeout(async()=>{let Q=await f(F,!0);if(!Q.connection?.pc){z?.("⚠️","no pc");return}H({pc:Q.connection?.pc,userId:Q.peer.userId,restart:()=>Q.close()}),await j(Q.peer)},500)}};h=X,await Z(X),A.set(h.peer.userId,h)}else if(h){if(clearTimeout(h.expirationTimeout),h.expirationTimeout=0,!h.connection?.pc||h.connection?.pc.signalingState==="closed")await Z(h)}return h.peer=F,h}async function j(F){let G=await f(F),h=G.connection?.pc,X=await h?.createOffer();await h?.setLocalDescription(X),F.receive("offer",{connectionId:G.connection?.id,offer:h.localDescription.toJSON()})}let{exitRoom:w,sendToServer:k}=O({userId:_,worldId:T,room:q,host:Y,logLine:z,workerUrl:x,autoRejoin:!0,onOpen(){S?.({room:q,host:Y}),$()},onError(){console.error("onError"),K()},onClose(F){B?.({room:q,host:Y,ev:F})},onPeerJoined(F){F.forEach(async(G)=>{let h=await f(G,!0),X=h.connection?.pc;if(!X){z?.("\uD83D\uDC64ℹ️","no pc: "+G.userId);return}H({pc:X,userId:G.userId,restart:()=>h.close()}),await j(G)})},onPeerLeft(F){F.forEach(({userId:G})=>{let h=A.get(G);if(!h)return;h.expirationTimeout=setTimeout(()=>D(G),J??0)})},onIceUrl(F,G){N.receiveIce(F,G)},async onMessage(F,G,h){if(F==="offer"&&G.offer){let X=await f(h,!1),Q=!X.connection||X.connection.pc.signalingState==="stable"?await Z(X):X.connection;z?.("\uD83D\uDCAC",{type:F,signalingState:Q.pc.signalingState}),Q.peerConnectionId=G.connectionId,H({pc:Q.pc,userId:h.userId,restart:()=>X.close()}),await Q.pc.setRemoteDescription(G.offer);let i=await Q.pc.createAnswer();await Q.pc.setLocalDescription(i),h.receive("answer",{connectionId:Q.id,answer:Q.pc.localDescription?.toJSON()}),await P(X);return}if(F==="answer"&&G.answer){let X=await f(h,!1),Q=X.connection&&X.connection.pc.signalingState!=="closed"?X.connection:await Z(X);z?.("\uD83D\uDCAC",{type:F,signalingState:Q.pc.signalingState}),await Q.pc.setRemoteDescription(G.answer),Q.peerConnectionId=G.connectionId,await P(X);return}if(F==="ice"&&G.ice){let X=await f(h,!1),Q=X.connection??await X.connectionPromise;if(!Q){z?.("⚠️","No connection");return}if(z?.("\uD83D\uDCAC",{type:F,signalingState:Q.pc.signalingState}),Q.peerConnectionId&&G.connectionId!==Q.peerConnectionId){z?.("⚠️","Mismatch peerConnectionID"+G.connectionId+"vs"+Q.peerConnectionId);return}if(!Q.pc.remoteDescription||!Q.peerConnectionId){Q.peerConnectionId=G.connectionId,Q.pendingRemoteIce.push(G.ice);return}try{await Q.pc.addIceCandidate(G.ice)}catch(i){z?.("⚠️ ERROR",{error:"add-ice-failed",userId:X.peer.userId,detail:String(i)})}return}if(F==="broadcast")R?.(G,h.userId)}});N.addRequester(k),C.set(`${Y}/room/${q}`,{exitRoom:()=>{w(),N.removeRequester(k)},room:q,host:Y,broadcast:(F)=>k("broadcast",F)})})}return{userId:_,enterRoom:b,exitRoom:M,leaveUser:D,async reset(q){A.get(q)?.reset()},broadcast(q){C.forEach((Y)=>Y.broadcast(q))},end(){C.forEach(({exitRoom:q})=>q()),C.clear(),A.forEach(({peer:q})=>D(q.userId)),A.clear()}}}export{s as collectPeerConnections};
2
2
 
3
- //# debugId=C1B79A8ADB47518264756E2164756E21
3
+ //# debugId=67E8B3D0D9449E2464756E2164756E21
4
4
  //# sourceMappingURL=webrtc-peer-collector.js.map
@@ -1,12 +1,14 @@
1
1
  {
2
2
  "version": 3,
3
- "sources": ["../src/browser/signal/impl/signal-room.ts", "../src/browser/signal/signal-room.ts", "../src/browser/webrtc-peer-collector.ts"],
3
+ "sources": ["../src/browser/utils/ice-url-provider.ts", "../src/browser/signal/impl/signal-room.ts", "../src/browser/signal/signal-room.ts", "../src/browser/utils/rtc-config.ts", "../src/browser/webrtc-peer-collector.ts"],
4
4
  "sourcesContent": [
5
+ "export class IceUrlProvider {\n private sendToServerFunctions = new Array<(command: \"request-ice\") => void>();\n private icePromiseResolve?: (url: {\n url: string;\n expiration: number;\n }) => void;\n private icePromise?: Promise<{ url: string; expiration: number }>;\n\n receiveIce(url: string, expiration: number) {\n this.icePromiseResolve?.({ url, expiration });\n this.icePromiseResolve = undefined;\n this.icePromise = undefined;\n }\n\n addRequester(requester: (command: \"request-ice\") => void) {\n this.sendToServerFunctions.push(requester);\n return () => {\n this.removeRequester(requester);\n };\n }\n\n removeRequester(requester: (command: \"request-ice\") => void) {\n this.sendToServerFunctions.splice(\n this.sendToServerFunctions.indexOf(requester),\n 1,\n );\n }\n\n sendToServer(command: \"request-ice\") {\n this.sendToServerFunctions[\n Math.floor(this.sendToServerFunctions.length * Math.random())\n ](command);\n }\n\n async requestIce() {\n if (!this.icePromise) {\n this.icePromise = new Promise<{ url: string; expiration: number }>(\n (resolve) => {\n this.icePromiseResolve = resolve;\n this.sendToServer(\"request-ice\");\n },\n );\n }\n return await this.icePromise;\n }\n}\n",
5
6
  "export interface IPeer<T extends string = string, P = any> {\n userId: string;\n receive(type: T, payload: P): boolean;\n}\n\ntype OutMessage = { type: string; to: string; payload: any };\n\n/**\n * enterRoom connects to the signaling room via WebSocket.\n */\nexport function enterRoom<T extends string, P = any>(params: {\n userId: string;\n worldId: string;\n room: string;\n host: string;\n onOpen?: () => void;\n onClose?: (ev: Pick<CloseEvent, \"code\" | \"reason\" | \"wasClean\">) => void;\n onError?: () => void;\n logLine?: (direction: string, obj?: any) => void;\n onPeerJoined(users: IPeer<T, P>[]): void;\n onPeerLeft(users: { userId: string }[]): void;\n onIceUrl?(url: string, expiration: number): void;\n onMessage(type: T, payload: P, from: IPeer<T, P>): void;\n autoRejoin?: boolean;\n}): {\n exitRoom: () => void;\n sendToServer: <P extends any>(type: T, payload?: P) => void;\n} {\n type Message = {\n type: \"peer-joined\" | \"peer-left\" | \"ice-server\" | T;\n userId: string;\n users: { userId: string }[];\n payload: P;\n url: string;\n expiration: number;\n };\n\n const { userId, worldId, room, host, autoRejoin = true, logLine } = params;\n\n let exited = false;\n let retryCount = 0;\n let ws: WebSocket;\n let timeoutId: ReturnType<typeof setTimeout>;\n let initialConnection = true;\n\n const peers = new Map<string, IPeer<T, P>>();\n const wsUrl = `wss://${host}/room/${worldId}/${room}?userId=${encodeURIComponent(\n userId,\n )}`;\n\n // Helper for sending (uses the current ws instance)\n const accumulatedMessages: OutMessage[] = [];\n let timeout: ReturnType<typeof setTimeout> = 0;\n function send(type: string, to: \"server\" | string, payload?: any) {\n if (!ws) {\n logLine?.(\"👤 ➡️ ❌\", \"no ws available\");\n return false;\n }\n const obj: OutMessage = { type, to, payload };\n accumulatedMessages.push(obj);\n logLine?.(\"👤 ➡️ 🖥️\", obj);\n clearTimeout(timeout);\n if (exited || ws.readyState !== WebSocket.OPEN) {\n logLine?.(\"👤 ➡️ ❌\", \"Not in opened state: \" + ws.readyState);\n return false;\n }\n timeout = setTimeout(() => {\n ws.send(JSON.stringify(accumulatedMessages));\n accumulatedMessages.length = 0;\n });\n return true;\n }\n\n function connect() {\n if (exited) return;\n\n ws = new WebSocket(wsUrl);\n\n ws.onopen = () => {\n if (initialConnection) {\n params.onOpen?.();\n initialConnection = false;\n }\n retryCount = 0; // Reset backoff on successful connection\n };\n\n ws.onmessage = (e: MessageEvent) => {\n try {\n const result = JSON.parse(e.data);\n const msgs: Message[] = Array.isArray(result) ? result : [result];\n msgs.forEach((msg) => {\n logLine?.(\"🖥️ ➡️ 👤\", msg);\n if (msg.type === \"peer-joined\" || msg.type === \"peer-left\") {\n updatePeers(msg.users, msg);\n } else if (msg.type === \"ice-server\") {\n params.onIceUrl?.(msg.url, msg.expiration);\n } else if (msg.userId) {\n params.onMessage(msg.type, msg.payload, {\n userId: msg.userId,\n receive: (type: T, payload: P) => send(type, msg.userId, payload),\n });\n }\n });\n } catch {\n logLine?.(\"⚠️ ERROR\", { error: \"invalid-json\" });\n }\n };\n\n ws.onclose = (ev: CloseEvent) => {\n // 1. Check if we should even try to reconnect\n const recoverableCodes = [1001, 1006, 1011, 1012, 1013];\n const isRecoverable = recoverableCodes.includes(ev.code);\n\n if (autoRejoin && !exited && isRecoverable) {\n // 2. Exponential Backoff: 1s, 2s, 4s, 8s... capped at 30s\n const backoff = Math.min(Math.pow(2, retryCount) * 1000, 30000);\n // 3. Add Jitter: +/- 1000ms randomness\n const jitter = Math.random() * 1000;\n const delay = backoff + jitter;\n\n logLine?.(\"🔄 RECONNECTING\", {\n attempt: retryCount + 1,\n delayMs: Math.round(delay),\n });\n\n retryCount++;\n timeoutId = setTimeout(connect, delay);\n } else {\n params.onClose?.({\n code: ev.code,\n reason: ev.reason,\n wasClean: ev.wasClean,\n });\n }\n };\n\n ws.onerror = (ev) => {\n console.error(\"WS Error\", ev);\n params.onError?.();\n };\n }\n\n // Helper for peer tracking (logic from your original code)\n function updatePeers(updatedUsers: { userId: string }[], msg: Message) {\n const joined: IPeer<T, P>[] = [];\n const left: { userId: string }[] = [];\n const updatedPeerSet = new Set<string>();\n\n updatedUsers.forEach(({ userId: pUserId }) => {\n if (pUserId === userId) return;\n if (\n !peers.has(pUserId) ||\n (msg.type === \"peer-joined\" && pUserId === msg.userId)\n ) {\n const newPeer = {\n userId: pUserId,\n receive: (t: T, p: P) => send(t, pUserId, p),\n };\n peers.set(pUserId, newPeer);\n joined.push(newPeer);\n }\n updatedPeerSet.add(pUserId);\n });\n\n for (const pUserId of peers.keys()) {\n if (\n !updatedPeerSet.has(pUserId) ||\n (msg.type === \"peer-left\" && pUserId === msg.userId)\n ) {\n peers.delete(pUserId);\n left.push({ userId: pUserId });\n }\n }\n\n // Notify peer joined first then peer left. (avoid disconnect in case the peer leaving / joining is on the same user).\n if (joined.length) params.onPeerJoined(joined);\n if (left.length) params.onPeerLeft(left);\n }\n\n // Start initial connection\n connect();\n\n return {\n sendToServer(type, payload) {\n send(type, \"server\", payload);\n },\n exitRoom: () => {\n exited = true;\n clearTimeout(timeoutId);\n ws.close();\n },\n };\n}\n",
6
7
  "import type { IPeer } from \"./impl/signal-room.js\";\nimport { enterRoom as baseEnterRoom } from \"./impl/signal-room.js\";\nimport { RoomEvent, WorkerCommand } from \"./signal-room.worker.js\";\n\nexport function enterRoom<T extends string, P = any>({\n userId,\n worldId,\n room,\n host,\n autoRejoin = true,\n onOpen,\n onClose,\n onError,\n onPeerJoined,\n onPeerLeft,\n onIceUrl,\n onMessage,\n logLine,\n workerUrl,\n}: {\n userId: string;\n worldId: string;\n room: string;\n host: string;\n autoRejoin?: boolean;\n onOpen?: () => void;\n onClose?: (ev: Pick<CloseEvent, \"code\" | \"reason\" | \"wasClean\">) => void;\n onError?: () => void;\n onPeerJoined: (users: IPeer<T, P>[]) => void;\n onPeerLeft: (users: { userId: string }[]) => void;\n onIceUrl?(url: string, expiration: number): void;\n onMessage: (type: T, payload: P, from: IPeer<T, P>) => void;\n logLine?: (direction: string, obj?: any) => void;\n\n // Pass the URL to your worker file (bundler will handle it)\n workerUrl?: URL;\n}): {\n exitRoom: () => void;\n sendToServer: <P extends any>(type: T, payload?: P) => void;\n} {\n if (!workerUrl) {\n const CDN_WORKER_URL = `https://cdn.jsdelivr.net/npm/@dobuki/hello-worker/dist/signal-room.worker.min.js`;\n\n console.warn(\n \"Warning: enterRoom called without workerUrl; this may cause issues in some environments. You should pass workerUrl explicitly. Use:\",\n CDN_WORKER_URL,\n );\n return baseEnterRoom<T, P>({\n userId,\n worldId,\n room,\n host,\n autoRejoin,\n onOpen,\n onClose,\n onError,\n onPeerJoined,\n onPeerLeft,\n onIceUrl,\n onMessage,\n });\n }\n const worker = new Worker(workerUrl, { type: \"module\" });\n let exited = false;\n\n function makeUser({ userId }: { userId: string }): IPeer<T, P> {\n return {\n userId,\n receive: (type: T, payload: P) => {\n if (exited) return false;\n worker.postMessage({\n cmd: \"send\",\n toUserId: userId,\n host,\n room,\n type,\n payload,\n } as WorkerCommand);\n return true;\n },\n };\n }\n\n const onWorkerMessage = (e: MessageEvent<RoomEvent<T, P>>) => {\n const ev = e.data;\n\n if (ev.kind === \"open\") onOpen?.();\n else if (ev.kind === \"close\") {\n worker.terminate();\n onClose?.(ev.ev);\n } else if (ev.kind === \"error\") onError?.();\n else if (ev.kind === \"peer-joined\")\n onPeerJoined(ev.users.map((ev) => makeUser({ userId: ev.userId })));\n else if (ev.kind === \"peer-left\") onPeerLeft(ev.users);\n else if (ev.kind === \"ice-server\") onIceUrl?.(ev.url, ev.expiration);\n else if (ev.kind === \"message\")\n onMessage(ev.type, ev.payload, makeUser({ userId: ev.fromUserId }));\n else if (ev.kind === \"log\") logLine?.(ev.direction, ev.obj);\n };\n\n worker.addEventListener(\"message\", onWorkerMessage);\n\n worker.postMessage({\n cmd: \"enter\",\n userId,\n worldId,\n room,\n host,\n autoRejoin,\n } as WorkerCommand);\n\n return {\n exitRoom: () => {\n exited = true;\n worker.removeEventListener(\"message\", onWorkerMessage);\n worker.postMessage({ cmd: \"exit\" } as WorkerCommand);\n },\n sendToServer: <P extends any>(type: T, payload?: P) => {\n worker.postMessage({\n cmd: \"send\",\n toUserId: \"server\",\n host,\n room,\n type,\n payload,\n } as WorkerCommand);\n },\n };\n}\n\nexport type EnterRoom<T extends string, P> = typeof enterRoom<T, P>;\n",
7
- "import { IPeer } from \"./signal/impl/signal-room\";\nimport { EnterRoom, enterRoom } from \"./signal/signal-room\";\n\nexport type SigType = \"offer\" | \"answer\" | \"ice\" | \"request-ice\" | \"broadcast\";\nexport type SigPayload = RTCSessionDescriptionInit | RTCIceCandidateInit;\n\ntype UserState = {\n pc?: RTCPeerConnection;\n\n // ICE that arrived before we had remoteDescription\n pendingRemoteIce: RTCIceCandidateInit[];\n\n // the signaling \"user\" handle so we can send messages\n peer: IPeer<SigType, SigPayload>;\n\n expirationTimeout?: number;\n close(): void;\n reset(): void;\n};\n\nconst DEFAULT_ENTER_ROOM = enterRoom;\n\nexport function collectPeerConnections({\n userId: passedUserId,\n worldId,\n receivePeerConnection,\n peerlessUserExpiration = 5000,\n fallbackRtcConfig = {\n iceServers: [{ urls: \"stun:stun.l.google.com:19302\" }],\n },\n enterRoomFunction: enterRoom = DEFAULT_ENTER_ROOM,\n logLine,\n onLeaveUser,\n workerUrl,\n onRoomReady,\n onRoomClose,\n onBroadcastMessage,\n}: {\n userId?: string;\n worldId: string;\n fallbackRtcConfig?: RTCConfiguration;\n enterRoomFunction?: EnterRoom<SigType, SigPayload>;\n onLeaveUser?: (userId: string) => void;\n logLine?: (direction: string, obj?: any) => void;\n workerUrl?: URL;\n peerlessUserExpiration?: number;\n receivePeerConnection(connection: {\n pc: RTCPeerConnection;\n userId: string;\n restart?: () => void;\n }): void;\n onRoomReady?(info: { host: string; room: string }): void;\n onRoomClose?(info: {\n host: string;\n room: string;\n ev: Pick<CloseEvent, \"reason\" | \"code\" | \"wasClean\">;\n }): void;\n onBroadcastMessage?<P extends any>(payload: P, from: string): void;\n}) {\n const userId = passedUserId ?? `user-${crypto.randomUUID()}`;\n const users: Map<string, UserState> = new Map();\n let iceUrl: { url: string; expiration: number } | undefined = undefined;\n let rtcConfig: RTCConfiguration & { timestamp: number } = {\n ...fallbackRtcConfig,\n timestamp: Date.now(),\n };\n\n const roomsEntered = new Map<\n string,\n {\n room: string;\n host: string;\n exitRoom: () => void;\n broadcast: <P extends any>(payload: P) => void;\n }\n >();\n\n async function getRtcConfig(\n iceUrl: string,\n retryIce: () => Promise<{ url: string }>,\n ): Promise<RTCConfiguration & { timestamp: number }> {\n if (iceUrl) {\n let retries = 3;\n for (let r = 0; r < retries; r++) {\n try {\n const r = await fetch(iceUrl);\n if (!r.ok) throw new Error(`ICE endpoint failed: ${r.status}`);\n rtcConfig = (await r.json()) as RTCConfiguration & {\n timestamp: number;\n };\n return rtcConfig;\n } catch (e) {\n console.warn(\"Failed fetching iceUrl\");\n }\n iceUrl = (await retryIce()).url;\n }\n }\n return rtcConfig;\n }\n\n function leaveUser(userId: string) {\n onLeaveUser?.(userId);\n const p = users.get(userId);\n if (!p) return;\n users.delete(userId);\n try {\n p.close();\n } catch {}\n }\n\n async function flushRemoteIce(state: UserState) {\n if (!state.pc?.remoteDescription) return;\n\n const queued = state.pendingRemoteIce;\n state.pendingRemoteIce = [];\n\n for (const ice of queued) {\n try {\n await state.pc.addIceCandidate(ice);\n } catch (e) {\n logLine?.(\"⚠️ ERROR\", {\n error: \"add-ice-failed\",\n userId: state.peer.userId,\n detail: String(e),\n });\n }\n }\n }\n\n function exit({ room, host }: { room: string; host: string }) {\n const key = `${host}/room/${room}`;\n const session = roomsEntered.get(key);\n if (session) {\n session.exitRoom();\n roomsEntered.delete(key);\n }\n }\n\n function enter({ room, host }: { room: string; host: string }) {\n return new Promise<void>(async (resolve, reject) => {\n async function setupPC(state: UserState) {\n const now = Date.now();\n if (now - (rtcConfig?.timestamp ?? 0) > 10000) {\n const ice =\n !iceUrl || iceUrl.expiration - now < 2000\n ? await requestIce()\n : iceUrl;\n rtcConfig = await getRtcConfig(ice.url, requestIce);\n }\n state.pc = new RTCPeerConnection(rtcConfig);\n // Send local ICE candidates to this peer\n state.pc.onicecandidate = (ev) => {\n if (!ev.candidate) return;\n state.peer.receive(\"ice\", ev.candidate.toJSON());\n };\n\n state.pc.onconnectionstatechange = async () => {\n logLine?.(\"💬\", {\n event: \"pc-state\",\n userId: state.peer.userId,\n state: state.pc?.connectionState,\n });\n if (state.pc?.connectionState === \"failed\") {\n // reset the connection\n state.close();\n const userState = await getPeer(state.peer, true);\n if (userState.pc) {\n receivePeerConnection({\n pc: userState.pc,\n userId: userState.peer.userId,\n restart: () => userState.close(),\n });\n } else {\n logLine?.(\"👤ℹ️\", \"no pc: \" + userState.peer.userId);\n }\n return;\n }\n };\n\n return state.pc;\n }\n\n async function getPeer(\n peer: IPeer<SigType, SigPayload>,\n forceReset?: boolean,\n ): Promise<UserState> {\n let state = users.get(peer.userId);\n if (!state || forceReset) {\n const newState: UserState = {\n pendingRemoteIce: [],\n peer,\n close() {\n this.pc?.close();\n this.pc = undefined;\n users.delete(peer.userId);\n },\n async reset() {\n newState.close();\n const userState = await getPeer(peer, true);\n console.log(\">>\", userState.peer.userId);\n\n setTimeout(async () => {\n if (!userState.pc) {\n console.log(\"no pc\");\n return;\n }\n receivePeerConnection({\n pc: userState.pc,\n userId: userState.peer.userId,\n restart: () => userState.close(),\n });\n await makeOffer(userState.peer);\n }, 3000);\n },\n };\n\n await setupPC(newState);\n state = newState;\n\n // New user\n users.set(state.peer.userId, state);\n } else if (state) {\n clearTimeout(state.expirationTimeout);\n state.expirationTimeout = 0;\n if (!state.pc || state.pc.signalingState === \"closed\") {\n await setupPC(state);\n }\n }\n state.peer = peer;\n return state;\n }\n\n async function makeOffer(user: IPeer) {\n // Offer flow: createOffer -> setLocalDescription -> send localDescription\n const state = await getPeer(user);\n const pc = state.pc;\n const offer = await pc?.createOffer();\n await pc?.setLocalDescription(offer);\n user.receive(\"offer\", pc?.localDescription?.toJSON()!);\n }\n\n let icePromiseResolve:\n | undefined\n | ((url: { url: string; expiration: number }) => void);\n async function requestIce() {\n const iceUrl = await new Promise<{ url: string; expiration: number }>(\n (resolve) => {\n icePromiseResolve = resolve;\n sendToServer(\"request-ice\");\n },\n );\n icePromiseResolve = undefined;\n return iceUrl;\n }\n\n const { exitRoom, sendToServer } = enterRoom({\n userId,\n worldId,\n room,\n host,\n logLine,\n workerUrl,\n autoRejoin: true,\n\n onOpen() {\n onRoomReady?.({ room, host });\n resolve();\n },\n onError() {\n console.error(\"onError\");\n reject();\n },\n onClose(ev: Pick<CloseEvent, \"reason\" | \"code\" | \"wasClean\">) {\n onRoomClose?.({ room, host, ev });\n },\n\n // Existing peers initiate to the newcomer\n onPeerJoined(joiningUsers: IPeer<SigType, SigPayload>[]) {\n joiningUsers.forEach(async (user) => {\n const state = await getPeer(user, true);\n const pc = state.pc;\n if (!pc) {\n logLine?.(\"👤ℹ️\", \"no pc: \" + user.userId);\n return;\n }\n\n receivePeerConnection({\n pc,\n userId: user.userId,\n restart: () => state.close(),\n });\n await makeOffer(user);\n });\n },\n\n onPeerLeft(leavingUsers: { userId: string }[]) {\n leavingUsers.forEach(({ userId }) => {\n const state = users.get(userId);\n if (!state) return;\n state.expirationTimeout = setTimeout(\n () => leaveUser(userId),\n peerlessUserExpiration ?? 0,\n );\n });\n },\n\n onIceUrl(url: string, expiration: number) {\n iceUrl = { url, expiration };\n icePromiseResolve?.(iceUrl);\n },\n\n async onMessage(type: SigType, payload: any, from: IPeer) {\n const state = await getPeer(from);\n\n if (type === \"offer\") {\n state.close(); // need new PC\n const pc = await setupPC(state);\n\n receivePeerConnection({\n pc,\n userId: from.userId,\n restart: () => state.close(),\n });\n // Responder: set remote offer\n await pc.setRemoteDescription(payload as RTCSessionDescriptionInit);\n\n // Create and send answer\n const answer = await pc.createAnswer();\n await pc.setLocalDescription(answer);\n\n from.receive(\"answer\", pc.localDescription?.toJSON()!);\n\n // Now safe to apply any queued ICE from this peer\n await flushRemoteIce(state);\n return;\n }\n\n const pc = state.pc ?? (await setupPC(state));\n\n if (type === \"answer\") {\n // Initiator: set remote answer\n await pc.setRemoteDescription(payload as RTCSessionDescriptionInit);\n await flushRemoteIce(state);\n return;\n }\n\n if (type === \"ice\") {\n const ice = payload as RTCIceCandidateInit;\n\n // If we don't have remoteDescription yet, queue it\n if (!pc.remoteDescription) {\n state.pendingRemoteIce.push(ice);\n return;\n }\n\n try {\n await state.pc?.addIceCandidate(ice);\n } catch (e) {\n logLine?.(\"⚠️ ERROR\", {\n error: \"add-ice-failed\",\n userId: state.peer.userId,\n detail: String(e),\n });\n }\n return;\n }\n\n if (type === \"broadcast\") {\n onBroadcastMessage?.(payload, from.userId);\n }\n },\n });\n roomsEntered.set(`${host}/room/${room}`, {\n exitRoom,\n room,\n host,\n broadcast: (payload) => sendToServer(\"broadcast\", payload),\n });\n });\n }\n\n return {\n userId,\n enterRoom: enter,\n exitRoom: exit,\n leaveUser,\n async reset(userId: string) {\n const userState = users.get(userId);\n userState?.reset();\n },\n broadcast<P extends any>(payload: P) {\n roomsEntered.forEach((room) => room.broadcast(payload));\n },\n end() {\n roomsEntered.forEach(({ exitRoom }) => exitRoom());\n roomsEntered.clear();\n users.forEach(({ peer }) => leaveUser(peer.userId));\n users.clear();\n },\n };\n}\n\n/*\nTurn Token ID\n<CF_TURN_TOKEN_ID>\n\nAPI Token\n<CF_RTC_API_TOKEN>\n\nCURL\ncurl \\\n\t-H \"Authorization: Bearer <CF_RTC_API_TOKEN>\" \\\n\t-H \"Content-Type: application/json\" -d '{\"ttl\": 86400}' \\\n\thttps://rtc.live.cloudflare.com/v1/turn/keys/<CF_TURN_TOKEN_ID>/credentials/generate-ice-servers\n\nJSON\n{\n\t\"iceServers\": [\n {\n \"urls\": [\n \"stun:stun.cloudflare.com:3478\",\n \"turn:turn.cloudflare.com:3478?transport=udp\",\n \"turn:turn.cloudflare.com:3478?transport=tcp\",\n \"turns:turn.cloudflare.com:5349?transport=tcp\"\n ],\n \"username\": \"xxxx\",\n \"credential\": \"yyyy\",\n }\n ]\n}\n\n*/\n"
8
+ "import { IceUrlProvider } from \"./ice-url-provider\";\n\nconst FALLBACK_RTC_CONFIG = {\n iceServers: [{ urls: \"stun:stun.l.google.com:19302\" }],\n};\n\nexport class RTCConfigProvider {\n constructor(private iceUrlProvider: IceUrlProvider) {}\n\n private rtcConfig: RTCConfiguration & { timestamp: number } = {\n ...FALLBACK_RTC_CONFIG,\n timestamp: Date.now(),\n };\n private rtcConfigPromise?: Promise<RTCConfiguration & { timestamp: number }>;\n\n async getRtcConfig(): Promise<RTCConfiguration & { timestamp: number }> {\n const now = Date.now();\n if (now - (this.rtcConfig?.timestamp ?? 0) < 10000) {\n return this.rtcConfig;\n }\n\n if (!this.rtcConfigPromise) {\n this.rtcConfigPromise = new Promise<\n RTCConfiguration & { timestamp: number }\n >(async (resolve) => {\n let retries = 3;\n for (let r = 0; r < retries; r++) {\n try {\n const iceUrl = (await this.iceUrlProvider.requestIce()).url;\n const r = await fetch(iceUrl);\n if (!r.ok) throw new Error(`ICE endpoint failed: ${r.status}`);\n const rtcConfig = (await r.json()) as RTCConfiguration & {\n timestamp: number;\n };\n resolve(rtcConfig);\n return;\n } catch (e) {\n console.warn(\"Failed fetching iceUrl\");\n }\n }\n });\n this.rtcConfig = await this.rtcConfigPromise;\n this.rtcConfigPromise = undefined;\n }\n return this.rtcConfig;\n }\n}\n",
9
+ "import { IceUrlProvider } from \"./utils/ice-url-provider\";\nimport { IPeer } from \"./signal/impl/signal-room\";\nimport { EnterRoom, enterRoom } from \"./signal/signal-room\";\nimport { RTCConfigProvider } from \"./utils/rtc-config\";\n\nexport type SigType = \"offer\" | \"answer\" | \"ice\" | \"request-ice\" | \"broadcast\";\nexport type SigPayload = {\n connectionId?: string;\n offer?: RTCSessionDescriptionInit;\n answer?: RTCSessionDescriptionInit;\n ice?: RTCIceCandidateInit;\n} & Record<string, any>;\n\ninterface Connection {\n id: string;\n peerConnectionId?: string;\n pc: RTCPeerConnection;\n // ICE that arrived before we had remoteDescription\n pendingRemoteIce: RTCIceCandidateInit[];\n}\n\ntype UserState = {\n connection?: Connection;\n\n // the signaling \"user\" handle so we can send messages\n peer: IPeer<SigType, SigPayload>;\n\n expirationTimeout?: number;\n close(): void;\n reset(): void;\n connectionPromise?: Promise<Connection>;\n};\n\nconst DEFAULT_ENTER_ROOM = enterRoom;\n\n/**\n * Collect peers\n */\nexport function collectPeerConnections({\n userId: passedUserId,\n worldId,\n receivePeerConnection,\n peerlessUserExpiration = 5000,\n enterRoomFunction: enterRoom = DEFAULT_ENTER_ROOM,\n logLine,\n onLeaveUser,\n workerUrl,\n onRoomReady,\n onRoomClose,\n onBroadcastMessage,\n}: {\n userId?: string;\n worldId: string;\n enterRoomFunction?: EnterRoom<SigType, SigPayload>;\n onLeaveUser?: (userId: string) => void;\n logLine?: (direction: string, obj?: any) => void;\n workerUrl?: URL;\n peerlessUserExpiration?: number;\n receivePeerConnection(connection: {\n pc: RTCPeerConnection;\n userId: string;\n restart?: () => void;\n }): void;\n onRoomReady?(info: { host: string; room: string }): void;\n onRoomClose?(info: {\n host: string;\n room: string;\n ev: Pick<CloseEvent, \"reason\" | \"code\" | \"wasClean\">;\n }): void;\n onBroadcastMessage?<P extends any>(payload: P, from: string): void;\n}) {\n const userId = passedUserId ?? `user-${crypto.randomUUID()}`;\n const users: Map<string, UserState> = new Map();\n\n const roomsEntered = new Map<\n string,\n {\n room: string;\n host: string;\n exitRoom: () => void;\n broadcast: <P extends any>(payload: P) => void;\n }\n >();\n\n function leaveUser(userId: string) {\n onLeaveUser?.(userId);\n const p = users.get(userId);\n if (!p) return;\n users.delete(userId);\n try {\n p.close();\n } catch {}\n }\n\n async function flushRemoteIce(state: UserState) {\n if (!state.connection?.pc?.remoteDescription) return;\n\n const queued = state.connection.pendingRemoteIce;\n state.connection.pendingRemoteIce = [];\n\n for (const ice of queued) {\n try {\n await state.connection.pc.addIceCandidate(ice);\n } catch (e) {\n logLine?.(\"⚠️ ERROR\", {\n error: \"add-ice-failed\",\n userId: state.peer.userId,\n detail: String(e),\n });\n }\n }\n }\n\n const iceUrlProvider = new IceUrlProvider();\n const rtcConfigProvider = new RTCConfigProvider(iceUrlProvider);\n\n function exit({ room, host }: { room: string; host: string }) {\n const key = `${host}/room/${room}`;\n const session = roomsEntered.get(key);\n if (session) {\n session.exitRoom();\n roomsEntered.delete(key);\n }\n }\n\n function enter({ room, host }: { room: string; host: string }) {\n return new Promise<void>(async (resolve, reject) => {\n async function setupConnection(state: UserState) {\n if (state.connectionPromise) {\n return state.connectionPromise;\n }\n return (state.connectionPromise = new Promise<Connection>(\n async (resolve) => {\n state.connection = {\n id: `conn-${crypto.randomUUID()}`,\n pc: new RTCPeerConnection(await rtcConfigProvider.getRtcConfig()),\n pendingRemoteIce: [],\n };\n\n // Send local ICE candidates to this peer\n state.connection.pc.onicecandidate = (ev) => {\n if (!ev.candidate) return;\n state.peer.receive(\"ice\", {\n connectionId: state.connection?.id,\n ice: ev.candidate.toJSON(),\n });\n };\n\n state.connection.pc.onconnectionstatechange = async () => {\n logLine?.(\"💬\", {\n event: \"pc-state\",\n userId: state.peer.userId,\n state: state.connection?.pc?.connectionState,\n });\n if (state.connection?.pc?.connectionState === \"failed\") {\n // reset the connection\n state.close();\n const userState = await getPeer(state.peer, true);\n if (userState.connection?.pc) {\n receivePeerConnection({\n pc: userState.connection?.pc,\n userId: userState.peer.userId,\n restart: () => userState.close(),\n });\n } else {\n logLine?.(\"👤ℹ️\", \"no pc: \" + userState.peer.userId);\n }\n return;\n }\n };\n\n resolve(state.connection);\n },\n ));\n }\n\n async function getPeer(\n peer: IPeer<SigType, SigPayload>,\n forceReset?: boolean,\n ): Promise<UserState> {\n let state = users.get(peer.userId);\n if (!state || forceReset) {\n const newState: UserState = {\n peer,\n close() {\n if (this.connection) {\n this.connection.pc.close();\n this.connection = undefined;\n }\n users.delete(peer.userId);\n },\n async reset() {\n newState.close();\n\n setTimeout(async () => {\n const userState = await getPeer(peer, true);\n if (!userState.connection?.pc) {\n logLine?.(\"⚠️\", \"no pc\");\n return;\n }\n receivePeerConnection({\n pc: userState.connection?.pc,\n userId: userState.peer.userId,\n restart: () => userState.close(),\n });\n await makeOffer(userState.peer);\n }, 500);\n },\n };\n state = newState;\n\n await setupConnection(newState);\n\n // New user\n users.set(state.peer.userId, state);\n } else if (state) {\n clearTimeout(state.expirationTimeout);\n state.expirationTimeout = 0;\n if (\n !state.connection?.pc ||\n state.connection?.pc.signalingState === \"closed\"\n ) {\n await setupConnection(state);\n }\n }\n state.peer = peer;\n return state;\n }\n\n async function makeOffer(user: IPeer<SigType, SigPayload>) {\n // Offer flow: createOffer -> setLocalDescription -> send localDescription\n const state = await getPeer(user);\n const pc = state.connection?.pc;\n const offer = await pc?.createOffer();\n await pc?.setLocalDescription(offer);\n user.receive(\"offer\", {\n connectionId: state.connection?.id,\n offer: pc!.localDescription!.toJSON(),\n });\n }\n\n const { exitRoom, sendToServer } = enterRoom({\n userId,\n worldId,\n room,\n host,\n logLine,\n workerUrl,\n autoRejoin: true,\n\n onOpen() {\n onRoomReady?.({ room, host });\n resolve();\n },\n onError() {\n console.error(\"onError\");\n reject();\n },\n onClose(ev: Pick<CloseEvent, \"reason\" | \"code\" | \"wasClean\">) {\n onRoomClose?.({ room, host, ev });\n },\n\n // Existing peers initiate to the newcomer\n onPeerJoined(joiningUsers: IPeer<SigType, SigPayload>[]) {\n joiningUsers.forEach(async (user) => {\n const state = await getPeer(user, true);\n const pc = state.connection?.pc;\n if (!pc) {\n logLine?.(\"👤ℹ️\", \"no pc: \" + user.userId);\n return;\n }\n\n receivePeerConnection({\n pc,\n userId: user.userId,\n restart: () => state.close(),\n });\n await makeOffer(user);\n });\n },\n\n onPeerLeft(leavingUsers: { userId: string }[]) {\n leavingUsers.forEach(({ userId }) => {\n const state = users.get(userId);\n if (!state) return;\n state.expirationTimeout = setTimeout(\n () => leaveUser(userId),\n peerlessUserExpiration ?? 0,\n );\n });\n },\n\n onIceUrl(url: string, expiration: number) {\n iceUrlProvider.receiveIce(url, expiration);\n },\n\n async onMessage(type, payload, from: IPeer<SigType, SigPayload>) {\n if (type === \"offer\" && payload.offer) {\n // Grab state and connection\n const state = await getPeer(from, false);\n const connection =\n !state.connection ||\n state.connection.pc.signalingState === \"stable\"\n ? await setupConnection(state)\n : state.connection; // reset\n logLine?.(\"💬\", {\n type,\n signalingState: connection.pc.signalingState,\n });\n\n connection.peerConnectionId = payload.connectionId;\n receivePeerConnection({\n pc: connection.pc,\n userId: from.userId,\n restart: () => state.close(),\n });\n // Responder: set remote offer\n await connection.pc.setRemoteDescription(payload.offer);\n\n // Create and send answer\n const answer = await connection.pc.createAnswer();\n await connection.pc.setLocalDescription(answer);\n\n from.receive(\"answer\", {\n connectionId: connection.id,\n answer: connection.pc.localDescription?.toJSON(),\n });\n\n // Now safe to apply any queued ICE from this peer\n await flushRemoteIce(state);\n return;\n }\n\n if (type === \"answer\" && payload.answer) {\n const state = await getPeer(from, false);\n const connection =\n state.connection &&\n state.connection.pc.signalingState !== \"closed\"\n ? state.connection\n : await setupConnection(state);\n logLine?.(\"💬\", {\n type,\n signalingState: connection.pc.signalingState,\n });\n\n // Initiator: set remote answer\n await connection.pc.setRemoteDescription(payload.answer);\n connection.peerConnectionId = payload.connectionId;\n await flushRemoteIce(state);\n return;\n }\n\n if (type === \"ice\" && payload.ice) {\n // Grab state and connection\n const state = await getPeer(from, false);\n const connection =\n state.connection ?? (await state.connectionPromise);\n if (!connection) {\n logLine?.(\"⚠️\", \"No connection\");\n return;\n }\n logLine?.(\"💬\", {\n type,\n signalingState: connection.pc.signalingState,\n });\n\n if (\n connection.peerConnectionId &&\n payload.connectionId !== connection.peerConnectionId\n ) {\n logLine?.(\n \"⚠️\",\n \"Mismatch peerConnectionID\" +\n payload.connectionId +\n \"vs\" +\n connection.peerConnectionId,\n );\n return;\n }\n\n // If we don't have remoteDescription yet (or if connectionId doesn't match), queue it\n if (\n !connection.pc.remoteDescription ||\n !connection.peerConnectionId\n ) {\n connection.peerConnectionId = payload.connectionId;\n connection.pendingRemoteIce.push(payload.ice);\n return;\n }\n\n try {\n await connection.pc.addIceCandidate(payload.ice);\n } catch (e) {\n logLine?.(\"⚠️ ERROR\", {\n error: \"add-ice-failed\",\n userId: state.peer.userId,\n detail: String(e),\n });\n }\n return;\n }\n\n if (type === \"broadcast\") {\n onBroadcastMessage?.(payload, from.userId);\n }\n },\n });\n\n iceUrlProvider.addRequester(sendToServer);\n\n roomsEntered.set(`${host}/room/${room}`, {\n exitRoom: () => {\n exitRoom();\n iceUrlProvider.removeRequester(sendToServer);\n },\n room,\n host,\n broadcast: (payload) => sendToServer(\"broadcast\", payload),\n });\n });\n }\n\n return {\n userId,\n enterRoom: enter,\n exitRoom: exit,\n leaveUser,\n async reset(userId: string) {\n const userState = users.get(userId);\n userState?.reset();\n },\n broadcast<P extends any>(payload: P) {\n roomsEntered.forEach((room) => room.broadcast(payload));\n },\n end() {\n roomsEntered.forEach(({ exitRoom }) => exitRoom());\n roomsEntered.clear();\n users.forEach(({ peer }) => leaveUser(peer.userId));\n users.clear();\n },\n };\n}\n\n/*\nTurn Token ID\n<CF_TURN_TOKEN_ID>\n\nAPI Token\n<CF_RTC_API_TOKEN>\n\nCURL\ncurl \\\n\t-H \"Authorization: Bearer <CF_RTC_API_TOKEN>\" \\\n\t-H \"Content-Type: application/json\" -d '{\"ttl\": 86400}' \\\n\thttps://rtc.live.cloudflare.com/v1/turn/keys/<CF_TURN_TOKEN_ID>/credentials/generate-ice-servers\n\nJSON\n{\n\t\"iceServers\": [\n {\n \"urls\": [\n \"stun:stun.cloudflare.com:3478\",\n \"turn:turn.cloudflare.com:3478?transport=udp\",\n \"turn:turn.cloudflare.com:3478?transport=tcp\",\n \"turns:turn.cloudflare.com:5349?transport=tcp\"\n ],\n \"username\": \"xxxx\",\n \"credential\": \"yyyy\",\n }\n ]\n}\n\n*/\n"
8
10
  ],
9
- "mappings": "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",
10
- "debugId": "C1B79A8ADB47518264756E2164756E21",
11
+ "mappings": "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",
12
+ "debugId": "67E8B3D0D9449E2464756E2164756E21",
11
13
  "names": []
12
14
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dobuki/hello-worker",
3
- "version": "1.0.77",
3
+ "version": "1.0.79",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",