@ai-matrx/meet 0.11.27 → 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 +12 -0
- package/dist/{collab-CIyXdQ5l.d.cts → collab-XY6Xj1dG.d.cts} +18 -0
- package/dist/{collab-CIyXdQ5l.d.ts → collab-XY6Xj1dG.d.ts} +18 -0
- package/dist/{components-DUvy3BQf.d.cts → components-4f9Mf8fz.d.cts} +1 -1
- package/dist/{components-DiGxfKXI.d.ts → components-Cwep2NYU.d.ts} +1 -1
- package/dist/core.cjs +515 -20
- package/dist/core.cjs.map +1 -1
- package/dist/core.d.cts +374 -165
- package/dist/core.d.ts +374 -165
- package/dist/core.js +515 -20
- package/dist/core.js.map +1 -1
- package/dist/index.cjs +515 -20
- 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 +515 -20
- package/dist/index.js.map +1 -1
- package/dist/react.cjs +575 -28
- 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 +575 -28
- package/dist/react.js.map +1 -1
- package/dist/skins/meet.cjs +18 -6
- 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 +18 -6
- package/dist/skins/meet.js.map +1 -1
- package/package.json +5 -5
package/dist/react.cjs
CHANGED
|
@@ -38,6 +38,7 @@ __export(react_exports, {
|
|
|
38
38
|
CAPTION_LANGUAGES: () => CAPTION_LANGUAGES,
|
|
39
39
|
CONSENT_NOTICE_ATTRIBUTE: () => CONSENT_NOTICE_ATTRIBUTE,
|
|
40
40
|
DATA_TOPICS: () => DATA_TOPICS,
|
|
41
|
+
DEFAULT_CONNECTION_POLICY: () => DEFAULT_CONNECTION_POLICY,
|
|
41
42
|
DEFAULT_EFFECTS: () => DEFAULT_EFFECTS,
|
|
42
43
|
DEFAULT_PAGE_SIZE: () => DEFAULT_PAGE_SIZE,
|
|
43
44
|
EMPTY_COLLAB: () => EMPTY_COLLAB,
|
|
@@ -55,6 +56,7 @@ __export(react_exports, {
|
|
|
55
56
|
IncomingCallHost: () => IncomingCallHost,
|
|
56
57
|
JOIN_REFUSAL_REASONS: () => JOIN_REFUSAL_REASONS,
|
|
57
58
|
LISTEN_ONLY_NOTICE: () => LISTEN_ONLY_NOTICE,
|
|
59
|
+
LIVEKIT_BASE_DELAYS_MS: () => LIVEKIT_BASE_DELAYS_MS,
|
|
58
60
|
LIVE_PHASES: () => LIVE_PHASES,
|
|
59
61
|
MEETING_INVITE_KIND: () => MEETING_INVITE_KIND,
|
|
60
62
|
MEETING_INVITE_VERSION: () => MEETING_INVITE_VERSION,
|
|
@@ -84,6 +86,7 @@ __export(react_exports, {
|
|
|
84
86
|
PARTICIPANT_STANDING_ATTRIBUTE: () => PARTICIPANT_STANDING_ATTRIBUTE,
|
|
85
87
|
REACTION_TTL_MS: () => REACTION_TTL_MS,
|
|
86
88
|
REFUSED_ABSENCE: () => REFUSED_ABSENCE,
|
|
89
|
+
RESUME_WINDOW_MS: () => RESUME_WINDOW_MS,
|
|
87
90
|
ROLE_RANK: () => ROLE_RANK,
|
|
88
91
|
ROOM_EVENTS: () => ROOM_EVENTS,
|
|
89
92
|
ROOM_EVENT_PREFIX: () => ROOM_EVENT_PREFIX,
|
|
@@ -114,6 +117,7 @@ __export(react_exports, {
|
|
|
114
117
|
attendanceTotals: () => attendanceTotals,
|
|
115
118
|
attendeeKey: () => attendeeKey,
|
|
116
119
|
autoAssign: () => autoAssign,
|
|
120
|
+
autoRejoinDelayMs: () => autoRejoinDelayMs,
|
|
117
121
|
availableActivities: () => availableActivities,
|
|
118
122
|
breakoutRemainingMs: () => breakoutRemainingMs,
|
|
119
123
|
callInviteAction: () => callInviteAction,
|
|
@@ -128,9 +132,11 @@ __export(react_exports, {
|
|
|
128
132
|
clockPosition: () => clockPosition,
|
|
129
133
|
collabFromState: () => collabFromState,
|
|
130
134
|
computeStage: () => computeStage,
|
|
135
|
+
connectionPolicyFrom: () => connectionPolicyFrom,
|
|
131
136
|
countLabel: () => countLabel,
|
|
132
137
|
createCallCenter: () => createCallCenter,
|
|
133
138
|
createCallInviteHandler: () => createCallInviteHandler,
|
|
139
|
+
createConnectionSupervisor: () => createConnectionSupervisor,
|
|
134
140
|
createDeviceManager: () => createDeviceManager,
|
|
135
141
|
createInRoomClient: () => createInRoomClient,
|
|
136
142
|
createLiveKitDriver: () => createLiveKitDriver,
|
|
@@ -142,11 +148,15 @@ __export(react_exports, {
|
|
|
142
148
|
createMeetingRecordCache: () => createMeetingRecordCache,
|
|
143
149
|
createMeetingSession: () => createMeetingSession,
|
|
144
150
|
createMemoryDeviceStorage: () => createMemoryDeviceStorage,
|
|
151
|
+
createReachabilityProbe: () => createReachabilityProbe,
|
|
152
|
+
createReconnectPolicy: () => createReconnectPolicy,
|
|
145
153
|
createRecordingController: () => createRecordingController,
|
|
146
154
|
createRoomEngine: () => createRoomEngine,
|
|
147
155
|
createTokenClient: () => createTokenClient,
|
|
148
156
|
createWebDeviceStorage: () => createWebDeviceStorage,
|
|
149
157
|
createWebEffectsStorage: () => createWebEffectsStorage,
|
|
158
|
+
createWebNetworkPort: () => createWebNetworkPort,
|
|
159
|
+
createWebResumeStorage: () => createWebResumeStorage,
|
|
150
160
|
createWebRoomTokenStorage: () => createWebRoomTokenStorage,
|
|
151
161
|
defaultCaptionLanguage: () => defaultCaptionLanguage,
|
|
152
162
|
displayNameFor: () => displayNameFor,
|
|
@@ -159,6 +169,7 @@ __export(react_exports, {
|
|
|
159
169
|
guestRecordAccessSetting: () => guestRecordAccessSetting,
|
|
160
170
|
hasEnded: () => hasEnded,
|
|
161
171
|
hostUserIds: () => hostUserIds,
|
|
172
|
+
httpUrlOf: () => httpUrlOf,
|
|
162
173
|
invalidResponse: () => invalidResponse,
|
|
163
174
|
isGuest: () => isGuest,
|
|
164
175
|
isGuestIdentity: () => isGuestIdentity,
|
|
@@ -172,6 +183,7 @@ __export(react_exports, {
|
|
|
172
183
|
isRealName: () => isRealName,
|
|
173
184
|
isRefusedPhase: () => isRefusedPhase,
|
|
174
185
|
isRoomEventTopic: () => isRoomEventTopic,
|
|
186
|
+
isUnannouncedDeparture: () => isUnannouncedDeparture,
|
|
175
187
|
isWaitingPhase: () => isWaitingPhase,
|
|
176
188
|
learnName: () => learnName,
|
|
177
189
|
learnNames: () => learnNames,
|
|
@@ -194,6 +206,7 @@ __export(react_exports, {
|
|
|
194
206
|
meetingTopic: () => meetingTopic,
|
|
195
207
|
meetingWhen: () => meetingWhen,
|
|
196
208
|
mergeCaption: () => mergeCaption,
|
|
209
|
+
mergeGhosts: () => mergeGhosts,
|
|
197
210
|
mergeRecord: () => mergeRecord,
|
|
198
211
|
microphoneNoticeCode: () => microphoneNoticeCode,
|
|
199
212
|
moveToRoom: () => moveToRoom,
|
|
@@ -307,6 +320,352 @@ __export(react_exports, {
|
|
|
307
320
|
});
|
|
308
321
|
module.exports = __toCommonJS(react_exports);
|
|
309
322
|
|
|
323
|
+
// src/core/connection-supervisor.ts
|
|
324
|
+
var DEFAULT_CONNECTION_POLICY = {
|
|
325
|
+
bannerDelayMs: 2e3,
|
|
326
|
+
autoRejoin: true,
|
|
327
|
+
autoRejoinMaxAttempts: 5,
|
|
328
|
+
reconnectGraceSeconds: 30
|
|
329
|
+
};
|
|
330
|
+
function clampInt(value, min, max, fallback) {
|
|
331
|
+
if (typeof value !== "number" || !Number.isFinite(value)) return fallback;
|
|
332
|
+
return Math.min(max, Math.max(min, Math.round(value)));
|
|
333
|
+
}
|
|
334
|
+
function connectionPolicyFrom(raw) {
|
|
335
|
+
if (typeof raw !== "object" || raw === null) return DEFAULT_CONNECTION_POLICY;
|
|
336
|
+
const row = raw;
|
|
337
|
+
const d = DEFAULT_CONNECTION_POLICY;
|
|
338
|
+
return {
|
|
339
|
+
bannerDelayMs: clampInt(row["reconnect_banner_delay_ms"], 0, 1e4, d.bannerDelayMs),
|
|
340
|
+
autoRejoin: typeof row["auto_rejoin"] === "boolean" ? row["auto_rejoin"] : d.autoRejoin,
|
|
341
|
+
autoRejoinMaxAttempts: clampInt(row["auto_rejoin_max_attempts"], 0, 20, d.autoRejoinMaxAttempts),
|
|
342
|
+
reconnectGraceSeconds: clampInt(row["reconnect_grace_seconds"], 0, 600, d.reconnectGraceSeconds)
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
var LIVEKIT_BASE_DELAYS_MS = [0, 300, 1200, 2700, 4800, 7e3];
|
|
346
|
+
function createReconnectPolicy(options = {}) {
|
|
347
|
+
const delays = options.delaysMs !== void 0 && options.delaysMs.length > 0 ? options.delaysMs : LIVEKIT_BASE_DELAYS_MS;
|
|
348
|
+
const giveUpMs = options.giveUpMs ?? 45e3;
|
|
349
|
+
const maxAttempts = options.maxAttempts ?? 12;
|
|
350
|
+
const random = options.random ?? Math.random;
|
|
351
|
+
return {
|
|
352
|
+
nextRetryDelayInMs({ retryCount, elapsedMs }) {
|
|
353
|
+
if (retryCount >= maxAttempts || elapsedMs >= giveUpMs) return null;
|
|
354
|
+
const base = delays[Math.min(retryCount, delays.length - 1)] ?? 0;
|
|
355
|
+
const spread = Math.min(2e3, Math.max(500, base / 2));
|
|
356
|
+
return Math.round(base + random() * spread);
|
|
357
|
+
}
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
function createWebNetworkPort() {
|
|
361
|
+
const scope = globalThis;
|
|
362
|
+
return {
|
|
363
|
+
online: () => scope.navigator?.onLine !== false,
|
|
364
|
+
subscribe(listener) {
|
|
365
|
+
if (typeof scope.addEventListener !== "function") return () => void 0;
|
|
366
|
+
const on = () => listener(true);
|
|
367
|
+
const off = () => listener(false);
|
|
368
|
+
scope.addEventListener("online", on);
|
|
369
|
+
scope.addEventListener("offline", off);
|
|
370
|
+
return () => {
|
|
371
|
+
scope.removeEventListener?.("online", on);
|
|
372
|
+
scope.removeEventListener?.("offline", off);
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
};
|
|
376
|
+
}
|
|
377
|
+
function createReachabilityProbe(urls, timeoutMs = 4e3) {
|
|
378
|
+
return async () => {
|
|
379
|
+
const targets = urls().filter((url) => url.length > 0);
|
|
380
|
+
if (targets.length === 0 || typeof fetch !== "function") return true;
|
|
381
|
+
const one = async (url) => {
|
|
382
|
+
const controller = typeof AbortController === "function" ? new AbortController() : null;
|
|
383
|
+
const timer = setTimeout(() => controller?.abort(), timeoutMs);
|
|
384
|
+
try {
|
|
385
|
+
await fetch(url, {
|
|
386
|
+
method: "GET",
|
|
387
|
+
mode: "no-cors",
|
|
388
|
+
cache: "no-store",
|
|
389
|
+
credentials: "omit",
|
|
390
|
+
...controller !== null ? { signal: controller.signal } : {}
|
|
391
|
+
});
|
|
392
|
+
return true;
|
|
393
|
+
} catch {
|
|
394
|
+
return false;
|
|
395
|
+
} finally {
|
|
396
|
+
clearTimeout(timer);
|
|
397
|
+
}
|
|
398
|
+
};
|
|
399
|
+
const answers = await Promise.all(targets.map(one));
|
|
400
|
+
return answers.every(Boolean);
|
|
401
|
+
};
|
|
402
|
+
}
|
|
403
|
+
function httpUrlOf(serverUrl) {
|
|
404
|
+
return serverUrl.replace(/^ws(s?):\/\//i, "http$1://");
|
|
405
|
+
}
|
|
406
|
+
var RESUME_WINDOW_MS = 15 * 6e4;
|
|
407
|
+
var RESUME_KEY = "mx-meet:resume:";
|
|
408
|
+
function createWebResumeStorage() {
|
|
409
|
+
const storage = globalThis.sessionStorage;
|
|
410
|
+
if (storage === void 0) return void 0;
|
|
411
|
+
return {
|
|
412
|
+
load(roomName) {
|
|
413
|
+
try {
|
|
414
|
+
const raw = storage.getItem(RESUME_KEY + roomName);
|
|
415
|
+
if (raw === null) return null;
|
|
416
|
+
const parsed = JSON.parse(raw);
|
|
417
|
+
return typeof parsed?.at === "number" && typeof parsed.request?.roomName === "string" ? parsed : null;
|
|
418
|
+
} catch {
|
|
419
|
+
return null;
|
|
420
|
+
}
|
|
421
|
+
},
|
|
422
|
+
save(roomName, marker) {
|
|
423
|
+
try {
|
|
424
|
+
storage.setItem(RESUME_KEY + roomName, JSON.stringify(marker));
|
|
425
|
+
} catch {
|
|
426
|
+
}
|
|
427
|
+
},
|
|
428
|
+
clear(roomName) {
|
|
429
|
+
try {
|
|
430
|
+
storage.removeItem(RESUME_KEY + roomName);
|
|
431
|
+
} catch {
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
};
|
|
435
|
+
}
|
|
436
|
+
var CLEARS_RESUME = (phase) => phase === "left" || phase === "removed" || phase === "superseded" || phase === "inactive" || phase === "ended" || phase === "record" || phase === "prejoin" || phase.startsWith("refused:") || phase.startsWith("waiting:");
|
|
437
|
+
function autoRejoinDelayMs(attempt, random = Math.random) {
|
|
438
|
+
const base = Math.min(15e3, 1e3 * 2 ** Math.max(0, attempt));
|
|
439
|
+
return Math.round(base * (0.5 + random()));
|
|
440
|
+
}
|
|
441
|
+
function createConnectionSupervisor(options) {
|
|
442
|
+
const { store, network } = options;
|
|
443
|
+
const now = options.now ?? (() => Date.now());
|
|
444
|
+
const random = options.random ?? Math.random;
|
|
445
|
+
const probeEveryMs = options.probeEveryMs ?? 3e3;
|
|
446
|
+
let driverRegion = null;
|
|
447
|
+
let probeTimer = null;
|
|
448
|
+
let probing = false;
|
|
449
|
+
let bannerTimer = null;
|
|
450
|
+
let rejoinTimer = null;
|
|
451
|
+
let rejoinAttempts = 0;
|
|
452
|
+
let lastPhase = store.snapshot().phase;
|
|
453
|
+
let lastSavedAt = 0;
|
|
454
|
+
let disposed = false;
|
|
455
|
+
const inCall = () => store.snapshot().phase === "in_call";
|
|
456
|
+
const dropped = () => store.snapshot().phase === "disconnected";
|
|
457
|
+
function setRegion(next) {
|
|
458
|
+
if (store.snapshot().connection !== next) store.patch({ connection: next });
|
|
459
|
+
}
|
|
460
|
+
function regionFor(reachable) {
|
|
461
|
+
if (!network.online()) return "offline";
|
|
462
|
+
if (reachable === false) return "server_unreachable";
|
|
463
|
+
if (dropped()) return reachable === true ? "stable" : store.snapshot().connection;
|
|
464
|
+
return driverRegion ?? "stable";
|
|
465
|
+
}
|
|
466
|
+
function needsWatch() {
|
|
467
|
+
const snap = store.snapshot();
|
|
468
|
+
if (dropped()) return true;
|
|
469
|
+
return inCall() && (driverRegion !== null || snap.connection !== "stable");
|
|
470
|
+
}
|
|
471
|
+
function stopProbe() {
|
|
472
|
+
if (probeTimer !== null) clearTimeout(probeTimer);
|
|
473
|
+
probeTimer = null;
|
|
474
|
+
}
|
|
475
|
+
function scheduleProbe(delayMs) {
|
|
476
|
+
if (disposed || probeTimer !== null) return;
|
|
477
|
+
probeTimer = setTimeout(() => {
|
|
478
|
+
probeTimer = null;
|
|
479
|
+
void runProbe();
|
|
480
|
+
}, delayMs);
|
|
481
|
+
}
|
|
482
|
+
async function runProbe() {
|
|
483
|
+
if (disposed || probing) return;
|
|
484
|
+
if (!needsWatch()) return;
|
|
485
|
+
if (!network.online()) {
|
|
486
|
+
setRegion("offline");
|
|
487
|
+
scheduleProbe(probeEveryMs);
|
|
488
|
+
return;
|
|
489
|
+
}
|
|
490
|
+
probing = true;
|
|
491
|
+
let reachable = false;
|
|
492
|
+
try {
|
|
493
|
+
reachable = await options.probe();
|
|
494
|
+
} catch {
|
|
495
|
+
reachable = false;
|
|
496
|
+
} finally {
|
|
497
|
+
probing = false;
|
|
498
|
+
}
|
|
499
|
+
if (disposed) return;
|
|
500
|
+
if (inCall() || dropped()) setRegion(regionFor(reachable));
|
|
501
|
+
if (dropped() && reachable) maybeRejoin();
|
|
502
|
+
if (needsWatch()) scheduleProbe(Math.round(probeEveryMs * (0.75 + random() * 0.5)));
|
|
503
|
+
}
|
|
504
|
+
function syncBanner() {
|
|
505
|
+
const snap = store.snapshot();
|
|
506
|
+
const unstable = snap.phase === "in_call" && snap.connection !== "stable";
|
|
507
|
+
if (!unstable) {
|
|
508
|
+
if (bannerTimer !== null) clearTimeout(bannerTimer);
|
|
509
|
+
bannerTimer = null;
|
|
510
|
+
if (snap.connectionBanner) store.patch({ connectionBanner: false });
|
|
511
|
+
return;
|
|
512
|
+
}
|
|
513
|
+
if (snap.connectionBanner || bannerTimer !== null) return;
|
|
514
|
+
const delay = options.policy().bannerDelayMs;
|
|
515
|
+
if (delay <= 0) {
|
|
516
|
+
store.patch({ connectionBanner: true });
|
|
517
|
+
return;
|
|
518
|
+
}
|
|
519
|
+
bannerTimer = setTimeout(() => {
|
|
520
|
+
bannerTimer = null;
|
|
521
|
+
const latest = store.snapshot();
|
|
522
|
+
if (latest.phase === "in_call" && latest.connection !== "stable" && !latest.connectionBanner) {
|
|
523
|
+
store.patch({ connectionBanner: true });
|
|
524
|
+
}
|
|
525
|
+
}, delay);
|
|
526
|
+
}
|
|
527
|
+
function stopRejoin() {
|
|
528
|
+
if (rejoinTimer !== null) clearTimeout(rejoinTimer);
|
|
529
|
+
rejoinTimer = null;
|
|
530
|
+
}
|
|
531
|
+
function maybeRejoin() {
|
|
532
|
+
if (disposed || rejoinTimer !== null || !dropped()) return;
|
|
533
|
+
const policy = options.policy();
|
|
534
|
+
if (!policy.autoRejoin) return;
|
|
535
|
+
if (rejoinAttempts >= policy.autoRejoinMaxAttempts) return;
|
|
536
|
+
if (!network.online()) return;
|
|
537
|
+
const attempt = rejoinAttempts;
|
|
538
|
+
rejoinTimer = setTimeout(() => {
|
|
539
|
+
rejoinTimer = null;
|
|
540
|
+
if (disposed || !dropped() || !network.online()) return;
|
|
541
|
+
rejoinAttempts = attempt + 1;
|
|
542
|
+
options.report?.("info", `meet: rejoining by itself (attempt ${rejoinAttempts} of ${policy.autoRejoinMaxAttempts})`);
|
|
543
|
+
void options.autoRejoin().catch(() => void 0);
|
|
544
|
+
}, autoRejoinDelayMs(attempt, random));
|
|
545
|
+
}
|
|
546
|
+
function saveMarker(force) {
|
|
547
|
+
const resume = options.resume;
|
|
548
|
+
const snap = store.snapshot();
|
|
549
|
+
if (resume === void 0 || snap.roomName === null) return;
|
|
550
|
+
const at = now();
|
|
551
|
+
if (!force && at - lastSavedAt < 1e4) return;
|
|
552
|
+
const request = options.currentRequest();
|
|
553
|
+
if (request === null) return;
|
|
554
|
+
lastSavedAt = at;
|
|
555
|
+
resume.save(snap.roomName, { request, at });
|
|
556
|
+
}
|
|
557
|
+
const unsubscribeStore = store.subscribe(() => {
|
|
558
|
+
if (disposed) return;
|
|
559
|
+
const snap = store.snapshot();
|
|
560
|
+
const phase = snap.phase;
|
|
561
|
+
const changed = phase !== lastPhase;
|
|
562
|
+
lastPhase = phase;
|
|
563
|
+
if (changed) {
|
|
564
|
+
if (phase === "in_call") {
|
|
565
|
+
rejoinAttempts = 0;
|
|
566
|
+
stopRejoin();
|
|
567
|
+
saveMarker(true);
|
|
568
|
+
} else if (phase === "disconnected") {
|
|
569
|
+
driverRegion = null;
|
|
570
|
+
saveMarker(true);
|
|
571
|
+
stopProbe();
|
|
572
|
+
scheduleProbe(0);
|
|
573
|
+
} else {
|
|
574
|
+
stopRejoin();
|
|
575
|
+
if (phase !== "joining") driverRegion = null;
|
|
576
|
+
if (CLEARS_RESUME(phase) && snap.roomName !== null) options.resume?.clear(snap.roomName);
|
|
577
|
+
}
|
|
578
|
+
} else if (phase === "in_call" || phase === "disconnected") {
|
|
579
|
+
saveMarker(false);
|
|
580
|
+
}
|
|
581
|
+
syncBanner();
|
|
582
|
+
});
|
|
583
|
+
const unsubscribeNetwork = network.subscribe((online) => {
|
|
584
|
+
if (disposed) return;
|
|
585
|
+
if (!online) {
|
|
586
|
+
if (inCall() || dropped()) setRegion("offline");
|
|
587
|
+
stopRejoin();
|
|
588
|
+
stopProbe();
|
|
589
|
+
if (needsWatch()) scheduleProbe(probeEveryMs);
|
|
590
|
+
return;
|
|
591
|
+
}
|
|
592
|
+
stopProbe();
|
|
593
|
+
if (needsWatch()) scheduleProbe(0);
|
|
594
|
+
});
|
|
595
|
+
return {
|
|
596
|
+
driverState(state) {
|
|
597
|
+
if (disposed) return;
|
|
598
|
+
if (state === "connected") {
|
|
599
|
+
driverRegion = null;
|
|
600
|
+
if (inCall() || store.snapshot().phase === "joining") setRegion(network.online() ? "stable" : "offline");
|
|
601
|
+
if (!needsWatch()) stopProbe();
|
|
602
|
+
return;
|
|
603
|
+
}
|
|
604
|
+
if (state === "reconnecting" || state === "signal_reconnecting") {
|
|
605
|
+
driverRegion = state;
|
|
606
|
+
if (inCall()) {
|
|
607
|
+
setRegion(network.online() ? state : "offline");
|
|
608
|
+
stopProbe();
|
|
609
|
+
scheduleProbe(0);
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
},
|
|
613
|
+
exitConnection() {
|
|
614
|
+
return network.online() ? "server_unreachable" : "offline";
|
|
615
|
+
},
|
|
616
|
+
resumable(roomName) {
|
|
617
|
+
const resume = options.resume;
|
|
618
|
+
if (resume === void 0) return null;
|
|
619
|
+
const marker = resume.load(roomName);
|
|
620
|
+
if (marker === null) return null;
|
|
621
|
+
if (now() - marker.at > RESUME_WINDOW_MS) {
|
|
622
|
+
resume.clear(roomName);
|
|
623
|
+
return null;
|
|
624
|
+
}
|
|
625
|
+
return marker;
|
|
626
|
+
},
|
|
627
|
+
dispose() {
|
|
628
|
+
disposed = true;
|
|
629
|
+
stopProbe();
|
|
630
|
+
stopRejoin();
|
|
631
|
+
if (bannerTimer !== null) clearTimeout(bannerTimer);
|
|
632
|
+
bannerTimer = null;
|
|
633
|
+
unsubscribeStore();
|
|
634
|
+
unsubscribeNetwork();
|
|
635
|
+
}
|
|
636
|
+
};
|
|
637
|
+
}
|
|
638
|
+
var DELIBERATE_DEPARTURES = /* @__PURE__ */ new Set([
|
|
639
|
+
"CLIENT_INITIATED",
|
|
640
|
+
"PARTICIPANT_REMOVED",
|
|
641
|
+
"ROOM_DELETED",
|
|
642
|
+
"ROOM_CLOSED",
|
|
643
|
+
"DUPLICATE_IDENTITY",
|
|
644
|
+
"USER_REJECTED",
|
|
645
|
+
"USER_UNAVAILABLE",
|
|
646
|
+
"SIP_TRUNK_FAILURE"
|
|
647
|
+
]);
|
|
648
|
+
function isUnannouncedDeparture(reason) {
|
|
649
|
+
return reason === null || !DELIBERATE_DEPARTURES.has(reason);
|
|
650
|
+
}
|
|
651
|
+
function mergeGhosts(live, ghosts, at) {
|
|
652
|
+
const present = new Set(live.map((p) => p.identity));
|
|
653
|
+
const kept = /* @__PURE__ */ new Map();
|
|
654
|
+
const extra = [];
|
|
655
|
+
for (const [identity, ghost] of ghosts) {
|
|
656
|
+
if (present.has(identity) || ghost.until <= at) continue;
|
|
657
|
+
kept.set(identity, ghost);
|
|
658
|
+
extra.push({
|
|
659
|
+
...ghost.participant,
|
|
660
|
+
micEnabled: false,
|
|
661
|
+
cameraEnabled: false,
|
|
662
|
+
screenSharing: false,
|
|
663
|
+
reconnecting: true
|
|
664
|
+
});
|
|
665
|
+
}
|
|
666
|
+
return { roster: [...live, ...extra], kept };
|
|
667
|
+
}
|
|
668
|
+
|
|
310
669
|
// src/react/provider.tsx
|
|
311
670
|
var import_global_slot2 = require("@ai-matrx/kit/global-slot");
|
|
312
671
|
var import_react = require("react");
|
|
@@ -703,7 +1062,8 @@ function parseRoomToken(body, roomName) {
|
|
|
703
1062
|
admissionState: readString(payload, "admission_state") ?? void 0,
|
|
704
1063
|
pollSeconds: typeof payload["poll_seconds"] === "number" ? payload["poll_seconds"] : void 0,
|
|
705
1064
|
breakoutRoomId: readString(payload, "breakout_room_id") ?? null,
|
|
706
|
-
observersVisibleTo: readString(payload, "observers_visible_to") === "hosts" ? "hosts" : "everyone"
|
|
1065
|
+
observersVisibleTo: readString(payload, "observers_visible_to") === "hosts" ? "hosts" : "everyone",
|
|
1066
|
+
connectionPolicy: connectionPolicyFrom(payload["connection_policy"])
|
|
707
1067
|
};
|
|
708
1068
|
}
|
|
709
1069
|
function createTokenClient(options) {
|
|
@@ -1404,8 +1764,14 @@ function nextPhase(from, event) {
|
|
|
1404
1764
|
return from === "resolving" ? "prejoin" : null;
|
|
1405
1765
|
case "unsupported":
|
|
1406
1766
|
return from === "resolving" ? "unsupported" : null;
|
|
1767
|
+
case "link_state":
|
|
1768
|
+
return from === "resolving" && event.reason !== null && isJoinRefusalReason(event.reason) ? refusalPhase(event.reason) : null;
|
|
1407
1769
|
case "join":
|
|
1408
1770
|
return JOINABLE_FROM.has(from) ? "joining" : null;
|
|
1771
|
+
case "auto_rejoin":
|
|
1772
|
+
return from === "disconnected" ? "joining" : null;
|
|
1773
|
+
case "resumed":
|
|
1774
|
+
return from === "resolving" ? "joining" : null;
|
|
1409
1775
|
case "back_to_prejoin":
|
|
1410
1776
|
return from === "left" || from === "disconnected" ? "prejoin" : null;
|
|
1411
1777
|
case "retry":
|
|
@@ -2166,6 +2532,8 @@ var RPCS = {
|
|
|
2166
2532
|
getOrCreateMeeting: "meet_get_or_create_meeting",
|
|
2167
2533
|
/** Resolve a durable public link, before/during/after its scheduled time. */
|
|
2168
2534
|
meetingBySlug: "meet_meeting_by_slug",
|
|
2535
|
+
/** The current run of a durable link (open / not started / ended + next start), no pass needed. */
|
|
2536
|
+
linkState: "meet_link_state",
|
|
2169
2537
|
/*
|
|
2170
2538
|
* ── S4 ADMISSION (CORE-DESIGN §2.4). Each returns the decided row; the caller then
|
|
2171
2539
|
* sends the payload-free `mx.state.changed` hint. Authorized by `who_may_admit`.
|
|
@@ -2474,6 +2842,14 @@ function createMeetRepository(options) {
|
|
|
2474
2842
|
if (response.error !== null) throw normalizeMeetError(response.error, operation);
|
|
2475
2843
|
}
|
|
2476
2844
|
}
|
|
2845
|
+
async function readLinkRun(slug) {
|
|
2846
|
+
try {
|
|
2847
|
+
const response = await db.rpc(RPCS.linkState, { p_slug: slug });
|
|
2848
|
+
return projectLinkRun(response.error === null ? response.data : null);
|
|
2849
|
+
} catch {
|
|
2850
|
+
return void 0;
|
|
2851
|
+
}
|
|
2852
|
+
}
|
|
2477
2853
|
async function guard(operation, run) {
|
|
2478
2854
|
let response = await run();
|
|
2479
2855
|
if (response.error !== null) {
|
|
@@ -2875,11 +3251,12 @@ function createMeetRepository(options) {
|
|
|
2875
3251
|
return projectMeeting(row);
|
|
2876
3252
|
},
|
|
2877
3253
|
async meetingBySlug(slug) {
|
|
2878
|
-
const row = await
|
|
2879
|
-
"meet.meetingBySlug",
|
|
2880
|
-
(
|
|
2881
|
-
);
|
|
2882
|
-
|
|
3254
|
+
const [row, linkRun] = await Promise.all([
|
|
3255
|
+
guard("meet.meetingBySlug", () => db.rpc(RPCS.meetingBySlug, { p_slug: slug })),
|
|
3256
|
+
readLinkRun(slug)
|
|
3257
|
+
]);
|
|
3258
|
+
const record = withRunNoteTaker(projectMeeting(row));
|
|
3259
|
+
return linkRun === void 0 ? record : { ...record, linkRun };
|
|
2883
3260
|
},
|
|
2884
3261
|
async meeting(id) {
|
|
2885
3262
|
const response = await db.from(TABLES.meetings).select(MEETING_COLUMNS).eq("id", id).single();
|
|
@@ -3068,6 +3445,15 @@ function snippetRuns(snippet) {
|
|
|
3068
3445
|
if (last < snippet.length) runs.push({ text: snippet.slice(last), hit: false });
|
|
3069
3446
|
return runs;
|
|
3070
3447
|
}
|
|
3448
|
+
function projectLinkRun(value) {
|
|
3449
|
+
if (typeof value !== "object" || value === null) return void 0;
|
|
3450
|
+
const row = value;
|
|
3451
|
+
const state = row["state"];
|
|
3452
|
+
if (state !== "open" && state !== "not_started" && state !== "ended" && state !== "cancelled") return void 0;
|
|
3453
|
+
const reason = row["reason"] === "ended" || row["reason"] === "cancelled" ? row["reason"] : null;
|
|
3454
|
+
const nextStart = typeof row["next_start"] === "string" ? row["next_start"] : null;
|
|
3455
|
+
return { state, reason, nextStart };
|
|
3456
|
+
}
|
|
3071
3457
|
|
|
3072
3458
|
// src/core/app-panel.ts
|
|
3073
3459
|
function appPanelOf(meeting) {
|
|
@@ -3525,6 +3911,7 @@ var EMPTY = {
|
|
|
3525
3911
|
phaseNotice: null,
|
|
3526
3912
|
roomName: null,
|
|
3527
3913
|
connection: "stable",
|
|
3914
|
+
connectionBanner: false,
|
|
3528
3915
|
share: NO_SHARE,
|
|
3529
3916
|
call: null,
|
|
3530
3917
|
waiting: null,
|
|
@@ -4804,6 +5191,18 @@ function createRoomEngine(options) {
|
|
|
4804
5191
|
let disposed = false;
|
|
4805
5192
|
let moving = false;
|
|
4806
5193
|
const confirmingEnds = /* @__PURE__ */ new WeakSet();
|
|
5194
|
+
let lastServerUrl = null;
|
|
5195
|
+
let connectionPolicy = DEFAULT_CONNECTION_POLICY;
|
|
5196
|
+
let ghosts = /* @__PURE__ */ new Map();
|
|
5197
|
+
let ghostTimer = null;
|
|
5198
|
+
let liveRoster = [];
|
|
5199
|
+
const departures = /* @__PURE__ */ new Map();
|
|
5200
|
+
function requestAsNow() {
|
|
5201
|
+
if (lastRequest === null) return null;
|
|
5202
|
+
const media = store.snapshot().media;
|
|
5203
|
+
if (store.snapshot().phase !== "in_call") return lastRequest;
|
|
5204
|
+
return { ...lastRequest, mode: media.camera.enabled ? "video" : "audio", microphone: media.microphone.enabled };
|
|
5205
|
+
}
|
|
4807
5206
|
async function dropRoom() {
|
|
4808
5207
|
const target = driver;
|
|
4809
5208
|
driver = null;
|
|
@@ -4814,6 +5213,7 @@ function createRoomEngine(options) {
|
|
|
4814
5213
|
store.setLocalTrack("microphone", IDLE_TRACK);
|
|
4815
5214
|
store.setLocalTrack("camera", IDLE_TRACK);
|
|
4816
5215
|
store.patch({ share: NO_SHARE, connection: "stable" });
|
|
5216
|
+
clearGhosts();
|
|
4817
5217
|
await target?.disconnect();
|
|
4818
5218
|
}
|
|
4819
5219
|
async function leaveRoom() {
|
|
@@ -4881,7 +5281,7 @@ function createRoomEngine(options) {
|
|
|
4881
5281
|
participants(list) {
|
|
4882
5282
|
const joinedAt = new Date(now()).toISOString();
|
|
4883
5283
|
const before = new Set(store.snapshot().participants.map((p) => p.identity));
|
|
4884
|
-
|
|
5284
|
+
projectRoster(list, joinedAt);
|
|
4885
5285
|
const snapshot = store.snapshot();
|
|
4886
5286
|
const newcomer = snapshot.participants.some(
|
|
4887
5287
|
(participant) => !participant.isLocal && !before.has(participant.identity)
|
|
@@ -4893,6 +5293,9 @@ function createRoomEngine(options) {
|
|
|
4893
5293
|
);
|
|
4894
5294
|
}
|
|
4895
5295
|
},
|
|
5296
|
+
participantLeft(who, reason) {
|
|
5297
|
+
departures.set(who, reason);
|
|
5298
|
+
},
|
|
4896
5299
|
roomMetadata(metadata) {
|
|
4897
5300
|
let spotlight = null;
|
|
4898
5301
|
if (metadata.length > 0) {
|
|
@@ -4919,12 +5322,8 @@ function createRoomEngine(options) {
|
|
|
4919
5322
|
store.setQuality(asParticipantIdentity(who), value);
|
|
4920
5323
|
},
|
|
4921
5324
|
connectionState(state) {
|
|
4922
|
-
|
|
4923
|
-
|
|
4924
|
-
store.dispatch({ type: "connected" });
|
|
4925
|
-
} else if (state === "reconnecting") {
|
|
4926
|
-
store.patch({ connection: "reconnecting" });
|
|
4927
|
-
}
|
|
5325
|
+
supervisor.driverState(state);
|
|
5326
|
+
if (state === "connected" && store.snapshot().phase === "joining") store.dispatch({ type: "connected" });
|
|
4928
5327
|
},
|
|
4929
5328
|
transcription(event) {
|
|
4930
5329
|
const snapshot = store.snapshot();
|
|
@@ -5057,6 +5456,7 @@ function createRoomEngine(options) {
|
|
|
5057
5456
|
};
|
|
5058
5457
|
async function resolveExitAndApply(hint) {
|
|
5059
5458
|
const connection = current?.identity ?? store.snapshot().localIdentity ?? null;
|
|
5459
|
+
lastRequest = requestAsNow();
|
|
5060
5460
|
await dropRoom();
|
|
5061
5461
|
let server;
|
|
5062
5462
|
if (options.readExit === void 0 || connection === null || store.snapshot().meeting === null) {
|
|
@@ -5070,7 +5470,7 @@ function createRoomEngine(options) {
|
|
|
5070
5470
|
}
|
|
5071
5471
|
if (disposed) return;
|
|
5072
5472
|
const resolved = resolveExit({ server, hint, frozen: options.wasFrozen?.() ?? false });
|
|
5073
|
-
store.patch({ connection: resolved.connection });
|
|
5473
|
+
store.patch({ connection: server === null ? supervisor.exitConnection() : resolved.connection });
|
|
5074
5474
|
if (resolved.to === "waiting:knocking") {
|
|
5075
5475
|
store.patch({
|
|
5076
5476
|
waiting: {
|
|
@@ -5102,6 +5502,8 @@ function createRoomEngine(options) {
|
|
|
5102
5502
|
...request.observe === true ? { observe: true } : {}
|
|
5103
5503
|
});
|
|
5104
5504
|
current = token;
|
|
5505
|
+
lastServerUrl = token.serverUrl;
|
|
5506
|
+
connectionPolicy = token.connectionPolicy ?? DEFAULT_CONNECTION_POLICY;
|
|
5105
5507
|
store.patch({
|
|
5106
5508
|
localIdentity: token.identity,
|
|
5107
5509
|
breakoutRoomId: token.breakoutRoomId ?? null,
|
|
@@ -5206,7 +5608,59 @@ function createRoomEngine(options) {
|
|
|
5206
5608
|
throw normalized;
|
|
5207
5609
|
}
|
|
5208
5610
|
}
|
|
5209
|
-
|
|
5611
|
+
function projectRoster(list, joinedAt) {
|
|
5612
|
+
const at = now();
|
|
5613
|
+
const previous = new Map(liveRoster.map((p) => [p.identity, p]));
|
|
5614
|
+
const present = new Set(list.map((p) => p.identity));
|
|
5615
|
+
const graceMs = connectionPolicy.reconnectGraceSeconds * 1e3;
|
|
5616
|
+
for (const [who, reason] of departures) {
|
|
5617
|
+
const last = previous.get(who);
|
|
5618
|
+
if (last !== void 0 && !last.isLocal && !present.has(who) && graceMs > 0 && isUnannouncedDeparture(reason)) {
|
|
5619
|
+
ghosts.set(who, { participant: last, until: at + graceMs });
|
|
5620
|
+
}
|
|
5621
|
+
}
|
|
5622
|
+
departures.clear();
|
|
5623
|
+
liveRoster = list;
|
|
5624
|
+
const merged = mergeGhosts(list, ghosts, at);
|
|
5625
|
+
ghosts = merged.kept;
|
|
5626
|
+
store.setParticipants(
|
|
5627
|
+
merged.roster.map((entry) => {
|
|
5628
|
+
const projected = projectParticipant(entry, joinedAt);
|
|
5629
|
+
return entry.reconnecting === true ? { ...projected, reconnecting: true, quality: "lost" } : projected;
|
|
5630
|
+
})
|
|
5631
|
+
);
|
|
5632
|
+
scheduleGhostExpiry();
|
|
5633
|
+
}
|
|
5634
|
+
function scheduleGhostExpiry() {
|
|
5635
|
+
if (ghostTimer !== null) clearTimeout(ghostTimer);
|
|
5636
|
+
ghostTimer = null;
|
|
5637
|
+
if (ghosts.size === 0 || disposed) return;
|
|
5638
|
+
const next = Math.min(...[...ghosts.values()].map((g) => g.until));
|
|
5639
|
+
ghostTimer = setTimeout(() => {
|
|
5640
|
+
ghostTimer = null;
|
|
5641
|
+
if (disposed || driver === null) return;
|
|
5642
|
+
projectRoster(liveRoster, new Date(now()).toISOString());
|
|
5643
|
+
}, Math.max(0, next - now()) + 50);
|
|
5644
|
+
}
|
|
5645
|
+
function clearGhosts() {
|
|
5646
|
+
ghosts = /* @__PURE__ */ new Map();
|
|
5647
|
+
departures.clear();
|
|
5648
|
+
liveRoster = [];
|
|
5649
|
+
if (ghostTimer !== null) clearTimeout(ghostTimer);
|
|
5650
|
+
ghostTimer = null;
|
|
5651
|
+
}
|
|
5652
|
+
const supervisor = createConnectionSupervisor({
|
|
5653
|
+
store,
|
|
5654
|
+
network: options.connection?.network ?? createWebNetworkPort(),
|
|
5655
|
+
probe: () => options.connection?.probe?.(current?.serverUrl ?? lastServerUrl) ?? Promise.resolve(true),
|
|
5656
|
+
policy: () => connectionPolicy,
|
|
5657
|
+
autoRejoin: () => engine.autoRejoin(),
|
|
5658
|
+
currentRequest: requestAsNow,
|
|
5659
|
+
resume: options.connection?.resume,
|
|
5660
|
+
now,
|
|
5661
|
+
report: (level, message) => report(level, message)
|
|
5662
|
+
});
|
|
5663
|
+
const engine = {
|
|
5210
5664
|
join(request) {
|
|
5211
5665
|
if (joining !== null) return joining;
|
|
5212
5666
|
if (store.snapshot().phase !== "joining" && !store.dispatch({ type: "join", click: true })) {
|
|
@@ -5251,6 +5705,35 @@ function createRoomEngine(options) {
|
|
|
5251
5705
|
});
|
|
5252
5706
|
return joining;
|
|
5253
5707
|
},
|
|
5708
|
+
async autoRejoin() {
|
|
5709
|
+
const request = lastRequest;
|
|
5710
|
+
if (request === null || joining !== null) return joining ?? void 0;
|
|
5711
|
+
if (!store.dispatch({ type: "auto_rejoin" })) return;
|
|
5712
|
+
tokens.invalidate(request.roomName);
|
|
5713
|
+
current = null;
|
|
5714
|
+
joining = connect(request).catch((error) => {
|
|
5715
|
+
throw failJoin(error, "meet.autoRejoin");
|
|
5716
|
+
}).finally(() => {
|
|
5717
|
+
joining = null;
|
|
5718
|
+
});
|
|
5719
|
+
return joining;
|
|
5720
|
+
},
|
|
5721
|
+
resume(roomName) {
|
|
5722
|
+
const marker = supervisor.resumable(roomName);
|
|
5723
|
+
if (marker === null || joining !== null) return false;
|
|
5724
|
+
if (!store.dispatch({ type: "resumed" })) return false;
|
|
5725
|
+
lastRequest = { ...marker.request, roomName };
|
|
5726
|
+
report("info", "meet: this tab was in the call before the page reloaded \u2014 rejoining");
|
|
5727
|
+
tokens.invalidate(roomName);
|
|
5728
|
+
current = null;
|
|
5729
|
+
joining = connect(lastRequest).catch((error) => {
|
|
5730
|
+
throw failJoin(error, "meet.resume");
|
|
5731
|
+
}).finally(() => {
|
|
5732
|
+
joining = null;
|
|
5733
|
+
});
|
|
5734
|
+
joining.catch(() => void 0);
|
|
5735
|
+
return true;
|
|
5736
|
+
},
|
|
5254
5737
|
async moveToBreakout(roomId) {
|
|
5255
5738
|
const request = lastRequest;
|
|
5256
5739
|
if (request === null || driver === null) {
|
|
@@ -5418,6 +5901,8 @@ function createRoomEngine(options) {
|
|
|
5418
5901
|
token: () => current,
|
|
5419
5902
|
async dispose() {
|
|
5420
5903
|
disposed = true;
|
|
5904
|
+
supervisor.dispose();
|
|
5905
|
+
clearGhosts();
|
|
5421
5906
|
if (driver !== null) {
|
|
5422
5907
|
const target = driver;
|
|
5423
5908
|
driver = null;
|
|
@@ -5426,6 +5911,7 @@ function createRoomEngine(options) {
|
|
|
5426
5911
|
store.reset();
|
|
5427
5912
|
}
|
|
5428
5913
|
};
|
|
5914
|
+
return engine;
|
|
5429
5915
|
}
|
|
5430
5916
|
function canModerate(participant) {
|
|
5431
5917
|
return participant !== void 0 && isHostRole(participant.role);
|
|
@@ -5736,8 +6222,11 @@ function mapConnectionState(state) {
|
|
|
5736
6222
|
case "connecting":
|
|
5737
6223
|
return "connecting";
|
|
5738
6224
|
case "reconnecting":
|
|
5739
|
-
case "signalReconnecting":
|
|
5740
6225
|
return "reconnecting";
|
|
6226
|
+
// Only the signal socket is down (S6b): its own region value, so the supervisor can tell a
|
|
6227
|
+
// signal blip from a full media reconnect.
|
|
6228
|
+
case "signalReconnecting":
|
|
6229
|
+
return "signal_reconnecting";
|
|
5741
6230
|
default:
|
|
5742
6231
|
return "disconnected";
|
|
5743
6232
|
}
|
|
@@ -5818,8 +6307,12 @@ function createLiveKitDriver(handlers, options = {}) {
|
|
|
5818
6307
|
// R5: large meetings degrade gracefully instead of melting a laptop.
|
|
5819
6308
|
adaptiveStream: true,
|
|
5820
6309
|
dynacast: true,
|
|
6310
|
+
// S6b: OUR retry schedule — jittered from attempt 0 (the SDK's default has no jitter on its
|
|
6311
|
+
// first two attempts), give-up limits in one place (`connection-supervisor.ts`).
|
|
6312
|
+
reconnectPolicy: createReconnectPolicy(),
|
|
5821
6313
|
...options.roomOptions
|
|
5822
6314
|
});
|
|
6315
|
+
const reasonName = (reason) => reason === void 0 || reason === null ? null : module2.DisconnectReason?.[reason] ?? String(reason);
|
|
5823
6316
|
const roster = () => {
|
|
5824
6317
|
const local = snapshotParticipant(created.localParticipant, true);
|
|
5825
6318
|
const remotes = [...created.remoteParticipants.values()].map(
|
|
@@ -5837,7 +6330,10 @@ function createLiveKitDriver(handlers, options = {}) {
|
|
|
5837
6330
|
if (track.kind !== Track.Kind.Audio) return;
|
|
5838
6331
|
for (const element of track.detach()) element.remove();
|
|
5839
6332
|
});
|
|
5840
|
-
created.on(RoomEvent.ParticipantConnected, roster).on(RoomEvent.ParticipantDisconnected,
|
|
6333
|
+
created.on(RoomEvent.ParticipantConnected, roster).on(RoomEvent.ParticipantDisconnected, (participant, reason) => {
|
|
6334
|
+
handlers.participantLeft?.(participant.identity, reasonName(reason));
|
|
6335
|
+
roster();
|
|
6336
|
+
}).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) => {
|
|
5841
6337
|
handlers.roomMetadata?.(metadata ?? "");
|
|
5842
6338
|
}).on(RoomEvent.ActiveSpeakersChanged, (speakers) => {
|
|
5843
6339
|
handlers.activeSpeakers(speakers.map((speaker) => speaker.identity));
|
|
@@ -5854,8 +6350,7 @@ function createLiveKitDriver(handlers, options = {}) {
|
|
|
5854
6350
|
}).on(RoomEvent.Disconnected, (reason) => {
|
|
5855
6351
|
audioContainer?.remove();
|
|
5856
6352
|
audioContainer = null;
|
|
5857
|
-
|
|
5858
|
-
handlers.disconnected(named);
|
|
6353
|
+
handlers.disconnected(reasonName(reason));
|
|
5859
6354
|
});
|
|
5860
6355
|
try {
|
|
5861
6356
|
created.registerTextStreamHandler(TRANSCRIPTION_TOPIC, (reader, participantInfo) => {
|
|
@@ -6245,7 +6740,14 @@ function MeetRuntime(props) {
|
|
|
6245
6740
|
effectsStorage,
|
|
6246
6741
|
resolveDevice: (kind) => devicesHolder.current?.resolve(kind) ?? null,
|
|
6247
6742
|
// §3.4: on every LiveKit disconnect, ask the server what happened to this connection.
|
|
6248
|
-
readExit: (connection) => meetings.readExit(connection)
|
|
6743
|
+
readExit: (connection) => meetings.readExit(connection),
|
|
6744
|
+
// S6b: the connection supervisor's ports — the device's network, whether OUR servers (the
|
|
6745
|
+
// API and the media server) answer, and the per-tab reload-after-offline marker.
|
|
6746
|
+
connection: {
|
|
6747
|
+
network: createWebNetworkPort(),
|
|
6748
|
+
resume: createWebResumeStorage(),
|
|
6749
|
+
probe: (serverUrl) => createReachabilityProbe(() => [baseUrl, ...serverUrl !== null ? [httpUrlOf(serverUrl)] : []])()
|
|
6750
|
+
}
|
|
6249
6751
|
});
|
|
6250
6752
|
const meetings = createMeetingSession({
|
|
6251
6753
|
manager,
|
|
@@ -6573,7 +7075,7 @@ function participantAttributes(p) {
|
|
|
6573
7075
|
"data-meet-participant-role": observedRole(p.role, p.standing),
|
|
6574
7076
|
"data-meet-participant-self": String(p.isLocal)
|
|
6575
7077
|
};
|
|
6576
|
-
const quality = observedQuality(p.quality);
|
|
7078
|
+
const quality = p.reconnecting === true ? "reconnecting" : observedQuality(p.quality);
|
|
6577
7079
|
if (quality !== null) attrs["data-meet-participant-connection"] = quality;
|
|
6578
7080
|
return attrs;
|
|
6579
7081
|
}
|
|
@@ -8792,6 +9294,7 @@ function useMeetCore() {
|
|
|
8792
9294
|
reason: snapshot?.reason ?? null,
|
|
8793
9295
|
phaseNotice: snapshot?.phaseNotice ?? null,
|
|
8794
9296
|
connection: snapshot?.connection ?? "stable",
|
|
9297
|
+
connectionBanner: snapshot?.connectionBanner ?? false,
|
|
8795
9298
|
share: snapshot?.share ?? NO_SHARE,
|
|
8796
9299
|
call: snapshot?.call ?? null,
|
|
8797
9300
|
actions
|
|
@@ -10058,7 +10561,8 @@ function ParticipantTile({
|
|
|
10058
10561
|
participant.displayName,
|
|
10059
10562
|
participant.isLocal ? " (you)" : "",
|
|
10060
10563
|
participant.isAgent ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "mx-meet__badge", children: "AI" }) : null,
|
|
10061
|
-
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(RoleBadge, { role: participant.role, standing: participant.standing })
|
|
10564
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(RoleBadge, { role: participant.role, standing: participant.standing }),
|
|
10565
|
+
participant.reconnecting === true ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "mx-meet__badge", children: "Reconnecting\u2026" }) : null
|
|
10062
10566
|
] }),
|
|
10063
10567
|
!participant.micEnabled ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(MicOffIcon, { width: 14, height: 14 }) : null,
|
|
10064
10568
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(QualityBars, { quality: participant.quality })
|
|
@@ -10101,6 +10605,12 @@ function QualityBars({ quality }) {
|
|
|
10101
10605
|
const label = quality === "lost" ? "Connection lost" : quality === "poor" ? "Poor connection" : `Connection ${quality}`;
|
|
10102
10606
|
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: `mx-meet__signal mx-meet__signal--${quality}`, title: label, "aria-label": label, role: "img", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(SignalIcon, { bars, width: 14, height: 14 }) });
|
|
10103
10607
|
}
|
|
10608
|
+
var CONNECTION_BANNER = {
|
|
10609
|
+
signal_reconnecting: "Reconnecting\u2026",
|
|
10610
|
+
reconnecting: "Reconnecting\u2026",
|
|
10611
|
+
offline: "You're offline. Reconnecting when your internet is back\u2026",
|
|
10612
|
+
server_unreachable: "Can't reach the meeting server. Retrying\u2026"
|
|
10613
|
+
};
|
|
10104
10614
|
function Stage({ pageSize }) {
|
|
10105
10615
|
const stage = useStage(pageSize);
|
|
10106
10616
|
const columns = gridColumns(stage.tiles.length);
|
|
@@ -11808,7 +12318,7 @@ function MeetSkinRoom({
|
|
|
11808
12318
|
headerControls !== void 0 && headerControls !== null ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "mx-meet__header-controls", "data-meet-header-controls": "", children: headerControls }) : null,
|
|
11809
12319
|
isHost ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(HostMenu, {}) : null
|
|
11810
12320
|
] }),
|
|
11811
|
-
core.connection !== "stable" ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { className: "mx-meet__banner", role: "status", children:
|
|
12321
|
+
core.connectionBanner && core.connection !== "stable" ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { className: "mx-meet__banner", role: "status", "data-meet-notice": `connection-${core.connection}`, children: CONNECTION_BANNER[core.connection] }) : null,
|
|
11812
12322
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(ShareNotice, {}),
|
|
11813
12323
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Stage, {}),
|
|
11814
12324
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Captions, {}),
|
|
@@ -11842,7 +12352,7 @@ var REFUSED_TITLES = {
|
|
|
11842
12352
|
registration_required: "Register to join this meeting"
|
|
11843
12353
|
};
|
|
11844
12354
|
function ExitScreen({ onLeave }) {
|
|
11845
|
-
const { phase, reason, host, snapshot, actions, call } = useMeetCore();
|
|
12355
|
+
const { phase, reason, host, snapshot, actions, call, connection } = useMeetCore();
|
|
11846
12356
|
const [busy, setBusy] = (0, import_react10.useState)(false);
|
|
11847
12357
|
const failure = snapshot?.failure ?? null;
|
|
11848
12358
|
const rejoin = /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
@@ -11882,7 +12392,7 @@ function ExitScreen({ onLeave }) {
|
|
|
11882
12392
|
if (phase.startsWith("refused:")) {
|
|
11883
12393
|
const code = phase.slice("refused:".length);
|
|
11884
12394
|
title = REFUSED_TITLES[code] ?? "You can't join this meeting";
|
|
11885
|
-
line = failure?.remedy ?? null;
|
|
12395
|
+
line = code === "too_early" || code === "registration_required" || code === "cancelled" ? failure?.remedy ?? null : null;
|
|
11886
12396
|
actionsNode = /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
|
|
11887
12397
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_design_system.Button, { type: "button", variant: "outline", onClick: actions.retry, children: "Try again" }),
|
|
11888
12398
|
home
|
|
@@ -11898,7 +12408,7 @@ function ExitScreen({ onLeave }) {
|
|
|
11898
12408
|
break;
|
|
11899
12409
|
case "disconnected":
|
|
11900
12410
|
title = "You lost your connection to the meeting";
|
|
11901
|
-
line = failure?.remedy ?? "Check your internet connection, then rejoin.";
|
|
12411
|
+
line = connection === "offline" ? "You're offline. You'll rejoin automatically when your internet is back." : connection === "server_unreachable" ? "Can't reach the meeting server. You'll rejoin automatically when it answers." : failure?.remedy ?? "Check your internet connection, then rejoin.";
|
|
11902
12412
|
notice = "disconnected";
|
|
11903
12413
|
actionsNode = /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
|
|
11904
12414
|
rejoin,
|
|
@@ -11932,6 +12442,10 @@ function ExitScreen({ onLeave }) {
|
|
|
11932
12442
|
case "ended":
|
|
11933
12443
|
case "record":
|
|
11934
12444
|
title = reason === "time_limit" ? "The meeting reached its time limit" : "The meeting has ended";
|
|
12445
|
+
if (failure?.reason === "ended" && failure.remedy.length > 0 && failure.message.startsWith("This run")) {
|
|
12446
|
+
title = failure.message.replace(/\.$/, "");
|
|
12447
|
+
line = failure.remedy;
|
|
12448
|
+
}
|
|
11935
12449
|
notice = "meeting-ended";
|
|
11936
12450
|
break;
|
|
11937
12451
|
default:
|
|
@@ -12502,8 +13016,16 @@ function MeetingSkinRootBody({
|
|
|
12502
13016
|
if (now.phase !== "resolving" && now.roomName !== null && now.roomName !== roomName && now.call === null) {
|
|
12503
13017
|
host.store.dispatch({ type: "reset" });
|
|
12504
13018
|
}
|
|
12505
|
-
if (host.store.snapshot().phase
|
|
12506
|
-
|
|
13019
|
+
if (host.store.snapshot().phase !== "resolving") return;
|
|
13020
|
+
if (given?.linkRun?.reason == null && host.engine.resume(roomName)) return;
|
|
13021
|
+
const reason = given?.linkRun?.reason ?? null;
|
|
13022
|
+
if (reason !== null && given?.linkRun !== void 0) {
|
|
13023
|
+
const words = linkRunWords(given.linkRun);
|
|
13024
|
+
host.store.patch({ failure: { message: words.message, remedy: words.remedy, reason } });
|
|
13025
|
+
if (host.store.dispatch({ type: "link_state", reason })) return;
|
|
13026
|
+
}
|
|
13027
|
+
host.store.dispatch({ type: "resolved" });
|
|
13028
|
+
}, [host, roomName, phase, given]);
|
|
12507
13029
|
(0, import_react12.useEffect)(() => {
|
|
12508
13030
|
if (given === void 0) return;
|
|
12509
13031
|
if (host.store.snapshot().meeting !== null) return;
|
|
@@ -12530,6 +13052,31 @@ function MeetingSkinRootBody({
|
|
|
12530
13052
|
}
|
|
12531
13053
|
);
|
|
12532
13054
|
}
|
|
13055
|
+
function linkRunWords(run) {
|
|
13056
|
+
if (run.reason === "cancelled") {
|
|
13057
|
+
return {
|
|
13058
|
+
message: "This occurrence of the meeting was cancelled.",
|
|
13059
|
+
remedy: "The series continues; open the same link at its next time."
|
|
13060
|
+
};
|
|
13061
|
+
}
|
|
13062
|
+
if (run.nextStart === null) {
|
|
13063
|
+
return {
|
|
13064
|
+
message: "This meeting has ended.",
|
|
13065
|
+
remedy: "The link still opens its record: summary, notes and transcript. Nobody can rejoin."
|
|
13066
|
+
};
|
|
13067
|
+
}
|
|
13068
|
+
const when2 = new Date(run.nextStart).toLocaleString(void 0, {
|
|
13069
|
+
weekday: "short",
|
|
13070
|
+
month: "short",
|
|
13071
|
+
day: "numeric",
|
|
13072
|
+
hour: "numeric",
|
|
13073
|
+
minute: "2-digit"
|
|
13074
|
+
});
|
|
13075
|
+
return {
|
|
13076
|
+
message: "This run of the meeting has ended.",
|
|
13077
|
+
remedy: `The next one starts ${when2}. Open the same link then.`
|
|
13078
|
+
};
|
|
13079
|
+
}
|
|
12533
13080
|
|
|
12534
13081
|
// src/react/calls-ui.tsx
|
|
12535
13082
|
var import_react13 = require("react");
|