@dobuki/hello-worker 1.0.4 → 1.0.6

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.
@@ -27,32 +27,31 @@ self.addEventListener("message", (e) => {
27
27
  },
28
28
  onPeerJoined: (user) => {
29
29
  // Save the ability to send to this peer
30
- peerSend.set(user.userId, (type, payload) => user.receive(type, payload));
31
- emit({ kind: "peer-joined", userId: user.userId });
30
+ peerSend.set(user.peerId, user.receive);
31
+ emit({ kind: "peer-joined", userId: user.userId, peerId: user.peerId });
32
32
  },
33
- onPeerLeft: (userId) => {
34
- peerSend.delete(userId);
35
- emit({ kind: "peer-left", userId });
33
+ onPeerLeft: (userId, peerId) => {
34
+ peerSend.delete(peerId);
35
+ emit({ kind: "peer-left", userId, peerId });
36
36
  },
37
37
  onMessage: (type, payload, from) => {
38
38
  // We can also learn peerSend via onMessage in case join events vary
39
- peerSend.set(from.userId, (t, p) => from.receive(t, p));
40
- emit({ kind: "message", type, payload, fromUserId: from.userId });
39
+ peerSend.set(from.peerId, from.receive);
40
+ emit({ kind: "message", type, payload, fromUserId: from.userId, fromPeerId: from.peerId });
41
41
  },
42
42
  });
43
43
  exitRoom = result.exitRoom;
44
44
  return;
45
45
  }
46
46
  if (msg.cmd === "send") {
47
- const sendFn = peerSend.get(msg.toUserId);
47
+ const sendFn = peerSend.get(msg.toPeerId);
48
48
  if (sendFn)
49
49
  sendFn(msg.type, msg.payload);
50
50
  return;
51
51
  }
52
52
  if (msg.cmd === "exit") {
53
53
  exitRoom?.();
54
- exitRoom = null;
55
- peerSend.clear();
54
+ self.close();
56
55
  return;
57
56
  }
58
57
  });
