@dobuki/hello-worker 1.0.85 → 1.0.86
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.d.ts +2 -1
- package/dist/enter-world.d.ts.map +1 -1
- package/dist/enter-world.js +780 -2
- package/dist/enter-world.js.map +5 -5
- package/dist/index.js +1010 -3
- package/dist/index.js.map +8 -7
- package/dist/listeners.js +22 -2
- package/dist/listeners.js.map +2 -2
- package/dist/sample/index.d.ts.map +1 -1
- package/dist/signal/impl/signal-room.d.ts +1 -0
- package/dist/signal/impl/signal-room.d.ts.map +1 -1
- package/dist/signal/signal-room.d.ts +2 -1
- package/dist/signal/signal-room.d.ts.map +1 -1
- package/dist/signal/signal-room.worker.d.ts +1 -0
- package/dist/signal/signal-room.worker.d.ts.map +1 -1
- package/dist/web-components/sync-button.d.ts.map +1 -1
- package/dist/webrtc-peer-collector.d.ts +2 -1
- package/dist/webrtc-peer-collector.d.ts.map +1 -1
- package/dist/webrtc-peer-collector.js +632 -2
- package/dist/webrtc-peer-collector.js.map +5 -5
- package/package.json +6 -3
|
@@ -1,4 +1,634 @@
|
|
|
1
|
-
|
|
1
|
+
// src/browser/utils/ice-url-provider.ts
|
|
2
|
+
class IceUrlProvider {
|
|
3
|
+
sendToServerFunctions = new Array;
|
|
4
|
+
icePromiseResolve;
|
|
5
|
+
icePromise;
|
|
6
|
+
receiveIce(url, expiration) {
|
|
7
|
+
this.icePromiseResolve?.({ url, expiration });
|
|
8
|
+
this.icePromiseResolve = undefined;
|
|
9
|
+
this.icePromise = undefined;
|
|
10
|
+
}
|
|
11
|
+
addRequester(requester) {
|
|
12
|
+
this.sendToServerFunctions.push(requester);
|
|
13
|
+
return () => {
|
|
14
|
+
this.removeRequester(requester);
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
removeRequester(requester) {
|
|
18
|
+
this.sendToServerFunctions.splice(this.sendToServerFunctions.indexOf(requester), 1);
|
|
19
|
+
}
|
|
20
|
+
sendToServer(command) {
|
|
21
|
+
this.sendToServerFunctions[Math.floor(this.sendToServerFunctions.length * Math.random())](command);
|
|
22
|
+
}
|
|
23
|
+
async requestIce() {
|
|
24
|
+
if (!this.icePromise) {
|
|
25
|
+
this.icePromise = new Promise((resolve) => {
|
|
26
|
+
this.icePromiseResolve = resolve;
|
|
27
|
+
this.sendToServer("request-ice");
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
return await this.icePromise;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
2
33
|
|
|
3
|
-
|
|
34
|
+
// src/browser/signal/impl/signal-room.ts
|
|
35
|
+
function enterRoom(params) {
|
|
36
|
+
const {
|
|
37
|
+
userId,
|
|
38
|
+
worldId,
|
|
39
|
+
room,
|
|
40
|
+
host,
|
|
41
|
+
protocol,
|
|
42
|
+
autoRejoin = true,
|
|
43
|
+
logLine
|
|
44
|
+
} = params;
|
|
45
|
+
let exited = false;
|
|
46
|
+
let retryCount = 0;
|
|
47
|
+
let ws;
|
|
48
|
+
let timeoutId;
|
|
49
|
+
let initialConnection = true;
|
|
50
|
+
const peers = new Map;
|
|
51
|
+
const wsUrl = `${protocol ?? "wss"}://${host}/room/${worldId}/${room}?userId=${encodeURIComponent(userId)}`;
|
|
52
|
+
const accumulatedMessages = [];
|
|
53
|
+
let timeout = 0;
|
|
54
|
+
function send(type, to, payload) {
|
|
55
|
+
if (!ws) {
|
|
56
|
+
logLine?.("\uD83D\uDC64 ➡️ ❌", "no ws available");
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
const obj = { type, to, payload };
|
|
60
|
+
accumulatedMessages.push(obj);
|
|
61
|
+
logLine?.("\uD83D\uDC64 ➡️ \uD83D\uDDA5️", obj);
|
|
62
|
+
clearTimeout(timeout);
|
|
63
|
+
if (exited || ws.readyState !== WebSocket.OPEN) {
|
|
64
|
+
logLine?.("\uD83D\uDC64 ➡️ ❌", "Not in opened state: " + ws.readyState);
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
timeout = setTimeout(() => {
|
|
68
|
+
ws.send(JSON.stringify(accumulatedMessages));
|
|
69
|
+
accumulatedMessages.length = 0;
|
|
70
|
+
});
|
|
71
|
+
return true;
|
|
72
|
+
}
|
|
73
|
+
function connect() {
|
|
74
|
+
if (exited)
|
|
75
|
+
return;
|
|
76
|
+
ws = new WebSocket(wsUrl);
|
|
77
|
+
ws.onopen = () => {
|
|
78
|
+
if (initialConnection) {
|
|
79
|
+
params.onOpen?.();
|
|
80
|
+
initialConnection = false;
|
|
81
|
+
}
|
|
82
|
+
retryCount = 0;
|
|
83
|
+
};
|
|
84
|
+
ws.onmessage = (e) => {
|
|
85
|
+
try {
|
|
86
|
+
const result = JSON.parse(e.data);
|
|
87
|
+
const msgs = Array.isArray(result) ? result : [result];
|
|
88
|
+
msgs.forEach((msg) => {
|
|
89
|
+
logLine?.("\uD83D\uDDA5️ ➡️ \uD83D\uDC64", msg);
|
|
90
|
+
if (msg.type === "peer-joined" || msg.type === "peer-left") {
|
|
91
|
+
updatePeers(msg.users, msg);
|
|
92
|
+
} else if (msg.type === "ice-server") {
|
|
93
|
+
params.onIceUrl?.(msg.url, msg.expiration);
|
|
94
|
+
} else if (msg.userId) {
|
|
95
|
+
params.onMessage(msg.type, msg.payload, msg.userId);
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
} catch {
|
|
99
|
+
logLine?.("⚠️ ERROR", { error: "invalid-json" });
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
ws.onclose = (ev) => {
|
|
103
|
+
const recoverableCodes = [1001, 1006, 1011, 1012, 1013];
|
|
104
|
+
const isRecoverable = recoverableCodes.includes(ev.code);
|
|
105
|
+
if (autoRejoin && !exited && isRecoverable) {
|
|
106
|
+
const backoff = Math.min(Math.pow(2, retryCount) * 1000, 15000);
|
|
107
|
+
const jitter = Math.random() * 1000;
|
|
108
|
+
const delay = backoff + jitter;
|
|
109
|
+
logLine?.("\uD83D\uDD04 RECONNECTING", {
|
|
110
|
+
attempt: retryCount + 1,
|
|
111
|
+
delayMs: Math.round(delay)
|
|
112
|
+
});
|
|
113
|
+
retryCount++;
|
|
114
|
+
timeoutId = setTimeout(connect, delay);
|
|
115
|
+
} else {
|
|
116
|
+
params.onClose?.({
|
|
117
|
+
code: ev.code,
|
|
118
|
+
reason: ev.reason,
|
|
119
|
+
wasClean: ev.wasClean
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
ws.onerror = (ev) => {
|
|
124
|
+
console.error("WS Error", ev);
|
|
125
|
+
params.onError?.();
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
function updatePeers(updatedUsers, msg) {
|
|
129
|
+
const joined = [];
|
|
130
|
+
const left = [];
|
|
131
|
+
const updatedPeerSet = new Set;
|
|
132
|
+
const selfPeer = updatedUsers.filter((peer) => peer.userId === userId)[0];
|
|
133
|
+
if (!selfPeer) {
|
|
134
|
+
logLine?.("⚠️", "Cannot find self in updated users");
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
const selfJoined = selfPeer.joined;
|
|
138
|
+
updatedUsers.forEach(({ userId: pUserId, joined: joinedTime }) => {
|
|
139
|
+
if (pUserId === userId)
|
|
140
|
+
return;
|
|
141
|
+
if (!peers.has(pUserId) || msg.type === "peer-joined" && pUserId === msg.userId) {
|
|
142
|
+
const newPeer = {
|
|
143
|
+
userId: pUserId,
|
|
144
|
+
joined: joinedTime
|
|
145
|
+
};
|
|
146
|
+
peers.set(pUserId, newPeer);
|
|
147
|
+
joined.push(newPeer);
|
|
148
|
+
}
|
|
149
|
+
updatedPeerSet.add(pUserId);
|
|
150
|
+
});
|
|
151
|
+
for (const pUserId of peers.keys()) {
|
|
152
|
+
if (!updatedPeerSet.has(pUserId) || msg.type === "peer-left" && pUserId === msg.userId) {
|
|
153
|
+
peers.delete(pUserId);
|
|
154
|
+
left.push({ userId: pUserId });
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
if (joined.length)
|
|
158
|
+
params.onPeerJoined(joined, selfJoined);
|
|
159
|
+
if (left.length)
|
|
160
|
+
params.onPeerLeft(left);
|
|
161
|
+
}
|
|
162
|
+
connect();
|
|
163
|
+
return {
|
|
164
|
+
send(type, userId2, payload) {
|
|
165
|
+
send(type, userId2, payload);
|
|
166
|
+
},
|
|
167
|
+
exitRoom: () => {
|
|
168
|
+
exited = true;
|
|
169
|
+
clearTimeout(timeoutId);
|
|
170
|
+
ws.close();
|
|
171
|
+
}
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// src/browser/signal/signal-room.ts
|
|
176
|
+
function enterRoom2({
|
|
177
|
+
userId,
|
|
178
|
+
worldId,
|
|
179
|
+
room,
|
|
180
|
+
protocol = "wss",
|
|
181
|
+
host,
|
|
182
|
+
autoRejoin = true,
|
|
183
|
+
onOpen,
|
|
184
|
+
onClose,
|
|
185
|
+
onError,
|
|
186
|
+
onPeerJoined,
|
|
187
|
+
onPeerLeft,
|
|
188
|
+
onIceUrl,
|
|
189
|
+
onMessage,
|
|
190
|
+
logLine,
|
|
191
|
+
workerUrl
|
|
192
|
+
}) {
|
|
193
|
+
if (!workerUrl) {
|
|
194
|
+
const CDN_WORKER_URL = `https://cdn.jsdelivr.net/npm/@dobuki/hello-worker/dist/signal-room.worker.min.js`;
|
|
195
|
+
console.warn("Warning: enterRoom called without workerUrl; this may cause issues in some environments. You should pass workerUrl explicitly. Use:", CDN_WORKER_URL);
|
|
196
|
+
return enterRoom({
|
|
197
|
+
userId,
|
|
198
|
+
worldId,
|
|
199
|
+
room,
|
|
200
|
+
protocol,
|
|
201
|
+
host,
|
|
202
|
+
autoRejoin,
|
|
203
|
+
onOpen,
|
|
204
|
+
onClose,
|
|
205
|
+
onError,
|
|
206
|
+
onPeerJoined,
|
|
207
|
+
onPeerLeft,
|
|
208
|
+
onIceUrl,
|
|
209
|
+
onMessage
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
let worker;
|
|
213
|
+
const res = fetch(workerUrl).then(async (res2) => {
|
|
214
|
+
if (!res2.ok) {
|
|
215
|
+
throw new Error(`Failed to load worker script: ${res2.status}`);
|
|
216
|
+
}
|
|
217
|
+
const source = await res2.text();
|
|
218
|
+
const blob = new Blob([source], { type: "text/javascript" });
|
|
219
|
+
const blobUrl = URL.createObjectURL(blob);
|
|
220
|
+
worker = new Worker(blobUrl, { type: "module" });
|
|
221
|
+
worker.addEventListener("message", onWorkerMessage);
|
|
222
|
+
worker.postMessage({
|
|
223
|
+
cmd: "enter",
|
|
224
|
+
userId,
|
|
225
|
+
worldId,
|
|
226
|
+
room,
|
|
227
|
+
protocol,
|
|
228
|
+
host,
|
|
229
|
+
autoRejoin
|
|
230
|
+
});
|
|
231
|
+
});
|
|
232
|
+
let exited = false;
|
|
233
|
+
const onWorkerMessage = (e) => {
|
|
234
|
+
const ev = e.data;
|
|
235
|
+
if (ev.kind === "open")
|
|
236
|
+
onOpen?.();
|
|
237
|
+
else if (ev.kind === "close") {
|
|
238
|
+
worker?.terminate();
|
|
239
|
+
onClose?.(ev.ev);
|
|
240
|
+
} else if (ev.kind === "error")
|
|
241
|
+
onError?.();
|
|
242
|
+
else if (ev.kind === "peer-joined")
|
|
243
|
+
onPeerJoined(ev.users.map((ev2) => ({ userId: ev2.userId, joined: ev2.joined })), ev.joined);
|
|
244
|
+
else if (ev.kind === "peer-left")
|
|
245
|
+
onPeerLeft(ev.users);
|
|
246
|
+
else if (ev.kind === "ice-server")
|
|
247
|
+
onIceUrl?.(ev.url, ev.expiration);
|
|
248
|
+
else if (ev.kind === "message")
|
|
249
|
+
onMessage(ev.type, ev.payload, ev.fromUserId);
|
|
250
|
+
else if (ev.kind === "log")
|
|
251
|
+
logLine?.(ev.direction, ev.obj);
|
|
252
|
+
};
|
|
253
|
+
return {
|
|
254
|
+
exitRoom: () => {
|
|
255
|
+
exited = true;
|
|
256
|
+
worker?.removeEventListener("message", onWorkerMessage);
|
|
257
|
+
worker?.postMessage({ cmd: "exit" });
|
|
258
|
+
},
|
|
259
|
+
send: (type, toUserId, payload) => {
|
|
260
|
+
worker?.postMessage({
|
|
261
|
+
cmd: "send",
|
|
262
|
+
toUserId,
|
|
263
|
+
host,
|
|
264
|
+
room,
|
|
265
|
+
type,
|
|
266
|
+
payload
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// src/browser/utils/rtc-config.ts
|
|
273
|
+
var FALLBACK_RTC_CONFIG = {
|
|
274
|
+
iceServers: [{ urls: "stun:stun.l.google.com:19302" }]
|
|
275
|
+
};
|
|
276
|
+
|
|
277
|
+
class RTCConfigProvider {
|
|
278
|
+
iceUrlProvider;
|
|
279
|
+
constructor(iceUrlProvider) {
|
|
280
|
+
this.iceUrlProvider = iceUrlProvider;
|
|
281
|
+
}
|
|
282
|
+
rtcConfig = {
|
|
283
|
+
...FALLBACK_RTC_CONFIG,
|
|
284
|
+
timestamp: Date.now()
|
|
285
|
+
};
|
|
286
|
+
rtcConfigPromise;
|
|
287
|
+
async getRtcConfig() {
|
|
288
|
+
const now = Date.now();
|
|
289
|
+
if (now - (this.rtcConfig?.timestamp ?? 0) < 1e4) {
|
|
290
|
+
return this.rtcConfig;
|
|
291
|
+
}
|
|
292
|
+
if (!this.rtcConfigPromise) {
|
|
293
|
+
this.rtcConfigPromise = new Promise(async (resolve) => {
|
|
294
|
+
let retries = 3;
|
|
295
|
+
for (let r = 0;r < retries; r++) {
|
|
296
|
+
try {
|
|
297
|
+
const iceUrl = (await this.iceUrlProvider.requestIce()).url;
|
|
298
|
+
const r2 = await fetch(iceUrl);
|
|
299
|
+
if (!r2.ok)
|
|
300
|
+
throw new Error(`ICE endpoint failed: ${r2.status}`);
|
|
301
|
+
const rtcConfig = await r2.json();
|
|
302
|
+
resolve(rtcConfig);
|
|
303
|
+
return;
|
|
304
|
+
} catch (e) {
|
|
305
|
+
console.warn("Failed fetching iceUrl");
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
});
|
|
309
|
+
this.rtcConfig = await this.rtcConfigPromise;
|
|
310
|
+
this.rtcConfigPromise = undefined;
|
|
311
|
+
}
|
|
312
|
+
return this.rtcConfig;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// src/browser/webrtc-peer-collector.ts
|
|
317
|
+
var DEFAULT_ENTER_ROOM = enterRoom2;
|
|
318
|
+
function collectPeerConnections({
|
|
319
|
+
userId: passedUserId,
|
|
320
|
+
worldId,
|
|
321
|
+
receivePeerConnection,
|
|
322
|
+
peerlessUserExpiration = 5000,
|
|
323
|
+
enterRoomFunction: enterRoom3 = DEFAULT_ENTER_ROOM,
|
|
324
|
+
logLine,
|
|
325
|
+
onLeaveUser,
|
|
326
|
+
workerUrl,
|
|
327
|
+
onRoomReady,
|
|
328
|
+
onRoomClose,
|
|
329
|
+
onBroadcastMessage
|
|
330
|
+
}) {
|
|
331
|
+
const userId = passedUserId ?? `user-${crypto.randomUUID()}`;
|
|
332
|
+
const users = new Map;
|
|
333
|
+
const roomsEntered = new Map;
|
|
334
|
+
function leaveUser(userId2) {
|
|
335
|
+
onLeaveUser?.(userId2);
|
|
336
|
+
const p = users.get(userId2);
|
|
337
|
+
if (!p)
|
|
338
|
+
return;
|
|
339
|
+
users.delete(userId2);
|
|
340
|
+
try {
|
|
341
|
+
p.close();
|
|
342
|
+
} catch {}
|
|
343
|
+
}
|
|
344
|
+
async function flushRemoteIce(state) {
|
|
345
|
+
if (!state.connection?.pc?.remoteDescription)
|
|
346
|
+
return;
|
|
347
|
+
const queued = state.connection.pendingRemoteIce;
|
|
348
|
+
state.connection.pendingRemoteIce = [];
|
|
349
|
+
for (const ice of queued) {
|
|
350
|
+
try {
|
|
351
|
+
await state.connection.pc.addIceCandidate(ice);
|
|
352
|
+
} catch (e) {
|
|
353
|
+
logLine?.("⚠️ ERROR", {
|
|
354
|
+
error: "add-ice-failed",
|
|
355
|
+
userId: state.peer,
|
|
356
|
+
detail: String(e)
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
const iceUrlProvider = new IceUrlProvider;
|
|
362
|
+
const rtcConfigProvider = new RTCConfigProvider(iceUrlProvider);
|
|
363
|
+
function exit({ room, host }) {
|
|
364
|
+
const key = `${host}/room/${room}`;
|
|
365
|
+
const session = roomsEntered.get(key);
|
|
366
|
+
if (session) {
|
|
367
|
+
session.exitRoom();
|
|
368
|
+
roomsEntered.delete(key);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
function enter({
|
|
372
|
+
room,
|
|
373
|
+
host,
|
|
374
|
+
protocol
|
|
375
|
+
}) {
|
|
376
|
+
return new Promise(async (resolve, reject) => {
|
|
377
|
+
async function setupConnection(state) {
|
|
378
|
+
if (state.connectionPromise) {
|
|
379
|
+
return state.connectionPromise;
|
|
380
|
+
}
|
|
381
|
+
const promise = new Promise(async (resolve2) => {
|
|
382
|
+
state.connection = {
|
|
383
|
+
id: `conn-${crypto.randomUUID()}`,
|
|
384
|
+
pc: new RTCPeerConnection(await rtcConfigProvider.getRtcConfig()),
|
|
385
|
+
pendingRemoteIce: []
|
|
386
|
+
};
|
|
387
|
+
state.connection.pc.onicecandidate = (ev) => {
|
|
388
|
+
if (!ev.candidate)
|
|
389
|
+
return;
|
|
390
|
+
send("ice", state.peer, {
|
|
391
|
+
connectionId: state.connection?.id,
|
|
392
|
+
ice: ev.candidate.toJSON()
|
|
393
|
+
});
|
|
394
|
+
};
|
|
395
|
+
state.connection.pc.onconnectionstatechange = async () => {
|
|
396
|
+
logLine?.("\uD83D\uDCAC", {
|
|
397
|
+
event: "pc-state",
|
|
398
|
+
userId: state.peer,
|
|
399
|
+
state: state.connection?.pc?.connectionState
|
|
400
|
+
});
|
|
401
|
+
if (state.connection?.pc?.connectionState === "failed") {
|
|
402
|
+
state.close();
|
|
403
|
+
const userState = await getPeer(state.peer, true);
|
|
404
|
+
if (userState.connection?.pc) {
|
|
405
|
+
receivePeerConnection({
|
|
406
|
+
pc: userState.connection?.pc,
|
|
407
|
+
userId: userState.peer,
|
|
408
|
+
restart: () => userState.close()
|
|
409
|
+
});
|
|
410
|
+
} else {
|
|
411
|
+
logLine?.("\uD83D\uDC64ℹ️", "no pc: " + userState.peer);
|
|
412
|
+
}
|
|
413
|
+
return;
|
|
414
|
+
}
|
|
415
|
+
};
|
|
416
|
+
resolve2(state.connection);
|
|
417
|
+
});
|
|
418
|
+
state.connectionPromise = promise;
|
|
419
|
+
await promise;
|
|
420
|
+
state.connectionPromise = undefined;
|
|
421
|
+
return promise;
|
|
422
|
+
}
|
|
423
|
+
async function getPeer(peer, forceReset) {
|
|
424
|
+
let state = users.get(peer);
|
|
425
|
+
if (!state || forceReset) {
|
|
426
|
+
const newState = {
|
|
427
|
+
peer,
|
|
428
|
+
close() {
|
|
429
|
+
if (this.connection) {
|
|
430
|
+
this.connection.pc.close();
|
|
431
|
+
this.connection = undefined;
|
|
432
|
+
}
|
|
433
|
+
users.delete(peer);
|
|
434
|
+
},
|
|
435
|
+
async reset() {
|
|
436
|
+
newState.close();
|
|
437
|
+
setTimeout(async () => {
|
|
438
|
+
const userState = await getPeer(peer, true);
|
|
439
|
+
if (!userState.connection?.pc) {
|
|
440
|
+
logLine?.("⚠️", "no pc");
|
|
441
|
+
return;
|
|
442
|
+
}
|
|
443
|
+
receivePeerConnection({
|
|
444
|
+
pc: userState.connection?.pc,
|
|
445
|
+
userId: userState.peer,
|
|
446
|
+
restart: () => userState.close()
|
|
447
|
+
});
|
|
448
|
+
await makeOffer(userState.peer);
|
|
449
|
+
}, 500);
|
|
450
|
+
}
|
|
451
|
+
};
|
|
452
|
+
state = newState;
|
|
453
|
+
await setupConnection(newState);
|
|
454
|
+
users.set(state.peer, state);
|
|
455
|
+
} else if (state) {
|
|
456
|
+
clearTimeout(state.expirationTimeout);
|
|
457
|
+
state.expirationTimeout = 0;
|
|
458
|
+
if (!state.connection?.pc || state.connection?.pc.signalingState === "closed") {
|
|
459
|
+
await setupConnection(state);
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
state.peer = peer;
|
|
463
|
+
return state;
|
|
464
|
+
}
|
|
465
|
+
async function makeOffer(userId2) {
|
|
466
|
+
const state = await getPeer(userId2);
|
|
467
|
+
const pc = state.connection?.pc;
|
|
468
|
+
const offer = await pc?.createOffer();
|
|
469
|
+
await pc?.setLocalDescription(offer);
|
|
470
|
+
send("offer", userId2, {
|
|
471
|
+
connectionId: state.connection?.id,
|
|
472
|
+
offer: pc?.localDescription?.toJSON()
|
|
473
|
+
});
|
|
474
|
+
}
|
|
475
|
+
const { exitRoom, send } = enterRoom3({
|
|
476
|
+
userId,
|
|
477
|
+
worldId,
|
|
478
|
+
room,
|
|
479
|
+
protocol,
|
|
480
|
+
host,
|
|
481
|
+
logLine,
|
|
482
|
+
workerUrl,
|
|
483
|
+
autoRejoin: true,
|
|
484
|
+
onOpen() {
|
|
485
|
+
onRoomReady?.({ room, host });
|
|
486
|
+
resolve();
|
|
487
|
+
},
|
|
488
|
+
onError() {
|
|
489
|
+
console.error("onError");
|
|
490
|
+
reject();
|
|
491
|
+
},
|
|
492
|
+
onClose(ev) {
|
|
493
|
+
onRoomClose?.({ room, host, ev });
|
|
494
|
+
},
|
|
495
|
+
onPeerJoined(joiningUsers, selfJoined) {
|
|
496
|
+
joiningUsers.forEach(async (user) => {
|
|
497
|
+
const state = await getPeer(user.userId, true);
|
|
498
|
+
state.joined = user.joined;
|
|
499
|
+
const pc = state.connection?.pc;
|
|
500
|
+
if (!pc) {
|
|
501
|
+
logLine?.("\uD83D\uDC64ℹ️", "no pc: " + user.userId);
|
|
502
|
+
return;
|
|
503
|
+
}
|
|
504
|
+
receivePeerConnection({
|
|
505
|
+
pc,
|
|
506
|
+
userId: user.userId,
|
|
507
|
+
restart: () => state.close()
|
|
508
|
+
});
|
|
509
|
+
if (user.joined > selfJoined || user.joined === selfJoined && user.userId.localeCompare(userId) > 0) {
|
|
510
|
+
await makeOffer(user.userId);
|
|
511
|
+
}
|
|
512
|
+
});
|
|
513
|
+
},
|
|
514
|
+
onPeerLeft(leavingUsers) {
|
|
515
|
+
leavingUsers.forEach(({ userId: userId2 }) => {
|
|
516
|
+
const state = users.get(userId2);
|
|
517
|
+
if (!state)
|
|
518
|
+
return;
|
|
519
|
+
state.expirationTimeout = setTimeout(() => leaveUser(userId2), peerlessUserExpiration ?? 0);
|
|
520
|
+
});
|
|
521
|
+
},
|
|
522
|
+
onIceUrl(url, expiration) {
|
|
523
|
+
iceUrlProvider.receiveIce(url, expiration);
|
|
524
|
+
},
|
|
525
|
+
async onMessage(type, payload, from) {
|
|
526
|
+
if (type === "offer" && payload.offer) {
|
|
527
|
+
const state = await getPeer(from, false);
|
|
528
|
+
const connection = !state.connection || state.connection.pc.signalingState === "stable" ? await setupConnection(state) : state.connection;
|
|
529
|
+
logLine?.("\uD83D\uDCAC", {
|
|
530
|
+
type,
|
|
531
|
+
signalingState: connection.pc.signalingState
|
|
532
|
+
});
|
|
533
|
+
connection.peerConnectionId = payload.connectionId;
|
|
534
|
+
receivePeerConnection({
|
|
535
|
+
pc: connection.pc,
|
|
536
|
+
userId: from,
|
|
537
|
+
restart: () => state.close()
|
|
538
|
+
});
|
|
539
|
+
await connection.pc.setRemoteDescription(payload.offer);
|
|
540
|
+
const answer = await connection.pc.createAnswer();
|
|
541
|
+
await connection.pc.setLocalDescription(answer);
|
|
542
|
+
send("answer", from, {
|
|
543
|
+
connectionId: connection.id,
|
|
544
|
+
answer: connection.pc.localDescription?.toJSON()
|
|
545
|
+
});
|
|
546
|
+
await flushRemoteIce(state);
|
|
547
|
+
return;
|
|
548
|
+
}
|
|
549
|
+
if (type === "answer" && payload.answer) {
|
|
550
|
+
const state = await getPeer(from, false);
|
|
551
|
+
const connection = state.connection && state.connection.pc.signalingState !== "closed" ? state.connection : await setupConnection(state);
|
|
552
|
+
logLine?.("\uD83D\uDCAC", {
|
|
553
|
+
type,
|
|
554
|
+
signalingState: connection.pc.signalingState
|
|
555
|
+
});
|
|
556
|
+
await connection.pc.setRemoteDescription(payload.answer);
|
|
557
|
+
connection.peerConnectionId = payload.connectionId;
|
|
558
|
+
await flushRemoteIce(state);
|
|
559
|
+
return;
|
|
560
|
+
}
|
|
561
|
+
if (type === "ice" && payload.ice) {
|
|
562
|
+
const state = await getPeer(from, false);
|
|
563
|
+
const connection = state.connection ?? await state.connectionPromise;
|
|
564
|
+
if (!connection) {
|
|
565
|
+
logLine?.("⚠️", "No connection");
|
|
566
|
+
return;
|
|
567
|
+
}
|
|
568
|
+
logLine?.("\uD83D\uDCAC", {
|
|
569
|
+
type,
|
|
570
|
+
signalingState: connection.pc.signalingState
|
|
571
|
+
});
|
|
572
|
+
if (connection.peerConnectionId && payload.connectionId !== connection.peerConnectionId) {
|
|
573
|
+
logLine?.("⚠️", "Mismatch peerConnectionID" + payload.connectionId + "vs" + connection.peerConnectionId);
|
|
574
|
+
return;
|
|
575
|
+
}
|
|
576
|
+
if (!connection.pc.remoteDescription || !connection.peerConnectionId) {
|
|
577
|
+
connection.peerConnectionId = payload.connectionId;
|
|
578
|
+
connection.pendingRemoteIce.push(payload.ice);
|
|
579
|
+
return;
|
|
580
|
+
}
|
|
581
|
+
try {
|
|
582
|
+
await connection.pc.addIceCandidate(payload.ice);
|
|
583
|
+
} catch (e) {
|
|
584
|
+
logLine?.("⚠️ ERROR", {
|
|
585
|
+
error: "add-ice-failed",
|
|
586
|
+
userId: state.peer,
|
|
587
|
+
detail: String(e)
|
|
588
|
+
});
|
|
589
|
+
}
|
|
590
|
+
return;
|
|
591
|
+
}
|
|
592
|
+
if (type === "broadcast") {
|
|
593
|
+
onBroadcastMessage?.(payload, from);
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
});
|
|
597
|
+
const removeRequester = iceUrlProvider.addRequester((command) => send(command, "server"));
|
|
598
|
+
roomsEntered.set(`${host}/room/${room}`, {
|
|
599
|
+
exitRoom: () => {
|
|
600
|
+
exitRoom();
|
|
601
|
+
removeRequester();
|
|
602
|
+
},
|
|
603
|
+
room,
|
|
604
|
+
host,
|
|
605
|
+
broadcast: (payload) => send("broadcast", "server", payload)
|
|
606
|
+
});
|
|
607
|
+
});
|
|
608
|
+
}
|
|
609
|
+
return {
|
|
610
|
+
userId,
|
|
611
|
+
enterRoom: enter,
|
|
612
|
+
exitRoom: exit,
|
|
613
|
+
leaveUser,
|
|
614
|
+
async reset(userId2) {
|
|
615
|
+
const userState = users.get(userId2);
|
|
616
|
+
userState?.reset();
|
|
617
|
+
},
|
|
618
|
+
broadcast(payload) {
|
|
619
|
+
roomsEntered.forEach((room) => room.broadcast(payload));
|
|
620
|
+
},
|
|
621
|
+
end() {
|
|
622
|
+
roomsEntered.forEach(({ exitRoom }) => exitRoom());
|
|
623
|
+
roomsEntered.clear();
|
|
624
|
+
users.forEach(({ peer }) => leaveUser(peer));
|
|
625
|
+
users.clear();
|
|
626
|
+
}
|
|
627
|
+
};
|
|
628
|
+
}
|
|
629
|
+
export {
|
|
630
|
+
collectPeerConnections
|
|
631
|
+
};
|
|
632
|
+
|
|
633
|
+
//# debugId=FD438A7CC3C2722364756E2164756E21
|
|
4
634
|
//# sourceMappingURL=webrtc-peer-collector.js.map
|