@ai-matrx/meet 0.11.28 → 0.11.29
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/CHANGELOG.md +6 -0
- package/dist/{collab-CUxjUtrb.d.cts → collab-XY6Xj1dG.d.cts} +5 -0
- package/dist/{collab-CUxjUtrb.d.ts → collab-XY6Xj1dG.d.ts} +5 -0
- package/dist/{components-DPdU3POT.d.cts → components-4f9Mf8fz.d.cts} +1 -1
- package/dist/{components-CUPzD0y3.d.ts → components-Cwep2NYU.d.ts} +1 -1
- package/dist/core.cjs +488 -15
- package/dist/core.cjs.map +1 -1
- package/dist/core.d.cts +364 -165
- package/dist/core.d.ts +364 -165
- package/dist/core.js +488 -15
- package/dist/core.js.map +1 -1
- package/dist/index.cjs +488 -15
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +488 -15
- package/dist/index.js.map +1 -1
- package/dist/react.cjs +509 -20
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +6 -4
- package/dist/react.d.ts +6 -4
- package/dist/react.js +509 -20
- package/dist/react.js.map +1 -1
- package/dist/skins/meet.cjs +13 -5
- package/dist/skins/meet.cjs.map +1 -1
- package/dist/skins/meet.d.cts +2 -2
- package/dist/skins/meet.d.ts +2 -2
- package/dist/skins/meet.js +13 -5
- package/dist/skins/meet.js.map +1 -1
- package/package.json +2 -2
package/dist/core.js
CHANGED
|
@@ -204,6 +204,352 @@ function invalidResponse(operation, detail) {
|
|
|
204
204
|
);
|
|
205
205
|
}
|
|
206
206
|
|
|
207
|
+
// src/core/connection-supervisor.ts
|
|
208
|
+
var DEFAULT_CONNECTION_POLICY = {
|
|
209
|
+
bannerDelayMs: 2e3,
|
|
210
|
+
autoRejoin: true,
|
|
211
|
+
autoRejoinMaxAttempts: 5,
|
|
212
|
+
reconnectGraceSeconds: 30
|
|
213
|
+
};
|
|
214
|
+
function clampInt(value, min, max, fallback) {
|
|
215
|
+
if (typeof value !== "number" || !Number.isFinite(value)) return fallback;
|
|
216
|
+
return Math.min(max, Math.max(min, Math.round(value)));
|
|
217
|
+
}
|
|
218
|
+
function connectionPolicyFrom(raw) {
|
|
219
|
+
if (typeof raw !== "object" || raw === null) return DEFAULT_CONNECTION_POLICY;
|
|
220
|
+
const row = raw;
|
|
221
|
+
const d = DEFAULT_CONNECTION_POLICY;
|
|
222
|
+
return {
|
|
223
|
+
bannerDelayMs: clampInt(row["reconnect_banner_delay_ms"], 0, 1e4, d.bannerDelayMs),
|
|
224
|
+
autoRejoin: typeof row["auto_rejoin"] === "boolean" ? row["auto_rejoin"] : d.autoRejoin,
|
|
225
|
+
autoRejoinMaxAttempts: clampInt(row["auto_rejoin_max_attempts"], 0, 20, d.autoRejoinMaxAttempts),
|
|
226
|
+
reconnectGraceSeconds: clampInt(row["reconnect_grace_seconds"], 0, 600, d.reconnectGraceSeconds)
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
var LIVEKIT_BASE_DELAYS_MS = [0, 300, 1200, 2700, 4800, 7e3];
|
|
230
|
+
function createReconnectPolicy(options = {}) {
|
|
231
|
+
const delays = options.delaysMs !== void 0 && options.delaysMs.length > 0 ? options.delaysMs : LIVEKIT_BASE_DELAYS_MS;
|
|
232
|
+
const giveUpMs = options.giveUpMs ?? 45e3;
|
|
233
|
+
const maxAttempts = options.maxAttempts ?? 12;
|
|
234
|
+
const random = options.random ?? Math.random;
|
|
235
|
+
return {
|
|
236
|
+
nextRetryDelayInMs({ retryCount, elapsedMs }) {
|
|
237
|
+
if (retryCount >= maxAttempts || elapsedMs >= giveUpMs) return null;
|
|
238
|
+
const base = delays[Math.min(retryCount, delays.length - 1)] ?? 0;
|
|
239
|
+
const spread = Math.min(2e3, Math.max(500, base / 2));
|
|
240
|
+
return Math.round(base + random() * spread);
|
|
241
|
+
}
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
function createWebNetworkPort() {
|
|
245
|
+
const scope = globalThis;
|
|
246
|
+
return {
|
|
247
|
+
online: () => scope.navigator?.onLine !== false,
|
|
248
|
+
subscribe(listener) {
|
|
249
|
+
if (typeof scope.addEventListener !== "function") return () => void 0;
|
|
250
|
+
const on = () => listener(true);
|
|
251
|
+
const off = () => listener(false);
|
|
252
|
+
scope.addEventListener("online", on);
|
|
253
|
+
scope.addEventListener("offline", off);
|
|
254
|
+
return () => {
|
|
255
|
+
scope.removeEventListener?.("online", on);
|
|
256
|
+
scope.removeEventListener?.("offline", off);
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
function createReachabilityProbe(urls, timeoutMs = 4e3) {
|
|
262
|
+
return async () => {
|
|
263
|
+
const targets = urls().filter((url) => url.length > 0);
|
|
264
|
+
if (targets.length === 0 || typeof fetch !== "function") return true;
|
|
265
|
+
const one = async (url) => {
|
|
266
|
+
const controller = typeof AbortController === "function" ? new AbortController() : null;
|
|
267
|
+
const timer = setTimeout(() => controller?.abort(), timeoutMs);
|
|
268
|
+
try {
|
|
269
|
+
await fetch(url, {
|
|
270
|
+
method: "GET",
|
|
271
|
+
mode: "no-cors",
|
|
272
|
+
cache: "no-store",
|
|
273
|
+
credentials: "omit",
|
|
274
|
+
...controller !== null ? { signal: controller.signal } : {}
|
|
275
|
+
});
|
|
276
|
+
return true;
|
|
277
|
+
} catch {
|
|
278
|
+
return false;
|
|
279
|
+
} finally {
|
|
280
|
+
clearTimeout(timer);
|
|
281
|
+
}
|
|
282
|
+
};
|
|
283
|
+
const answers = await Promise.all(targets.map(one));
|
|
284
|
+
return answers.every(Boolean);
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
function httpUrlOf(serverUrl) {
|
|
288
|
+
return serverUrl.replace(/^ws(s?):\/\//i, "http$1://");
|
|
289
|
+
}
|
|
290
|
+
var RESUME_WINDOW_MS = 15 * 6e4;
|
|
291
|
+
var RESUME_KEY = "mx-meet:resume:";
|
|
292
|
+
function createWebResumeStorage() {
|
|
293
|
+
const storage = globalThis.sessionStorage;
|
|
294
|
+
if (storage === void 0) return void 0;
|
|
295
|
+
return {
|
|
296
|
+
load(roomName) {
|
|
297
|
+
try {
|
|
298
|
+
const raw = storage.getItem(RESUME_KEY + roomName);
|
|
299
|
+
if (raw === null) return null;
|
|
300
|
+
const parsed = JSON.parse(raw);
|
|
301
|
+
return typeof parsed?.at === "number" && typeof parsed.request?.roomName === "string" ? parsed : null;
|
|
302
|
+
} catch {
|
|
303
|
+
return null;
|
|
304
|
+
}
|
|
305
|
+
},
|
|
306
|
+
save(roomName, marker) {
|
|
307
|
+
try {
|
|
308
|
+
storage.setItem(RESUME_KEY + roomName, JSON.stringify(marker));
|
|
309
|
+
} catch {
|
|
310
|
+
}
|
|
311
|
+
},
|
|
312
|
+
clear(roomName) {
|
|
313
|
+
try {
|
|
314
|
+
storage.removeItem(RESUME_KEY + roomName);
|
|
315
|
+
} catch {
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
var CLEARS_RESUME = (phase) => phase === "left" || phase === "removed" || phase === "superseded" || phase === "inactive" || phase === "ended" || phase === "record" || phase === "prejoin" || phase.startsWith("refused:") || phase.startsWith("waiting:");
|
|
321
|
+
function autoRejoinDelayMs(attempt, random = Math.random) {
|
|
322
|
+
const base = Math.min(15e3, 1e3 * 2 ** Math.max(0, attempt));
|
|
323
|
+
return Math.round(base * (0.5 + random()));
|
|
324
|
+
}
|
|
325
|
+
function createConnectionSupervisor(options) {
|
|
326
|
+
const { store, network } = options;
|
|
327
|
+
const now = options.now ?? (() => Date.now());
|
|
328
|
+
const random = options.random ?? Math.random;
|
|
329
|
+
const probeEveryMs = options.probeEveryMs ?? 3e3;
|
|
330
|
+
let driverRegion = null;
|
|
331
|
+
let probeTimer = null;
|
|
332
|
+
let probing = false;
|
|
333
|
+
let bannerTimer = null;
|
|
334
|
+
let rejoinTimer = null;
|
|
335
|
+
let rejoinAttempts = 0;
|
|
336
|
+
let lastPhase = store.snapshot().phase;
|
|
337
|
+
let lastSavedAt = 0;
|
|
338
|
+
let disposed = false;
|
|
339
|
+
const inCall = () => store.snapshot().phase === "in_call";
|
|
340
|
+
const dropped = () => store.snapshot().phase === "disconnected";
|
|
341
|
+
function setRegion(next) {
|
|
342
|
+
if (store.snapshot().connection !== next) store.patch({ connection: next });
|
|
343
|
+
}
|
|
344
|
+
function regionFor(reachable) {
|
|
345
|
+
if (!network.online()) return "offline";
|
|
346
|
+
if (reachable === false) return "server_unreachable";
|
|
347
|
+
if (dropped()) return reachable === true ? "stable" : store.snapshot().connection;
|
|
348
|
+
return driverRegion ?? "stable";
|
|
349
|
+
}
|
|
350
|
+
function needsWatch() {
|
|
351
|
+
const snap = store.snapshot();
|
|
352
|
+
if (dropped()) return true;
|
|
353
|
+
return inCall() && (driverRegion !== null || snap.connection !== "stable");
|
|
354
|
+
}
|
|
355
|
+
function stopProbe() {
|
|
356
|
+
if (probeTimer !== null) clearTimeout(probeTimer);
|
|
357
|
+
probeTimer = null;
|
|
358
|
+
}
|
|
359
|
+
function scheduleProbe(delayMs) {
|
|
360
|
+
if (disposed || probeTimer !== null) return;
|
|
361
|
+
probeTimer = setTimeout(() => {
|
|
362
|
+
probeTimer = null;
|
|
363
|
+
void runProbe();
|
|
364
|
+
}, delayMs);
|
|
365
|
+
}
|
|
366
|
+
async function runProbe() {
|
|
367
|
+
if (disposed || probing) return;
|
|
368
|
+
if (!needsWatch()) return;
|
|
369
|
+
if (!network.online()) {
|
|
370
|
+
setRegion("offline");
|
|
371
|
+
scheduleProbe(probeEveryMs);
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
374
|
+
probing = true;
|
|
375
|
+
let reachable = false;
|
|
376
|
+
try {
|
|
377
|
+
reachable = await options.probe();
|
|
378
|
+
} catch {
|
|
379
|
+
reachable = false;
|
|
380
|
+
} finally {
|
|
381
|
+
probing = false;
|
|
382
|
+
}
|
|
383
|
+
if (disposed) return;
|
|
384
|
+
if (inCall() || dropped()) setRegion(regionFor(reachable));
|
|
385
|
+
if (dropped() && reachable) maybeRejoin();
|
|
386
|
+
if (needsWatch()) scheduleProbe(Math.round(probeEveryMs * (0.75 + random() * 0.5)));
|
|
387
|
+
}
|
|
388
|
+
function syncBanner() {
|
|
389
|
+
const snap = store.snapshot();
|
|
390
|
+
const unstable = snap.phase === "in_call" && snap.connection !== "stable";
|
|
391
|
+
if (!unstable) {
|
|
392
|
+
if (bannerTimer !== null) clearTimeout(bannerTimer);
|
|
393
|
+
bannerTimer = null;
|
|
394
|
+
if (snap.connectionBanner) store.patch({ connectionBanner: false });
|
|
395
|
+
return;
|
|
396
|
+
}
|
|
397
|
+
if (snap.connectionBanner || bannerTimer !== null) return;
|
|
398
|
+
const delay = options.policy().bannerDelayMs;
|
|
399
|
+
if (delay <= 0) {
|
|
400
|
+
store.patch({ connectionBanner: true });
|
|
401
|
+
return;
|
|
402
|
+
}
|
|
403
|
+
bannerTimer = setTimeout(() => {
|
|
404
|
+
bannerTimer = null;
|
|
405
|
+
const latest = store.snapshot();
|
|
406
|
+
if (latest.phase === "in_call" && latest.connection !== "stable" && !latest.connectionBanner) {
|
|
407
|
+
store.patch({ connectionBanner: true });
|
|
408
|
+
}
|
|
409
|
+
}, delay);
|
|
410
|
+
}
|
|
411
|
+
function stopRejoin() {
|
|
412
|
+
if (rejoinTimer !== null) clearTimeout(rejoinTimer);
|
|
413
|
+
rejoinTimer = null;
|
|
414
|
+
}
|
|
415
|
+
function maybeRejoin() {
|
|
416
|
+
if (disposed || rejoinTimer !== null || !dropped()) return;
|
|
417
|
+
const policy = options.policy();
|
|
418
|
+
if (!policy.autoRejoin) return;
|
|
419
|
+
if (rejoinAttempts >= policy.autoRejoinMaxAttempts) return;
|
|
420
|
+
if (!network.online()) return;
|
|
421
|
+
const attempt = rejoinAttempts;
|
|
422
|
+
rejoinTimer = setTimeout(() => {
|
|
423
|
+
rejoinTimer = null;
|
|
424
|
+
if (disposed || !dropped() || !network.online()) return;
|
|
425
|
+
rejoinAttempts = attempt + 1;
|
|
426
|
+
options.report?.("info", `meet: rejoining by itself (attempt ${rejoinAttempts} of ${policy.autoRejoinMaxAttempts})`);
|
|
427
|
+
void options.autoRejoin().catch(() => void 0);
|
|
428
|
+
}, autoRejoinDelayMs(attempt, random));
|
|
429
|
+
}
|
|
430
|
+
function saveMarker(force) {
|
|
431
|
+
const resume = options.resume;
|
|
432
|
+
const snap = store.snapshot();
|
|
433
|
+
if (resume === void 0 || snap.roomName === null) return;
|
|
434
|
+
const at = now();
|
|
435
|
+
if (!force && at - lastSavedAt < 1e4) return;
|
|
436
|
+
const request = options.currentRequest();
|
|
437
|
+
if (request === null) return;
|
|
438
|
+
lastSavedAt = at;
|
|
439
|
+
resume.save(snap.roomName, { request, at });
|
|
440
|
+
}
|
|
441
|
+
const unsubscribeStore = store.subscribe(() => {
|
|
442
|
+
if (disposed) return;
|
|
443
|
+
const snap = store.snapshot();
|
|
444
|
+
const phase = snap.phase;
|
|
445
|
+
const changed = phase !== lastPhase;
|
|
446
|
+
lastPhase = phase;
|
|
447
|
+
if (changed) {
|
|
448
|
+
if (phase === "in_call") {
|
|
449
|
+
rejoinAttempts = 0;
|
|
450
|
+
stopRejoin();
|
|
451
|
+
saveMarker(true);
|
|
452
|
+
} else if (phase === "disconnected") {
|
|
453
|
+
driverRegion = null;
|
|
454
|
+
saveMarker(true);
|
|
455
|
+
stopProbe();
|
|
456
|
+
scheduleProbe(0);
|
|
457
|
+
} else {
|
|
458
|
+
stopRejoin();
|
|
459
|
+
if (phase !== "joining") driverRegion = null;
|
|
460
|
+
if (CLEARS_RESUME(phase) && snap.roomName !== null) options.resume?.clear(snap.roomName);
|
|
461
|
+
}
|
|
462
|
+
} else if (phase === "in_call" || phase === "disconnected") {
|
|
463
|
+
saveMarker(false);
|
|
464
|
+
}
|
|
465
|
+
syncBanner();
|
|
466
|
+
});
|
|
467
|
+
const unsubscribeNetwork = network.subscribe((online) => {
|
|
468
|
+
if (disposed) return;
|
|
469
|
+
if (!online) {
|
|
470
|
+
if (inCall() || dropped()) setRegion("offline");
|
|
471
|
+
stopRejoin();
|
|
472
|
+
stopProbe();
|
|
473
|
+
if (needsWatch()) scheduleProbe(probeEveryMs);
|
|
474
|
+
return;
|
|
475
|
+
}
|
|
476
|
+
stopProbe();
|
|
477
|
+
if (needsWatch()) scheduleProbe(0);
|
|
478
|
+
});
|
|
479
|
+
return {
|
|
480
|
+
driverState(state) {
|
|
481
|
+
if (disposed) return;
|
|
482
|
+
if (state === "connected") {
|
|
483
|
+
driverRegion = null;
|
|
484
|
+
if (inCall() || store.snapshot().phase === "joining") setRegion(network.online() ? "stable" : "offline");
|
|
485
|
+
if (!needsWatch()) stopProbe();
|
|
486
|
+
return;
|
|
487
|
+
}
|
|
488
|
+
if (state === "reconnecting" || state === "signal_reconnecting") {
|
|
489
|
+
driverRegion = state;
|
|
490
|
+
if (inCall()) {
|
|
491
|
+
setRegion(network.online() ? state : "offline");
|
|
492
|
+
stopProbe();
|
|
493
|
+
scheduleProbe(0);
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
},
|
|
497
|
+
exitConnection() {
|
|
498
|
+
return network.online() ? "server_unreachable" : "offline";
|
|
499
|
+
},
|
|
500
|
+
resumable(roomName) {
|
|
501
|
+
const resume = options.resume;
|
|
502
|
+
if (resume === void 0) return null;
|
|
503
|
+
const marker = resume.load(roomName);
|
|
504
|
+
if (marker === null) return null;
|
|
505
|
+
if (now() - marker.at > RESUME_WINDOW_MS) {
|
|
506
|
+
resume.clear(roomName);
|
|
507
|
+
return null;
|
|
508
|
+
}
|
|
509
|
+
return marker;
|
|
510
|
+
},
|
|
511
|
+
dispose() {
|
|
512
|
+
disposed = true;
|
|
513
|
+
stopProbe();
|
|
514
|
+
stopRejoin();
|
|
515
|
+
if (bannerTimer !== null) clearTimeout(bannerTimer);
|
|
516
|
+
bannerTimer = null;
|
|
517
|
+
unsubscribeStore();
|
|
518
|
+
unsubscribeNetwork();
|
|
519
|
+
}
|
|
520
|
+
};
|
|
521
|
+
}
|
|
522
|
+
var DELIBERATE_DEPARTURES = /* @__PURE__ */ new Set([
|
|
523
|
+
"CLIENT_INITIATED",
|
|
524
|
+
"PARTICIPANT_REMOVED",
|
|
525
|
+
"ROOM_DELETED",
|
|
526
|
+
"ROOM_CLOSED",
|
|
527
|
+
"DUPLICATE_IDENTITY",
|
|
528
|
+
"USER_REJECTED",
|
|
529
|
+
"USER_UNAVAILABLE",
|
|
530
|
+
"SIP_TRUNK_FAILURE"
|
|
531
|
+
]);
|
|
532
|
+
function isUnannouncedDeparture(reason) {
|
|
533
|
+
return reason === null || !DELIBERATE_DEPARTURES.has(reason);
|
|
534
|
+
}
|
|
535
|
+
function mergeGhosts(live, ghosts, at) {
|
|
536
|
+
const present = new Set(live.map((p) => p.identity));
|
|
537
|
+
const kept = /* @__PURE__ */ new Map();
|
|
538
|
+
const extra = [];
|
|
539
|
+
for (const [identity, ghost] of ghosts) {
|
|
540
|
+
if (present.has(identity) || ghost.until <= at) continue;
|
|
541
|
+
kept.set(identity, ghost);
|
|
542
|
+
extra.push({
|
|
543
|
+
...ghost.participant,
|
|
544
|
+
micEnabled: false,
|
|
545
|
+
cameraEnabled: false,
|
|
546
|
+
screenSharing: false,
|
|
547
|
+
reconnecting: true
|
|
548
|
+
});
|
|
549
|
+
}
|
|
550
|
+
return { roster: [...live, ...extra], kept };
|
|
551
|
+
}
|
|
552
|
+
|
|
207
553
|
// src/core/tokens.ts
|
|
208
554
|
function createWebRoomTokenStorage(onDiagnostic) {
|
|
209
555
|
const store = globalThis.localStorage;
|
|
@@ -389,7 +735,8 @@ function parseRoomToken(body, roomName) {
|
|
|
389
735
|
admissionState: readString(payload, "admission_state") ?? void 0,
|
|
390
736
|
pollSeconds: typeof payload["poll_seconds"] === "number" ? payload["poll_seconds"] : void 0,
|
|
391
737
|
breakoutRoomId: readString(payload, "breakout_room_id") ?? null,
|
|
392
|
-
observersVisibleTo: readString(payload, "observers_visible_to") === "hosts" ? "hosts" : "everyone"
|
|
738
|
+
observersVisibleTo: readString(payload, "observers_visible_to") === "hosts" ? "hosts" : "everyone",
|
|
739
|
+
connectionPolicy: connectionPolicyFrom(payload["connection_policy"])
|
|
393
740
|
};
|
|
394
741
|
}
|
|
395
742
|
function createTokenClient(options) {
|
|
@@ -1119,6 +1466,10 @@ function nextPhase(from, event) {
|
|
|
1119
1466
|
return from === "resolving" && event.reason !== null && isJoinRefusalReason(event.reason) ? refusalPhase(event.reason) : null;
|
|
1120
1467
|
case "join":
|
|
1121
1468
|
return JOINABLE_FROM.has(from) ? "joining" : null;
|
|
1469
|
+
case "auto_rejoin":
|
|
1470
|
+
return from === "disconnected" ? "joining" : null;
|
|
1471
|
+
case "resumed":
|
|
1472
|
+
return from === "resolving" ? "joining" : null;
|
|
1122
1473
|
case "back_to_prejoin":
|
|
1123
1474
|
return from === "left" || from === "disconnected" ? "prejoin" : null;
|
|
1124
1475
|
case "retry":
|
|
@@ -3032,6 +3383,7 @@ var EMPTY = {
|
|
|
3032
3383
|
phaseNotice: null,
|
|
3033
3384
|
roomName: null,
|
|
3034
3385
|
connection: "stable",
|
|
3386
|
+
connectionBanner: false,
|
|
3035
3387
|
share: NO_SHARE,
|
|
3036
3388
|
call: null,
|
|
3037
3389
|
waiting: null,
|
|
@@ -3986,8 +4338,11 @@ function mapConnectionState(state) {
|
|
|
3986
4338
|
case "connecting":
|
|
3987
4339
|
return "connecting";
|
|
3988
4340
|
case "reconnecting":
|
|
3989
|
-
case "signalReconnecting":
|
|
3990
4341
|
return "reconnecting";
|
|
4342
|
+
// Only the signal socket is down (S6b): its own region value, so the supervisor can tell a
|
|
4343
|
+
// signal blip from a full media reconnect.
|
|
4344
|
+
case "signalReconnecting":
|
|
4345
|
+
return "signal_reconnecting";
|
|
3991
4346
|
default:
|
|
3992
4347
|
return "disconnected";
|
|
3993
4348
|
}
|
|
@@ -4068,8 +4423,12 @@ function createLiveKitDriver(handlers, options = {}) {
|
|
|
4068
4423
|
// R5: large meetings degrade gracefully instead of melting a laptop.
|
|
4069
4424
|
adaptiveStream: true,
|
|
4070
4425
|
dynacast: true,
|
|
4426
|
+
// S6b: OUR retry schedule — jittered from attempt 0 (the SDK's default has no jitter on its
|
|
4427
|
+
// first two attempts), give-up limits in one place (`connection-supervisor.ts`).
|
|
4428
|
+
reconnectPolicy: createReconnectPolicy(),
|
|
4071
4429
|
...options.roomOptions
|
|
4072
4430
|
});
|
|
4431
|
+
const reasonName = (reason) => reason === void 0 || reason === null ? null : module.DisconnectReason?.[reason] ?? String(reason);
|
|
4073
4432
|
const roster = () => {
|
|
4074
4433
|
const local = snapshotParticipant(created.localParticipant, true);
|
|
4075
4434
|
const remotes = [...created.remoteParticipants.values()].map(
|
|
@@ -4087,7 +4446,10 @@ function createLiveKitDriver(handlers, options = {}) {
|
|
|
4087
4446
|
if (track.kind !== Track.Kind.Audio) return;
|
|
4088
4447
|
for (const element of track.detach()) element.remove();
|
|
4089
4448
|
});
|
|
4090
|
-
created.on(RoomEvent.ParticipantConnected, roster).on(RoomEvent.ParticipantDisconnected,
|
|
4449
|
+
created.on(RoomEvent.ParticipantConnected, roster).on(RoomEvent.ParticipantDisconnected, (participant, reason) => {
|
|
4450
|
+
handlers.participantLeft?.(participant.identity, reasonName(reason));
|
|
4451
|
+
roster();
|
|
4452
|
+
}).on(RoomEvent.TrackSubscribed, roster).on(RoomEvent.TrackUnsubscribed, roster).on(RoomEvent.TrackMuted, roster).on(RoomEvent.TrackUnmuted, roster).on(RoomEvent.LocalTrackPublished, roster).on(RoomEvent.LocalTrackUnpublished, roster).on(RoomEvent.ParticipantNameChanged, roster).on(RoomEvent.ParticipantAttributesChanged, roster).on(RoomEvent.ParticipantMetadataChanged, roster).on(RoomEvent.RoomMetadataChanged, (metadata) => {
|
|
4091
4453
|
handlers.roomMetadata?.(metadata ?? "");
|
|
4092
4454
|
}).on(RoomEvent.ActiveSpeakersChanged, (speakers) => {
|
|
4093
4455
|
handlers.activeSpeakers(speakers.map((speaker) => speaker.identity));
|
|
@@ -4104,8 +4466,7 @@ function createLiveKitDriver(handlers, options = {}) {
|
|
|
4104
4466
|
}).on(RoomEvent.Disconnected, (reason) => {
|
|
4105
4467
|
audioContainer?.remove();
|
|
4106
4468
|
audioContainer = null;
|
|
4107
|
-
|
|
4108
|
-
handlers.disconnected(named);
|
|
4469
|
+
handlers.disconnected(reasonName(reason));
|
|
4109
4470
|
});
|
|
4110
4471
|
try {
|
|
4111
4472
|
created.registerTextStreamHandler(TRANSCRIPTION_TOPIC, (reader, participantInfo) => {
|
|
@@ -5478,6 +5839,18 @@ function createRoomEngine(options) {
|
|
|
5478
5839
|
let disposed = false;
|
|
5479
5840
|
let moving = false;
|
|
5480
5841
|
const confirmingEnds = /* @__PURE__ */ new WeakSet();
|
|
5842
|
+
let lastServerUrl = null;
|
|
5843
|
+
let connectionPolicy = DEFAULT_CONNECTION_POLICY;
|
|
5844
|
+
let ghosts = /* @__PURE__ */ new Map();
|
|
5845
|
+
let ghostTimer = null;
|
|
5846
|
+
let liveRoster = [];
|
|
5847
|
+
const departures = /* @__PURE__ */ new Map();
|
|
5848
|
+
function requestAsNow() {
|
|
5849
|
+
if (lastRequest === null) return null;
|
|
5850
|
+
const media = store.snapshot().media;
|
|
5851
|
+
if (store.snapshot().phase !== "in_call") return lastRequest;
|
|
5852
|
+
return { ...lastRequest, mode: media.camera.enabled ? "video" : "audio", microphone: media.microphone.enabled };
|
|
5853
|
+
}
|
|
5481
5854
|
async function dropRoom() {
|
|
5482
5855
|
const target = driver;
|
|
5483
5856
|
driver = null;
|
|
@@ -5488,6 +5861,7 @@ function createRoomEngine(options) {
|
|
|
5488
5861
|
store.setLocalTrack("microphone", IDLE_TRACK);
|
|
5489
5862
|
store.setLocalTrack("camera", IDLE_TRACK);
|
|
5490
5863
|
store.patch({ share: NO_SHARE, connection: "stable" });
|
|
5864
|
+
clearGhosts();
|
|
5491
5865
|
await target?.disconnect();
|
|
5492
5866
|
}
|
|
5493
5867
|
async function leaveRoom() {
|
|
@@ -5555,7 +5929,7 @@ function createRoomEngine(options) {
|
|
|
5555
5929
|
participants(list) {
|
|
5556
5930
|
const joinedAt = new Date(now()).toISOString();
|
|
5557
5931
|
const before = new Set(store.snapshot().participants.map((p) => p.identity));
|
|
5558
|
-
|
|
5932
|
+
projectRoster(list, joinedAt);
|
|
5559
5933
|
const snapshot = store.snapshot();
|
|
5560
5934
|
const newcomer = snapshot.participants.some(
|
|
5561
5935
|
(participant) => !participant.isLocal && !before.has(participant.identity)
|
|
@@ -5567,6 +5941,9 @@ function createRoomEngine(options) {
|
|
|
5567
5941
|
);
|
|
5568
5942
|
}
|
|
5569
5943
|
},
|
|
5944
|
+
participantLeft(who, reason) {
|
|
5945
|
+
departures.set(who, reason);
|
|
5946
|
+
},
|
|
5570
5947
|
roomMetadata(metadata) {
|
|
5571
5948
|
let spotlight = null;
|
|
5572
5949
|
if (metadata.length > 0) {
|
|
@@ -5593,12 +5970,8 @@ function createRoomEngine(options) {
|
|
|
5593
5970
|
store.setQuality(asParticipantIdentity(who), value);
|
|
5594
5971
|
},
|
|
5595
5972
|
connectionState(state) {
|
|
5596
|
-
|
|
5597
|
-
|
|
5598
|
-
store.dispatch({ type: "connected" });
|
|
5599
|
-
} else if (state === "reconnecting") {
|
|
5600
|
-
store.patch({ connection: "reconnecting" });
|
|
5601
|
-
}
|
|
5973
|
+
supervisor.driverState(state);
|
|
5974
|
+
if (state === "connected" && store.snapshot().phase === "joining") store.dispatch({ type: "connected" });
|
|
5602
5975
|
},
|
|
5603
5976
|
transcription(event) {
|
|
5604
5977
|
const snapshot = store.snapshot();
|
|
@@ -5731,6 +6104,7 @@ function createRoomEngine(options) {
|
|
|
5731
6104
|
};
|
|
5732
6105
|
async function resolveExitAndApply(hint) {
|
|
5733
6106
|
const connection = current?.identity ?? store.snapshot().localIdentity ?? null;
|
|
6107
|
+
lastRequest = requestAsNow();
|
|
5734
6108
|
await dropRoom();
|
|
5735
6109
|
let server;
|
|
5736
6110
|
if (options.readExit === void 0 || connection === null || store.snapshot().meeting === null) {
|
|
@@ -5744,7 +6118,7 @@ function createRoomEngine(options) {
|
|
|
5744
6118
|
}
|
|
5745
6119
|
if (disposed) return;
|
|
5746
6120
|
const resolved = resolveExit({ server, hint, frozen: options.wasFrozen?.() ?? false });
|
|
5747
|
-
store.patch({ connection: resolved.connection });
|
|
6121
|
+
store.patch({ connection: server === null ? supervisor.exitConnection() : resolved.connection });
|
|
5748
6122
|
if (resolved.to === "waiting:knocking") {
|
|
5749
6123
|
store.patch({
|
|
5750
6124
|
waiting: {
|
|
@@ -5776,6 +6150,8 @@ function createRoomEngine(options) {
|
|
|
5776
6150
|
...request.observe === true ? { observe: true } : {}
|
|
5777
6151
|
});
|
|
5778
6152
|
current = token;
|
|
6153
|
+
lastServerUrl = token.serverUrl;
|
|
6154
|
+
connectionPolicy = token.connectionPolicy ?? DEFAULT_CONNECTION_POLICY;
|
|
5779
6155
|
store.patch({
|
|
5780
6156
|
localIdentity: token.identity,
|
|
5781
6157
|
breakoutRoomId: token.breakoutRoomId ?? null,
|
|
@@ -5880,7 +6256,59 @@ function createRoomEngine(options) {
|
|
|
5880
6256
|
throw normalized;
|
|
5881
6257
|
}
|
|
5882
6258
|
}
|
|
5883
|
-
|
|
6259
|
+
function projectRoster(list, joinedAt) {
|
|
6260
|
+
const at = now();
|
|
6261
|
+
const previous = new Map(liveRoster.map((p) => [p.identity, p]));
|
|
6262
|
+
const present = new Set(list.map((p) => p.identity));
|
|
6263
|
+
const graceMs = connectionPolicy.reconnectGraceSeconds * 1e3;
|
|
6264
|
+
for (const [who, reason] of departures) {
|
|
6265
|
+
const last = previous.get(who);
|
|
6266
|
+
if (last !== void 0 && !last.isLocal && !present.has(who) && graceMs > 0 && isUnannouncedDeparture(reason)) {
|
|
6267
|
+
ghosts.set(who, { participant: last, until: at + graceMs });
|
|
6268
|
+
}
|
|
6269
|
+
}
|
|
6270
|
+
departures.clear();
|
|
6271
|
+
liveRoster = list;
|
|
6272
|
+
const merged = mergeGhosts(list, ghosts, at);
|
|
6273
|
+
ghosts = merged.kept;
|
|
6274
|
+
store.setParticipants(
|
|
6275
|
+
merged.roster.map((entry) => {
|
|
6276
|
+
const projected = projectParticipant(entry, joinedAt);
|
|
6277
|
+
return entry.reconnecting === true ? { ...projected, reconnecting: true, quality: "lost" } : projected;
|
|
6278
|
+
})
|
|
6279
|
+
);
|
|
6280
|
+
scheduleGhostExpiry();
|
|
6281
|
+
}
|
|
6282
|
+
function scheduleGhostExpiry() {
|
|
6283
|
+
if (ghostTimer !== null) clearTimeout(ghostTimer);
|
|
6284
|
+
ghostTimer = null;
|
|
6285
|
+
if (ghosts.size === 0 || disposed) return;
|
|
6286
|
+
const next = Math.min(...[...ghosts.values()].map((g) => g.until));
|
|
6287
|
+
ghostTimer = setTimeout(() => {
|
|
6288
|
+
ghostTimer = null;
|
|
6289
|
+
if (disposed || driver === null) return;
|
|
6290
|
+
projectRoster(liveRoster, new Date(now()).toISOString());
|
|
6291
|
+
}, Math.max(0, next - now()) + 50);
|
|
6292
|
+
}
|
|
6293
|
+
function clearGhosts() {
|
|
6294
|
+
ghosts = /* @__PURE__ */ new Map();
|
|
6295
|
+
departures.clear();
|
|
6296
|
+
liveRoster = [];
|
|
6297
|
+
if (ghostTimer !== null) clearTimeout(ghostTimer);
|
|
6298
|
+
ghostTimer = null;
|
|
6299
|
+
}
|
|
6300
|
+
const supervisor = createConnectionSupervisor({
|
|
6301
|
+
store,
|
|
6302
|
+
network: options.connection?.network ?? createWebNetworkPort(),
|
|
6303
|
+
probe: () => options.connection?.probe?.(current?.serverUrl ?? lastServerUrl) ?? Promise.resolve(true),
|
|
6304
|
+
policy: () => connectionPolicy,
|
|
6305
|
+
autoRejoin: () => engine.autoRejoin(),
|
|
6306
|
+
currentRequest: requestAsNow,
|
|
6307
|
+
resume: options.connection?.resume,
|
|
6308
|
+
now,
|
|
6309
|
+
report: (level, message) => report(level, message)
|
|
6310
|
+
});
|
|
6311
|
+
const engine = {
|
|
5884
6312
|
join(request) {
|
|
5885
6313
|
if (joining !== null) return joining;
|
|
5886
6314
|
if (store.snapshot().phase !== "joining" && !store.dispatch({ type: "join", click: true })) {
|
|
@@ -5925,6 +6353,35 @@ function createRoomEngine(options) {
|
|
|
5925
6353
|
});
|
|
5926
6354
|
return joining;
|
|
5927
6355
|
},
|
|
6356
|
+
async autoRejoin() {
|
|
6357
|
+
const request = lastRequest;
|
|
6358
|
+
if (request === null || joining !== null) return joining ?? void 0;
|
|
6359
|
+
if (!store.dispatch({ type: "auto_rejoin" })) return;
|
|
6360
|
+
tokens.invalidate(request.roomName);
|
|
6361
|
+
current = null;
|
|
6362
|
+
joining = connect(request).catch((error) => {
|
|
6363
|
+
throw failJoin(error, "meet.autoRejoin");
|
|
6364
|
+
}).finally(() => {
|
|
6365
|
+
joining = null;
|
|
6366
|
+
});
|
|
6367
|
+
return joining;
|
|
6368
|
+
},
|
|
6369
|
+
resume(roomName) {
|
|
6370
|
+
const marker = supervisor.resumable(roomName);
|
|
6371
|
+
if (marker === null || joining !== null) return false;
|
|
6372
|
+
if (!store.dispatch({ type: "resumed" })) return false;
|
|
6373
|
+
lastRequest = { ...marker.request, roomName };
|
|
6374
|
+
report("info", "meet: this tab was in the call before the page reloaded \u2014 rejoining");
|
|
6375
|
+
tokens.invalidate(roomName);
|
|
6376
|
+
current = null;
|
|
6377
|
+
joining = connect(lastRequest).catch((error) => {
|
|
6378
|
+
throw failJoin(error, "meet.resume");
|
|
6379
|
+
}).finally(() => {
|
|
6380
|
+
joining = null;
|
|
6381
|
+
});
|
|
6382
|
+
joining.catch(() => void 0);
|
|
6383
|
+
return true;
|
|
6384
|
+
},
|
|
5928
6385
|
async moveToBreakout(roomId) {
|
|
5929
6386
|
const request = lastRequest;
|
|
5930
6387
|
if (request === null || driver === null) {
|
|
@@ -6092,6 +6549,8 @@ function createRoomEngine(options) {
|
|
|
6092
6549
|
token: () => current,
|
|
6093
6550
|
async dispose() {
|
|
6094
6551
|
disposed = true;
|
|
6552
|
+
supervisor.dispose();
|
|
6553
|
+
clearGhosts();
|
|
6095
6554
|
if (driver !== null) {
|
|
6096
6555
|
const target = driver;
|
|
6097
6556
|
driver = null;
|
|
@@ -6100,6 +6559,7 @@ function createRoomEngine(options) {
|
|
|
6100
6559
|
store.reset();
|
|
6101
6560
|
}
|
|
6102
6561
|
};
|
|
6562
|
+
return engine;
|
|
6103
6563
|
}
|
|
6104
6564
|
function canModerate(participant) {
|
|
6105
6565
|
return participant !== void 0 && isHostRole(participant.role);
|
|
@@ -7185,7 +7645,7 @@ function participantAttributes(p) {
|
|
|
7185
7645
|
"data-meet-participant-role": observedRole(p.role, p.standing),
|
|
7186
7646
|
"data-meet-participant-self": String(p.isLocal)
|
|
7187
7647
|
};
|
|
7188
|
-
const quality = observedQuality(p.quality);
|
|
7648
|
+
const quality = p.reconnecting === true ? "reconnecting" : observedQuality(p.quality);
|
|
7189
7649
|
if (quality !== null) attrs["data-meet-participant-connection"] = quality;
|
|
7190
7650
|
return attrs;
|
|
7191
7651
|
}
|
|
@@ -7201,6 +7661,7 @@ export {
|
|
|
7201
7661
|
CAPTION_LANGUAGES,
|
|
7202
7662
|
CONSENT_NOTICE_ATTRIBUTE,
|
|
7203
7663
|
DATA_TOPICS,
|
|
7664
|
+
DEFAULT_CONNECTION_POLICY,
|
|
7204
7665
|
DEFAULT_EFFECTS,
|
|
7205
7666
|
DEFAULT_PAGE_SIZE,
|
|
7206
7667
|
EMPTY_COLLAB,
|
|
@@ -7215,6 +7676,7 @@ export {
|
|
|
7215
7676
|
IDLE_TRACK,
|
|
7216
7677
|
JOIN_REFUSAL_REASONS,
|
|
7217
7678
|
LISTEN_ONLY_NOTICE,
|
|
7679
|
+
LIVEKIT_BASE_DELAYS_MS,
|
|
7218
7680
|
LIVE_PHASES,
|
|
7219
7681
|
MEETING_INVITE_KIND,
|
|
7220
7682
|
MEETING_INVITE_VERSION,
|
|
@@ -7238,6 +7700,7 @@ export {
|
|
|
7238
7700
|
PARTICIPANT_STANDING_ATTRIBUTE,
|
|
7239
7701
|
REACTION_TTL_MS,
|
|
7240
7702
|
REFUSED_ABSENCE,
|
|
7703
|
+
RESUME_WINDOW_MS,
|
|
7241
7704
|
ROLE_RANK,
|
|
7242
7705
|
ROOM_EVENTS,
|
|
7243
7706
|
ROOM_EVENT_PREFIX,
|
|
@@ -7267,6 +7730,7 @@ export {
|
|
|
7267
7730
|
attendanceTotals,
|
|
7268
7731
|
attendeeKey,
|
|
7269
7732
|
autoAssign,
|
|
7733
|
+
autoRejoinDelayMs,
|
|
7270
7734
|
availableActivities,
|
|
7271
7735
|
breakoutRemainingMs,
|
|
7272
7736
|
callInviteAction,
|
|
@@ -7281,9 +7745,11 @@ export {
|
|
|
7281
7745
|
clockPosition,
|
|
7282
7746
|
collabFromState,
|
|
7283
7747
|
computeStage,
|
|
7748
|
+
connectionPolicyFrom,
|
|
7284
7749
|
countLabel,
|
|
7285
7750
|
createCallCenter,
|
|
7286
7751
|
createCallInviteHandler,
|
|
7752
|
+
createConnectionSupervisor,
|
|
7287
7753
|
createDeviceManager,
|
|
7288
7754
|
createInRoomClient,
|
|
7289
7755
|
createLiveKitDriver,
|
|
@@ -7295,11 +7761,15 @@ export {
|
|
|
7295
7761
|
createMeetingRecordCache,
|
|
7296
7762
|
createMeetingSession,
|
|
7297
7763
|
createMemoryDeviceStorage,
|
|
7764
|
+
createReachabilityProbe,
|
|
7765
|
+
createReconnectPolicy,
|
|
7298
7766
|
createRecordingController,
|
|
7299
7767
|
createRoomEngine,
|
|
7300
7768
|
createTokenClient,
|
|
7301
7769
|
createWebDeviceStorage,
|
|
7302
7770
|
createWebEffectsStorage,
|
|
7771
|
+
createWebNetworkPort,
|
|
7772
|
+
createWebResumeStorage,
|
|
7303
7773
|
createWebRoomTokenStorage,
|
|
7304
7774
|
defaultCaptionLanguage,
|
|
7305
7775
|
displayNameFor,
|
|
@@ -7312,6 +7782,7 @@ export {
|
|
|
7312
7782
|
guestRecordAccessSetting,
|
|
7313
7783
|
hasEnded,
|
|
7314
7784
|
hostUserIds,
|
|
7785
|
+
httpUrlOf,
|
|
7315
7786
|
invalidResponse,
|
|
7316
7787
|
isGuest,
|
|
7317
7788
|
isGuestIdentity,
|
|
@@ -7325,6 +7796,7 @@ export {
|
|
|
7325
7796
|
isRealName,
|
|
7326
7797
|
isRefusedPhase,
|
|
7327
7798
|
isRoomEventTopic,
|
|
7799
|
+
isUnannouncedDeparture,
|
|
7328
7800
|
isWaitingPhase,
|
|
7329
7801
|
learnName,
|
|
7330
7802
|
learnNames,
|
|
@@ -7346,6 +7818,7 @@ export {
|
|
|
7346
7818
|
meetingTopic,
|
|
7347
7819
|
meetingWhen,
|
|
7348
7820
|
mergeCaption,
|
|
7821
|
+
mergeGhosts,
|
|
7349
7822
|
mergeRecord,
|
|
7350
7823
|
microphoneNoticeCode,
|
|
7351
7824
|
moveToRoom,
|