@@ -1 +1 @@
1
- {"version":3,"file":"signal-room.worker.js","sourceRoot":"","sources":["../src/browser/signal-room.worker.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"signal-room.worker.js","sourceRoot":"","sources":["../src/browser/signal-room.worker.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,34 @@
1
+ import { EnterRoom } from "./signal-room";
2
+ export type SigType = "offer" | "answer" | "ice";
3
+ export type SigPayload = RTCSessionDescriptionInit | RTCIceCandidateInit;
4
+ export declare function collectPeerConnections({ userId, receivePeerConnection, leaveUserWithoutPeer, rtcConfig, enterRoomFunction: enterRoom, logLine, onLeaveUser, workerUrl, }: {
5
+ userId: string;
6
+ rtcConfig?: RTCConfiguration;
7
+ enterRoomFunction?: EnterRoom<SigType, SigPayload>;
8
+ onLeaveUser?: (userId: string) => void;
9
+ logLine?: (direction: string, obj?: any) => void;
10
+ workerUrl?: URL;
11
+ leaveUserWithoutPeer?: boolean;
12
+ receivePeerConnection(connection: {
13
+ pc: RTCPeerConnection;
14
+ userId: string;
15
+ initiator: boolean;
16
+ }): void;
17
+ }): {
18
+ enterRoom: ({ room, host }: {
19
+ room: string;
20
+ host: string;
21
+ }) => void;
22
+ exitRoom: ({ room, host }: {
23
+ room: string;
24
+ host: string;
25
+ }) => void;
26
+ leaveUser: (userId: string) => void;
27
+ getUsers(): string[];
28
+ getRooms(): {
29
+ room: string;
30
+ host: string;
31
+ exitRoom: () => void;
32
+ }[];
33
+ };
34
+ //# sourceMappingURL=webrtc-peer-collector.d.ts.map
@@ -0,0 +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;AAgBzE,wBAAgB,sBAAsB,CAAC,EACrC,MAAM,EACN,qBAAqB,EACrB,oBAA4B,EAC5B,SAAsE,EACtE,iBAAiB,EAAE,SAA8B,EACjD,OAAuB,EACvB,WAAW,EACX,SAAS,GACV,EAAE;IACD,MAAM,EAAE,MAAM,CAAC;IACf,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,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,qBAAqB,CAAC,UAAU,EAAE;QAAE,EAAE,EAAE,iBAAiB,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,OAAO,CAAA;KAAE,GAAG,IAAI,CAAC;CACxG;gCAoEgC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;KAAE;+BAThC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;KAAE;wBA1BlC,MAAM;;;cAwBY,MAAM;cAAQ,MAAM;kBAAY,MAAM,IAAI;;EAwGxF"}
@@ -0,0 +1,154 @@
1
+ import { enterRoom } from "./signal-room";
2
+ const DEFAULT_ENTER_ROOM = enterRoom;
3
+ export function collectPeerConnections({ userId, receivePeerConnection, leaveUserWithoutPeer = false, rtcConfig = { iceServers: [{ urls: "stun:stun.l.google.com:19302" }] }, enterRoomFunction: enterRoom = DEFAULT_ENTER_ROOM, logLine = console.debug, onLeaveUser, workerUrl, }) {
4
+ const users = new Map();
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
+ // Offer flow: createOffer -> setLocalDescription -> send localDescription
83
+ const offer = await pc.createOffer();
84
+ await pc.setLocalDescription(offer);
85
+ user.receive("offer", pc.localDescription?.toJSON());
86
+ },
87
+ onPeerLeft(userId, peerId) {
88
+ const state = users.get(userId);
89
+ if (!state)
90
+ return;
91
+ for (const user of state.peers) {
92
+ if (user.peerId === peerId) {
93
+ state.peers.delete(user);
94
+ break;
95
+ }
96
+ }
97
+ if (state.peers.size === 0 && leaveUserWithoutPeer) {
98
+ leaveUser(userId);
99
+ }
100
+ },
101
+ async onMessage(type, payload, from) {
102
+ const state = getPeer(from);
103
+ const pc = state.pc;
104
+ if (type === "offer") {
105
+ // Responder: set remote offer
106
+ await pc.setRemoteDescription(payload);
107
+ // Create and send answer
108
+ const answer = await pc.createAnswer();
109
+ await pc.setLocalDescription(answer);
110
+ from.receive("answer", pc.localDescription?.toJSON());
111
+ // Now safe to apply any queued ICE from this peer
112
+ await flushRemoteIce(state);
113
+ receivePeerConnection({ pc, userId: from.userId, initiator: false });
114
+ return;
115
+ }
116
+ if (type === "answer") {
117
+ // Initiator: set remote answer
118
+ await pc.setRemoteDescription(payload);
119
+ await flushRemoteIce(state);
120
+ receivePeerConnection({ pc, userId: from.userId, initiator: true });
121
+ return;
122
+ }
123
+ if (type === "ice") {
124
+ const ice = payload;
125
+ // If we don't have remoteDescription yet, queue it
126
+ if (!pc.remoteDescription) {
127
+ state.pendingRemoteIce.push(ice);
128
+ return;
129
+ }
130
+ try {
131
+ await pc.addIceCandidate(ice);
132
+ }
133
+ catch (e) {
134
+ logLine("⚠️ ERROR", { error: "add-ice-failed", userId: state.userId, detail: String(e) });
135
+ }
136
+ return;
137
+ }
138
+ },
139
+ });
140
+ roomsEntered.set(`${host}/room/${room}`, { exitRoom, room, host });
141
+ }
142
+ return {
143
+ enterRoom: enter,
144
+ exitRoom: exit,
145
+ leaveUser,
146
+ getUsers() {
147
+ return Array.from(users.keys());
148
+ },
149
+ getRooms() {
150
+ return Array.from(roomsEntered.values());
151
+ },
152
+ };
153
+ }
154
+ //# sourceMappingURL=webrtc-peer-collector.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webrtc-peer-collector.js","sourceRoot":"","sources":["../src/browser/webrtc-peer-collector.ts"],"names":[],"mappings":"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"}
@@ -1,24 +1,52 @@
1
1
  import { EnterRoom } from "./signal-room";
2
2
  type SigType = "offer" | "answer" | "ice";
3
3
  type SigPayload = RTCSessionDescriptionInit | RTCIceCandidateInit;
