@dobuki/hello-worker 1.0.71 → 1.0.73

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
- import { EnterRoom } from "./signal-room";
1
+ import { EnterRoom } from "./signal/signal-room";
2
2
  import { SigType, SigPayload } from "./webrtc-peer-collector";
3
3
  type UserListener = (user: string, action: "join" | "leave", users: string[]) => void;
4
4
  export declare function enterWorld<S extends string | ArrayBufferView = string | ArrayBufferView, R extends string | ArrayBufferLike = string | ArrayBufferLike>({ userId: passedUserId, worldId, logLine, enterRoomFunction, peerlessUserExpiration, workerUrl, onRoomReady, onRoomClose, dataChannelOptions, }: {
@@ -1 +1 @@
1
- {"version":3,"file":"enter-world.d.ts","sourceRoot":"","sources":["../src/browser/enter-world.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAa,MAAM,eAAe,CAAC;AACrD,OAAO,EACL,OAAO,EACP,UAAU,EAEX,MAAM,yBAAyB,CAAC;AAEjC,KAAK,YAAY,GAAG,CAClB,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,GAAG,OAAO,EACxB,KAAK,EAAE,MAAM,EAAE,KACZ,IAAI,CAAC;AAEV,wBAAgB,UAAU,CACxB,CAAC,SAAS,MAAM,GAAG,eAAe,GAAG,MAAM,GAAG,eAAe,EAC7D,CAAC,SAAS,MAAM,GAAG,eAAe,GAAG,MAAM,GAAG,eAAe,EAC7D,EACA,MAAM,EAAE,YAAY,EACpB,OAAO,EACP,OAAO,EACP,iBAA6B,EAC7B,sBAAsB,EACtB,SAAS,EACT,WAAW,EACX,WAAW,EACX,kBAAkB,GACnB,EAAE;IACD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,GAAG,KAAK,IAAI,CAAC;IACjD,iBAAiB,CAAC,EAAE,SAAS,CAAC,OAAO,EAAE,UAAU,CAAC,CAAC;IACnD,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,SAAS,CAAC,EAAE,GAAG,CAAC;IAChB,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,EAAE,kBAAkB,CAAC;CACzC;;iBA4FqB,CAAC,WAAW,MAAM;;;;;;;;;;;;mCAaA,CAAC,IAAI,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,KAAK,IAAI;sCAJ5B,CAAC,IAAI,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,KAAK,IAAI;gCAerC,YAAY;mCAJT,YAAY;;EAmCnD"}
1
+ {"version":3,"file":"enter-world.d.ts","sourceRoot":"","sources":["../src/browser/enter-world.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAa,MAAM,sBAAsB,CAAC;AAC5D,OAAO,EACL,OAAO,EACP,UAAU,EAEX,MAAM,yBAAyB,CAAC;AAEjC,KAAK,YAAY,GAAG,CAClB,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,GAAG,OAAO,EACxB,KAAK,EAAE,MAAM,EAAE,KACZ,IAAI,CAAC;AAEV,wBAAgB,UAAU,CACxB,CAAC,SAAS,MAAM,GAAG,eAAe,GAAG,MAAM,GAAG,eAAe,EAC7D,CAAC,SAAS,MAAM,GAAG,eAAe,GAAG,MAAM,GAAG,eAAe,EAC7D,EACA,MAAM,EAAE,YAAY,EACpB,OAAO,EACP,OAAO,EACP,iBAA6B,EAC7B,sBAAsB,EACtB,SAAS,EACT,WAAW,EACX,WAAW,EACX,kBAAkB,GACnB,EAAE;IACD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,GAAG,KAAK,IAAI,CAAC;IACjD,iBAAiB,CAAC,EAAE,SAAS,CAAC,OAAO,EAAE,UAAU,CAAC,CAAC;IACnD,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,SAAS,CAAC,EAAE,GAAG,CAAC;IAChB,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,EAAE,kBAAkB,CAAC;CACzC;;iBA+FqB,CAAC,WAAW,MAAM;;;;;;;;;;;;mCAaA,CAAC,IAAI,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,KAAK,IAAI;sCAJ5B,CAAC,IAAI,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,KAAK,IAAI;gCAerC,YAAY;mCAJT,YAAY;;EAmCnD"}
@@ -1,4 +1,4 @@
1
- function c(b){let{userId:h,worldId:A,room:R,host:C,autoRejoin:y=!0,logLine:B}=b,x=!1,E=0,q,k,w=!0,T=new Map,J=`wss://${C}/room/${A}/${R}?userId=${encodeURIComponent(h)}`,$=[],K=0;function D(Q,W,M){if(!q)return B?.("\uD83D\uDC64 ➡️ ❌","no ws available"),!1;if(x||q.readyState!==WebSocket.OPEN)return B?.("\uD83D\uDC64 ➡️ ❌","Not in opened state: "+q.readyState),!1;let G={type:Q,to:W,payload:M};return $.push(G),B?.("\uD83D\uDC64 ➡️ \uD83D\uDDA5️",G),clearTimeout(K),K=setTimeout(()=>{q.send(JSON.stringify($)),$.length=0}),!0}function L(){if(x)return;q=new WebSocket(J),q.onopen=()=>{if(w)b.onOpen?.(),w=!1;E=0},q.onmessage=(Q)=>{try{let W=JSON.parse(Q.data);(Array.isArray(W)?W:[W]).forEach((G)=>{if(B?.("\uD83D\uDDA5️ ➡️ \uD83D\uDC64",G),G.type==="peer-joined"||G.type==="peer-left")F(G.users);else if(G.type==="ice-server")b.onIceUrl?.(G.url,G.expiration);else if(G.userId)b.onMessage(G.type,G.payload,{userId:G.userId,receive:(X,S)=>D(X,G.userId,S)})})}catch{B?.("⚠️ ERROR",{error:"invalid-json"})}},q.onclose=(Q)=>{let M=[1001,1006,1011,1012,1013].includes(Q.code);if(y&&!x&&M){let G=Math.min(Math.pow(2,E)*1000,30000),X=Math.random()*1000,S=G+X;F([]),B?.("\uD83D\uDD04 RECONNECTING",{attempt:E+1,delayMs:Math.round(S)}),E++,k=setTimeout(L,S)}else b.onClose?.({code:Q.code,reason:Q.reason,wasClean:Q.wasClean})},q.onerror=(Q)=>{console.error("WS Error",Q),b.onError?.()}}function F(Q){let W=[],M=[],G=new Set;Q.forEach(({userId:X})=>{if(X===h)return;if(!T.has(X)){let S={userId:X,receive:(P,v)=>D(P,X,v)};T.set(X,S),W.push(S)}G.add(X)});for(let X of T.keys())if(!G.has(X))T.delete(X),M.push({userId:X});if(W.length)b.onPeerJoined(W);if(M.length)b.onPeerLeft(M)}return L(),{sendToServer(Q,W){D(Q,"server",W)},exitRoom:()=>{x=!0,clearTimeout(k),q.close()}}}function m({userId:b,worldId:h,room:A,host:R,autoRejoin:C=!0,onOpen:y,onClose:B,onError:x,onPeerJoined:E,onPeerLeft:q,onIceUrl:k,onMessage:w,logLine:T,workerUrl:J}){if(!J)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"),c({userId:b,worldId:h,room:A,host:R,autoRejoin:C,onOpen:y,onClose:B,onError:x,onPeerJoined:E,onPeerLeft:q,onIceUrl:k,onMessage:w});let $=new Worker(J,{type:"module"}),K=!1;function D({userId:F}){return{userId:F,receive:(Q,W)=>{if(K)return!1;return $.postMessage({cmd:"send",toUserId:F,host:R,room:A,type:Q,payload:W}),!0}}}let L=(F)=>{let Q=F.data;if(Q.kind==="open")y?.();else if(Q.kind==="close")$.terminate(),B?.(Q.ev);else if(Q.kind==="error")x?.();else if(Q.kind==="peer-joined")E(Q.users.map((W)=>D({userId:W.userId})));else if(Q.kind==="peer-left")q(Q.users);else if(Q.kind==="ice-server")k?.(Q.url,Q.expiration);else if(Q.kind==="message")w(Q.type,Q.payload,D({userId:Q.fromUserId}));else if(Q.kind==="log")T?.(Q.direction,Q.obj)};return $.addEventListener("message",L),$.postMessage({cmd:"enter",userId:b,worldId:h,room:A,host:R,autoRejoin:C}),{exitRoom:()=>{K=!0,$.removeEventListener("message",L),$.postMessage({cmd:"exit"})},sendToServer:(F,Q)=>{$.postMessage({cmd:"send",toUserId:"server",host:R,room:A,type:F,payload:Q})}}}var i=m;function u({userId:b,worldId:h,receivePeerConnection:A,peerlessUserExpiration:R=5000,fallbackRtcConfig:C={iceServers:[{urls:"stun:stun.l.google.com:19302"}]},enterRoomFunction:y=i,logLine:B,onLeaveUser:x,workerUrl:E,onRoomReady:q,onRoomClose:k,onBroadcastMessage:w}){let T=b??`user-${crypto.randomUUID()}`,J=new Map,$=void 0,K={...C,timestamp:Date.now()},D=new Map;async function L(G){if(G)try{let X=await fetch(G);if(!X.ok)throw Error(`ICE endpoint failed: ${X.status}`);K=await X.json()}catch(X){console.warn("Using fallback rtcConfig:",X)}return K}function F(G){x?.(G);let X=J.get(G);if(!X)return;try{X.pc?.close()}catch{}J.delete(G)}async function Q(G){if(!G.pc?.remoteDescription)return;let X=G.pendingRemoteIce;G.pendingRemoteIce=[];for(let S of X)try{await G.pc.addIceCandidate(S)}catch(P){B?.("⚠️ ERROR",{error:"add-ice-failed",userId:G.userId,detail:String(P)})}}function W({room:G,host:X}){let S=`${X}/room/${G}`,P=D.get(S);if(P)P.exitRoom(),D.delete(S)}function M({room:G,host:X}){return new Promise(async(S,P)=>{async function v(Z){let Y=Date.now();if(Y-(K?.timestamp??0)>1e4){let H=!$||$.expiration-Y<2000?await f():$;K=await L(H.url)}return Z.pc=new RTCPeerConnection(K),Z.pc.onicecandidate=(H)=>{if(!H.candidate)return;Z.peer.receive("ice",H.candidate.toJSON())},Z.pc.onconnectionstatechange=()=>{B?.("\uD83D\uDCAC",{event:"pc-state",userId:Z.userId,state:Z.pc?.connectionState})},Z.pc}async function V(Z){let Y=J.get(Z.userId),H=!1;if(!Y){let O={userId:Z.userId,pendingRemoteIce:[],peer:Z};await v(O),Y=O,J.set(Y.userId,Y),H=!0}else if(Y)clearTimeout(Y.expirationTimeout),Y.expirationTimeout=0;if(!Y.pc||Y.pc?.signalingState==="closed")await v(Y);return Y.peer=Z,[Y,H]}async function z(Z){let[Y]=await V(Z),H=Y.pc,O=await H?.createOffer();await H?.setLocalDescription(O),Z.receive("offer",H?.localDescription?.toJSON())}let N;async function f(){let Z=await new Promise((Y)=>{N=Y,I("request-ice")});return N=void 0,Z}let{exitRoom:_,sendToServer:I}=y({userId:T,worldId:h,room:G,host:X,logLine:B,workerUrl:E,autoRejoin:!0,onOpen(){q?.({room:G,host:X}),S()},onError(){console.error("onError"),P()},onClose(Z){k?.({room:G,host:X,ev:Z})},onPeerJoined(Z){Z.forEach(async(Y)=>{let[H,O]=await V(Y);if(!O){B?.("\uD83D\uDC64ℹ️","not a new peer: "+Y.userId);return}let j=H.pc;if(!j){B?.("\uD83D\uDC64ℹ️","no pc: "+Y.userId);return}async function U(){let g=J.get(Y.userId);if(g){g.pc=void 0;let p=await v(g);A({pc:p,userId:Y.userId,initiator:!0,restart:U}),await new Promise((l)=>setTimeout(l,3000)),await z(Y)}}A({pc:j,userId:Y.userId,initiator:!0,restart:U}),await z(Y)})},onPeerLeft(Z){Z.forEach(({userId:Y})=>{let H=J.get(Y);if(!H)return;H.expirationTimeout=setTimeout(()=>F(Y),R??0)})},onIceUrl(Z,Y){$={url:Z,expiration:Y},N?.($)},async onMessage(Z,Y,H){let[O]=await V(H),j=O.pc;if(!j)return;if(Z==="offer"){A({pc:j,userId:H.userId,initiator:!1,restart(){O.pc=void 0}}),await j.setRemoteDescription(Y);let U=await j.createAnswer();await j.setLocalDescription(U),H.receive("answer",j.localDescription?.toJSON()),await Q(O);return}if(Z==="answer"){await j.setRemoteDescription(Y),await Q(O);return}if(Z==="ice"){let U=Y;if(!j.remoteDescription){O.pendingRemoteIce.push(U);return}try{await j.addIceCandidate(U)}catch(g){B?.("⚠️ ERROR",{error:"add-ice-failed",userId:O.userId,detail:String(g)})}return}if(Z==="broadcast")w?.(Y,H.userId)}});D.set(`${X}/room/${G}`,{exitRoom:_,room:G,host:X,broadcast:(Z)=>{I("broadcast",Z)}})})}return{userId:T,enterRoom:M,exitRoom:W,leaveUser:F,broadcast(G){D.forEach((X)=>X.broadcast(G))},end(){D.forEach(({exitRoom:G})=>G()),D.clear(),J.forEach(({userId:G})=>F(G)),J.clear()}}}function XG({userId:b,worldId:h,logLine:A,enterRoomFunction:R=m,peerlessUserExpiration:C,workerUrl:y,onRoomReady:B,onRoomClose:x,dataChannelOptions:E}){let q=[],k=new Set;function w(V,z,N,f){if(N){let _=V.createDataChannel("data",E);J(z,_,f),$.set(z,_)}else{let _=function(I){let Z=I.channel;J(z,Z,f),$.set(z,Z)};return V.addEventListener("datachannel",_),()=>{V.removeEventListener("datachannel",_)}}}function T(V,z){k.forEach((N)=>N(V,z))}function J(V,z,N){z.onopen=()=>{A?.("\uD83D\uDCAC",{event:"dc-open",userId:V}),q.push(V),K.forEach((_)=>_(V,"join",q))};let f=({data:_})=>{T(_,V)};z.addEventListener("message",f),z.addEventListener("close",()=>{A?.("\uD83D\uDCAC",{event:"dc-close",userId:V}),q.splice(q.indexOf(V),1),K.forEach((_)=>_(V,"leave",q)),z.removeEventListener("message",f),N?.()}),z.onerror=()=>A?.("⚠️ ERROR",{error:"dc-error",userId:V})}let $=new Map,K=new Set,{userId:D,enterRoom:L,exitRoom:F,leaveUser:Q,broadcast:W,end:M}=u({userId:b,worldId:h,enterRoomFunction:R,logLine:A,workerUrl:y,peerlessUserExpiration:C,onRoomReady:B,onRoomClose:x,onLeaveUser(V){let z=$.get(V);try{z?.close()}catch{}$.delete(V)},receivePeerConnection({pc:V,userId:z,initiator:N,restart:f}){w(V,z,N,f)},onBroadcastMessage(V,z){T(V,z),A?.("\uD83D\uDCE2",{event:"broadcast",userId:D,data:V})}});function G(V,z){$.forEach((N,f)=>{if(z&&f!==z)return;if(N.readyState==="open")N.send(V)})}function X(V){k.delete(V)}function S(V){return k.add(V),()=>{X(V)}}function P(V){K.delete(V)}function v(V){return K.add(V),()=>{P(V)}}return{userId:D,send:G,broadcast:W,enterRoom:L,exitRoom:F,leaveUser:Q,getUsers:()=>q,addMessageListener:S,removeMessageListener:X,addUserListener:v,removeUserListener:P,end(){$.forEach((V)=>{try{V.close()}catch{}}),$.clear(),M(),K.clear(),q.length=0}}}export{XG as enterWorld};
1
+ function i(K){let{userId:C,worldId:b,room:j,host:w,autoRejoin:v=!0,logLine:W}=K,f=!1,x=0,$,_,k=!0,h=new Map,q=`wss://${w}/room/${b}/${j}?userId=${encodeURIComponent(C)}`,Y=[],H=0;function E(B,A,L){if(!$)return W?.("\uD83D\uDC64 ➡️ ❌","no ws available"),!1;let D={type:B,to:A,payload:L};if(Y.push(D),W?.("\uD83D\uDC64 ➡️ \uD83D\uDDA5️",D),clearTimeout(H),f||$.readyState!==WebSocket.OPEN)return W?.("\uD83D\uDC64 ➡️ ❌","Not in opened state: "+$.readyState),!1;return H=setTimeout(()=>{$.send(JSON.stringify(Y)),Y.length=0}),!0}function P(){if(f)return;$=new WebSocket(q),$.onopen=()=>{if(k)K.onOpen?.(),k=!1;x=0},$.onmessage=(B)=>{try{let A=JSON.parse(B.data);(Array.isArray(A)?A:[A]).forEach((D)=>{if(W?.("\uD83D\uDDA5️ ➡️ \uD83D\uDC64",D),D.type==="peer-joined"||D.type==="peer-left")J(D.users);else if(D.type==="ice-server")K.onIceUrl?.(D.url,D.expiration);else if(D.userId)K.onMessage(D.type,D.payload,{userId:D.userId,receive:(G,F)=>E(G,D.userId,F)})})}catch{W?.("⚠️ ERROR",{error:"invalid-json"})}},$.onclose=(B)=>{let L=[1001,1006,1011,1012,1013].includes(B.code);if(v&&!f&&L){let D=Math.min(Math.pow(2,x)*1000,30000),G=Math.random()*1000,F=D+G;W?.("\uD83D\uDD04 RECONNECTING",{attempt:x+1,delayMs:Math.round(F)}),x++,_=setTimeout(P,F)}else K.onClose?.({code:B.code,reason:B.reason,wasClean:B.wasClean})},$.onerror=(B)=>{console.error("WS Error",B),K.onError?.()}}function J(B){let A=[],L=[],D=new Set;B.forEach(({userId:G})=>{if(G===C)return;if(!h.has(G)){let F={userId:G,receive:(R,n)=>E(R,G,n)};h.set(G,F),A.push(F)}D.add(G)});for(let G of h.keys())if(!D.has(G))h.delete(G),L.push({userId:G});if(A.length)K.onPeerJoined(A);if(L.length)K.onPeerLeft(L)}return P(),{sendToServer(B,A){E(B,"server",A)},exitRoom:()=>{f=!0,clearTimeout(_),$.close()}}}function c({userId:K,worldId:C,room:b,host:j,autoRejoin:w=!0,onOpen:v,onClose:W,onError:f,onPeerJoined:x,onPeerLeft:$,onIceUrl:_,onMessage:k,logLine:h,workerUrl:q}){if(!q)return console.warn("Warning: enterRoom called without workerUrl; this may cause issues in some environments. You should pass workerUrl explicitly. Use:","https://cdn.jsdelivr.net/npm/@dobuki/hello-worker/dist/signal-room.worker.min.js"),i({userId:K,worldId:C,room:b,host:j,autoRejoin:w,onOpen:v,onClose:W,onError:f,onPeerJoined:x,onPeerLeft:$,onIceUrl:_,onMessage:k});let Y=new Worker(q,{type:"module"}),H=!1;function E({userId:J}){return{userId:J,receive:(B,A)=>{if(H)return!1;return Y.postMessage({cmd:"send",toUserId:J,host:j,room:b,type:B,payload:A}),!0}}}let P=(J)=>{let B=J.data;if(B.kind==="open")v?.();else if(B.kind==="close")Y.terminate(),W?.(B.ev);else if(B.kind==="error")f?.();else if(B.kind==="peer-joined")x(B.users.map((A)=>E({userId:A.userId})));else if(B.kind==="peer-left")$(B.users);else if(B.kind==="ice-server")_?.(B.url,B.expiration);else if(B.kind==="message")k(B.type,B.payload,E({userId:B.fromUserId}));else if(B.kind==="log")h?.(B.direction,B.obj)};return Y.addEventListener("message",P),Y.postMessage({cmd:"enter",userId:K,worldId:C,room:b,host:j,autoRejoin:w}),{exitRoom:()=>{H=!0,Y.removeEventListener("message",P),Y.postMessage({cmd:"exit"})},sendToServer:(J,B)=>{Y.postMessage({cmd:"send",toUserId:"server",host:j,room:b,type:J,payload:B})}}}var m=c;function I({userId:K,worldId:C,receivePeerConnection:b,peerlessUserExpiration:j=5000,fallbackRtcConfig:w={iceServers:[{urls:"stun:stun.l.google.com:19302"}]},enterRoomFunction:v=m,logLine:W,onLeaveUser:f,workerUrl:x,onRoomReady:$,onRoomClose:_,onBroadcastMessage:k}){let h=K??`user-${crypto.randomUUID()}`,q=new Map,Y=void 0,H={...w,timestamp:Date.now()},E=new Map;async function P(D){if(D)try{let G=await fetch(D);if(!G.ok)throw Error(`ICE endpoint failed: ${G.status}`);H=await G.json()}catch(G){console.warn("Using fallback rtcConfig:",G)}return H}function J(D){f?.(D);let G=q.get(D);if(!G)return;q.delete(D);try{G.pc?.close()}catch{}}async function B(D){if(!D.pc?.remoteDescription)return;let G=D.pendingRemoteIce;D.pendingRemoteIce=[];for(let F of G)try{await D.pc.addIceCandidate(F)}catch(R){W?.("⚠️ ERROR",{error:"add-ice-failed",userId:D.userId,detail:String(R)})}}function A({room:D,host:G}){let F=`${G}/room/${D}`,R=E.get(F);if(R)R.exitRoom(),E.delete(F)}function L({room:D,host:G}){return new Promise(async(F,R)=>{async function n(Q){let X=q.get(Q.userId);if(!X)return;X.close();let S=await V(X);b({pc:S,userId:Q.userId,initiator:!0,restart:()=>n(Q)}),await new Promise((z)=>setTimeout(z,3000)),await M(Q)}async function V(Q){let X=Date.now();if(X-(H?.timestamp??0)>1e4){let S=!Y||Y.expiration-X<2000?await T():Y;H=await P(S.url)}return Q.pc=new RTCPeerConnection(H),Q.pc.onicecandidate=(S)=>{if(!S.candidate)return;Q.peer.receive("ice",S.candidate.toJSON())},Q.pc.onconnectionstatechange=()=>{W?.("\uD83D\uDCAC",{event:"pc-state",userId:Q.userId,state:Q.pc?.connectionState})},Q.pc}async function Z(Q,X){let S=q.get(Q.userId);if(!S||X){let z={userId:Q.userId,pendingRemoteIce:[],peer:Q,initiateForThisUser:S?.initiateForThisUser??!1,close(){this.pc?.close(),this.pc=void 0}};await V(z),S=z,q.set(S.userId,S)}else if(S)clearTimeout(S.expirationTimeout),S.expirationTimeout=0;if(!S.pc||S.pc?.signalingState==="closed")await V(S);return S.peer=Q,S}async function M(Q){let S=(await Z(Q)).pc,z=await S?.createOffer();await S?.setLocalDescription(z),Q.receive("offer",S?.localDescription?.toJSON())}let N;async function T(){let Q=await new Promise((X)=>{N=X,y("request-ice")});return N=void 0,Q}let{exitRoom:U,sendToServer:y}=v({userId:h,worldId:C,room:D,host:G,logLine:W,workerUrl:x,autoRejoin:!0,onOpen(){$?.({room:D,host:G}),F()},onError(){console.error("onError"),R()},onClose(Q){_?.({room:D,host:G,ev:Q})},onPeerJoined(Q){Q.forEach(async(X)=>{let S=q.has(X.userId),z=await Z(X,!0);if(!S)z.initiateForThisUser=!0;let O=z.pc;if(!O){W?.("\uD83D\uDC64ℹ️","no pc: "+X.userId);return}if(b({pc:O,userId:X.userId,initiator:z.initiateForThisUser,restart:z.initiateForThisUser?()=>n(X):()=>z.close()}),z.initiateForThisUser)await M(X)})},onPeerLeft(Q){Q.forEach(({userId:X})=>{let S=q.get(X);if(!S)return;S.expirationTimeout=setTimeout(()=>J(X),j??0)})},onIceUrl(Q,X){Y={url:Q,expiration:X},N?.(Y)},async onMessage(Q,X,S){let z=await Z(S),O=z.pc;if(!O)return;if(Q==="offer"){z.initiateForThisUser=!1,b({pc:O,userId:S.userId,initiator:!1,restart(){z.close()}}),await O.setRemoteDescription(X);let g=await O.createAnswer();await O.setLocalDescription(g),S.receive("answer",O.localDescription?.toJSON()),await B(z);return}if(Q==="answer"){await O.setRemoteDescription(X),await B(z);return}if(Q==="ice"){let g=X;if(!O.remoteDescription){z.pendingRemoteIce.push(g);return}try{await O.addIceCandidate(g)}catch(d){W?.("⚠️ ERROR",{error:"add-ice-failed",userId:z.userId,detail:String(d)})}return}if(Q==="broadcast")k?.(X,S.userId)}});E.set(`${G}/room/${D}`,{exitRoom:U,room:D,host:G,broadcast:(Q)=>{y("broadcast",Q)}})})}return{userId:h,enterRoom:L,exitRoom:A,leaveUser:J,broadcast(D){E.forEach((G)=>G.broadcast(D))},end(){E.forEach(({exitRoom:D})=>D()),E.clear(),q.forEach(({userId:D})=>J(D)),q.clear()}}}function VD({userId:K,worldId:C,logLine:b,enterRoomFunction:j=c,peerlessUserExpiration:w,workerUrl:v,onRoomReady:W,onRoomClose:f,dataChannelOptions:x}){let $=new Set,_=new Set;function k(V,Z,M,N){if(M){let T=V.createDataChannel("data",x);q(Z,T,N),Y.set(Z,T)}else{let T=function(U){let y=U.channel;q(Z,y,N),Y.set(Z,y)};return V.addEventListener("datachannel",T),()=>{V.removeEventListener("datachannel",T)}}}function h(V,Z){_.forEach((M)=>M(V,Z))}function q(V,Z,M){Z.onopen=()=>{b?.("\uD83D\uDCAC",{event:"dc-open",userId:V}),$.add(V),H.forEach((T)=>T(V,"join",[...$]))};let N=({data:T})=>{h(T,V)};Z.addEventListener("message",N),Z.addEventListener("close",()=>{b?.("\uD83D\uDCAC",{event:"dc-close",userId:V}),$.delete(V),H.forEach((T)=>T(V,"leave",[...$])),Z.removeEventListener("message",N),M?.()}),Z.onerror=()=>b?.("⚠️ ERROR",{error:"dc-error",userId:V})}let Y=new Map,H=new Set,{userId:E,enterRoom:P,exitRoom:J,leaveUser:B,broadcast:A,end:L}=I({userId:K,worldId:C,enterRoomFunction:j,logLine:b,workerUrl:v,peerlessUserExpiration:w,onRoomReady:W,onRoomClose:f,onLeaveUser(V){let Z=Y.get(V);try{Z?.close()}catch{}Y.delete(V)},receivePeerConnection({pc:V,userId:Z,initiator:M,restart:N}){k(V,Z,M,N)},onBroadcastMessage(V,Z){h(V,Z),b?.("\uD83D\uDCE2",{event:"broadcast",userId:E,data:V})}});function D(V,Z){Y.forEach((M,N)=>{if(Z&&N!==Z)return;if(M.readyState==="open")M.send(V)})}function G(V){_.delete(V)}function F(V){return _.add(V),()=>{G(V)}}function R(V){H.delete(V)}function n(V){return H.add(V),()=>{R(V)}}return{userId:E,send:D,broadcast:A,enterRoom:P,exitRoom:J,leaveUser:B,getUsers:()=>[...$],addMessageListener:F,removeMessageListener:G,addUserListener:n,removeUserListener:R,end(){Y.forEach((V)=>{try{V.close()}catch{}}),Y.clear(),L(),H.clear(),$.clear()}}}export{VD as enterWorld};
2
2
 
3
- //# debugId=BDFDBC2E7057D89B64756E2164756E21
3
+ //# debugId=A17EC2E586033FAE64756E2164756E21
4
4
  //# sourceMappingURL=enter-world.js.map
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "version": 3,
3
- "sources": ["../src/browser/impl/signal-room.ts", "../src/browser/signal-room.ts", "../src/browser/webrtc-peer-collector.ts", "../src/browser/enter-world.ts"],
3
+ "sources": ["../src/browser/signal/impl/signal-room.ts", "../src/browser/signal/signal-room.ts", "../src/browser/webrtc-peer-collector.ts", "../src/browser/enter-world.ts"],
4
4
  "sourcesContent": [
5
- "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 if (exited || ws.readyState !== WebSocket.OPEN) {\n logLine?.(\"👤 ➡️ ❌\", \"Not in opened state: \" + ws.readyState);\n return false;\n }\n const obj: OutMessage = { type, to, payload };\n accumulatedMessages.push(obj);\n // ws.send(JSON.stringify(obj));\n logLine?.(\"👤 ➡️ 🖥️\", obj);\n clearTimeout(timeout);\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);\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 updatePeers([]);\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 }[]) {\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 (!peers.has(pUserId)) {\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 (!updatedPeerSet.has(pUserId)) {\n peers.delete(pUserId);\n left.push({ userId: pUserId });\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",
5
+ "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);\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 }[]) {\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 (!peers.has(pUserId)) {\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 (!updatedPeerSet.has(pUserId)) {\n peers.delete(pUserId);\n left.push({ userId: pUserId });\n }\n }\n // Notify peer joined first then peer left. (avoid disconnect in case the peer leaving / joining is on the same user).\n if (joined.length) params.onPeerJoined(joined);\n if (left.length) params.onPeerLeft(left);\n }\n\n // Start initial connection\n connect();\n\n return {\n sendToServer(type, payload) {\n send(type, \"server\", payload);\n },\n exitRoom: () => {\n exited = true;\n clearTimeout(timeoutId);\n ws.close();\n },\n };\n}\n",
6
6
  "import type { IPeer } from \"./impl/signal-room.js\";\nimport { enterRoom as baseEnterRoom } from \"./impl/signal-room.js\";\nimport { RoomEvent, WorkerCommand } from \"./signal-room.worker.js\";\n\nexport function enterRoom<T extends string, P = any>({\n userId,\n worldId,\n room,\n host,\n autoRejoin = true,\n onOpen,\n onClose,\n onError,\n onPeerJoined,\n onPeerLeft,\n onIceUrl,\n onMessage,\n logLine,\n workerUrl,\n}: {\n userId: string;\n worldId: string;\n room: string;\n host: string;\n autoRejoin?: boolean;\n onOpen?: () => void;\n onClose?: (ev: Pick<CloseEvent, \"code\" | \"reason\" | \"wasClean\">) => void;\n onError?: () => void;\n onPeerJoined: (users: IPeer<T, P>[]) => void;\n onPeerLeft: (users: { userId: string }[]) => void;\n onIceUrl?(url: string, expiration: number): void;\n onMessage: (type: T, payload: P, from: IPeer<T, P>) => void;\n logLine?: (direction: string, obj?: any) => void;\n\n // Pass the URL to your worker file (bundler will handle it)\n workerUrl?: URL;\n}): {\n exitRoom: () => void;\n sendToServer: <P extends any>(type: T, payload?: P) => void;\n} {\n if (!workerUrl) {\n const CDN_WORKER_URL = `https://cdn.jsdelivr.net/npm/@dobuki/hello-worker/dist/signal-room.worker.min.js`;\n\n console.warn(\n \"Warning: enterRoom called without workerUrl; this may cause issues in some environments. You should pass workerUrl explicitly. Use:\",\n CDN_WORKER_URL,\n );\n return baseEnterRoom<T, P>({\n userId,\n worldId,\n room,\n host,\n autoRejoin,\n onOpen,\n onClose,\n onError,\n onPeerJoined,\n onPeerLeft,\n onIceUrl,\n onMessage,\n });\n }\n const worker = new Worker(workerUrl, { type: \"module\" });\n let exited = false;\n\n function makeUser({ userId }: { userId: string }): IPeer<T, P> {\n return {\n userId,\n receive: (type: T, payload: P) => {\n if (exited) return false;\n worker.postMessage({\n cmd: \"send\",\n toUserId: userId,\n host,\n room,\n type,\n payload,\n } as WorkerCommand);\n return true;\n },\n };\n }\n\n const onWorkerMessage = (e: MessageEvent<RoomEvent<T, P>>) => {\n const ev = e.data;\n\n if (ev.kind === \"open\") onOpen?.();\n else if (ev.kind === \"close\") {\n worker.terminate();\n onClose?.(ev.ev);\n } else if (ev.kind === \"error\") onError?.();\n else if (ev.kind === \"peer-joined\")\n onPeerJoined(ev.users.map((ev) => makeUser({ userId: ev.userId })));\n else if (ev.kind === \"peer-left\") onPeerLeft(ev.users);\n else if (ev.kind === \"ice-server\") onIceUrl?.(ev.url, ev.expiration);\n else if (ev.kind === \"message\")\n onMessage(ev.type, ev.payload, makeUser({ userId: ev.fromUserId }));\n else if (ev.kind === \"log\") logLine?.(ev.direction, ev.obj);\n };\n\n worker.addEventListener(\"message\", onWorkerMessage);\n\n worker.postMessage({\n cmd: \"enter\",\n userId,\n worldId,\n room,\n host,\n autoRejoin,\n } as WorkerCommand);\n\n return {\n exitRoom: () => {\n exited = true;\n worker.removeEventListener(\"message\", onWorkerMessage);\n worker.postMessage({ cmd: \"exit\" } as WorkerCommand);\n },\n sendToServer: <P extends any>(type: T, payload?: P) => {\n worker.postMessage({\n cmd: \"send\",\n toUserId: \"server\",\n host,\n room,\n type,\n payload,\n } as WorkerCommand);\n },\n };\n}\n\nexport type EnterRoom<T extends string, P> = typeof enterRoom<T, P>;\n",
7
- "import { IPeer } from \"./impl/signal-room\";\nimport { EnterRoom, enterRoom } from \"./signal-room\";\n\nexport type SigType = \"offer\" | \"answer\" | \"ice\" | \"request-ice\" | \"broadcast\";\nexport type SigPayload = RTCSessionDescriptionInit | RTCIceCandidateInit;\n\ntype UserState = {\n userId: string;\n pc?: RTCPeerConnection;\n\n // ICE that arrived before we had remoteDescription\n pendingRemoteIce: RTCIceCandidateInit[];\n\n // the signaling \"user\" handle so we can send messages\n peer: IPeer<SigType, SigPayload>;\n\n expirationTimeout?: number;\n};\n\nconst DEFAULT_ENTER_ROOM = enterRoom;\n\nexport function collectPeerConnections({\n userId: passedUserId,\n worldId,\n receivePeerConnection,\n peerlessUserExpiration = 5000,\n fallbackRtcConfig = {\n iceServers: [{ urls: \"stun:stun.l.google.com:19302\" }],\n },\n enterRoomFunction: enterRoom = DEFAULT_ENTER_ROOM,\n logLine,\n onLeaveUser,\n workerUrl,\n onRoomReady,\n onRoomClose,\n onBroadcastMessage,\n}: {\n userId?: string;\n worldId: string;\n fallbackRtcConfig?: RTCConfiguration;\n enterRoomFunction?: EnterRoom<SigType, SigPayload>;\n onLeaveUser?: (userId: string) => void;\n logLine?: (direction: string, obj?: any) => void;\n workerUrl?: URL;\n peerlessUserExpiration?: number;\n receivePeerConnection(connection: {\n pc: RTCPeerConnection;\n userId: string;\n initiator: boolean;\n restart?: () => void;\n }): void;\n onRoomReady?(info: { host: string; room: string }): void;\n onRoomClose?(info: {\n host: string;\n room: string;\n ev: Pick<CloseEvent, \"reason\" | \"code\" | \"wasClean\">;\n }): void;\n onBroadcastMessage?<P extends any>(payload: P, from: string): void;\n}) {\n const userId = passedUserId ?? `user-${crypto.randomUUID()}`;\n const users: Map<string, UserState> = new Map();\n let iceUrl: { url: string; expiration: number } | undefined = undefined;\n let rtcConfig: RTCConfiguration & { timestamp: number } = {\n ...fallbackRtcConfig,\n timestamp: Date.now(),\n };\n\n const roomsEntered = new Map<\n string,\n {\n room: string;\n host: string;\n exitRoom: () => void;\n broadcast: <P extends any>(payload: P) => void;\n }\n >();\n\n async function getRtcConfig(\n iceUrl: string,\n ): Promise<RTCConfiguration & { timestamp: number }> {\n if (iceUrl) {\n try {\n const r = await fetch(iceUrl);\n if (!r.ok) throw new Error(`ICE endpoint failed: ${r.status}`);\n rtcConfig = (await r.json()) as RTCConfiguration & {\n timestamp: number;\n };\n } catch (e) {\n console.warn(\"Using fallback rtcConfig:\", e);\n }\n }\n return rtcConfig;\n }\n\n function leaveUser(userId: string) {\n onLeaveUser?.(userId);\n const p = users.get(userId);\n if (!p) return;\n try {\n p.pc?.close();\n } catch {}\n users.delete(userId);\n }\n\n async function flushRemoteIce(state: UserState) {\n if (!state.pc?.remoteDescription) return;\n\n const queued = state.pendingRemoteIce;\n state.pendingRemoteIce = [];\n\n for (const ice of queued) {\n try {\n await state.pc.addIceCandidate(ice);\n } catch (e) {\n logLine?.(\"⚠️ ERROR\", {\n error: \"add-ice-failed\",\n userId: state.userId,\n detail: String(e),\n });\n }\n }\n }\n\n function exit({ room, host }: { room: string; host: string }) {\n const key = `${host}/room/${room}`;\n const session = roomsEntered.get(key);\n if (session) {\n session.exitRoom();\n roomsEntered.delete(key);\n }\n }\n\n function enter({ room, host }: { room: string; host: string }) {\n return new Promise<void>(async (resolve, reject) => {\n async function setupPC(state: UserState) {\n const now = Date.now();\n if (now - (rtcConfig?.timestamp ?? 0) > 10000) {\n const ice =\n !iceUrl || iceUrl.expiration - now < 2000\n ? await requestIce()\n : iceUrl;\n rtcConfig = await getRtcConfig(ice.url);\n }\n state.pc = new RTCPeerConnection(rtcConfig);\n // Send local ICE candidates to this peer\n state.pc.onicecandidate = (ev) => {\n if (!ev.candidate) return;\n state.peer.receive(\"ice\", ev.candidate.toJSON());\n };\n\n state.pc.onconnectionstatechange = () => {\n logLine?.(\"💬\", {\n event: \"pc-state\",\n userId: state.userId,\n state: state.pc?.connectionState,\n });\n };\n return state.pc;\n }\n\n async function getPeer(\n peer: IPeer<SigType, SigPayload>,\n ): Promise<[UserState, boolean]> {\n let state = users.get(peer.userId);\n let isNewPeer = false;\n if (!state) {\n const newState: UserState = {\n userId: peer.userId,\n pendingRemoteIce: [],\n peer,\n };\n\n await setupPC(newState);\n state = newState;\n\n // New user\n users.set(state.userId, state);\n isNewPeer = true;\n } else if (state) {\n clearTimeout(state.expirationTimeout);\n state.expirationTimeout = 0;\n }\n if (!state.pc || state.pc?.signalingState === \"closed\") {\n await setupPC(state);\n }\n state.peer = peer;\n return [state, isNewPeer];\n }\n\n async function makeOffer(user: IPeer) {\n // Offer flow: createOffer -> setLocalDescription -> send localDescription\n const [state] = await getPeer(user);\n const pc = state.pc;\n const offer = await pc?.createOffer();\n await pc?.setLocalDescription(offer);\n user.receive(\"offer\", pc?.localDescription?.toJSON()!);\n }\n\n let icePromiseResolve:\n | undefined\n | ((url: { url: string; expiration: number }) => void);\n async function requestIce() {\n const iceUrl = await new Promise<{ url: string; expiration: number }>(\n (resolve) => {\n icePromiseResolve = resolve;\n sendToServer(\"request-ice\");\n },\n );\n icePromiseResolve = undefined;\n return iceUrl;\n }\n\n const { exitRoom, sendToServer } = enterRoom({\n userId,\n worldId,\n room,\n host,\n logLine,\n workerUrl,\n autoRejoin: true,\n\n onOpen() {\n onRoomReady?.({ room, host });\n resolve();\n },\n onError() {\n console.error(\"onError\");\n reject();\n },\n onClose(ev) {\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, isNewPeer] = await getPeer(user);\n if (!isNewPeer) {\n logLine?.(\"👤ℹ️\", \"not a new peer: \" + user.userId);\n return;\n }\n const pc = state.pc;\n if (!pc) {\n logLine?.(\"👤ℹ️\", \"no pc: \" + user.userId);\n return;\n }\n\n async function restart() {\n const state = users.get(user.userId);\n if (state) {\n state.pc = undefined;\n const pc = await setupPC(state);\n receivePeerConnection({\n pc,\n userId: user.userId,\n initiator: true,\n restart,\n });\n await new Promise((resolve) => setTimeout(resolve, 3000));\n await makeOffer(user);\n }\n }\n\n receivePeerConnection({\n pc,\n userId: user.userId,\n initiator: true,\n restart,\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, expiration) {\n iceUrl = { url, expiration };\n icePromiseResolve?.(iceUrl);\n },\n\n async onMessage(type: SigType, payload: any, from: IPeer) {\n const [state] = await getPeer(from);\n const pc = state.pc;\n if (!pc) return;\n\n if (type === \"offer\") {\n receivePeerConnection({\n pc,\n userId: from.userId,\n initiator: false,\n restart() {\n // reset PC\n state.pc = undefined;\n },\n });\n // Responder: set remote offer\n await pc.setRemoteDescription(payload as RTCSessionDescriptionInit);\n\n // Create and send answer\n const answer = await pc.createAnswer();\n await pc.setLocalDescription(answer);\n\n from.receive(\"answer\", pc.localDescription?.toJSON()!);\n\n // Now safe to apply any queued ICE from this peer\n await flushRemoteIce(state);\n return;\n }\n\n if (type === \"answer\") {\n // Initiator: set remote answer\n await pc.setRemoteDescription(payload as RTCSessionDescriptionInit);\n await flushRemoteIce(state);\n return;\n }\n\n if (type === \"ice\") {\n const ice = payload as RTCIceCandidateInit;\n\n // If we don't have remoteDescription yet, queue it\n if (!pc.remoteDescription) {\n state.pendingRemoteIce.push(ice);\n return;\n }\n\n try {\n await pc.addIceCandidate(ice);\n } catch (e) {\n logLine?.(\"⚠️ ERROR\", {\n error: \"add-ice-failed\",\n userId: state.userId,\n detail: String(e),\n });\n }\n return;\n }\n\n if (type === \"broadcast\") {\n onBroadcastMessage?.(payload, from.userId);\n }\n },\n });\n roomsEntered.set(`${host}/room/${room}`, {\n exitRoom,\n room,\n host,\n broadcast: (payload) => {\n sendToServer(\"broadcast\", payload);\n },\n });\n });\n }\n\n return {\n userId,\n enterRoom: enter,\n exitRoom: exit,\n leaveUser,\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(({ userId }) => leaveUser(userId));\n users.clear();\n },\n };\n}\n\n/*\nTurn Token ID\n<CF_TURN_TOKEN_ID>\n\nAPI Token\n<CF_RTC_API_TOKEN>\n\nCURL\ncurl \\\n\t-H \"Authorization: Bearer <CF_RTC_API_TOKEN>\" \\\n\t-H \"Content-Type: application/json\" -d '{\"ttl\": 86400}' \\\n\thttps://rtc.live.cloudflare.com/v1/turn/keys/<CF_TURN_TOKEN_ID>/credentials/generate-ice-servers\n\nJSON\n{\n\t\"iceServers\": [\n {\n \"urls\": [\n \"stun:stun.cloudflare.com:3478\",\n \"turn:turn.cloudflare.com:3478?transport=udp\",\n \"turn:turn.cloudflare.com:3478?transport=tcp\",\n \"turns:turn.cloudflare.com:5349?transport=tcp\"\n ],\n \"username\": \"xxxx\",\n \"credential\": \"yyyy\",\n }\n ]\n}\n\n*/\n",
8
- "import { EnterRoom, enterRoom } from \"./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?: (direction: string, 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: string[] = [];\n\n const messagesListeners = new Set<(data: R, from: string) => void>();\n\n function createDataChannel(\n pc: RTCPeerConnection,\n peerUserId: string,\n initiator: boolean,\n restart?: () => void,\n ) {\n if (initiator) {\n const dc = pc.createDataChannel(\"data\", dataChannelOptions);\n wireDataChannel(peerUserId, dc, restart);\n dataChannels.set(peerUserId, dc);\n } else {\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 return () => {\n pc.removeEventListener(\"datachannel\", listener);\n };\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.push(userId);\n userListeners.forEach((listener) => listener(userId, \"join\", userIds));\n };\n const onmessage = ({ data }: MessageEvent) => {\n conveyMessage(data, userId);\n // logLine(\"💬\", { event: \"dc-message\", userId, data });\n };\n dc.addEventListener(\"message\", onmessage);\n dc.addEventListener(\"close\", () => {\n logLine?.(\"💬\", { event: \"dc-close\", userId });\n userIds.splice(userIds.indexOf(userId), 1);\n userListeners.forEach((listener) => listener(userId, \"leave\", userIds));\n dc.removeEventListener(\"message\", onmessage);\n restart?.();\n });\n dc.onerror = () => logLine?.(\"⚠️ ERROR\", { error: \"dc-error\", userId });\n }\n\n const dataChannels = new Map<string, RTCDataChannel>();\n const userListeners = new Set<UserListener>();\n\n const {\n userId,\n enterRoom,\n exitRoom,\n leaveUser,\n broadcast,\n end: endPeerCollection,\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, initiator, restart }) {\n createDataChannel(pc, userId, initiator, 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 end() {\n dataChannels.forEach((dataChannel) => {\n try {\n dataChannel.close();\n } catch {}\n });\n dataChannels.clear();\n endPeerCollection();\n userListeners.clear();\n userIds.length = 0;\n },\n };\n}\n"
7
+ "import { IPeer } from \"./signal/impl/signal-room\";\nimport { EnterRoom, enterRoom } from \"./signal/signal-room\";\n\nexport type SigType = \"offer\" | \"answer\" | \"ice\" | \"request-ice\" | \"broadcast\";\nexport type SigPayload = RTCSessionDescriptionInit | RTCIceCandidateInit;\n\ntype UserState = {\n userId: string;\n pc?: RTCPeerConnection;\n\n // ICE that arrived before we had remoteDescription\n pendingRemoteIce: RTCIceCandidateInit[];\n\n // the signaling \"user\" handle so we can send messages\n peer: IPeer<SigType, SigPayload>;\n\n expirationTimeout?: number;\n initiateForThisUser: boolean;\n close: () => void;\n};\n\nconst DEFAULT_ENTER_ROOM = enterRoom;\n\nexport function collectPeerConnections({\n userId: passedUserId,\n worldId,\n receivePeerConnection,\n peerlessUserExpiration = 5000,\n fallbackRtcConfig = {\n iceServers: [{ urls: \"stun:stun.l.google.com:19302\" }],\n },\n enterRoomFunction: enterRoom = DEFAULT_ENTER_ROOM,\n logLine,\n onLeaveUser,\n workerUrl,\n onRoomReady,\n onRoomClose,\n onBroadcastMessage,\n}: {\n userId?: string;\n worldId: string;\n fallbackRtcConfig?: RTCConfiguration;\n enterRoomFunction?: EnterRoom<SigType, SigPayload>;\n onLeaveUser?: (userId: string) => void;\n logLine?: (direction: string, obj?: any) => void;\n workerUrl?: URL;\n peerlessUserExpiration?: number;\n receivePeerConnection(connection: {\n pc: RTCPeerConnection;\n userId: string;\n initiator: boolean;\n restart?: () => void;\n }): void;\n onRoomReady?(info: { host: string; room: string }): void;\n onRoomClose?(info: {\n host: string;\n room: string;\n ev: Pick<CloseEvent, \"reason\" | \"code\" | \"wasClean\">;\n }): void;\n onBroadcastMessage?<P extends any>(payload: P, from: string): void;\n}) {\n const userId = passedUserId ?? `user-${crypto.randomUUID()}`;\n const users: Map<string, UserState> = new Map();\n let iceUrl: { url: string; expiration: number } | undefined = undefined;\n let rtcConfig: RTCConfiguration & { timestamp: number } = {\n ...fallbackRtcConfig,\n timestamp: Date.now(),\n };\n\n const roomsEntered = new Map<\n string,\n {\n room: string;\n host: string;\n exitRoom: () => void;\n broadcast: <P extends any>(payload: P) => void;\n }\n >();\n\n async function getRtcConfig(\n iceUrl: string,\n ): Promise<RTCConfiguration & { timestamp: number }> {\n if (iceUrl) {\n try {\n const r = await fetch(iceUrl);\n if (!r.ok) throw new Error(`ICE endpoint failed: ${r.status}`);\n rtcConfig = (await r.json()) as RTCConfiguration & {\n timestamp: number;\n };\n } catch (e) {\n console.warn(\"Using fallback rtcConfig:\", e);\n }\n }\n return rtcConfig;\n }\n\n function leaveUser(userId: string) {\n onLeaveUser?.(userId);\n const p = users.get(userId);\n if (!p) return;\n users.delete(userId);\n try {\n p.pc?.close();\n } catch {}\n }\n\n async function flushRemoteIce(state: UserState) {\n if (!state.pc?.remoteDescription) return;\n\n const queued = state.pendingRemoteIce;\n state.pendingRemoteIce = [];\n\n for (const ice of queued) {\n try {\n await state.pc.addIceCandidate(ice);\n } catch (e) {\n logLine?.(\"⚠️ ERROR\", {\n error: \"add-ice-failed\",\n userId: state.userId,\n detail: String(e),\n });\n }\n }\n }\n\n function exit({ room, host }: { room: string; host: string }) {\n const key = `${host}/room/${room}`;\n const session = roomsEntered.get(key);\n if (session) {\n session.exitRoom();\n roomsEntered.delete(key);\n }\n }\n\n function enter({ room, host }: { room: string; host: string }) {\n return new Promise<void>(async (resolve, reject) => {\n async function restartInitiator(user: IPeer) {\n const state = users.get(user.userId);\n if (!state) return; // user left\n state.close();\n const pc = await setupPC(state);\n receivePeerConnection({\n pc,\n userId: user.userId,\n initiator: true,\n restart: () => restartInitiator(user),\n });\n await new Promise((resolve) => setTimeout(resolve, 3000));\n await makeOffer(user);\n }\n\n async function setupPC(state: UserState) {\n const now = Date.now();\n if (now - (rtcConfig?.timestamp ?? 0) > 10000) {\n const ice =\n !iceUrl || iceUrl.expiration - now < 2000\n ? await requestIce()\n : iceUrl;\n rtcConfig = await getRtcConfig(ice.url);\n }\n state.pc = new RTCPeerConnection(rtcConfig);\n // Send local ICE candidates to this peer\n state.pc.onicecandidate = (ev) => {\n if (!ev.candidate) return;\n state.peer.receive(\"ice\", ev.candidate.toJSON());\n };\n\n state.pc.onconnectionstatechange = () => {\n logLine?.(\"💬\", {\n event: \"pc-state\",\n userId: state.userId,\n state: state.pc?.connectionState,\n });\n };\n\n return state.pc;\n }\n\n async function getPeer(\n peer: IPeer<SigType, SigPayload>,\n forceReset?: boolean,\n ): Promise<UserState> {\n let state = users.get(peer.userId);\n if (!state || forceReset) {\n const newState: UserState = {\n userId: peer.userId,\n pendingRemoteIce: [],\n peer,\n initiateForThisUser: state?.initiateForThisUser ?? false,\n close() {\n this.pc?.close();\n this.pc = undefined;\n },\n };\n\n await setupPC(newState);\n state = newState;\n\n // New user\n users.set(state.userId, state);\n } else if (state) {\n clearTimeout(state.expirationTimeout);\n state.expirationTimeout = 0;\n }\n if (!state.pc || state.pc?.signalingState === \"closed\") {\n await setupPC(state);\n }\n state.peer = peer;\n return state;\n }\n\n async function makeOffer(user: IPeer) {\n // Offer flow: createOffer -> setLocalDescription -> send localDescription\n const state = await getPeer(user);\n const pc = state.pc;\n const offer = await pc?.createOffer();\n await pc?.setLocalDescription(offer);\n user.receive(\"offer\", pc?.localDescription?.toJSON()!);\n }\n\n let icePromiseResolve:\n | undefined\n | ((url: { url: string; expiration: number }) => void);\n async function requestIce() {\n const iceUrl = await new Promise<{ url: string; expiration: number }>(\n (resolve) => {\n icePromiseResolve = resolve;\n sendToServer(\"request-ice\");\n },\n );\n icePromiseResolve = undefined;\n return iceUrl;\n }\n\n const { exitRoom, sendToServer } = enterRoom({\n userId,\n worldId,\n room,\n host,\n logLine,\n workerUrl,\n autoRejoin: true,\n\n onOpen() {\n onRoomReady?.({ room, host });\n resolve();\n },\n onError() {\n console.error(\"onError\");\n reject();\n },\n onClose(ev: Pick<CloseEvent, \"reason\" | \"code\" | \"wasClean\">) {\n onRoomClose?.({ room, host, ev });\n },\n\n // Existing peers initiate to the newcomer\n onPeerJoined(joiningUsers: IPeer<SigType, SigPayload>[]) {\n joiningUsers.forEach(async (user) => {\n const hadState = users.has(user.userId);\n const state = await getPeer(user, true);\n if (!hadState) {\n state.initiateForThisUser = true;\n }\n const pc = state.pc;\n if (!pc) {\n logLine?.(\"👤ℹ️\", \"no pc: \" + user.userId);\n return;\n }\n\n receivePeerConnection({\n pc,\n userId: user.userId,\n initiator: state.initiateForThisUser,\n restart: state.initiateForThisUser\n ? () => restartInitiator(user)\n : () => state.close(),\n });\n if (state.initiateForThisUser) {\n await makeOffer(user);\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 iceUrl = { url, expiration };\n icePromiseResolve?.(iceUrl);\n },\n\n async onMessage(type: SigType, payload: any, from: IPeer) {\n const state = await getPeer(from);\n const pc = state.pc;\n if (!pc) return;\n\n if (type === \"offer\") {\n state.initiateForThisUser = false;\n receivePeerConnection({\n pc,\n userId: from.userId,\n initiator: false,\n restart() {\n // reset PC\n state.close();\n },\n });\n // Responder: set remote offer\n await pc.setRemoteDescription(payload as RTCSessionDescriptionInit);\n\n // Create and send answer\n const answer = await pc.createAnswer();\n await pc.setLocalDescription(answer);\n\n from.receive(\"answer\", pc.localDescription?.toJSON()!);\n\n // Now safe to apply any queued ICE from this peer\n await flushRemoteIce(state);\n return;\n }\n\n if (type === \"answer\") {\n // Initiator: set remote answer\n await pc.setRemoteDescription(payload as RTCSessionDescriptionInit);\n await flushRemoteIce(state);\n return;\n }\n\n if (type === \"ice\") {\n const ice = payload as RTCIceCandidateInit;\n\n // If we don't have remoteDescription yet, queue it\n if (!pc.remoteDescription) {\n state.pendingRemoteIce.push(ice);\n return;\n }\n\n try {\n await pc.addIceCandidate(ice);\n } catch (e) {\n logLine?.(\"⚠️ ERROR\", {\n error: \"add-ice-failed\",\n userId: state.userId,\n detail: String(e),\n });\n }\n return;\n }\n\n if (type === \"broadcast\") {\n onBroadcastMessage?.(payload, from.userId);\n }\n },\n });\n roomsEntered.set(`${host}/room/${room}`, {\n exitRoom,\n room,\n host,\n broadcast: (payload) => {\n sendToServer(\"broadcast\", payload);\n },\n });\n });\n }\n\n return {\n userId,\n enterRoom: enter,\n exitRoom: exit,\n leaveUser,\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(({ userId }) => leaveUser(userId));\n users.clear();\n },\n };\n}\n\n/*\nTurn Token ID\n<CF_TURN_TOKEN_ID>\n\nAPI Token\n<CF_RTC_API_TOKEN>\n\nCURL\ncurl \\\n\t-H \"Authorization: Bearer <CF_RTC_API_TOKEN>\" \\\n\t-H \"Content-Type: application/json\" -d '{\"ttl\": 86400}' \\\n\thttps://rtc.live.cloudflare.com/v1/turn/keys/<CF_TURN_TOKEN_ID>/credentials/generate-ice-servers\n\nJSON\n{\n\t\"iceServers\": [\n {\n \"urls\": [\n \"stun:stun.cloudflare.com:3478\",\n \"turn:turn.cloudflare.com:3478?transport=udp\",\n \"turn:turn.cloudflare.com:3478?transport=tcp\",\n \"turns:turn.cloudflare.com:5349?transport=tcp\"\n ],\n \"username\": \"xxxx\",\n \"credential\": \"yyyy\",\n }\n ]\n}\n\n*/\n",
8
+ "import { 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?: (direction: string, 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\n const messagesListeners = new Set<(data: R, from: string) => void>();\n\n function createDataChannel(\n pc: RTCPeerConnection,\n peerUserId: string,\n initiator: boolean,\n restart?: () => void,\n ) {\n if (initiator) {\n const dc = pc.createDataChannel(\"data\", dataChannelOptions);\n wireDataChannel(peerUserId, dc, restart);\n dataChannels.set(peerUserId, dc);\n } else {\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 return () => {\n pc.removeEventListener(\"datachannel\", listener);\n };\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.addEventListener(\"close\", () => {\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 restart?.();\n });\n dc.onerror = () => logLine?.(\"⚠️ ERROR\", { error: \"dc-error\", userId });\n }\n\n const dataChannels = new Map<string, RTCDataChannel>();\n const userListeners = new Set<UserListener>();\n\n const {\n userId,\n enterRoom,\n exitRoom,\n leaveUser,\n broadcast,\n end: endPeerCollection,\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, initiator, restart }) {\n createDataChannel(pc, userId, initiator, 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 end() {\n dataChannels.forEach((dataChannel) => {\n try {\n dataChannel.close();\n } catch {}\n });\n dataChannels.clear();\n endPeerCollection();\n userListeners.clear();\n userIds.clear();\n },\n };\n}\n"
9
9
  ],
10
- "mappings": "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",
11
- "debugId": "BDFDBC2E7057D89B64756E2164756E21",
10
+ "mappings": "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",
11
+ "debugId": "A17EC2E586033FAE64756E2164756E21",
12
12
  "names": []
13
13
  }
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
1
  export { collectPeerConnections } from "./webrtc-peer-collector";
2
- export { enterRoom } from "./impl/signal-room";
2
+ export { enterRoom } from "./signal/impl/signal-room";
3
3
  export { enterWorld } from "./enter-world";
4
4
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/browser/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,sBAAsB,EAAE,MAAM,yBAAyB,CAAC;AACjE,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAC/C,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/browser/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,sBAAsB,EAAE,MAAM,yBAAyB,CAAC;AACjE,OAAO,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAC;AACtD,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC"}
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- function c(S){let{userId:h,worldId:A,room:R,host:C,autoRejoin:y=!0,logLine:B}=S,j=!1,E=0,W,k,w=!0,T=new Map,J=`wss://${C}/room/${A}/${R}?userId=${encodeURIComponent(h)}`,$=[],K=0;function b(Q,H,M){if(!W)return B?.("\uD83D\uDC64 ➡️ ❌","no ws available"),!1;if(j||W.readyState!==WebSocket.OPEN)return B?.("\uD83D\uDC64 ➡️ ❌","Not in opened state: "+W.readyState),!1;let G={type:Q,to:H,payload:M};return $.push(G),B?.("\uD83D\uDC64 ➡️ \uD83D\uDDA5️",G),clearTimeout(K),K=setTimeout(()=>{W.send(JSON.stringify($)),$.length=0}),!0}function L(){if(j)return;W=new WebSocket(J),W.onopen=()=>{if(w)S.onOpen?.(),w=!1;E=0},W.onmessage=(Q)=>{try{let H=JSON.parse(Q.data);(Array.isArray(H)?H:[H]).forEach((G)=>{if(B?.("\uD83D\uDDA5️ ➡️ \uD83D\uDC64",G),G.type==="peer-joined"||G.type==="peer-left")D(G.users);else if(G.type==="ice-server")S.onIceUrl?.(G.url,G.expiration);else if(G.userId)S.onMessage(G.type,G.payload,{userId:G.userId,receive:(X,F)=>b(X,G.userId,F)})})}catch{B?.("⚠️ ERROR",{error:"invalid-json"})}},W.onclose=(Q)=>{let M=[1001,1006,1011,1012,1013].includes(Q.code);if(y&&!j&&M){let G=Math.min(Math.pow(2,E)*1000,30000),X=Math.random()*1000,F=G+X;D([]),B?.("\uD83D\uDD04 RECONNECTING",{attempt:E+1,delayMs:Math.round(F)}),E++,k=setTimeout(L,F)}else S.onClose?.({code:Q.code,reason:Q.reason,wasClean:Q.wasClean})},W.onerror=(Q)=>{console.error("WS Error",Q),S.onError?.()}}function D(Q){let H=[],M=[],G=new Set;Q.forEach(({userId:X})=>{if(X===h)return;if(!T.has(X)){let F={userId:X,receive:(P,v)=>b(P,X,v)};T.set(X,F),H.push(F)}G.add(X)});for(let X of T.keys())if(!G.has(X))T.delete(X),M.push({userId:X});if(H.length)S.onPeerJoined(H);if(M.length)S.onPeerLeft(M)}return L(),{sendToServer(Q,H){b(Q,"server",H)},exitRoom:()=>{j=!0,clearTimeout(k),W.close()}}}function m({userId:S,worldId:h,room:A,host:R,autoRejoin:C=!0,onOpen:y,onClose:B,onError:j,onPeerJoined:E,onPeerLeft:W,onIceUrl:k,onMessage:w,logLine:T,workerUrl:J}){if(!J)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"),c({userId:S,worldId:h,room:A,host:R,autoRejoin:C,onOpen:y,onClose:B,onError:j,onPeerJoined:E,onPeerLeft:W,onIceUrl:k,onMessage:w});let $=new Worker(J,{type:"module"}),K=!1;function b({userId:D}){return{userId:D,receive:(Q,H)=>{if(K)return!1;return $.postMessage({cmd:"send",toUserId:D,host:R,room:A,type:Q,payload:H}),!0}}}let L=(D)=>{let Q=D.data;if(Q.kind==="open")y?.();else if(Q.kind==="close")$.terminate(),B?.(Q.ev);else if(Q.kind==="error")j?.();else if(Q.kind==="peer-joined")E(Q.users.map((H)=>b({userId:H.userId})));else if(Q.kind==="peer-left")W(Q.users);else if(Q.kind==="ice-server")k?.(Q.url,Q.expiration);else if(Q.kind==="message")w(Q.type,Q.payload,b({userId:Q.fromUserId}));else if(Q.kind==="log")T?.(Q.direction,Q.obj)};return $.addEventListener("message",L),$.postMessage({cmd:"enter",userId:S,worldId:h,room:A,host:R,autoRejoin:C}),{exitRoom:()=>{K=!0,$.removeEventListener("message",L),$.postMessage({cmd:"exit"})},sendToServer:(D,Q)=>{$.postMessage({cmd:"send",toUserId:"server",host:R,room:A,type:D,payload:Q})}}}var i=m;function u({userId:S,worldId:h,receivePeerConnection:A,peerlessUserExpiration:R=5000,fallbackRtcConfig:C={iceServers:[{urls:"stun:stun.l.google.com:19302"}]},enterRoomFunction:y=i,logLine:B,onLeaveUser:j,workerUrl:E,onRoomReady:W,onRoomClose:k,onBroadcastMessage:w}){let T=S??`user-${crypto.randomUUID()}`,J=new Map,$=void 0,K={...C,timestamp:Date.now()},b=new Map;async function L(G){if(G)try{let X=await fetch(G);if(!X.ok)throw Error(`ICE endpoint failed: ${X.status}`);K=await X.json()}catch(X){console.warn("Using fallback rtcConfig:",X)}return K}function D(G){j?.(G);let X=J.get(G);if(!X)return;try{X.pc?.close()}catch{}J.delete(G)}async function Q(G){if(!G.pc?.remoteDescription)return;let X=G.pendingRemoteIce;G.pendingRemoteIce=[];for(let F of X)try{await G.pc.addIceCandidate(F)}catch(P){B?.("⚠️ ERROR",{error:"add-ice-failed",userId:G.userId,detail:String(P)})}}function H({room:G,host:X}){let F=`${X}/room/${G}`,P=b.get(F);if(P)P.exitRoom(),b.delete(F)}function M({room:G,host:X}){return new Promise(async(F,P)=>{async function v(Z){let Y=Date.now();if(Y-(K?.timestamp??0)>1e4){let q=!$||$.expiration-Y<2000?await f():$;K=await L(q.url)}return Z.pc=new RTCPeerConnection(K),Z.pc.onicecandidate=(q)=>{if(!q.candidate)return;Z.peer.receive("ice",q.candidate.toJSON())},Z.pc.onconnectionstatechange=()=>{B?.("\uD83D\uDCAC",{event:"pc-state",userId:Z.userId,state:Z.pc?.connectionState})},Z.pc}async function V(Z){let Y=J.get(Z.userId),q=!1;if(!Y){let O={userId:Z.userId,pendingRemoteIce:[],peer:Z};await v(O),Y=O,J.set(Y.userId,Y),q=!0}else if(Y)clearTimeout(Y.expirationTimeout),Y.expirationTimeout=0;if(!Y.pc||Y.pc?.signalingState==="closed")await v(Y);return Y.peer=Z,[Y,q]}async function z(Z){let[Y]=await V(Z),q=Y.pc,O=await q?.createOffer();await q?.setLocalDescription(O),Z.receive("offer",q?.localDescription?.toJSON())}let N;async function f(){let Z=await new Promise((Y)=>{N=Y,I("request-ice")});return N=void 0,Z}let{exitRoom:_,sendToServer:I}=y({userId:T,worldId:h,room:G,host:X,logLine:B,workerUrl:E,autoRejoin:!0,onOpen(){W?.({room:G,host:X}),F()},onError(){console.error("onError"),P()},onClose(Z){k?.({room:G,host:X,ev:Z})},onPeerJoined(Z){Z.forEach(async(Y)=>{let[q,O]=await V(Y);if(!O){B?.("\uD83D\uDC64ℹ️","not a new peer: "+Y.userId);return}let x=q.pc;if(!x){B?.("\uD83D\uDC64ℹ️","no pc: "+Y.userId);return}async function U(){let g=J.get(Y.userId);if(g){g.pc=void 0;let p=await v(g);A({pc:p,userId:Y.userId,initiator:!0,restart:U}),await new Promise((l)=>setTimeout(l,3000)),await z(Y)}}A({pc:x,userId:Y.userId,initiator:!0,restart:U}),await z(Y)})},onPeerLeft(Z){Z.forEach(({userId:Y})=>{let q=J.get(Y);if(!q)return;q.expirationTimeout=setTimeout(()=>D(Y),R??0)})},onIceUrl(Z,Y){$={url:Z,expiration:Y},N?.($)},async onMessage(Z,Y,q){let[O]=await V(q),x=O.pc;if(!x)return;if(Z==="offer"){A({pc:x,userId:q.userId,initiator:!1,restart(){O.pc=void 0}}),await x.setRemoteDescription(Y);let U=await x.createAnswer();await x.setLocalDescription(U),q.receive("answer",x.localDescription?.toJSON()),await Q(O);return}if(Z==="answer"){await x.setRemoteDescription(Y),await Q(O);return}if(Z==="ice"){let U=Y;if(!x.remoteDescription){O.pendingRemoteIce.push(U);return}try{await x.addIceCandidate(U)}catch(g){B?.("⚠️ ERROR",{error:"add-ice-failed",userId:O.userId,detail:String(g)})}return}if(Z==="broadcast")w?.(Y,q.userId)}});b.set(`${X}/room/${G}`,{exitRoom:_,room:G,host:X,broadcast:(Z)=>{I("broadcast",Z)}})})}return{userId:T,enterRoom:M,exitRoom:H,leaveUser:D,broadcast(G){b.forEach((X)=>X.broadcast(G))},end(){b.forEach(({exitRoom:G})=>G()),b.clear(),J.forEach(({userId:G})=>D(G)),J.clear()}}}function d({userId:S,worldId:h,logLine:A,enterRoomFunction:R=m,peerlessUserExpiration:C,workerUrl:y,onRoomReady:B,onRoomClose:j,dataChannelOptions:E}){let W=[],k=new Set;function w(V,z,N,f){if(N){let _=V.createDataChannel("data",E);J(z,_,f),$.set(z,_)}else{let _=function(I){let Z=I.channel;J(z,Z,f),$.set(z,Z)};return V.addEventListener("datachannel",_),()=>{V.removeEventListener("datachannel",_)}}}function T(V,z){k.forEach((N)=>N(V,z))}function J(V,z,N){z.onopen=()=>{A?.("\uD83D\uDCAC",{event:"dc-open",userId:V}),W.push(V),K.forEach((_)=>_(V,"join",W))};let f=({data:_})=>{T(_,V)};z.addEventListener("message",f),z.addEventListener("close",()=>{A?.("\uD83D\uDCAC",{event:"dc-close",userId:V}),W.splice(W.indexOf(V),1),K.forEach((_)=>_(V,"leave",W)),z.removeEventListener("message",f),N?.()}),z.onerror=()=>A?.("⚠️ ERROR",{error:"dc-error",userId:V})}let $=new Map,K=new Set,{userId:b,enterRoom:L,exitRoom:D,leaveUser:Q,broadcast:H,end:M}=u({userId:S,worldId:h,enterRoomFunction:R,logLine:A,workerUrl:y,peerlessUserExpiration:C,onRoomReady:B,onRoomClose:j,onLeaveUser(V){let z=$.get(V);try{z?.close()}catch{}$.delete(V)},receivePeerConnection({pc:V,userId:z,initiator:N,restart:f}){w(V,z,N,f)},onBroadcastMessage(V,z){T(V,z),A?.("\uD83D\uDCE2",{event:"broadcast",userId:b,data:V})}});function G(V,z){$.forEach((N,f)=>{if(z&&f!==z)return;if(N.readyState==="open")N.send(V)})}function X(V){k.delete(V)}function F(V){return k.add(V),()=>{X(V)}}function P(V){K.delete(V)}function v(V){return K.add(V),()=>{P(V)}}return{userId:b,send:G,broadcast:H,enterRoom:L,exitRoom:D,leaveUser:Q,getUsers:()=>W,addMessageListener:F,removeMessageListener:X,addUserListener:v,removeUserListener:P,end(){$.forEach((V)=>{try{V.close()}catch{}}),$.clear(),M(),K.clear(),W.length=0}}}export{d as enterWorld,c as enterRoom,u as collectPeerConnections};
1
+ function i(K){let{userId:j,worldId:H,room:C,host:w,autoRejoin:n=!0,logLine:z}=K,L=!1,O=0,Y,R,P=!0,T=new Map,A=`wss://${w}/room/${H}/${C}?userId=${encodeURIComponent(j)}`,W=[],q=0;function E(B,$,x){if(!Y)return z?.("\uD83D\uDC64 ➡️ ❌","no ws available"),!1;let D={type:B,to:$,payload:x};if(W.push(D),z?.("\uD83D\uDC64 ➡️ \uD83D\uDDA5️",D),clearTimeout(q),L||Y.readyState!==WebSocket.OPEN)return z?.("\uD83D\uDC64 ➡️ ❌","Not in opened state: "+Y.readyState),!1;return q=setTimeout(()=>{Y.send(JSON.stringify(W)),W.length=0}),!0}function k(){if(L)return;Y=new WebSocket(A),Y.onopen=()=>{if(P)K.onOpen?.(),P=!1;O=0},Y.onmessage=(B)=>{try{let $=JSON.parse(B.data);(Array.isArray($)?$:[$]).forEach((D)=>{if(z?.("\uD83D\uDDA5️ ➡️ \uD83D\uDC64",D),D.type==="peer-joined"||D.type==="peer-left")J(D.users);else if(D.type==="ice-server")K.onIceUrl?.(D.url,D.expiration);else if(D.userId)K.onMessage(D.type,D.payload,{userId:D.userId,receive:(b,F)=>E(b,D.userId,F)})})}catch{z?.("⚠️ ERROR",{error:"invalid-json"})}},Y.onclose=(B)=>{let x=[1001,1006,1011,1012,1013].includes(B.code);if(n&&!L&&x){let D=Math.min(Math.pow(2,O)*1000,30000),b=Math.random()*1000,F=D+b;z?.("\uD83D\uDD04 RECONNECTING",{attempt:O+1,delayMs:Math.round(F)}),O++,R=setTimeout(k,F)}else K.onClose?.({code:B.code,reason:B.reason,wasClean:B.wasClean})},Y.onerror=(B)=>{console.error("WS Error",B),K.onError?.()}}function J(B){let $=[],x=[],D=new Set;B.forEach(({userId:b})=>{if(b===j)return;if(!T.has(b)){let F={userId:b,receive:(_,v)=>E(_,b,v)};T.set(b,F),$.push(F)}D.add(b)});for(let b of T.keys())if(!D.has(b))T.delete(b),x.push({userId:b});if($.length)K.onPeerJoined($);if(x.length)K.onPeerLeft(x)}return k(),{sendToServer(B,$){E(B,"server",$)},exitRoom:()=>{L=!0,clearTimeout(R),Y.close()}}}function c({userId:K,worldId:j,room:H,host:C,autoRejoin:w=!0,onOpen:n,onClose:z,onError:L,onPeerJoined:O,onPeerLeft:Y,onIceUrl:R,onMessage:P,logLine:T,workerUrl:A}){if(!A)return console.warn("Warning: enterRoom called without workerUrl; this may cause issues in some environments. You should pass workerUrl explicitly. Use:","https://cdn.jsdelivr.net/npm/@dobuki/hello-worker/dist/signal-room.worker.min.js"),i({userId:K,worldId:j,room:H,host:C,autoRejoin:w,onOpen:n,onClose:z,onError:L,onPeerJoined:O,onPeerLeft:Y,onIceUrl:R,onMessage:P});let W=new Worker(A,{type:"module"}),q=!1;function E({userId:J}){return{userId:J,receive:(B,$)=>{if(q)return!1;return W.postMessage({cmd:"send",toUserId:J,host:C,room:H,type:B,payload:$}),!0}}}let k=(J)=>{let B=J.data;if(B.kind==="open")n?.();else if(B.kind==="close")W.terminate(),z?.(B.ev);else if(B.kind==="error")L?.();else if(B.kind==="peer-joined")O(B.users.map(($)=>E({userId:$.userId})));else if(B.kind==="peer-left")Y(B.users);else if(B.kind==="ice-server")R?.(B.url,B.expiration);else if(B.kind==="message")P(B.type,B.payload,E({userId:B.fromUserId}));else if(B.kind==="log")T?.(B.direction,B.obj)};return W.addEventListener("message",k),W.postMessage({cmd:"enter",userId:K,worldId:j,room:H,host:C,autoRejoin:w}),{exitRoom:()=>{q=!0,W.removeEventListener("message",k),W.postMessage({cmd:"exit"})},sendToServer:(J,B)=>{W.postMessage({cmd:"send",toUserId:"server",host:C,room:H,type:J,payload:B})}}}var d=c;function I({userId:K,worldId:j,receivePeerConnection:H,peerlessUserExpiration:C=5000,fallbackRtcConfig:w={iceServers:[{urls:"stun:stun.l.google.com:19302"}]},enterRoomFunction:n=d,logLine:z,onLeaveUser:L,workerUrl:O,onRoomReady:Y,onRoomClose:R,onBroadcastMessage:P}){let T=K??`user-${crypto.randomUUID()}`,A=new Map,W=void 0,q={...w,timestamp:Date.now()},E=new Map;async function k(D){if(D)try{let b=await fetch(D);if(!b.ok)throw Error(`ICE endpoint failed: ${b.status}`);q=await b.json()}catch(b){console.warn("Using fallback rtcConfig:",b)}return q}function J(D){L?.(D);let b=A.get(D);if(!b)return;A.delete(D);try{b.pc?.close()}catch{}}async function B(D){if(!D.pc?.remoteDescription)return;let b=D.pendingRemoteIce;D.pendingRemoteIce=[];for(let F of b)try{await D.pc.addIceCandidate(F)}catch(_){z?.("⚠️ ERROR",{error:"add-ice-failed",userId:D.userId,detail:String(_)})}}function $({room:D,host:b}){let F=`${b}/room/${D}`,_=E.get(F);if(_)_.exitRoom(),E.delete(F)}function x({room:D,host:b}){return new Promise(async(F,_)=>{async function v(G){let S=A.get(G.userId);if(!S)return;S.close();let Q=await V(S);H({pc:Q,userId:G.userId,initiator:!0,restart:()=>v(G)}),await new Promise((Z)=>setTimeout(Z,3000)),await M(G)}async function V(G){let S=Date.now();if(S-(q?.timestamp??0)>1e4){let Q=!W||W.expiration-S<2000?await N():W;q=await k(Q.url)}return G.pc=new RTCPeerConnection(q),G.pc.onicecandidate=(Q)=>{if(!Q.candidate)return;G.peer.receive("ice",Q.candidate.toJSON())},G.pc.onconnectionstatechange=()=>{z?.("\uD83D\uDCAC",{event:"pc-state",userId:G.userId,state:G.pc?.connectionState})},G.pc}async function X(G,S){let Q=A.get(G.userId);if(!Q||S){let Z={userId:G.userId,pendingRemoteIce:[],peer:G,initiateForThisUser:Q?.initiateForThisUser??!1,close(){this.pc?.close(),this.pc=void 0}};await V(Z),Q=Z,A.set(Q.userId,Q)}else if(Q)clearTimeout(Q.expirationTimeout),Q.expirationTimeout=0;if(!Q.pc||Q.pc?.signalingState==="closed")await V(Q);return Q.peer=G,Q}async function M(G){let Q=(await X(G)).pc,Z=await Q?.createOffer();await Q?.setLocalDescription(Z),G.receive("offer",Q?.localDescription?.toJSON())}let f;async function N(){let G=await new Promise((S)=>{f=S,y("request-ice")});return f=void 0,G}let{exitRoom:U,sendToServer:y}=n({userId:T,worldId:j,room:D,host:b,logLine:z,workerUrl:O,autoRejoin:!0,onOpen(){Y?.({room:D,host:b}),F()},onError(){console.error("onError"),_()},onClose(G){R?.({room:D,host:b,ev:G})},onPeerJoined(G){G.forEach(async(S)=>{let Q=A.has(S.userId),Z=await X(S,!0);if(!Q)Z.initiateForThisUser=!0;let h=Z.pc;if(!h){z?.("\uD83D\uDC64ℹ️","no pc: "+S.userId);return}if(H({pc:h,userId:S.userId,initiator:Z.initiateForThisUser,restart:Z.initiateForThisUser?()=>v(S):()=>Z.close()}),Z.initiateForThisUser)await M(S)})},onPeerLeft(G){G.forEach(({userId:S})=>{let Q=A.get(S);if(!Q)return;Q.expirationTimeout=setTimeout(()=>J(S),C??0)})},onIceUrl(G,S){W={url:G,expiration:S},f?.(W)},async onMessage(G,S,Q){let Z=await X(Q),h=Z.pc;if(!h)return;if(G==="offer"){Z.initiateForThisUser=!1,H({pc:h,userId:Q.userId,initiator:!1,restart(){Z.close()}}),await h.setRemoteDescription(S);let g=await h.createAnswer();await h.setLocalDescription(g),Q.receive("answer",h.localDescription?.toJSON()),await B(Z);return}if(G==="answer"){await h.setRemoteDescription(S),await B(Z);return}if(G==="ice"){let g=S;if(!h.remoteDescription){Z.pendingRemoteIce.push(g);return}try{await h.addIceCandidate(g)}catch(m){z?.("⚠️ ERROR",{error:"add-ice-failed",userId:Z.userId,detail:String(m)})}return}if(G==="broadcast")P?.(S,Q.userId)}});E.set(`${b}/room/${D}`,{exitRoom:U,room:D,host:b,broadcast:(G)=>{y("broadcast",G)}})})}return{userId:T,enterRoom:x,exitRoom:$,leaveUser:J,broadcast(D){E.forEach((b)=>b.broadcast(D))},end(){E.forEach(({exitRoom:D})=>D()),E.clear(),A.forEach(({userId:D})=>J(D)),A.clear()}}}function u({userId:K,worldId:j,logLine:H,enterRoomFunction:C=c,peerlessUserExpiration:w,workerUrl:n,onRoomReady:z,onRoomClose:L,dataChannelOptions:O}){let Y=new Set,R=new Set;function P(V,X,M,f){if(M){let N=V.createDataChannel("data",O);A(X,N,f),W.set(X,N)}else{let N=function(U){let y=U.channel;A(X,y,f),W.set(X,y)};return V.addEventListener("datachannel",N),()=>{V.removeEventListener("datachannel",N)}}}function T(V,X){R.forEach((M)=>M(V,X))}function A(V,X,M){X.onopen=()=>{H?.("\uD83D\uDCAC",{event:"dc-open",userId:V}),Y.add(V),q.forEach((N)=>N(V,"join",[...Y]))};let f=({data:N})=>{T(N,V)};X.addEventListener("message",f),X.addEventListener("close",()=>{H?.("\uD83D\uDCAC",{event:"dc-close",userId:V}),Y.delete(V),q.forEach((N)=>N(V,"leave",[...Y])),X.removeEventListener("message",f),M?.()}),X.onerror=()=>H?.("⚠️ ERROR",{error:"dc-error",userId:V})}let W=new Map,q=new Set,{userId:E,enterRoom:k,exitRoom:J,leaveUser:B,broadcast:$,end:x}=I({userId:K,worldId:j,enterRoomFunction:C,logLine:H,workerUrl:n,peerlessUserExpiration:w,onRoomReady:z,onRoomClose:L,onLeaveUser(V){let X=W.get(V);try{X?.close()}catch{}W.delete(V)},receivePeerConnection({pc:V,userId:X,initiator:M,restart:f}){P(V,X,M,f)},onBroadcastMessage(V,X){T(V,X),H?.("\uD83D\uDCE2",{event:"broadcast",userId:E,data:V})}});function D(V,X){W.forEach((M,f)=>{if(X&&f!==X)return;if(M.readyState==="open")M.send(V)})}function b(V){R.delete(V)}function F(V){return R.add(V),()=>{b(V)}}function _(V){q.delete(V)}function v(V){return q.add(V),()=>{_(V)}}return{userId:E,send:D,broadcast:$,enterRoom:k,exitRoom:J,leaveUser:B,getUsers:()=>[...Y],addMessageListener:F,removeMessageListener:b,addUserListener:v,removeUserListener:_,end(){W.forEach((V)=>{try{V.close()}catch{}}),W.clear(),x(),q.clear(),Y.clear()}}}export{u as enterWorld,i as enterRoom,I as collectPeerConnections};
2
2
 
3
- //# debugId=72FB56CFD0D0370464756E2164756E21
3
+ //# debugId=6417898587532A6564756E2164756E21
4
4
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "version": 3,
3
- "sources": ["../src/browser/impl/signal-room.ts", "../src/browser/signal-room.ts", "../src/browser/webrtc-peer-collector.ts", "../src/browser/enter-world.ts"],
3
+ "sources": ["../src/browser/signal/impl/signal-room.ts", "../src/browser/signal/signal-room.ts", "../src/browser/webrtc-peer-collector.ts", "../src/browser/enter-world.ts"],
4
4
  "sourcesContent": [
5
- "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 if (exited || ws.readyState !== WebSocket.OPEN) {\n logLine?.(\"👤 ➡️ ❌\", \"Not in opened state: \" + ws.readyState);\n return false;\n }\n const obj: OutMessage = { type, to, payload };\n accumulatedMessages.push(obj);\n // ws.send(JSON.stringify(obj));\n logLine?.(\"👤 ➡️ 🖥️\", obj);\n clearTimeout(timeout);\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);\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 updatePeers([]);\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 }[]) {\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 (!peers.has(pUserId)) {\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 (!updatedPeerSet.has(pUserId)) {\n peers.delete(pUserId);\n left.push({ userId: pUserId });\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",
5
+ "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);\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 }[]) {\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 (!peers.has(pUserId)) {\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 (!updatedPeerSet.has(pUserId)) {\n peers.delete(pUserId);\n left.push({ userId: pUserId });\n }\n }\n // Notify peer joined first then peer left. (avoid disconnect in case the peer leaving / joining is on the same user).\n if (joined.length) params.onPeerJoined(joined);\n if (left.length) params.onPeerLeft(left);\n }\n\n // Start initial connection\n connect();\n\n return {\n sendToServer(type, payload) {\n send(type, \"server\", payload);\n },\n exitRoom: () => {\n exited = true;\n clearTimeout(timeoutId);\n ws.close();\n },\n };\n}\n",
6
6
  "import type { IPeer } from \"./impl/signal-room.js\";\nimport { enterRoom as baseEnterRoom } from \"./impl/signal-room.js\";\nimport { RoomEvent, WorkerCommand } from \"./signal-room.worker.js\";\n\nexport function enterRoom<T extends string, P = any>({\n userId,\n worldId,\n room,\n host,\n autoRejoin = true,\n onOpen,\n onClose,\n onError,\n onPeerJoined,\n onPeerLeft,\n onIceUrl,\n onMessage,\n logLine,\n workerUrl,\n}: {\n userId: string;\n worldId: string;\n room: string;\n host: string;\n autoRejoin?: boolean;\n onOpen?: () => void;\n onClose?: (ev: Pick<CloseEvent, \"code\" | \"reason\" | \"wasClean\">) => void;\n onError?: () => void;\n onPeerJoined: (users: IPeer<T, P>[]) => void;\n onPeerLeft: (users: { userId: string }[]) => void;\n onIceUrl?(url: string, expiration: number): void;\n onMessage: (type: T, payload: P, from: IPeer<T, P>) => void;\n logLine?: (direction: string, obj?: any) => void;\n\n // Pass the URL to your worker file (bundler will handle it)\n workerUrl?: URL;\n}): {\n exitRoom: () => void;\n sendToServer: <P extends any>(type: T, payload?: P) => void;\n} {\n if (!workerUrl) {\n const CDN_WORKER_URL = `https://cdn.jsdelivr.net/npm/@dobuki/hello-worker/dist/signal-room.worker.min.js`;\n\n console.warn(\n \"Warning: enterRoom called without workerUrl; this may cause issues in some environments. You should pass workerUrl explicitly. Use:\",\n CDN_WORKER_URL,\n );\n return baseEnterRoom<T, P>({\n userId,\n worldId,\n room,\n host,\n autoRejoin,\n onOpen,\n onClose,\n onError,\n onPeerJoined,\n onPeerLeft,\n onIceUrl,\n onMessage,\n });\n }\n const worker = new Worker(workerUrl, { type: \"module\" });\n let exited = false;\n\n function makeUser({ userId }: { userId: string }): IPeer<T, P> {\n return {\n userId,\n receive: (type: T, payload: P) => {\n if (exited) return false;\n worker.postMessage({\n cmd: \"send\",\n toUserId: userId,\n host,\n room,\n type,\n payload,\n } as WorkerCommand);\n return true;\n },\n };\n }\n\n const onWorkerMessage = (e: MessageEvent<RoomEvent<T, P>>) => {\n const ev = e.data;\n\n if (ev.kind === \"open\") onOpen?.();\n else if (ev.kind === \"close\") {\n worker.terminate();\n onClose?.(ev.ev);\n } else if (ev.kind === \"error\") onError?.();\n else if (ev.kind === \"peer-joined\")\n onPeerJoined(ev.users.map((ev) => makeUser({ userId: ev.userId })));\n else if (ev.kind === \"peer-left\") onPeerLeft(ev.users);\n else if (ev.kind === \"ice-server\") onIceUrl?.(ev.url, ev.expiration);\n else if (ev.kind === \"message\")\n onMessage(ev.type, ev.payload, makeUser({ userId: ev.fromUserId }));\n else if (ev.kind === \"log\") logLine?.(ev.direction, ev.obj);\n };\n\n worker.addEventListener(\"message\", onWorkerMessage);\n\n worker.postMessage({\n cmd: \"enter\",\n userId,\n worldId,\n room,\n host,\n autoRejoin,\n } as WorkerCommand);\n\n return {\n exitRoom: () => {\n exited = true;\n worker.removeEventListener(\"message\", onWorkerMessage);\n worker.postMessage({ cmd: \"exit\" } as WorkerCommand);\n },\n sendToServer: <P extends any>(type: T, payload?: P) => {\n worker.postMessage({\n cmd: \"send\",\n toUserId: \"server\",\n host,\n room,\n type,\n payload,\n } as WorkerCommand);\n },\n };\n}\n\nexport type EnterRoom<T extends string, P> = typeof enterRoom<T, P>;\n",
7
- "import { IPeer } from \"./impl/signal-room\";\nimport { EnterRoom, enterRoom } from \"./signal-room\";\n\nexport type SigType = \"offer\" | \"answer\" | \"ice\" | \"request-ice\" | \"broadcast\";\nexport type SigPayload = RTCSessionDescriptionInit | RTCIceCandidateInit;\n\ntype UserState = {\n userId: string;\n pc?: RTCPeerConnection;\n\n // ICE that arrived before we had remoteDescription\n pendingRemoteIce: RTCIceCandidateInit[];\n\n // the signaling \"user\" handle so we can send messages\n peer: IPeer<SigType, SigPayload>;\n\n expirationTimeout?: number;\n};\n\nconst DEFAULT_ENTER_ROOM = enterRoom;\n\nexport function collectPeerConnections({\n userId: passedUserId,\n worldId,\n receivePeerConnection,\n peerlessUserExpiration = 5000,\n fallbackRtcConfig = {\n iceServers: [{ urls: \"stun:stun.l.google.com:19302\" }],\n },\n enterRoomFunction: enterRoom = DEFAULT_ENTER_ROOM,\n logLine,\n onLeaveUser,\n workerUrl,\n onRoomReady,\n onRoomClose,\n onBroadcastMessage,\n}: {\n userId?: string;\n worldId: string;\n fallbackRtcConfig?: RTCConfiguration;\n enterRoomFunction?: EnterRoom<SigType, SigPayload>;\n onLeaveUser?: (userId: string) => void;\n logLine?: (direction: string, obj?: any) => void;\n workerUrl?: URL;\n peerlessUserExpiration?: number;\n receivePeerConnection(connection: {\n pc: RTCPeerConnection;\n userId: string;\n initiator: boolean;\n restart?: () => void;\n }): void;\n onRoomReady?(info: { host: string; room: string }): void;\n onRoomClose?(info: {\n host: string;\n room: string;\n ev: Pick<CloseEvent, \"reason\" | \"code\" | \"wasClean\">;\n }): void;\n onBroadcastMessage?<P extends any>(payload: P, from: string): void;\n}) {\n const userId = passedUserId ?? `user-${crypto.randomUUID()}`;\n const users: Map<string, UserState> = new Map();\n let iceUrl: { url: string; expiration: number } | undefined = undefined;\n let rtcConfig: RTCConfiguration & { timestamp: number } = {\n ...fallbackRtcConfig,\n timestamp: Date.now(),\n };\n\n const roomsEntered = new Map<\n string,\n {\n room: string;\n host: string;\n exitRoom: () => void;\n broadcast: <P extends any>(payload: P) => void;\n }\n >();\n\n async function getRtcConfig(\n iceUrl: string,\n ): Promise<RTCConfiguration & { timestamp: number }> {\n if (iceUrl) {\n try {\n const r = await fetch(iceUrl);\n if (!r.ok) throw new Error(`ICE endpoint failed: ${r.status}`);\n rtcConfig = (await r.json()) as RTCConfiguration & {\n timestamp: number;\n };\n } catch (e) {\n console.warn(\"Using fallback rtcConfig:\", e);\n }\n }\n return rtcConfig;\n }\n\n function leaveUser(userId: string) {\n onLeaveUser?.(userId);\n const p = users.get(userId);\n if (!p) return;\n try {\n p.pc?.close();\n } catch {}\n users.delete(userId);\n }\n\n async function flushRemoteIce(state: UserState) {\n if (!state.pc?.remoteDescription) return;\n\n const queued = state.pendingRemoteIce;\n state.pendingRemoteIce = [];\n\n for (const ice of queued) {\n try {\n await state.pc.addIceCandidate(ice);\n } catch (e) {\n logLine?.(\"⚠️ ERROR\", {\n error: \"add-ice-failed\",\n userId: state.userId,\n detail: String(e),\n });\n }\n }\n }\n\n function exit({ room, host }: { room: string; host: string }) {\n const key = `${host}/room/${room}`;\n const session = roomsEntered.get(key);\n if (session) {\n session.exitRoom();\n roomsEntered.delete(key);\n }\n }\n\n function enter({ room, host }: { room: string; host: string }) {\n return new Promise<void>(async (resolve, reject) => {\n async function setupPC(state: UserState) {\n const now = Date.now();\n if (now - (rtcConfig?.timestamp ?? 0) > 10000) {\n const ice =\n !iceUrl || iceUrl.expiration - now < 2000\n ? await requestIce()\n : iceUrl;\n rtcConfig = await getRtcConfig(ice.url);\n }\n state.pc = new RTCPeerConnection(rtcConfig);\n // Send local ICE candidates to this peer\n state.pc.onicecandidate = (ev) => {\n if (!ev.candidate) return;\n state.peer.receive(\"ice\", ev.candidate.toJSON());\n };\n\n state.pc.onconnectionstatechange = () => {\n logLine?.(\"💬\", {\n event: \"pc-state\",\n userId: state.userId,\n state: state.pc?.connectionState,\n });\n };\n return state.pc;\n }\n\n async function getPeer(\n peer: IPeer<SigType, SigPayload>,\n ): Promise<[UserState, boolean]> {\n let state = users.get(peer.userId);\n let isNewPeer = false;\n if (!state) {\n const newState: UserState = {\n userId: peer.userId,\n pendingRemoteIce: [],\n peer,\n };\n\n await setupPC(newState);\n state = newState;\n\n // New user\n users.set(state.userId, state);\n isNewPeer = true;\n } else if (state) {\n clearTimeout(state.expirationTimeout);\n state.expirationTimeout = 0;\n }\n if (!state.pc || state.pc?.signalingState === \"closed\") {\n await setupPC(state);\n }\n state.peer = peer;\n return [state, isNewPeer];\n }\n\n async function makeOffer(user: IPeer) {\n // Offer flow: createOffer -> setLocalDescription -> send localDescription\n const [state] = await getPeer(user);\n const pc = state.pc;\n const offer = await pc?.createOffer();\n await pc?.setLocalDescription(offer);\n user.receive(\"offer\", pc?.localDescription?.toJSON()!);\n }\n\n let icePromiseResolve:\n | undefined\n | ((url: { url: string; expiration: number }) => void);\n async function requestIce() {\n const iceUrl = await new Promise<{ url: string; expiration: number }>(\n (resolve) => {\n icePromiseResolve = resolve;\n sendToServer(\"request-ice\");\n },\n );\n icePromiseResolve = undefined;\n return iceUrl;\n }\n\n const { exitRoom, sendToServer } = enterRoom({\n userId,\n worldId,\n room,\n host,\n logLine,\n workerUrl,\n autoRejoin: true,\n\n onOpen() {\n onRoomReady?.({ room, host });\n resolve();\n },\n onError() {\n console.error(\"onError\");\n reject();\n },\n onClose(ev) {\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, isNewPeer] = await getPeer(user);\n if (!isNewPeer) {\n logLine?.(\"👤ℹ️\", \"not a new peer: \" + user.userId);\n return;\n }\n const pc = state.pc;\n if (!pc) {\n logLine?.(\"👤ℹ️\", \"no pc: \" + user.userId);\n return;\n }\n\n async function restart() {\n const state = users.get(user.userId);\n if (state) {\n state.pc = undefined;\n const pc = await setupPC(state);\n receivePeerConnection({\n pc,\n userId: user.userId,\n initiator: true,\n restart,\n });\n await new Promise((resolve) => setTimeout(resolve, 3000));\n await makeOffer(user);\n }\n }\n\n receivePeerConnection({\n pc,\n userId: user.userId,\n initiator: true,\n restart,\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, expiration) {\n iceUrl = { url, expiration };\n icePromiseResolve?.(iceUrl);\n },\n\n async onMessage(type: SigType, payload: any, from: IPeer) {\n const [state] = await getPeer(from);\n const pc = state.pc;\n if (!pc) return;\n\n if (type === \"offer\") {\n receivePeerConnection({\n pc,\n userId: from.userId,\n initiator: false,\n restart() {\n // reset PC\n state.pc = undefined;\n },\n });\n // Responder: set remote offer\n await pc.setRemoteDescription(payload as RTCSessionDescriptionInit);\n\n // Create and send answer\n const answer = await pc.createAnswer();\n await pc.setLocalDescription(answer);\n\n from.receive(\"answer\", pc.localDescription?.toJSON()!);\n\n // Now safe to apply any queued ICE from this peer\n await flushRemoteIce(state);\n return;\n }\n\n if (type === \"answer\") {\n // Initiator: set remote answer\n await pc.setRemoteDescription(payload as RTCSessionDescriptionInit);\n await flushRemoteIce(state);\n return;\n }\n\n if (type === \"ice\") {\n const ice = payload as RTCIceCandidateInit;\n\n // If we don't have remoteDescription yet, queue it\n if (!pc.remoteDescription) {\n state.pendingRemoteIce.push(ice);\n return;\n }\n\n try {\n await pc.addIceCandidate(ice);\n } catch (e) {\n logLine?.(\"⚠️ ERROR\", {\n error: \"add-ice-failed\",\n userId: state.userId,\n detail: String(e),\n });\n }\n return;\n }\n\n if (type === \"broadcast\") {\n onBroadcastMessage?.(payload, from.userId);\n }\n },\n });\n roomsEntered.set(`${host}/room/${room}`, {\n exitRoom,\n room,\n host,\n broadcast: (payload) => {\n sendToServer(\"broadcast\", payload);\n },\n });\n });\n }\n\n return {\n userId,\n enterRoom: enter,\n exitRoom: exit,\n leaveUser,\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(({ userId }) => leaveUser(userId));\n users.clear();\n },\n };\n}\n\n/*\nTurn Token ID\n<CF_TURN_TOKEN_ID>\n\nAPI Token\n<CF_RTC_API_TOKEN>\n\nCURL\ncurl \\\n\t-H \"Authorization: Bearer <CF_RTC_API_TOKEN>\" \\\n\t-H \"Content-Type: application/json\" -d '{\"ttl\": 86400}' \\\n\thttps://rtc.live.cloudflare.com/v1/turn/keys/<CF_TURN_TOKEN_ID>/credentials/generate-ice-servers\n\nJSON\n{\n\t\"iceServers\": [\n {\n \"urls\": [\n \"stun:stun.cloudflare.com:3478\",\n \"turn:turn.cloudflare.com:3478?transport=udp\",\n \"turn:turn.cloudflare.com:3478?transport=tcp\",\n \"turns:turn.cloudflare.com:5349?transport=tcp\"\n ],\n \"username\": \"xxxx\",\n \"credential\": \"yyyy\",\n }\n ]\n}\n\n*/\n",
8
- "import { EnterRoom, enterRoom } from \"./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?: (direction: string, 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: string[] = [];\n\n const messagesListeners = new Set<(data: R, from: string) => void>();\n\n function createDataChannel(\n pc: RTCPeerConnection,\n peerUserId: string,\n initiator: boolean,\n restart?: () => void,\n ) {\n if (initiator) {\n const dc = pc.createDataChannel(\"data\", dataChannelOptions);\n wireDataChannel(peerUserId, dc, restart);\n dataChannels.set(peerUserId, dc);\n } else {\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 return () => {\n pc.removeEventListener(\"datachannel\", listener);\n };\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.push(userId);\n userListeners.forEach((listener) => listener(userId, \"join\", userIds));\n };\n const onmessage = ({ data }: MessageEvent) => {\n conveyMessage(data, userId);\n // logLine(\"💬\", { event: \"dc-message\", userId, data });\n };\n dc.addEventListener(\"message\", onmessage);\n dc.addEventListener(\"close\", () => {\n logLine?.(\"💬\", { event: \"dc-close\", userId });\n userIds.splice(userIds.indexOf(userId), 1);\n userListeners.forEach((listener) => listener(userId, \"leave\", userIds));\n dc.removeEventListener(\"message\", onmessage);\n restart?.();\n });\n dc.onerror = () => logLine?.(\"⚠️ ERROR\", { error: \"dc-error\", userId });\n }\n\n const dataChannels = new Map<string, RTCDataChannel>();\n const userListeners = new Set<UserListener>();\n\n const {\n userId,\n enterRoom,\n exitRoom,\n leaveUser,\n broadcast,\n end: endPeerCollection,\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, initiator, restart }) {\n createDataChannel(pc, userId, initiator, 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 end() {\n dataChannels.forEach((dataChannel) => {\n try {\n dataChannel.close();\n } catch {}\n });\n dataChannels.clear();\n endPeerCollection();\n userListeners.clear();\n userIds.length = 0;\n },\n };\n}\n"
7
+ "import { IPeer } from \"./signal/impl/signal-room\";\nimport { EnterRoom, enterRoom } from \"./signal/signal-room\";\n\nexport type SigType = \"offer\" | \"answer\" | \"ice\" | \"request-ice\" | \"broadcast\";\nexport type SigPayload = RTCSessionDescriptionInit | RTCIceCandidateInit;\n\ntype UserState = {\n userId: string;\n pc?: RTCPeerConnection;\n\n // ICE that arrived before we had remoteDescription\n pendingRemoteIce: RTCIceCandidateInit[];\n\n // the signaling \"user\" handle so we can send messages\n peer: IPeer<SigType, SigPayload>;\n\n expirationTimeout?: number;\n initiateForThisUser: boolean;\n close: () => void;\n};\n\nconst DEFAULT_ENTER_ROOM = enterRoom;\n\nexport function collectPeerConnections({\n userId: passedUserId,\n worldId,\n receivePeerConnection,\n peerlessUserExpiration = 5000,\n fallbackRtcConfig = {\n iceServers: [{ urls: \"stun:stun.l.google.com:19302\" }],\n },\n enterRoomFunction: enterRoom = DEFAULT_ENTER_ROOM,\n logLine,\n onLeaveUser,\n workerUrl,\n onRoomReady,\n onRoomClose,\n onBroadcastMessage,\n}: {\n userId?: string;\n worldId: string;\n fallbackRtcConfig?: RTCConfiguration;\n enterRoomFunction?: EnterRoom<SigType, SigPayload>;\n onLeaveUser?: (userId: string) => void;\n logLine?: (direction: string, obj?: any) => void;\n workerUrl?: URL;\n peerlessUserExpiration?: number;\n receivePeerConnection(connection: {\n pc: RTCPeerConnection;\n userId: string;\n initiator: boolean;\n restart?: () => void;\n }): void;\n onRoomReady?(info: { host: string; room: string }): void;\n onRoomClose?(info: {\n host: string;\n room: string;\n ev: Pick<CloseEvent, \"reason\" | \"code\" | \"wasClean\">;\n }): void;\n onBroadcastMessage?<P extends any>(payload: P, from: string): void;\n}) {\n const userId = passedUserId ?? `user-${crypto.randomUUID()}`;\n const users: Map<string, UserState> = new Map();\n let iceUrl: { url: string; expiration: number } | undefined = undefined;\n let rtcConfig: RTCConfiguration & { timestamp: number } = {\n ...fallbackRtcConfig,\n timestamp: Date.now(),\n };\n\n const roomsEntered = new Map<\n string,\n {\n room: string;\n host: string;\n exitRoom: () => void;\n broadcast: <P extends any>(payload: P) => void;\n }\n >();\n\n async function getRtcConfig(\n iceUrl: string,\n ): Promise<RTCConfiguration & { timestamp: number }> {\n if (iceUrl) {\n try {\n const r = await fetch(iceUrl);\n if (!r.ok) throw new Error(`ICE endpoint failed: ${r.status}`);\n rtcConfig = (await r.json()) as RTCConfiguration & {\n timestamp: number;\n };\n } catch (e) {\n console.warn(\"Using fallback rtcConfig:\", e);\n }\n }\n return rtcConfig;\n }\n\n function leaveUser(userId: string) {\n onLeaveUser?.(userId);\n const p = users.get(userId);\n if (!p) return;\n users.delete(userId);\n try {\n p.pc?.close();\n } catch {}\n }\n\n async function flushRemoteIce(state: UserState) {\n if (!state.pc?.remoteDescription) return;\n\n const queued = state.pendingRemoteIce;\n state.pendingRemoteIce = [];\n\n for (const ice of queued) {\n try {\n await state.pc.addIceCandidate(ice);\n } catch (e) {\n logLine?.(\"⚠️ ERROR\", {\n error: \"add-ice-failed\",\n userId: state.userId,\n detail: String(e),\n });\n }\n }\n }\n\n function exit({ room, host }: { room: string; host: string }) {\n const key = `${host}/room/${room}`;\n const session = roomsEntered.get(key);\n if (session) {\n session.exitRoom();\n roomsEntered.delete(key);\n }\n }\n\n function enter({ room, host }: { room: string; host: string }) {\n return new Promise<void>(async (resolve, reject) => {\n async function restartInitiator(user: IPeer) {\n const state = users.get(user.userId);\n if (!state) return; // user left\n state.close();\n const pc = await setupPC(state);\n receivePeerConnection({\n pc,\n userId: user.userId,\n initiator: true,\n restart: () => restartInitiator(user),\n });\n await new Promise((resolve) => setTimeout(resolve, 3000));\n await makeOffer(user);\n }\n\n async function setupPC(state: UserState) {\n const now = Date.now();\n if (now - (rtcConfig?.timestamp ?? 0) > 10000) {\n const ice =\n !iceUrl || iceUrl.expiration - now < 2000\n ? await requestIce()\n : iceUrl;\n rtcConfig = await getRtcConfig(ice.url);\n }\n state.pc = new RTCPeerConnection(rtcConfig);\n // Send local ICE candidates to this peer\n state.pc.onicecandidate = (ev) => {\n if (!ev.candidate) return;\n state.peer.receive(\"ice\", ev.candidate.toJSON());\n };\n\n state.pc.onconnectionstatechange = () => {\n logLine?.(\"💬\", {\n event: \"pc-state\",\n userId: state.userId,\n state: state.pc?.connectionState,\n });\n };\n\n return state.pc;\n }\n\n async function getPeer(\n peer: IPeer<SigType, SigPayload>,\n forceReset?: boolean,\n ): Promise<UserState> {\n let state = users.get(peer.userId);\n if (!state || forceReset) {\n const newState: UserState = {\n userId: peer.userId,\n pendingRemoteIce: [],\n peer,\n initiateForThisUser: state?.initiateForThisUser ?? false,\n close() {\n this.pc?.close();\n this.pc = undefined;\n },\n };\n\n await setupPC(newState);\n state = newState;\n\n // New user\n users.set(state.userId, state);\n } else if (state) {\n clearTimeout(state.expirationTimeout);\n state.expirationTimeout = 0;\n }\n if (!state.pc || state.pc?.signalingState === \"closed\") {\n await setupPC(state);\n }\n state.peer = peer;\n return state;\n }\n\n async function makeOffer(user: IPeer) {\n // Offer flow: createOffer -> setLocalDescription -> send localDescription\n const state = await getPeer(user);\n const pc = state.pc;\n const offer = await pc?.createOffer();\n await pc?.setLocalDescription(offer);\n user.receive(\"offer\", pc?.localDescription?.toJSON()!);\n }\n\n let icePromiseResolve:\n | undefined\n | ((url: { url: string; expiration: number }) => void);\n async function requestIce() {\n const iceUrl = await new Promise<{ url: string; expiration: number }>(\n (resolve) => {\n icePromiseResolve = resolve;\n sendToServer(\"request-ice\");\n },\n );\n icePromiseResolve = undefined;\n return iceUrl;\n }\n\n const { exitRoom, sendToServer } = enterRoom({\n userId,\n worldId,\n room,\n host,\n logLine,\n workerUrl,\n autoRejoin: true,\n\n onOpen() {\n onRoomReady?.({ room, host });\n resolve();\n },\n onError() {\n console.error(\"onError\");\n reject();\n },\n onClose(ev: Pick<CloseEvent, \"reason\" | \"code\" | \"wasClean\">) {\n onRoomClose?.({ room, host, ev });\n },\n\n // Existing peers initiate to the newcomer\n onPeerJoined(joiningUsers: IPeer<SigType, SigPayload>[]) {\n joiningUsers.forEach(async (user) => {\n const hadState = users.has(user.userId);\n const state = await getPeer(user, true);\n if (!hadState) {\n state.initiateForThisUser = true;\n }\n const pc = state.pc;\n if (!pc) {\n logLine?.(\"👤ℹ️\", \"no pc: \" + user.userId);\n return;\n }\n\n receivePeerConnection({\n pc,\n userId: user.userId,\n initiator: state.initiateForThisUser,\n restart: state.initiateForThisUser\n ? () => restartInitiator(user)\n : () => state.close(),\n });\n if (state.initiateForThisUser) {\n await makeOffer(user);\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 iceUrl = { url, expiration };\n icePromiseResolve?.(iceUrl);\n },\n\n async onMessage(type: SigType, payload: any, from: IPeer) {\n const state = await getPeer(from);\n const pc = state.pc;\n if (!pc) return;\n\n if (type === \"offer\") {\n state.initiateForThisUser = false;\n receivePeerConnection({\n pc,\n userId: from.userId,\n initiator: false,\n restart() {\n // reset PC\n state.close();\n },\n });\n // Responder: set remote offer\n await pc.setRemoteDescription(payload as RTCSessionDescriptionInit);\n\n // Create and send answer\n const answer = await pc.createAnswer();\n await pc.setLocalDescription(answer);\n\n from.receive(\"answer\", pc.localDescription?.toJSON()!);\n\n // Now safe to apply any queued ICE from this peer\n await flushRemoteIce(state);\n return;\n }\n\n if (type === \"answer\") {\n // Initiator: set remote answer\n await pc.setRemoteDescription(payload as RTCSessionDescriptionInit);\n await flushRemoteIce(state);\n return;\n }\n\n if (type === \"ice\") {\n const ice = payload as RTCIceCandidateInit;\n\n // If we don't have remoteDescription yet, queue it\n if (!pc.remoteDescription) {\n state.pendingRemoteIce.push(ice);\n return;\n }\n\n try {\n await pc.addIceCandidate(ice);\n } catch (e) {\n logLine?.(\"⚠️ ERROR\", {\n error: \"add-ice-failed\",\n userId: state.userId,\n detail: String(e),\n });\n }\n return;\n }\n\n if (type === \"broadcast\") {\n onBroadcastMessage?.(payload, from.userId);\n }\n },\n });\n roomsEntered.set(`${host}/room/${room}`, {\n exitRoom,\n room,\n host,\n broadcast: (payload) => {\n sendToServer(\"broadcast\", payload);\n },\n });\n });\n }\n\n return {\n userId,\n enterRoom: enter,\n exitRoom: exit,\n leaveUser,\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(({ userId }) => leaveUser(userId));\n users.clear();\n },\n };\n}\n\n/*\nTurn Token ID\n<CF_TURN_TOKEN_ID>\n\nAPI Token\n<CF_RTC_API_TOKEN>\n\nCURL\ncurl \\\n\t-H \"Authorization: Bearer <CF_RTC_API_TOKEN>\" \\\n\t-H \"Content-Type: application/json\" -d '{\"ttl\": 86400}' \\\n\thttps://rtc.live.cloudflare.com/v1/turn/keys/<CF_TURN_TOKEN_ID>/credentials/generate-ice-servers\n\nJSON\n{\n\t\"iceServers\": [\n {\n \"urls\": [\n \"stun:stun.cloudflare.com:3478\",\n \"turn:turn.cloudflare.com:3478?transport=udp\",\n \"turn:turn.cloudflare.com:3478?transport=tcp\",\n \"turns:turn.cloudflare.com:5349?transport=tcp\"\n ],\n \"username\": \"xxxx\",\n \"credential\": \"yyyy\",\n }\n ]\n}\n\n*/\n",
8
+ "import { 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?: (direction: string, 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\n const messagesListeners = new Set<(data: R, from: string) => void>();\n\n function createDataChannel(\n pc: RTCPeerConnection,\n peerUserId: string,\n initiator: boolean,\n restart?: () => void,\n ) {\n if (initiator) {\n const dc = pc.createDataChannel(\"data\", dataChannelOptions);\n wireDataChannel(peerUserId, dc, restart);\n dataChannels.set(peerUserId, dc);\n } else {\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 return () => {\n pc.removeEventListener(\"datachannel\", listener);\n };\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.addEventListener(\"close\", () => {\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 restart?.();\n });\n dc.onerror = () => logLine?.(\"⚠️ ERROR\", { error: \"dc-error\", userId });\n }\n\n const dataChannels = new Map<string, RTCDataChannel>();\n const userListeners = new Set<UserListener>();\n\n const {\n userId,\n enterRoom,\n exitRoom,\n leaveUser,\n broadcast,\n end: endPeerCollection,\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, initiator, restart }) {\n createDataChannel(pc, userId, initiator, 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 end() {\n dataChannels.forEach((dataChannel) => {\n try {\n dataChannel.close();\n } catch {}\n });\n dataChannels.clear();\n endPeerCollection();\n userListeners.clear();\n userIds.clear();\n },\n };\n}\n"
9
9
  ],
10
- "mappings": "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",
11
- "debugId": "72FB56CFD0D0370464756E2164756E21",
10
+ "mappings": "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",
11
+ "debugId": "6417898587532A6564756E2164756E21",
12
12
  "names": []
13
13
  }
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/browser/sample/index.ts"],"names":[],"mappings":"AA0BA,wBAAgB,QAAQ,SAEvB;AAED,wBAAgB,WAAW,eAuC1B;AAED,wBAAgB,UAAU,CAAC,kBAAkB,EAAE,OAAO,cA4HrD"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/browser/sample/index.ts"],"names":[],"mappings":"AA6BA,wBAAgB,QAAQ,SAEvB;AAED,wBAAgB,WAAW,eAuC1B;AAED,wBAAgB,UAAU,CAAC,kBAAkB,EAAE,OAAO,cA4HrD"}
@@ -0,0 +1,28 @@
1
+ export interface IPeer<T extends string = string, P = any> {
2
+ userId: string;
3
+ receive(type: T, payload: P): boolean;
4
+ }
5
+ /**
6
+ * enterRoom connects to the signaling room via WebSocket.
7
+ */
8
+ export declare function enterRoom<T extends string, P = any>(params: {
9
+ userId: string;
10
+ worldId: string;
11
+ room: string;
12
+ host: string;
13
+ onOpen?: () => void;
14
+ onClose?: (ev: Pick<CloseEvent, "code" | "reason" | "wasClean">) => void;
15
+ onError?: () => void;
16
+ logLine?: (direction: string, obj?: any) => void;
17
+ onPeerJoined(users: IPeer<T, P>[]): void;
18
+ onPeerLeft(users: {
19
+ userId: string;
20
+ }[]): void;
21
+ onIceUrl?(url: string, expiration: number): void;
22
+ onMessage(type: T, payload: P, from: IPeer<T, P>): void;
23
+ autoRejoin?: boolean;
24
+ }): {
25
+ exitRoom: () => void;
26
+ sendToServer: <P extends any>(type: T, payload?: P) => void;
27
+ };
28
+ //# sourceMappingURL=signal-room.d.ts.map
@@ -0,0 +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,CA8JA"}
@@ -0,0 +1,24 @@
1
+ import type { IPeer } from "./impl/signal-room.js";
2
+ export declare function enterRoom<T extends string, P = any>({ userId, worldId, room, host, autoRejoin, onOpen, onClose, onError, onPeerJoined, onPeerLeft, onIceUrl, onMessage, logLine, workerUrl, }: {
3
+ userId: string;
4
+ worldId: string;
5
+ room: string;
6
+ host: string;
7
+ autoRejoin?: boolean;
8
+ onOpen?: () => void;
9
+ onClose?: (ev: Pick<CloseEvent, "code" | "reason" | "wasClean">) => void;
10
+ onError?: () => void;
11
+ onPeerJoined: (users: IPeer<T, P>[]) => void;
12
+ onPeerLeft: (users: {
13
+ userId: string;
14
+ }[]) => void;
15
+ onIceUrl?(url: string, expiration: number): void;
16
+ onMessage: (type: T, payload: P, from: IPeer<T, P>) => void;
17
+ logLine?: (direction: string, obj?: any) => void;
18
+ workerUrl?: URL;
19
+ }): {
20
+ exitRoom: () => void;
21
+ sendToServer: <P extends any>(type: T, payload?: P) => void;
22
+ };
23
+ export type EnterRoom<T extends string, P> = typeof enterRoom<T, P>;
24
+ //# sourceMappingURL=signal-room.d.ts.map
@@ -0,0 +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"}
@@ -0,0 +1,49 @@
1
+ export type RoomEvent<T extends string = string, P = any> = {
2
+ kind: "open";
3
+ } | {
4
+ kind: "close";
5
+ ev: Pick<CloseEvent, "code" | "reason" | "wasClean">;
6
+ } | {
7
+ kind: "error";
8
+ } | {
9
+ kind: "peer-joined";
10
+ users: {
11
+ userId: string;
12
+ }[];
13
+ } | {
14
+ kind: "peer-left";
15
+ users: {
16
+ userId: string;
17
+ }[];
18
+ } | {
19
+ kind: "ice-server";
20
+ url: string;
21
+ expiration: number;
22
+ } | {
23
+ kind: "message";
24
+ type: T;
25
+ payload: P;
26
+ fromUserId: string;
27
+ } | {
28
+ kind: "log";
29
+ direction: string;
30
+ obj?: any;
31
+ };
32
+ export type WorkerCommand<T extends string = string, P = any> = {
33
+ cmd: "enter";
34
+ userId: string;
35
+ worldId: string;
36
+ room: string;
37
+ host: string;
38
+ autoRejoin: boolean;
39
+ } | {
40
+ cmd: "exit";
41
+ } | {
42
+ cmd: "send";
43
+ host: string;
44
+ room: string;
45
+ toUserId?: "server" | string;
46
+ type: T;
47
+ payload?: P;
48
+ };
49
+ //# sourceMappingURL=signal-room.worker.d.ts.map
@@ -0,0 +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,4 +1,4 @@
1
- function R(F){let{userId:N,worldId:Y,room:Z,host:E,autoRejoin:b=!0,logLine:K}=F,T=!1,V=0,D,W,S=!0,_=new Map,x=`wss://${E}/room/${Y}/${Z}?userId=${encodeURIComponent(N)}`,G=[],J=0;function $(q,A,Q){if(!D)return K?.("\uD83D\uDC64 ➡️ ❌","no ws available"),!1;if(T||D.readyState!==WebSocket.OPEN)return K?.("\uD83D\uDC64 ➡️ ❌","Not in opened state: "+D.readyState),!1;let z={type:q,to:A,payload:Q};return G.push(z),K?.("\uD83D\uDC64 ➡️ \uD83D\uDDA5️",z),clearTimeout(J),J=setTimeout(()=>{D.send(JSON.stringify(G)),G.length=0}),!0}function O(){if(T)return;D=new WebSocket(x),D.onopen=()=>{if(S)F.onOpen?.(),S=!1;V=0},D.onmessage=(q)=>{try{let A=JSON.parse(q.data);(Array.isArray(A)?A:[A]).forEach((z)=>{if(K?.("\uD83D\uDDA5️ ➡️ \uD83D\uDC64",z),z.type==="peer-joined"||z.type==="peer-left")H(z.users);else if(z.type==="ice-server")F.onIceUrl?.(z.url,z.expiration);else if(z.userId)F.onMessage(z.type,z.payload,{userId:z.userId,receive:(B,X)=>$(B,z.userId,X)})})}catch{K?.("⚠️ ERROR",{error:"invalid-json"})}},D.onclose=(q)=>{let Q=[1001,1006,1011,1012,1013].includes(q.code);if(b&&!T&&Q){let z=Math.min(Math.pow(2,V)*1000,30000),B=Math.random()*1000,X=z+B;H([]),K?.("\uD83D\uDD04 RECONNECTING",{attempt:V+1,delayMs:Math.round(X)}),V++,W=setTimeout(O,X)}else F.onClose?.({code:q.code,reason:q.reason,wasClean:q.wasClean})},D.onerror=(q)=>{console.error("WS Error",q),F.onError?.()}}function H(q){let A=[],Q=[],z=new Set;q.forEach(({userId:B})=>{if(B===N)return;if(!_.has(B)){let X={userId:B,receive:(M,C)=>$(M,B,C)};_.set(B,X),A.push(X)}z.add(B)});for(let B of _.keys())if(!z.has(B))_.delete(B),Q.push({userId:B});if(A.length)F.onPeerJoined(A);if(Q.length)F.onPeerLeft(Q)}return O(),{sendToServer(q,A){$(q,"server",A)},exitRoom:()=>{T=!0,clearTimeout(W),D.close()}}}function h({userId:F,worldId:N,room:Y,host:Z,autoRejoin:E=!0,onOpen:b,onClose:K,onError:T,onPeerJoined:V,onPeerLeft:D,onIceUrl:W,onMessage:S,logLine:_,workerUrl:x}){if(!x)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"),R({userId:F,worldId:N,room:Y,host:Z,autoRejoin:E,onOpen:b,onClose:K,onError:T,onPeerJoined:V,onPeerLeft:D,onIceUrl:W,onMessage:S});let G=new Worker(x,{type:"module"}),J=!1;function $({userId:H}){return{userId:H,receive:(q,A)=>{if(J)return!1;return G.postMessage({cmd:"send",toUserId:H,host:Z,room:Y,type:q,payload:A}),!0}}}let O=(H)=>{let q=H.data;if(q.kind==="open")b?.();else if(q.kind==="close")G.terminate(),K?.(q.ev);else if(q.kind==="error")T?.();else if(q.kind==="peer-joined")V(q.users.map((A)=>$({userId:A.userId})));else if(q.kind==="peer-left")D(q.users);else if(q.kind==="ice-server")W?.(q.url,q.expiration);else if(q.kind==="message")S(q.type,q.payload,$({userId:q.fromUserId}));else if(q.kind==="log")_?.(q.direction,q.obj)};return G.addEventListener("message",O),G.postMessage({cmd:"enter",userId:F,worldId:N,room:Y,host:Z,autoRejoin:E}),{exitRoom:()=>{J=!0,G.removeEventListener("message",O),G.postMessage({cmd:"exit"})},sendToServer:(H,q)=>{G.postMessage({cmd:"send",toUserId:"server",host:Z,room:Y,type:H,payload:q})}}}export{h as enterRoom};
1
+ function R(F){let{userId:Y,worldId:Z,room:_,host:E,autoRejoin:b=!0,logLine:K}=F,T=!1,V=0,D,W,S=!0,$=new Map,x=`wss://${E}/room/${Z}/${_}?userId=${encodeURIComponent(Y)}`,G=[],J=0;function N(q,A,Q){if(!D)return K?.("\uD83D\uDC64 ➡️ ❌","no ws available"),!1;let z={type:q,to:A,payload:Q};if(G.push(z),K?.("\uD83D\uDC64 ➡️ \uD83D\uDDA5️",z),clearTimeout(J),T||D.readyState!==WebSocket.OPEN)return K?.("\uD83D\uDC64 ➡️ ❌","Not in opened state: "+D.readyState),!1;return J=setTimeout(()=>{D.send(JSON.stringify(G)),G.length=0}),!0}function O(){if(T)return;D=new WebSocket(x),D.onopen=()=>{if(S)F.onOpen?.(),S=!1;V=0},D.onmessage=(q)=>{try{let A=JSON.parse(q.data);(Array.isArray(A)?A:[A]).forEach((z)=>{if(K?.("\uD83D\uDDA5️ ➡️ \uD83D\uDC64",z),z.type==="peer-joined"||z.type==="peer-left")H(z.users);else if(z.type==="ice-server")F.onIceUrl?.(z.url,z.expiration);else if(z.userId)F.onMessage(z.type,z.payload,{userId:z.userId,receive:(B,X)=>N(B,z.userId,X)})})}catch{K?.("⚠️ ERROR",{error:"invalid-json"})}},D.onclose=(q)=>{let Q=[1001,1006,1011,1012,1013].includes(q.code);if(b&&!T&&Q){let z=Math.min(Math.pow(2,V)*1000,30000),B=Math.random()*1000,X=z+B;H([{userId:Y}]),K?.("\uD83D\uDD04 RECONNECTING",{attempt:V+1,delayMs:Math.round(X)}),V++,W=setTimeout(O,X)}else F.onClose?.({code:q.code,reason:q.reason,wasClean:q.wasClean})},D.onerror=(q)=>{console.error("WS Error",q),F.onError?.()}}function H(q){let A=[],Q=[],z=new Set;q.forEach(({userId:B})=>{if(B===Y)return;if(!$.has(B)){let X={userId:B,receive:(M,C)=>N(M,B,C)};$.set(B,X),A.push(X)}z.add(B)});for(let B of $.keys())if(!z.has(B))$.delete(B),Q.push({userId:B});if(A.length)F.onPeerJoined(A);if(Q.length)F.onPeerLeft(Q)}return O(),{sendToServer(q,A){N(q,"server",A)},exitRoom:()=>{T=!0,clearTimeout(W),D.close()}}}function h({userId:F,worldId:Y,room:Z,host:_,autoRejoin:E=!0,onOpen:b,onClose:K,onError:T,onPeerJoined:V,onPeerLeft:D,onIceUrl:W,onMessage:S,logLine:$,workerUrl:x}){if(!x)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"),R({userId:F,worldId:Y,room:Z,host:_,autoRejoin:E,onOpen:b,onClose:K,onError:T,onPeerJoined:V,onPeerLeft:D,onIceUrl:W,onMessage:S});let G=new Worker(x,{type:"module"}),J=!1;function N({userId:H}){return{userId:H,receive:(q,A)=>{if(J)return!1;return G.postMessage({cmd:"send",toUserId:H,host:_,room:Z,type:q,payload:A}),!0}}}let O=(H)=>{let q=H.data;if(q.kind==="open")b?.();else if(q.kind==="close")G.terminate(),K?.(q.ev);else if(q.kind==="error")T?.();else if(q.kind==="peer-joined")V(q.users.map((A)=>N({userId:A.userId})));else if(q.kind==="peer-left")D(q.users);else if(q.kind==="ice-server")W?.(q.url,q.expiration);else if(q.kind==="message")S(q.type,q.payload,N({userId:q.fromUserId}));else if(q.kind==="log")$?.(q.direction,q.obj)};return G.addEventListener("message",O),G.postMessage({cmd:"enter",userId:F,worldId:Y,room:Z,host:_,autoRejoin:E}),{exitRoom:()=>{J=!0,G.removeEventListener("message",O),G.postMessage({cmd:"exit"})},sendToServer:(H,q)=>{G.postMessage({cmd:"send",toUserId:"server",host:_,room:Z,type:H,payload:q})}}}export{h as enterRoom};
2
2
 
3
- //# debugId=1C982C019A47BA5E64756E2164756E21
3
+ //# debugId=2E73154A4F20DF8064756E2164756E21
4
4
  //# sourceMappingURL=signal-room.js.map
@@ -2,10 +2,10 @@
2
2
  "version": 3,
3
3
  "sources": ["../src/browser/impl/signal-room.ts", "../src/browser/signal-room.ts"],
4
4
  "sourcesContent": [
5
- "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 if (exited || ws.readyState !== WebSocket.OPEN) {\n logLine?.(\"👤 ➡️ ❌\", \"Not in opened state: \" + ws.readyState);\n return false;\n }\n const obj: OutMessage = { type, to, payload };\n accumulatedMessages.push(obj);\n // ws.send(JSON.stringify(obj));\n logLine?.(\"👤 ➡️ 🖥️\", obj);\n clearTimeout(timeout);\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);\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 updatePeers([]);\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 }[]) {\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 (!peers.has(pUserId)) {\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 (!updatedPeerSet.has(pUserId)) {\n peers.delete(pUserId);\n left.push({ userId: pUserId });\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",
5
+ "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 // ws.send(JSON.stringify(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);\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 updatePeers([{ userId }]);\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 }[]) {\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 (!peers.has(pUserId)) {\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 (!updatedPeerSet.has(pUserId)) {\n peers.delete(pUserId);\n left.push({ userId: pUserId });\n }\n }\n // Notify peer joined first then peer left. (avoid disconnect in case the peer leaving / joining is on the same user).\n if (joined.length) params.onPeerJoined(joined);\n if (left.length) params.onPeerLeft(left);\n }\n\n // Start initial connection\n connect();\n\n return {\n sendToServer(type, payload) {\n send(type, \"server\", payload);\n },\n exitRoom: () => {\n exited = true;\n clearTimeout(timeoutId);\n ws.close();\n },\n };\n}\n",
6
6
  "import type { IPeer } from \"./impl/signal-room.js\";\nimport { enterRoom as baseEnterRoom } from \"./impl/signal-room.js\";\nimport { RoomEvent, WorkerCommand } from \"./signal-room.worker.js\";\n\nexport function enterRoom<T extends string, P = any>({\n userId,\n worldId,\n room,\n host,\n autoRejoin = true,\n onOpen,\n onClose,\n onError,\n onPeerJoined,\n onPeerLeft,\n onIceUrl,\n onMessage,\n logLine,\n workerUrl,\n}: {\n userId: string;\n worldId: string;\n room: string;\n host: string;\n autoRejoin?: boolean;\n onOpen?: () => void;\n onClose?: (ev: Pick<CloseEvent, \"code\" | \"reason\" | \"wasClean\">) => void;\n onError?: () => void;\n onPeerJoined: (users: IPeer<T, P>[]) => void;\n onPeerLeft: (users: { userId: string }[]) => void;\n onIceUrl?(url: string, expiration: number): void;\n onMessage: (type: T, payload: P, from: IPeer<T, P>) => void;\n logLine?: (direction: string, obj?: any) => void;\n\n // Pass the URL to your worker file (bundler will handle it)\n workerUrl?: URL;\n}): {\n exitRoom: () => void;\n sendToServer: <P extends any>(type: T, payload?: P) => void;\n} {\n if (!workerUrl) {\n const CDN_WORKER_URL = `https://cdn.jsdelivr.net/npm/@dobuki/hello-worker/dist/signal-room.worker.min.js`;\n\n console.warn(\n \"Warning: enterRoom called without workerUrl; this may cause issues in some environments. You should pass workerUrl explicitly. Use:\",\n CDN_WORKER_URL,\n );\n return baseEnterRoom<T, P>({\n userId,\n worldId,\n room,\n host,\n autoRejoin,\n onOpen,\n onClose,\n onError,\n onPeerJoined,\n onPeerLeft,\n onIceUrl,\n onMessage,\n });\n }\n const worker = new Worker(workerUrl, { type: \"module\" });\n let exited = false;\n\n function makeUser({ userId }: { userId: string }): IPeer<T, P> {\n return {\n userId,\n receive: (type: T, payload: P) => {\n if (exited) return false;\n worker.postMessage({\n cmd: \"send\",\n toUserId: userId,\n host,\n room,\n type,\n payload,\n } as WorkerCommand);\n return true;\n },\n };\n }\n\n const onWorkerMessage = (e: MessageEvent<RoomEvent<T, P>>) => {\n const ev = e.data;\n\n if (ev.kind === \"open\") onOpen?.();\n else if (ev.kind === \"close\") {\n worker.terminate();\n onClose?.(ev.ev);\n } else if (ev.kind === \"error\") onError?.();\n else if (ev.kind === \"peer-joined\")\n onPeerJoined(ev.users.map((ev) => makeUser({ userId: ev.userId })));\n else if (ev.kind === \"peer-left\") onPeerLeft(ev.users);\n else if (ev.kind === \"ice-server\") onIceUrl?.(ev.url, ev.expiration);\n else if (ev.kind === \"message\")\n onMessage(ev.type, ev.payload, makeUser({ userId: ev.fromUserId }));\n else if (ev.kind === \"log\") logLine?.(ev.direction, ev.obj);\n };\n\n worker.addEventListener(\"message\", onWorkerMessage);\n\n worker.postMessage({\n cmd: \"enter\",\n userId,\n worldId,\n room,\n host,\n autoRejoin,\n } as WorkerCommand);\n\n return {\n exitRoom: () => {\n exited = true;\n worker.removeEventListener(\"message\", onWorkerMessage);\n worker.postMessage({ cmd: \"exit\" } as WorkerCommand);\n },\n sendToServer: <P extends any>(type: T, payload?: P) => {\n worker.postMessage({\n cmd: \"send\",\n toUserId: \"server\",\n host,\n room,\n type,\n payload,\n } as WorkerCommand);\n },\n };\n}\n\nexport type EnterRoom<T extends string, P> = typeof enterRoom<T, P>;\n"
7
7
  ],
8
- "mappings": "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",
9
- "debugId": "1C982C019A47BA5E64756E2164756E21",
8
+ "mappings": "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",
9
+ "debugId": "2E73154A4F20DF8064756E2164756E21",
10
10
  "names": []
11
11
  }
@@ -1,4 +1,4 @@
1
- function R(G){let{userId:q,worldId:V,room:A,host:B,autoRejoin:K=!0,logLine:Y}=G,_=!1,$=0,J,T,h=!0,k=new Map,C=`wss://${B}/room/${V}/${A}?userId=${encodeURIComponent(q)}`,W=[],M=0;function F(D,H,O){if(!J)return Y?.("\uD83D\uDC64 ➡️ ❌","no ws available"),!1;if(_||J.readyState!==WebSocket.OPEN)return Y?.("\uD83D\uDC64 ➡️ ❌","Not in opened state: "+J.readyState),!1;let z={type:D,to:H,payload:O};return W.push(z),Y?.("\uD83D\uDC64 ➡️ \uD83D\uDDA5️",z),clearTimeout(M),M=setTimeout(()=>{J.send(JSON.stringify(W)),W.length=0}),!0}function L(){if(_)return;J=new WebSocket(C),J.onopen=()=>{if(h)G.onOpen?.(),h=!1;$=0},J.onmessage=(D)=>{try{let H=JSON.parse(D.data);(Array.isArray(H)?H:[H]).forEach((z)=>{if(Y?.("\uD83D\uDDA5️ ➡️ \uD83D\uDC64",z),z.type==="peer-joined"||z.type==="peer-left")P(z.users);else if(z.type==="ice-server")G.onIceUrl?.(z.url,z.expiration);else if(z.userId)G.onMessage(z.type,z.payload,{userId:z.userId,receive:(E,X)=>F(E,z.userId,X)})})}catch{Y?.("⚠️ ERROR",{error:"invalid-json"})}},J.onclose=(D)=>{let O=[1001,1006,1011,1012,1013].includes(D.code);if(K&&!_&&O){let z=Math.min(Math.pow(2,$)*1000,30000),E=Math.random()*1000,X=z+E;P([]),Y?.("\uD83D\uDD04 RECONNECTING",{attempt:$+1,delayMs:Math.round(X)}),$++,T=setTimeout(L,X)}else G.onClose?.({code:D.code,reason:D.reason,wasClean:D.wasClean})},J.onerror=(D)=>{console.error("WS Error",D),G.onError?.()}}function P(D){let H=[],O=[],z=new Set;D.forEach(({userId:E})=>{if(E===q)return;if(!k.has(E)){let X={userId:E,receive:(x,S)=>F(x,E,S)};k.set(E,X),H.push(X)}z.add(E)});for(let E of k.keys())if(!z.has(E))k.delete(E),O.push({userId:E});if(H.length)G.onPeerJoined(H);if(O.length)G.onPeerLeft(O)}return L(),{sendToServer(D,H){F(D,"server",H)},exitRoom:()=>{_=!0,clearTimeout(T),J.close()}}}var N=null,b,Z=new Map;function Q(G){self.postMessage(G)}self.addEventListener("message",(G)=>{let q=G.data;if(console.debug("[signal-room.worker] received command",q),q.cmd==="enter"){N?.(),N=null,Z.clear();let V=R({userId:q.userId,worldId:q.worldId,room:q.room,host:q.host,autoRejoin:q.autoRejoin,onOpen:()=>Q({kind:"open"}),onClose:({code:A,reason:B,wasClean:K})=>Q({kind:"close",ev:{code:A,reason:B,wasClean:K}}),onError:()=>Q({kind:"error"}),logLine:(A,B)=>{console.debug(`[signal-room.worker] ${A}`,B),Q({kind:"log",direction:A,obj:B})},onPeerJoined:(A)=>{A.forEach(({userId:B,receive:K})=>Z.set(`${q.host}/${q.room}/${B}`,K)),Q({kind:"peer-joined",users:A.map(({userId:B})=>({userId:B}))})},onPeerLeft:(A)=>{A.forEach(({userId:B})=>Z.delete(`${q.host}/${q.room}/${B}`)),Q({kind:"peer-left",users:A})},onIceUrl(A,B){Q({kind:"ice-server",url:A,expiration:B})},onMessage:(A,B,K)=>{Z.set(`${q.host}/${q.room}/${K.userId}`,K.receive),Q({kind:"message",type:A,payload:B,fromUserId:K.userId})}});N=V.exitRoom,b=V.sendToServer;return}if(q.cmd==="send"){if(q.toUserId==="server")b?.(q.type,q.payload);else if(q.toUserId){let V=Z.get(`${q.host}/${q.room}/${q.toUserId}`);if(V)V(q.type,q.payload)}return}if(q.cmd==="exit"){N?.(),self.close();return}});
1
+ function R(G){let{userId:q,worldId:V,room:A,host:B,autoRejoin:K=!0,logLine:Y}=G,_=!1,$=0,J,T,h=!0,k=new Map,C=`wss://${B}/room/${V}/${A}?userId=${encodeURIComponent(q)}`,W=[],M=0;function F(D,H,O){if(!J)return Y?.("\uD83D\uDC64 ➡️ ❌","no ws available"),!1;let z={type:D,to:H,payload:O};if(W.push(z),Y?.("\uD83D\uDC64 ➡️ \uD83D\uDDA5️",z),clearTimeout(M),_||J.readyState!==WebSocket.OPEN)return Y?.("\uD83D\uDC64 ➡️ ❌","Not in opened state: "+J.readyState),!1;return M=setTimeout(()=>{J.send(JSON.stringify(W)),W.length=0}),!0}function L(){if(_)return;J=new WebSocket(C),J.onopen=()=>{if(h)G.onOpen?.(),h=!1;$=0},J.onmessage=(D)=>{try{let H=JSON.parse(D.data);(Array.isArray(H)?H:[H]).forEach((z)=>{if(Y?.("\uD83D\uDDA5️ ➡️ \uD83D\uDC64",z),z.type==="peer-joined"||z.type==="peer-left")P(z.users);else if(z.type==="ice-server")G.onIceUrl?.(z.url,z.expiration);else if(z.userId)G.onMessage(z.type,z.payload,{userId:z.userId,receive:(E,X)=>F(E,z.userId,X)})})}catch{Y?.("⚠️ ERROR",{error:"invalid-json"})}},J.onclose=(D)=>{let O=[1001,1006,1011,1012,1013].includes(D.code);if(K&&!_&&O){let z=Math.min(Math.pow(2,$)*1000,30000),E=Math.random()*1000,X=z+E;P([{userId:q}]),Y?.("\uD83D\uDD04 RECONNECTING",{attempt:$+1,delayMs:Math.round(X)}),$++,T=setTimeout(L,X)}else G.onClose?.({code:D.code,reason:D.reason,wasClean:D.wasClean})},J.onerror=(D)=>{console.error("WS Error",D),G.onError?.()}}function P(D){let H=[],O=[],z=new Set;D.forEach(({userId:E})=>{if(E===q)return;if(!k.has(E)){let X={userId:E,receive:(x,S)=>F(x,E,S)};k.set(E,X),H.push(X)}z.add(E)});for(let E of k.keys())if(!z.has(E))k.delete(E),O.push({userId:E});if(H.length)G.onPeerJoined(H);if(O.length)G.onPeerLeft(O)}return L(),{sendToServer(D,H){F(D,"server",H)},exitRoom:()=>{_=!0,clearTimeout(T),J.close()}}}var N=null,b,Z=new Map;function Q(G){self.postMessage(G)}self.addEventListener("message",(G)=>{let q=G.data;if(console.debug("[signal-room.worker] received command",q),q.cmd==="enter"){N?.(),N=null,Z.clear();let V=R({userId:q.userId,worldId:q.worldId,room:q.room,host:q.host,autoRejoin:q.autoRejoin,onOpen:()=>Q({kind:"open"}),onClose:({code:A,reason:B,wasClean:K})=>Q({kind:"close",ev:{code:A,reason:B,wasClean:K}}),onError:()=>Q({kind:"error"}),logLine:(A,B)=>{console.debug(`[signal-room.worker] ${A}`,B),Q({kind:"log",direction:A,obj:B})},onPeerJoined:(A)=>{A.forEach(({userId:B,receive:K})=>Z.set(`${q.host}/${q.room}/${B}`,K)),Q({kind:"peer-joined",users:A.map(({userId:B})=>({userId:B}))})},onPeerLeft:(A)=>{A.forEach(({userId:B})=>Z.delete(`${q.host}/${q.room}/${B}`)),Q({kind:"peer-left",users:A})},onIceUrl(A,B){Q({kind:"ice-server",url:A,expiration:B})},onMessage:(A,B,K)=>{Z.set(`${q.host}/${q.room}/${K.userId}`,K.receive),Q({kind:"message",type:A,payload:B,fromUserId:K.userId})}});N=V.exitRoom,b=V.sendToServer;return}if(q.cmd==="send"){if(q.toUserId==="server")b?.(q.type,q.payload);else if(q.toUserId){let V=Z.get(`${q.host}/${q.room}/${q.toUserId}`);if(V)V(q.type,q.payload)}return}if(q.cmd==="exit"){N?.(),self.close();return}});
2
2
 
3
- //# debugId=07D20F83682E41A764756E2164756E21
3
+ //# debugId=7038C64E6E6DC81764756E2164756E21
4
4
  //# sourceMappingURL=signal-room.worker.js.map
@@ -2,10 +2,10 @@
2
2
  "version": 3,
3
3
  "sources": ["../src/browser/impl/signal-room.ts", "../src/browser/signal-room.worker.ts"],
4
4
  "sourcesContent": [
5
- "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 if (exited || ws.readyState !== WebSocket.OPEN) {\n logLine?.(\"👤 ➡️ ❌\", \"Not in opened state: \" + ws.readyState);\n return false;\n }\n const obj: OutMessage = { type, to, payload };\n accumulatedMessages.push(obj);\n // ws.send(JSON.stringify(obj));\n logLine?.(\"👤 ➡️ 🖥️\", obj);\n clearTimeout(timeout);\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);\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 updatePeers([]);\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 }[]) {\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 (!peers.has(pUserId)) {\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 (!updatedPeerSet.has(pUserId)) {\n peers.delete(pUserId);\n left.push({ userId: pUserId });\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",
5
+ "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 // ws.send(JSON.stringify(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);\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 updatePeers([{ userId }]);\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 }[]) {\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 (!peers.has(pUserId)) {\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 (!updatedPeerSet.has(pUserId)) {\n peers.delete(pUserId);\n left.push({ userId: pUserId });\n }\n }\n // Notify peer joined first then peer left. (avoid disconnect in case the peer leaving / joining is on the same user).\n if (joined.length) params.onPeerJoined(joined);\n if (left.length) params.onPeerLeft(left);\n }\n\n // Start initial connection\n connect();\n\n return {\n sendToServer(type, payload) {\n send(type, \"server\", payload);\n },\n exitRoom: () => {\n exited = true;\n clearTimeout(timeoutId);\n ws.close();\n },\n };\n}\n",
6
6
  "/// <reference lib=\"webworker\" />\n\nimport { enterRoom, type IPeer } from \"./impl/signal-room.js\";\n\nexport type RoomEvent<T extends string = string, P = any> =\n | { kind: \"open\" }\n | { kind: \"close\"; ev: Pick<CloseEvent, \"code\" | \"reason\" | \"wasClean\"> }\n | { kind: \"error\" }\n | { kind: \"peer-joined\"; users: { userId: string }[] }\n | { kind: \"peer-left\"; users: { userId: string }[] }\n | { kind: \"ice-server\"; url: string; expiration: number }\n | {\n kind: \"message\";\n type: T;\n payload: P;\n fromUserId: string;\n }\n | { kind: \"log\"; direction: string; obj?: any };\n\nexport type WorkerCommand<T extends string = string, P = any> =\n | {\n cmd: \"enter\";\n userId: string;\n worldId: string;\n room: string;\n host: string;\n autoRejoin: boolean;\n }\n | { cmd: \"exit\" }\n | {\n cmd: \"send\";\n host: string;\n room: string;\n toUserId?: \"server\" | string;\n type: T;\n payload?: P;\n };\n\nlet exitRoom: (() => void) | null = null;\nlet sendToServer: (type: string, payload?: any) => void;\n\n// Map from userId -> a function to send to that peer (comes from IUser.receive)\nconst peerSend = new Map<string, (type: any, payload: any) => boolean>();\n\nfunction emit<T extends string, P>(ev: RoomEvent<T, P>) {\n (self as DedicatedWorkerGlobalScope).postMessage(ev);\n}\n\nself.addEventListener(\"message\", (e: MessageEvent<WorkerCommand>) => {\n const msg = e.data;\n console.debug(\"[signal-room.worker] received command\", msg);\n\n if (msg.cmd === \"enter\") {\n // If re-entering, clean up first\n exitRoom?.();\n exitRoom = null;\n peerSend.clear();\n\n const result = enterRoom({\n userId: msg.userId,\n worldId: msg.worldId,\n room: msg.room,\n host: msg.host,\n autoRejoin: msg.autoRejoin,\n onOpen: () => emit({ kind: \"open\" }),\n onClose: ({\n code,\n reason,\n wasClean,\n }: Pick<CloseEvent, \"code\" | \"reason\" | \"wasClean\">) =>\n emit({ kind: \"close\", ev: { code, reason, wasClean } }),\n onError: () => emit({ kind: \"error\" }),\n logLine: (direction: string, obj?: any) => {\n console.debug(`[signal-room.worker] ${direction}`, obj);\n emit({ kind: \"log\", direction, obj });\n },\n onPeerJoined: (users: IPeer[]) => {\n // Save the ability to send to this peer\n users.forEach(({ userId, receive }) =>\n peerSend.set(`${msg.host}/${msg.room}/${userId}`, receive),\n );\n emit({\n kind: \"peer-joined\",\n users: users.map(({ userId }) => ({ userId })),\n });\n },\n onPeerLeft: (users: { userId: string }[]) => {\n users.forEach(({ userId }) =>\n peerSend.delete(`${msg.host}/${msg.room}/${userId}`),\n );\n emit({ kind: \"peer-left\", users });\n },\n onIceUrl(url: string, expiration: number) {\n emit({ kind: \"ice-server\", url, expiration });\n },\n onMessage: (type: any, payload: any, from: IPeer) => {\n // We can also learn peerSend via onMessage in case join events vary\n peerSend.set(`${msg.host}/${msg.room}/${from.userId}`, from.receive);\n emit({\n kind: \"message\",\n type,\n payload,\n fromUserId: from.userId,\n });\n },\n });\n\n exitRoom = result.exitRoom;\n sendToServer = result.sendToServer;\n return;\n }\n\n if (msg.cmd === \"send\") {\n if (msg.toUserId === \"server\") {\n sendToServer?.(msg.type, msg.payload);\n } else if (msg.toUserId) {\n const sendFn = peerSend.get(`${msg.host}/${msg.room}/${msg.toUserId}`);\n if (sendFn) sendFn(msg.type, msg.payload);\n }\n return;\n }\n\n if (msg.cmd === \"exit\") {\n exitRoom?.();\n self.close();\n return;\n }\n});\n"
7
7
  ],
8
- "mappings": "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",
9
- "debugId": "07D20F83682E41A764756E2164756E21",
8
+ "mappings": "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",
9
+ "debugId": "7038C64E6E6DC81764756E2164756E21",
10
10
  "names": []
11
11
  }
@@ -1,4 +1,4 @@
1
- import { EnterRoom } from "./signal-room";
1
+ import { EnterRoom } from "./signal/signal-room";
2
2
  export type SigType = "offer" | "answer" | "ice" | "request-ice" | "broadcast";
3
3
  export type SigPayload = RTCSessionDescriptionInit | RTCIceCandidateInit;
4
4
  export declare function collectPeerConnections({ userId: passedUserId, worldId, receivePeerConnection, peerlessUserExpiration, fallbackRtcConfig, enterRoomFunction: enterRoom, logLine, onLeaveUser, workerUrl, onRoomReady, onRoomClose, onBroadcastMessage, }: {
@@ -1 +1 @@
1
- {"version":3,"file":"webrtc-peer-collector.d.ts","sourceRoot":"","sources":["../src/browser/webrtc-peer-collector.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAa,MAAM,eAAe,CAAC;AAErD,MAAM,MAAM,OAAO,GAAG,OAAO,GAAG,QAAQ,GAAG,KAAK,GAAG,aAAa,GAAG,WAAW,CAAC;AAC/E,MAAM,MAAM,UAAU,GAAG,yBAAyB,GAAG,mBAAmB,CAAC;AAiBzE,wBAAgB,sBAAsB,CAAC,EACrC,MAAM,EAAE,YAAY,EACpB,OAAO,EACP,qBAAqB,EACrB,sBAA6B,EAC7B,iBAEC,EACD,iBAAiB,EAAE,SAA8B,EACjD,OAAO,EACP,WAAW,EACX,SAAS,EACT,WAAW,EACX,WAAW,EACX,kBAAkB,GACnB,EAAE;IACD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,iBAAiB,CAAC,EAAE,gBAAgB,CAAC;IACrC,iBAAiB,CAAC,EAAE,SAAS,CAAC,OAAO,EAAE,UAAU,CAAC,CAAC;IACnD,WAAW,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IACvC,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,GAAG,KAAK,IAAI,CAAC;IACjD,SAAS,CAAC,EAAE,GAAG,CAAC;IAChB,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,qBAAqB,CAAC,UAAU,EAAE;QAChC,EAAE,EAAE,iBAAiB,CAAC;QACtB,MAAM,EAAE,MAAM,CAAC;QACf,SAAS,EAAE,OAAO,CAAC;QACnB,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;;gCA0EgC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE;+BAT/B;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE;wBA7BjC,MAAM;cAiRrB,CAAC,2BAAuB,CAAC;;EAUtC"}
1
+ {"version":3,"file":"webrtc-peer-collector.d.ts","sourceRoot":"","sources":["../src/browser/webrtc-peer-collector.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAa,MAAM,sBAAsB,CAAC;AAE5D,MAAM,MAAM,OAAO,GAAG,OAAO,GAAG,QAAQ,GAAG,KAAK,GAAG,aAAa,GAAG,WAAW,CAAC;AAC/E,MAAM,MAAM,UAAU,GAAG,yBAAyB,GAAG,mBAAmB,CAAC;AAmBzE,wBAAgB,sBAAsB,CAAC,EACrC,MAAM,EAAE,YAAY,EACpB,OAAO,EACP,qBAAqB,EACrB,sBAA6B,EAC7B,iBAEC,EACD,iBAAiB,EAAE,SAA8B,EACjD,OAAO,EACP,WAAW,EACX,SAAS,EACT,WAAW,EACX,WAAW,EACX,kBAAkB,GACnB,EAAE;IACD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,iBAAiB,CAAC,EAAE,gBAAgB,CAAC;IACrC,iBAAiB,CAAC,EAAE,SAAS,CAAC,OAAO,EAAE,UAAU,CAAC,CAAC;IACnD,WAAW,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IACvC,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,GAAG,KAAK,IAAI,CAAC;IACjD,SAAS,CAAC,EAAE,GAAG,CAAC;IAChB,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,qBAAqB,CAAC,UAAU,EAAE;QAChC,EAAE,EAAE,iBAAiB,CAAC;QACtB,MAAM,EAAE,MAAM,CAAC;QACf,SAAS,EAAE,OAAO,CAAC;QACnB,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;;gCA0EgC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE;+BAT/B;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE;wBA7BjC,MAAM;cA0RrB,CAAC,2BAAuB,CAAC;;EAUtC"}
@@ -1,4 +1,4 @@
1
- function v(D){let{userId:j,worldId:S,room:T,host:R,autoRejoin:P=!0,logLine:q}=D,N=!1,O=0,H,E,k=!0,_=new Map,K=`wss://${R}/room/${S}/${T}?userId=${encodeURIComponent(j)}`,z=[],b=0;function A(Q,$,M){if(!H)return q?.("\uD83D\uDC64 ➡️ ❌","no ws available"),!1;if(N||H.readyState!==WebSocket.OPEN)return q?.("\uD83D\uDC64 ➡️ ❌","Not in opened state: "+H.readyState),!1;let G={type:Q,to:$,payload:M};return z.push(G),q?.("\uD83D\uDC64 ➡️ \uD83D\uDDA5️",G),clearTimeout(b),b=setTimeout(()=>{H.send(JSON.stringify(z)),z.length=0}),!0}function L(){if(N)return;H=new WebSocket(K),H.onopen=()=>{if(k)D.onOpen?.(),k=!1;O=0},H.onmessage=(Q)=>{try{let $=JSON.parse(Q.data);(Array.isArray($)?$:[$]).forEach((G)=>{if(q?.("\uD83D\uDDA5️ ➡️ \uD83D\uDC64",G),G.type==="peer-joined"||G.type==="peer-left")W(G.users);else if(G.type==="ice-server")D.onIceUrl?.(G.url,G.expiration);else if(G.userId)D.onMessage(G.type,G.payload,{userId:G.userId,receive:(X,B)=>A(X,G.userId,B)})})}catch{q?.("⚠️ ERROR",{error:"invalid-json"})}},H.onclose=(Q)=>{let M=[1001,1006,1011,1012,1013].includes(Q.code);if(P&&!N&&M){let G=Math.min(Math.pow(2,O)*1000,30000),X=Math.random()*1000,B=G+X;W([]),q?.("\uD83D\uDD04 RECONNECTING",{attempt:O+1,delayMs:Math.round(B)}),O++,E=setTimeout(L,B)}else D.onClose?.({code:Q.code,reason:Q.reason,wasClean:Q.wasClean})},H.onerror=(Q)=>{console.error("WS Error",Q),D.onError?.()}}function W(Q){let $=[],M=[],G=new Set;Q.forEach(({userId:X})=>{if(X===j)return;if(!_.has(X)){let B={userId:X,receive:(x,C)=>A(x,X,C)};_.set(X,B),$.push(B)}G.add(X)});for(let X of _.keys())if(!G.has(X))_.delete(X),M.push({userId:X});if($.length)D.onPeerJoined($);if(M.length)D.onPeerLeft(M)}return L(),{sendToServer(Q,$){A(Q,"server",$)},exitRoom:()=>{N=!0,clearTimeout(E),H.close()}}}function g({userId:D,worldId:j,room:S,host:T,autoRejoin:R=!0,onOpen:P,onClose:q,onError:N,onPeerJoined:O,onPeerLeft:H,onIceUrl:E,onMessage:k,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"),v({userId:D,worldId:j,room:S,host:T,autoRejoin:R,onOpen:P,onClose:q,onError:N,onPeerJoined:O,onPeerLeft:H,onIceUrl:E,onMessage:k});let z=new Worker(K,{type:"module"}),b=!1;function A({userId:W}){return{userId:W,receive:(Q,$)=>{if(b)return!1;return z.postMessage({cmd:"send",toUserId:W,host:T,room:S,type:Q,payload:$}),!0}}}let L=(W)=>{let Q=W.data;if(Q.kind==="open")P?.();else if(Q.kind==="close")z.terminate(),q?.(Q.ev);else if(Q.kind==="error")N?.();else if(Q.kind==="peer-joined")O(Q.users.map(($)=>A({userId:$.userId})));else if(Q.kind==="peer-left")H(Q.users);else if(Q.kind==="ice-server")E?.(Q.url,Q.expiration);else if(Q.kind==="message")k(Q.type,Q.payload,A({userId:Q.fromUserId}));else if(Q.kind==="log")_?.(Q.direction,Q.obj)};return z.addEventListener("message",L),z.postMessage({cmd:"enter",userId:D,worldId:j,room:S,host:T,autoRejoin:R}),{exitRoom:()=>{b=!0,z.removeEventListener("message",L),z.postMessage({cmd:"exit"})},sendToServer:(W,Q)=>{z.postMessage({cmd:"send",toUserId:"server",host:T,room:S,type:W,payload:Q})}}}var d=g;function s({userId:D,worldId:j,receivePeerConnection:S,peerlessUserExpiration:T=5000,fallbackRtcConfig:R={iceServers:[{urls:"stun:stun.l.google.com:19302"}]},enterRoomFunction:P=d,logLine:q,onLeaveUser:N,workerUrl:O,onRoomReady:H,onRoomClose:E,onBroadcastMessage:k}){let _=D??`user-${crypto.randomUUID()}`,K=new Map,z=void 0,b={...R,timestamp:Date.now()},A=new Map;async function L(G){if(G)try{let X=await fetch(G);if(!X.ok)throw Error(`ICE endpoint failed: ${X.status}`);b=await X.json()}catch(X){console.warn("Using fallback rtcConfig:",X)}return b}function W(G){N?.(G);let X=K.get(G);if(!X)return;try{X.pc?.close()}catch{}K.delete(G)}async function Q(G){if(!G.pc?.remoteDescription)return;let X=G.pendingRemoteIce;G.pendingRemoteIce=[];for(let B of X)try{await G.pc.addIceCandidate(B)}catch(x){q?.("⚠️ ERROR",{error:"add-ice-failed",userId:G.userId,detail:String(x)})}}function $({room:G,host:X}){let B=`${X}/room/${G}`,x=A.get(B);if(x)x.exitRoom(),A.delete(B)}function M({room:G,host:X}){return new Promise(async(B,x)=>{async function C(Y){let V=Date.now();if(V-(b?.timestamp??0)>1e4){let Z=!z||z.expiration-V<2000?await c():z;b=await L(Z.url)}return Y.pc=new RTCPeerConnection(b),Y.pc.onicecandidate=(Z)=>{if(!Z.candidate)return;Y.peer.receive("ice",Z.candidate.toJSON())},Y.pc.onconnectionstatechange=()=>{q?.("\uD83D\uDCAC",{event:"pc-state",userId:Y.userId,state:Y.pc?.connectionState})},Y.pc}async function w(Y){let V=K.get(Y.userId),Z=!1;if(!V){let F={userId:Y.userId,pendingRemoteIce:[],peer:Y};await C(F),V=F,K.set(V.userId,V),Z=!0}else if(V)clearTimeout(V.expirationTimeout),V.expirationTimeout=0;if(!V.pc||V.pc?.signalingState==="closed")await C(V);return V.peer=Y,[V,Z]}async function U(Y){let[V]=await w(Y),Z=V.pc,F=await Z?.createOffer();await Z?.setLocalDescription(F),Y.receive("offer",Z?.localDescription?.toJSON())}let y;async function c(){let Y=await new Promise((V)=>{y=V,I("request-ice")});return y=void 0,Y}let{exitRoom:u,sendToServer:I}=P({userId:_,worldId:j,room:G,host:X,logLine:q,workerUrl:O,autoRejoin:!0,onOpen(){H?.({room:G,host:X}),B()},onError(){console.error("onError"),x()},onClose(Y){E?.({room:G,host:X,ev:Y})},onPeerJoined(Y){Y.forEach(async(V)=>{let[Z,F]=await w(V);if(!F){q?.("\uD83D\uDC64ℹ️","not a new peer: "+V.userId);return}let J=Z.pc;if(!J){q?.("\uD83D\uDC64ℹ️","no pc: "+V.userId);return}async function h(){let f=K.get(V.userId);if(f){f.pc=void 0;let m=await C(f);S({pc:m,userId:V.userId,initiator:!0,restart:h}),await new Promise((p)=>setTimeout(p,3000)),await U(V)}}S({pc:J,userId:V.userId,initiator:!0,restart:h}),await U(V)})},onPeerLeft(Y){Y.forEach(({userId:V})=>{let Z=K.get(V);if(!Z)return;Z.expirationTimeout=setTimeout(()=>W(V),T??0)})},onIceUrl(Y,V){z={url:Y,expiration:V},y?.(z)},async onMessage(Y,V,Z){let[F]=await w(Z),J=F.pc;if(!J)return;if(Y==="offer"){S({pc:J,userId:Z.userId,initiator:!1,restart(){F.pc=void 0}}),await J.setRemoteDescription(V);let h=await J.createAnswer();await J.setLocalDescription(h),Z.receive("answer",J.localDescription?.toJSON()),await Q(F);return}if(Y==="answer"){await J.setRemoteDescription(V),await Q(F);return}if(Y==="ice"){let h=V;if(!J.remoteDescription){F.pendingRemoteIce.push(h);return}try{await J.addIceCandidate(h)}catch(f){q?.("⚠️ ERROR",{error:"add-ice-failed",userId:F.userId,detail:String(f)})}return}if(Y==="broadcast")k?.(V,Z.userId)}});A.set(`${X}/room/${G}`,{exitRoom:u,room:G,host:X,broadcast:(Y)=>{I("broadcast",Y)}})})}return{userId:_,enterRoom:M,exitRoom:$,leaveUser:W,broadcast(G){A.forEach((X)=>X.broadcast(G))},end(){A.forEach(({exitRoom:G})=>G()),A.clear(),K.forEach(({userId:G})=>W(G)),K.clear()}}}export{s as collectPeerConnections};
1
+ function v(K){let{userId:j,worldId:N,room:T,host:h,autoRejoin:P=!0,logLine:H}=K,M=!1,O=0,W,E,L=!0,_=new Map,B=`wss://${h}/room/${N}/${T}?userId=${encodeURIComponent(j)}`,q=[],b=0;function A(Q,z,S){if(!W)return H?.("\uD83D\uDC64 ➡️ ❌","no ws available"),!1;let G={type:Q,to:z,payload:S};if(q.push(G),H?.("\uD83D\uDC64 ➡️ \uD83D\uDDA5️",G),clearTimeout(b),M||W.readyState!==WebSocket.OPEN)return H?.("\uD83D\uDC64 ➡️ ❌","Not in opened state: "+W.readyState),!1;return b=setTimeout(()=>{W.send(JSON.stringify(q)),q.length=0}),!0}function k(){if(M)return;W=new WebSocket(B),W.onopen=()=>{if(L)K.onOpen?.(),L=!1;O=0},W.onmessage=(Q)=>{try{let z=JSON.parse(Q.data);(Array.isArray(z)?z:[z]).forEach((G)=>{if(H?.("\uD83D\uDDA5️ ➡️ \uD83D\uDC64",G),G.type==="peer-joined"||G.type==="peer-left")F(G.users);else if(G.type==="ice-server")K.onIceUrl?.(G.url,G.expiration);else if(G.userId)K.onMessage(G.type,G.payload,{userId:G.userId,receive:(X,D)=>A(X,G.userId,D)})})}catch{H?.("⚠️ ERROR",{error:"invalid-json"})}},W.onclose=(Q)=>{let S=[1001,1006,1011,1012,1013].includes(Q.code);if(P&&!M&&S){let G=Math.min(Math.pow(2,O)*1000,30000),X=Math.random()*1000,D=G+X;H?.("\uD83D\uDD04 RECONNECTING",{attempt:O+1,delayMs:Math.round(D)}),O++,E=setTimeout(k,D)}else K.onClose?.({code:Q.code,reason:Q.reason,wasClean:Q.wasClean})},W.onerror=(Q)=>{console.error("WS Error",Q),K.onError?.()}}function F(Q){let z=[],S=[],G=new Set;Q.forEach(({userId:X})=>{if(X===j)return;if(!_.has(X)){let D={userId:X,receive:(x,C)=>A(x,X,C)};_.set(X,D),z.push(D)}G.add(X)});for(let X of _.keys())if(!G.has(X))_.delete(X),S.push({userId:X});if(z.length)K.onPeerJoined(z);if(S.length)K.onPeerLeft(S)}return k(),{sendToServer(Q,z){A(Q,"server",z)},exitRoom:()=>{M=!0,clearTimeout(E),W.close()}}}function g({userId:K,worldId:j,room:N,host:T,autoRejoin:h=!0,onOpen:P,onClose:H,onError:M,onPeerJoined:O,onPeerLeft:W,onIceUrl:E,onMessage:L,logLine:_,workerUrl:B}){if(!B)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"),v({userId:K,worldId:j,room:N,host:T,autoRejoin:h,onOpen:P,onClose:H,onError:M,onPeerJoined:O,onPeerLeft:W,onIceUrl:E,onMessage:L});let q=new Worker(B,{type:"module"}),b=!1;function A({userId:F}){return{userId:F,receive:(Q,z)=>{if(b)return!1;return q.postMessage({cmd:"send",toUserId:F,host:T,room:N,type:Q,payload:z}),!0}}}let k=(F)=>{let Q=F.data;if(Q.kind==="open")P?.();else if(Q.kind==="close")q.terminate(),H?.(Q.ev);else if(Q.kind==="error")M?.();else if(Q.kind==="peer-joined")O(Q.users.map((z)=>A({userId:z.userId})));else if(Q.kind==="peer-left")W(Q.users);else if(Q.kind==="ice-server")E?.(Q.url,Q.expiration);else if(Q.kind==="message")L(Q.type,Q.payload,A({userId:Q.fromUserId}));else if(Q.kind==="log")_?.(Q.direction,Q.obj)};return q.addEventListener("message",k),q.postMessage({cmd:"enter",userId:K,worldId:j,room:N,host:T,autoRejoin:h}),{exitRoom:()=>{b=!0,q.removeEventListener("message",k),q.postMessage({cmd:"exit"})},sendToServer:(F,Q)=>{q.postMessage({cmd:"send",toUserId:"server",host:T,room:N,type:F,payload:Q})}}}var p=g;function t({userId:K,worldId:j,receivePeerConnection:N,peerlessUserExpiration:T=5000,fallbackRtcConfig:h={iceServers:[{urls:"stun:stun.l.google.com:19302"}]},enterRoomFunction:P=p,logLine:H,onLeaveUser:M,workerUrl:O,onRoomReady:W,onRoomClose:E,onBroadcastMessage:L}){let _=K??`user-${crypto.randomUUID()}`,B=new Map,q=void 0,b={...h,timestamp:Date.now()},A=new Map;async function k(G){if(G)try{let X=await fetch(G);if(!X.ok)throw Error(`ICE endpoint failed: ${X.status}`);b=await X.json()}catch(X){console.warn("Using fallback rtcConfig:",X)}return b}function F(G){M?.(G);let X=B.get(G);if(!X)return;B.delete(G);try{X.pc?.close()}catch{}}async function Q(G){if(!G.pc?.remoteDescription)return;let X=G.pendingRemoteIce;G.pendingRemoteIce=[];for(let D of X)try{await G.pc.addIceCandidate(D)}catch(x){H?.("⚠️ ERROR",{error:"add-ice-failed",userId:G.userId,detail:String(x)})}}function z({room:G,host:X}){let D=`${X}/room/${G}`,x=A.get(D);if(x)x.exitRoom(),A.delete(D)}function S({room:G,host:X}){return new Promise(async(D,x)=>{async function C(V){let Z=B.get(V.userId);if(!Z)return;Z.close();let Y=await w(Z);N({pc:Y,userId:V.userId,initiator:!0,restart:()=>C(V)}),await new Promise(($)=>setTimeout($,3000)),await U(V)}async function w(V){let Z=Date.now();if(Z-(b?.timestamp??0)>1e4){let Y=!q||q.expiration-Z<2000?await c():q;b=await k(Y.url)}return V.pc=new RTCPeerConnection(b),V.pc.onicecandidate=(Y)=>{if(!Y.candidate)return;V.peer.receive("ice",Y.candidate.toJSON())},V.pc.onconnectionstatechange=()=>{H?.("\uD83D\uDCAC",{event:"pc-state",userId:V.userId,state:V.pc?.connectionState})},V.pc}async function f(V,Z){let Y=B.get(V.userId);if(!Y||Z){let $={userId:V.userId,pendingRemoteIce:[],peer:V,initiateForThisUser:Y?.initiateForThisUser??!1,close(){this.pc?.close(),this.pc=void 0}};await w($),Y=$,B.set(Y.userId,Y)}else if(Y)clearTimeout(Y.expirationTimeout),Y.expirationTimeout=0;if(!Y.pc||Y.pc?.signalingState==="closed")await w(Y);return Y.peer=V,Y}async function U(V){let Y=(await f(V)).pc,$=await Y?.createOffer();await Y?.setLocalDescription($),V.receive("offer",Y?.localDescription?.toJSON())}let y;async function c(){let V=await new Promise((Z)=>{y=Z,I("request-ice")});return y=void 0,V}let{exitRoom:m,sendToServer:I}=P({userId:_,worldId:j,room:G,host:X,logLine:H,workerUrl:O,autoRejoin:!0,onOpen(){W?.({room:G,host:X}),D()},onError(){console.error("onError"),x()},onClose(V){E?.({room:G,host:X,ev:V})},onPeerJoined(V){V.forEach(async(Z)=>{let Y=B.has(Z.userId),$=await f(Z,!0);if(!Y)$.initiateForThisUser=!0;let J=$.pc;if(!J){H?.("\uD83D\uDC64ℹ️","no pc: "+Z.userId);return}if(N({pc:J,userId:Z.userId,initiator:$.initiateForThisUser,restart:$.initiateForThisUser?()=>C(Z):()=>$.close()}),$.initiateForThisUser)await U(Z)})},onPeerLeft(V){V.forEach(({userId:Z})=>{let Y=B.get(Z);if(!Y)return;Y.expirationTimeout=setTimeout(()=>F(Z),T??0)})},onIceUrl(V,Z){q={url:V,expiration:Z},y?.(q)},async onMessage(V,Z,Y){let $=await f(Y),J=$.pc;if(!J)return;if(V==="offer"){$.initiateForThisUser=!1,N({pc:J,userId:Y.userId,initiator:!1,restart(){$.close()}}),await J.setRemoteDescription(Z);let R=await J.createAnswer();await J.setLocalDescription(R),Y.receive("answer",J.localDescription?.toJSON()),await Q($);return}if(V==="answer"){await J.setRemoteDescription(Z),await Q($);return}if(V==="ice"){let R=Z;if(!J.remoteDescription){$.pendingRemoteIce.push(R);return}try{await J.addIceCandidate(R)}catch(u){H?.("⚠️ ERROR",{error:"add-ice-failed",userId:$.userId,detail:String(u)})}return}if(V==="broadcast")L?.(Z,Y.userId)}});A.set(`${X}/room/${G}`,{exitRoom:m,room:G,host:X,broadcast:(V)=>{I("broadcast",V)}})})}return{userId:_,enterRoom:S,exitRoom:z,leaveUser:F,broadcast(G){A.forEach((X)=>X.broadcast(G))},end(){A.forEach(({exitRoom:G})=>G()),A.clear(),B.forEach(({userId:G})=>F(G)),B.clear()}}}export{t as collectPeerConnections};
2
2
 
3
- //# debugId=E40DA212505EA63464756E2164756E21
3
+ //# debugId=B8C169F711088F9E64756E2164756E21
4
4
  //# sourceMappingURL=webrtc-peer-collector.js.map
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "version": 3,
3
- "sources": ["../src/browser/impl/signal-room.ts", "../src/browser/signal-room.ts", "../src/browser/webrtc-peer-collector.ts"],
3
+ "sources": ["../src/browser/signal/impl/signal-room.ts", "../src/browser/signal/signal-room.ts", "../src/browser/webrtc-peer-collector.ts"],
4
4
  "sourcesContent": [
5
- "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 if (exited || ws.readyState !== WebSocket.OPEN) {\n logLine?.(\"👤 ➡️ ❌\", \"Not in opened state: \" + ws.readyState);\n return false;\n }\n const obj: OutMessage = { type, to, payload };\n accumulatedMessages.push(obj);\n // ws.send(JSON.stringify(obj));\n logLine?.(\"👤 ➡️ 🖥️\", obj);\n clearTimeout(timeout);\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);\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 updatePeers([]);\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 }[]) {\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 (!peers.has(pUserId)) {\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 (!updatedPeerSet.has(pUserId)) {\n peers.delete(pUserId);\n left.push({ userId: pUserId });\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",
5
+ "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);\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 }[]) {\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 (!peers.has(pUserId)) {\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 (!updatedPeerSet.has(pUserId)) {\n peers.delete(pUserId);\n left.push({ userId: pUserId });\n }\n }\n // Notify peer joined first then peer left. (avoid disconnect in case the peer leaving / joining is on the same user).\n if (joined.length) params.onPeerJoined(joined);\n if (left.length) params.onPeerLeft(left);\n }\n\n // Start initial connection\n connect();\n\n return {\n sendToServer(type, payload) {\n send(type, \"server\", payload);\n },\n exitRoom: () => {\n exited = true;\n clearTimeout(timeoutId);\n ws.close();\n },\n };\n}\n",
6
6
  "import type { IPeer } from \"./impl/signal-room.js\";\nimport { enterRoom as baseEnterRoom } from \"./impl/signal-room.js\";\nimport { RoomEvent, WorkerCommand } from \"./signal-room.worker.js\";\n\nexport function enterRoom<T extends string, P = any>({\n userId,\n worldId,\n room,\n host,\n autoRejoin = true,\n onOpen,\n onClose,\n onError,\n onPeerJoined,\n onPeerLeft,\n onIceUrl,\n onMessage,\n logLine,\n workerUrl,\n}: {\n userId: string;\n worldId: string;\n room: string;\n host: string;\n autoRejoin?: boolean;\n onOpen?: () => void;\n onClose?: (ev: Pick<CloseEvent, \"code\" | \"reason\" | \"wasClean\">) => void;\n onError?: () => void;\n onPeerJoined: (users: IPeer<T, P>[]) => void;\n onPeerLeft: (users: { userId: string }[]) => void;\n onIceUrl?(url: string, expiration: number): void;\n onMessage: (type: T, payload: P, from: IPeer<T, P>) => void;\n logLine?: (direction: string, obj?: any) => void;\n\n // Pass the URL to your worker file (bundler will handle it)\n workerUrl?: URL;\n}): {\n exitRoom: () => void;\n sendToServer: <P extends any>(type: T, payload?: P) => void;\n} {\n if (!workerUrl) {\n const CDN_WORKER_URL = `https://cdn.jsdelivr.net/npm/@dobuki/hello-worker/dist/signal-room.worker.min.js`;\n\n console.warn(\n \"Warning: enterRoom called without workerUrl; this may cause issues in some environments. You should pass workerUrl explicitly. Use:\",\n CDN_WORKER_URL,\n );\n return baseEnterRoom<T, P>({\n userId,\n worldId,\n room,\n host,\n autoRejoin,\n onOpen,\n onClose,\n onError,\n onPeerJoined,\n onPeerLeft,\n onIceUrl,\n onMessage,\n });\n }\n const worker = new Worker(workerUrl, { type: \"module\" });\n let exited = false;\n\n function makeUser({ userId }: { userId: string }): IPeer<T, P> {\n return {\n userId,\n receive: (type: T, payload: P) => {\n if (exited) return false;\n worker.postMessage({\n cmd: \"send\",\n toUserId: userId,\n host,\n room,\n type,\n payload,\n } as WorkerCommand);\n return true;\n },\n };\n }\n\n const onWorkerMessage = (e: MessageEvent<RoomEvent<T, P>>) => {\n const ev = e.data;\n\n if (ev.kind === \"open\") onOpen?.();\n else if (ev.kind === \"close\") {\n worker.terminate();\n onClose?.(ev.ev);\n } else if (ev.kind === \"error\") onError?.();\n else if (ev.kind === \"peer-joined\")\n onPeerJoined(ev.users.map((ev) => makeUser({ userId: ev.userId })));\n else if (ev.kind === \"peer-left\") onPeerLeft(ev.users);\n else if (ev.kind === \"ice-server\") onIceUrl?.(ev.url, ev.expiration);\n else if (ev.kind === \"message\")\n onMessage(ev.type, ev.payload, makeUser({ userId: ev.fromUserId }));\n else if (ev.kind === \"log\") logLine?.(ev.direction, ev.obj);\n };\n\n worker.addEventListener(\"message\", onWorkerMessage);\n\n worker.postMessage({\n cmd: \"enter\",\n userId,\n worldId,\n room,\n host,\n autoRejoin,\n } as WorkerCommand);\n\n return {\n exitRoom: () => {\n exited = true;\n worker.removeEventListener(\"message\", onWorkerMessage);\n worker.postMessage({ cmd: \"exit\" } as WorkerCommand);\n },\n sendToServer: <P extends any>(type: T, payload?: P) => {\n worker.postMessage({\n cmd: \"send\",\n toUserId: \"server\",\n host,\n room,\n type,\n payload,\n } as WorkerCommand);\n },\n };\n}\n\nexport type EnterRoom<T extends string, P> = typeof enterRoom<T, P>;\n",
7
- "import { IPeer } from \"./impl/signal-room\";\nimport { EnterRoom, enterRoom } from \"./signal-room\";\n\nexport type SigType = \"offer\" | \"answer\" | \"ice\" | \"request-ice\" | \"broadcast\";\nexport type SigPayload = RTCSessionDescriptionInit | RTCIceCandidateInit;\n\ntype UserState = {\n userId: string;\n pc?: RTCPeerConnection;\n\n // ICE that arrived before we had remoteDescription\n pendingRemoteIce: RTCIceCandidateInit[];\n\n // the signaling \"user\" handle so we can send messages\n peer: IPeer<SigType, SigPayload>;\n\n expirationTimeout?: number;\n};\n\nconst DEFAULT_ENTER_ROOM = enterRoom;\n\nexport function collectPeerConnections({\n userId: passedUserId,\n worldId,\n receivePeerConnection,\n peerlessUserExpiration = 5000,\n fallbackRtcConfig = {\n iceServers: [{ urls: \"stun:stun.l.google.com:19302\" }],\n },\n enterRoomFunction: enterRoom = DEFAULT_ENTER_ROOM,\n logLine,\n onLeaveUser,\n workerUrl,\n onRoomReady,\n onRoomClose,\n onBroadcastMessage,\n}: {\n userId?: string;\n worldId: string;\n fallbackRtcConfig?: RTCConfiguration;\n enterRoomFunction?: EnterRoom<SigType, SigPayload>;\n onLeaveUser?: (userId: string) => void;\n logLine?: (direction: string, obj?: any) => void;\n workerUrl?: URL;\n peerlessUserExpiration?: number;\n receivePeerConnection(connection: {\n pc: RTCPeerConnection;\n userId: string;\n initiator: boolean;\n restart?: () => void;\n }): void;\n onRoomReady?(info: { host: string; room: string }): void;\n onRoomClose?(info: {\n host: string;\n room: string;\n ev: Pick<CloseEvent, \"reason\" | \"code\" | \"wasClean\">;\n }): void;\n onBroadcastMessage?<P extends any>(payload: P, from: string): void;\n}) {\n const userId = passedUserId ?? `user-${crypto.randomUUID()}`;\n const users: Map<string, UserState> = new Map();\n let iceUrl: { url: string; expiration: number } | undefined = undefined;\n let rtcConfig: RTCConfiguration & { timestamp: number } = {\n ...fallbackRtcConfig,\n timestamp: Date.now(),\n };\n\n const roomsEntered = new Map<\n string,\n {\n room: string;\n host: string;\n exitRoom: () => void;\n broadcast: <P extends any>(payload: P) => void;\n }\n >();\n\n async function getRtcConfig(\n iceUrl: string,\n ): Promise<RTCConfiguration & { timestamp: number }> {\n if (iceUrl) {\n try {\n const r = await fetch(iceUrl);\n if (!r.ok) throw new Error(`ICE endpoint failed: ${r.status}`);\n rtcConfig = (await r.json()) as RTCConfiguration & {\n timestamp: number;\n };\n } catch (e) {\n console.warn(\"Using fallback rtcConfig:\", e);\n }\n }\n return rtcConfig;\n }\n\n function leaveUser(userId: string) {\n onLeaveUser?.(userId);\n const p = users.get(userId);\n if (!p) return;\n try {\n p.pc?.close();\n } catch {}\n users.delete(userId);\n }\n\n async function flushRemoteIce(state: UserState) {\n if (!state.pc?.remoteDescription) return;\n\n const queued = state.pendingRemoteIce;\n state.pendingRemoteIce = [];\n\n for (const ice of queued) {\n try {\n await state.pc.addIceCandidate(ice);\n } catch (e) {\n logLine?.(\"⚠️ ERROR\", {\n error: \"add-ice-failed\",\n userId: state.userId,\n detail: String(e),\n });\n }\n }\n }\n\n function exit({ room, host }: { room: string; host: string }) {\n const key = `${host}/room/${room}`;\n const session = roomsEntered.get(key);\n if (session) {\n session.exitRoom();\n roomsEntered.delete(key);\n }\n }\n\n function enter({ room, host }: { room: string; host: string }) {\n return new Promise<void>(async (resolve, reject) => {\n async function setupPC(state: UserState) {\n const now = Date.now();\n if (now - (rtcConfig?.timestamp ?? 0) > 10000) {\n const ice =\n !iceUrl || iceUrl.expiration - now < 2000\n ? await requestIce()\n : iceUrl;\n rtcConfig = await getRtcConfig(ice.url);\n }\n state.pc = new RTCPeerConnection(rtcConfig);\n // Send local ICE candidates to this peer\n state.pc.onicecandidate = (ev) => {\n if (!ev.candidate) return;\n state.peer.receive(\"ice\", ev.candidate.toJSON());\n };\n\n state.pc.onconnectionstatechange = () => {\n logLine?.(\"💬\", {\n event: \"pc-state\",\n userId: state.userId,\n state: state.pc?.connectionState,\n });\n };\n return state.pc;\n }\n\n async function getPeer(\n peer: IPeer<SigType, SigPayload>,\n ): Promise<[UserState, boolean]> {\n let state = users.get(peer.userId);\n let isNewPeer = false;\n if (!state) {\n const newState: UserState = {\n userId: peer.userId,\n pendingRemoteIce: [],\n peer,\n };\n\n await setupPC(newState);\n state = newState;\n\n // New user\n users.set(state.userId, state);\n isNewPeer = true;\n } else if (state) {\n clearTimeout(state.expirationTimeout);\n state.expirationTimeout = 0;\n }\n if (!state.pc || state.pc?.signalingState === \"closed\") {\n await setupPC(state);\n }\n state.peer = peer;\n return [state, isNewPeer];\n }\n\n async function makeOffer(user: IPeer) {\n // Offer flow: createOffer -> setLocalDescription -> send localDescription\n const [state] = await getPeer(user);\n const pc = state.pc;\n const offer = await pc?.createOffer();\n await pc?.setLocalDescription(offer);\n user.receive(\"offer\", pc?.localDescription?.toJSON()!);\n }\n\n let icePromiseResolve:\n | undefined\n | ((url: { url: string; expiration: number }) => void);\n async function requestIce() {\n const iceUrl = await new Promise<{ url: string; expiration: number }>(\n (resolve) => {\n icePromiseResolve = resolve;\n sendToServer(\"request-ice\");\n },\n );\n icePromiseResolve = undefined;\n return iceUrl;\n }\n\n const { exitRoom, sendToServer } = enterRoom({\n userId,\n worldId,\n room,\n host,\n logLine,\n workerUrl,\n autoRejoin: true,\n\n onOpen() {\n onRoomReady?.({ room, host });\n resolve();\n },\n onError() {\n console.error(\"onError\");\n reject();\n },\n onClose(ev) {\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, isNewPeer] = await getPeer(user);\n if (!isNewPeer) {\n logLine?.(\"👤ℹ️\", \"not a new peer: \" + user.userId);\n return;\n }\n const pc = state.pc;\n if (!pc) {\n logLine?.(\"👤ℹ️\", \"no pc: \" + user.userId);\n return;\n }\n\n async function restart() {\n const state = users.get(user.userId);\n if (state) {\n state.pc = undefined;\n const pc = await setupPC(state);\n receivePeerConnection({\n pc,\n userId: user.userId,\n initiator: true,\n restart,\n });\n await new Promise((resolve) => setTimeout(resolve, 3000));\n await makeOffer(user);\n }\n }\n\n receivePeerConnection({\n pc,\n userId: user.userId,\n initiator: true,\n restart,\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, expiration) {\n iceUrl = { url, expiration };\n icePromiseResolve?.(iceUrl);\n },\n\n async onMessage(type: SigType, payload: any, from: IPeer) {\n const [state] = await getPeer(from);\n const pc = state.pc;\n if (!pc) return;\n\n if (type === \"offer\") {\n receivePeerConnection({\n pc,\n userId: from.userId,\n initiator: false,\n restart() {\n // reset PC\n state.pc = undefined;\n },\n });\n // Responder: set remote offer\n await pc.setRemoteDescription(payload as RTCSessionDescriptionInit);\n\n // Create and send answer\n const answer = await pc.createAnswer();\n await pc.setLocalDescription(answer);\n\n from.receive(\"answer\", pc.localDescription?.toJSON()!);\n\n // Now safe to apply any queued ICE from this peer\n await flushRemoteIce(state);\n return;\n }\n\n if (type === \"answer\") {\n // Initiator: set remote answer\n await pc.setRemoteDescription(payload as RTCSessionDescriptionInit);\n await flushRemoteIce(state);\n return;\n }\n\n if (type === \"ice\") {\n const ice = payload as RTCIceCandidateInit;\n\n // If we don't have remoteDescription yet, queue it\n if (!pc.remoteDescription) {\n state.pendingRemoteIce.push(ice);\n return;\n }\n\n try {\n await pc.addIceCandidate(ice);\n } catch (e) {\n logLine?.(\"⚠️ ERROR\", {\n error: \"add-ice-failed\",\n userId: state.userId,\n detail: String(e),\n });\n }\n return;\n }\n\n if (type === \"broadcast\") {\n onBroadcastMessage?.(payload, from.userId);\n }\n },\n });\n roomsEntered.set(`${host}/room/${room}`, {\n exitRoom,\n room,\n host,\n broadcast: (payload) => {\n sendToServer(\"broadcast\", payload);\n },\n });\n });\n }\n\n return {\n userId,\n enterRoom: enter,\n exitRoom: exit,\n leaveUser,\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(({ userId }) => leaveUser(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"
7
+ "import { IPeer } from \"./signal/impl/signal-room\";\nimport { EnterRoom, enterRoom } from \"./signal/signal-room\";\n\nexport type SigType = \"offer\" | \"answer\" | \"ice\" | \"request-ice\" | \"broadcast\";\nexport type SigPayload = RTCSessionDescriptionInit | RTCIceCandidateInit;\n\ntype UserState = {\n userId: string;\n pc?: RTCPeerConnection;\n\n // ICE that arrived before we had remoteDescription\n pendingRemoteIce: RTCIceCandidateInit[];\n\n // the signaling \"user\" handle so we can send messages\n peer: IPeer<SigType, SigPayload>;\n\n expirationTimeout?: number;\n initiateForThisUser: boolean;\n close: () => void;\n};\n\nconst DEFAULT_ENTER_ROOM = enterRoom;\n\nexport function collectPeerConnections({\n userId: passedUserId,\n worldId,\n receivePeerConnection,\n peerlessUserExpiration = 5000,\n fallbackRtcConfig = {\n iceServers: [{ urls: \"stun:stun.l.google.com:19302\" }],\n },\n enterRoomFunction: enterRoom = DEFAULT_ENTER_ROOM,\n logLine,\n onLeaveUser,\n workerUrl,\n onRoomReady,\n onRoomClose,\n onBroadcastMessage,\n}: {\n userId?: string;\n worldId: string;\n fallbackRtcConfig?: RTCConfiguration;\n enterRoomFunction?: EnterRoom<SigType, SigPayload>;\n onLeaveUser?: (userId: string) => void;\n logLine?: (direction: string, obj?: any) => void;\n workerUrl?: URL;\n peerlessUserExpiration?: number;\n receivePeerConnection(connection: {\n pc: RTCPeerConnection;\n userId: string;\n initiator: boolean;\n restart?: () => void;\n }): void;\n onRoomReady?(info: { host: string; room: string }): void;\n onRoomClose?(info: {\n host: string;\n room: string;\n ev: Pick<CloseEvent, \"reason\" | \"code\" | \"wasClean\">;\n }): void;\n onBroadcastMessage?<P extends any>(payload: P, from: string): void;\n}) {\n const userId = passedUserId ?? `user-${crypto.randomUUID()}`;\n const users: Map<string, UserState> = new Map();\n let iceUrl: { url: string; expiration: number } | undefined = undefined;\n let rtcConfig: RTCConfiguration & { timestamp: number } = {\n ...fallbackRtcConfig,\n timestamp: Date.now(),\n };\n\n const roomsEntered = new Map<\n string,\n {\n room: string;\n host: string;\n exitRoom: () => void;\n broadcast: <P extends any>(payload: P) => void;\n }\n >();\n\n async function getRtcConfig(\n iceUrl: string,\n ): Promise<RTCConfiguration & { timestamp: number }> {\n if (iceUrl) {\n try {\n const r = await fetch(iceUrl);\n if (!r.ok) throw new Error(`ICE endpoint failed: ${r.status}`);\n rtcConfig = (await r.json()) as RTCConfiguration & {\n timestamp: number;\n };\n } catch (e) {\n console.warn(\"Using fallback rtcConfig:\", e);\n }\n }\n return rtcConfig;\n }\n\n function leaveUser(userId: string) {\n onLeaveUser?.(userId);\n const p = users.get(userId);\n if (!p) return;\n users.delete(userId);\n try {\n p.pc?.close();\n } catch {}\n }\n\n async function flushRemoteIce(state: UserState) {\n if (!state.pc?.remoteDescription) return;\n\n const queued = state.pendingRemoteIce;\n state.pendingRemoteIce = [];\n\n for (const ice of queued) {\n try {\n await state.pc.addIceCandidate(ice);\n } catch (e) {\n logLine?.(\"⚠️ ERROR\", {\n error: \"add-ice-failed\",\n userId: state.userId,\n detail: String(e),\n });\n }\n }\n }\n\n function exit({ room, host }: { room: string; host: string }) {\n const key = `${host}/room/${room}`;\n const session = roomsEntered.get(key);\n if (session) {\n session.exitRoom();\n roomsEntered.delete(key);\n }\n }\n\n function enter({ room, host }: { room: string; host: string }) {\n return new Promise<void>(async (resolve, reject) => {\n async function restartInitiator(user: IPeer) {\n const state = users.get(user.userId);\n if (!state) return; // user left\n state.close();\n const pc = await setupPC(state);\n receivePeerConnection({\n pc,\n userId: user.userId,\n initiator: true,\n restart: () => restartInitiator(user),\n });\n await new Promise((resolve) => setTimeout(resolve, 3000));\n await makeOffer(user);\n }\n\n async function setupPC(state: UserState) {\n const now = Date.now();\n if (now - (rtcConfig?.timestamp ?? 0) > 10000) {\n const ice =\n !iceUrl || iceUrl.expiration - now < 2000\n ? await requestIce()\n : iceUrl;\n rtcConfig = await getRtcConfig(ice.url);\n }\n state.pc = new RTCPeerConnection(rtcConfig);\n // Send local ICE candidates to this peer\n state.pc.onicecandidate = (ev) => {\n if (!ev.candidate) return;\n state.peer.receive(\"ice\", ev.candidate.toJSON());\n };\n\n state.pc.onconnectionstatechange = () => {\n logLine?.(\"💬\", {\n event: \"pc-state\",\n userId: state.userId,\n state: state.pc?.connectionState,\n });\n };\n\n return state.pc;\n }\n\n async function getPeer(\n peer: IPeer<SigType, SigPayload>,\n forceReset?: boolean,\n ): Promise<UserState> {\n let state = users.get(peer.userId);\n if (!state || forceReset) {\n const newState: UserState = {\n userId: peer.userId,\n pendingRemoteIce: [],\n peer,\n initiateForThisUser: state?.initiateForThisUser ?? false,\n close() {\n this.pc?.close();\n this.pc = undefined;\n },\n };\n\n await setupPC(newState);\n state = newState;\n\n // New user\n users.set(state.userId, state);\n } else if (state) {\n clearTimeout(state.expirationTimeout);\n state.expirationTimeout = 0;\n }\n if (!state.pc || state.pc?.signalingState === \"closed\") {\n await setupPC(state);\n }\n state.peer = peer;\n return state;\n }\n\n async function makeOffer(user: IPeer) {\n // Offer flow: createOffer -> setLocalDescription -> send localDescription\n const state = await getPeer(user);\n const pc = state.pc;\n const offer = await pc?.createOffer();\n await pc?.setLocalDescription(offer);\n user.receive(\"offer\", pc?.localDescription?.toJSON()!);\n }\n\n let icePromiseResolve:\n | undefined\n | ((url: { url: string; expiration: number }) => void);\n async function requestIce() {\n const iceUrl = await new Promise<{ url: string; expiration: number }>(\n (resolve) => {\n icePromiseResolve = resolve;\n sendToServer(\"request-ice\");\n },\n );\n icePromiseResolve = undefined;\n return iceUrl;\n }\n\n const { exitRoom, sendToServer } = enterRoom({\n userId,\n worldId,\n room,\n host,\n logLine,\n workerUrl,\n autoRejoin: true,\n\n onOpen() {\n onRoomReady?.({ room, host });\n resolve();\n },\n onError() {\n console.error(\"onError\");\n reject();\n },\n onClose(ev: Pick<CloseEvent, \"reason\" | \"code\" | \"wasClean\">) {\n onRoomClose?.({ room, host, ev });\n },\n\n // Existing peers initiate to the newcomer\n onPeerJoined(joiningUsers: IPeer<SigType, SigPayload>[]) {\n joiningUsers.forEach(async (user) => {\n const hadState = users.has(user.userId);\n const state = await getPeer(user, true);\n if (!hadState) {\n state.initiateForThisUser = true;\n }\n const pc = state.pc;\n if (!pc) {\n logLine?.(\"👤ℹ️\", \"no pc: \" + user.userId);\n return;\n }\n\n receivePeerConnection({\n pc,\n userId: user.userId,\n initiator: state.initiateForThisUser,\n restart: state.initiateForThisUser\n ? () => restartInitiator(user)\n : () => state.close(),\n });\n if (state.initiateForThisUser) {\n await makeOffer(user);\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 iceUrl = { url, expiration };\n icePromiseResolve?.(iceUrl);\n },\n\n async onMessage(type: SigType, payload: any, from: IPeer) {\n const state = await getPeer(from);\n const pc = state.pc;\n if (!pc) return;\n\n if (type === \"offer\") {\n state.initiateForThisUser = false;\n receivePeerConnection({\n pc,\n userId: from.userId,\n initiator: false,\n restart() {\n // reset PC\n state.close();\n },\n });\n // Responder: set remote offer\n await pc.setRemoteDescription(payload as RTCSessionDescriptionInit);\n\n // Create and send answer\n const answer = await pc.createAnswer();\n await pc.setLocalDescription(answer);\n\n from.receive(\"answer\", pc.localDescription?.toJSON()!);\n\n // Now safe to apply any queued ICE from this peer\n await flushRemoteIce(state);\n return;\n }\n\n if (type === \"answer\") {\n // Initiator: set remote answer\n await pc.setRemoteDescription(payload as RTCSessionDescriptionInit);\n await flushRemoteIce(state);\n return;\n }\n\n if (type === \"ice\") {\n const ice = payload as RTCIceCandidateInit;\n\n // If we don't have remoteDescription yet, queue it\n if (!pc.remoteDescription) {\n state.pendingRemoteIce.push(ice);\n return;\n }\n\n try {\n await pc.addIceCandidate(ice);\n } catch (e) {\n logLine?.(\"⚠️ ERROR\", {\n error: \"add-ice-failed\",\n userId: state.userId,\n detail: String(e),\n });\n }\n return;\n }\n\n if (type === \"broadcast\") {\n onBroadcastMessage?.(payload, from.userId);\n }\n },\n });\n roomsEntered.set(`${host}/room/${room}`, {\n exitRoom,\n room,\n host,\n broadcast: (payload) => {\n sendToServer(\"broadcast\", payload);\n },\n });\n });\n }\n\n return {\n userId,\n enterRoom: enter,\n exitRoom: exit,\n leaveUser,\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(({ userId }) => leaveUser(userId));\n users.clear();\n },\n };\n}\n\n/*\nTurn Token ID\n<CF_TURN_TOKEN_ID>\n\nAPI Token\n<CF_RTC_API_TOKEN>\n\nCURL\ncurl \\\n\t-H \"Authorization: Bearer <CF_RTC_API_TOKEN>\" \\\n\t-H \"Content-Type: application/json\" -d '{\"ttl\": 86400}' \\\n\thttps://rtc.live.cloudflare.com/v1/turn/keys/<CF_TURN_TOKEN_ID>/credentials/generate-ice-servers\n\nJSON\n{\n\t\"iceServers\": [\n {\n \"urls\": [\n \"stun:stun.cloudflare.com:3478\",\n \"turn:turn.cloudflare.com:3478?transport=udp\",\n \"turn:turn.cloudflare.com:3478?transport=tcp\",\n \"turns:turn.cloudflare.com:5349?transport=tcp\"\n ],\n \"username\": \"xxxx\",\n \"credential\": \"yyyy\",\n }\n ]\n}\n\n*/\n"
8
8
  ],
9
- "mappings": "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",
10
- "debugId": "E40DA212505EA63464756E2164756E21",
9
+ "mappings": "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",
10
+ "debugId": "B8C169F711088F9E64756E2164756E21",
11
11
  "names": []
12
12
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dobuki/hello-worker",
3
- "version": "1.0.71",
3
+ "version": "1.0.73",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -16,7 +16,7 @@
16
16
  "scripts": {
17
17
  "dev": "wrangler dev src/index.ts --config wrangler.dev.toml",
18
18
  "deploy": "wrangler deploy",
19
- "build:sample": "bun build src/browser/sample/index.ts src/browser/signal-room.worker.ts --target browser --format esm --outdir public --minify --sourcemap",
19
+ "build:sample": "bun build src/browser/sample/index.ts src/browser/signal/signal-room.worker.ts --target browser --format esm --outdir public --minify --sourcemap",
20
20
  "build:types": "tsc -p tsconfig.json",
21
21
  "build:main": "bun build src/browser/*.* --target browser --format esm --outdir dist --minify --sourcemap",
22
22
  "build": "bun i && bun run build:main && bun run build:sample && bun run build:types",