@dobuki/hello-worker 1.0.17 → 1.0.19
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/enter-world.js +4 -101
- package/dist/enter-world.js.map +13 -1
- package/dist/index.js +4 -6
- package/dist/index.js.map +13 -1
- package/dist/signal-room.js +4 -59
- package/dist/signal-room.js.map +11 -1
- package/dist/webrtc-peer-collector.js +4 -155
- package/dist/webrtc-peer-collector.js.map +12 -1
- package/package.json +4 -4
- package/dist/impl/signal-room.js +0 -76
- package/dist/impl/signal-room.js.map +0 -1
- package/dist/sample/index.js +0 -145
- package/dist/sample/index.js.map +0 -1
- package/dist/sample.d.ts +0 -4
- package/dist/sample.d.ts.map +0 -1
- package/dist/sample.js +0 -147
- package/dist/sample.js.map +0 -1
- package/dist/webrtc-room.d.ts +0 -52
- package/dist/webrtc-room.d.ts.map +0 -1
- package/dist/webrtc-room.js +0 -216
- package/dist/webrtc-room.js.map +0 -1
package/dist/enter-world.js
CHANGED
|
@@ -1,101 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
const rtcConfig = {
|
|
6
|
-
iceServers: [{ urls: "stun:stun.l.google.com:19302" }],
|
|
7
|
-
};
|
|
8
|
-
const messagesListeners = new Set();
|
|
9
|
-
const usersListener = new Set();
|
|
10
|
-
function wireDataChannel(userId, dc) {
|
|
11
|
-
dc.onopen = () => logLine("💬", { event: "dc-open", userId });
|
|
12
|
-
dc.onmessage = ({ data }) => {
|
|
13
|
-
messagesListeners.forEach(listener => listener(data, userId));
|
|
14
|
-
logLine("💬", { event: "dc-message", userId, data });
|
|
15
|
-
};
|
|
16
|
-
dc.onclose = () => logLine("💬", { event: "dc-close", userId });
|
|
17
|
-
dc.onerror = () => logLine("⚠️ ERROR", { error: "dc-error", userId });
|
|
18
|
-
}
|
|
19
|
-
const dataChannels = new Map();
|
|
20
|
-
const { enterRoom, exitRoom, leaveUser, getUsers, getRooms } = collectPeerConnections({
|
|
21
|
-
userId,
|
|
22
|
-
rtcConfig,
|
|
23
|
-
enterRoomFunction,
|
|
24
|
-
logLine,
|
|
25
|
-
workerUrl,
|
|
26
|
-
leaveUserWithoutPeer: autoLeaveUsers,
|
|
27
|
-
onLeaveUser(userId) {
|
|
28
|
-
const dc = dataChannels.get(userId);
|
|
29
|
-
try {
|
|
30
|
-
dc?.close();
|
|
31
|
-
}
|
|
32
|
-
catch { }
|
|
33
|
-
dataChannels.delete(userId);
|
|
34
|
-
},
|
|
35
|
-
receivePeerConnection({ pc, userId, initiator }) {
|
|
36
|
-
if (initiator) {
|
|
37
|
-
const dc = pc.createDataChannel("data");
|
|
38
|
-
wireDataChannel(userId, dc);
|
|
39
|
-
dataChannels.set(userId, dc);
|
|
40
|
-
}
|
|
41
|
-
else {
|
|
42
|
-
pc.ondatachannel = (ev) => {
|
|
43
|
-
const dc = ev.channel;
|
|
44
|
-
wireDataChannel(userId, dc);
|
|
45
|
-
dataChannels.set(userId, dc);
|
|
46
|
-
};
|
|
47
|
-
}
|
|
48
|
-
logLine("💬", { event: "pc-ready", userId, initiator });
|
|
49
|
-
},
|
|
50
|
-
});
|
|
51
|
-
function send(data, userId) {
|
|
52
|
-
dataChannels.forEach((dataChannel, pUserId) => {
|
|
53
|
-
if (userId && pUserId !== userId)
|
|
54
|
-
return;
|
|
55
|
-
if (dataChannel.readyState === "open")
|
|
56
|
-
dataChannel.send(data);
|
|
57
|
-
});
|
|
58
|
-
}
|
|
59
|
-
function removeMessageListener(listener) {
|
|
60
|
-
messagesListeners.delete(listener);
|
|
61
|
-
}
|
|
62
|
-
function addMessageListener(listener) {
|
|
63
|
-
messagesListeners.add(listener);
|
|
64
|
-
return () => {
|
|
65
|
-
removeMessageListener(listener);
|
|
66
|
-
};
|
|
67
|
-
}
|
|
68
|
-
function removeUserListener(listener) {
|
|
69
|
-
usersListener.delete(listener);
|
|
70
|
-
}
|
|
71
|
-
function addUserListener(listener) {
|
|
72
|
-
usersListener.add(listener);
|
|
73
|
-
return () => {
|
|
74
|
-
removeUserListener(listener);
|
|
75
|
-
};
|
|
76
|
-
}
|
|
77
|
-
return {
|
|
78
|
-
userId,
|
|
79
|
-
send,
|
|
80
|
-
enterRoom,
|
|
81
|
-
exitRoom,
|
|
82
|
-
leaveUser,
|
|
83
|
-
getUsers,
|
|
84
|
-
addMessageListener,
|
|
85
|
-
removeMessageListener,
|
|
86
|
-
addUserListener,
|
|
87
|
-
removeUserListener,
|
|
88
|
-
end() {
|
|
89
|
-
getUsers().forEach(user => leaveUser(user));
|
|
90
|
-
getRooms().forEach(({ room, host }) => exitRoom({ room, host }));
|
|
91
|
-
dataChannels.forEach((dataChannel) => {
|
|
92
|
-
try {
|
|
93
|
-
dataChannel.close();
|
|
94
|
-
}
|
|
95
|
-
catch { }
|
|
96
|
-
});
|
|
97
|
-
dataChannels.clear();
|
|
98
|
-
},
|
|
99
|
-
};
|
|
100
|
-
}
|
|
101
|
-
//# sourceMappingURL=enter-world.js.map
|
|
1
|
+
function k({userId:h,appId:f,room:H,host:j,onOpen:W,onClose:S,onError:F,logLine:M,onPeerJoined:C,onPeerLeft:G,onMessage:K}){let Y=`wss://${j}/room/${f}/${H}?userId=${encodeURIComponent(h)}`,z=new WebSocket(Y),J=h,Z=new Map,Q=!1;function q(A,c,D){if(Q)return!1;let T={type:A,to:c,payload:D};return z.send(JSON.stringify(T)),M?.("\uD83D\uDC64 ➡️ \uD83D\uDDA5️",T),!0}function $(A){let c=[],D=[],T=new Set;if(A.forEach(({userId:R,peerId:B})=>{if(R===J)return;if(!Z.has(B)){let O={userId:R,peerId:B,receive:(N,X)=>q(N,B,X)};Z.set(B,O),c.push(O)}T.add(B)}),Z.values().forEach(({peerId:R,userId:B})=>{if(!T.has(R))Z.delete(R),D.push({peerId:R,userId:B})}),c.length)C(c);if(D.length)G(D)}function b(A){let c;try{c=JSON.parse(A.data)}catch{M?.("⚠️ ERROR",{error:"invalid-json"});return}if(M?.("\uD83D\uDDA5️ ➡️ \uD83D\uDC64",c),c.type==="peer-joined"){$(c.users);return}if(c.type==="peer-left"){$(c.users);return}if(c.peerId&&c.userId){let{userId:D,peerId:T}=c;K(c.type,c.payload,{userId:D,peerId:T,receive:(R,B)=>q(R,T,B)})}}if(z.addEventListener("message",b),W)z.addEventListener("open",W);if(S)z.addEventListener("close",S);if(F)z.addEventListener("error",F);return{exitRoom:()=>{if(Q=!0,z.close(),z.removeEventListener("message",b),W)z.removeEventListener("open",W);if(S)z.removeEventListener("close",S);if(F)z.removeEventListener("error",F)}}}function E({userId:h,appId:f,room:H,host:j,onOpen:W,onClose:S,onError:F,onPeerJoined:M,onPeerLeft:C,onMessage:G,logLine:K,workerUrl:Y}){if(!Y)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:h,appId:f,room:H,host:j,onOpen:W,onClose:S,onError:F,onPeerJoined:M,onPeerLeft:C,onMessage:G});let z=new Worker(Y,{type:"module"}),J=!1;function Z({userId:q,peerId:$}){return{userId:q,peerId:$,receive:(b,A)=>{if(J)return!1;return z.postMessage({cmd:"send",toPeerId:$,type:b,payload:A}),!0}}}let Q=(q)=>{let $=q.data;if($.kind==="open")W?.();else if($.kind==="close")z.terminate();else if($.kind==="error")F?.();else if($.kind==="peer-joined")M($.users.map((b)=>Z({userId:b.userId,peerId:b.peerId})));else if($.kind==="peer-left")C($.users);else if($.kind==="message")G($.type,$.payload,Z({userId:$.fromUserId,peerId:$.fromPeerId}));else if($.kind==="log")K?.($.direction,$.obj)};return z.addEventListener("message",Q),z.postMessage({cmd:"enter",userId:h,appId:f,room:H,host:j}),{exitRoom:()=>{J=!0,z.removeEventListener("message",Q),z.postMessage({cmd:"exit"})}}}var v=E;function U({userId:h,appId:f,receivePeerConnection:H,peerlessUserExpiration:j,rtcConfig:W={iceServers:[{urls:"stun:stun.l.google.com:19302"}]},enterRoomFunction:S=v,logLine:F=console.debug,onLeaveUser:M,workerUrl:C}){let G=new Map;function K(){return[...G.keys()]}let Y=new Set;function z(c){let D=G.get(c.userId);if(!D){let T={userId:c.userId,pc:new RTCPeerConnection(W),pendingRemoteIce:[],peers:new Map};T.peers.set(c.peerId,c),G.set(c.userId,T),T.pc.onicecandidate=(R)=>{if(!R.candidate)return;for(let B of T.peers.values())if(B.receive("ice",R.candidate.toJSON()))break},T.pc.onconnectionstatechange=()=>{F("\uD83D\uDCAC",{event:"pc-state",userId:T.userId,state:T.pc.connectionState})},D=T,Y.forEach((R)=>R(c.userId,"join",K())),G.set(D.userId,D)}else if(D)clearTimeout(D.expirationTimeout),D.expirationTimeout=0,D.peers.set(c.peerId,c);return D}function J(c){M?.(c);let D=G.get(c);if(!D)return;try{D.pc.close()}catch{}G.delete(c),Y.forEach((T)=>T(c,"leave",K())),F("\uD83D\uDC64 USER LEFT",c)}async function Z(c){if(!c.pc.remoteDescription)return;let D=c.pendingRemoteIce;c.pendingRemoteIce=[];for(let T of D)try{await c.pc.addIceCandidate(T)}catch(R){F("⚠️ ERROR",{error:"add-ice-failed",userId:c.userId,detail:String(R)})}}let Q=new Map;function q({room:c,host:D}){let T=`${D}/room/${c}`,R=Q.get(T);if(R)R.exitRoom(),Q.delete(T)}function $({room:c,host:D}){return new Promise((T,R)=>{async function B(N){let _=z(N).pc,V=await _.createOffer();await _.setLocalDescription(V),N.receive("offer",_.localDescription?.toJSON())}let{exitRoom:O}=S({userId:h,appId:f,room:c,host:D,logLine:F,workerUrl:C,onOpen:T,onError:R,onPeerJoined(N){N.forEach((X)=>{let V=z(X).pc;H({pc:V,userId:X.userId,initiator:!0}),B(X)})},onPeerLeft(N){N.forEach(({userId:X,peerId:_})=>{let V=G.get(X);if(!V)return;if(V.peers.delete(_),V.peers.size===0)V.expirationTimeout=setTimeout(()=>J(X),j??0)})},async onMessage(N,X,_){let V=z(_),x=V.pc;if(N==="offer"){H({pc:x,userId:_.userId,initiator:!1}),await x.setRemoteDescription(X);let y=await x.createAnswer();await x.setLocalDescription(y),_.receive("answer",x.localDescription?.toJSON()),await Z(V);return}if(N==="answer"){await x.setRemoteDescription(X),await Z(V),H({pc:x,userId:_.userId,initiator:!0});return}if(N==="ice"){let y=X;if(!x.remoteDescription){V.pendingRemoteIce.push(y);return}try{await x.addIceCandidate(y)}catch(P){F("⚠️ ERROR",{error:"add-ice-failed",userId:V.userId,detail:String(P)})}return}}});Q.set(`${D}/room/${c}`,{exitRoom:O,room:c,host:D})})}function b(c){Y.delete(c)}function A(c){return Y.add(c),()=>{b(c)}}return{enterRoom:$,exitRoom:q,leaveUser:J,getUsers:K,addUserListener:A,removeUserListener:b,getRooms(){return Array.from(Q.values())},end(){Q.forEach(({exitRoom:c})=>c()),Q.clear(),G.forEach(({userId:c})=>J(c)),Y.clear()}}}function t({uid:h,appId:f,logLine:H=console.debug,enterRoomFunction:j=E,peerlessUserExpiration:W,workerUrl:S}){let F=h??`user-${crypto.randomUUID()}`,M={iceServers:[{urls:"stun:stun.l.google.com:19302"}]},C=new Set;function G(D,T){T.onopen=()=>H("\uD83D\uDCAC",{event:"dc-open",userId:D}),T.onmessage=({data:R})=>{C.forEach((B)=>B(R,D)),H("\uD83D\uDCAC",{event:"dc-message",userId:D,data:R})},T.onclose=()=>H("\uD83D\uDCAC",{event:"dc-close",userId:D}),T.onerror=()=>H("⚠️ ERROR",{error:"dc-error",userId:D})}let K=new Map,{enterRoom:Y,exitRoom:z,getUsers:J,leaveUser:Z,addUserListener:Q,removeUserListener:q,end:$}=U({userId:F,appId:f,rtcConfig:M,enterRoomFunction:j,logLine:H,workerUrl:S,peerlessUserExpiration:W,onLeaveUser(D){let T=K.get(D);try{T?.close()}catch{}K.delete(D)},receivePeerConnection({pc:D,userId:T,initiator:R}){if(R){let B=D.createDataChannel("data");G(T,B),K.set(T,B)}else D.ondatachannel=(B)=>{let O=B.channel;G(T,O),K.set(T,O)};H("\uD83D\uDCAC",{event:"pc-ready",userId:T,initiator:R})}});function b(D,T){K.forEach((R,B)=>{if(T&&B!==T)return;if(R.readyState==="open")R.send(D)})}function A(D){C.delete(D)}function c(D){return C.add(D),()=>{A(D)}}return{userId:F,send:b,enterRoom:Y,exitRoom:z,leaveUser:Z,getUsers:J,addMessageListener:c,removeMessageListener:A,addUserListener:Q,removeUserListener:q,end(){K.forEach((D)=>{try{D.close()}catch{}}),K.clear(),$()}}}export{t as enterWorld};
|
|
2
|
+
|
|
3
|
+
//# debugId=785DC53F308F768364756E2164756E21
|
|
4
|
+
//# sourceMappingURL=enter-world.js.map
|
package/dist/enter-world.js.map
CHANGED
|
@@ -1 +1,13 @@
|
|
|
1
|
-
{
|
|
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": "785DC53F308F768364756E2164756E21",
|
|
12
|
+
"names": []
|
|
13
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
export { enterWorld } from "./enter-world";
|
|
6
|
-
//# sourceMappingURL=index.js.map
|
|
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=ECF8FB936A1DF5A064756E2164756E21
|
|
4
|
+
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1,13 @@
|
|
|
1
|
-
{
|
|
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": "ECF8FB936A1DF5A064756E2164756E21",
|
|
12
|
+
"names": []
|
|
13
|
+
}
|
package/dist/signal-room.js
CHANGED
|
@@ -1,59 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
console.warn("Warning: enterRoom called without workerUrl; this may cause issues in some environments. You should pass workerUrl explicitly. Use:", CDN_WORKER_URL);
|
|
6
|
-
return baseEnterRoom({
|
|
7
|
-
userId,
|
|
8
|
-
room,
|
|
9
|
-
host,
|
|
10
|
-
onOpen,
|
|
11
|
-
onClose,
|
|
12
|
-
onError,
|
|
13
|
-
onPeerJoined,
|
|
14
|
-
onPeerLeft,
|
|
15
|
-
onMessage,
|
|
16
|
-
});
|
|
17
|
-
}
|
|
18
|
-
const worker = new Worker(workerUrl, { type: "module" });
|
|
19
|
-
let exited = false;
|
|
20
|
-
function makeUser({ userId, peerId }) {
|
|
21
|
-
return {
|
|
22
|
-
userId,
|
|
23
|
-
peerId,
|
|
24
|
-
receive: (type, payload) => {
|
|
25
|
-
if (exited)
|
|
26
|
-
return false;
|
|
27
|
-
worker.postMessage({ cmd: "send", toPeerId: peerId, type, payload });
|
|
28
|
-
return true;
|
|
29
|
-
},
|
|
30
|
-
};
|
|
31
|
-
}
|
|
32
|
-
const onWorkerMessage = (e) => {
|
|
33
|
-
const ev = e.data;
|
|
34
|
-
if (ev.kind === "open")
|
|
35
|
-
onOpen?.();
|
|
36
|
-
else if (ev.kind === "close")
|
|
37
|
-
worker.terminate();
|
|
38
|
-
else if (ev.kind === "error")
|
|
39
|
-
onError?.();
|
|
40
|
-
else if (ev.kind === "peer-joined")
|
|
41
|
-
onPeerJoined(makeUser({ userId: ev.userId, peerId: ev.peerId }));
|
|
42
|
-
else if (ev.kind === "peer-left")
|
|
43
|
-
onPeerLeft(ev.userId, ev.peerId);
|
|
44
|
-
else if (ev.kind === "message")
|
|
45
|
-
onMessage(ev.type, ev.payload, makeUser({ userId: ev.fromUserId, peerId: ev.fromPeerId }));
|
|
46
|
-
else if (ev.kind === "log")
|
|
47
|
-
logLine?.(ev.direction, ev.obj);
|
|
48
|
-
};
|
|
49
|
-
worker.addEventListener("message", onWorkerMessage);
|
|
50
|
-
worker.postMessage({ cmd: "enter", userId, room, host });
|
|
51
|
-
return {
|
|
52
|
-
exitRoom: () => {
|
|
53
|
-
exited = true;
|
|
54
|
-
worker.removeEventListener("message", onWorkerMessage);
|
|
55
|
-
worker.postMessage({ cmd: "exit" });
|
|
56
|
-
},
|
|
57
|
-
};
|
|
58
|
-
}
|
|
59
|
-
//# sourceMappingURL=signal-room.js.map
|
|
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"){q(A.users);return}if(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=6467E57AEE09475564756E2164756E21
|
|
4
|
+
//# sourceMappingURL=signal-room.js.map
|
package/dist/signal-room.js.map
CHANGED
|
@@ -1 +1,11 @@
|
|
|
1
|
-
{
|
|
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\") {\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
|
+
],
|
|
8
|
+
"mappings": "AASO,SAAS,CAAoC,EAChD,SACA,QACA,OACA,OACA,SACA,UACA,UACA,UACA,eACA,aACA,aAayB,CACzB,IAAM,EAAQ,SAAS,UAAa,KAAS,YAAe,mBAAmB,CAAM,IAC/E,EAAK,IAAI,UAAU,CAAK,EACxB,EAAa,EAEb,EAAQ,IAAI,IACd,EAAS,GACb,SAAS,CAAI,CAAC,EAAS,EAAkB,EAAY,CACjD,GAAI,EAAQ,MAAO,GACnB,IAAM,EAAM,CAAE,OAAM,GAAI,EAAU,SAAQ,EAG1C,OAFA,EAAG,KAAK,KAAK,UAAU,CAAG,CAAC,EAC3B,IAAU,gCAAY,CAAG,EAClB,GAGX,SAAS,CAAW,CAAC,EAAoD,CACrE,IAAM,EAAuB,CAAC,EACxB,EAAsC,CAAC,EACvC,EAAiB,IAAI,IAgB3B,GAfA,EAAa,QAAQ,EAAG,SAAQ,YAAa,CACzC,GAAI,IAAW,EAAY,OAC3B,GAAI,CAAC,EAAM,IAAI,CAAM,EAAG,CACpB,IAAM,EAAU,CAAE,SAAQ,SAAQ,QAAS,CAAC,EAAS,IAAe,EAAK,EAAM,EAAQ,CAAO,CAAC,EAC/F,EAAM,IAAI,EAAQ,CAAO,EACzB,EAAO,KAAK,CAAO,EAEvB,EAAe,IAAI,CAAM,EAC5B,EACD,EAAM,OAAO,EAAE,QAAQ,EAAG,SAAQ,YAAa,CAC3C,GAAI,CAAC,EAAe,IAAI,CAAM,EAC1B,EAAM,OAAO,CAAM,EACnB,EAAK,KAAK,CAAE,SAAQ,QAAO,CAAC,EAEnC,EACG,EAAO,OAAQ,EAAa,CAAM,EACtC,GAAI,EAAK,OAAQ,EAAW,CAAI,EAGpC,SAAS,CAAS,CAAC,EAAiB,CAChC,IAAI,EAOJ,GAAI,CAAE,EAAM,KAAK,MAAM,EAAE,IAAI,EAC7B,KAAM,CACF,IAAU,WAAW,CAAE,MAAO,cAAe,CAAC,EAC9C,OAMJ,GAHA,IAAU,gCAAY,CAAG,EAGrB,EAAI,OAAS,cAAe,CAC5B,EAAY,EAAI,KAAK,EACrB,OAEJ,GAAI,EAAI,OAAS,YAAa,CAC1B,EAAY,EAAI,KAAK,EACrB,OAEJ,GAAI,EAAI,QAAU,EAAI,OAAQ,CAC1B,IAAQ,SAAQ,UAAW,EAC3B,EAAU,EAAI,KAAM,EAAI,QAAS,CAC7B,SACA,SACA,QAAS,CAAC,EAAS,IAAe,EAAK,EAAM,EAAQ,CAAO,CAChE,CAAC,GAKT,GADA,EAAG,iBAAiB,UAAW,CAAS,EACpC,EAAQ,EAAG,iBAAiB,OAAQ,CAAM,EAC9C,GAAI,EAAS,EAAG,iBAAiB,QAAS,CAAO,EACjD,GAAI,EAAS,EAAG,iBAAiB,QAAS,CAAO,EACjD,MAAO,CACH,SAAU,IAAM,CAIZ,GAHA,EAAS,GACT,EAAG,MAAM,EACT,EAAG,oBAAoB,UAAW,CAAS,EACvC,EAAQ,EAAG,oBAAoB,OAAQ,CAAM,EACjD,GAAI,EAAS,EAAG,oBAAoB,QAAS,CAAO,EACpD,GAAI,EAAS,EAAG,oBAAoB,QAAS,CAAO,EAE5D,ECnHG,SAAS,CAAoC,EAClD,SACA,QACA,OACA,OACA,SACA,UACA,UACA,eACA,aACA,YACA,UACA,aAgB2B,CACzB,GAAI,CAAC,EAID,OADA,QAAQ,KAAK,sIAFU,kFAE2I,EAC3J,EAAoB,CACvB,SACA,QACA,OACA,OACA,SACA,UACA,UACA,eACA,aACA,WACJ,CAAC,EAEP,IAAM,EAAS,IAAI,OAAO,EAAW,CAAE,KAAM,QAAS,CAAC,EACnD,EAAS,GAEb,SAAS,CAAQ,EAAG,SAAQ,UAA2D,CACrF,MAAO,CACL,SACA,SACA,QAAS,CAAC,EAAS,IAAe,CAChC,GAAI,EAAQ,MAAO,GAEnB,OADA,EAAO,YAAY,CAAE,IAAK,OAAQ,SAAU,EAAQ,OAAM,SAAQ,CAAC,EAC5D,GAEX,EAGF,IAAM,EAAkB,CAAC,IAAqC,CAC5D,IAAM,EAAK,EAAE,KAEb,GAAI,EAAG,OAAS,OAAQ,IAAS,EAC5B,QAAI,EAAG,OAAS,QAAS,EAAO,UAAU,EAC1C,QAAI,EAAG,OAAS,QAAS,IAAU,EACnC,QAAI,EAAG,OAAS,cAAe,EAAa,EAAG,MAAM,IAAI,KAAM,EAAS,CAAE,OAAQ,EAAG,OAAQ,OAAQ,EAAG,MAAO,CAAC,CAAC,CAAC,EAClH,QAAI,EAAG,OAAS,YAAa,EAAW,EAAG,KAAK,EAChD,QAAI,EAAG,OAAS,UAAW,EAAU,EAAG,KAAM,EAAG,QAAS,EAAS,CAAE,OAAQ,EAAG,WAAY,OAAQ,EAAG,UAAW,CAAC,CAAC,EACpH,QAAI,EAAG,OAAS,MAAO,IAAU,EAAG,UAAW,EAAG,GAAG,GAO5D,OAJA,EAAO,iBAAiB,UAAW,CAAe,EAElD,EAAO,YAAY,CAAE,IAAK,QAAS,SAAQ,QAAO,OAAM,MAAK,CAAC,EAEvD,CACL,SAAU,IAAM,CACd,EAAS,GACT,EAAO,oBAAoB,UAAW,CAAe,EACrD,EAAO,YAAY,CAAE,IAAK,MAAO,CAAC,EAEtC",
|
|
9
|
+
"debugId": "6467E57AEE09475564756E2164756E21",
|
|
10
|
+
"names": []
|
|
11
|
+
}
|
|
@@ -1,155 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
function getPeer(peer) {
|
|
6
|
-
let state = users.get(peer.userId);
|
|
7
|
-
if (!state) {
|
|
8
|
-
const newState = {
|
|
9
|
-
userId: peer.userId,
|
|
10
|
-
pc: new RTCPeerConnection(rtcConfig),
|
|
11
|
-
pendingRemoteIce: [],
|
|
12
|
-
peers: new Set([peer]),
|
|
13
|
-
};
|
|
14
|
-
users.set(peer.userId, newState);
|
|
15
|
-
// Send local ICE candidates to this peer
|
|
16
|
-
newState.pc.onicecandidate = (ev) => {
|
|
17
|
-
if (!ev.candidate)
|
|
18
|
-
return;
|
|
19
|
-
for (let user of newState.peers) {
|
|
20
|
-
const success = user.receive("ice", ev.candidate.toJSON());
|
|
21
|
-
if (success)
|
|
22
|
-
break;
|
|
23
|
-
}
|
|
24
|
-
};
|
|
25
|
-
newState.pc.onconnectionstatechange = () => {
|
|
26
|
-
logLine("💬", { event: "pc-state", userId: newState.userId, state: newState.pc.connectionState });
|
|
27
|
-
};
|
|
28
|
-
state = newState;
|
|
29
|
-
}
|
|
30
|
-
else {
|
|
31
|
-
state.peers.add(peer);
|
|
32
|
-
}
|
|
33
|
-
users.set(state.userId, state);
|
|
34
|
-
return state;
|
|
35
|
-
}
|
|
36
|
-
function leaveUser(userId) {
|
|
37
|
-
onLeaveUser?.(userId);
|
|
38
|
-
const p = users.get(userId);
|
|
39
|
-
if (!p)
|
|
40
|
-
return;
|
|
41
|
-
try {
|
|
42
|
-
p.pc.close();
|
|
43
|
-
}
|
|
44
|
-
catch { }
|
|
45
|
-
users.delete(userId);
|
|
46
|
-
logLine("👤 USER LEFT", userId);
|
|
47
|
-
}
|
|
48
|
-
async function flushRemoteIce(state) {
|
|
49
|
-
if (!state.pc.remoteDescription)
|
|
50
|
-
return;
|
|
51
|
-
const queued = state.pendingRemoteIce;
|
|
52
|
-
state.pendingRemoteIce = [];
|
|
53
|
-
for (const ice of queued) {
|
|
54
|
-
try {
|
|
55
|
-
await state.pc.addIceCandidate(ice);
|
|
56
|
-
}
|
|
57
|
-
catch (e) {
|
|
58
|
-
logLine("⚠️ ERROR", { error: "add-ice-failed", userId: state.userId, detail: String(e) });
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
const roomsEntered = new Map();
|
|
63
|
-
function exit({ room, host }) {
|
|
64
|
-
const key = `${host}/room/${room}`;
|
|
65
|
-
const session = roomsEntered.get(key);
|
|
66
|
-
if (session) {
|
|
67
|
-
session.exitRoom();
|
|
68
|
-
roomsEntered.delete(key);
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
function enter({ room, host }) {
|
|
72
|
-
const { exitRoom } = enterRoom({
|
|
73
|
-
userId,
|
|
74
|
-
room,
|
|
75
|
-
host,
|
|
76
|
-
logLine,
|
|
77
|
-
workerUrl,
|
|
78
|
-
// Existing peers initiate to the newcomer (Option 1)
|
|
79
|
-
async onPeerJoined(user) {
|
|
80
|
-
const state = getPeer(user);
|
|
81
|
-
const pc = state.pc;
|
|
82
|
-
receivePeerConnection({ pc, userId: user.userId, initiator: true });
|
|
83
|
-
// Offer flow: createOffer -> setLocalDescription -> send localDescription
|
|
84
|
-
const offer = await pc.createOffer();
|
|
85
|
-
await pc.setLocalDescription(offer);
|
|
86
|
-
user.receive("offer", pc.localDescription?.toJSON());
|
|
87
|
-
},
|
|
88
|
-
onPeerLeft(userId, peerId) {
|
|
89
|
-
const state = users.get(userId);
|
|
90
|
-
if (!state)
|
|
91
|
-
return;
|
|
92
|
-
for (const user of state.peers) {
|
|
93
|
-
if (user.peerId === peerId) {
|
|
94
|
-
state.peers.delete(user);
|
|
95
|
-
break;
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
if (state.peers.size === 0 && leaveUserWithoutPeer) {
|
|
99
|
-
leaveUser(userId);
|
|
100
|
-
}
|
|
101
|
-
},
|
|
102
|
-
async onMessage(type, payload, from) {
|
|
103
|
-
const state = getPeer(from);
|
|
104
|
-
const pc = state.pc;
|
|
105
|
-
if (type === "offer") {
|
|
106
|
-
receivePeerConnection({ pc, userId: from.userId, initiator: false });
|
|
107
|
-
// Responder: set remote offer
|
|
108
|
-
await pc.setRemoteDescription(payload);
|
|
109
|
-
// Create and send answer
|
|
110
|
-
const answer = await pc.createAnswer();
|
|
111
|
-
await pc.setLocalDescription(answer);
|
|
112
|
-
from.receive("answer", pc.localDescription?.toJSON());
|
|
113
|
-
// Now safe to apply any queued ICE from this peer
|
|
114
|
-
await flushRemoteIce(state);
|
|
115
|
-
return;
|
|
116
|
-
}
|
|
117
|
-
if (type === "answer") {
|
|
118
|
-
// Initiator: set remote answer
|
|
119
|
-
await pc.setRemoteDescription(payload);
|
|
120
|
-
await flushRemoteIce(state);
|
|
121
|
-
receivePeerConnection({ pc, userId: from.userId, initiator: true });
|
|
122
|
-
return;
|
|
123
|
-
}
|
|
124
|
-
if (type === "ice") {
|
|
125
|
-
const ice = payload;
|
|
126
|
-
// If we don't have remoteDescription yet, queue it
|
|
127
|
-
if (!pc.remoteDescription) {
|
|
128
|
-
state.pendingRemoteIce.push(ice);
|
|
129
|
-
return;
|
|
130
|
-
}
|
|
131
|
-
try {
|
|
132
|
-
await pc.addIceCandidate(ice);
|
|
133
|
-
}
|
|
134
|
-
catch (e) {
|
|
135
|
-
logLine("⚠️ ERROR", { error: "add-ice-failed", userId: state.userId, detail: String(e) });
|
|
136
|
-
}
|
|
137
|
-
return;
|
|
138
|
-
}
|
|
139
|
-
},
|
|
140
|
-
});
|
|
141
|
-
roomsEntered.set(`${host}/room/${room}`, { exitRoom, room, host });
|
|
142
|
-
}
|
|
143
|
-
return {
|
|
144
|
-
enterRoom: enter,
|
|
145
|
-
exitRoom: exit,
|
|
146
|
-
leaveUser,
|
|
147
|
-
getUsers() {
|
|
148
|
-
return Array.from(users.keys());
|
|
149
|
-
},
|
|
150
|
-
getRooms() {
|
|
151
|
-
return Array.from(roomsEntered.values());
|
|
152
|
-
},
|
|
153
|
-
};
|
|
154
|
-
}
|
|
155
|
-
//# sourceMappingURL=webrtc-peer-collector.js.map
|
|
1
|
+
function P({userId:R,appId:C,room:M,host:h,onOpen:W,onClose:S,onError:X,logLine:j,onPeerJoined:k,onPeerLeft:Y,onMessage:O}){let q=`wss://${h}/room/${C}/${M}?userId=${encodeURIComponent(R)}`,K=new WebSocket(q),x=R,D=new Map,A=!1;function _(b,z,B){if(A)return!1;let G={type:b,to:z,payload:B};return K.send(JSON.stringify(G)),j?.("\uD83D\uDC64 ➡️ \uD83D\uDDA5️",G),!0}function H(b){let z=[],B=[],G=new Set;if(b.forEach(({userId:Q,peerId:Z})=>{if(Q===x)return;if(!D.has(Z)){let E={userId:Q,peerId:Z,receive:(F,$)=>_(F,Z,$)};D.set(Z,E),z.push(E)}G.add(Z)}),D.values().forEach(({peerId:Q,userId:Z})=>{if(!G.has(Q))D.delete(Q),B.push({peerId:Q,userId:Z})}),z.length)k(z);if(B.length)Y(B)}function T(b){let z;try{z=JSON.parse(b.data)}catch{j?.("⚠️ ERROR",{error:"invalid-json"});return}if(j?.("\uD83D\uDDA5️ ➡️ \uD83D\uDC64",z),z.type==="peer-joined"){H(z.users);return}if(z.type==="peer-left"){H(z.users);return}if(z.peerId&&z.userId){let{userId:B,peerId:G}=z;O(z.type,z.payload,{userId:B,peerId:G,receive:(Q,Z)=>_(Q,G,Z)})}}if(K.addEventListener("message",T),W)K.addEventListener("open",W);if(S)K.addEventListener("close",S);if(X)K.addEventListener("error",X);return{exitRoom:()=>{if(A=!0,K.close(),K.removeEventListener("message",T),W)K.removeEventListener("open",W);if(S)K.removeEventListener("close",S);if(X)K.removeEventListener("error",X)}}}function U({userId:R,appId:C,room:M,host:h,onOpen:W,onClose:S,onError:X,onPeerJoined:j,onPeerLeft:k,onMessage:Y,logLine:O,workerUrl:q}){if(!q)return console.warn("Warning: enterRoom called without workerUrl; this may cause issues in some environments. You should pass workerUrl explicitly. Use:","https://cdn.jsdelivr.net/npm/@dobuki/hello-worker/dist/signal-room.worker.min.js"),P({userId:R,appId:C,room:M,host:h,onOpen:W,onClose:S,onError:X,onPeerJoined:j,onPeerLeft:k,onMessage:Y});let K=new Worker(q,{type:"module"}),x=!1;function D({userId:_,peerId:H}){return{userId:_,peerId:H,receive:(T,b)=>{if(x)return!1;return K.postMessage({cmd:"send",toPeerId:H,type:T,payload:b}),!0}}}let A=(_)=>{let H=_.data;if(H.kind==="open")W?.();else if(H.kind==="close")K.terminate();else if(H.kind==="error")X?.();else if(H.kind==="peer-joined")j(H.users.map((T)=>D({userId:T.userId,peerId:T.peerId})));else if(H.kind==="peer-left")k(H.users);else if(H.kind==="message")Y(H.type,H.payload,D({userId:H.fromUserId,peerId:H.fromPeerId}));else if(H.kind==="log")O?.(H.direction,H.obj)};return K.addEventListener("message",A),K.postMessage({cmd:"enter",userId:R,appId:C,room:M,host:h}),{exitRoom:()=>{x=!0,K.removeEventListener("message",A),K.postMessage({cmd:"exit"})}}}var L=U;function u({userId:R,appId:C,receivePeerConnection:M,peerlessUserExpiration:h,rtcConfig:W={iceServers:[{urls:"stun:stun.l.google.com:19302"}]},enterRoomFunction:S=L,logLine:X=console.debug,onLeaveUser:j,workerUrl:k}){let Y=new Map;function O(){return[...Y.keys()]}let q=new Set;function K(z){let B=Y.get(z.userId);if(!B){let G={userId:z.userId,pc:new RTCPeerConnection(W),pendingRemoteIce:[],peers:new Map};G.peers.set(z.peerId,z),Y.set(z.userId,G),G.pc.onicecandidate=(Q)=>{if(!Q.candidate)return;for(let Z of G.peers.values())if(Z.receive("ice",Q.candidate.toJSON()))break},G.pc.onconnectionstatechange=()=>{X("\uD83D\uDCAC",{event:"pc-state",userId:G.userId,state:G.pc.connectionState})},B=G,q.forEach((Q)=>Q(z.userId,"join",O())),Y.set(B.userId,B)}else if(B)clearTimeout(B.expirationTimeout),B.expirationTimeout=0,B.peers.set(z.peerId,z);return B}function x(z){j?.(z);let B=Y.get(z);if(!B)return;try{B.pc.close()}catch{}Y.delete(z),q.forEach((G)=>G(z,"leave",O())),X("\uD83D\uDC64 USER LEFT",z)}async function D(z){if(!z.pc.remoteDescription)return;let B=z.pendingRemoteIce;z.pendingRemoteIce=[];for(let G of B)try{await z.pc.addIceCandidate(G)}catch(Q){X("⚠️ ERROR",{error:"add-ice-failed",userId:z.userId,detail:String(Q)})}}let A=new Map;function _({room:z,host:B}){let G=`${B}/room/${z}`,Q=A.get(G);if(Q)Q.exitRoom(),A.delete(G)}function H({room:z,host:B}){return new Promise((G,Q)=>{async function Z(F){let N=K(F).pc,V=await N.createOffer();await N.setLocalDescription(V),F.receive("offer",N.localDescription?.toJSON())}let{exitRoom:E}=S({userId:R,appId:C,room:z,host:B,logLine:X,workerUrl:k,onOpen:G,onError:Q,onPeerJoined(F){F.forEach(($)=>{let V=K($).pc;M({pc:V,userId:$.userId,initiator:!0}),Z($)})},onPeerLeft(F){F.forEach(({userId:$,peerId:N})=>{let V=Y.get($);if(!V)return;if(V.peers.delete(N),V.peers.size===0)V.expirationTimeout=setTimeout(()=>x($),h??0)})},async onMessage(F,$,N){let V=K(N),J=V.pc;if(F==="offer"){M({pc:J,userId:N.userId,initiator:!1}),await J.setRemoteDescription($);let f=await J.createAnswer();await J.setLocalDescription(f),N.receive("answer",J.localDescription?.toJSON()),await D(V);return}if(F==="answer"){await J.setRemoteDescription($),await D(V),M({pc:J,userId:N.userId,initiator:!0});return}if(F==="ice"){let f=$;if(!J.remoteDescription){V.pendingRemoteIce.push(f);return}try{await J.addIceCandidate(f)}catch(y){X("⚠️ ERROR",{error:"add-ice-failed",userId:V.userId,detail:String(y)})}return}}});A.set(`${B}/room/${z}`,{exitRoom:E,room:z,host:B})})}function T(z){q.delete(z)}function b(z){return q.add(z),()=>{T(z)}}return{enterRoom:H,exitRoom:_,leaveUser:x,getUsers:O,addUserListener:b,removeUserListener:T,getRooms(){return Array.from(A.values())},end(){A.forEach(({exitRoom:z})=>z()),A.clear(),Y.forEach(({userId:z})=>x(z)),q.clear()}}}export{u as collectPeerConnections};
|
|
2
|
+
|
|
3
|
+
//# debugId=B41F283EA3BA21EC64756E2164756E21
|
|
4
|
+
//# sourceMappingURL=webrtc-peer-collector.js.map
|