@dobuki/hello-worker 1.0.80 → 1.0.81

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
- 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,f,B){if(!G)return Z?.("\uD83D\uDC64 ➡️ ❌","no ws available"),!1;let F={type:b,to:f,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 f=JSON.parse(b.data);(Array.isArray(f)?f:[f]).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,f){let B=[],F=[],z=new Set;b.forEach(({userId:A})=>{if(A===H)return;if(!$.has(A)||f.type==="peer-joined"&&A===f.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)||f.type==="peer-left"&&A===f.userId)$.delete(A),F.push({userId:A});if(B.length)T.onPeerJoined(B);if(F.length)T.onPeerLeft(F)}return O(),{sendToServer(b,f){W(b,"server",f)},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,f)=>{if(N)return!1;return X.postMessage({cmd:"send",toUserId:M,host:S,room:Q,type:b,payload:f}),!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((f)=>W({userId:f.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 u=j;function g({userId:T,worldId:H,receivePeerConnection:Q,peerlessUserExpiration:S=5000,enterRoomFunction:J=u,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(f){Z?.(f);let B=$.get(f);if(!B)return;$.delete(f);try{B.close()}catch{}}async function N(f){if(!f.connection?.pc?.remoteDescription)return;let B=f.connection.pendingRemoteIce;f.connection.pendingRemoteIce=[];for(let F of B)try{await f.connection.pc.addIceCandidate(F)}catch(z){V?.("⚠️ ERROR",{error:"add-ice-failed",userId:f.peer.userId,detail:String(z)})}}let W=new k,O=new n(W);function M({room:f,host:B}){let F=`${B}/room/${f}`,z=K.get(F);if(z)z.exitRoom(),K.delete(F)}function b({room:f,host:B}){return new Promise(async(F,z)=>{async function A(i){if(i.connectionPromise)return i.connectionPromise;let C=new Promise(async(D)=>{i.connection={id:`conn-${crypto.randomUUID()}`,pc:new RTCPeerConnection(await O.getRtcConfig()),pendingRemoteIce:[]},i.connection.pc.onicecandidate=(q)=>{if(!q.candidate)return;i.peer.receive("ice",{connectionId:i.connection?.id,ice:q.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 q=await c(i.peer,!0);if(q.connection?.pc)Q({pc:q.connection?.pc,userId:q.peer.userId,restart:()=>q.close()});else V?.("\uD83D\uDC64ℹ️","no pc: "+q.peer.userId);return}},D(i.connection)});return i.connectionPromise=C,await C,i.connectionPromise=void 0,C}async function c(i,C){let D=$.get(i.userId);if(!D||C){let q={peer:i,close(){if(this.connection)this.connection.pc.close(),this.connection=void 0;$.delete(i.userId)},async reset(){q.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)}};D=q,await A(q),$.set(D.peer.userId,D)}else if(D){if(clearTimeout(D.expirationTimeout),D.expirationTimeout=0,!D.connection?.pc||D.connection?.pc.signalingState==="closed")await A(D)}return D.peer=i,D}async function v(i){let C=await c(i),D=C.connection?.pc,q=await D?.createOffer();await D?.setLocalDescription(q),i.receive("offer",{connectionId:C.connection?.id,offer:D.localDescription.toJSON()})}let{exitRoom:w,sendToServer:h}=J({userId:E,worldId:H,room:f,host:B,logLine:V,workerUrl:P,autoRejoin:!0,onOpen(){x?.({room:f,host:B}),F()},onError(){console.error("onError"),z()},onClose(i){G?.({room:f,host:B,ev:i})},onPeerJoined(i){i.forEach(async(C)=>{let D=await c(C,!0),q=D.connection?.pc;if(!q){V?.("\uD83D\uDC64ℹ️","no pc: "+C.userId);return}Q({pc:q,userId:C.userId,restart:()=>D.close()}),await v(C)})},onPeerLeft(i){i.forEach(({userId:C})=>{let D=$.get(C);if(!D)return;D.expirationTimeout=setTimeout(()=>X(C),S??0)})},onIceUrl(i,C){W.receiveIce(i,C)},async onMessage(i,C,D){if(i==="offer"&&C.offer){let q=await c(D,!1),R=!q.connection||q.connection.pc.signalingState==="stable"?await A(q):q.connection;V?.("\uD83D\uDCAC",{type:i,signalingState:R.pc.signalingState}),R.peerConnectionId=C.connectionId,Q({pc:R.pc,userId:D.userId,restart:()=>q.close()}),await R.pc.setRemoteDescription(C.offer);let _=await R.pc.createAnswer();await R.pc.setLocalDescription(_),D.receive("answer",{connectionId:R.id,answer:R.pc.localDescription?.toJSON()}),await N(q);return}if(i==="answer"&&C.answer){let q=await c(D,!1),R=q.connection&&q.connection.pc.signalingState!=="closed"?q.connection:await A(q);V?.("\uD83D\uDCAC",{type:i,signalingState:R.pc.signalingState}),await R.pc.setRemoteDescription(C.answer),R.peerConnectionId=C.connectionId,await N(q);return}if(i==="ice"&&C.ice){let q=await c(D,!1),R=q.connection??await q.connectionPromise;if(!R){V?.("⚠️","No connection");return}if(V?.("\uD83D\uDCAC",{type:i,signalingState:R.pc.signalingState}),R.peerConnectionId&&C.connectionId!==R.peerConnectionId){V?.("⚠️","Mismatch peerConnectionID"+C.connectionId+"vs"+R.peerConnectionId);return}if(!R.pc.remoteDescription||!R.peerConnectionId){R.peerConnectionId=C.connectionId,R.pendingRemoteIce.push(C.ice);return}try{await R.pc.addIceCandidate(C.ice)}catch(_){V?.("⚠️ ERROR",{error:"add-ice-failed",userId:q.peer.userId,detail:String(_)})}return}if(i==="broadcast")Y?.(C,D.userId)}});W.addRequester(h),K.set(`${B}/room/${f}`,{exitRoom:()=>{w(),W.removeRequester(h)},room:f,host:B,broadcast:(i)=>h("broadcast",i)})})}return{userId:E,enterRoom:b,exitRoom:M,leaveUser:X,async reset(f){$.get(f)?.reset()},broadcast(f){K.forEach((B)=>B.broadcast(f))},end(){K.forEach(({exitRoom:f})=>f()),K.clear(),$.forEach(({peer:f})=>X(f.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(h,i,C){function D(R){let _=R.channel;N(i,_,C),Y.set(i,_)}h.addEventListener("datachannel",D);let q=h.createDataChannel("data",x);return N(i,q,C),Y.set(i,q),()=>{h.removeEventListener("datachannel",D)}}function X(h,i){$.forEach((C)=>C(h,i))}function N(h,i,C){i.onopen=()=>{Q?.("\uD83D\uDCAC",{event:"dc-open",userId:h}),G.add(h),E.forEach((q)=>q(h,"join",[...G]))};let D=({data:q})=>{X(q,h)};i.addEventListener("message",D),i.onclose=()=>{Q?.("\uD83D\uDCAC",{event:"dc-close",userId:h}),G.delete(h),E.forEach((q)=>q(h,"leave",[...G])),i.removeEventListener("message",D),i.onopen=null,i.onclose=null,i.onerror=null,i.close(),C?.()},i.onerror=()=>Q?.("⚠️ ERROR",{error:"dc-error",userId:h})}let{userId:W,enterRoom:O,exitRoom:M,leaveUser:b,broadcast:f,end:B,reset:F}=g({userId:T,worldId:H,enterRoomFunction:S,logLine:Q,workerUrl:V,peerlessUserExpiration:J,onRoomReady:Z,onRoomClose:P,onLeaveUser(h){let i=Y.get(h);try{i?.close()}catch{}Y.delete(h)},receivePeerConnection({pc:h,userId:i,restart:C}){K(h,i,C)},onBroadcastMessage(h,i){X(h,i),Q?.("\uD83D\uDCE2",{event:"broadcast",userId:W,data:h})}});function z(h,i){Y.forEach((C,D)=>{if(i&&D!==i)return;if(C.readyState==="open")C.send(h)})}function A(h){$.delete(h)}function c(h){return $.add(h),()=>{A(h)}}function v(h){E.delete(h)}function w(h){return E.add(h),()=>{v(h)}}return{userId:W,send:z,broadcast:f,enterRoom:O,exitRoom:M,leaveUser:b,getUsers:()=>[...G],addMessageListener:c,removeMessageListener:A,addUserListener:w,removeUserListener:v,reset(){G.forEach((h)=>{Y.get(h)?.close(),Y.delete(h),F(h)})},end(){Y.forEach((h)=>{try{h.close()}catch{}}),Y.clear(),B(),E.clear(),G.clear()}}}export{Ci as enterWorld};
1
+ class L{sendToServerFunctions=[];icePromiseResolve;icePromise;receiveIce(T,W){this.icePromiseResolve?.({url:T,expiration:W}),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 g(T){let{userId:W,worldId:X,room:c,host:x,autoRejoin:G=!0,logLine:z}=T,P=!1,N=0,S,Y,E=!0,$=new Map,H=`wss://${x}/room/${X}/${c}?userId=${encodeURIComponent(W)}`,Z=[],_=0;function O(V,C,R){if(!S)return z?.("\uD83D\uDC64 ➡️ ❌","no ws available"),!1;let F={type:V,to:C,payload:R};if(Z.push(F),z?.("\uD83D\uDC64 ➡️ \uD83D\uDDA5️",F),clearTimeout(_),P||S.readyState!==WebSocket.OPEN)return z?.("\uD83D\uDC64 ➡️ ❌","Not in opened state: "+S.readyState),!1;return _=setTimeout(()=>{S.send(JSON.stringify(Z)),Z.length=0}),!0}function J(){if(P)return;S=new WebSocket(H),S.onopen=()=>{if(E)T.onOpen?.(),E=!1;N=0},S.onmessage=(V)=>{try{let C=JSON.parse(V.data);(Array.isArray(C)?C:[C]).forEach((F)=>{if(z?.("\uD83D\uDDA5️ ➡️ \uD83D\uDC64",F),F.type==="peer-joined"||F.type==="peer-left")B(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,F.userId)})}catch{z?.("⚠️ ERROR",{error:"invalid-json"})}},S.onclose=(V)=>{let R=[1001,1006,1011,1012,1013].includes(V.code);if(G&&!P&&R){let F=Math.min(Math.pow(2,N)*1000,15000),K=Math.random()*1000,M=F+K;z?.("\uD83D\uDD04 RECONNECTING",{attempt:N+1,delayMs:Math.round(M)}),N++,Y=setTimeout(J,M)}else T.onClose?.({code:V.code,reason:V.reason,wasClean:V.wasClean})},S.onerror=(V)=>{console.error("WS Error",V),T.onError?.()}}function B(V,C){let R=[],F=[],K=new Set,M=V.filter((Q)=>Q.userId===W)[0];if(!M){z?.("⚠️","Cannot find self in updated users");return}let v=M.joined;V.forEach(({userId:Q,joined:w})=>{if(Q===W)return;if(!$.has(Q)||C.type==="peer-joined"&&Q===C.userId){let h={userId:Q,joined:w};$.set(Q,h),R.push(h)}K.add(Q)});for(let Q of $.keys())if(!K.has(Q)||C.type==="peer-left"&&Q===C.userId)$.delete(Q),F.push({userId:Q});if(R.length)T.onPeerJoined(R,v);if(F.length)T.onPeerLeft(F)}return J(),{send(V,C,R){O(V,C,R)},exitRoom:()=>{P=!0,clearTimeout(Y),S.close()}}}function j({userId:T,worldId:W,room:X,host:c,autoRejoin:x=!0,onOpen:G,onClose:z,onError:P,onPeerJoined:N,onPeerLeft:S,onIceUrl:Y,onMessage:E,logLine:$,workerUrl:H}){if(!H)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:T,worldId:W,room:X,host:c,autoRejoin:x,onOpen:G,onClose:z,onError:P,onPeerJoined:N,onPeerLeft:S,onIceUrl:Y,onMessage:E});let Z=new Worker(H,{type:"module"}),_=!1,O=(J)=>{let B=J.data;if(B.kind==="open")G?.();else if(B.kind==="close")Z.terminate(),z?.(B.ev);else if(B.kind==="error")P?.();else if(B.kind==="peer-joined")N(B.users.map((V)=>({userId:V.userId,joined:V.joined})),B.joined);else if(B.kind==="peer-left")S(B.users);else if(B.kind==="ice-server")Y?.(B.url,B.expiration);else if(B.kind==="message")E(B.type,B.payload,B.fromUserId);else if(B.kind==="log")$?.(B.direction,B.obj)};return Z.addEventListener("message",O),Z.postMessage({cmd:"enter",userId:T,worldId:W,room:X,host:c,autoRejoin:x}),{exitRoom:()=>{_=!0,Z.removeEventListener("message",O),Z.postMessage({cmd:"exit"})},send:(J,B,V)=>{Z.postMessage({cmd:"send",toUserId:B,host:c,room:X,type:J,payload:V})}}}var m={iceServers:[{urls:"stun:stun.l.google.com:19302"}]};class n{iceUrlProvider;constructor(T){this.iceUrlProvider=T}rtcConfig={...m,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(W)=>{let X=3;for(let c=0;c<X;c++)try{let x=(await this.iceUrlProvider.requestIce()).url,G=await fetch(x);if(!G.ok)throw Error(`ICE endpoint failed: ${G.status}`);let z=await G.json();W(z);return}catch(x){console.warn("Failed fetching iceUrl")}}),this.rtcConfig=await this.rtcConfigPromise,this.rtcConfigPromise=void 0;return this.rtcConfig}}var y=j;function U({userId:T,worldId:W,receivePeerConnection:X,peerlessUserExpiration:c=5000,enterRoomFunction:x=y,logLine:G,onLeaveUser:z,workerUrl:P,onRoomReady:N,onRoomClose:S,onBroadcastMessage:Y}){let E=T??`user-${crypto.randomUUID()}`,$=new Map,H=new Map;function Z(C){z?.(C);let R=$.get(C);if(!R)return;$.delete(C);try{R.close()}catch{}}async function _(C){if(!C.connection?.pc?.remoteDescription)return;let R=C.connection.pendingRemoteIce;C.connection.pendingRemoteIce=[];for(let F of R)try{await C.connection.pc.addIceCandidate(F)}catch(K){G?.("⚠️ ERROR",{error:"add-ice-failed",userId:C.peer,detail:String(K)})}}let O=new L,J=new n(O);function B({room:C,host:R}){let F=`${R}/room/${C}`,K=H.get(F);if(K)K.exitRoom(),H.delete(F)}function V({room:C,host:R}){return new Promise(async(F,K)=>{async function M(i){if(i.connectionPromise)return i.connectionPromise;let b=new Promise(async(f)=>{i.connection={id:`conn-${crypto.randomUUID()}`,pc:new RTCPeerConnection(await J.getRtcConfig()),pendingRemoteIce:[]},i.connection.pc.onicecandidate=(q)=>{if(!q.candidate)return;h("ice",i.peer,{connectionId:i.connection?.id,ice:q.candidate.toJSON()})},i.connection.pc.onconnectionstatechange=async()=>{if(G?.("\uD83D\uDCAC",{event:"pc-state",userId:i.peer,state:i.connection?.pc?.connectionState}),i.connection?.pc?.connectionState==="failed"){i.close();let q=await v(i.peer,!0);if(q.connection?.pc)X({pc:q.connection?.pc,userId:q.peer,restart:()=>q.close()});else G?.("\uD83D\uDC64ℹ️","no pc: "+q.peer);return}},f(i.connection)});return i.connectionPromise=b,await b,i.connectionPromise=void 0,b}async function v(i,b){let f=$.get(i);if(!f||b){let q={peer:i,close(){if(this.connection)this.connection.pc.close(),this.connection=void 0;$.delete(i)},async reset(){q.close(),setTimeout(async()=>{let D=await v(i,!0);if(!D.connection?.pc){G?.("⚠️","no pc");return}X({pc:D.connection?.pc,userId:D.peer,restart:()=>D.close()}),await Q(D.peer)},500)}};f=q,await M(q),$.set(f.peer,f)}else if(f){if(clearTimeout(f.expirationTimeout),f.expirationTimeout=0,!f.connection?.pc||f.connection?.pc.signalingState==="closed")await M(f)}return f.peer=i,f}async function Q(i){let b=await v(i),f=b.connection?.pc,q=await f?.createOffer();await f?.setLocalDescription(q),h("offer",i,{connectionId:b.connection?.id,offer:f?.localDescription?.toJSON()})}let{exitRoom:w,send:h}=x({userId:E,worldId:W,room:C,host:R,logLine:G,workerUrl:P,autoRejoin:!0,onOpen(){N?.({room:C,host:R}),F()},onError(){console.error("onError"),K()},onClose(i){S?.({room:C,host:R,ev:i})},onPeerJoined(i,b){i.forEach(async(f)=>{let q=await v(f.userId,!0);q.joined=f.joined;let D=q.connection?.pc;if(!D){G?.("\uD83D\uDC64ℹ️","no pc: "+f.userId);return}if(X({pc:D,userId:f.userId,restart:()=>q.close()}),f.joined>b||f.joined===b&&f.userId.localeCompare(E)>0)await Q(f.userId)})},onPeerLeft(i){i.forEach(({userId:b})=>{let f=$.get(b);if(!f)return;f.expirationTimeout=setTimeout(()=>Z(b),c??0)})},onIceUrl(i,b){O.receiveIce(i,b)},async onMessage(i,b,f){if(i==="offer"&&b.offer){let q=await v(f,!1),D=!q.connection||q.connection.pc.signalingState==="stable"?await M(q):q.connection;G?.("\uD83D\uDCAC",{type:i,signalingState:D.pc.signalingState}),D.peerConnectionId=b.connectionId,X({pc:D.pc,userId:f,restart:()=>q.close()}),await D.pc.setRemoteDescription(b.offer);let k=await D.pc.createAnswer();await D.pc.setLocalDescription(k),h("answer",f,{connectionId:D.id,answer:D.pc.localDescription?.toJSON()}),await _(q);return}if(i==="answer"&&b.answer){let q=await v(f,!1),D=q.connection&&q.connection.pc.signalingState!=="closed"?q.connection:await M(q);G?.("\uD83D\uDCAC",{type:i,signalingState:D.pc.signalingState}),await D.pc.setRemoteDescription(b.answer),D.peerConnectionId=b.connectionId,await _(q);return}if(i==="ice"&&b.ice){let q=await v(f,!1),D=q.connection??await q.connectionPromise;if(!D){G?.("⚠️","No connection");return}if(G?.("\uD83D\uDCAC",{type:i,signalingState:D.pc.signalingState}),D.peerConnectionId&&b.connectionId!==D.peerConnectionId){G?.("⚠️","Mismatch peerConnectionID"+b.connectionId+"vs"+D.peerConnectionId);return}if(!D.pc.remoteDescription||!D.peerConnectionId){D.peerConnectionId=b.connectionId,D.pendingRemoteIce.push(b.ice);return}try{await D.pc.addIceCandidate(b.ice)}catch(k){G?.("⚠️ ERROR",{error:"add-ice-failed",userId:q.peer,detail:String(k)})}return}if(i==="broadcast")Y?.(b,f)}}),A=O.addRequester((i)=>h(i,"server"));H.set(`${R}/room/${C}`,{exitRoom:()=>{w(),A()},room:C,host:R,broadcast:(i)=>h("broadcast","server",i)})})}return{userId:E,enterRoom:V,exitRoom:B,leaveUser:Z,async reset(C){$.get(C)?.reset()},broadcast(C){H.forEach((R)=>R.broadcast(C))},end(){H.forEach(({exitRoom:C})=>C()),H.clear(),$.forEach(({peer:C})=>Z(C)),$.clear()}}}function bi({userId:T,worldId:W,logLine:X,enterRoomFunction:c=j,peerlessUserExpiration:x,workerUrl:G,onRoomReady:z,onRoomClose:P,dataChannelOptions:N}){let S=new Set,Y=new Map,E=new Set,$=new Set;function H(h,A,i){function b(q){let D=q.channel;_(A,D,i),Y.set(A,D)}h.addEventListener("datachannel",b);let f=h.createDataChannel("data",N);return _(A,f,i),Y.set(A,f),()=>{h.removeEventListener("datachannel",b)}}function Z(h,A){$.forEach((i)=>i(h,A))}function _(h,A,i){A.onopen=()=>{X?.("\uD83D\uDCAC",{event:"dc-open",userId:h}),S.add(h),E.forEach((f)=>f(h,"join",[...S]))};let b=({data:f})=>{Z(f,h)};A.addEventListener("message",b),A.onclose=()=>{X?.("\uD83D\uDCAC",{event:"dc-close",userId:h}),S.delete(h),E.forEach((f)=>f(h,"leave",[...S])),A.removeEventListener("message",b),A.onopen=null,A.onclose=null,A.onerror=null,A.close(),i?.()},A.onerror=()=>X?.("⚠️ ERROR",{error:"dc-error",userId:h})}let{userId:O,enterRoom:J,exitRoom:B,leaveUser:V,broadcast:C,end:R,reset:F}=U({userId:T,worldId:W,enterRoomFunction:c,logLine:X,workerUrl:G,peerlessUserExpiration:x,onRoomReady:z,onRoomClose:P,onLeaveUser(h){let A=Y.get(h);try{A?.close()}catch{}Y.delete(h)},receivePeerConnection({pc:h,userId:A,restart:i}){H(h,A,i)},onBroadcastMessage(h,A){Z(h,A),X?.("\uD83D\uDCE2",{event:"broadcast",userId:O,data:h})}});function K(h,A){Y.forEach((i,b)=>{if(A&&b!==A)return;if(i.readyState==="open")i.send(h)})}function M(h){$.delete(h)}function v(h){return $.add(h),()=>{M(h)}}function Q(h){E.delete(h)}function w(h){return E.add(h),()=>{Q(h)}}return{userId:O,send:K,broadcast:C,enterRoom:J,exitRoom:B,leaveUser:V,getUsers:()=>[...S],addMessageListener:v,removeMessageListener:M,addUserListener:w,removeUserListener:Q,reset(){S.forEach((h)=>{Y.get(h)?.close(),Y.delete(h),F(h)})},end(){Y.forEach((h)=>{try{h.close()}catch{}}),Y.clear(),R(),E.clear(),S.clear()}}}export{bi as enterWorld};
2
2
 
3
- //# debugId=F76F84CB423911CF64756E2164756E21
3
+ //# debugId=B5A8EDFB241B8FF864756E2164756E21
4
4
  //# sourceMappingURL=enter-world.js.map
@@ -3,13 +3,13 @@
3
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
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",
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",
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",
6
+ "export interface IPeer {\n userId: string;\n joined: number;\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[], selfJoined: number): void;\n onPeerLeft(users: { userId: string }[]): void;\n onIceUrl?(url: string, expiration: number): void;\n onMessage(type: T, payload: P, from: string): void;\n autoRejoin?: boolean;\n}): {\n exitRoom: () => void;\n send: <P extends any>(\n type: T,\n userId: \"server\" | string,\n payload?: P,\n ) => void;\n} {\n type Message = {\n type: \"peer-joined\" | \"peer-left\" | \"ice-server\" | T;\n userId: string;\n users: { userId: string; joined: number }[];\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>();\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, msg.userId);\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, 15000);\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(\n updatedUsers: { userId: string; joined: number }[],\n msg: Message,\n ) {\n const joined: IPeer[] = [];\n const left: { userId: string }[] = [];\n const updatedPeerSet = new Set<string>();\n\n const selfPeer = updatedUsers.filter((peer) => peer.userId === userId)[0];\n if (!selfPeer) {\n logLine?.(\"⚠️\", \"Cannot find self in updated users\");\n return;\n }\n const selfJoined = selfPeer.joined;\n\n updatedUsers.forEach(({ userId: pUserId, joined: joinedTime }) => {\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 joined: joinedTime,\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, selfJoined);\n if (left.length) params.onPeerLeft(left);\n }\n\n // Start initial connection\n connect();\n\n return {\n send(type, userId, payload) {\n send(type, userId, payload);\n },\n exitRoom: () => {\n exited = true;\n clearTimeout(timeoutId);\n ws.close();\n },\n };\n}\n",
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[], selfJoined: number) => void;\n onPeerLeft: (users: { userId: string }[]) => void;\n onIceUrl?(url: string, expiration: number): void;\n onMessage: (type: T, payload: P, from: string) => 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 send: <P extends any>(\n type: T,\n userId: \"server\" | string,\n payload?: P,\n ) => 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 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(\n ev.users.map((ev) => ({ userId: ev.userId, joined: ev.joined })),\n ev.joined,\n );\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, 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 send: (type, toUserId, payload) => {\n worker.postMessage({\n cmd: \"send\",\n toUserId,\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",
8
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 const promise = new Promise<Connection>(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 state.connectionPromise = promise;\n await promise;\n state.connectionPromise = undefined;\n return promise;\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",
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: string;\n joined?: number;\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,\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 const promise = new Promise<Connection>(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 send(\"ice\", state.peer, {\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,\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,\n restart: () => userState.close(),\n });\n } else {\n logLine?.(\"👤ℹ️\", \"no pc: \" + userState.peer);\n }\n return;\n }\n };\n\n resolve(state.connection);\n });\n state.connectionPromise = promise;\n await promise;\n state.connectionPromise = undefined;\n return promise;\n }\n\n async function getPeer(\n peer: string,\n forceReset?: boolean,\n ): Promise<UserState> {\n let state = users.get(peer);\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);\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,\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, 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(userId: string) {\n // Offer flow: createOffer -> setLocalDescription -> send localDescription\n const state = await getPeer(userId);\n const pc = state.connection?.pc;\n const offer = await pc?.createOffer();\n await pc?.setLocalDescription(offer);\n send(\"offer\", userId, {\n connectionId: state.connection?.id,\n offer: pc?.localDescription?.toJSON(),\n });\n }\n\n const { exitRoom, send } = 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[], selfJoined: number) {\n joiningUsers.forEach(async (user) => {\n const state = await getPeer(user.userId, true);\n state.joined = user.joined;\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 if (\n user.joined > selfJoined ||\n (user.joined === selfJoined &&\n user.userId.localeCompare(userId) > 0)\n ) {\n await makeOffer(user.userId);\n }\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: string) {\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,\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 send(\"answer\", from, {\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,\n detail: String(e),\n });\n }\n return;\n }\n\n if (type === \"broadcast\") {\n onBroadcastMessage?.(payload, from);\n }\n },\n });\n\n const removeRequester = iceUrlProvider.addRequester((command) =>\n send(command, \"server\"),\n );\n\n roomsEntered.set(`${host}/room/${room}`, {\n exitRoom: () => {\n exitRoom();\n removeRequester();\n },\n room,\n host,\n broadcast: (payload) => send(\"broadcast\", \"server\", 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));\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",
10
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"
11
11
  ],
12
- "mappings": "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",
13
- "debugId": "F76F84CB423911CF64756E2164756E21",
12
+ "mappings": "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",
13
+ "debugId": "B5A8EDFB241B8FF864756E2164756E21",
14
14
  "names": []
15
15
  }
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
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,f,B){if(!G)return X?.("\uD83D\uDC64 ➡️ ❌","no ws available"),!1;let D={type:q,to:f,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 f=JSON.parse(q.data);(Array.isArray(f)?f:[f]).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,f){let B=[],D=[],Y=new Set;q.forEach(({userId:A})=>{if(A===E)return;if(!$.has(A)||f.type==="peer-joined"&&A===f.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)||f.type==="peer-left"&&A===f.userId)$.delete(A),D.push({userId:A});if(B.length)T.onPeerJoined(B);if(D.length)T.onPeerLeft(D)}return O(),{sendToServer(q,f){z(q,"server",f)},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,f)=>{if(N)return!1;return Q.postMessage({cmd:"send",toUserId:K,host:H,room:c,type:q,payload:f}),!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((f)=>z({userId:f.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(f){X?.(f);let B=$.get(f);if(!B)return;$.delete(f);try{B.close()}catch{}}async function N(f){if(!f.connection?.pc?.remoteDescription)return;let B=f.connection.pendingRemoteIce;f.connection.pendingRemoteIce=[];for(let D of B)try{await f.connection.pc.addIceCandidate(D)}catch(Y){V?.("⚠️ ERROR",{error:"add-ice-failed",userId:f.peer.userId,detail:String(Y)})}}let z=new j,O=new L(z);function K({room:f,host:B}){let D=`${B}/room/${f}`,Y=S.get(D);if(Y)Y.exitRoom(),S.delete(D)}function q({room:f,host:B}){return new Promise(async(D,Y)=>{async function A(i){if(i.connectionPromise)return i.connectionPromise;let C=new Promise(async(R)=>{i.connection={id:`conn-${crypto.randomUUID()}`,pc:new RTCPeerConnection(await O.getRtcConfig()),pendingRemoteIce:[]},i.connection.pc.onicecandidate=(b)=>{if(!b.candidate)return;i.peer.receive("ice",{connectionId:i.connection?.id,ice:b.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 b=await x(i.peer,!0);if(b.connection?.pc)c({pc:b.connection?.pc,userId:b.peer.userId,restart:()=>b.close()});else V?.("\uD83D\uDC64ℹ️","no pc: "+b.peer.userId);return}},R(i.connection)});return i.connectionPromise=C,await C,i.connectionPromise=void 0,C}async function x(i,C){let R=$.get(i.userId);if(!R||C){let b={peer:i,close(){if(this.connection)this.connection.pc.close(),this.connection=void 0;$.delete(i.userId)},async reset(){b.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)}};R=b,await A(b),$.set(R.peer.userId,R)}else if(R){if(clearTimeout(R.expirationTimeout),R.expirationTimeout=0,!R.connection?.pc||R.connection?.pc.signalingState==="closed")await A(R)}return R.peer=i,R}async function v(i){let C=await x(i),R=C.connection?.pc,b=await R?.createOffer();await R?.setLocalDescription(b),i.receive("offer",{connectionId:C.connection?.id,offer:R.localDescription.toJSON()})}let{exitRoom:n,sendToServer:h}=M({userId:Z,worldId:E,room:f,host:B,logLine:V,workerUrl:J,autoRejoin:!0,onOpen(){P?.({room:f,host:B}),D()},onError(){console.error("onError"),Y()},onClose(i){G?.({room:f,host:B,ev:i})},onPeerJoined(i){i.forEach(async(C)=>{let R=await x(C,!0),b=R.connection?.pc;if(!b){V?.("\uD83D\uDC64ℹ️","no pc: "+C.userId);return}c({pc:b,userId:C.userId,restart:()=>R.close()}),await v(C)})},onPeerLeft(i){i.forEach(({userId:C})=>{let R=$.get(C);if(!R)return;R.expirationTimeout=setTimeout(()=>Q(C),H??0)})},onIceUrl(i,C){z.receiveIce(i,C)},async onMessage(i,C,R){if(i==="offer"&&C.offer){let b=await x(R,!1),F=!b.connection||b.connection.pc.signalingState==="stable"?await A(b):b.connection;V?.("\uD83D\uDCAC",{type:i,signalingState:F.pc.signalingState}),F.peerConnectionId=C.connectionId,c({pc:F.pc,userId:R.userId,restart:()=>b.close()}),await F.pc.setRemoteDescription(C.offer);let _=await F.pc.createAnswer();await F.pc.setLocalDescription(_),R.receive("answer",{connectionId:F.id,answer:F.pc.localDescription?.toJSON()}),await N(b);return}if(i==="answer"&&C.answer){let b=await x(R,!1),F=b.connection&&b.connection.pc.signalingState!=="closed"?b.connection:await A(b);V?.("\uD83D\uDCAC",{type:i,signalingState:F.pc.signalingState}),await F.pc.setRemoteDescription(C.answer),F.peerConnectionId=C.connectionId,await N(b);return}if(i==="ice"&&C.ice){let b=await x(R,!1),F=b.connection??await b.connectionPromise;if(!F){V?.("⚠️","No connection");return}if(V?.("\uD83D\uDCAC",{type:i,signalingState:F.pc.signalingState}),F.peerConnectionId&&C.connectionId!==F.peerConnectionId){V?.("⚠️","Mismatch peerConnectionID"+C.connectionId+"vs"+F.peerConnectionId);return}if(!F.pc.remoteDescription||!F.peerConnectionId){F.peerConnectionId=C.connectionId,F.pendingRemoteIce.push(C.ice);return}try{await F.pc.addIceCandidate(C.ice)}catch(_){V?.("⚠️ ERROR",{error:"add-ice-failed",userId:b.peer.userId,detail:String(_)})}return}if(i==="broadcast")W?.(C,R.userId)}});z.addRequester(h),S.set(`${B}/room/${f}`,{exitRoom:()=>{n(),z.removeRequester(h)},room:f,host:B,broadcast:(i)=>h("broadcast",i)})})}return{userId:Z,enterRoom:q,exitRoom:K,leaveUser:Q,async reset(f){$.get(f)?.reset()},broadcast(f){S.forEach((B)=>B.broadcast(f))},end(){S.forEach(({exitRoom:f})=>f()),S.clear(),$.forEach(({peer:f})=>Q(f.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(h,i,C){function R(F){let _=F.channel;N(i,_,C),W.set(i,_)}h.addEventListener("datachannel",R);let b=h.createDataChannel("data",P);return N(i,b,C),W.set(i,b),()=>{h.removeEventListener("datachannel",R)}}function Q(h,i){$.forEach((C)=>C(h,i))}function N(h,i,C){i.onopen=()=>{c?.("\uD83D\uDCAC",{event:"dc-open",userId:h}),G.add(h),Z.forEach((b)=>b(h,"join",[...G]))};let R=({data:b})=>{Q(b,h)};i.addEventListener("message",R),i.onclose=()=>{c?.("\uD83D\uDCAC",{event:"dc-close",userId:h}),G.delete(h),Z.forEach((b)=>b(h,"leave",[...G])),i.removeEventListener("message",R),i.onopen=null,i.onclose=null,i.onerror=null,i.close(),C?.()},i.onerror=()=>c?.("⚠️ ERROR",{error:"dc-error",userId:h})}let{userId:z,enterRoom:O,exitRoom:K,leaveUser:q,broadcast:f,end:B,reset:D}=g({userId:T,worldId:E,enterRoomFunction:H,logLine:c,workerUrl:V,peerlessUserExpiration:M,onRoomReady:X,onRoomClose:J,onLeaveUser(h){let i=W.get(h);try{i?.close()}catch{}W.delete(h)},receivePeerConnection({pc:h,userId:i,restart:C}){S(h,i,C)},onBroadcastMessage(h,i){Q(h,i),c?.("\uD83D\uDCE2",{event:"broadcast",userId:z,data:h})}});function Y(h,i){W.forEach((C,R)=>{if(i&&R!==i)return;if(C.readyState==="open")C.send(h)})}function A(h){$.delete(h)}function x(h){return $.add(h),()=>{A(h)}}function v(h){Z.delete(h)}function n(h){return Z.add(h),()=>{v(h)}}return{userId:z,send:Y,broadcast:f,enterRoom:O,exitRoom:K,leaveUser:q,getUsers:()=>[...G],addMessageListener:x,removeMessageListener:A,addUserListener:n,removeUserListener:v,reset(){G.forEach((h)=>{W.get(h)?.close(),W.delete(h),D(h)})},end(){W.forEach((h)=>{try{h.close()}catch{}}),W.clear(),B(),Z.clear(),G.clear()}}}export{u as enterWorld,k as enterRoom,g as collectPeerConnections};
1
+ class n{sendToServerFunctions=[];icePromiseResolve;icePromise;receiveIce(R,z){this.icePromiseResolve?.({url:R,expiration:z}),this.icePromiseResolve=void 0,this.icePromise=void 0}addRequester(R){return this.sendToServerFunctions.push(R),()=>{this.removeRequester(R)}}removeRequester(R){this.sendToServerFunctions.splice(this.sendToServerFunctions.indexOf(R),1)}sendToServer(R){this.sendToServerFunctions[Math.floor(this.sendToServerFunctions.length*Math.random())](R)}async requestIce(){if(!this.icePromise)this.icePromise=new Promise((R)=>{this.icePromiseResolve=R,this.sendToServer("request-ice")});return await this.icePromise}}function L(R){let{userId:z,worldId:W,room:K,host:M,autoRejoin:G=!0,logLine:Z}=R,P=!1,N=0,S,X,c=!0,$=new Map,E=`wss://${M}/room/${W}/${K}?userId=${encodeURIComponent(z)}`,Y=[],_=0;function O(V,C,T){if(!S)return Z?.("\uD83D\uDC64 ➡️ ❌","no ws available"),!1;let F={type:V,to:C,payload:T};if(Y.push(F),Z?.("\uD83D\uDC64 ➡️ \uD83D\uDDA5️",F),clearTimeout(_),P||S.readyState!==WebSocket.OPEN)return Z?.("\uD83D\uDC64 ➡️ ❌","Not in opened state: "+S.readyState),!1;return _=setTimeout(()=>{S.send(JSON.stringify(Y)),Y.length=0}),!0}function J(){if(P)return;S=new WebSocket(E),S.onopen=()=>{if(c)R.onOpen?.(),c=!1;N=0},S.onmessage=(V)=>{try{let C=JSON.parse(V.data);(Array.isArray(C)?C:[C]).forEach((F)=>{if(Z?.("\uD83D\uDDA5️ ➡️ \uD83D\uDC64",F),F.type==="peer-joined"||F.type==="peer-left")B(F.users,F);else if(F.type==="ice-server")R.onIceUrl?.(F.url,F.expiration);else if(F.userId)R.onMessage(F.type,F.payload,F.userId)})}catch{Z?.("⚠️ ERROR",{error:"invalid-json"})}},S.onclose=(V)=>{let T=[1001,1006,1011,1012,1013].includes(V.code);if(G&&!P&&T){let F=Math.min(Math.pow(2,N)*1000,15000),H=Math.random()*1000,x=F+H;Z?.("\uD83D\uDD04 RECONNECTING",{attempt:N+1,delayMs:Math.round(x)}),N++,X=setTimeout(J,x)}else R.onClose?.({code:V.code,reason:V.reason,wasClean:V.wasClean})},S.onerror=(V)=>{console.error("WS Error",V),R.onError?.()}}function B(V,C){let T=[],F=[],H=new Set,x=V.filter((Q)=>Q.userId===z)[0];if(!x){Z?.("⚠️","Cannot find self in updated users");return}let v=x.joined;V.forEach(({userId:Q,joined:w})=>{if(Q===z)return;if(!$.has(Q)||C.type==="peer-joined"&&Q===C.userId){let h={userId:Q,joined:w};$.set(Q,h),T.push(h)}H.add(Q)});for(let Q of $.keys())if(!H.has(Q)||C.type==="peer-left"&&Q===C.userId)$.delete(Q),F.push({userId:Q});if(T.length)R.onPeerJoined(T,v);if(F.length)R.onPeerLeft(F)}return J(),{send(V,C,T){O(V,C,T)},exitRoom:()=>{P=!0,clearTimeout(X),S.close()}}}function j({userId:R,worldId:z,room:W,host:K,autoRejoin:M=!0,onOpen:G,onClose:Z,onError:P,onPeerJoined:N,onPeerLeft:S,onIceUrl:X,onMessage:c,logLine:$,workerUrl:E}){if(!E)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:R,worldId:z,room:W,host:K,autoRejoin:M,onOpen:G,onClose:Z,onError:P,onPeerJoined:N,onPeerLeft:S,onIceUrl:X,onMessage:c});let Y=new Worker(E,{type:"module"}),_=!1,O=(J)=>{let B=J.data;if(B.kind==="open")G?.();else if(B.kind==="close")Y.terminate(),Z?.(B.ev);else if(B.kind==="error")P?.();else if(B.kind==="peer-joined")N(B.users.map((V)=>({userId:V.userId,joined:V.joined})),B.joined);else if(B.kind==="peer-left")S(B.users);else if(B.kind==="ice-server")X?.(B.url,B.expiration);else if(B.kind==="message")c(B.type,B.payload,B.fromUserId);else if(B.kind==="log")$?.(B.direction,B.obj)};return Y.addEventListener("message",O),Y.postMessage({cmd:"enter",userId:R,worldId:z,room:W,host:K,autoRejoin:M}),{exitRoom:()=>{_=!0,Y.removeEventListener("message",O),Y.postMessage({cmd:"exit"})},send:(J,B,V)=>{Y.postMessage({cmd:"send",toUserId:B,host:K,room:W,type:J,payload:V})}}}var m={iceServers:[{urls:"stun:stun.l.google.com:19302"}]};class g{iceUrlProvider;constructor(R){this.iceUrlProvider=R}rtcConfig={...m,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(z)=>{let W=3;for(let K=0;K<W;K++)try{let M=(await this.iceUrlProvider.requestIce()).url,G=await fetch(M);if(!G.ok)throw Error(`ICE endpoint failed: ${G.status}`);let Z=await G.json();z(Z);return}catch(M){console.warn("Failed fetching iceUrl")}}),this.rtcConfig=await this.rtcConfigPromise,this.rtcConfigPromise=void 0;return this.rtcConfig}}var y=j;function U({userId:R,worldId:z,receivePeerConnection:W,peerlessUserExpiration:K=5000,enterRoomFunction:M=y,logLine:G,onLeaveUser:Z,workerUrl:P,onRoomReady:N,onRoomClose:S,onBroadcastMessage:X}){let c=R??`user-${crypto.randomUUID()}`,$=new Map,E=new Map;function Y(C){Z?.(C);let T=$.get(C);if(!T)return;$.delete(C);try{T.close()}catch{}}async function _(C){if(!C.connection?.pc?.remoteDescription)return;let T=C.connection.pendingRemoteIce;C.connection.pendingRemoteIce=[];for(let F of T)try{await C.connection.pc.addIceCandidate(F)}catch(H){G?.("⚠️ ERROR",{error:"add-ice-failed",userId:C.peer,detail:String(H)})}}let O=new n,J=new g(O);function B({room:C,host:T}){let F=`${T}/room/${C}`,H=E.get(F);if(H)H.exitRoom(),E.delete(F)}function V({room:C,host:T}){return new Promise(async(F,H)=>{async function x(i){if(i.connectionPromise)return i.connectionPromise;let b=new Promise(async(f)=>{i.connection={id:`conn-${crypto.randomUUID()}`,pc:new RTCPeerConnection(await J.getRtcConfig()),pendingRemoteIce:[]},i.connection.pc.onicecandidate=(q)=>{if(!q.candidate)return;h("ice",i.peer,{connectionId:i.connection?.id,ice:q.candidate.toJSON()})},i.connection.pc.onconnectionstatechange=async()=>{if(G?.("\uD83D\uDCAC",{event:"pc-state",userId:i.peer,state:i.connection?.pc?.connectionState}),i.connection?.pc?.connectionState==="failed"){i.close();let q=await v(i.peer,!0);if(q.connection?.pc)W({pc:q.connection?.pc,userId:q.peer,restart:()=>q.close()});else G?.("\uD83D\uDC64ℹ️","no pc: "+q.peer);return}},f(i.connection)});return i.connectionPromise=b,await b,i.connectionPromise=void 0,b}async function v(i,b){let f=$.get(i);if(!f||b){let q={peer:i,close(){if(this.connection)this.connection.pc.close(),this.connection=void 0;$.delete(i)},async reset(){q.close(),setTimeout(async()=>{let D=await v(i,!0);if(!D.connection?.pc){G?.("⚠️","no pc");return}W({pc:D.connection?.pc,userId:D.peer,restart:()=>D.close()}),await Q(D.peer)},500)}};f=q,await x(q),$.set(f.peer,f)}else if(f){if(clearTimeout(f.expirationTimeout),f.expirationTimeout=0,!f.connection?.pc||f.connection?.pc.signalingState==="closed")await x(f)}return f.peer=i,f}async function Q(i){let b=await v(i),f=b.connection?.pc,q=await f?.createOffer();await f?.setLocalDescription(q),h("offer",i,{connectionId:b.connection?.id,offer:f?.localDescription?.toJSON()})}let{exitRoom:w,send:h}=M({userId:c,worldId:z,room:C,host:T,logLine:G,workerUrl:P,autoRejoin:!0,onOpen(){N?.({room:C,host:T}),F()},onError(){console.error("onError"),H()},onClose(i){S?.({room:C,host:T,ev:i})},onPeerJoined(i,b){i.forEach(async(f)=>{let q=await v(f.userId,!0);q.joined=f.joined;let D=q.connection?.pc;if(!D){G?.("\uD83D\uDC64ℹ️","no pc: "+f.userId);return}if(W({pc:D,userId:f.userId,restart:()=>q.close()}),f.joined>b||f.joined===b&&f.userId.localeCompare(c)>0)await Q(f.userId)})},onPeerLeft(i){i.forEach(({userId:b})=>{let f=$.get(b);if(!f)return;f.expirationTimeout=setTimeout(()=>Y(b),K??0)})},onIceUrl(i,b){O.receiveIce(i,b)},async onMessage(i,b,f){if(i==="offer"&&b.offer){let q=await v(f,!1),D=!q.connection||q.connection.pc.signalingState==="stable"?await x(q):q.connection;G?.("\uD83D\uDCAC",{type:i,signalingState:D.pc.signalingState}),D.peerConnectionId=b.connectionId,W({pc:D.pc,userId:f,restart:()=>q.close()}),await D.pc.setRemoteDescription(b.offer);let k=await D.pc.createAnswer();await D.pc.setLocalDescription(k),h("answer",f,{connectionId:D.id,answer:D.pc.localDescription?.toJSON()}),await _(q);return}if(i==="answer"&&b.answer){let q=await v(f,!1),D=q.connection&&q.connection.pc.signalingState!=="closed"?q.connection:await x(q);G?.("\uD83D\uDCAC",{type:i,signalingState:D.pc.signalingState}),await D.pc.setRemoteDescription(b.answer),D.peerConnectionId=b.connectionId,await _(q);return}if(i==="ice"&&b.ice){let q=await v(f,!1),D=q.connection??await q.connectionPromise;if(!D){G?.("⚠️","No connection");return}if(G?.("\uD83D\uDCAC",{type:i,signalingState:D.pc.signalingState}),D.peerConnectionId&&b.connectionId!==D.peerConnectionId){G?.("⚠️","Mismatch peerConnectionID"+b.connectionId+"vs"+D.peerConnectionId);return}if(!D.pc.remoteDescription||!D.peerConnectionId){D.peerConnectionId=b.connectionId,D.pendingRemoteIce.push(b.ice);return}try{await D.pc.addIceCandidate(b.ice)}catch(k){G?.("⚠️ ERROR",{error:"add-ice-failed",userId:q.peer,detail:String(k)})}return}if(i==="broadcast")X?.(b,f)}}),A=O.addRequester((i)=>h(i,"server"));E.set(`${T}/room/${C}`,{exitRoom:()=>{w(),A()},room:C,host:T,broadcast:(i)=>h("broadcast","server",i)})})}return{userId:c,enterRoom:V,exitRoom:B,leaveUser:Y,async reset(C){$.get(C)?.reset()},broadcast(C){E.forEach((T)=>T.broadcast(C))},end(){E.forEach(({exitRoom:C})=>C()),E.clear(),$.forEach(({peer:C})=>Y(C)),$.clear()}}}function u({userId:R,worldId:z,logLine:W,enterRoomFunction:K=j,peerlessUserExpiration:M,workerUrl:G,onRoomReady:Z,onRoomClose:P,dataChannelOptions:N}){let S=new Set,X=new Map,c=new Set,$=new Set;function E(h,A,i){function b(q){let D=q.channel;_(A,D,i),X.set(A,D)}h.addEventListener("datachannel",b);let f=h.createDataChannel("data",N);return _(A,f,i),X.set(A,f),()=>{h.removeEventListener("datachannel",b)}}function Y(h,A){$.forEach((i)=>i(h,A))}function _(h,A,i){A.onopen=()=>{W?.("\uD83D\uDCAC",{event:"dc-open",userId:h}),S.add(h),c.forEach((f)=>f(h,"join",[...S]))};let b=({data:f})=>{Y(f,h)};A.addEventListener("message",b),A.onclose=()=>{W?.("\uD83D\uDCAC",{event:"dc-close",userId:h}),S.delete(h),c.forEach((f)=>f(h,"leave",[...S])),A.removeEventListener("message",b),A.onopen=null,A.onclose=null,A.onerror=null,A.close(),i?.()},A.onerror=()=>W?.("⚠️ ERROR",{error:"dc-error",userId:h})}let{userId:O,enterRoom:J,exitRoom:B,leaveUser:V,broadcast:C,end:T,reset:F}=U({userId:R,worldId:z,enterRoomFunction:K,logLine:W,workerUrl:G,peerlessUserExpiration:M,onRoomReady:Z,onRoomClose:P,onLeaveUser(h){let A=X.get(h);try{A?.close()}catch{}X.delete(h)},receivePeerConnection({pc:h,userId:A,restart:i}){E(h,A,i)},onBroadcastMessage(h,A){Y(h,A),W?.("\uD83D\uDCE2",{event:"broadcast",userId:O,data:h})}});function H(h,A){X.forEach((i,b)=>{if(A&&b!==A)return;if(i.readyState==="open")i.send(h)})}function x(h){$.delete(h)}function v(h){return $.add(h),()=>{x(h)}}function Q(h){c.delete(h)}function w(h){return c.add(h),()=>{Q(h)}}return{userId:O,send:H,broadcast:C,enterRoom:J,exitRoom:B,leaveUser:V,getUsers:()=>[...S],addMessageListener:v,removeMessageListener:x,addUserListener:w,removeUserListener:Q,reset(){S.forEach((h)=>{X.get(h)?.close(),X.delete(h),F(h)})},end(){X.forEach((h)=>{try{h.close()}catch{}}),X.clear(),T(),c.clear(),S.clear()}}}export{u as enterWorld,L as enterRoom,U as collectPeerConnections};
2
2
 
3
- //# debugId=AADEA038C84812ED64756E2164756E21
3
+ //# debugId=CDB8161D2897B75F64756E2164756E21
4
4
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -3,13 +3,13 @@
3
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
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",
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",
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",
6
+ "export interface IPeer {\n userId: string;\n joined: number;\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[], selfJoined: number): void;\n onPeerLeft(users: { userId: string }[]): void;\n onIceUrl?(url: string, expiration: number): void;\n onMessage(type: T, payload: P, from: string): void;\n autoRejoin?: boolean;\n}): {\n exitRoom: () => void;\n send: <P extends any>(\n type: T,\n userId: \"server\" | string,\n payload?: P,\n ) => void;\n} {\n type Message = {\n type: \"peer-joined\" | \"peer-left\" | \"ice-server\" | T;\n userId: string;\n users: { userId: string; joined: number }[];\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>();\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, msg.userId);\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, 15000);\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(\n updatedUsers: { userId: string; joined: number }[],\n msg: Message,\n ) {\n const joined: IPeer[] = [];\n const left: { userId: string }[] = [];\n const updatedPeerSet = new Set<string>();\n\n const selfPeer = updatedUsers.filter((peer) => peer.userId === userId)[0];\n if (!selfPeer) {\n logLine?.(\"⚠️\", \"Cannot find self in updated users\");\n return;\n }\n const selfJoined = selfPeer.joined;\n\n updatedUsers.forEach(({ userId: pUserId, joined: joinedTime }) => {\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 joined: joinedTime,\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, selfJoined);\n if (left.length) params.onPeerLeft(left);\n }\n\n // Start initial connection\n connect();\n\n return {\n send(type, userId, payload) {\n send(type, userId, payload);\n },\n exitRoom: () => {\n exited = true;\n clearTimeout(timeoutId);\n ws.close();\n },\n };\n}\n",
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[], selfJoined: number) => void;\n onPeerLeft: (users: { userId: string }[]) => void;\n onIceUrl?(url: string, expiration: number): void;\n onMessage: (type: T, payload: P, from: string) => 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 send: <P extends any>(\n type: T,\n userId: \"server\" | string,\n payload?: P,\n ) => 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 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(\n ev.users.map((ev) => ({ userId: ev.userId, joined: ev.joined })),\n ev.joined,\n );\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, 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 send: (type, toUserId, payload) => {\n worker.postMessage({\n cmd: \"send\",\n toUserId,\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",
8
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 const promise = new Promise<Connection>(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 state.connectionPromise = promise;\n await promise;\n state.connectionPromise = undefined;\n return promise;\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",
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: string;\n joined?: number;\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,\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 const promise = new Promise<Connection>(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 send(\"ice\", state.peer, {\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,\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,\n restart: () => userState.close(),\n });\n } else {\n logLine?.(\"👤ℹ️\", \"no pc: \" + userState.peer);\n }\n return;\n }\n };\n\n resolve(state.connection);\n });\n state.connectionPromise = promise;\n await promise;\n state.connectionPromise = undefined;\n return promise;\n }\n\n async function getPeer(\n peer: string,\n forceReset?: boolean,\n ): Promise<UserState> {\n let state = users.get(peer);\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);\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,\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, 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(userId: string) {\n // Offer flow: createOffer -> setLocalDescription -> send localDescription\n const state = await getPeer(userId);\n const pc = state.connection?.pc;\n const offer = await pc?.createOffer();\n await pc?.setLocalDescription(offer);\n send(\"offer\", userId, {\n connectionId: state.connection?.id,\n offer: pc?.localDescription?.toJSON(),\n });\n }\n\n const { exitRoom, send } = 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[], selfJoined: number) {\n joiningUsers.forEach(async (user) => {\n const state = await getPeer(user.userId, true);\n state.joined = user.joined;\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 if (\n user.joined > selfJoined ||\n (user.joined === selfJoined &&\n user.userId.localeCompare(userId) > 0)\n ) {\n await makeOffer(user.userId);\n }\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: string) {\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,\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 send(\"answer\", from, {\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,\n detail: String(e),\n });\n }\n return;\n }\n\n if (type === \"broadcast\") {\n onBroadcastMessage?.(payload, from);\n }\n },\n });\n\n const removeRequester = iceUrlProvider.addRequester((command) =>\n send(command, \"server\"),\n );\n\n roomsEntered.set(`${host}/room/${room}`, {\n exitRoom: () => {\n exitRoom();\n removeRequester();\n },\n room,\n host,\n broadcast: (payload) => send(\"broadcast\", \"server\", 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));\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",
10
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"
11
11
  ],
12
- "mappings": "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",
13
- "debugId": "AADEA038C84812ED64756E2164756E21",
12
+ "mappings": "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",
13
+ "debugId": "CDB8161D2897B75F64756E2164756E21",
14
14
  "names": []
15
15
  }
@@ -1,6 +1,6 @@
1
- export interface IPeer<T extends string = string, P = any> {
1
+ export interface IPeer {
2
2
  userId: string;
3
- receive(type: T, payload: P): boolean;
3
+ joined: number;
4
4
  }
5
5
  /**
6
6
  * enterRoom connects to the signaling room via WebSocket.
@@ -14,15 +14,15 @@ export declare function enterRoom<T extends string, P = any>(params: {
14
14
  onClose?: (ev: Pick<CloseEvent, "code" | "reason" | "wasClean">) => void;
15
15
  onError?: () => void;
16
16
  logLine?: (direction: string, obj?: any) => void;
17
- onPeerJoined(users: IPeer<T, P>[]): void;
17
+ onPeerJoined(users: IPeer[], selfJoined: number): void;
18
18
  onPeerLeft(users: {
19
19
  userId: string;
20
20
  }[]): void;
21
21
  onIceUrl?(url: string, expiration: number): void;
22
- onMessage(type: T, payload: P, from: IPeer<T, P>): void;
22
+ onMessage(type: T, payload: P, from: string): void;
23
23
  autoRejoin?: boolean;
24
24
  }): {
25
25
  exitRoom: () => void;
26
- sendToServer: <P extends any>(type: T, payload?: P) => void;
26
+ send: <P extends any>(type: T, userId: "server" | string, payload?: P) => void;
27
27
  };
28
28
  //# sourceMappingURL=signal-room.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"signal-room.d.ts","sourceRoot":"","sources":["../../../src/browser/signal/impl/signal-room.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,KAAK,CAAC,CAAC,SAAS,MAAM,GAAG,MAAM,EAAE,CAAC,GAAG,GAAG;IACvD,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,IAAI,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,GAAG,OAAO,CAAC;CACvC;AAID;;GAEG;AACH,wBAAgB,SAAS,CAAC,CAAC,SAAS,MAAM,EAAE,CAAC,GAAG,GAAG,EAAE,MAAM,EAAE;IAC3D,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;IACpB,OAAO,CAAC,EAAE,CAAC,EAAE,EAAE,IAAI,CAAC,UAAU,EAAE,MAAM,GAAG,QAAQ,GAAG,UAAU,CAAC,KAAK,IAAI,CAAC;IACzE,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,GAAG,KAAK,IAAI,CAAC;IACjD,YAAY,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,GAAG,IAAI,CAAC;IACzC,UAAU,CAAC,KAAK,EAAE;QAAE,MAAM,EAAE,MAAM,CAAA;KAAE,EAAE,GAAG,IAAI,CAAC;IAC9C,QAAQ,CAAC,CAAC,GAAG,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IACjD,SAAS,CAAC,IAAI,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,IAAI,CAAC;IACxD,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB,GAAG;IACF,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,YAAY,EAAE,CAAC,CAAC,SAAS,GAAG,EAAE,IAAI,EAAE,CAAC,EAAE,OAAO,CAAC,EAAE,CAAC,KAAK,IAAI,CAAC;CAC7D,CAqKA"}
1
+ {"version":3,"file":"signal-room.d.ts","sourceRoot":"","sources":["../../../src/browser/signal/impl/signal-room.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,KAAK;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;CAChB;AAID;;GAEG;AACH,wBAAgB,SAAS,CAAC,CAAC,SAAS,MAAM,EAAE,CAAC,GAAG,GAAG,EAAE,MAAM,EAAE;IAC3D,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;IACpB,OAAO,CAAC,EAAE,CAAC,EAAE,EAAE,IAAI,CAAC,UAAU,EAAE,MAAM,GAAG,QAAQ,GAAG,UAAU,CAAC,KAAK,IAAI,CAAC;IACzE,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,GAAG,KAAK,IAAI,CAAC;IACjD,YAAY,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IACvD,UAAU,CAAC,KAAK,EAAE;QAAE,MAAM,EAAE,MAAM,CAAA;KAAE,EAAE,GAAG,IAAI,CAAC;IAC9C,QAAQ,CAAC,CAAC,GAAG,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IACjD,SAAS,CAAC,IAAI,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACnD,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB,GAAG;IACF,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,IAAI,EAAE,CAAC,CAAC,SAAS,GAAG,EAClB,IAAI,EAAE,CAAC,EACP,MAAM,EAAE,QAAQ,GAAG,MAAM,EACzB,OAAO,CAAC,EAAE,CAAC,KACR,IAAI,CAAC;CACX,CA4KA"}
@@ -8,17 +8,17 @@ export declare function enterRoom<T extends string, P = any>({ userId, worldId,
8
8
  onOpen?: () => void;
9
9
  onClose?: (ev: Pick<CloseEvent, "code" | "reason" | "wasClean">) => void;
10
10
  onError?: () => void;
11
- onPeerJoined: (users: IPeer<T, P>[]) => void;
11
+ onPeerJoined: (users: IPeer[], selfJoined: number) => void;
12
12
  onPeerLeft: (users: {
13
13
  userId: string;
14
14
  }[]) => void;
15
15
  onIceUrl?(url: string, expiration: number): void;
16
- onMessage: (type: T, payload: P, from: IPeer<T, P>) => void;
16
+ onMessage: (type: T, payload: P, from: string) => void;
17
17
  logLine?: (direction: string, obj?: any) => void;
18
18
  workerUrl?: URL;
19
19
  }): {
20
20
  exitRoom: () => void;
21
- sendToServer: <P extends any>(type: T, payload?: P) => void;
21
+ send: <P extends any>(type: T, userId: "server" | string, payload?: P) => void;
22
22
  };
23
23
  export type EnterRoom<T extends string, P> = typeof enterRoom<T, P>;
24
24
  //# sourceMappingURL=signal-room.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"signal-room.d.ts","sourceRoot":"","sources":["../../src/browser/signal/signal-room.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,uBAAuB,CAAC;AAInD,wBAAgB,SAAS,CAAC,CAAC,SAAS,MAAM,EAAE,CAAC,GAAG,GAAG,EAAE,EACnD,MAAM,EACN,OAAO,EACP,IAAI,EACJ,IAAI,EACJ,UAAiB,EACjB,MAAM,EACN,OAAO,EACP,OAAO,EACP,YAAY,EACZ,UAAU,EACV,QAAQ,EACR,SAAS,EACT,OAAO,EACP,SAAS,GACV,EAAE;IACD,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;IACpB,OAAO,CAAC,EAAE,CAAC,EAAE,EAAE,IAAI,CAAC,UAAU,EAAE,MAAM,GAAG,QAAQ,GAAG,UAAU,CAAC,KAAK,IAAI,CAAC;IACzE,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,YAAY,EAAE,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,KAAK,IAAI,CAAC;IAC7C,UAAU,EAAE,CAAC,KAAK,EAAE;QAAE,MAAM,EAAE,MAAM,CAAA;KAAE,EAAE,KAAK,IAAI,CAAC;IAClD,QAAQ,CAAC,CAAC,GAAG,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IACjD,SAAS,EAAE,CAAC,IAAI,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,KAAK,IAAI,CAAC;IAC5D,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,GAAG,KAAK,IAAI,CAAC;IAGjD,SAAS,CAAC,EAAE,GAAG,CAAC;CACjB,GAAG;IACF,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,YAAY,EAAE,CAAC,CAAC,SAAS,GAAG,EAAE,IAAI,EAAE,CAAC,EAAE,OAAO,CAAC,EAAE,CAAC,KAAK,IAAI,CAAC;CAC7D,CAyFA;AAED,MAAM,MAAM,SAAS,CAAC,CAAC,SAAS,MAAM,EAAE,CAAC,IAAI,OAAO,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC"}
1
+ {"version":3,"file":"signal-room.d.ts","sourceRoot":"","sources":["../../src/browser/signal/signal-room.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,uBAAuB,CAAC;AAInD,wBAAgB,SAAS,CAAC,CAAC,SAAS,MAAM,EAAE,CAAC,GAAG,GAAG,EAAE,EACnD,MAAM,EACN,OAAO,EACP,IAAI,EACJ,IAAI,EACJ,UAAiB,EACjB,MAAM,EACN,OAAO,EACP,OAAO,EACP,YAAY,EACZ,UAAU,EACV,QAAQ,EACR,SAAS,EACT,OAAO,EACP,SAAS,GACV,EAAE;IACD,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;IACpB,OAAO,CAAC,EAAE,CAAC,EAAE,EAAE,IAAI,CAAC,UAAU,EAAE,MAAM,GAAG,QAAQ,GAAG,UAAU,CAAC,KAAK,IAAI,CAAC;IACzE,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,YAAY,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE,UAAU,EAAE,MAAM,KAAK,IAAI,CAAC;IAC3D,UAAU,EAAE,CAAC,KAAK,EAAE;QAAE,MAAM,EAAE,MAAM,CAAA;KAAE,EAAE,KAAK,IAAI,CAAC;IAClD,QAAQ,CAAC,CAAC,GAAG,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IACjD,SAAS,EAAE,CAAC,IAAI,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACvD,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,GAAG,KAAK,IAAI,CAAC;IAGjD,SAAS,CAAC,EAAE,GAAG,CAAC;CACjB,GAAG;IACF,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,IAAI,EAAE,CAAC,CAAC,SAAS,GAAG,EAClB,IAAI,EAAE,CAAC,EACP,MAAM,EAAE,QAAQ,GAAG,MAAM,EACzB,OAAO,CAAC,EAAE,CAAC,KACR,IAAI,CAAC;CACX,CA0EA;AAED,MAAM,MAAM,SAAS,CAAC,CAAC,SAAS,MAAM,EAAE,CAAC,IAAI,OAAO,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC"}
@@ -9,7 +9,9 @@ export type RoomEvent<T extends string = string, P = any> = {
9
9
  kind: "peer-joined";
10
10
  users: {
11
11
  userId: string;
12
+ joined: number;
12
13
  }[];
14
+ joined: number;
13
15
  } | {
14
16
  kind: "peer-left";
15
17
  users: {
@@ -1 +1 @@
1
- {"version":3,"file":"signal-room.worker.d.ts","sourceRoot":"","sources":["../../src/browser/signal/signal-room.worker.ts"],"names":[],"mappings":"AAIA,MAAM,MAAM,SAAS,CAAC,CAAC,SAAS,MAAM,GAAG,MAAM,EAAE,CAAC,GAAG,GAAG,IACpD;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAChB;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,EAAE,EAAE,IAAI,CAAC,UAAU,EAAE,MAAM,GAAG,QAAQ,GAAG,UAAU,CAAC,CAAA;CAAE,GACvE;IAAE,IAAI,EAAE,OAAO,CAAA;CAAE,GACjB;IAAE,IAAI,EAAE,aAAa,CAAC;IAAC,KAAK,EAAE;QAAE,MAAM,EAAE,MAAM,CAAA;KAAE,EAAE,CAAA;CAAE,GACpD;IAAE,IAAI,EAAE,WAAW,CAAC;IAAC,KAAK,EAAE;QAAE,MAAM,EAAE,MAAM,CAAA;KAAE,EAAE,CAAA;CAAE,GAClD;IAAE,IAAI,EAAE,YAAY,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,GACvD;IACE,IAAI,EAAE,SAAS,CAAC;IAChB,IAAI,EAAE,CAAC,CAAC;IACR,OAAO,EAAE,CAAC,CAAC;IACX,UAAU,EAAE,MAAM,CAAC;CACpB,GACD;IAAE,IAAI,EAAE,KAAK,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,GAAG,CAAA;CAAE,CAAC;AAElD,MAAM,MAAM,aAAa,CAAC,CAAC,SAAS,MAAM,GAAG,MAAM,EAAE,CAAC,GAAG,GAAG,IACxD;IACE,GAAG,EAAE,OAAO,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,OAAO,CAAC;CACrB,GACD;IAAE,GAAG,EAAE,MAAM,CAAA;CAAE,GACf;IACE,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,QAAQ,GAAG,MAAM,CAAC;IAC7B,IAAI,EAAE,CAAC,CAAC;IACR,OAAO,CAAC,EAAE,CAAC,CAAC;CACb,CAAC"}
1
+ {"version":3,"file":"signal-room.worker.d.ts","sourceRoot":"","sources":["../../src/browser/signal/signal-room.worker.ts"],"names":[],"mappings":"AAIA,MAAM,MAAM,SAAS,CAAC,CAAC,SAAS,MAAM,GAAG,MAAM,EAAE,CAAC,GAAG,GAAG,IACpD;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAChB;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,EAAE,EAAE,IAAI,CAAC,UAAU,EAAE,MAAM,GAAG,QAAQ,GAAG,UAAU,CAAC,CAAA;CAAE,GACvE;IAAE,IAAI,EAAE,OAAO,CAAA;CAAE,GACjB;IACE,IAAI,EAAE,aAAa,CAAC;IACpB,KAAK,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAC5C,MAAM,EAAE,MAAM,CAAC;CAChB,GACD;IAAE,IAAI,EAAE,WAAW,CAAC;IAAC,KAAK,EAAE;QAAE,MAAM,EAAE,MAAM,CAAA;KAAE,EAAE,CAAA;CAAE,GAClD;IAAE,IAAI,EAAE,YAAY,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,GACvD;IACE,IAAI,EAAE,SAAS,CAAC;IAChB,IAAI,EAAE,CAAC,CAAC;IACR,OAAO,EAAE,CAAC,CAAC;IACX,UAAU,EAAE,MAAM,CAAC;CACpB,GACD;IAAE,IAAI,EAAE,KAAK,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,GAAG,CAAA;CAAE,CAAC;AAElD,MAAM,MAAM,aAAa,CAAC,CAAC,SAAS,MAAM,GAAG,MAAM,EAAE,CAAC,GAAG,GAAG,IACxD;IACE,GAAG,EAAE,OAAO,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,OAAO,CAAC;CACrB,GACD;IAAE,GAAG,EAAE,MAAM,CAAA;CAAE,GACf;IACE,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,QAAQ,GAAG,MAAM,CAAC;IAC7B,IAAI,EAAE,CAAC,CAAC;IACR,OAAO,CAAC,EAAE,CAAC,CAAC;CACb,CAAC"}
@@ -1 +1 @@
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;kBAyVX,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;AAyBxB;;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;kBAkWX,MAAM;cAIhB,CAAC,2BAAuB,CAAC;;EAUtC"}
@@ -1,4 +1,4 @@
1
- class L{sendToServerFunctions=[];icePromiseResolve;icePromise;receiveIce(X,T){this.icePromiseResolve?.({url:X,expiration:T}),this.icePromiseResolve=void 0,this.icePromise=void 0}addRequester(X){return this.sendToServerFunctions.push(X),()=>{this.removeRequester(X)}}removeRequester(X){this.sendToServerFunctions.splice(this.sendToServerFunctions.indexOf(X),1)}sendToServer(X){this.sendToServerFunctions[Math.floor(this.sendToServerFunctions.length*Math.random())](X)}async requestIce(){if(!this.icePromise)this.icePromise=new Promise((X)=>{this.icePromiseResolve=X,this.sendToServer("request-ice")});return await this.icePromise}}function U(X){let{userId:T,worldId:H,room:J,host:O,autoRejoin:z=!0,logLine:W}=X,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 Q={type:b,to:q,payload:Y};if(D.push(Q),W?.("\uD83D\uDC64 ➡️ \uD83D\uDDA5️",Q),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(_)X.onOpen?.(),_=!1;S=0},B.onmessage=(b)=>{try{let q=JSON.parse(b.data);(Array.isArray(q)?q:[q]).forEach((Q)=>{if(W?.("\uD83D\uDDA5️ ➡️ \uD83D\uDC64",Q),Q.type==="peer-joined"||Q.type==="peer-left")M(Q.users,Q);else if(Q.type==="ice-server")X.onIceUrl?.(Q.url,Q.expiration);else if(Q.userId)X.onMessage(Q.type,Q.payload,{userId:Q.userId,receive:(K,Z)=>N(K,Q.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 Q=Math.min(Math.pow(2,S)*1000,30000),K=Math.random()*1000,Z=Q+K;W?.("\uD83D\uDD04 RECONNECTING",{attempt:S+1,delayMs:Math.round(Z)}),S++,R=setTimeout(E,Z)}else X.onClose?.({code:b.code,reason:b.reason,wasClean:b.wasClean})},B.onerror=(b)=>{console.error("WS Error",b),X.onError?.()}}function M(b,q){let Y=[],Q=[],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),Q.push({userId:Z});if(Y.length)X.onPeerJoined(Y);if(Q.length)X.onPeerLeft(Q)}return E(),{sendToServer(b,q){N(b,"server",q)},exitRoom:()=>{x=!0,clearTimeout(R),B.close()}}}function I({userId:X,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:X,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:X,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(X){this.iceUrlProvider=X}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:X,worldId:T,receivePeerConnection:H,peerlessUserExpiration:J=5000,enterRoomFunction:O=u,logLine:z,onLeaveUser:W,workerUrl:x,onRoomReady:S,onRoomClose:B,onBroadcastMessage:R}){let _=X??`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 Q of Y)try{await q.connection.pc.addIceCandidate(Q)}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 Q=`${Y}/room/${q}`,K=C.get(Q);if(K)K.exitRoom(),C.delete(Q)}function b({room:q,host:Y}){return new Promise(async(Q,K)=>{async function Z(F){if(F.connectionPromise)return F.connectionPromise;let G=new Promise(async($)=>{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}},$(F.connection)});return F.connectionPromise=G,await G,F.connectionPromise=void 0,G}async function f(F,G){let $=A.get(F.userId);if(!$||G){let h={peer:F,close(){if(this.connection)this.connection.pc.close(),this.connection=void 0;A.delete(F.userId)},async reset(){h.close(),setTimeout(async()=>{let V=await f(F,!0);if(!V.connection?.pc){z?.("⚠️","no pc");return}H({pc:V.connection?.pc,userId:V.peer.userId,restart:()=>V.close()}),await j(V.peer)},500)}};$=h,await Z(h),A.set($.peer.userId,$)}else if($){if(clearTimeout($.expirationTimeout),$.expirationTimeout=0,!$.connection?.pc||$.connection?.pc.signalingState==="closed")await Z($)}return $.peer=F,$}async function j(F){let G=await f(F),$=G.connection?.pc,h=await $?.createOffer();await $?.setLocalDescription(h),F.receive("offer",{connectionId:G.connection?.id,offer:$.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}),Q()},onError(){console.error("onError"),K()},onClose(F){B?.({room:q,host:Y,ev:F})},onPeerJoined(F){F.forEach(async(G)=>{let $=await f(G,!0),h=$.connection?.pc;if(!h){z?.("\uD83D\uDC64ℹ️","no pc: "+G.userId);return}H({pc:h,userId:G.userId,restart:()=>$.close()}),await j(G)})},onPeerLeft(F){F.forEach(({userId:G})=>{let $=A.get(G);if(!$)return;$.expirationTimeout=setTimeout(()=>D(G),J??0)})},onIceUrl(F,G){N.receiveIce(F,G)},async onMessage(F,G,$){if(F==="offer"&&G.offer){let h=await f($,!1),V=!h.connection||h.connection.pc.signalingState==="stable"?await Z(h):h.connection;z?.("\uD83D\uDCAC",{type:F,signalingState:V.pc.signalingState}),V.peerConnectionId=G.connectionId,H({pc:V.pc,userId:$.userId,restart:()=>h.close()}),await V.pc.setRemoteDescription(G.offer);let i=await V.pc.createAnswer();await V.pc.setLocalDescription(i),$.receive("answer",{connectionId:V.id,answer:V.pc.localDescription?.toJSON()}),await P(h);return}if(F==="answer"&&G.answer){let h=await f($,!1),V=h.connection&&h.connection.pc.signalingState!=="closed"?h.connection:await Z(h);z?.("\uD83D\uDCAC",{type:F,signalingState:V.pc.signalingState}),await V.pc.setRemoteDescription(G.answer),V.peerConnectionId=G.connectionId,await P(h);return}if(F==="ice"&&G.ice){let h=await f($,!1),V=h.connection??await h.connectionPromise;if(!V){z?.("⚠️","No connection");return}if(z?.("\uD83D\uDCAC",{type:F,signalingState:V.pc.signalingState}),V.peerConnectionId&&G.connectionId!==V.peerConnectionId){z?.("⚠️","Mismatch peerConnectionID"+G.connectionId+"vs"+V.peerConnectionId);return}if(!V.pc.remoteDescription||!V.peerConnectionId){V.peerConnectionId=G.connectionId,V.pendingRemoteIce.push(G.ice);return}try{await V.pc.addIceCandidate(G.ice)}catch(i){z?.("⚠️ ERROR",{error:"add-ice-failed",userId:h.peer.userId,detail:String(i)})}return}if(F==="broadcast")R?.(G,$.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};
1
+ class v{sendToServerFunctions=[];icePromiseResolve;icePromise;receiveIce(V,W){this.icePromiseResolve?.({url:V,expiration:W}),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:W,worldId:K,room:S,host:M,autoRejoin:z=!0,logLine:H}=V,x=!1,O=0,D,j,_=!0,A=new Map,C=`wss://${M}/room/${K}/${S}?userId=${encodeURIComponent(W)}`,T=[],k=0;function E(Z,F,X){if(!D)return H?.("\uD83D\uDC64 ➡️ ❌","no ws available"),!1;let Q={type:Z,to:F,payload:X};if(T.push(Q),H?.("\uD83D\uDC64 ➡️ \uD83D\uDDA5️",Q),clearTimeout(k),x||D.readyState!==WebSocket.OPEN)return H?.("\uD83D\uDC64 ➡️ ❌","Not in opened state: "+D.readyState),!1;return k=setTimeout(()=>{D.send(JSON.stringify(T)),T.length=0}),!0}function P(){if(x)return;D=new WebSocket(C),D.onopen=()=>{if(_)V.onOpen?.(),_=!1;O=0},D.onmessage=(Z)=>{try{let F=JSON.parse(Z.data);(Array.isArray(F)?F:[F]).forEach((Q)=>{if(H?.("\uD83D\uDDA5️ ➡️ \uD83D\uDC64",Q),Q.type==="peer-joined"||Q.type==="peer-left")Y(Q.users,Q);else if(Q.type==="ice-server")V.onIceUrl?.(Q.url,Q.expiration);else if(Q.userId)V.onMessage(Q.type,Q.payload,Q.userId)})}catch{H?.("⚠️ ERROR",{error:"invalid-json"})}},D.onclose=(Z)=>{let X=[1001,1006,1011,1012,1013].includes(Z.code);if(z&&!x&&X){let Q=Math.min(Math.pow(2,O)*1000,15000),N=Math.random()*1000,J=Q+N;H?.("\uD83D\uDD04 RECONNECTING",{attempt:O+1,delayMs:Math.round(J)}),O++,j=setTimeout(P,J)}else V.onClose?.({code:Z.code,reason:Z.reason,wasClean:Z.wasClean})},D.onerror=(Z)=>{console.error("WS Error",Z),V.onError?.()}}function Y(Z,F){let X=[],Q=[],N=new Set,J=Z.filter((B)=>B.userId===W)[0];if(!J){H?.("⚠️","Cannot find self in updated users");return}let f=J.joined;Z.forEach(({userId:B,joined:w})=>{if(B===W)return;if(!A.has(B)||F.type==="peer-joined"&&B===F.userId){let R={userId:B,joined:w};A.set(B,R),X.push(R)}N.add(B)});for(let B of A.keys())if(!N.has(B)||F.type==="peer-left"&&B===F.userId)A.delete(B),Q.push({userId:B});if(X.length)V.onPeerJoined(X,f);if(Q.length)V.onPeerLeft(Q)}return P(),{send(Z,F,X){E(Z,F,X)},exitRoom:()=>{x=!0,clearTimeout(j),D.close()}}}function c({userId:V,worldId:W,room:K,host:S,autoRejoin:M=!0,onOpen:z,onClose:H,onError:x,onPeerJoined:O,onPeerLeft:D,onIceUrl:j,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:W,room:K,host:S,autoRejoin:M,onOpen:z,onClose:H,onError:x,onPeerJoined:O,onPeerLeft:D,onIceUrl:j,onMessage:_});let T=new Worker(C,{type:"module"}),k=!1,E=(P)=>{let Y=P.data;if(Y.kind==="open")z?.();else if(Y.kind==="close")T.terminate(),H?.(Y.ev);else if(Y.kind==="error")x?.();else if(Y.kind==="peer-joined")O(Y.users.map((Z)=>({userId:Z.userId,joined:Z.joined})),Y.joined);else if(Y.kind==="peer-left")D(Y.users);else if(Y.kind==="ice-server")j?.(Y.url,Y.expiration);else if(Y.kind==="message")_(Y.type,Y.payload,Y.fromUserId);else if(Y.kind==="log")A?.(Y.direction,Y.obj)};return T.addEventListener("message",E),T.postMessage({cmd:"enter",userId:V,worldId:W,room:K,host:S,autoRejoin:M}),{exitRoom:()=>{k=!0,T.removeEventListener("message",E),T.postMessage({cmd:"exit"})},send:(P,Y,Z)=>{T.postMessage({cmd:"send",toUserId:Y,host:S,room:K,type:P,payload:Z})}}}var y={iceServers:[{urls:"stun:stun.l.google.com:19302"}]};class i{iceUrlProvider;constructor(V){this.iceUrlProvider=V}rtcConfig={...y,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(W)=>{let K=3;for(let S=0;S<K;S++)try{let M=(await this.iceUrlProvider.requestIce()).url,z=await fetch(M);if(!z.ok)throw Error(`ICE endpoint failed: ${z.status}`);let H=await z.json();W(H);return}catch(M){console.warn("Failed fetching iceUrl")}}),this.rtcConfig=await this.rtcConfigPromise,this.rtcConfigPromise=void 0;return this.rtcConfig}}var g=c;function s({userId:V,worldId:W,receivePeerConnection:K,peerlessUserExpiration:S=5000,enterRoomFunction:M=g,logLine:z,onLeaveUser:H,workerUrl:x,onRoomReady:O,onRoomClose:D,onBroadcastMessage:j}){let _=V??`user-${crypto.randomUUID()}`,A=new Map,C=new Map;function T(F){H?.(F);let X=A.get(F);if(!X)return;A.delete(F);try{X.close()}catch{}}async function k(F){if(!F.connection?.pc?.remoteDescription)return;let X=F.connection.pendingRemoteIce;F.connection.pendingRemoteIce=[];for(let Q of X)try{await F.connection.pc.addIceCandidate(Q)}catch(N){z?.("⚠️ ERROR",{error:"add-ice-failed",userId:F.peer,detail:String(N)})}}let E=new v,P=new i(E);function Y({room:F,host:X}){let Q=`${X}/room/${F}`,N=C.get(Q);if(N)N.exitRoom(),C.delete(Q)}function Z({room:F,host:X}){return new Promise(async(Q,N)=>{async function J(q){if(q.connectionPromise)return q.connectionPromise;let $=new Promise(async(b)=>{q.connection={id:`conn-${crypto.randomUUID()}`,pc:new RTCPeerConnection(await P.getRtcConfig()),pendingRemoteIce:[]},q.connection.pc.onicecandidate=(h)=>{if(!h.candidate)return;R("ice",q.peer,{connectionId:q.connection?.id,ice:h.candidate.toJSON()})},q.connection.pc.onconnectionstatechange=async()=>{if(z?.("\uD83D\uDCAC",{event:"pc-state",userId:q.peer,state:q.connection?.pc?.connectionState}),q.connection?.pc?.connectionState==="failed"){q.close();let h=await f(q.peer,!0);if(h.connection?.pc)K({pc:h.connection?.pc,userId:h.peer,restart:()=>h.close()});else z?.("\uD83D\uDC64ℹ️","no pc: "+h.peer);return}},b(q.connection)});return q.connectionPromise=$,await $,q.connectionPromise=void 0,$}async function f(q,$){let b=A.get(q);if(!b||$){let h={peer:q,close(){if(this.connection)this.connection.pc.close(),this.connection=void 0;A.delete(q)},async reset(){h.close(),setTimeout(async()=>{let G=await f(q,!0);if(!G.connection?.pc){z?.("⚠️","no pc");return}K({pc:G.connection?.pc,userId:G.peer,restart:()=>G.close()}),await B(G.peer)},500)}};b=h,await J(h),A.set(b.peer,b)}else if(b){if(clearTimeout(b.expirationTimeout),b.expirationTimeout=0,!b.connection?.pc||b.connection?.pc.signalingState==="closed")await J(b)}return b.peer=q,b}async function B(q){let $=await f(q),b=$.connection?.pc,h=await b?.createOffer();await b?.setLocalDescription(h),R("offer",q,{connectionId:$.connection?.id,offer:b?.localDescription?.toJSON()})}let{exitRoom:w,send:R}=M({userId:_,worldId:W,room:F,host:X,logLine:z,workerUrl:x,autoRejoin:!0,onOpen(){O?.({room:F,host:X}),Q()},onError(){console.error("onError"),N()},onClose(q){D?.({room:F,host:X,ev:q})},onPeerJoined(q,$){q.forEach(async(b)=>{let h=await f(b.userId,!0);h.joined=b.joined;let G=h.connection?.pc;if(!G){z?.("\uD83D\uDC64ℹ️","no pc: "+b.userId);return}if(K({pc:G,userId:b.userId,restart:()=>h.close()}),b.joined>$||b.joined===$&&b.userId.localeCompare(_)>0)await B(b.userId)})},onPeerLeft(q){q.forEach(({userId:$})=>{let b=A.get($);if(!b)return;b.expirationTimeout=setTimeout(()=>T($),S??0)})},onIceUrl(q,$){E.receiveIce(q,$)},async onMessage(q,$,b){if(q==="offer"&&$.offer){let h=await f(b,!1),G=!h.connection||h.connection.pc.signalingState==="stable"?await J(h):h.connection;z?.("\uD83D\uDCAC",{type:q,signalingState:G.pc.signalingState}),G.peerConnectionId=$.connectionId,K({pc:G.pc,userId:b,restart:()=>h.close()}),await G.pc.setRemoteDescription($.offer);let L=await G.pc.createAnswer();await G.pc.setLocalDescription(L),R("answer",b,{connectionId:G.id,answer:G.pc.localDescription?.toJSON()}),await k(h);return}if(q==="answer"&&$.answer){let h=await f(b,!1),G=h.connection&&h.connection.pc.signalingState!=="closed"?h.connection:await J(h);z?.("\uD83D\uDCAC",{type:q,signalingState:G.pc.signalingState}),await G.pc.setRemoteDescription($.answer),G.peerConnectionId=$.connectionId,await k(h);return}if(q==="ice"&&$.ice){let h=await f(b,!1),G=h.connection??await h.connectionPromise;if(!G){z?.("⚠️","No connection");return}if(z?.("\uD83D\uDCAC",{type:q,signalingState:G.pc.signalingState}),G.peerConnectionId&&$.connectionId!==G.peerConnectionId){z?.("⚠️","Mismatch peerConnectionID"+$.connectionId+"vs"+G.peerConnectionId);return}if(!G.pc.remoteDescription||!G.peerConnectionId){G.peerConnectionId=$.connectionId,G.pendingRemoteIce.push($.ice);return}try{await G.pc.addIceCandidate($.ice)}catch(L){z?.("⚠️ ERROR",{error:"add-ice-failed",userId:h.peer,detail:String(L)})}return}if(q==="broadcast")j?.($,b)}}),I=E.addRequester((q)=>R(q,"server"));C.set(`${X}/room/${F}`,{exitRoom:()=>{w(),I()},room:F,host:X,broadcast:(q)=>R("broadcast","server",q)})})}return{userId:_,enterRoom:Z,exitRoom:Y,leaveUser:T,async reset(F){A.get(F)?.reset()},broadcast(F){C.forEach((X)=>X.broadcast(F))},end(){C.forEach(({exitRoom:F})=>F()),C.clear(),A.forEach(({peer:F})=>T(F)),A.clear()}}}export{s as collectPeerConnections};
2
2
 
3
- //# debugId=8E74421F9D5F8A3A64756E2164756E21
3
+ //# debugId=676E5AB7574F379664756E2164756E21
4
4
  //# sourceMappingURL=webrtc-peer-collector.js.map
@@ -3,12 +3,12 @@
3
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
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",
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",
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",
6
+ "export interface IPeer {\n userId: string;\n joined: number;\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[], selfJoined: number): void;\n onPeerLeft(users: { userId: string }[]): void;\n onIceUrl?(url: string, expiration: number): void;\n onMessage(type: T, payload: P, from: string): void;\n autoRejoin?: boolean;\n}): {\n exitRoom: () => void;\n send: <P extends any>(\n type: T,\n userId: \"server\" | string,\n payload?: P,\n ) => void;\n} {\n type Message = {\n type: \"peer-joined\" | \"peer-left\" | \"ice-server\" | T;\n userId: string;\n users: { userId: string; joined: number }[];\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>();\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, msg.userId);\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, 15000);\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(\n updatedUsers: { userId: string; joined: number }[],\n msg: Message,\n ) {\n const joined: IPeer[] = [];\n const left: { userId: string }[] = [];\n const updatedPeerSet = new Set<string>();\n\n const selfPeer = updatedUsers.filter((peer) => peer.userId === userId)[0];\n if (!selfPeer) {\n logLine?.(\"⚠️\", \"Cannot find self in updated users\");\n return;\n }\n const selfJoined = selfPeer.joined;\n\n updatedUsers.forEach(({ userId: pUserId, joined: joinedTime }) => {\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 joined: joinedTime,\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, selfJoined);\n if (left.length) params.onPeerLeft(left);\n }\n\n // Start initial connection\n connect();\n\n return {\n send(type, userId, payload) {\n send(type, userId, payload);\n },\n exitRoom: () => {\n exited = true;\n clearTimeout(timeoutId);\n ws.close();\n },\n };\n}\n",
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[], selfJoined: number) => void;\n onPeerLeft: (users: { userId: string }[]) => void;\n onIceUrl?(url: string, expiration: number): void;\n onMessage: (type: T, payload: P, from: string) => 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 send: <P extends any>(\n type: T,\n userId: \"server\" | string,\n payload?: P,\n ) => 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 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(\n ev.users.map((ev) => ({ userId: ev.userId, joined: ev.joined })),\n ev.joined,\n );\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, 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 send: (type, toUserId, payload) => {\n worker.postMessage({\n cmd: \"send\",\n toUserId,\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",
8
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 const promise = new Promise<Connection>(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 state.connectionPromise = promise;\n await promise;\n state.connectionPromise = undefined;\n return promise;\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"
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: string;\n joined?: number;\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,\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 const promise = new Promise<Connection>(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 send(\"ice\", state.peer, {\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,\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,\n restart: () => userState.close(),\n });\n } else {\n logLine?.(\"👤ℹ️\", \"no pc: \" + userState.peer);\n }\n return;\n }\n };\n\n resolve(state.connection);\n });\n state.connectionPromise = promise;\n await promise;\n state.connectionPromise = undefined;\n return promise;\n }\n\n async function getPeer(\n peer: string,\n forceReset?: boolean,\n ): Promise<UserState> {\n let state = users.get(peer);\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);\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,\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, 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(userId: string) {\n // Offer flow: createOffer -> setLocalDescription -> send localDescription\n const state = await getPeer(userId);\n const pc = state.connection?.pc;\n const offer = await pc?.createOffer();\n await pc?.setLocalDescription(offer);\n send(\"offer\", userId, {\n connectionId: state.connection?.id,\n offer: pc?.localDescription?.toJSON(),\n });\n }\n\n const { exitRoom, send } = 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[], selfJoined: number) {\n joiningUsers.forEach(async (user) => {\n const state = await getPeer(user.userId, true);\n state.joined = user.joined;\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 if (\n user.joined > selfJoined ||\n (user.joined === selfJoined &&\n user.userId.localeCompare(userId) > 0)\n ) {\n await makeOffer(user.userId);\n }\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: string) {\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,\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 send(\"answer\", from, {\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,\n detail: String(e),\n });\n }\n return;\n }\n\n if (type === \"broadcast\") {\n onBroadcastMessage?.(payload, from);\n }\n },\n });\n\n const removeRequester = iceUrlProvider.addRequester((command) =>\n send(command, \"server\"),\n );\n\n roomsEntered.set(`${host}/room/${room}`, {\n exitRoom: () => {\n exitRoom();\n removeRequester();\n },\n room,\n host,\n broadcast: (payload) => send(\"broadcast\", \"server\", 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));\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"
10
10
  ],
11
- "mappings": "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",
12
- "debugId": "8E74421F9D5F8A3A64756E2164756E21",
11
+ "mappings": "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",
12
+ "debugId": "676E5AB7574F379664756E2164756E21",
13
13
  "names": []
14
14
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dobuki/hello-worker",
3
- "version": "1.0.80",
3
+ "version": "1.0.81",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",