4
- export declare function joinWebRTCRoom({ onMessage, logLine, enterRoom, workerUrl, }: {
4
+ export declare function collectPeerConnections({ userId, receivePeerConnection, leaveUserWithoutPeer, rtcConfig, enterRoomFunction: enterRoom, logLine, onLeaveUser, workerUrl, }: {
5
+ userId: string;
6
+ rtcConfig?: RTCConfiguration;
7
+ enterRoomFunction?: EnterRoom<SigType, SigPayload>;
8
+ onLeaveUser?: (userId: string) => void;
9
+ logLine?: (direction: string, obj?: any) => void;
10
+ workerUrl?: URL;
11
+ leaveUserWithoutPeer?: boolean;
12
+ receivePeerConnection(connection: {
13
+ pc: RTCPeerConnection;
14
+ userId: string;
15
+ initiator: boolean;
16
+ }): void;
17
+ }): {
18
+ enterRoom: ({ room, host }: {
19
+ room: string;
20
+ host: string;
21
+ }) => void;
22
+ exitRoom: ({ room, host }: {
23
+ room: string;
24
+ host: string;
25
+ }) => void;
26
+ leaveUser: (userId: string) => void;
27
+ getUsers(): string[];
28
+ };
29
+ export declare function joinWebRTCRoom({ uid, onMessage, logLine, enterRoomFunction, autoLeaveUsers, workerUrl, }: {
30
+ uid?: string;
5
31
  onMessage?: (data: any, from: string) => void;
6
32
  logLine?: (direction: string, obj?: any) => void;
7
- enterRoom?: EnterRoom<SigType, SigPayload>;
33
+ enterRoomFunction?: EnterRoom<SigType, SigPayload>;
34
+ autoLeaveUsers?: boolean;
8
35
  workerUrl?: URL;
9
36
  }): {
10
- userId: `${string}-${string}-${string}-${string}-${string}`;
11
- sendToUser: (userId: string, data: string) => void;
12
- sendToAll: (data: string) => void;
13
- end: () => void;
14
- enter: ({ room, host }: {
37
+ userId: string;
38
+ send: (data: any, userId?: string) => void;
39
+ enterRoom: ({ room, host }: {
15
40
  room: string;
16
41
  host: string;
17
42
  }) => void;
18
- exit: ({ room, host }: {
43
+ exitRoom: ({ room, host }: {
19
44
  room: string;
20
45
  host: string;
21
46
  }) => void;
47
+ leaveUser: (userId: string) => void;
48
+ getUsers: () => string[];
49
+ end(): void;
22
50
  };
23
51
  export {};
24
52
  //# sourceMappingURL=webrtc-room.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"webrtc-room.d.ts","sourceRoot":"","sources":["../src/browser/webrtc-room.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAoB,MAAM,eAAe,CAAC;AAE5D,KAAK,OAAO,GAAG,OAAO,GAAG,QAAQ,GAAG,KAAK,CAAC;AAC1C,KAAK,UAAU,GAAG,yBAAyB,GAAG,mBAAmB,CAAC;AAiBlE,wBAAgB,cAAc,CAAC,EAC7B,SAAS,EACT,OAAuB,EACvB,SAA8B,EAC9B,SAAS,GACV,EAAE;IACD,SAAS,CAAC,EAAE,CAAC,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC9C,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,GAAG,KAAK,IAAI,CAAC;IACjD,SAAS,CAAC,EAAE,SAAS,CAAC,OAAO,EAAE,UAAU,CAAC,CAAC;IAC3C,SAAS,CAAC,EAAE,GAAG,CAAC;CACjB;;yBAsL6B,MAAM,QAAQ,MAAM;sBAMvB,MAAM;;4BAxGA;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;KAAE;2BAyFhC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;KAAE;EAuC9D"}
1
+ {"version":3,"file":"webrtc-room.d.ts","sourceRoot":"","sources":["../src/browser/webrtc-room.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAa,MAAM,eAAe,CAAC;AAErD,KAAK,OAAO,GAAG,OAAO,GAAG,QAAQ,GAAG,KAAK,CAAC;AAC1C,KAAK,UAAU,GAAG,yBAAyB,GAAG,mBAAmB,CAAC;AAgBlE,wBAAgB,sBAAsB,CAAC,EACrC,MAAM,EACN,qBAAqB,EACrB,oBAA4B,EAC5B,SAAsE,EACtE,iBAAiB,EAAE,SAA8B,EACjD,OAAuB,EACvB,WAAW,EACX,SAAS,GACV,EAAE;IACD,MAAM,EAAE,MAAM,CAAC;IACf,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,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,qBAAqB,CAAC,UAAU,EAAE;QAAE,EAAE,EAAE,iBAAiB,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,OAAO,CAAA;KAAE,GAAG,IAAI,CAAC;CACxG;gCAoEgC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;KAAE;+BAThC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;KAAE;wBA1BlC,MAAM;;EAsHlC;AAGD,wBAAgB,cAAc,CAAC,EAC7B,GAAG,EACH,SAAS,EACT,OAAuB,EACvB,iBAAsC,EACtC,cAAsB,EACtB,SAAS,GACV,EAAE;IACD,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,CAAC,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC9C,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,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,SAAS,CAAC,EAAE,GAAG,CAAC;CACjB;;iBA+CqB,GAAG,WAAW,MAAM;gCAnJT;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;KAAE;+BAThC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;KAAE;wBA1BlC,MAAM;;;EA2MlC"}
@@ -1,91 +1,73 @@
1
1
  import { enterRoom } from "./signal-room";
2
2
  const DEFAULT_ENTER_ROOM = enterRoom;
3
- export function joinWebRTCRoom({ onMessage, logLine = console.debug, enterRoom = DEFAULT_ENTER_ROOM, workerUrl, }) {
4
- const userId = crypto.randomUUID();
5
- const rtcConfig = {
6
- iceServers: [{ urls: "stun:stun.l.google.com:19302" }],
7
- };
8
- const peers = new Map();
9
- function wireDataChannel(state) {
10
- const dc = state.dataChannel;
11
- if (!dc)
12
- return;
13
- dc.onopen = () => logLine("💬", { event: "dc-open", userId: state.userId });
14
- dc.onmessage = (e) => {
15
- onMessage?.(e.data, state.userId);
16
- logLine("💬", { event: "dc-message", userId: state.userId, data: e.data });
17
- };
18
- dc.onclose = () => logLine("💬", { event: "dc-close", userId: state.userId });
19
- dc.onerror = () => logLine("⚠️ ERROR", { error: "dc-error", userId: state.userId });
20
- }
21
- async function flushRemoteIce(state) {
22
- if (!state.pc.remoteDescription)
23
- return;
24
- const queued = state.pendingRemoteIce;
25
- state.pendingRemoteIce = [];
26
- for (const ice of queued) {
27
- try {
28
- await state.pc.addIceCandidate(ice);
29
- }
30
- catch (e) {
31
- logLine("⚠️ ERROR", { error: "add-ice-failed", userId: state.userId, detail: String(e) });
32
- }
33
- }
34
- }
35
- function getPeer(user) {
36
- let state = peers.get(user.userId);
3
+ export function collectPeerConnections({ userId, receivePeerConnection, leaveUserWithoutPeer = false, rtcConfig = { iceServers: [{ urls: "stun:stun.l.google.com:19302" }] }, enterRoomFunction: enterRoom = DEFAULT_ENTER_ROOM, logLine = console.debug, onLeaveUser, workerUrl, }) {
4
+ const users = new Map();
5
+ function getPeer(peer) {
6
+ let state = users.get(peer.userId);
37
7
  if (!state) {
38
8
  const newState = {
39
- userId: user.userId,
9
+ userId: peer.userId,
40
10
  pc: new RTCPeerConnection(rtcConfig),
41
11
  pendingRemoteIce: [],
42
- users: new Set([user]),
43
- dataChannel: null,
12
+ peers: new Set([peer]),
44
13
  };
45
- peers.set(user.userId, newState);
14
+ users.set(peer.userId, newState);
46
15
  // Send local ICE candidates to this peer
47
16
  newState.pc.onicecandidate = (ev) => {
48
17
  if (!ev.candidate)
49
18
  return;
50
- for (let user of newState.users) {
19
+ for (let user of newState.peers) {
51
20
  const success = user.receive("ice", ev.candidate.toJSON());
52
21
  if (success)
53
22
  break;
54
- newState.users.delete(user);
55
23
  }
56
24
  };
57
- // Responder receives DataChannel here
58
- newState.pc.ondatachannel = (ev) => {
59
- newState.dataChannel = ev.channel;
60
- wireDataChannel(newState);
61
- };
62
25
  newState.pc.onconnectionstatechange = () => {
63
26
  logLine("💬", { event: "pc-state", userId: newState.userId, state: newState.pc.connectionState });
64
27
  };
65
28
  state = newState;
66
29
  }
67
30
  else {
68
- state.users.add(user);
31
+ state.peers.add(peer);
69
32
  }
70
- peers.set(state.userId, state);
33
+ users.set(state.userId, state);
71
34
  return state;
72
35
  }
73
36
  function leaveUser(userId) {
74
- const p = peers.get(userId);
37
+ onLeaveUser?.(userId);
38
+ const p = users.get(userId);
75
39
  if (!p)
76
40
  return;
77
- try {
78
- p.dataChannel?.close();
79
- }
80
- catch { }
81
41
  try {
82
42
  p.pc.close();
83
43
  }
84
44
  catch { }
85
- peers.delete(userId);
45
+ users.delete(userId);
86
46
  logLine("👤 USER LEFT", userId);
87
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
+ }
88
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
+ }
89
71
  function enter({ room, host }) {
90
72
  const { exitRoom } = enterRoom({
91
73
  userId,
@@ -94,42 +76,29 @@ export function joinWebRTCRoom({ onMessage, logLine = console.debug, enterRoom =
94
76
  logLine,
95
77
  workerUrl,
96
78
  // Existing peers initiate to the newcomer (Option 1)
97
- onPeerJoined: async (user) => {
79
+ async onPeerJoined(user) {
98
80
  const state = getPeer(user);
99
81
  const pc = state.pc;
100
- // Initiator creates the DataChannel
101
- if (!state.dataChannel) {
102
- state.dataChannel = pc.createDataChannel("data");
103
- wireDataChannel(state);
104
- }
105
82
  // Offer flow: createOffer -> setLocalDescription -> send localDescription
106
83
  const offer = await pc.createOffer();
107
84
  await pc.setLocalDescription(offer);
108
- user.receive("offer", pc.localDescription);
85
+ user.receive("offer", pc.localDescription?.toJSON());
109
86
  },
110
- onPeerLeft: (userId) => {
111
- const state = peers.get(userId);
87
+ onPeerLeft(userId, peerId) {
88
+ const state = users.get(userId);
112
89
  if (!state)
113
90
  return;
114
- for (const user of state.users) {
115
- if (user.userId === userId) {
116
- state.users.delete(user);
91
+ for (const user of state.peers) {
92
+ if (user.peerId === peerId) {
93
+ state.peers.delete(user);
117
94
  break;
118
95
  }
119
96
  }
120
- if (state.users.size === 0) {
121
- try {
122
- state.dataChannel?.close();
123
- }
124
- catch { }
125
- try {
126
- state.pc.close();
127
- }
128
- catch { }
97
+ if (state.peers.size === 0 && leaveUserWithoutPeer) {
129
98
  leaveUser(userId);
130
99
  }
131
100
  },
132
- onMessage: async (type, payload, from) => {
101
+ async onMessage(type, payload, from) {
133
102
  const state = getPeer(from);
134
103
  const pc = state.pc;
135
104
  if (type === "offer") {
@@ -138,15 +107,17 @@ export function joinWebRTCRoom({ onMessage, logLine = console.debug, enterRoom =
138
107
  // Create and send answer
139
108
  const answer = await pc.createAnswer();
140
109
  await pc.setLocalDescription(answer);
141
- from.receive("answer", pc.localDescription);
110
+ from.receive("answer", pc.localDescription?.toJSON());
142
111
  // Now safe to apply any queued ICE from this peer
143
112
  await flushRemoteIce(state);
113
+ receivePeerConnection({ pc, userId: from.userId, initiator: false });
144
114
  return;
145
115
  }
146
116
  if (type === "answer") {
147
117
  // Initiator: set remote answer
148
118
  await pc.setRemoteDescription(payload);
149
119
  await flushRemoteIce(state);
120
+ receivePeerConnection({ pc, userId: from.userId, initiator: true });
150
121
  return;
151
122
  }
152
123
  if (type === "ice") {
@@ -166,50 +137,80 @@ export function joinWebRTCRoom({ onMessage, logLine = console.debug, enterRoom =
166
137
  }
167
138
  },
168
139
  });
169
- roomsEntered.set(`${host}/room/${room}`, { exitRoom, host, room });
170
- }
171
- function exit({ room, host }) {
172
- const key = `${host}/room/${room}`;
173
- const session = roomsEntered.get(key);
174
- if (session) {
175
- session.exitRoom();
176
- roomsEntered.delete(key);
177
- }
140
+ roomsEntered.set(`${host}/room/${room}`, { exitRoom });
178
141
  }
179
- const sendToUser = (userId, data) => {
180
- const p = peers.get(userId);
181
- if (!p)
182
- return;
183
- if (p.dataChannel?.readyState === "open")
184
- p.dataChannel.send(data);
142
+ return { enterRoom: enter, exitRoom: exit, leaveUser, getUsers() { return Array.from(users.keys()); } };
143
+ }
144
+ export function joinWebRTCRoom({ uid, onMessage, logLine = console.debug, enterRoomFunction = DEFAULT_ENTER_ROOM, autoLeaveUsers = false, workerUrl, }) {
145
+ const userId = uid ?? `user-${crypto.randomUUID()}`;
146
+ const rtcConfig = {
147
+ iceServers: [{ urls: "stun:stun.l.google.com:19302" }],
185
148
  };
186
- function sendToAll(data) {
187
- for (const p of peers.values()) {
188
- if (p.dataChannel?.readyState === "open")
189
- p.dataChannel.send(data);
190
- }
149
+ function wireDataChannel(userId, dc) {
150
+ dc.onopen = () => logLine("💬", { event: "dc-open", userId });
151
+ dc.onmessage = ({ data }) => {
152
+ onMessage?.(data, userId);
153
+ logLine("💬", { event: "dc-message", userId, data: data });
154
+ };
155
+ dc.onclose = () => logLine("💬", { event: "dc-close", userId });
156
+ dc.onerror = () => logLine("⚠️ ERROR", { error: "dc-error", userId });
157
+ }
158
+ const dataChannels = new Map();
159
+ const { enterRoom, exitRoom, leaveUser, getUsers } = collectPeerConnections({
160
+ userId,
161
+ rtcConfig,
162
+ enterRoomFunction,
163
+ logLine,
164
+ workerUrl,
165
+ leaveUserWithoutPeer: autoLeaveUsers,
166
+ onLeaveUser: (userId) => {
167
+ const dc = dataChannels.get(userId);
168
+ try {
169
+ dc?.close();
170
+ }
171
+ catch { }
172
+ dataChannels.delete(userId);
173
+ },
174
+ receivePeerConnection({ pc, userId, initiator }) {
175
+ if (initiator) {
176
+ const dc = pc.createDataChannel("data");
177
+ wireDataChannel(userId, dc);
178
+ dataChannels.set(userId, dc);
179
+ }
180
+ else {
181
+ pc.ondatachannel = (ev) => {
182
+ const dc = ev.channel;
183
+ wireDataChannel(userId, dc);
184
+ dataChannels.set(userId, dc);
185
+ };
186
+ }
187
+ logLine("💬", { event: "pc-ready", userId, initiator });
188
+ },
189
+ });
190
+ function send(data, userId) {
191
+ dataChannels.forEach((dataChannel, pUserId) => {
192
+ if (userId && pUserId !== userId)
193
+ return;
194
+ if (dataChannel.readyState === "open")
195
+ dataChannel.send(data);
196
+ });
191
197
  }
192
198
  return {
193
199
  userId,
194
- sendToUser,
195
- sendToAll,
196
- end: () => {
197
- roomsEntered.values().forEach(({ exitRoom }) => exitRoom());
198
- roomsEntered.clear();
199
- for (const p of peers.values()) {
200
+ send,
201
+ enterRoom,
202
+ exitRoom,
203
+ leaveUser,
204
+ getUsers,
205
+ end() {
206
+ dataChannels.forEach((dataChannel) => {
200
207
  try {
201
- p.dataChannel?.close();
208
+ dataChannel.close();
202
209
  }
203
210
  catch { }
204
- try {
205
- p.pc.close();
206
- }
207
- catch { }
208
- }
209
- peers.clear();
211
+ });
212
+ dataChannels.clear();
210
213
  },
211
- enter,
212
- exit,
213
214
  };
214
215
  }
215
216
  //# sourceMappingURL=webrtc-room.js.map