@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.cjs
CHANGED
|
@@ -36,6 +36,7 @@ __export(core_exports, {
|
|
|
36
36
|
CAPTION_LANGUAGES: () => CAPTION_LANGUAGES,
|
|
37
37
|
CONSENT_NOTICE_ATTRIBUTE: () => CONSENT_NOTICE_ATTRIBUTE,
|
|
38
38
|
DATA_TOPICS: () => DATA_TOPICS,
|
|
39
|
+
DEFAULT_CONNECTION_POLICY: () => DEFAULT_CONNECTION_POLICY,
|
|
39
40
|
DEFAULT_EFFECTS: () => DEFAULT_EFFECTS,
|
|
40
41
|
DEFAULT_PAGE_SIZE: () => DEFAULT_PAGE_SIZE,
|
|
41
42
|
EMPTY_COLLAB: () => EMPTY_COLLAB,
|
|
@@ -50,6 +51,7 @@ __export(core_exports, {
|
|
|
50
51
|
IDLE_TRACK: () => IDLE_TRACK,
|
|
51
52
|
JOIN_REFUSAL_REASONS: () => JOIN_REFUSAL_REASONS,
|
|
52
53
|
LISTEN_ONLY_NOTICE: () => LISTEN_ONLY_NOTICE,
|
|
54
|
+
LIVEKIT_BASE_DELAYS_MS: () => LIVEKIT_BASE_DELAYS_MS,
|
|
53
55
|
LIVE_PHASES: () => LIVE_PHASES,
|
|
54
56
|
MEETING_INVITE_KIND: () => MEETING_INVITE_KIND,
|
|
55
57
|
MEETING_INVITE_VERSION: () => MEETING_INVITE_VERSION,
|
|
@@ -73,6 +75,7 @@ __export(core_exports, {
|
|
|
73
75
|
PARTICIPANT_STANDING_ATTRIBUTE: () => PARTICIPANT_STANDING_ATTRIBUTE,
|
|
74
76
|
REACTION_TTL_MS: () => REACTION_TTL_MS,
|
|
75
77
|
REFUSED_ABSENCE: () => REFUSED_ABSENCE,
|
|
78
|
+
RESUME_WINDOW_MS: () => RESUME_WINDOW_MS,
|
|
76
79
|
ROLE_RANK: () => ROLE_RANK,
|
|
77
80
|
ROOM_EVENTS: () => ROOM_EVENTS,
|
|
78
81
|
ROOM_EVENT_PREFIX: () => ROOM_EVENT_PREFIX,
|
|
@@ -102,6 +105,7 @@ __export(core_exports, {
|
|
|
102
105
|
attendanceTotals: () => attendanceTotals,
|
|
103
106
|
attendeeKey: () => attendeeKey,
|
|
104
107
|
autoAssign: () => autoAssign,
|
|
108
|
+
autoRejoinDelayMs: () => autoRejoinDelayMs,
|
|
105
109
|
availableActivities: () => availableActivities,
|
|
106
110
|
breakoutRemainingMs: () => breakoutRemainingMs,
|
|
107
111
|
callInviteAction: () => callInviteAction,
|
|
@@ -116,9 +120,11 @@ __export(core_exports, {
|
|
|
116
120
|
clockPosition: () => clockPosition,
|
|
117
121
|
collabFromState: () => collabFromState,
|
|
118
122
|
computeStage: () => computeStage,
|
|
123
|
+
connectionPolicyFrom: () => connectionPolicyFrom,
|
|
119
124
|
countLabel: () => countLabel,
|
|
120
125
|
createCallCenter: () => createCallCenter,
|
|
121
126
|
createCallInviteHandler: () => createCallInviteHandler,
|
|
127
|
+
createConnectionSupervisor: () => createConnectionSupervisor,
|
|
122
128
|
createDeviceManager: () => createDeviceManager,
|
|
123
129
|
createInRoomClient: () => createInRoomClient,
|
|
124
130
|
createLiveKitDriver: () => createLiveKitDriver,
|
|
@@ -130,11 +136,15 @@ __export(core_exports, {
|
|
|
130
136
|
createMeetingRecordCache: () => createMeetingRecordCache,
|
|
131
137
|
createMeetingSession: () => createMeetingSession,
|
|
132
138
|
createMemoryDeviceStorage: () => createMemoryDeviceStorage,
|
|
139
|
+
createReachabilityProbe: () => createReachabilityProbe,
|
|
140
|
+
createReconnectPolicy: () => createReconnectPolicy,
|
|
133
141
|
createRecordingController: () => createRecordingController,
|
|
134
142
|
createRoomEngine: () => createRoomEngine,
|
|
135
143
|
createTokenClient: () => createTokenClient,
|
|
136
144
|
createWebDeviceStorage: () => createWebDeviceStorage,
|
|
137
145
|
createWebEffectsStorage: () => createWebEffectsStorage,
|
|
146
|
+
createWebNetworkPort: () => createWebNetworkPort,
|
|
147
|
+
createWebResumeStorage: () => createWebResumeStorage,
|
|
138
148
|
createWebRoomTokenStorage: () => createWebRoomTokenStorage,
|
|
139
149
|
defaultCaptionLanguage: () => defaultCaptionLanguage,
|
|
140
150
|
displayNameFor: () => displayNameFor,
|
|
@@ -147,6 +157,7 @@ __export(core_exports, {
|
|
|
147
157
|
guestRecordAccessSetting: () => guestRecordAccessSetting,
|
|
148
158
|
hasEnded: () => hasEnded,
|
|
149
159
|
hostUserIds: () => hostUserIds,
|
|
160
|
+
httpUrlOf: () => httpUrlOf,
|
|
150
161
|
invalidResponse: () => invalidResponse,
|
|
151
162
|
isGuest: () => isGuest,
|
|
152
163
|
isGuestIdentity: () => isGuestIdentity,
|
|
@@ -160,6 +171,7 @@ __export(core_exports, {
|
|
|
160
171
|
isRealName: () => isRealName,
|
|
161
172
|
isRefusedPhase: () => isRefusedPhase,
|
|
162
173
|
isRoomEventTopic: () => isRoomEventTopic,
|
|
174
|
+
isUnannouncedDeparture: () => isUnannouncedDeparture,
|
|
163
175
|
isWaitingPhase: () => isWaitingPhase,
|
|
164
176
|
learnName: () => learnName,
|
|
165
177
|
learnNames: () => learnNames,
|
|
@@ -181,6 +193,7 @@ __export(core_exports, {
|
|
|
181
193
|
meetingTopic: () => meetingTopic,
|
|
182
194
|
meetingWhen: () => meetingWhen,
|
|
183
195
|
mergeCaption: () => mergeCaption,
|
|
196
|
+
mergeGhosts: () => mergeGhosts,
|
|
184
197
|
mergeRecord: () => mergeRecord,
|
|
185
198
|
microphoneNoticeCode: () => microphoneNoticeCode,
|
|
186
199
|
moveToRoom: () => moveToRoom,
|
|
@@ -456,6 +469,352 @@ function invalidResponse(operation, detail) {
|
|
|
456
469
|
);
|
|
457
470
|
}
|
|
458
471
|
|
|
472
|
+
// src/core/connection-supervisor.ts
|
|
473
|
+
var DEFAULT_CONNECTION_POLICY = {
|
|
474
|
+
bannerDelayMs: 2e3,
|
|
475
|
+
autoRejoin: true,
|
|
476
|
+
autoRejoinMaxAttempts: 5,
|
|
477
|
+
reconnectGraceSeconds: 30
|
|
478
|
+
};
|
|
479
|
+
function clampInt(value, min, max, fallback) {
|
|
480
|
+
if (typeof value !== "number" || !Number.isFinite(value)) return fallback;
|
|
481
|
+
return Math.min(max, Math.max(min, Math.round(value)));
|
|
482
|
+
}
|
|
483
|
+
function connectionPolicyFrom(raw) {
|
|
484
|
+
if (typeof raw !== "object" || raw === null) return DEFAULT_CONNECTION_POLICY;
|
|
485
|
+
const row = raw;
|
|
486
|
+
const d = DEFAULT_CONNECTION_POLICY;
|
|
487
|
+
return {
|
|
488
|
+
bannerDelayMs: clampInt(row["reconnect_banner_delay_ms"], 0, 1e4, d.bannerDelayMs),
|
|
489
|
+
autoRejoin: typeof row["auto_rejoin"] === "boolean" ? row["auto_rejoin"] : d.autoRejoin,
|
|
490
|
+
autoRejoinMaxAttempts: clampInt(row["auto_rejoin_max_attempts"], 0, 20, d.autoRejoinMaxAttempts),
|
|
491
|
+
reconnectGraceSeconds: clampInt(row["reconnect_grace_seconds"], 0, 600, d.reconnectGraceSeconds)
|
|
492
|
+
};
|
|
493
|
+
}
|
|
494
|
+
var LIVEKIT_BASE_DELAYS_MS = [0, 300, 1200, 2700, 4800, 7e3];
|
|
495
|
+
function createReconnectPolicy(options = {}) {
|
|
496
|
+
const delays = options.delaysMs !== void 0 && options.delaysMs.length > 0 ? options.delaysMs : LIVEKIT_BASE_DELAYS_MS;
|
|
497
|
+
const giveUpMs = options.giveUpMs ?? 45e3;
|
|
498
|
+
const maxAttempts = options.maxAttempts ?? 12;
|
|
499
|
+
const random = options.random ?? Math.random;
|
|
500
|
+
return {
|
|
501
|
+
nextRetryDelayInMs({ retryCount, elapsedMs }) {
|
|
502
|
+
if (retryCount >= maxAttempts || elapsedMs >= giveUpMs) return null;
|
|
503
|
+
const base = delays[Math.min(retryCount, delays.length - 1)] ?? 0;
|
|
504
|
+
const spread = Math.min(2e3, Math.max(500, base / 2));
|
|
505
|
+
return Math.round(base + random() * spread);
|
|
506
|
+
}
|
|
507
|
+
};
|
|
508
|
+
}
|
|
509
|
+
function createWebNetworkPort() {
|
|
510
|
+
const scope = globalThis;
|
|
511
|
+
return {
|
|
512
|
+
online: () => scope.navigator?.onLine !== false,
|
|
513
|
+
subscribe(listener) {
|
|
514
|
+
if (typeof scope.addEventListener !== "function") return () => void 0;
|
|
515
|
+
const on = () => listener(true);
|
|
516
|
+
const off = () => listener(false);
|
|
517
|
+
scope.addEventListener("online", on);
|
|
518
|
+
scope.addEventListener("offline", off);
|
|
519
|
+
return () => {
|
|
520
|
+
scope.removeEventListener?.("online", on);
|
|
521
|
+
scope.removeEventListener?.("offline", off);
|
|
522
|
+
};
|
|
523
|
+
}
|
|
524
|
+
};
|
|
525
|
+
}
|
|
526
|
+
function createReachabilityProbe(urls, timeoutMs = 4e3) {
|
|
527
|
+
return async () => {
|
|
528
|
+
const targets = urls().filter((url) => url.length > 0);
|
|
529
|
+
if (targets.length === 0 || typeof fetch !== "function") return true;
|
|
530
|
+
const one = async (url) => {
|
|
531
|
+
const controller = typeof AbortController === "function" ? new AbortController() : null;
|
|
532
|
+
const timer = setTimeout(() => controller?.abort(), timeoutMs);
|
|
533
|
+
try {
|
|
534
|
+
await fetch(url, {
|
|
535
|
+
method: "GET",
|
|
536
|
+
mode: "no-cors",
|
|
537
|
+
cache: "no-store",
|
|
538
|
+
credentials: "omit",
|
|
539
|
+
...controller !== null ? { signal: controller.signal } : {}
|
|
540
|
+
});
|
|
541
|
+
return true;
|
|
542
|
+
} catch {
|
|
543
|
+
return false;
|
|
544
|
+
} finally {
|
|
545
|
+
clearTimeout(timer);
|
|
546
|
+
}
|
|
547
|
+
};
|
|
548
|
+
const answers = await Promise.all(targets.map(one));
|
|
549
|
+
return answers.every(Boolean);
|
|
550
|
+
};
|
|
551
|
+
}
|
|
552
|
+
function httpUrlOf(serverUrl) {
|
|
553
|
+
return serverUrl.replace(/^ws(s?):\/\//i, "http$1://");
|
|
554
|
+
}
|
|
555
|
+
var RESUME_WINDOW_MS = 15 * 6e4;
|
|
556
|
+
var RESUME_KEY = "mx-meet:resume:";
|
|
557
|
+
function createWebResumeStorage() {
|
|
558
|
+
const storage = globalThis.sessionStorage;
|
|
559
|
+
if (storage === void 0) return void 0;
|
|
560
|
+
return {
|
|
561
|
+
load(roomName) {
|
|
562
|
+
try {
|
|
563
|
+
const raw = storage.getItem(RESUME_KEY + roomName);
|
|
564
|
+
if (raw === null) return null;
|
|
565
|
+
const parsed = JSON.parse(raw);
|
|
566
|
+
return typeof parsed?.at === "number" && typeof parsed.request?.roomName === "string" ? parsed : null;
|
|
567
|
+
} catch {
|
|
568
|
+
return null;
|
|
569
|
+
}
|
|
570
|
+
},
|
|
571
|
+
save(roomName, marker) {
|
|
572
|
+
try {
|
|
573
|
+
storage.setItem(RESUME_KEY + roomName, JSON.stringify(marker));
|
|
574
|
+
} catch {
|
|
575
|
+
}
|
|
576
|
+
},
|
|
577
|
+
clear(roomName) {
|
|
578
|
+
try {
|
|
579
|
+
storage.removeItem(RESUME_KEY + roomName);
|
|
580
|
+
} catch {
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
};
|
|
584
|
+
}
|
|
585
|
+
var CLEARS_RESUME = (phase) => phase === "left" || phase === "removed" || phase === "superseded" || phase === "inactive" || phase === "ended" || phase === "record" || phase === "prejoin" || phase.startsWith("refused:") || phase.startsWith("waiting:");
|
|
586
|
+
function autoRejoinDelayMs(attempt, random = Math.random) {
|
|
587
|
+
const base = Math.min(15e3, 1e3 * 2 ** Math.max(0, attempt));
|
|
588
|
+
return Math.round(base * (0.5 + random()));
|
|
589
|
+
}
|
|
590
|
+
function createConnectionSupervisor(options) {
|
|
591
|
+
const { store, network } = options;
|
|
592
|
+
const now = options.now ?? (() => Date.now());
|
|
593
|
+
const random = options.random ?? Math.random;
|
|
594
|
+
const probeEveryMs = options.probeEveryMs ?? 3e3;
|
|
595
|
+
let driverRegion = null;
|
|
596
|
+
let probeTimer = null;
|
|
597
|
+
let probing = false;
|
|
598
|
+
let bannerTimer = null;
|
|
599
|
+
let rejoinTimer = null;
|
|
600
|
+
let rejoinAttempts = 0;
|
|
601
|
+
let lastPhase = store.snapshot().phase;
|
|
602
|
+
let lastSavedAt = 0;
|
|
603
|
+
let disposed = false;
|
|
604
|
+
const inCall = () => store.snapshot().phase === "in_call";
|
|
605
|
+
const dropped = () => store.snapshot().phase === "disconnected";
|
|
606
|
+
function setRegion(next) {
|
|
607
|
+
if (store.snapshot().connection !== next) store.patch({ connection: next });
|
|
608
|
+
}
|
|
609
|
+
function regionFor(reachable) {
|
|
610
|
+
if (!network.online()) return "offline";
|
|
611
|
+
if (reachable === false) return "server_unreachable";
|
|
612
|
+
if (dropped()) return reachable === true ? "stable" : store.snapshot().connection;
|
|
613
|
+
return driverRegion ?? "stable";
|
|
614
|
+
}
|
|
615
|
+
function needsWatch() {
|
|
616
|
+
const snap = store.snapshot();
|
|
617
|
+
if (dropped()) return true;
|
|
618
|
+
return inCall() && (driverRegion !== null || snap.connection !== "stable");
|
|
619
|
+
}
|
|
620
|
+
function stopProbe() {
|
|
621
|
+
if (probeTimer !== null) clearTimeout(probeTimer);
|
|
622
|
+
probeTimer = null;
|
|
623
|
+
}
|
|
624
|
+
function scheduleProbe(delayMs) {
|
|
625
|
+
if (disposed || probeTimer !== null) return;
|
|
626
|
+
probeTimer = setTimeout(() => {
|
|
627
|
+
probeTimer = null;
|
|
628
|
+
void runProbe();
|
|
629
|
+
}, delayMs);
|
|
630
|
+
}
|
|
631
|
+
async function runProbe() {
|
|
632
|
+
if (disposed || probing) return;
|
|
633
|
+
if (!needsWatch()) return;
|
|
634
|
+
if (!network.online()) {
|
|
635
|
+
setRegion("offline");
|
|
636
|
+
scheduleProbe(probeEveryMs);
|
|
637
|
+
return;
|
|
638
|
+
}
|
|
639
|
+
probing = true;
|
|
640
|
+
let reachable = false;
|
|
641
|
+
try {
|
|
642
|
+
reachable = await options.probe();
|
|
643
|
+
} catch {
|
|
644
|
+
reachable = false;
|
|
645
|
+
} finally {
|
|
646
|
+
probing = false;
|
|
647
|
+
}
|
|
648
|
+
if (disposed) return;
|
|
649
|
+
if (inCall() || dropped()) setRegion(regionFor(reachable));
|
|
650
|
+
if (dropped() && reachable) maybeRejoin();
|
|
651
|
+
if (needsWatch()) scheduleProbe(Math.round(probeEveryMs * (0.75 + random() * 0.5)));
|
|
652
|
+
}
|
|
653
|
+
function syncBanner() {
|
|
654
|
+
const snap = store.snapshot();
|
|
655
|
+
const unstable = snap.phase === "in_call" && snap.connection !== "stable";
|
|
656
|
+
if (!unstable) {
|
|
657
|
+
if (bannerTimer !== null) clearTimeout(bannerTimer);
|
|
658
|
+
bannerTimer = null;
|
|
659
|
+
if (snap.connectionBanner) store.patch({ connectionBanner: false });
|
|
660
|
+
return;
|
|
661
|
+
}
|
|
662
|
+
if (snap.connectionBanner || bannerTimer !== null) return;
|
|
663
|
+
const delay = options.policy().bannerDelayMs;
|
|
664
|
+
if (delay <= 0) {
|
|
665
|
+
store.patch({ connectionBanner: true });
|
|
666
|
+
return;
|
|
667
|
+
}
|
|
668
|
+
bannerTimer = setTimeout(() => {
|
|
669
|
+
bannerTimer = null;
|
|
670
|
+
const latest = store.snapshot();
|
|
671
|
+
if (latest.phase === "in_call" && latest.connection !== "stable" && !latest.connectionBanner) {
|
|
672
|
+
store.patch({ connectionBanner: true });
|
|
673
|
+
}
|
|
674
|
+
}, delay);
|
|
675
|
+
}
|
|
676
|
+
function stopRejoin() {
|
|
677
|
+
if (rejoinTimer !== null) clearTimeout(rejoinTimer);
|
|
678
|
+
rejoinTimer = null;
|
|
679
|
+
}
|
|
680
|
+
function maybeRejoin() {
|
|
681
|
+
if (disposed || rejoinTimer !== null || !dropped()) return;
|
|
682
|
+
const policy = options.policy();
|
|
683
|
+
if (!policy.autoRejoin) return;
|
|
684
|
+
if (rejoinAttempts >= policy.autoRejoinMaxAttempts) return;
|
|
685
|
+
if (!network.online()) return;
|
|
686
|
+
const attempt = rejoinAttempts;
|
|
687
|
+
rejoinTimer = setTimeout(() => {
|
|
688
|
+
rejoinTimer = null;
|
|
689
|
+
if (disposed || !dropped() || !network.online()) return;
|
|
690
|
+
rejoinAttempts = attempt + 1;
|
|
691
|
+
options.report?.("info", `meet: rejoining by itself (attempt ${rejoinAttempts} of ${policy.autoRejoinMaxAttempts})`);
|
|
692
|
+
void options.autoRejoin().catch(() => void 0);
|
|
693
|
+
}, autoRejoinDelayMs(attempt, random));
|
|
694
|
+
}
|
|
695
|
+
function saveMarker(force) {
|
|
696
|
+
const resume = options.resume;
|
|
697
|
+
const snap = store.snapshot();
|
|
698
|
+
if (resume === void 0 || snap.roomName === null) return;
|
|
699
|
+
const at = now();
|
|
700
|
+
if (!force && at - lastSavedAt < 1e4) return;
|
|
701
|
+
const request = options.currentRequest();
|
|
702
|
+
if (request === null) return;
|
|
703
|
+
lastSavedAt = at;
|
|
704
|
+
resume.save(snap.roomName, { request, at });
|
|
705
|
+
}
|
|
706
|
+
const unsubscribeStore = store.subscribe(() => {
|
|
707
|
+
if (disposed) return;
|
|
708
|
+
const snap = store.snapshot();
|
|
709
|
+
const phase = snap.phase;
|
|
710
|
+
const changed = phase !== lastPhase;
|
|
711
|
+
lastPhase = phase;
|
|
712
|
+
if (changed) {
|
|
713
|
+
if (phase === "in_call") {
|
|
714
|
+
rejoinAttempts = 0;
|
|
715
|
+
stopRejoin();
|
|
716
|
+
saveMarker(true);
|
|
717
|
+
} else if (phase === "disconnected") {
|
|
718
|
+
driverRegion = null;
|
|
719
|
+
saveMarker(true);
|
|
720
|
+
stopProbe();
|
|
721
|
+
scheduleProbe(0);
|
|
722
|
+
} else {
|
|
723
|
+
stopRejoin();
|
|
724
|
+
if (phase !== "joining") driverRegion = null;
|
|
725
|
+
if (CLEARS_RESUME(phase) && snap.roomName !== null) options.resume?.clear(snap.roomName);
|
|
726
|
+
}
|
|
727
|
+
} else if (phase === "in_call" || phase === "disconnected") {
|
|
728
|
+
saveMarker(false);
|
|
729
|
+
}
|
|
730
|
+
syncBanner();
|
|
731
|
+
});
|
|
732
|
+
const unsubscribeNetwork = network.subscribe((online) => {
|
|
733
|
+
if (disposed) return;
|
|
734
|
+
if (!online) {
|
|
735
|
+
if (inCall() || dropped()) setRegion("offline");
|
|
736
|
+
stopRejoin();
|
|
737
|
+
stopProbe();
|
|
738
|
+
if (needsWatch()) scheduleProbe(probeEveryMs);
|
|
739
|
+
return;
|
|
740
|
+
}
|
|
741
|
+
stopProbe();
|
|
742
|
+
if (needsWatch()) scheduleProbe(0);
|
|
743
|
+
});
|
|
744
|
+
return {
|
|
745
|
+
driverState(state) {
|
|
746
|
+
if (disposed) return;
|
|
747
|
+
if (state === "connected") {
|
|
748
|
+
driverRegion = null;
|
|
749
|
+
if (inCall() || store.snapshot().phase === "joining") setRegion(network.online() ? "stable" : "offline");
|
|
750
|
+
if (!needsWatch()) stopProbe();
|
|
751
|
+
return;
|
|
752
|
+
}
|
|
753
|
+
if (state === "reconnecting" || state === "signal_reconnecting") {
|
|
754
|
+
driverRegion = state;
|
|
755
|
+
if (inCall()) {
|
|
756
|
+
setRegion(network.online() ? state : "offline");
|
|
757
|
+
stopProbe();
|
|
758
|
+
scheduleProbe(0);
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
},
|
|
762
|
+
exitConnection() {
|
|
763
|
+
return network.online() ? "server_unreachable" : "offline";
|
|
764
|
+
},
|
|
765
|
+
resumable(roomName) {
|
|
766
|
+
const resume = options.resume;
|
|
767
|
+
if (resume === void 0) return null;
|
|
768
|
+
const marker = resume.load(roomName);
|
|
769
|
+
if (marker === null) return null;
|
|
770
|
+
if (now() - marker.at > RESUME_WINDOW_MS) {
|
|
771
|
+
resume.clear(roomName);
|
|
772
|
+
return null;
|
|
773
|
+
}
|
|
774
|
+
return marker;
|
|
775
|
+
},
|
|
776
|
+
dispose() {
|
|
777
|
+
disposed = true;
|
|
778
|
+
stopProbe();
|
|
779
|
+
stopRejoin();
|
|
780
|
+
if (bannerTimer !== null) clearTimeout(bannerTimer);
|
|
781
|
+
bannerTimer = null;
|
|
782
|
+
unsubscribeStore();
|
|
783
|
+
unsubscribeNetwork();
|
|
784
|
+
}
|
|
785
|
+
};
|
|
786
|
+
}
|
|
787
|
+
var DELIBERATE_DEPARTURES = /* @__PURE__ */ new Set([
|
|
788
|
+
"CLIENT_INITIATED",
|
|
789
|
+
"PARTICIPANT_REMOVED",
|
|
790
|
+
"ROOM_DELETED",
|
|
791
|
+
"ROOM_CLOSED",
|
|
792
|
+
"DUPLICATE_IDENTITY",
|
|
793
|
+
"USER_REJECTED",
|
|
794
|
+
"USER_UNAVAILABLE",
|
|
795
|
+
"SIP_TRUNK_FAILURE"
|
|
796
|
+
]);
|
|
797
|
+
function isUnannouncedDeparture(reason) {
|
|
798
|
+
return reason === null || !DELIBERATE_DEPARTURES.has(reason);
|
|
799
|
+
}
|
|
800
|
+
function mergeGhosts(live, ghosts, at) {
|
|
801
|
+
const present = new Set(live.map((p) => p.identity));
|
|
802
|
+
const kept = /* @__PURE__ */ new Map();
|
|
803
|
+
const extra = [];
|
|
804
|
+
for (const [identity, ghost] of ghosts) {
|
|
805
|
+
if (present.has(identity) || ghost.until <= at) continue;
|
|
806
|
+
kept.set(identity, ghost);
|
|
807
|
+
extra.push({
|
|
808
|
+
...ghost.participant,
|
|
809
|
+
micEnabled: false,
|
|
810
|
+
cameraEnabled: false,
|
|
811
|
+
screenSharing: false,
|
|
812
|
+
reconnecting: true
|
|
813
|
+
});
|
|
814
|
+
}
|
|
815
|
+
return { roster: [...live, ...extra], kept };
|
|
816
|
+
}
|
|
817
|
+
|
|
459
818
|
// src/core/tokens.ts
|
|
460
819
|
function createWebRoomTokenStorage(onDiagnostic) {
|
|
461
820
|
const store = globalThis.localStorage;
|
|
@@ -641,7 +1000,8 @@ function parseRoomToken(body, roomName) {
|
|
|
641
1000
|
admissionState: readString(payload, "admission_state") ?? void 0,
|
|
642
1001
|
pollSeconds: typeof payload["poll_seconds"] === "number" ? payload["poll_seconds"] : void 0,
|
|
643
1002
|
breakoutRoomId: readString(payload, "breakout_room_id") ?? null,
|
|
644
|
-
observersVisibleTo: readString(payload, "observers_visible_to") === "hosts" ? "hosts" : "everyone"
|
|
1003
|
+
observersVisibleTo: readString(payload, "observers_visible_to") === "hosts" ? "hosts" : "everyone",
|
|
1004
|
+
connectionPolicy: connectionPolicyFrom(payload["connection_policy"])
|
|
645
1005
|
};
|
|
646
1006
|
}
|
|
647
1007
|
function createTokenClient(options) {
|
|
@@ -1371,6 +1731,10 @@ function nextPhase(from, event) {
|
|
|
1371
1731
|
return from === "resolving" && event.reason !== null && isJoinRefusalReason(event.reason) ? refusalPhase(event.reason) : null;
|
|
1372
1732
|
case "join":
|
|
1373
1733
|
return JOINABLE_FROM.has(from) ? "joining" : null;
|
|
1734
|
+
case "auto_rejoin":
|
|
1735
|
+
return from === "disconnected" ? "joining" : null;
|
|
1736
|
+
case "resumed":
|
|
1737
|
+
return from === "resolving" ? "joining" : null;
|
|
1374
1738
|
case "back_to_prejoin":
|
|
1375
1739
|
return from === "left" || from === "disconnected" ? "prejoin" : null;
|
|
1376
1740
|
case "retry":
|
|
@@ -3284,6 +3648,7 @@ var EMPTY = {
|
|
|
3284
3648
|
phaseNotice: null,
|
|
3285
3649
|
roomName: null,
|
|
3286
3650
|
connection: "stable",
|
|
3651
|
+
connectionBanner: false,
|
|
3287
3652
|
share: NO_SHARE,
|
|
3288
3653
|
call: null,
|
|
3289
3654
|
waiting: null,
|
|
@@ -4238,8 +4603,11 @@ function mapConnectionState(state) {
|
|
|
4238
4603
|
case "connecting":
|
|
4239
4604
|
return "connecting";
|
|
4240
4605
|
case "reconnecting":
|
|
4241
|
-
case "signalReconnecting":
|
|
4242
4606
|
return "reconnecting";
|
|
4607
|
+
// Only the signal socket is down (S6b): its own region value, so the supervisor can tell a
|
|
4608
|
+
// signal blip from a full media reconnect.
|
|
4609
|
+
case "signalReconnecting":
|
|
4610
|
+
return "signal_reconnecting";
|
|
4243
4611
|
default:
|
|
4244
4612
|
return "disconnected";
|
|
4245
4613
|
}
|
|
@@ -4320,8 +4688,12 @@ function createLiveKitDriver(handlers, options = {}) {
|
|
|
4320
4688
|
// R5: large meetings degrade gracefully instead of melting a laptop.
|
|
4321
4689
|
adaptiveStream: true,
|
|
4322
4690
|
dynacast: true,
|
|
4691
|
+
// S6b: OUR retry schedule — jittered from attempt 0 (the SDK's default has no jitter on its
|
|
4692
|
+
// first two attempts), give-up limits in one place (`connection-supervisor.ts`).
|
|
4693
|
+
reconnectPolicy: createReconnectPolicy(),
|
|
4323
4694
|
...options.roomOptions
|
|
4324
4695
|
});
|
|
4696
|
+
const reasonName = (reason) => reason === void 0 || reason === null ? null : module2.DisconnectReason?.[reason] ?? String(reason);
|
|
4325
4697
|
const roster = () => {
|
|
4326
4698
|
const local = snapshotParticipant(created.localParticipant, true);
|
|
4327
4699
|
const remotes = [...created.remoteParticipants.values()].map(
|
|
@@ -4339,7 +4711,10 @@ function createLiveKitDriver(handlers, options = {}) {
|
|
|
4339
4711
|
if (track.kind !== Track.Kind.Audio) return;
|
|
4340
4712
|
for (const element of track.detach()) element.remove();
|
|
4341
4713
|
});
|
|
4342
|
-
created.on(RoomEvent.ParticipantConnected, roster).on(RoomEvent.ParticipantDisconnected,
|
|
4714
|
+
created.on(RoomEvent.ParticipantConnected, roster).on(RoomEvent.ParticipantDisconnected, (participant, reason) => {
|
|
4715
|
+
handlers.participantLeft?.(participant.identity, reasonName(reason));
|
|
4716
|
+
roster();
|
|
4717
|
+
}).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) => {
|
|
4343
4718
|
handlers.roomMetadata?.(metadata ?? "");
|
|
4344
4719
|
}).on(RoomEvent.ActiveSpeakersChanged, (speakers) => {
|
|
4345
4720
|
handlers.activeSpeakers(speakers.map((speaker) => speaker.identity));
|
|
@@ -4356,8 +4731,7 @@ function createLiveKitDriver(handlers, options = {}) {
|
|
|
4356
4731
|
}).on(RoomEvent.Disconnected, (reason) => {
|
|
4357
4732
|
audioContainer?.remove();
|
|
4358
4733
|
audioContainer = null;
|
|
4359
|
-
|
|
4360
|
-
handlers.disconnected(named);
|
|
4734
|
+
handlers.disconnected(reasonName(reason));
|
|
4361
4735
|
});
|
|
4362
4736
|
try {
|
|
4363
4737
|
created.registerTextStreamHandler(TRANSCRIPTION_TOPIC, (reader, participantInfo) => {
|
|
@@ -5730,6 +6104,18 @@ function createRoomEngine(options) {
|
|
|
5730
6104
|
let disposed = false;
|
|
5731
6105
|
let moving = false;
|
|
5732
6106
|
const confirmingEnds = /* @__PURE__ */ new WeakSet();
|
|
6107
|
+
let lastServerUrl = null;
|
|
6108
|
+
let connectionPolicy = DEFAULT_CONNECTION_POLICY;
|
|
6109
|
+
let ghosts = /* @__PURE__ */ new Map();
|
|
6110
|
+
let ghostTimer = null;
|
|
6111
|
+
let liveRoster = [];
|
|
6112
|
+
const departures = /* @__PURE__ */ new Map();
|
|
6113
|
+
function requestAsNow() {
|
|
6114
|
+
if (lastRequest === null) return null;
|
|
6115
|
+
const media = store.snapshot().media;
|
|
6116
|
+
if (store.snapshot().phase !== "in_call") return lastRequest;
|
|
6117
|
+
return { ...lastRequest, mode: media.camera.enabled ? "video" : "audio", microphone: media.microphone.enabled };
|
|
6118
|
+
}
|
|
5733
6119
|
async function dropRoom() {
|
|
5734
6120
|
const target = driver;
|
|
5735
6121
|
driver = null;
|
|
@@ -5740,6 +6126,7 @@ function createRoomEngine(options) {
|
|
|
5740
6126
|
store.setLocalTrack("microphone", IDLE_TRACK);
|
|
5741
6127
|
store.setLocalTrack("camera", IDLE_TRACK);
|
|
5742
6128
|
store.patch({ share: NO_SHARE, connection: "stable" });
|
|
6129
|
+
clearGhosts();
|
|
5743
6130
|
await target?.disconnect();
|
|
5744
6131
|
}
|
|
5745
6132
|
async function leaveRoom() {
|
|
@@ -5807,7 +6194,7 @@ function createRoomEngine(options) {
|
|
|
5807
6194
|
participants(list) {
|
|
5808
6195
|
const joinedAt = new Date(now()).toISOString();
|
|
5809
6196
|
const before = new Set(store.snapshot().participants.map((p) => p.identity));
|
|
5810
|
-
|
|
6197
|
+
projectRoster(list, joinedAt);
|
|
5811
6198
|
const snapshot = store.snapshot();
|
|
5812
6199
|
const newcomer = snapshot.participants.some(
|
|
5813
6200
|
(participant) => !participant.isLocal && !before.has(participant.identity)
|
|
@@ -5819,6 +6206,9 @@ function createRoomEngine(options) {
|
|
|
5819
6206
|
);
|
|
5820
6207
|
}
|
|
5821
6208
|
},
|
|
6209
|
+
participantLeft(who, reason) {
|
|
6210
|
+
departures.set(who, reason);
|
|
6211
|
+
},
|
|
5822
6212
|
roomMetadata(metadata) {
|
|
5823
6213
|
let spotlight = null;
|
|
5824
6214
|
if (metadata.length > 0) {
|
|
@@ -5845,12 +6235,8 @@ function createRoomEngine(options) {
|
|
|
5845
6235
|
store.setQuality(asParticipantIdentity(who), value);
|
|
5846
6236
|
},
|
|
5847
6237
|
connectionState(state) {
|
|
5848
|
-
|
|
5849
|
-
|
|
5850
|
-
store.dispatch({ type: "connected" });
|
|
5851
|
-
} else if (state === "reconnecting") {
|
|
5852
|
-
store.patch({ connection: "reconnecting" });
|
|
5853
|
-
}
|
|
6238
|
+
supervisor.driverState(state);
|
|
6239
|
+
if (state === "connected" && store.snapshot().phase === "joining") store.dispatch({ type: "connected" });
|
|
5854
6240
|
},
|
|
5855
6241
|
transcription(event) {
|
|
5856
6242
|
const snapshot = store.snapshot();
|
|
@@ -5983,6 +6369,7 @@ function createRoomEngine(options) {
|
|
|
5983
6369
|
};
|
|
5984
6370
|
async function resolveExitAndApply(hint) {
|
|
5985
6371
|
const connection = current?.identity ?? store.snapshot().localIdentity ?? null;
|
|
6372
|
+
lastRequest = requestAsNow();
|
|
5986
6373
|
await dropRoom();
|
|
5987
6374
|
let server;
|
|
5988
6375
|
if (options.readExit === void 0 || connection === null || store.snapshot().meeting === null) {
|
|
@@ -5996,7 +6383,7 @@ function createRoomEngine(options) {
|
|
|
5996
6383
|
}
|
|
5997
6384
|
if (disposed) return;
|
|
5998
6385
|
const resolved = resolveExit({ server, hint, frozen: options.wasFrozen?.() ?? false });
|
|
5999
|
-
store.patch({ connection: resolved.connection });
|
|
6386
|
+
store.patch({ connection: server === null ? supervisor.exitConnection() : resolved.connection });
|
|
6000
6387
|
if (resolved.to === "waiting:knocking") {
|
|
6001
6388
|
store.patch({
|
|
6002
6389
|
waiting: {
|
|
@@ -6028,6 +6415,8 @@ function createRoomEngine(options) {
|
|
|
6028
6415
|
...request.observe === true ? { observe: true } : {}
|
|
6029
6416
|
});
|
|
6030
6417
|
current = token;
|
|
6418
|
+
lastServerUrl = token.serverUrl;
|
|
6419
|
+
connectionPolicy = token.connectionPolicy ?? DEFAULT_CONNECTION_POLICY;
|
|
6031
6420
|
store.patch({
|
|
6032
6421
|
localIdentity: token.identity,
|
|
6033
6422
|
breakoutRoomId: token.breakoutRoomId ?? null,
|
|
@@ -6132,7 +6521,59 @@ function createRoomEngine(options) {
|
|
|
6132
6521
|
throw normalized;
|
|
6133
6522
|
}
|
|
6134
6523
|
}
|
|
6135
|
-
|
|
6524
|
+
function projectRoster(list, joinedAt) {
|
|
6525
|
+
const at = now();
|
|
6526
|
+
const previous = new Map(liveRoster.map((p) => [p.identity, p]));
|
|
6527
|
+
const present = new Set(list.map((p) => p.identity));
|
|
6528
|
+
const graceMs = connectionPolicy.reconnectGraceSeconds * 1e3;
|
|
6529
|
+
for (const [who, reason] of departures) {
|
|
6530
|
+
const last = previous.get(who);
|
|
6531
|
+
if (last !== void 0 && !last.isLocal && !present.has(who) && graceMs > 0 && isUnannouncedDeparture(reason)) {
|
|
6532
|
+
ghosts.set(who, { participant: last, until: at + graceMs });
|
|
6533
|
+
}
|
|
6534
|
+
}
|
|
6535
|
+
departures.clear();
|
|
6536
|
+
liveRoster = list;
|
|
6537
|
+
const merged = mergeGhosts(list, ghosts, at);
|
|
6538
|
+
ghosts = merged.kept;
|
|
6539
|
+
store.setParticipants(
|
|
6540
|
+
merged.roster.map((entry) => {
|
|
6541
|
+
const projected = projectParticipant(entry, joinedAt);
|
|
6542
|
+
return entry.reconnecting === true ? { ...projected, reconnecting: true, quality: "lost" } : projected;
|
|
6543
|
+
})
|
|
6544
|
+
);
|
|
6545
|
+
scheduleGhostExpiry();
|
|
6546
|
+
}
|
|
6547
|
+
function scheduleGhostExpiry() {
|
|
6548
|
+
if (ghostTimer !== null) clearTimeout(ghostTimer);
|
|
6549
|
+
ghostTimer = null;
|
|
6550
|
+
if (ghosts.size === 0 || disposed) return;
|
|
6551
|
+
const next = Math.min(...[...ghosts.values()].map((g) => g.until));
|
|
6552
|
+
ghostTimer = setTimeout(() => {
|
|
6553
|
+
ghostTimer = null;
|
|
6554
|
+
if (disposed || driver === null) return;
|
|
6555
|
+
projectRoster(liveRoster, new Date(now()).toISOString());
|
|
6556
|
+
}, Math.max(0, next - now()) + 50);
|
|
6557
|
+
}
|
|
6558
|
+
function clearGhosts() {
|
|
6559
|
+
ghosts = /* @__PURE__ */ new Map();
|
|
6560
|
+
departures.clear();
|
|
6561
|
+
liveRoster = [];
|
|
6562
|
+
if (ghostTimer !== null) clearTimeout(ghostTimer);
|
|
6563
|
+
ghostTimer = null;
|
|
6564
|
+
}
|
|
6565
|
+
const supervisor = createConnectionSupervisor({
|
|
6566
|
+
store,
|
|
6567
|
+
network: options.connection?.network ?? createWebNetworkPort(),
|
|
6568
|
+
probe: () => options.connection?.probe?.(current?.serverUrl ?? lastServerUrl) ?? Promise.resolve(true),
|
|
6569
|
+
policy: () => connectionPolicy,
|
|
6570
|
+
autoRejoin: () => engine.autoRejoin(),
|
|
6571
|
+
currentRequest: requestAsNow,
|
|
6572
|
+
resume: options.connection?.resume,
|
|
6573
|
+
now,
|
|
6574
|
+
report: (level, message) => report(level, message)
|
|
6575
|
+
});
|
|
6576
|
+
const engine = {
|
|
6136
6577
|
join(request) {
|
|
6137
6578
|
if (joining !== null) return joining;
|
|
6138
6579
|
if (store.snapshot().phase !== "joining" && !store.dispatch({ type: "join", click: true })) {
|
|
@@ -6177,6 +6618,35 @@ function createRoomEngine(options) {
|
|
|
6177
6618
|
});
|
|
6178
6619
|
return joining;
|
|
6179
6620
|
},
|
|
6621
|
+
async autoRejoin() {
|
|
6622
|
+
const request = lastRequest;
|
|
6623
|
+
if (request === null || joining !== null) return joining ?? void 0;
|
|
6624
|
+
if (!store.dispatch({ type: "auto_rejoin" })) return;
|
|
6625
|
+
tokens.invalidate(request.roomName);
|
|
6626
|
+
current = null;
|
|
6627
|
+
joining = connect(request).catch((error) => {
|
|
6628
|
+
throw failJoin(error, "meet.autoRejoin");
|
|
6629
|
+
}).finally(() => {
|
|
6630
|
+
joining = null;
|
|
6631
|
+
});
|
|
6632
|
+
return joining;
|
|
6633
|
+
},
|
|
6634
|
+
resume(roomName) {
|
|
6635
|
+
const marker = supervisor.resumable(roomName);
|
|
6636
|
+
if (marker === null || joining !== null) return false;
|
|
6637
|
+
if (!store.dispatch({ type: "resumed" })) return false;
|
|
6638
|
+
lastRequest = { ...marker.request, roomName };
|
|
6639
|
+
report("info", "meet: this tab was in the call before the page reloaded \u2014 rejoining");
|
|
6640
|
+
tokens.invalidate(roomName);
|
|
6641
|
+
current = null;
|
|
6642
|
+
joining = connect(lastRequest).catch((error) => {
|
|
6643
|
+
throw failJoin(error, "meet.resume");
|
|
6644
|
+
}).finally(() => {
|
|
6645
|
+
joining = null;
|
|
6646
|
+
});
|
|
6647
|
+
joining.catch(() => void 0);
|
|
6648
|
+
return true;
|
|
6649
|
+
},
|
|
6180
6650
|
async moveToBreakout(roomId) {
|
|
6181
6651
|
const request = lastRequest;
|
|
6182
6652
|
if (request === null || driver === null) {
|
|
@@ -6344,6 +6814,8 @@ function createRoomEngine(options) {
|
|
|
6344
6814
|
token: () => current,
|
|
6345
6815
|
async dispose() {
|
|
6346
6816
|
disposed = true;
|
|
6817
|
+
supervisor.dispose();
|
|
6818
|
+
clearGhosts();
|
|
6347
6819
|
if (driver !== null) {
|
|
6348
6820
|
const target = driver;
|
|
6349
6821
|
driver = null;
|
|
@@ -6352,6 +6824,7 @@ function createRoomEngine(options) {
|
|
|
6352
6824
|
store.reset();
|
|
6353
6825
|
}
|
|
6354
6826
|
};
|
|
6827
|
+
return engine;
|
|
6355
6828
|
}
|
|
6356
6829
|
function canModerate(participant) {
|
|
6357
6830
|
return participant !== void 0 && isHostRole(participant.role);
|
|
@@ -7437,7 +7910,7 @@ function participantAttributes(p) {
|
|
|
7437
7910
|
"data-meet-participant-role": observedRole(p.role, p.standing),
|
|
7438
7911
|
"data-meet-participant-self": String(p.isLocal)
|
|
7439
7912
|
};
|
|
7440
|
-
const quality = observedQuality(p.quality);
|
|
7913
|
+
const quality = p.reconnecting === true ? "reconnecting" : observedQuality(p.quality);
|
|
7441
7914
|
if (quality !== null) attrs["data-meet-participant-connection"] = quality;
|
|
7442
7915
|
return attrs;
|
|
7443
7916
|
}
|