@dobuki/hello-worker 1.0.18 → 1.0.21

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.
@@ -22,8 +22,8 @@ export declare function enterWorld({ uid, appId, logLine, enterRoomFunction, pee
22
22
  getUsers: () => string[];
23
23
  addMessageListener: (listener: (data: any, from: string) => void) => () => void;
24
24
  removeMessageListener: (listener: (data: any, from: string) => void) => void;
25
- addUserListener: (listener: (user: string, action: "join" | "leave", users: string[]) => void) => () => void;
26
- removeUserListener: (listener: (user: string, action: "join" | "leave", users: string[]) => void) => void;
25
+ addUserListener: (listener: (userId: string, action: "join" | "leave", users: string[]) => void) => () => void;
26
+ removeUserListener: (listener: (userId: string, action: "join" | "leave", users: string[]) => void) => void;
27
27
  end(): void;
28
28
  };
29
29
  //# sourceMappingURL=enter-world.d.ts.map
@@ -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,EAAE,OAAO,EAAE,UAAU,EAA0B,MAAM,yBAAyB,CAAC;AAEtF,wBAAgB,UAAU,CAAC,EACzB,GAAG,EAAE,KAAK,EAAE,OAAuB,EAAE,iBAA6B,EAAE,sBAAsB,EAAE,SAAS,GACtG,EAAE;IACD,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,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;CACjB;;iBAkDqB,GAAG,WAAW,MAAM;;;;;;;;;;;mCAWF,CAAC,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,KAAK,IAAI;sCAJ9B,CAAC,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,KAAK,IAAI;;;;EA8B3E"}
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,EAAE,OAAO,EAAE,UAAU,EAA0B,MAAM,yBAAyB,CAAC;AAEtF,wBAAgB,UAAU,CAAC,EACzB,GAAG,EAAE,KAAK,EAAE,OAAuB,EAAE,iBAA6B,EAAE,sBAAsB,EAAE,SAAS,GACtG,EAAE;IACD,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,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;CACjB;;iBA+DqB,GAAG,WAAW,MAAM;;;;;;;;;;;mCAWF,CAAC,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,KAAK,IAAI;sCAJ9B,CAAC,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,KAAK,IAAI;gCAevC,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAC,MAAM,GAAC,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,IAAI;mCAJ7D,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAC,MAAM,GAAC,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,IAAI;;EAgCvG"}
@@ -0,0 +1,4 @@
1
+ function M({userId:q,appId:C,room:Z,host:i,onOpen:A,onClose:f,onError:Y,logLine:X,onPeerJoined:j,onPeerLeft:Q,onMessage:S}){let N=`wss://${i}/room/${C}/${Z}?userId=${encodeURIComponent(q)}`,z=new WebSocket(N),V=q,W=new Map,_=!1;function T(H,F,b){if(_)return!1;let G={type:H,to:F,payload:b};return z.send(JSON.stringify(G)),X?.("\uD83D\uDC64 ➡️ \uD83D\uDDA5️",G),!0}function D(H){let F=[],b=[],G=new Set;if(H.forEach(({userId:c,peerId:R})=>{if(c===V)return;if(!W.has(R)){let $={userId:c,peerId:R,receive:(K,h)=>T(K,R,h)};W.set(R,$),F.push($)}G.add(R)}),W.values().forEach(({peerId:c,userId:R})=>{if(!G.has(c))W.delete(c),b.push({peerId:c,userId:R})}),F.length)j(F);if(b.length)Q(b)}function B(H){let F;try{F=JSON.parse(H.data)}catch{X?.("⚠️ ERROR",{error:"invalid-json"});return}if(X?.("\uD83D\uDDA5️ ➡️ \uD83D\uDC64",F),F.type==="peer-joined"||F.type==="peer-left"){D(F.users);return}if(F.peerId&&F.userId){let{userId:b,peerId:G}=F;S(F.type,F.payload,{userId:b,peerId:G,receive:(c,R)=>T(c,G,R)})}}if(z.addEventListener("message",B),A)z.addEventListener("open",A);if(f)z.addEventListener("close",f);if(Y)z.addEventListener("error",Y);return{exitRoom:()=>{if(_=!0,z.close(),z.removeEventListener("message",B),A)z.removeEventListener("open",A);if(f)z.removeEventListener("close",f);if(Y)z.removeEventListener("error",Y)}}}function J({userId:q,appId:C,room:Z,host:i,onOpen:A,onClose:f,onError:Y,onPeerJoined:X,onPeerLeft:j,onMessage:Q,logLine:S,workerUrl:N}){if(!N)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"),M({userId:q,appId:C,room:Z,host:i,onOpen:A,onClose:f,onError:Y,onPeerJoined:X,onPeerLeft:j,onMessage:Q});let z=new Worker(N,{type:"module"}),V=!1;function W({userId:T,peerId:D}){return{userId:T,peerId:D,receive:(B,H)=>{if(V)return!1;return z.postMessage({cmd:"send",toPeerId:D,type:B,payload:H}),!0}}}let _=(T)=>{let D=T.data;if(D.kind==="open")A?.();else if(D.kind==="close")z.terminate();else if(D.kind==="error")Y?.();else if(D.kind==="peer-joined")X(D.users.map((B)=>W({userId:B.userId,peerId:B.peerId})));else if(D.kind==="peer-left")j(D.users);else if(D.kind==="message")Q(D.type,D.payload,W({userId:D.fromUserId,peerId:D.fromPeerId}));else if(D.kind==="log")S?.(D.direction,D.obj)};return z.addEventListener("message",_),z.postMessage({cmd:"enter",userId:q,appId:C,room:Z,host:i}),{exitRoom:()=>{V=!0,z.removeEventListener("message",_),z.postMessage({cmd:"exit"})}}}var U=J;function x({userId:q,appId:C,receivePeerConnection:Z,peerlessUserExpiration:i,rtcConfig:A={iceServers:[{urls:"stun:stun.l.google.com:19302"}]},enterRoomFunction:f=U,logLine:Y=console.debug,onLeaveUser:X,workerUrl:j}){let Q=new Map;function S(T){let D=Q.get(T.userId);if(!D){let B={userId:T.userId,pc:new RTCPeerConnection(A),pendingRemoteIce:[],peers:new Map};B.peers.set(T.peerId,T),Q.set(T.userId,B),B.pc.onicecandidate=(H)=>{if(!H.candidate)return;for(let F of B.peers.values())if(F.receive("ice",H.candidate.toJSON()))break},B.pc.onconnectionstatechange=()=>{Y("\uD83D\uDCAC",{event:"pc-state",userId:B.userId,state:B.pc.connectionState})},D=B,Q.set(D.userId,D)}else if(D)clearTimeout(D.expirationTimeout),D.expirationTimeout=0,D.peers.set(T.peerId,T);return D}function N(T){X?.(T);let D=Q.get(T);if(!D)return;try{D.pc.close()}catch{}Q.delete(T)}async function z(T){if(!T.pc.remoteDescription)return;let D=T.pendingRemoteIce;T.pendingRemoteIce=[];for(let B of D)try{await T.pc.addIceCandidate(B)}catch(H){Y("⚠️ ERROR",{error:"add-ice-failed",userId:T.userId,detail:String(H)})}}let V=new Map;function W({room:T,host:D}){let B=`${D}/room/${T}`,H=V.get(B);if(H)H.exitRoom(),V.delete(B)}function _({room:T,host:D}){return new Promise((B,H)=>{async function F(G){let R=S(G).pc,$=await R.createOffer();await R.setLocalDescription($),G.receive("offer",R.localDescription?.toJSON())}let{exitRoom:b}=f({userId:q,appId:C,room:T,host:D,logLine:Y,workerUrl:j,onOpen:B,onError:H,onPeerJoined(G){G.forEach((c)=>{let $=S(c).pc;Z({pc:$,userId:c.userId,initiator:!0}),F(c)})},onPeerLeft(G){G.forEach(({userId:c,peerId:R})=>{let $=Q.get(c);if(!$)return;if($.peers.delete(R),!$.peers.size)$.expirationTimeout=setTimeout(()=>N(c),i??0)})},async onMessage(G,c,R){let $=S(R),K=$.pc;if(G==="offer"){Z({pc:K,userId:R.userId,initiator:!1}),await K.setRemoteDescription(c);let h=await K.createAnswer();await K.setLocalDescription(h),R.receive("answer",K.localDescription?.toJSON()),await z($);return}if(G==="answer"){await K.setRemoteDescription(c),await z($);return}if(G==="ice"){let h=c;if(!K.remoteDescription){$.pendingRemoteIce.push(h);return}try{await K.addIceCandidate(h)}catch(O){Y("⚠️ ERROR",{error:"add-ice-failed",userId:$.userId,detail:String(O)})}return}}});V.set(`${D}/room/${T}`,{exitRoom:b,room:T,host:D})})}return{enterRoom:_,exitRoom:W,leaveUser:N,getRooms(){return Array.from(V.values())},end(){V.forEach(({exitRoom:T})=>T()),V.clear(),Q.forEach(({userId:T})=>N(T)),Q.clear()}}}function l({uid:q,appId:C,logLine:Z=console.debug,enterRoomFunction:i=J,peerlessUserExpiration:A,workerUrl:f}){let Y=q??`user-${crypto.randomUUID()}`,X=[],j={iceServers:[{urls:"stun:stun.l.google.com:19302"}]},Q=new Set;function S(){return X}function N(c,R){R.onopen=()=>{Z("\uD83D\uDCAC",{event:"dc-open",userId:c}),X.push(c),V.forEach(($)=>$(c,"join",X))},R.onmessage=({data:$})=>{Q.forEach((K)=>K($,c)),Z("\uD83D\uDCAC",{event:"dc-message",userId:c,data:$})},R.onclose=()=>{Z("\uD83D\uDCAC",{event:"dc-close",userId:c}),X.splice(X.indexOf(c),1),V.forEach(($)=>$(c,"leave",X))},R.onerror=()=>Z("⚠️ ERROR",{error:"dc-error",userId:c})}let z=new Map,V=new Set,{enterRoom:W,exitRoom:_,leaveUser:T,end:D}=x({userId:Y,appId:C,rtcConfig:j,enterRoomFunction:i,logLine:Z,workerUrl:f,peerlessUserExpiration:A,onLeaveUser(c){let R=z.get(c);try{R?.close()}catch{}z.delete(c)},receivePeerConnection({pc:c,userId:R,initiator:$}){if($){let K=c.createDataChannel("data");N(R,K),z.set(R,K)}else c.ondatachannel=(K)=>{let h=K.channel;N(R,h),z.set(R,h),c.ondatachannel=null}}});function B(c,R){z.forEach(($,K)=>{if(R&&K!==R)return;if($.readyState==="open")$.send(c)})}function H(c){Q.delete(c)}function F(c){return Q.add(c),()=>{H(c)}}function b(c){V.delete(c)}function G(c){return V.add(c),()=>{b(c)}}return{userId:Y,send:B,enterRoom:W,exitRoom:_,leaveUser:T,getUsers:S,addMessageListener:F,removeMessageListener:H,addUserListener:G,removeUserListener:b,end(){z.forEach((c)=>{try{c.close()}catch{}}),z.clear(),D(),V.clear(),X.length=0}}}export{l as enterWorld};
2
+
3
+ //# debugId=EAC82A036A31C3CC64756E2164756E21
4
+ //# sourceMappingURL=enter-world.js.map
@@ -0,0 +1,13 @@
1
+ {
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"],
4
+ "sourcesContent": [
5
+ "export interface IPeer<T extends string = string, P = any> {\n userId: string;\n peerId: string;\n receive(type: T, payload: P): boolean;\n}\n\n/**\n * enterRoom connects to the signaling room via WebSocket.\n */\nexport function enterRoom<T extends string, P = any>({\n userId,\n appId,\n room,\n host,\n onOpen,\n onClose,\n onError,\n logLine,\n onPeerJoined,\n onPeerLeft,\n onMessage,\n}: {\n userId: string;\n appId: string;\n room: string;\n host: string;\n onOpen?: () => void;\n onClose?: () => void;\n onError?: () => void;\n logLine?: (direction: string, obj?: any) => void;\n onPeerJoined(users: IPeer<T, P>[]) : void;\n onPeerLeft(users: {userId: string, peerId: string}[]) : void;\n onMessage(type: T, payload: P, from: IPeer<T, P>) : void;\n}): { exitRoom: () => void } {\n const wsUrl = `wss://${host}/room/${appId}/${room}?userId=${encodeURIComponent(userId)}`;\n const ws = new WebSocket(wsUrl);\n const selfUserId = userId;\n\n const peers = new Map<string, IPeer<T, P>>();\n let exited = false;\n function send(type: T, toPeerId: string, payload: P) {\n if (exited) return false;\n const obj = { type, to: toPeerId, payload };\n ws.send(JSON.stringify(obj));\n logLine?.(\"👤 ➡️ 🖥️\", obj);\n return true;\n }\n\n function updatePeers(updatedUsers: { peerId: string; userId: string }[]) {\n const joined: IPeer<T,P>[] = [];\n const left: Omit<IPeer<T,P>, \"receive\">[] = [];\n const updatedPeerSet = new Set<string>();\n updatedUsers.forEach(({ userId, peerId }) => {\n if (userId === selfUserId) return;\n if (!peers.has(peerId)) {\n const newPeer = { userId, peerId, receive: (type: T, payload: P) => send(type, peerId, payload)};\n peers.set(peerId, newPeer);\n joined.push(newPeer);\n }\n updatedPeerSet.add(peerId);\n });\n peers.values().forEach(({ peerId, userId }) => {\n if (!updatedPeerSet.has(peerId)) {\n peers.delete(peerId);\n left.push({ peerId, userId });\n }\n });\n if (joined.length) onPeerJoined(joined);\n if (left.length) onPeerLeft(left);\n }\n\n function onmessage(e: MessageEvent) {\n let msg: {\n type: T;\n peerId: string;\n userId: string;\n users: { peerId: string, userId: string }[],\n payload: P;\n };\n try { msg = JSON.parse(e.data); }\n catch {\n logLine?.(\"⚠️ ERROR\", { error: \"invalid-json\" });\n return;\n }\n\n logLine?.(\"🖥️ ➡️ 👤\", msg);\n\n // Existing client greets newcomers\n if (msg.type === \"peer-joined\" || msg.type === \"peer-left\") {\n updatePeers(msg.users);\n return;\n }\n if (msg.peerId && msg.userId) {\n const { userId, peerId } = msg;\n onMessage(msg.type, msg.payload, {\n userId,\n peerId,\n receive: (type: T, payload: P) => send(type, peerId, payload),\n });\n }\n };\n\n ws.addEventListener(\"message\", onmessage);\n if (onOpen) ws.addEventListener(\"open\", onOpen);\n if (onClose) ws.addEventListener(\"close\", onClose);\n if (onError) ws.addEventListener(\"error\", onError);\n return {\n exitRoom: () => {\n exited = true;\n ws.close();\n ws.removeEventListener(\"message\", onmessage);\n if (onOpen) ws.removeEventListener(\"open\", onOpen);\n if (onClose) ws.removeEventListener(\"close\", onClose);\n if (onError) ws.removeEventListener(\"error\", onError);\n },\n };\n}\n",
6
+ "import type { IPeer } from \"./impl/signal-room.js\";\nimport { enterRoom as baseEnterRoom } from \"./impl/signal-room.js\";\nimport { RoomEvent } from \"./signal-room.worker.js\";\n\nexport function enterRoom<T extends string, P = any>({\n userId,\n appId,\n room,\n host,\n onOpen,\n onClose,\n onError,\n onPeerJoined,\n onPeerLeft,\n onMessage,\n logLine,\n workerUrl,\n}: {\n userId: string;\n appId: string;\n room: string;\n host: string;\n onOpen?: () => void;\n onClose?: () => void;\n onError?: () => void;\n onPeerJoined: (users: IPeer<T, P>[]) => void;\n onPeerLeft: (users: {userId: string, peerId: string}[]) => 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}): { exitRoom: () => void } {\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(\"Warning: enterRoom called without workerUrl; this may cause issues in some environments. You should pass workerUrl explicitly. Use:\", CDN_WORKER_URL);\n return baseEnterRoom<T, P>({\n userId,\n appId,\n room,\n host,\n onOpen,\n onClose,\n onError,\n onPeerJoined,\n onPeerLeft,\n onMessage,\n });\n }\n const worker = new Worker(workerUrl, { type: \"module\" });\n let exited = false;\n\n function makeUser({ userId, peerId }: { userId: string; peerId: string }): IPeer<T, P> {\n return {\n userId,\n peerId,\n receive: (type: T, payload: P) => {\n if (exited) return false;\n worker.postMessage({ cmd: \"send\", toPeerId: peerId, type, payload });\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\") worker.terminate();\n else if (ev.kind === \"error\") onError?.();\n else if (ev.kind === \"peer-joined\") onPeerJoined(ev.users.map(ev => makeUser({ userId: ev.userId, peerId: ev.peerId })));\n else if (ev.kind === \"peer-left\") onPeerLeft(ev.users);\n else if (ev.kind === \"message\") onMessage(ev.type, ev.payload, makeUser({ userId: ev.fromUserId, peerId: ev.fromPeerId }));\n else if (ev.kind === \"log\") logLine?.(ev.direction, ev.obj);\n };\n\n worker.addEventListener(\"message\", onWorkerMessage);\n\n worker.postMessage({ cmd: \"enter\", userId, appId, room, host });\n\n return {\n exitRoom: () => {\n exited = true;\n worker.removeEventListener(\"message\", onWorkerMessage);\n worker.postMessage({ cmd: \"exit\" });\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\";\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 peers: Map<string, IPeer<SigType, SigPayload>>;\n\n expirationTimeout?: number;\n};\ntype UserListener = (user: string, action: \"join\"|\"leave\") => void;\n\nconst DEFAULT_ENTER_ROOM = enterRoom;\n\n\nexport function collectPeerConnections({\n userId,\n appId,\n receivePeerConnection,\n peerlessUserExpiration,\n rtcConfig = { iceServers: [{ urls: \"stun:stun.l.google.com:19302\" }] },\n enterRoomFunction: enterRoom = DEFAULT_ENTER_ROOM,\n logLine = console.debug,\n onLeaveUser,\n workerUrl,\n}: {\n userId: string;\n appId: string;\n rtcConfig?: 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: { pc: RTCPeerConnection, userId: string, initiator: boolean }): void;\n}) {\n const users: Map<string, UserState> = new Map();\n\n function getPeer(peer: IPeer<SigType, SigPayload>): UserState {\n let state = users.get(peer.userId);\n if (!state) {\n const newState: UserState = {\n userId: peer.userId,\n pc: new RTCPeerConnection(rtcConfig),\n pendingRemoteIce: [],\n peers: new Map(),\n };\n newState.peers.set(peer.peerId, peer);\n users.set(peer.userId, newState);\n\n // Send local ICE candidates to this peer\n newState.pc.onicecandidate = (ev) => {\n if (!ev.candidate) return;\n for(let user of newState.peers.values()) {\n const success = user.receive(\"ice\", ev.candidate.toJSON());\n if (success) break;\n }\n };\n \n newState.pc.onconnectionstatechange = () => {\n logLine(\"💬\", { event: \"pc-state\", userId: newState.userId, state: newState.pc.connectionState });\n };\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 state.peers.set(peer.peerId, peer);\n }\n return state;\n }\n\n function leaveUser(userId: string) {\n onLeaveUser?.(userId);\n const p = users.get(userId);\n if (!p) return;\n try { p.pc.close(); } 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\", { error: \"add-ice-failed\", userId: state.userId, detail: String(e) });\n }\n }\n }\n\n const roomsEntered = new Map<string, { room: string; host: string; exitRoom: () => void }>();\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>((resolve, reject) => {\n async function makeOffer(user: IPeer) {\n // Offer flow: createOffer -> setLocalDescription -> send localDescription\n const state = 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 const { exitRoom } = enterRoom({\n userId,\n appId,\n room,\n host,\n logLine,\n workerUrl,\n\n onOpen: resolve,\n onError: reject,\n\n // Existing peers initiate to the newcomer (Option 1)\n onPeerJoined(joiningUsers: IPeer<SigType, SigPayload>[]) {\n joiningUsers.forEach(user => {\n const state = getPeer(user);\n const pc = state.pc;\n receivePeerConnection({ pc, userId: user.userId, initiator: true });\n makeOffer(user);\n });\n },\n\n onPeerLeft(leavingUsers: { userId: string; peerId: string }[]) {\n leavingUsers.forEach(({ userId, peerId }) => {\n const state = users.get(userId);\n if (!state) return;\n state.peers.delete(peerId);\n if (!state.peers.size) {\n state.expirationTimeout = setTimeout(() => leaveUser(userId), peerlessUserExpiration ?? 0);\n }\n });\n },\n\n async onMessage(type: SigType, payload: any, from: IPeer) {\n const state = getPeer(from);\n const pc = state.pc;\n\n if (type === \"offer\") {\n receivePeerConnection({ pc, userId: from.userId, initiator: false });\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\", { error: \"add-ice-failed\", userId: state.userId, detail: String(e) });\n }\n return;\n }\n },\n });\n roomsEntered.set(`${host}/room/${room}`, { exitRoom, room, host });\n });\n }\n\n return {\n enterRoom: enter,\n exitRoom: exit,\n leaveUser,\n getRooms() {\n return Array.from(roomsEntered.values());\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\n\n",
8
+ "import { EnterRoom, enterRoom } from \"./signal-room\";\nimport { SigType, SigPayload, collectPeerConnections } from \"./webrtc-peer-collector\";\n\nexport function enterWorld({\n uid, appId, logLine = console.debug, enterRoomFunction = enterRoom, peerlessUserExpiration, workerUrl,\n}: {\n uid?: string;\n appId: string;\n logLine?: (direction: string, obj?: any) => void;\n enterRoomFunction?: EnterRoom<SigType, SigPayload>;\n peerlessUserExpiration?: number;\n workerUrl?: URL;\n}) {\n const userId = uid ?? `user-${crypto.randomUUID()}`;\n const userIds: string[] = [];\n const rtcConfig: RTCConfiguration = {\n iceServers: [{ urls: \"stun:stun.l.google.com:19302\" }],\n };\n\n const messagesListeners = new Set<(data: any, from: string) => void>();\n\n function getUsers() {\n return userIds;\n }\n\n function wireDataChannel(userId: string, dc: RTCDataChannel) {\n dc.onopen = () => {\n logLine(\"💬\", { event: \"dc-open\", userId });\n userIds.push(userId);\n userListeners.forEach(listener => listener(userId, \"join\", userIds));\n };\n dc.onmessage = ({ data }) => {\n messagesListeners.forEach(listener => listener(data as any, userId));\n logLine(\"💬\", { event: \"dc-message\", userId, data });\n };\n dc.onclose = () => {\n logLine(\"💬\", { event: \"dc-close\", userId });\n userIds.splice(userIds.indexOf(userId), 1);\n userListeners.forEach(listener => listener(userId, \"leave\", userIds));\n };\n dc.onerror = () => logLine(\"⚠️ ERROR\", { error: \"dc-error\", userId });\n }\n\n const dataChannels = new Map<string, RTCDataChannel>();\n const userListeners = new Set<(userId: string, action: \"join\"|\"leave\", users: string[]) => void>();\n\n const { enterRoom, exitRoom, leaveUser, end: endPeerCollection } = collectPeerConnections({\n userId,\n appId,\n rtcConfig,\n enterRoomFunction,\n logLine,\n workerUrl,\n peerlessUserExpiration,\n onLeaveUser(userId: string) {\n const dc = dataChannels.get(userId);\n try { dc?.close(); } catch { }\n dataChannels.delete(userId);\n },\n receivePeerConnection({ pc, userId, initiator }) {\n if (initiator) {\n const dc = pc.createDataChannel(\"data\");\n wireDataChannel(userId, dc);\n dataChannels.set(userId, dc);\n } else {\n pc.ondatachannel = (ev) => {\n const dc = ev.channel;\n wireDataChannel(userId, dc);\n dataChannels.set(userId, dc);\n pc.ondatachannel = null;\n };\n }\n },\n });\n\n function send(data: any, userId?: string) {\n dataChannels.forEach((dataChannel, pUserId) => {\n if (userId && pUserId !== userId) return;\n if (dataChannel.readyState === \"open\") dataChannel.send(data);\n });\n }\n\n function removeMessageListener(listener: (data: any, from: string) => void) {\n messagesListeners.delete(listener);\n }\n\n function addMessageListener(listener: (data: any, from: string) => void) {\n messagesListeners.add(listener);\n return () => {\n removeMessageListener(listener);\n };\n }\n\n function removeUserListener(listener: (userId: string, action:\"join\"|\"leave\", users: string[]) => void) {\n userListeners.delete(listener);\n }\n\n function addUserListener(listener: (userId: string, action:\"join\"|\"leave\", users: string[]) => void) {\n userListeners.add(listener);\n return () => {\n removeUserListener(listener);\n };\n }\n\n return {\n userId,\n send,\n enterRoom,\n exitRoom,\n leaveUser,\n getUsers,\n addMessageListener,\n removeMessageListener,\n addUserListener,\n removeUserListener,\n end() {\n dataChannels.forEach((dataChannel) => {\n try { dataChannel.close(); } catch { }\n });\n dataChannels.clear();\n endPeerCollection();\n userListeners.clear();\n userIds.length = 0;\n },\n };\n}\n"
9
+ ],
10
+ "mappings": "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",
11
+ "debugId": "EAC82A036A31C3CC64756E2164756E21",
12
+ "names": []
13
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"signal-room.d.ts","sourceRoot":"","sources":["../../src/browser/impl/signal-room.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,KAAK,CAAC,CAAC,SAAS,MAAM,GAAG,MAAM,EAAE,CAAC,GAAG,GAAG;IACrD,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,IAAI,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,GAAG,OAAO,CAAC;CACzC;AAED;;GAEG;AACH,wBAAgB,SAAS,CAAC,CAAC,SAAS,MAAM,EAAE,CAAC,GAAG,GAAG,EAAE,EACjD,MAAM,EACN,KAAK,EACL,IAAI,EACJ,IAAI,EACJ,MAAM,EACN,OAAO,EACP,OAAO,EACP,OAAO,EACP,YAAY,EACZ,UAAU,EACV,SAAS,GACZ,EAAE;IACC,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,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,GAAI,IAAI,CAAC;IAC1C,UAAU,CAAC,KAAK,EAAE;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAC,EAAE,GAAI,IAAI,CAAC;IAC7D,SAAS,CAAC,IAAI,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,GAAI,IAAI,CAAC;CAC5D,GAAG;IAAE,QAAQ,EAAE,MAAM,IAAI,CAAA;CAAE,CAuF3B"}
1
+ {"version":3,"file":"signal-room.d.ts","sourceRoot":"","sources":["../../src/browser/impl/signal-room.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,KAAK,CAAC,CAAC,SAAS,MAAM,GAAG,MAAM,EAAE,CAAC,GAAG,GAAG;IACrD,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,IAAI,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,GAAG,OAAO,CAAC;CACzC;AAED;;GAEG;AACH,wBAAgB,SAAS,CAAC,CAAC,SAAS,MAAM,EAAE,CAAC,GAAG,GAAG,EAAE,EACjD,MAAM,EACN,KAAK,EACL,IAAI,EACJ,IAAI,EACJ,MAAM,EACN,OAAO,EACP,OAAO,EACP,OAAO,EACP,YAAY,EACZ,UAAU,EACV,SAAS,GACZ,EAAE;IACC,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,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,GAAI,IAAI,CAAC;IAC1C,UAAU,CAAC,KAAK,EAAE;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAC,EAAE,GAAI,IAAI,CAAC;IAC7D,SAAS,CAAC,IAAI,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,GAAI,IAAI,CAAC;CAC5D,GAAG;IAAE,QAAQ,EAAE,MAAM,IAAI,CAAA;CAAE,CAmF3B"}
package/dist/index.js ADDED
@@ -0,0 +1,4 @@
1
+ function J({userId:i,appId:h,room:W,host:C,onOpen:Y,onClose:N,onError:V,logLine:Q,onPeerJoined:x,onPeerLeft:H,onMessage:q}){let Z=`wss://${C}/room/${h}/${W}?userId=${encodeURIComponent(i)}`,R=new WebSocket(Z),K=i,A=new Map,S=!1;function D(F,z,X){if(S)return!1;let B={type:F,to:z,payload:X};return R.send(JSON.stringify(B)),Q?.("\uD83D\uDC64 ➡️ \uD83D\uDDA5️",B),!0}function f(F){let z=[],X=[],B=new Set;if(F.forEach(({userId:c,peerId:T})=>{if(c===K)return;if(!A.has(T)){let b={userId:c,peerId:T,receive:(G,_)=>D(G,T,_)};A.set(T,b),z.push(b)}B.add(T)}),A.values().forEach(({peerId:c,userId:T})=>{if(!B.has(c))A.delete(c),X.push({peerId:c,userId:T})}),z.length)x(z);if(X.length)H(X)}function $(F){let z;try{z=JSON.parse(F.data)}catch{Q?.("⚠️ ERROR",{error:"invalid-json"});return}if(Q?.("\uD83D\uDDA5️ ➡️ \uD83D\uDC64",z),z.type==="peer-joined"||z.type==="peer-left"){f(z.users);return}if(z.peerId&&z.userId){let{userId:X,peerId:B}=z;q(z.type,z.payload,{userId:X,peerId:B,receive:(c,T)=>D(c,B,T)})}}if(R.addEventListener("message",$),Y)R.addEventListener("open",Y);if(N)R.addEventListener("close",N);if(V)R.addEventListener("error",V);return{exitRoom:()=>{if(S=!0,R.close(),R.removeEventListener("message",$),Y)R.removeEventListener("open",Y);if(N)R.removeEventListener("close",N);if(V)R.removeEventListener("error",V)}}}function j({userId:i,appId:h,room:W,host:C,onOpen:Y,onClose:N,onError:V,onPeerJoined:Q,onPeerLeft:x,onMessage:H,logLine:q,workerUrl:Z}){if(!Z)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"),J({userId:i,appId:h,room:W,host:C,onOpen:Y,onClose:N,onError:V,onPeerJoined:Q,onPeerLeft:x,onMessage:H});let R=new Worker(Z,{type:"module"}),K=!1;function A({userId:D,peerId:f}){return{userId:D,peerId:f,receive:($,F)=>{if(K)return!1;return R.postMessage({cmd:"send",toPeerId:f,type:$,payload:F}),!0}}}let S=(D)=>{let f=D.data;if(f.kind==="open")Y?.();else if(f.kind==="close")R.terminate();else if(f.kind==="error")V?.();else if(f.kind==="peer-joined")Q(f.users.map(($)=>A({userId:$.userId,peerId:$.peerId})));else if(f.kind==="peer-left")x(f.users);else if(f.kind==="message")H(f.type,f.payload,A({userId:f.fromUserId,peerId:f.fromPeerId}));else if(f.kind==="log")q?.(f.direction,f.obj)};return R.addEventListener("message",S),R.postMessage({cmd:"enter",userId:i,appId:h,room:W,host:C}),{exitRoom:()=>{K=!0,R.removeEventListener("message",S),R.postMessage({cmd:"exit"})}}}var U=j;function M({userId:i,appId:h,receivePeerConnection:W,peerlessUserExpiration:C,rtcConfig:Y={iceServers:[{urls:"stun:stun.l.google.com:19302"}]},enterRoomFunction:N=U,logLine:V=console.debug,onLeaveUser:Q,workerUrl:x}){let H=new Map;function q(D){let f=H.get(D.userId);if(!f){let $={userId:D.userId,pc:new RTCPeerConnection(Y),pendingRemoteIce:[],peers:new Map};$.peers.set(D.peerId,D),H.set(D.userId,$),$.pc.onicecandidate=(F)=>{if(!F.candidate)return;for(let z of $.peers.values())if(z.receive("ice",F.candidate.toJSON()))break},$.pc.onconnectionstatechange=()=>{V("\uD83D\uDCAC",{event:"pc-state",userId:$.userId,state:$.pc.connectionState})},f=$,H.set(f.userId,f)}else if(f)clearTimeout(f.expirationTimeout),f.expirationTimeout=0,f.peers.set(D.peerId,D);return f}function Z(D){Q?.(D);let f=H.get(D);if(!f)return;try{f.pc.close()}catch{}H.delete(D)}async function R(D){if(!D.pc.remoteDescription)return;let f=D.pendingRemoteIce;D.pendingRemoteIce=[];for(let $ of f)try{await D.pc.addIceCandidate($)}catch(F){V("⚠️ ERROR",{error:"add-ice-failed",userId:D.userId,detail:String(F)})}}let K=new Map;function A({room:D,host:f}){let $=`${f}/room/${D}`,F=K.get($);if(F)F.exitRoom(),K.delete($)}function S({room:D,host:f}){return new Promise(($,F)=>{async function z(B){let T=q(B).pc,b=await T.createOffer();await T.setLocalDescription(b),B.receive("offer",T.localDescription?.toJSON())}let{exitRoom:X}=N({userId:i,appId:h,room:D,host:f,logLine:V,workerUrl:x,onOpen:$,onError:F,onPeerJoined(B){B.forEach((c)=>{let b=q(c).pc;W({pc:b,userId:c.userId,initiator:!0}),z(c)})},onPeerLeft(B){B.forEach(({userId:c,peerId:T})=>{let b=H.get(c);if(!b)return;if(b.peers.delete(T),!b.peers.size)b.expirationTimeout=setTimeout(()=>Z(c),C??0)})},async onMessage(B,c,T){let b=q(T),G=b.pc;if(B==="offer"){W({pc:G,userId:T.userId,initiator:!1}),await G.setRemoteDescription(c);let _=await G.createAnswer();await G.setLocalDescription(_),T.receive("answer",G.localDescription?.toJSON()),await R(b);return}if(B==="answer"){await G.setRemoteDescription(c),await R(b);return}if(B==="ice"){let _=c;if(!G.remoteDescription){b.pendingRemoteIce.push(_);return}try{await G.addIceCandidate(_)}catch(O){V("⚠️ ERROR",{error:"add-ice-failed",userId:b.userId,detail:String(O)})}return}}});K.set(`${f}/room/${D}`,{exitRoom:X,room:D,host:f})})}return{enterRoom:S,exitRoom:A,leaveUser:Z,getRooms(){return Array.from(K.values())},end(){K.forEach(({exitRoom:D})=>D()),K.clear(),H.forEach(({userId:D})=>Z(D)),H.clear()}}}function E({uid:i,appId:h,logLine:W=console.debug,enterRoomFunction:C=j,peerlessUserExpiration:Y,workerUrl:N}){let V=i??`user-${crypto.randomUUID()}`,Q=[],x={iceServers:[{urls:"stun:stun.l.google.com:19302"}]},H=new Set;function q(){return Q}function Z(c,T){T.onopen=()=>{W("\uD83D\uDCAC",{event:"dc-open",userId:c}),Q.push(c),K.forEach((b)=>b(c,"join",Q))},T.onmessage=({data:b})=>{H.forEach((G)=>G(b,c)),W("\uD83D\uDCAC",{event:"dc-message",userId:c,data:b})},T.onclose=()=>{W("\uD83D\uDCAC",{event:"dc-close",userId:c}),Q.splice(Q.indexOf(c),1),K.forEach((b)=>b(c,"leave",Q))},T.onerror=()=>W("⚠️ ERROR",{error:"dc-error",userId:c})}let R=new Map,K=new Set,{enterRoom:A,exitRoom:S,leaveUser:D,end:f}=M({userId:V,appId:h,rtcConfig:x,enterRoomFunction:C,logLine:W,workerUrl:N,peerlessUserExpiration:Y,onLeaveUser(c){let T=R.get(c);try{T?.close()}catch{}R.delete(c)},receivePeerConnection({pc:c,userId:T,initiator:b}){if(b){let G=c.createDataChannel("data");Z(T,G),R.set(T,G)}else c.ondatachannel=(G)=>{let _=G.channel;Z(T,_),R.set(T,_),c.ondatachannel=null}}});function $(c,T){R.forEach((b,G)=>{if(T&&G!==T)return;if(b.readyState==="open")b.send(c)})}function F(c){H.delete(c)}function z(c){return H.add(c),()=>{F(c)}}function X(c){K.delete(c)}function B(c){return K.add(c),()=>{X(c)}}return{userId:V,send:$,enterRoom:A,exitRoom:S,leaveUser:D,getUsers:q,addMessageListener:z,removeMessageListener:F,addUserListener:B,removeUserListener:X,end(){R.forEach((c)=>{try{c.close()}catch{}}),R.clear(),f(),K.clear(),Q.length=0}}}export{E as enterWorld,J as enterRoom,M as collectPeerConnections};
2
+
3
+ //# debugId=CF29A88A42A5985F64756E2164756E21
4
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,13 @@
1
+ {
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"],
4
+ "sourcesContent": [
5
+ "export interface IPeer<T extends string = string, P = any> {\n userId: string;\n peerId: string;\n receive(type: T, payload: P): boolean;\n}\n\n/**\n * enterRoom connects to the signaling room via WebSocket.\n */\nexport function enterRoom<T extends string, P = any>({\n userId,\n appId,\n room,\n host,\n onOpen,\n onClose,\n onError,\n logLine,\n onPeerJoined,\n onPeerLeft,\n onMessage,\n}: {\n userId: string;\n appId: string;\n room: string;\n host: string;\n onOpen?: () => void;\n onClose?: () => void;\n onError?: () => void;\n logLine?: (direction: string, obj?: any) => void;\n onPeerJoined(users: IPeer<T, P>[]) : void;\n onPeerLeft(users: {userId: string, peerId: string}[]) : void;\n onMessage(type: T, payload: P, from: IPeer<T, P>) : void;\n}): { exitRoom: () => void } {\n const wsUrl = `wss://${host}/room/${appId}/${room}?userId=${encodeURIComponent(userId)}`;\n const ws = new WebSocket(wsUrl);\n const selfUserId = userId;\n\n const peers = new Map<string, IPeer<T, P>>();\n let exited = false;\n function send(type: T, toPeerId: string, payload: P) {\n if (exited) return false;\n const obj = { type, to: toPeerId, payload };\n ws.send(JSON.stringify(obj));\n logLine?.(\"👤 ➡️ 🖥️\", obj);\n return true;\n }\n\n function updatePeers(updatedUsers: { peerId: string; userId: string }[]) {\n const joined: IPeer<T,P>[] = [];\n const left: Omit<IPeer<T,P>, \"receive\">[] = [];\n const updatedPeerSet = new Set<string>();\n updatedUsers.forEach(({ userId, peerId }) => {\n if (userId === selfUserId) return;\n if (!peers.has(peerId)) {\n const newPeer = { userId, peerId, receive: (type: T, payload: P) => send(type, peerId, payload)};\n peers.set(peerId, newPeer);\n joined.push(newPeer);\n }\n updatedPeerSet.add(peerId);\n });\n peers.values().forEach(({ peerId, userId }) => {\n if (!updatedPeerSet.has(peerId)) {\n peers.delete(peerId);\n left.push({ peerId, userId });\n }\n });\n if (joined.length) onPeerJoined(joined);\n if (left.length) onPeerLeft(left);\n }\n\n function onmessage(e: MessageEvent) {\n let msg: {\n type: T;\n peerId: string;\n userId: string;\n users: { peerId: string, userId: string }[],\n payload: P;\n };\n try { msg = JSON.parse(e.data); }\n catch {\n logLine?.(\"⚠️ ERROR\", { error: \"invalid-json\" });\n return;\n }\n\n logLine?.(\"🖥️ ➡️ 👤\", msg);\n\n // Existing client greets newcomers\n if (msg.type === \"peer-joined\" || msg.type === \"peer-left\") {\n updatePeers(msg.users);\n return;\n }\n if (msg.peerId && msg.userId) {\n const { userId, peerId } = msg;\n onMessage(msg.type, msg.payload, {\n userId,\n peerId,\n receive: (type: T, payload: P) => send(type, peerId, payload),\n });\n }\n };\n\n ws.addEventListener(\"message\", onmessage);\n if (onOpen) ws.addEventListener(\"open\", onOpen);\n if (onClose) ws.addEventListener(\"close\", onClose);\n if (onError) ws.addEventListener(\"error\", onError);\n return {\n exitRoom: () => {\n exited = true;\n ws.close();\n ws.removeEventListener(\"message\", onmessage);\n if (onOpen) ws.removeEventListener(\"open\", onOpen);\n if (onClose) ws.removeEventListener(\"close\", onClose);\n if (onError) ws.removeEventListener(\"error\", onError);\n },\n };\n}\n",
6
+ "import type { IPeer } from \"./impl/signal-room.js\";\nimport { enterRoom as baseEnterRoom } from \"./impl/signal-room.js\";\nimport { RoomEvent } from \"./signal-room.worker.js\";\n\nexport function enterRoom<T extends string, P = any>({\n userId,\n appId,\n room,\n host,\n onOpen,\n onClose,\n onError,\n onPeerJoined,\n onPeerLeft,\n onMessage,\n logLine,\n workerUrl,\n}: {\n userId: string;\n appId: string;\n room: string;\n host: string;\n onOpen?: () => void;\n onClose?: () => void;\n onError?: () => void;\n onPeerJoined: (users: IPeer<T, P>[]) => void;\n onPeerLeft: (users: {userId: string, peerId: string}[]) => 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}): { exitRoom: () => void } {\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(\"Warning: enterRoom called without workerUrl; this may cause issues in some environments. You should pass workerUrl explicitly. Use:\", CDN_WORKER_URL);\n return baseEnterRoom<T, P>({\n userId,\n appId,\n room,\n host,\n onOpen,\n onClose,\n onError,\n onPeerJoined,\n onPeerLeft,\n onMessage,\n });\n }\n const worker = new Worker(workerUrl, { type: \"module\" });\n let exited = false;\n\n function makeUser({ userId, peerId }: { userId: string; peerId: string }): IPeer<T, P> {\n return {\n userId,\n peerId,\n receive: (type: T, payload: P) => {\n if (exited) return false;\n worker.postMessage({ cmd: \"send\", toPeerId: peerId, type, payload });\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\") worker.terminate();\n else if (ev.kind === \"error\") onError?.();\n else if (ev.kind === \"peer-joined\") onPeerJoined(ev.users.map(ev => makeUser({ userId: ev.userId, peerId: ev.peerId })));\n else if (ev.kind === \"peer-left\") onPeerLeft(ev.users);\n else if (ev.kind === \"message\") onMessage(ev.type, ev.payload, makeUser({ userId: ev.fromUserId, peerId: ev.fromPeerId }));\n else if (ev.kind === \"log\") logLine?.(ev.direction, ev.obj);\n };\n\n worker.addEventListener(\"message\", onWorkerMessage);\n\n worker.postMessage({ cmd: \"enter\", userId, appId, room, host });\n\n return {\n exitRoom: () => {\n exited = true;\n worker.removeEventListener(\"message\", onWorkerMessage);\n worker.postMessage({ cmd: \"exit\" });\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\";\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 peers: Map<string, IPeer<SigType, SigPayload>>;\n\n expirationTimeout?: number;\n};\ntype UserListener = (user: string, action: \"join\"|\"leave\") => void;\n\nconst DEFAULT_ENTER_ROOM = enterRoom;\n\n\nexport function collectPeerConnections({\n userId,\n appId,\n receivePeerConnection,\n peerlessUserExpiration,\n rtcConfig = { iceServers: [{ urls: \"stun:stun.l.google.com:19302\" }] },\n enterRoomFunction: enterRoom = DEFAULT_ENTER_ROOM,\n logLine = console.debug,\n onLeaveUser,\n workerUrl,\n}: {\n userId: string;\n appId: string;\n rtcConfig?: 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: { pc: RTCPeerConnection, userId: string, initiator: boolean }): void;\n}) {\n const users: Map<string, UserState> = new Map();\n\n function getPeer(peer: IPeer<SigType, SigPayload>): UserState {\n let state = users.get(peer.userId);\n if (!state) {\n const newState: UserState = {\n userId: peer.userId,\n pc: new RTCPeerConnection(rtcConfig),\n pendingRemoteIce: [],\n peers: new Map(),\n };\n newState.peers.set(peer.peerId, peer);\n users.set(peer.userId, newState);\n\n // Send local ICE candidates to this peer\n newState.pc.onicecandidate = (ev) => {\n if (!ev.candidate) return;\n for(let user of newState.peers.values()) {\n const success = user.receive(\"ice\", ev.candidate.toJSON());\n if (success) break;\n }\n };\n \n newState.pc.onconnectionstatechange = () => {\n logLine(\"💬\", { event: \"pc-state\", userId: newState.userId, state: newState.pc.connectionState });\n };\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 state.peers.set(peer.peerId, peer);\n }\n return state;\n }\n\n function leaveUser(userId: string) {\n onLeaveUser?.(userId);\n const p = users.get(userId);\n if (!p) return;\n try { p.pc.close(); } 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\", { error: \"add-ice-failed\", userId: state.userId, detail: String(e) });\n }\n }\n }\n\n const roomsEntered = new Map<string, { room: string; host: string; exitRoom: () => void }>();\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>((resolve, reject) => {\n async function makeOffer(user: IPeer) {\n // Offer flow: createOffer -> setLocalDescription -> send localDescription\n const state = 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 const { exitRoom } = enterRoom({\n userId,\n appId,\n room,\n host,\n logLine,\n workerUrl,\n\n onOpen: resolve,\n onError: reject,\n\n // Existing peers initiate to the newcomer (Option 1)\n onPeerJoined(joiningUsers: IPeer<SigType, SigPayload>[]) {\n joiningUsers.forEach(user => {\n const state = getPeer(user);\n const pc = state.pc;\n receivePeerConnection({ pc, userId: user.userId, initiator: true });\n makeOffer(user);\n });\n },\n\n onPeerLeft(leavingUsers: { userId: string; peerId: string }[]) {\n leavingUsers.forEach(({ userId, peerId }) => {\n const state = users.get(userId);\n if (!state) return;\n state.peers.delete(peerId);\n if (!state.peers.size) {\n state.expirationTimeout = setTimeout(() => leaveUser(userId), peerlessUserExpiration ?? 0);\n }\n });\n },\n\n async onMessage(type: SigType, payload: any, from: IPeer) {\n const state = getPeer(from);\n const pc = state.pc;\n\n if (type === \"offer\") {\n receivePeerConnection({ pc, userId: from.userId, initiator: false });\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\", { error: \"add-ice-failed\", userId: state.userId, detail: String(e) });\n }\n return;\n }\n },\n });\n roomsEntered.set(`${host}/room/${room}`, { exitRoom, room, host });\n });\n }\n\n return {\n enterRoom: enter,\n exitRoom: exit,\n leaveUser,\n getRooms() {\n return Array.from(roomsEntered.values());\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\n\n",
8
+ "import { EnterRoom, enterRoom } from \"./signal-room\";\nimport { SigType, SigPayload, collectPeerConnections } from \"./webrtc-peer-collector\";\n\nexport function enterWorld({\n uid, appId, logLine = console.debug, enterRoomFunction = enterRoom, peerlessUserExpiration, workerUrl,\n}: {\n uid?: string;\n appId: string;\n logLine?: (direction: string, obj?: any) => void;\n enterRoomFunction?: EnterRoom<SigType, SigPayload>;\n peerlessUserExpiration?: number;\n workerUrl?: URL;\n}) {\n const userId = uid ?? `user-${crypto.randomUUID()}`;\n const userIds: string[] = [];\n const rtcConfig: RTCConfiguration = {\n iceServers: [{ urls: \"stun:stun.l.google.com:19302\" }],\n };\n\n const messagesListeners = new Set<(data: any, from: string) => void>();\n\n function getUsers() {\n return userIds;\n }\n\n function wireDataChannel(userId: string, dc: RTCDataChannel) {\n dc.onopen = () => {\n logLine(\"💬\", { event: \"dc-open\", userId });\n userIds.push(userId);\n userListeners.forEach(listener => listener(userId, \"join\", userIds));\n };\n dc.onmessage = ({ data }) => {\n messagesListeners.forEach(listener => listener(data as any, userId));\n logLine(\"💬\", { event: \"dc-message\", userId, data });\n };\n dc.onclose = () => {\n logLine(\"💬\", { event: \"dc-close\", userId });\n userIds.splice(userIds.indexOf(userId), 1);\n userListeners.forEach(listener => listener(userId, \"leave\", userIds));\n };\n dc.onerror = () => logLine(\"⚠️ ERROR\", { error: \"dc-error\", userId });\n }\n\n const dataChannels = new Map<string, RTCDataChannel>();\n const userListeners = new Set<(userId: string, action: \"join\"|\"leave\", users: string[]) => void>();\n\n const { enterRoom, exitRoom, leaveUser, end: endPeerCollection } = collectPeerConnections({\n userId,\n appId,\n rtcConfig,\n enterRoomFunction,\n logLine,\n workerUrl,\n peerlessUserExpiration,\n onLeaveUser(userId: string) {\n const dc = dataChannels.get(userId);\n try { dc?.close(); } catch { }\n dataChannels.delete(userId);\n },\n receivePeerConnection({ pc, userId, initiator }) {\n if (initiator) {\n const dc = pc.createDataChannel(\"data\");\n wireDataChannel(userId, dc);\n dataChannels.set(userId, dc);\n } else {\n pc.ondatachannel = (ev) => {\n const dc = ev.channel;\n wireDataChannel(userId, dc);\n dataChannels.set(userId, dc);\n pc.ondatachannel = null;\n };\n }\n },\n });\n\n function send(data: any, userId?: string) {\n dataChannels.forEach((dataChannel, pUserId) => {\n if (userId && pUserId !== userId) return;\n if (dataChannel.readyState === \"open\") dataChannel.send(data);\n });\n }\n\n function removeMessageListener(listener: (data: any, from: string) => void) {\n messagesListeners.delete(listener);\n }\n\n function addMessageListener(listener: (data: any, from: string) => void) {\n messagesListeners.add(listener);\n return () => {\n removeMessageListener(listener);\n };\n }\n\n function removeUserListener(listener: (userId: string, action:\"join\"|\"leave\", users: string[]) => void) {\n userListeners.delete(listener);\n }\n\n function addUserListener(listener: (userId: string, action:\"join\"|\"leave\", users: string[]) => void) {\n userListeners.add(listener);\n return () => {\n removeUserListener(listener);\n };\n }\n\n return {\n userId,\n send,\n enterRoom,\n exitRoom,\n leaveUser,\n getUsers,\n addMessageListener,\n removeMessageListener,\n addUserListener,\n removeUserListener,\n end() {\n dataChannels.forEach((dataChannel) => {\n try { dataChannel.close(); } catch { }\n });\n dataChannels.clear();\n endPeerCollection();\n userListeners.clear();\n userIds.length = 0;\n },\n };\n}\n"
9
+ ],
10
+ "mappings": "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",
11
+ "debugId": "CF29A88A42A5985F64756E2164756E21",
12
+ "names": []
13
+ }
@@ -0,0 +1,4 @@
1
+ function h({userId:Z,appId:$,room:c,host:R,onOpen:G,onClose:X,onError:H,logLine:Y,onPeerJoined:S,onPeerLeft:W,onMessage:J}){let b=`wss://${R}/room/${$}/${c}?userId=${encodeURIComponent(Z)}`,z=new WebSocket(b),x=Z,K=new Map,_=!1;function N(T,A,V){if(_)return!1;let D={type:T,to:A,payload:V};return z.send(JSON.stringify(D)),Y?.("\uD83D\uDC64 ➡️ \uD83D\uDDA5️",D),!0}function q(T){let A=[],V=[],D=new Set;if(T.forEach(({userId:F,peerId:B})=>{if(F===x)return;if(!K.has(B)){let j={userId:F,peerId:B,receive:(M,C)=>N(M,B,C)};K.set(B,j),A.push(j)}D.add(B)}),K.values().forEach(({peerId:F,userId:B})=>{if(!D.has(F))K.delete(F),V.push({peerId:F,userId:B})}),A.length)S(A);if(V.length)W(V)}function Q(T){let A;try{A=JSON.parse(T.data)}catch{Y?.("⚠️ ERROR",{error:"invalid-json"});return}if(Y?.("\uD83D\uDDA5️ ➡️ \uD83D\uDC64",A),A.type==="peer-joined"||A.type==="peer-left"){q(A.users);return}if(A.peerId&&A.userId){let{userId:V,peerId:D}=A;J(A.type,A.payload,{userId:V,peerId:D,receive:(F,B)=>N(F,D,B)})}}if(z.addEventListener("message",Q),G)z.addEventListener("open",G);if(X)z.addEventListener("close",X);if(H)z.addEventListener("error",H);return{exitRoom:()=>{if(_=!0,z.close(),z.removeEventListener("message",Q),G)z.removeEventListener("open",G);if(X)z.removeEventListener("close",X);if(H)z.removeEventListener("error",H)}}}function O({userId:Z,appId:$,room:c,host:R,onOpen:G,onClose:X,onError:H,onPeerJoined:Y,onPeerLeft:S,onMessage:W,logLine:J,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"),h({userId:Z,appId:$,room:c,host:R,onOpen:G,onClose:X,onError:H,onPeerJoined:Y,onPeerLeft:S,onMessage:W});let z=new Worker(b,{type:"module"}),x=!1;function K({userId:N,peerId:q}){return{userId:N,peerId:q,receive:(Q,T)=>{if(x)return!1;return z.postMessage({cmd:"send",toPeerId:q,type:Q,payload:T}),!0}}}let _=(N)=>{let q=N.data;if(q.kind==="open")G?.();else if(q.kind==="close")z.terminate();else if(q.kind==="error")H?.();else if(q.kind==="peer-joined")Y(q.users.map((Q)=>K({userId:Q.userId,peerId:Q.peerId})));else if(q.kind==="peer-left")S(q.users);else if(q.kind==="message")W(q.type,q.payload,K({userId:q.fromUserId,peerId:q.fromPeerId}));else if(q.kind==="log")J?.(q.direction,q.obj)};return z.addEventListener("message",_),z.postMessage({cmd:"enter",userId:Z,appId:$,room:c,host:R}),{exitRoom:()=>{x=!0,z.removeEventListener("message",_),z.postMessage({cmd:"exit"})}}}export{O as enterRoom};
2
+
3
+ //# debugId=BFB04698485FDADB64756E2164756E21
4
+ //# sourceMappingURL=signal-room.js.map
@@ -0,0 +1,11 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/browser/impl/signal-room.ts", "../src/browser/signal-room.ts"],
4
+ "sourcesContent": [
5
+ "export interface IPeer<T extends string = string, P = any> {\n userId: string;\n peerId: string;\n receive(type: T, payload: P): boolean;\n}\n\n/**\n * enterRoom connects to the signaling room via WebSocket.\n */\nexport function enterRoom<T extends string, P = any>({\n userId,\n appId,\n room,\n host,\n onOpen,\n onClose,\n onError,\n logLine,\n onPeerJoined,\n onPeerLeft,\n onMessage,\n}: {\n userId: string;\n appId: string;\n room: string;\n host: string;\n onOpen?: () => void;\n onClose?: () => void;\n onError?: () => void;\n logLine?: (direction: string, obj?: any) => void;\n onPeerJoined(users: IPeer<T, P>[]) : void;\n onPeerLeft(users: {userId: string, peerId: string}[]) : void;\n onMessage(type: T, payload: P, from: IPeer<T, P>) : void;\n}): { exitRoom: () => void } {\n const wsUrl = `wss://${host}/room/${appId}/${room}?userId=${encodeURIComponent(userId)}`;\n const ws = new WebSocket(wsUrl);\n const selfUserId = userId;\n\n const peers = new Map<string, IPeer<T, P>>();\n let exited = false;\n function send(type: T, toPeerId: string, payload: P) {\n if (exited) return false;\n const obj = { type, to: toPeerId, payload };\n ws.send(JSON.stringify(obj));\n logLine?.(\"👤 ➡️ 🖥️\", obj);\n return true;\n }\n\n function updatePeers(updatedUsers: { peerId: string; userId: string }[]) {\n const joined: IPeer<T,P>[] = [];\n const left: Omit<IPeer<T,P>, \"receive\">[] = [];\n const updatedPeerSet = new Set<string>();\n updatedUsers.forEach(({ userId, peerId }) => {\n if (userId === selfUserId) return;\n if (!peers.has(peerId)) {\n const newPeer = { userId, peerId, receive: (type: T, payload: P) => send(type, peerId, payload)};\n peers.set(peerId, newPeer);\n joined.push(newPeer);\n }\n updatedPeerSet.add(peerId);\n });\n peers.values().forEach(({ peerId, userId }) => {\n if (!updatedPeerSet.has(peerId)) {\n peers.delete(peerId);\n left.push({ peerId, userId });\n }\n });\n if (joined.length) onPeerJoined(joined);\n if (left.length) onPeerLeft(left);\n }\n\n function onmessage(e: MessageEvent) {\n let msg: {\n type: T;\n peerId: string;\n userId: string;\n users: { peerId: string, userId: string }[],\n payload: P;\n };\n try { msg = JSON.parse(e.data); }\n catch {\n logLine?.(\"⚠️ ERROR\", { error: \"invalid-json\" });\n return;\n }\n\n logLine?.(\"🖥️ ➡️ 👤\", msg);\n\n // Existing client greets newcomers\n if (msg.type === \"peer-joined\" || msg.type === \"peer-left\") {\n updatePeers(msg.users);\n return;\n }\n if (msg.peerId && msg.userId) {\n const { userId, peerId } = msg;\n onMessage(msg.type, msg.payload, {\n userId,\n peerId,\n receive: (type: T, payload: P) => send(type, peerId, payload),\n });\n }\n };\n\n ws.addEventListener(\"message\", onmessage);\n if (onOpen) ws.addEventListener(\"open\", onOpen);\n if (onClose) ws.addEventListener(\"close\", onClose);\n if (onError) ws.addEventListener(\"error\", onError);\n return {\n exitRoom: () => {\n exited = true;\n ws.close();\n ws.removeEventListener(\"message\", onmessage);\n if (onOpen) ws.removeEventListener(\"open\", onOpen);\n if (onClose) ws.removeEventListener(\"close\", onClose);\n if (onError) ws.removeEventListener(\"error\", onError);\n },\n };\n}\n",
6
+ "import type { IPeer } from \"./impl/signal-room.js\";\nimport { enterRoom as baseEnterRoom } from \"./impl/signal-room.js\";\nimport { RoomEvent } from \"./signal-room.worker.js\";\n\nexport function enterRoom<T extends string, P = any>({\n userId,\n appId,\n room,\n host,\n onOpen,\n onClose,\n onError,\n onPeerJoined,\n onPeerLeft,\n onMessage,\n logLine,\n workerUrl,\n}: {\n userId: string;\n appId: string;\n room: string;\n host: string;\n onOpen?: () => void;\n onClose?: () => void;\n onError?: () => void;\n onPeerJoined: (users: IPeer<T, P>[]) => void;\n onPeerLeft: (users: {userId: string, peerId: string}[]) => 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}): { exitRoom: () => void } {\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(\"Warning: enterRoom called without workerUrl; this may cause issues in some environments. You should pass workerUrl explicitly. Use:\", CDN_WORKER_URL);\n return baseEnterRoom<T, P>({\n userId,\n appId,\n room,\n host,\n onOpen,\n onClose,\n onError,\n onPeerJoined,\n onPeerLeft,\n onMessage,\n });\n }\n const worker = new Worker(workerUrl, { type: \"module\" });\n let exited = false;\n\n function makeUser({ userId, peerId }: { userId: string; peerId: string }): IPeer<T, P> {\n return {\n userId,\n peerId,\n receive: (type: T, payload: P) => {\n if (exited) return false;\n worker.postMessage({ cmd: \"send\", toPeerId: peerId, type, payload });\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\") worker.terminate();\n else if (ev.kind === \"error\") onError?.();\n else if (ev.kind === \"peer-joined\") onPeerJoined(ev.users.map(ev => makeUser({ userId: ev.userId, peerId: ev.peerId })));\n else if (ev.kind === \"peer-left\") onPeerLeft(ev.users);\n else if (ev.kind === \"message\") onMessage(ev.type, ev.payload, makeUser({ userId: ev.fromUserId, peerId: ev.fromPeerId }));\n else if (ev.kind === \"log\") logLine?.(ev.direction, ev.obj);\n };\n\n worker.addEventListener(\"message\", onWorkerMessage);\n\n worker.postMessage({ cmd: \"enter\", userId, appId, room, host });\n\n return {\n exitRoom: () => {\n exited = true;\n worker.removeEventListener(\"message\", onWorkerMessage);\n worker.postMessage({ cmd: \"exit\" });\n },\n };\n}\n\nexport type EnterRoom<T extends string, P> = typeof enterRoom<T, P>;\n"
7
+ ],
8
+ "mappings": "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",
9
+ "debugId": "BFB04698485FDADB64756E2164756E21",
10
+ "names": []
11
+ }
@@ -1,4 +1,4 @@
1
- function R({userId:T,appId:z,room:V,host:B,onOpen:q,onClose:A,onError:Z,logLine:W,onPeerJoined:b,onPeerLeft:v,onMessage:x}){let C=`wss://${B}/room/${z}/${V}?userId=${encodeURIComponent(T)}`,D=new WebSocket(C),L=T,_=new Map,h=!1;function F(X,k,N){if(h)return!1;let H={type:X,to:k,payload:N};return D.send(JSON.stringify(H)),W?.("\uD83D\uDC64 ➡️ \uD83D\uDDA5️",H),!0}function J(X){let k=[],N=[],H=new Set;if(X.forEach(({userId:K,peerId:G})=>{if(K===L)return;if(!_.has(G)){let U={userId:K,peerId:G,receive:(O,S)=>F(O,G,S)};_.set(G,U),k.push(U)}H.add(G)}),_.values().forEach(({peerId:K,userId:G})=>{if(!H.has(K))_.delete(K),N.push({peerId:K,userId:G})}),k.length)b(k);if(N.length)v(N)}function M(X){let k;try{k=JSON.parse(X.data)}catch{W?.("⚠️ ERROR",{error:"invalid-json"});return}if(W?.("\uD83D\uDDA5️ ➡️ \uD83D\uDC64",k),k.type==="peer-joined"){J(k.users);return}if(k.type==="peer-left"){J(k.users);return}if(k.peerId&&k.userId){let{userId:N,peerId:H}=k;x(k.type,k.payload,{userId:N,peerId:H,receive:(K,G)=>F(K,H,G)})}}if(D.addEventListener("message",M),q)D.addEventListener("open",q);if(A)D.addEventListener("close",A);if(Z)D.addEventListener("error",Z);return{exitRoom:()=>{if(h=!0,D.close(),D.removeEventListener("message",M),q)D.removeEventListener("open",q);if(A)D.removeEventListener("close",A);if(Z)D.removeEventListener("error",Z)}}}var $=null,Y=new Map;function Q(T){self.postMessage(T)}self.addEventListener("message",(T)=>{let z=T.data;if(console.debug("[signal-room.worker] received command",z),z.cmd==="enter"){$?.(),$=null,Y.clear(),$=R({userId:z.userId,appId:z.appId,room:z.room,host:z.host,onOpen:()=>Q({kind:"open"}),onClose:()=>Q({kind:"close"}),onError:()=>Q({kind:"error"}),logLine:(B,q)=>{console.debug(`[signal-room.worker] ${B}`,q),Q({kind:"log",direction:B,obj:q})},onPeerJoined:(B)=>{B.forEach(({peerId:q,receive:A})=>Y.set(q,A)),Q({kind:"peer-joined",users:B.map(({userId:q,peerId:A})=>({userId:q,peerId:A}))})},onPeerLeft:(B)=>{B.forEach(({peerId:q})=>Y.delete(q)),Q({kind:"peer-left",users:B})},onMessage:(B,q,A)=>{Y.set(A.peerId,A.receive),Q({kind:"message",type:B,payload:q,fromUserId:A.userId,fromPeerId:A.peerId})}}).exitRoom;return}if(z.cmd==="send"){let V=Y.get(z.toPeerId);if(V)V(z.type,z.payload);return}if(z.cmd==="exit"){$?.(),self.close();return}});
1
+ function U({userId:T,appId:z,room:V,host:B,onOpen:q,onClose:A,onError:Z,logLine:W,onPeerJoined:R,onPeerLeft:b,onMessage:v}){let x=`wss://${B}/room/${z}/${V}?userId=${encodeURIComponent(T)}`,D=new WebSocket(x),C=T,_=new Map,h=!1;function F(X,k,N){if(h)return!1;let H={type:X,to:k,payload:N};return D.send(JSON.stringify(H)),W?.("\uD83D\uDC64 ➡️ \uD83D\uDDA5️",H),!0}function L(X){let k=[],N=[],H=new Set;if(X.forEach(({userId:K,peerId:G})=>{if(K===C)return;if(!_.has(G)){let M={userId:K,peerId:G,receive:(O,S)=>F(O,G,S)};_.set(G,M),k.push(M)}H.add(G)}),_.values().forEach(({peerId:K,userId:G})=>{if(!H.has(K))_.delete(K),N.push({peerId:K,userId:G})}),k.length)R(k);if(N.length)b(N)}function J(X){let k;try{k=JSON.parse(X.data)}catch{W?.("⚠️ ERROR",{error:"invalid-json"});return}if(W?.("\uD83D\uDDA5️ ➡️ \uD83D\uDC64",k),k.type==="peer-joined"||k.type==="peer-left"){L(k.users);return}if(k.peerId&&k.userId){let{userId:N,peerId:H}=k;v(k.type,k.payload,{userId:N,peerId:H,receive:(K,G)=>F(K,H,G)})}}if(D.addEventListener("message",J),q)D.addEventListener("open",q);if(A)D.addEventListener("close",A);if(Z)D.addEventListener("error",Z);return{exitRoom:()=>{if(h=!0,D.close(),D.removeEventListener("message",J),q)D.removeEventListener("open",q);if(A)D.removeEventListener("close",A);if(Z)D.removeEventListener("error",Z)}}}var $=null,Y=new Map;function Q(T){self.postMessage(T)}self.addEventListener("message",(T)=>{let z=T.data;if(console.debug("[signal-room.worker] received command",z),z.cmd==="enter"){$?.(),$=null,Y.clear(),$=U({userId:z.userId,appId:z.appId,room:z.room,host:z.host,onOpen:()=>Q({kind:"open"}),onClose:()=>Q({kind:"close"}),onError:()=>Q({kind:"error"}),logLine:(B,q)=>{console.debug(`[signal-room.worker] ${B}`,q),Q({kind:"log",direction:B,obj:q})},onPeerJoined:(B)=>{B.forEach(({peerId:q,receive:A})=>Y.set(q,A)),Q({kind:"peer-joined",users:B.map(({userId:q,peerId:A})=>({userId:q,peerId:A}))})},onPeerLeft:(B)=>{B.forEach(({peerId:q})=>Y.delete(q)),Q({kind:"peer-left",users:B})},onMessage:(B,q,A)=>{Y.set(A.peerId,A.receive),Q({kind:"message",type:B,payload:q,fromUserId:A.userId,fromPeerId:A.peerId})}}).exitRoom;return}if(z.cmd==="send"){let V=Y.get(z.toPeerId);if(V)V(z.type,z.payload);return}if(z.cmd==="exit"){$?.(),self.close();return}});
2
2
 
3
- //# debugId=5465A5D210652A5664756E2164756E21
3
+ //# debugId=B4D6FE1CAC5FA63564756E2164756E21
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 peerId: string;\n receive(type: T, payload: P): boolean;\n}\n\n/**\n * enterRoom connects to the signaling room via WebSocket.\n */\nexport function enterRoom<T extends string, P = any>({\n userId,\n appId,\n room,\n host,\n onOpen,\n onClose,\n onError,\n logLine,\n onPeerJoined,\n onPeerLeft,\n onMessage,\n}: {\n userId: string;\n appId: string;\n room: string;\n host: string;\n onOpen?: () => void;\n onClose?: () => void;\n onError?: () => void;\n logLine?: (direction: string, obj?: any) => void;\n onPeerJoined(users: IPeer<T, P>[]) : void;\n onPeerLeft(users: {userId: string, peerId: string}[]) : void;\n onMessage(type: T, payload: P, from: IPeer<T, P>) : void;\n}): { exitRoom: () => void } {\n const wsUrl = `wss://${host}/room/${appId}/${room}?userId=${encodeURIComponent(userId)}`;\n const ws = new WebSocket(wsUrl);\n const selfUserId = userId;\n\n const peers = new Map<string, IPeer<T, P>>();\n let exited = false;\n function send(type: T, toPeerId: string, payload: P) {\n if (exited) return false;\n const obj = { type, to: toPeerId, payload };\n ws.send(JSON.stringify(obj));\n logLine?.(\"👤 ➡️ 🖥️\", obj);\n return true;\n }\n\n function updatePeers(updatedUsers: { peerId: string; userId: string }[]) {\n const joined: IPeer<T,P>[] = [];\n const left: Omit<IPeer<T,P>, \"receive\">[] = [];\n const updatedPeerSet = new Set<string>();\n updatedUsers.forEach(({ userId, peerId }) => {\n if (userId === selfUserId) return;\n if (!peers.has(peerId)) {\n const newPeer = { userId, peerId, receive: (type: T, payload: P) => send(type, peerId, payload)};\n peers.set(peerId, newPeer);\n joined.push(newPeer);\n }\n updatedPeerSet.add(peerId);\n });\n peers.values().forEach(({ peerId, userId }) => {\n if (!updatedPeerSet.has(peerId)) {\n peers.delete(peerId);\n left.push({ peerId, userId });\n }\n });\n if (joined.length) onPeerJoined(joined);\n if (left.length) onPeerLeft(left);\n }\n\n function onmessage(e: MessageEvent) {\n let msg: {\n type: T;\n peerId: string;\n userId: string;\n users: { peerId: string, userId: string }[],\n payload: P;\n };\n try { msg = JSON.parse(e.data); }\n catch {\n logLine?.(\"⚠️ ERROR\", { error: \"invalid-json\" });\n return;\n }\n\n logLine?.(\"🖥️ ➡️ 👤\", msg);\n\n // Existing client greets newcomers\n if (msg.type === \"peer-joined\") {\n updatePeers(msg.users);\n return;\n }\n if (msg.type === \"peer-left\") {\n updatePeers(msg.users);\n return;\n }\n if (msg.peerId && msg.userId) {\n const { userId, peerId } = msg;\n onMessage(msg.type, msg.payload, {\n userId,\n peerId,\n receive: (type: T, payload: P) => send(type, peerId, payload),\n });\n }\n };\n\n ws.addEventListener(\"message\", onmessage);\n if (onOpen) ws.addEventListener(\"open\", onOpen);\n if (onClose) ws.addEventListener(\"close\", onClose);\n if (onError) ws.addEventListener(\"error\", onError);\n return {\n exitRoom: () => {\n exited = true;\n ws.close();\n ws.removeEventListener(\"message\", onmessage);\n if (onOpen) ws.removeEventListener(\"open\", onOpen);\n if (onClose) ws.removeEventListener(\"close\", onClose);\n if (onError) ws.removeEventListener(\"error\", onError);\n },\n };\n}\n",
5
+ "export interface IPeer<T extends string = string, P = any> {\n userId: string;\n peerId: string;\n receive(type: T, payload: P): boolean;\n}\n\n/**\n * enterRoom connects to the signaling room via WebSocket.\n */\nexport function enterRoom<T extends string, P = any>({\n userId,\n appId,\n room,\n host,\n onOpen,\n onClose,\n onError,\n logLine,\n onPeerJoined,\n onPeerLeft,\n onMessage,\n}: {\n userId: string;\n appId: string;\n room: string;\n host: string;\n onOpen?: () => void;\n onClose?: () => void;\n onError?: () => void;\n logLine?: (direction: string, obj?: any) => void;\n onPeerJoined(users: IPeer<T, P>[]) : void;\n onPeerLeft(users: {userId: string, peerId: string}[]) : void;\n onMessage(type: T, payload: P, from: IPeer<T, P>) : void;\n}): { exitRoom: () => void } {\n const wsUrl = `wss://${host}/room/${appId}/${room}?userId=${encodeURIComponent(userId)}`;\n const ws = new WebSocket(wsUrl);\n const selfUserId = userId;\n\n const peers = new Map<string, IPeer<T, P>>();\n let exited = false;\n function send(type: T, toPeerId: string, payload: P) {\n if (exited) return false;\n const obj = { type, to: toPeerId, payload };\n ws.send(JSON.stringify(obj));\n logLine?.(\"👤 ➡️ 🖥️\", obj);\n return true;\n }\n\n function updatePeers(updatedUsers: { peerId: string; userId: string }[]) {\n const joined: IPeer<T,P>[] = [];\n const left: Omit<IPeer<T,P>, \"receive\">[] = [];\n const updatedPeerSet = new Set<string>();\n updatedUsers.forEach(({ userId, peerId }) => {\n if (userId === selfUserId) return;\n if (!peers.has(peerId)) {\n const newPeer = { userId, peerId, receive: (type: T, payload: P) => send(type, peerId, payload)};\n peers.set(peerId, newPeer);\n joined.push(newPeer);\n }\n updatedPeerSet.add(peerId);\n });\n peers.values().forEach(({ peerId, userId }) => {\n if (!updatedPeerSet.has(peerId)) {\n peers.delete(peerId);\n left.push({ peerId, userId });\n }\n });\n if (joined.length) onPeerJoined(joined);\n if (left.length) onPeerLeft(left);\n }\n\n function onmessage(e: MessageEvent) {\n let msg: {\n type: T;\n peerId: string;\n userId: string;\n users: { peerId: string, userId: string }[],\n payload: P;\n };\n try { msg = JSON.parse(e.data); }\n catch {\n logLine?.(\"⚠️ ERROR\", { error: \"invalid-json\" });\n return;\n }\n\n logLine?.(\"🖥️ ➡️ 👤\", msg);\n\n // Existing client greets newcomers\n if (msg.type === \"peer-joined\" || msg.type === \"peer-left\") {\n updatePeers(msg.users);\n return;\n }\n if (msg.peerId && msg.userId) {\n const { userId, peerId } = msg;\n onMessage(msg.type, msg.payload, {\n userId,\n peerId,\n receive: (type: T, payload: P) => send(type, peerId, payload),\n });\n }\n };\n\n ws.addEventListener(\"message\", onmessage);\n if (onOpen) ws.addEventListener(\"open\", onOpen);\n if (onClose) ws.addEventListener(\"close\", onClose);\n if (onError) ws.addEventListener(\"error\", onError);\n return {\n exitRoom: () => {\n exited = true;\n ws.close();\n ws.removeEventListener(\"message\", onmessage);\n if (onOpen) ws.removeEventListener(\"open\", onOpen);\n if (onClose) ws.removeEventListener(\"close\", onClose);\n if (onError) ws.removeEventListener(\"error\", onError);\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\" }\n | { kind: \"error\" }\n | { kind: \"peer-joined\"; users: {userId: string, peerId: string}[] }\n | { kind: \"peer-left\"; users: {userId: string, peerId: string}[] }\n | { kind: \"message\"; type: T; payload: P; fromUserId: string; fromPeerId: string }\n | { kind: \"log\"; direction: string; obj?: any };\n\ntype WorkerCommand<T extends string = string, P = any> =\n | { cmd: \"enter\"; userId: string; appId: string; room: string; host: string }\n | { cmd: \"exit\" }\n | { cmd: \"send\"; toPeerId: string; type: T; payload: P };\n\nlet exitRoom: (() => void) | null = null;\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 appId: msg.appId,\n room: msg.room,\n host: msg.host,\n onOpen: () => emit({ kind: \"open\" }),\n onClose: () => emit({ kind: \"close\" }),\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(({ peerId, receive }) => peerSend.set(peerId, receive));\n emit({ kind: \"peer-joined\", users: users.map(({ userId, peerId }) => ({ userId, peerId })) });\n },\n onPeerLeft: (users: {userId: string, peerId: string}[]) => {\n users.forEach(({ peerId }) => peerSend.delete(peerId));\n emit({ kind: \"peer-left\", users });\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(from.peerId, from.receive);\n emit({ kind: \"message\", type, payload, fromUserId: from.userId, fromPeerId: from.peerId });\n },\n });\n\n exitRoom = result.exitRoom;\n return;\n }\n\n if (msg.cmd === \"send\") {\n const sendFn = peerSend.get(msg.toPeerId);\n if (sendFn) sendFn(msg.type, msg.payload);\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": "5465A5D210652A5664756E2164756E21",
8
+ "mappings": "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",
9
+ "debugId": "B4D6FE1CAC5FA63564756E2164756E21",
10
10
  "names": []
11
11
  }
@@ -1,7 +1,6 @@
1
1
  import { EnterRoom } from "./signal-room";
2
2
  export type SigType = "offer" | "answer" | "ice";
3
3
  export type SigPayload = RTCSessionDescriptionInit | RTCIceCandidateInit;
4
- type UserListener = (user: string, action: "join" | "leave", users: string[]) => void;
5
4
  export declare function collectPeerConnections({ userId, appId, receivePeerConnection, peerlessUserExpiration, rtcConfig, enterRoomFunction: enterRoom, logLine, onLeaveUser, workerUrl, }: {
6
5
  userId: string;
7
6
  appId: string;
@@ -26,9 +25,6 @@ export declare function collectPeerConnections({ userId, appId, receivePeerConne
26
25
  host: string;
27
26
  }) => void;
28
27
  leaveUser: (userId: string) => void;
29
- getUsers: () => string[];
30
- addUserListener: (listener: UserListener) => () => void;
31
- removeUserListener: (listener: UserListener) => void;
32
28
  getRooms(): {
33
29
  room: string;
34
30
  host: string;
@@ -36,5 +32,4 @@ export declare function collectPeerConnections({ userId, appId, receivePeerConne
36
32
  }[];
37
33
  end(): void;
38
34
  };
39
- export {};
40
35
  //# sourceMappingURL=webrtc-peer-collector.d.ts.map
@@ -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,CAAC;AACjD,MAAM,MAAM,UAAU,GAAG,yBAAyB,GAAG,mBAAmB,CAAC;AAczE,KAAK,YAAY,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAC,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;AAKpF,wBAAgB,sBAAsB,CAAC,EACrC,MAAM,EACN,KAAK,EACL,qBAAqB,EACrB,sBAAsB,EACtB,SAAsE,EACtE,iBAAiB,EAAE,SAA8B,EACjD,OAAuB,EACvB,WAAW,EACX,SAAS,GACV,EAAE;IACD,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,gBAAgB,CAAC;IAC7B,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;QAAE,EAAE,EAAE,iBAAiB,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,OAAO,CAAA;KAAE,GAAG,IAAI,CAAC;CACxG;gCAgFgC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;KAAE;+BAThC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;KAAE;wBA3BlC,MAAM;;gCAqIE,YAAY;mCAJT,YAAY;;cAxGL,MAAM;cAAQ,MAAM;kBAAY,MAAM,IAAI;;;EAoIxF"}
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,CAAC;AACjD,MAAM,MAAM,UAAU,GAAG,yBAAyB,GAAG,mBAAmB,CAAC;AAmBzE,wBAAgB,sBAAsB,CAAC,EACrC,MAAM,EACN,KAAK,EACL,qBAAqB,EACrB,sBAAsB,EACtB,SAAsE,EACtE,iBAAiB,EAAE,SAA8B,EACjD,OAAuB,EACvB,WAAW,EACX,SAAS,GACV,EAAE;IACD,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,gBAAgB,CAAC;IAC7B,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;QAAE,EAAE,EAAE,iBAAiB,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,OAAO,CAAA;KAAE,GAAG,IAAI,CAAC;CACxG;gCAyEgC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;KAAE;+BAThC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;KAAE;wBAzBlC,MAAM;;cAuBY,MAAM;cAAQ,MAAM;kBAAY,MAAM,IAAI;;;EAqHxF"}
@@ -0,0 +1,4 @@
1
+ function h({userId:J,appId:x,room:M,host:R,onOpen:D,onClose:W,onError:A,logLine:_,onPeerJoined:O,onPeerLeft:$,onMessage:S}){let b=`wss://${R}/room/${x}/${M}?userId=${encodeURIComponent(J)}`,H=new WebSocket(b),F=J,T=new Map,j=!1;function B(Y,K,N){if(j)return!1;let X={type:Y,to:K,payload:N};return H.send(JSON.stringify(X)),_?.("\uD83D\uDC64 ➡️ \uD83D\uDDA5️",X),!0}function z(Y){let K=[],N=[],X=new Set;if(Y.forEach(({userId:Q,peerId:V})=>{if(Q===F)return;if(!T.has(V)){let Z={userId:Q,peerId:V,receive:(q,C)=>B(q,V,C)};T.set(V,Z),K.push(Z)}X.add(V)}),T.values().forEach(({peerId:Q,userId:V})=>{if(!X.has(Q))T.delete(Q),N.push({peerId:Q,userId:V})}),K.length)O(K);if(N.length)$(N)}function G(Y){let K;try{K=JSON.parse(Y.data)}catch{_?.("⚠️ ERROR",{error:"invalid-json"});return}if(_?.("\uD83D\uDDA5️ ➡️ \uD83D\uDC64",K),K.type==="peer-joined"||K.type==="peer-left"){z(K.users);return}if(K.peerId&&K.userId){let{userId:N,peerId:X}=K;S(K.type,K.payload,{userId:N,peerId:X,receive:(Q,V)=>B(Q,X,V)})}}if(H.addEventListener("message",G),D)H.addEventListener("open",D);if(W)H.addEventListener("close",W);if(A)H.addEventListener("error",A);return{exitRoom:()=>{if(j=!0,H.close(),H.removeEventListener("message",G),D)H.removeEventListener("open",D);if(W)H.removeEventListener("close",W);if(A)H.removeEventListener("error",A)}}}function k({userId:J,appId:x,room:M,host:R,onOpen:D,onClose:W,onError:A,onPeerJoined:_,onPeerLeft:O,onMessage:$,logLine:S,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"),h({userId:J,appId:x,room:M,host:R,onOpen:D,onClose:W,onError:A,onPeerJoined:_,onPeerLeft:O,onMessage:$});let H=new Worker(b,{type:"module"}),F=!1;function T({userId:B,peerId:z}){return{userId:B,peerId:z,receive:(G,Y)=>{if(F)return!1;return H.postMessage({cmd:"send",toPeerId:z,type:G,payload:Y}),!0}}}let j=(B)=>{let z=B.data;if(z.kind==="open")D?.();else if(z.kind==="close")H.terminate();else if(z.kind==="error")A?.();else if(z.kind==="peer-joined")_(z.users.map((G)=>T({userId:G.userId,peerId:G.peerId})));else if(z.kind==="peer-left")O(z.users);else if(z.kind==="message")$(z.type,z.payload,T({userId:z.fromUserId,peerId:z.fromPeerId}));else if(z.kind==="log")S?.(z.direction,z.obj)};return H.addEventListener("message",j),H.postMessage({cmd:"enter",userId:J,appId:x,room:M,host:R}),{exitRoom:()=>{F=!0,H.removeEventListener("message",j),H.postMessage({cmd:"exit"})}}}var U=k;function v({userId:J,appId:x,receivePeerConnection:M,peerlessUserExpiration:R,rtcConfig:D={iceServers:[{urls:"stun:stun.l.google.com:19302"}]},enterRoomFunction:W=U,logLine:A=console.debug,onLeaveUser:_,workerUrl:O}){let $=new Map;function S(B){let z=$.get(B.userId);if(!z){let G={userId:B.userId,pc:new RTCPeerConnection(D),pendingRemoteIce:[],peers:new Map};G.peers.set(B.peerId,B),$.set(B.userId,G),G.pc.onicecandidate=(Y)=>{if(!Y.candidate)return;for(let K of G.peers.values())if(K.receive("ice",Y.candidate.toJSON()))break},G.pc.onconnectionstatechange=()=>{A("\uD83D\uDCAC",{event:"pc-state",userId:G.userId,state:G.pc.connectionState})},z=G,$.set(z.userId,z)}else if(z)clearTimeout(z.expirationTimeout),z.expirationTimeout=0,z.peers.set(B.peerId,B);return z}function b(B){_?.(B);let z=$.get(B);if(!z)return;try{z.pc.close()}catch{}$.delete(B)}async function H(B){if(!B.pc.remoteDescription)return;let z=B.pendingRemoteIce;B.pendingRemoteIce=[];for(let G of z)try{await B.pc.addIceCandidate(G)}catch(Y){A("⚠️ ERROR",{error:"add-ice-failed",userId:B.userId,detail:String(Y)})}}let F=new Map;function T({room:B,host:z}){let G=`${z}/room/${B}`,Y=F.get(G);if(Y)Y.exitRoom(),F.delete(G)}function j({room:B,host:z}){return new Promise((G,Y)=>{async function K(X){let V=S(X).pc,Z=await V.createOffer();await V.setLocalDescription(Z),X.receive("offer",V.localDescription?.toJSON())}let{exitRoom:N}=W({userId:J,appId:x,room:B,host:z,logLine:A,workerUrl:O,onOpen:G,onError:Y,onPeerJoined(X){X.forEach((Q)=>{let Z=S(Q).pc;M({pc:Z,userId:Q.userId,initiator:!0}),K(Q)})},onPeerLeft(X){X.forEach(({userId:Q,peerId:V})=>{let Z=$.get(Q);if(!Z)return;if(Z.peers.delete(V),!Z.peers.size)Z.expirationTimeout=setTimeout(()=>b(Q),R??0)})},async onMessage(X,Q,V){let Z=S(V),q=Z.pc;if(X==="offer"){M({pc:q,userId:V.userId,initiator:!1}),await q.setRemoteDescription(Q);let C=await q.createAnswer();await q.setLocalDescription(C),V.receive("answer",q.localDescription?.toJSON()),await H(Z);return}if(X==="answer"){await q.setRemoteDescription(Q),await H(Z);return}if(X==="ice"){let C=Q;if(!q.remoteDescription){Z.pendingRemoteIce.push(C);return}try{await q.addIceCandidate(C)}catch(E){A("⚠️ ERROR",{error:"add-ice-failed",userId:Z.userId,detail:String(E)})}return}}});F.set(`${z}/room/${B}`,{exitRoom:N,room:B,host:z})})}return{enterRoom:j,exitRoom:T,leaveUser:b,getRooms(){return Array.from(F.values())},end(){F.forEach(({exitRoom:B})=>B()),F.clear(),$.forEach(({userId:B})=>b(B)),$.clear()}}}export{v as collectPeerConnections};
2
+
3
+ //# debugId=3EAF43CDF7956DDF64756E2164756E21
4
+ //# sourceMappingURL=webrtc-peer-collector.js.map
@@ -0,0 +1,12 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/browser/impl/signal-room.ts", "../src/browser/signal-room.ts", "../src/browser/webrtc-peer-collector.ts"],
4
+ "sourcesContent": [
5
+ "export interface IPeer<T extends string = string, P = any> {\n userId: string;\n peerId: string;\n receive(type: T, payload: P): boolean;\n}\n\n/**\n * enterRoom connects to the signaling room via WebSocket.\n */\nexport function enterRoom<T extends string, P = any>({\n userId,\n appId,\n room,\n host,\n onOpen,\n onClose,\n onError,\n logLine,\n onPeerJoined,\n onPeerLeft,\n onMessage,\n}: {\n userId: string;\n appId: string;\n room: string;\n host: string;\n onOpen?: () => void;\n onClose?: () => void;\n onError?: () => void;\n logLine?: (direction: string, obj?: any) => void;\n onPeerJoined(users: IPeer<T, P>[]) : void;\n onPeerLeft(users: {userId: string, peerId: string}[]) : void;\n onMessage(type: T, payload: P, from: IPeer<T, P>) : void;\n}): { exitRoom: () => void } {\n const wsUrl = `wss://${host}/room/${appId}/${room}?userId=${encodeURIComponent(userId)}`;\n const ws = new WebSocket(wsUrl);\n const selfUserId = userId;\n\n const peers = new Map<string, IPeer<T, P>>();\n let exited = false;\n function send(type: T, toPeerId: string, payload: P) {\n if (exited) return false;\n const obj = { type, to: toPeerId, payload };\n ws.send(JSON.stringify(obj));\n logLine?.(\"👤 ➡️ 🖥️\", obj);\n return true;\n }\n\n function updatePeers(updatedUsers: { peerId: string; userId: string }[]) {\n const joined: IPeer<T,P>[] = [];\n const left: Omit<IPeer<T,P>, \"receive\">[] = [];\n const updatedPeerSet = new Set<string>();\n updatedUsers.forEach(({ userId, peerId }) => {\n if (userId === selfUserId) return;\n if (!peers.has(peerId)) {\n const newPeer = { userId, peerId, receive: (type: T, payload: P) => send(type, peerId, payload)};\n peers.set(peerId, newPeer);\n joined.push(newPeer);\n }\n updatedPeerSet.add(peerId);\n });\n peers.values().forEach(({ peerId, userId }) => {\n if (!updatedPeerSet.has(peerId)) {\n peers.delete(peerId);\n left.push({ peerId, userId });\n }\n });\n if (joined.length) onPeerJoined(joined);\n if (left.length) onPeerLeft(left);\n }\n\n function onmessage(e: MessageEvent) {\n let msg: {\n type: T;\n peerId: string;\n userId: string;\n users: { peerId: string, userId: string }[],\n payload: P;\n };\n try { msg = JSON.parse(e.data); }\n catch {\n logLine?.(\"⚠️ ERROR\", { error: \"invalid-json\" });\n return;\n }\n\n logLine?.(\"🖥️ ➡️ 👤\", msg);\n\n // Existing client greets newcomers\n if (msg.type === \"peer-joined\" || msg.type === \"peer-left\") {\n updatePeers(msg.users);\n return;\n }\n if (msg.peerId && msg.userId) {\n const { userId, peerId } = msg;\n onMessage(msg.type, msg.payload, {\n userId,\n peerId,\n receive: (type: T, payload: P) => send(type, peerId, payload),\n });\n }\n };\n\n ws.addEventListener(\"message\", onmessage);\n if (onOpen) ws.addEventListener(\"open\", onOpen);\n if (onClose) ws.addEventListener(\"close\", onClose);\n if (onError) ws.addEventListener(\"error\", onError);\n return {\n exitRoom: () => {\n exited = true;\n ws.close();\n ws.removeEventListener(\"message\", onmessage);\n if (onOpen) ws.removeEventListener(\"open\", onOpen);\n if (onClose) ws.removeEventListener(\"close\", onClose);\n if (onError) ws.removeEventListener(\"error\", onError);\n },\n };\n}\n",
6
+ "import type { IPeer } from \"./impl/signal-room.js\";\nimport { enterRoom as baseEnterRoom } from \"./impl/signal-room.js\";\nimport { RoomEvent } from \"./signal-room.worker.js\";\n\nexport function enterRoom<T extends string, P = any>({\n userId,\n appId,\n room,\n host,\n onOpen,\n onClose,\n onError,\n onPeerJoined,\n onPeerLeft,\n onMessage,\n logLine,\n workerUrl,\n}: {\n userId: string;\n appId: string;\n room: string;\n host: string;\n onOpen?: () => void;\n onClose?: () => void;\n onError?: () => void;\n onPeerJoined: (users: IPeer<T, P>[]) => void;\n onPeerLeft: (users: {userId: string, peerId: string}[]) => 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}): { exitRoom: () => void } {\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(\"Warning: enterRoom called without workerUrl; this may cause issues in some environments. You should pass workerUrl explicitly. Use:\", CDN_WORKER_URL);\n return baseEnterRoom<T, P>({\n userId,\n appId,\n room,\n host,\n onOpen,\n onClose,\n onError,\n onPeerJoined,\n onPeerLeft,\n onMessage,\n });\n }\n const worker = new Worker(workerUrl, { type: \"module\" });\n let exited = false;\n\n function makeUser({ userId, peerId }: { userId: string; peerId: string }): IPeer<T, P> {\n return {\n userId,\n peerId,\n receive: (type: T, payload: P) => {\n if (exited) return false;\n worker.postMessage({ cmd: \"send\", toPeerId: peerId, type, payload });\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\") worker.terminate();\n else if (ev.kind === \"error\") onError?.();\n else if (ev.kind === \"peer-joined\") onPeerJoined(ev.users.map(ev => makeUser({ userId: ev.userId, peerId: ev.peerId })));\n else if (ev.kind === \"peer-left\") onPeerLeft(ev.users);\n else if (ev.kind === \"message\") onMessage(ev.type, ev.payload, makeUser({ userId: ev.fromUserId, peerId: ev.fromPeerId }));\n else if (ev.kind === \"log\") logLine?.(ev.direction, ev.obj);\n };\n\n worker.addEventListener(\"message\", onWorkerMessage);\n\n worker.postMessage({ cmd: \"enter\", userId, appId, room, host });\n\n return {\n exitRoom: () => {\n exited = true;\n worker.removeEventListener(\"message\", onWorkerMessage);\n worker.postMessage({ cmd: \"exit\" });\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\";\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 peers: Map<string, IPeer<SigType, SigPayload>>;\n\n expirationTimeout?: number;\n};\ntype UserListener = (user: string, action: \"join\"|\"leave\") => void;\n\nconst DEFAULT_ENTER_ROOM = enterRoom;\n\n\nexport function collectPeerConnections({\n userId,\n appId,\n receivePeerConnection,\n peerlessUserExpiration,\n rtcConfig = { iceServers: [{ urls: \"stun:stun.l.google.com:19302\" }] },\n enterRoomFunction: enterRoom = DEFAULT_ENTER_ROOM,\n logLine = console.debug,\n onLeaveUser,\n workerUrl,\n}: {\n userId: string;\n appId: string;\n rtcConfig?: 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: { pc: RTCPeerConnection, userId: string, initiator: boolean }): void;\n}) {\n const users: Map<string, UserState> = new Map();\n\n function getPeer(peer: IPeer<SigType, SigPayload>): UserState {\n let state = users.get(peer.userId);\n if (!state) {\n const newState: UserState = {\n userId: peer.userId,\n pc: new RTCPeerConnection(rtcConfig),\n pendingRemoteIce: [],\n peers: new Map(),\n };\n newState.peers.set(peer.peerId, peer);\n users.set(peer.userId, newState);\n\n // Send local ICE candidates to this peer\n newState.pc.onicecandidate = (ev) => {\n if (!ev.candidate) return;\n for(let user of newState.peers.values()) {\n const success = user.receive(\"ice\", ev.candidate.toJSON());\n if (success) break;\n }\n };\n \n newState.pc.onconnectionstatechange = () => {\n logLine(\"💬\", { event: \"pc-state\", userId: newState.userId, state: newState.pc.connectionState });\n };\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 state.peers.set(peer.peerId, peer);\n }\n return state;\n }\n\n function leaveUser(userId: string) {\n onLeaveUser?.(userId);\n const p = users.get(userId);\n if (!p) return;\n try { p.pc.close(); } 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\", { error: \"add-ice-failed\", userId: state.userId, detail: String(e) });\n }\n }\n }\n\n const roomsEntered = new Map<string, { room: string; host: string; exitRoom: () => void }>();\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>((resolve, reject) => {\n async function makeOffer(user: IPeer) {\n // Offer flow: createOffer -> setLocalDescription -> send localDescription\n const state = 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 const { exitRoom } = enterRoom({\n userId,\n appId,\n room,\n host,\n logLine,\n workerUrl,\n\n onOpen: resolve,\n onError: reject,\n\n // Existing peers initiate to the newcomer (Option 1)\n onPeerJoined(joiningUsers: IPeer<SigType, SigPayload>[]) {\n joiningUsers.forEach(user => {\n const state = getPeer(user);\n const pc = state.pc;\n receivePeerConnection({ pc, userId: user.userId, initiator: true });\n makeOffer(user);\n });\n },\n\n onPeerLeft(leavingUsers: { userId: string; peerId: string }[]) {\n leavingUsers.forEach(({ userId, peerId }) => {\n const state = users.get(userId);\n if (!state) return;\n state.peers.delete(peerId);\n if (!state.peers.size) {\n state.expirationTimeout = setTimeout(() => leaveUser(userId), peerlessUserExpiration ?? 0);\n }\n });\n },\n\n async onMessage(type: SigType, payload: any, from: IPeer) {\n const state = getPeer(from);\n const pc = state.pc;\n\n if (type === \"offer\") {\n receivePeerConnection({ pc, userId: from.userId, initiator: false });\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\", { error: \"add-ice-failed\", userId: state.userId, detail: String(e) });\n }\n return;\n }\n },\n });\n roomsEntered.set(`${host}/room/${room}`, { exitRoom, room, host });\n });\n }\n\n return {\n enterRoom: enter,\n exitRoom: exit,\n leaveUser,\n getRooms() {\n return Array.from(roomsEntered.values());\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\n\n"
8
+ ],
9
+ "mappings": "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",
10
+ "debugId": "3EAF43CDF7956DDF64756E2164756E21",
11
+ "names": []
12
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dobuki/hello-worker",
3
- "version": "1.0.18",
3
+ "version": "1.0.21",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -16,11 +16,10 @@
16
16
  "scripts": {
17
17
  "dev": "wrangler dev src/index.ts --config wrangler.dev.toml",
18
18
  "deploy": "wrangler deploy",
19
- "build:worker": "bun build src/browser/signal-room.worker.ts --target browser --format esm --outdir dist --minify --sourcemap",
20
19
  "build:sample": "bun build src/browser/sample/index.ts src/browser/signal-room.worker.ts --target browser --format esm --outdir public --minify --sourcemap",
21
20
  "build:types": "tsc -p tsconfig.json",
22
- "build:main": "bun build src/browser --target browser --format esm --outdir dist --minify --sourcemap",
23
- "build": "bun i && bun run build:worker && bun run build:main && bun run build:sample && bun run build:types",
21
+ "build:main": "bun build src/browser/*.* --target browser --format esm --outdir dist --minify --sourcemap",
22
+ "build": "bun i && bun run build:main && bun run build:sample && bun run build:types",
24
23
  "prepublishOnly": "bun run build",
25
24
  "npm-patch": "npm version patch && npm publish"
26
25
  },
@@ -1,4 +0,0 @@
1
- function k({userId:M,appId:h,room:G,host:j,onOpen:N,onClose:S,onError:B,logLine:f,onPeerJoined:x,onPeerLeft:F,onMessage:H}){let W=`wss://${j}/room/${h}/${G}?userId=${encodeURIComponent(M)}`,$=new WebSocket(W),C=M,X=new Map,K=!1;function q(Z,c,D){if(K)return!1;let T={type:Z,to:c,payload:D};return $.send(JSON.stringify(T)),f?.("\uD83D\uDC64 ➡️ \uD83D\uDDA5️",T),!0}function R(Z){let c=[],D=[],T=new Set;if(Z.forEach(({userId:b,peerId:z})=>{if(b===C)return;if(!X.has(z)){let O={userId:b,peerId:z,receive:(A,V)=>q(A,z,V)};X.set(z,O),c.push(O)}T.add(z)}),X.values().forEach(({peerId:b,userId:z})=>{if(!T.has(b))X.delete(b),D.push({peerId:b,userId:z})}),c.length)x(c);if(D.length)F(D)}function Y(Z){let c;try{c=JSON.parse(Z.data)}catch{f?.("⚠️ ERROR",{error:"invalid-json"});return}if(f?.("\uD83D\uDDA5️ ➡️ \uD83D\uDC64",c),c.type==="peer-joined"){R(c.users);return}if(c.type==="peer-left"){R(c.users);return}if(c.peerId&&c.userId){let{userId:D,peerId:T}=c;H(c.type,c.payload,{userId:D,peerId:T,receive:(b,z)=>q(b,T,z)})}}if($.addEventListener("message",Y),N)$.addEventListener("open",N);if(S)$.addEventListener("close",S);if(B)$.addEventListener("error",B);return{exitRoom:()=>{if(K=!0,$.close(),$.removeEventListener("message",Y),N)$.removeEventListener("open",N);if(S)$.removeEventListener("close",S);if(B)$.removeEventListener("error",B)}}}function E({userId:M,appId:h,room:G,host:j,onOpen:N,onClose:S,onError:B,onPeerJoined:f,onPeerLeft:x,onMessage:F,logLine:H,workerUrl:W}){if(!W)return console.warn("Warning: enterRoom called without workerUrl; this may cause issues in some environments. You should pass workerUrl explicitly. Use:","https://cdn.jsdelivr.net/npm/@dobuki/hello-worker/dist/signal-room.worker.min.js"),k({userId:M,appId:h,room:G,host:j,onOpen:N,onClose:S,onError:B,onPeerJoined:f,onPeerLeft:x,onMessage:F});let $=new Worker(W,{type:"module"}),C=!1;function X({userId:q,peerId:R}){return{userId:q,peerId:R,receive:(Y,Z)=>{if(C)return!1;return $.postMessage({cmd:"send",toPeerId:R,type:Y,payload:Z}),!0}}}let K=(q)=>{let R=q.data;if(R.kind==="open")N?.();else if(R.kind==="close")$.terminate();else if(R.kind==="error")B?.();else if(R.kind==="peer-joined")f(R.users.map((Y)=>X({userId:Y.userId,peerId:Y.peerId})));else if(R.kind==="peer-left")x(R.users);else if(R.kind==="message")F(R.type,R.payload,X({userId:R.fromUserId,peerId:R.fromPeerId}));else if(R.kind==="log")H?.(R.direction,R.obj)};return $.addEventListener("message",K),$.postMessage({cmd:"enter",userId:M,appId:h,room:G,host:j}),{exitRoom:()=>{C=!0,$.removeEventListener("message",K),$.postMessage({cmd:"exit"})}}}var v=E;function U({userId:M,appId:h,receivePeerConnection:G,peerlessUserExpiration:j,rtcConfig:N={iceServers:[{urls:"stun:stun.l.google.com:19302"}]},enterRoomFunction:S=v,logLine:B=console.debug,onLeaveUser:f,workerUrl:x}){let F=new Map;function H(){return[...F.keys()]}let W=new Set;function $(c){let D=F.get(c.userId);if(!D){let T={userId:c.userId,pc:new RTCPeerConnection(N),pendingRemoteIce:[],peers:new Map};T.peers.set(c.peerId,c),F.set(c.userId,T),T.pc.onicecandidate=(b)=>{if(!b.candidate)return;for(let z of T.peers.values())if(z.receive("ice",b.candidate.toJSON()))break},T.pc.onconnectionstatechange=()=>{B("\uD83D\uDCAC",{event:"pc-state",userId:T.userId,state:T.pc.connectionState})},D=T,W.forEach((b)=>b(c.userId,"join",H())),F.set(D.userId,D)}else if(D)clearTimeout(D.expirationTimeout),D.expirationTimeout=0,D.peers.set(c.peerId,c);return D}function C(c){f?.(c);let D=F.get(c);if(!D)return;try{D.pc.close()}catch{}F.delete(c),W.forEach((T)=>T(c,"leave",H())),B("\uD83D\uDC64 USER LEFT",c)}async function X(c){if(!c.pc.remoteDescription)return;let D=c.pendingRemoteIce;c.pendingRemoteIce=[];for(let T of D)try{await c.pc.addIceCandidate(T)}catch(b){B("⚠️ ERROR",{error:"add-ice-failed",userId:c.userId,detail:String(b)})}}let K=new Map;function q({room:c,host:D}){let T=`${D}/room/${c}`,b=K.get(T);if(b)b.exitRoom(),K.delete(T)}function R({room:c,host:D}){return new Promise((T,b)=>{async function z(A){let _=$(A).pc,Q=await _.createOffer();await _.setLocalDescription(Q),A.receive("offer",_.localDescription?.toJSON())}let{exitRoom:O}=S({userId:M,appId:h,room:c,host:D,logLine:B,workerUrl:x,onOpen:T,onError:b,onPeerJoined(A){A.forEach((V)=>{let Q=$(V).pc;G({pc:Q,userId:V.userId,initiator:!0}),z(V)})},onPeerLeft(A){A.forEach(({userId:V,peerId:_})=>{let Q=F.get(V);if(!Q)return;if(Q.peers.delete(_),Q.peers.size===0)Q.expirationTimeout=setTimeout(()=>C(V),j??0)})},async onMessage(A,V,_){let Q=$(_),J=Q.pc;if(A==="offer"){G({pc:J,userId:_.userId,initiator:!1}),await J.setRemoteDescription(V);let y=await J.createAnswer();await J.setLocalDescription(y),_.receive("answer",J.localDescription?.toJSON()),await X(Q);return}if(A==="answer"){await J.setRemoteDescription(V),await X(Q),G({pc:J,userId:_.userId,initiator:!0});return}if(A==="ice"){let y=V;if(!J.remoteDescription){Q.pendingRemoteIce.push(y);return}try{await J.addIceCandidate(y)}catch(P){B("⚠️ ERROR",{error:"add-ice-failed",userId:Q.userId,detail:String(P)})}return}}});K.set(`${D}/room/${c}`,{exitRoom:O,room:c,host:D})})}function Y(c){W.delete(c)}function Z(c){return W.add(c),()=>{Y(c)}}return{enterRoom:R,exitRoom:q,leaveUser:C,getUsers:H,addUserListener:Z,removeUserListener:Y,getRooms(){return Array.from(K.values())},end(){K.forEach(({exitRoom:c})=>c()),K.clear(),F.forEach(({userId:c})=>C(c)),W.clear()}}}function L({uid:M,appId:h,logLine:G=console.debug,enterRoomFunction:j=E,peerlessUserExpiration:N,workerUrl:S}){let B=M??`user-${crypto.randomUUID()}`,f={iceServers:[{urls:"stun:stun.l.google.com:19302"}]},x=new Set;function F(D,T){T.onopen=()=>G("\uD83D\uDCAC",{event:"dc-open",userId:D}),T.onmessage=({data:b})=>{x.forEach((z)=>z(b,D)),G("\uD83D\uDCAC",{event:"dc-message",userId:D,data:b})},T.onclose=()=>G("\uD83D\uDCAC",{event:"dc-close",userId:D}),T.onerror=()=>G("⚠️ ERROR",{error:"dc-error",userId:D})}let H=new Map,{enterRoom:W,exitRoom:$,getUsers:C,leaveUser:X,addUserListener:K,removeUserListener:q,end:R}=U({userId:B,appId:h,rtcConfig:f,enterRoomFunction:j,logLine:G,workerUrl:S,peerlessUserExpiration:N,onLeaveUser(D){let T=H.get(D);try{T?.close()}catch{}H.delete(D)},receivePeerConnection({pc:D,userId:T,initiator:b}){if(b){let z=D.createDataChannel("data");F(T,z),H.set(T,z)}else D.ondatachannel=(z)=>{let O=z.channel;F(T,O),H.set(T,O)};G("\uD83D\uDCAC",{event:"pc-ready",userId:T,initiator:b})}});function Y(D,T){H.forEach((b,z)=>{if(T&&z!==T)return;if(b.readyState==="open")b.send(D)})}function Z(D){x.delete(D)}function c(D){return x.add(D),()=>{Z(D)}}return{userId:B,send:Y,enterRoom:W,exitRoom:$,leaveUser:X,getUsers:C,addMessageListener:c,removeMessageListener:Z,addUserListener:K,removeUserListener:q,end(){H.forEach((D)=>{try{D.close()}catch{}}),H.clear(),R()}}}export{L as enterWorld,k as enterRoom,U as collectPeerConnections};
2
-
3
- //# debugId=7979C2C0F3726FCA64756E2164756E21
4
- //# sourceMappingURL=index.js.map
@@ -1,13 +0,0 @@
1
- {
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"],
4
- "sourcesContent": [
5
- "export interface IPeer<T extends string = string, P = any> {\n userId: string;\n peerId: string;\n receive(type: T, payload: P): boolean;\n}\n\n/**\n * enterRoom connects to the signaling room via WebSocket.\n */\nexport function enterRoom<T extends string, P = any>({\n userId,\n appId,\n room,\n host,\n onOpen,\n onClose,\n onError,\n logLine,\n onPeerJoined,\n onPeerLeft,\n onMessage,\n}: {\n userId: string;\n appId: string;\n room: string;\n host: string;\n onOpen?: () => void;\n onClose?: () => void;\n onError?: () => void;\n logLine?: (direction: string, obj?: any) => void;\n onPeerJoined(users: IPeer<T, P>[]) : void;\n onPeerLeft(users: {userId: string, peerId: string}[]) : void;\n onMessage(type: T, payload: P, from: IPeer<T, P>) : void;\n}): { exitRoom: () => void } {\n const wsUrl = `wss://${host}/room/${appId}/${room}?userId=${encodeURIComponent(userId)}`;\n const ws = new WebSocket(wsUrl);\n const selfUserId = userId;\n\n const peers = new Map<string, IPeer<T, P>>();\n let exited = false;\n function send(type: T, toPeerId: string, payload: P) {\n if (exited) return false;\n const obj = { type, to: toPeerId, payload };\n ws.send(JSON.stringify(obj));\n logLine?.(\"👤 ➡️ 🖥️\", obj);\n return true;\n }\n\n function updatePeers(updatedUsers: { peerId: string; userId: string }[]) {\n const joined: IPeer<T,P>[] = [];\n const left: Omit<IPeer<T,P>, \"receive\">[] = [];\n const updatedPeerSet = new Set<string>();\n updatedUsers.forEach(({ userId, peerId }) => {\n if (userId === selfUserId) return;\n if (!peers.has(peerId)) {\n const newPeer = { userId, peerId, receive: (type: T, payload: P) => send(type, peerId, payload)};\n peers.set(peerId, newPeer);\n joined.push(newPeer);\n }\n updatedPeerSet.add(peerId);\n });\n peers.values().forEach(({ peerId, userId }) => {\n if (!updatedPeerSet.has(peerId)) {\n peers.delete(peerId);\n left.push({ peerId, userId });\n }\n });\n if (joined.length) onPeerJoined(joined);\n if (left.length) onPeerLeft(left);\n }\n\n function onmessage(e: MessageEvent) {\n let msg: {\n type: T;\n peerId: string;\n userId: string;\n users: { peerId: string, userId: string }[],\n payload: P;\n };\n try { msg = JSON.parse(e.data); }\n catch {\n logLine?.(\"⚠️ ERROR\", { error: \"invalid-json\" });\n return;\n }\n\n logLine?.(\"🖥️ ➡️ 👤\", msg);\n\n // Existing client greets newcomers\n if (msg.type === \"peer-joined\") {\n updatePeers(msg.users);\n return;\n }\n if (msg.type === \"peer-left\") {\n updatePeers(msg.users);\n return;\n }\n if (msg.peerId && msg.userId) {\n const { userId, peerId } = msg;\n onMessage(msg.type, msg.payload, {\n userId,\n peerId,\n receive: (type: T, payload: P) => send(type, peerId, payload),\n });\n }\n };\n\n ws.addEventListener(\"message\", onmessage);\n if (onOpen) ws.addEventListener(\"open\", onOpen);\n if (onClose) ws.addEventListener(\"close\", onClose);\n if (onError) ws.addEventListener(\"error\", onError);\n return {\n exitRoom: () => {\n exited = true;\n ws.close();\n ws.removeEventListener(\"message\", onmessage);\n if (onOpen) ws.removeEventListener(\"open\", onOpen);\n if (onClose) ws.removeEventListener(\"close\", onClose);\n if (onError) ws.removeEventListener(\"error\", onError);\n },\n };\n}\n",
6
- "import type { IPeer } from \"./impl/signal-room.js\";\nimport { enterRoom as baseEnterRoom } from \"./impl/signal-room.js\";\nimport { RoomEvent } from \"./signal-room.worker.js\";\n\nexport function enterRoom<T extends string, P = any>({\n userId,\n appId,\n room,\n host,\n onOpen,\n onClose,\n onError,\n onPeerJoined,\n onPeerLeft,\n onMessage,\n logLine,\n workerUrl,\n}: {\n userId: string;\n appId: string;\n room: string;\n host: string;\n onOpen?: () => void;\n onClose?: () => void;\n onError?: () => void;\n onPeerJoined: (users: IPeer<T, P>[]) => void;\n onPeerLeft: (users: {userId: string, peerId: string}[]) => 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}): { exitRoom: () => void } {\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(\"Warning: enterRoom called without workerUrl; this may cause issues in some environments. You should pass workerUrl explicitly. Use:\", CDN_WORKER_URL);\n return baseEnterRoom<T, P>({\n userId,\n appId,\n room,\n host,\n onOpen,\n onClose,\n onError,\n onPeerJoined,\n onPeerLeft,\n onMessage,\n });\n }\n const worker = new Worker(workerUrl, { type: \"module\" });\n let exited = false;\n\n function makeUser({ userId, peerId }: { userId: string; peerId: string }): IPeer<T, P> {\n return {\n userId,\n peerId,\n receive: (type: T, payload: P) => {\n if (exited) return false;\n worker.postMessage({ cmd: \"send\", toPeerId: peerId, type, payload });\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\") worker.terminate();\n else if (ev.kind === \"error\") onError?.();\n else if (ev.kind === \"peer-joined\") onPeerJoined(ev.users.map(ev => makeUser({ userId: ev.userId, peerId: ev.peerId })));\n else if (ev.kind === \"peer-left\") onPeerLeft(ev.users);\n else if (ev.kind === \"message\") onMessage(ev.type, ev.payload, makeUser({ userId: ev.fromUserId, peerId: ev.fromPeerId }));\n else if (ev.kind === \"log\") logLine?.(ev.direction, ev.obj);\n };\n\n worker.addEventListener(\"message\", onWorkerMessage);\n\n worker.postMessage({ cmd: \"enter\", userId, appId, room, host });\n\n return {\n exitRoom: () => {\n exited = true;\n worker.removeEventListener(\"message\", onWorkerMessage);\n worker.postMessage({ cmd: \"exit\" });\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\";\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 peers: Map<string, IPeer<SigType, SigPayload>>;\n\n expirationTimeout?: number;\n};\ntype UserListener = (user: string, action: \"join\"|\"leave\", users: string[]) => void;\n\nconst DEFAULT_ENTER_ROOM = enterRoom;\n\n\nexport function collectPeerConnections({\n userId,\n appId,\n receivePeerConnection,\n peerlessUserExpiration,\n rtcConfig = { iceServers: [{ urls: \"stun:stun.l.google.com:19302\" }] },\n enterRoomFunction: enterRoom = DEFAULT_ENTER_ROOM,\n logLine = console.debug,\n onLeaveUser,\n workerUrl,\n}: {\n userId: string;\n appId: string;\n rtcConfig?: 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: { pc: RTCPeerConnection, userId: string, initiator: boolean }): void;\n}) {\n const users: Map<string, UserState> = new Map();\n function getUsers() {\n return [...users.keys()];\n }\n\n const userListener: Set<UserListener> = new Set();\n function getPeer(peer: IPeer<SigType, SigPayload>): UserState {\n let state = users.get(peer.userId);\n if (!state) {\n const newState: UserState = {\n userId: peer.userId,\n pc: new RTCPeerConnection(rtcConfig),\n pendingRemoteIce: [],\n peers: new Map(),\n };\n newState.peers.set(peer.peerId, peer);\n users.set(peer.userId, newState);\n\n // Send local ICE candidates to this peer\n newState.pc.onicecandidate = (ev) => {\n if (!ev.candidate) return;\n for(let user of newState.peers.values()) {\n const success = user.receive(\"ice\", ev.candidate.toJSON());\n if (success) break;\n }\n };\n \n newState.pc.onconnectionstatechange = () => {\n logLine(\"💬\", { event: \"pc-state\", userId: newState.userId, state: newState.pc.connectionState });\n };\n state = newState;\n\n // New user\n userListener.forEach(listener => listener(peer.userId, \"join\", getUsers()));\n users.set(state.userId, state);\n } else if (state) {\n clearTimeout(state.expirationTimeout);\n state.expirationTimeout = 0;\n state.peers.set(peer.peerId, peer);\n }\n return state;\n }\n\n function leaveUser(userId: string) {\n onLeaveUser?.(userId);\n const p = users.get(userId);\n if (!p) return;\n try { p.pc.close(); } catch {}\n users.delete(userId);\n userListener.forEach(listener => listener(userId, \"leave\", getUsers()));\n logLine(\"👤 USER LEFT\", 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\", { error: \"add-ice-failed\", userId: state.userId, detail: String(e) });\n }\n }\n }\n\n const roomsEntered = new Map<string, { room: string; host: string; exitRoom: () => void }>();\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>((resolve, reject) => {\n async function makeOffer(user: IPeer) {\n // Offer flow: createOffer -> setLocalDescription -> send localDescription\n const state = 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 const { exitRoom } = enterRoom({\n userId,\n appId,\n room,\n host,\n logLine,\n workerUrl,\n\n onOpen: resolve,\n onError: reject,\n\n // Existing peers initiate to the newcomer (Option 1)\n onPeerJoined(joiningUsers: IPeer<SigType, SigPayload>[]) {\n joiningUsers.forEach(user => {\n const state = getPeer(user);\n const pc = state.pc;\n receivePeerConnection({ pc, userId: user.userId, initiator: true });\n makeOffer(user);\n });\n },\n\n onPeerLeft(leavingUsers: { userId: string; peerId: string }[]) {\n leavingUsers.forEach(({ userId, peerId }) => {\n const state = users.get(userId);\n if (!state) return;\n state.peers.delete(peerId);\n if (state.peers.size === 0) {\n state.expirationTimeout = setTimeout(() => leaveUser(userId), peerlessUserExpiration ?? 0);\n }\n });\n },\n\n async onMessage(type: SigType, payload: any, from: IPeer) {\n const state = getPeer(from);\n const pc = state.pc;\n\n if (type === \"offer\") {\n receivePeerConnection({ pc, userId: from.userId, initiator: false });\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 receivePeerConnection({ pc, userId: from.userId, initiator: true });\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\", { error: \"add-ice-failed\", userId: state.userId, detail: String(e) });\n }\n return;\n }\n },\n });\n roomsEntered.set(`${host}/room/${room}`, { exitRoom, room, host });\n });\n }\n\n function removeUserListener(listener: UserListener) {\n userListener.delete(listener);\n }\n\n function addUserListener(listener: UserListener) {\n userListener.add(listener);\n return () => {\n removeUserListener(listener);\n };\n }\n\n return {\n enterRoom: enter,\n exitRoom: exit,\n leaveUser,\n getUsers,\n addUserListener,\n removeUserListener,\n getRooms() {\n return Array.from(roomsEntered.values());\n },\n end() {\n roomsEntered.forEach(({ exitRoom }) => exitRoom());\n roomsEntered.clear();\n users.forEach(({ userId }) => leaveUser(userId));\n userListener.clear();\n },\n };\n}\n\n\n\n",
8
- "import { EnterRoom, enterRoom } from \"./signal-room\";\nimport { SigType, SigPayload, collectPeerConnections } from \"./webrtc-peer-collector\";\n\nexport function enterWorld({\n uid, appId, logLine = console.debug, enterRoomFunction = enterRoom, peerlessUserExpiration, workerUrl,\n}: {\n uid?: string;\n appId: string;\n logLine?: (direction: string, obj?: any) => void;\n enterRoomFunction?: EnterRoom<SigType, SigPayload>;\n peerlessUserExpiration?: number;\n workerUrl?: URL;\n}) {\n const userId = uid ?? `user-${crypto.randomUUID()}`;\n const rtcConfig: RTCConfiguration = {\n iceServers: [{ urls: \"stun:stun.l.google.com:19302\" }],\n };\n\n const messagesListeners = new Set<(data: any, from: string) => void>();\n\n function wireDataChannel(userId: string, dc: RTCDataChannel) {\n dc.onopen = () => logLine(\"💬\", { event: \"dc-open\", userId });\n dc.onmessage = ({ data }) => {\n messagesListeners.forEach(listener => listener(data as any, userId));\n logLine(\"💬\", { event: \"dc-message\", userId, data });\n };\n dc.onclose = () => logLine(\"💬\", { event: \"dc-close\", userId });\n dc.onerror = () => logLine(\"⚠️ ERROR\", { error: \"dc-error\", userId });\n }\n\n const dataChannels: Map<string, RTCDataChannel> = new Map();\n\n const { enterRoom, exitRoom, getUsers, leaveUser, addUserListener, removeUserListener, end: endPeerCollection } = collectPeerConnections({\n userId,\n appId,\n rtcConfig,\n enterRoomFunction,\n logLine,\n workerUrl,\n peerlessUserExpiration,\n onLeaveUser(userId: string) {\n const dc = dataChannels.get(userId);\n try { dc?.close(); } catch { }\n dataChannels.delete(userId);\n },\n receivePeerConnection({ pc, userId, initiator }) {\n if (initiator) {\n const dc = pc.createDataChannel(\"data\");\n wireDataChannel(userId, dc);\n dataChannels.set(userId, dc);\n } else {\n pc.ondatachannel = (ev) => {\n const dc = ev.channel;\n wireDataChannel(userId, dc);\n dataChannels.set(userId, dc);\n };\n }\n\n logLine(\"💬\", { event: \"pc-ready\", userId, initiator });\n },\n });\n\n function send(data: any, userId?: string) {\n dataChannels.forEach((dataChannel, pUserId) => {\n if (userId && pUserId !== userId) return;\n if (dataChannel.readyState === \"open\") dataChannel.send(data);\n });\n }\n\n function removeMessageListener(listener: (data: any, from: string) => void) {\n messagesListeners.delete(listener);\n }\n\n function addMessageListener(listener: (data: any, from: string) => void) {\n messagesListeners.add(listener);\n return () => {\n removeMessageListener(listener);\n };\n }\n\n return {\n userId,\n send,\n enterRoom,\n exitRoom,\n leaveUser,\n getUsers,\n addMessageListener,\n removeMessageListener,\n addUserListener,\n removeUserListener,\n end() {\n dataChannels.forEach((dataChannel) => {\n try { dataChannel.close(); } catch { }\n });\n dataChannels.clear();\n endPeerCollection();\n },\n };\n}\n"
9
- ],
10
- "mappings": "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",
11
- "debugId": "7979C2C0F3726FCA64756E2164756E21",
12
- "names": []
13
- }