@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/index.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) {
|
|
@@ -1115,8 +1462,14 @@ function nextPhase(from, event) {
|
|
|
1115
1462
|
return from === "resolving" ? "prejoin" : null;
|
|
1116
1463
|
case "unsupported":
|
|
1117
1464
|
return from === "resolving" ? "unsupported" : null;
|
|
1465
|
+
case "link_state":
|
|
1466
|
+
return from === "resolving" && event.reason !== null && isJoinRefusalReason(event.reason) ? refusalPhase(event.reason) : null;
|
|
1118
1467
|
case "join":
|
|
1119
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;
|
|
1120
1473
|
case "back_to_prejoin":
|
|
1121
1474
|
return from === "left" || from === "disconnected" ? "prejoin" : null;
|
|
1122
1475
|
case "retry":
|
|
@@ -1523,6 +1876,8 @@ var RPCS = {
|
|
|
1523
1876
|
getOrCreateMeeting: "meet_get_or_create_meeting",
|
|
1524
1877
|
/** Resolve a durable public link, before/during/after its scheduled time. */
|
|
1525
1878
|
meetingBySlug: "meet_meeting_by_slug",
|
|
1879
|
+
/** The current run of a durable link (open / not started / ended + next start), no pass needed. */
|
|
1880
|
+
linkState: "meet_link_state",
|
|
1526
1881
|
/*
|
|
1527
1882
|
* ── S4 ADMISSION (CORE-DESIGN §2.4). Each returns the decided row; the caller then
|
|
1528
1883
|
* sends the payload-free `mx.state.changed` hint. Authorized by `who_may_admit`.
|
|
@@ -1831,6 +2186,14 @@ function createMeetRepository(options) {
|
|
|
1831
2186
|
if (response.error !== null) throw normalizeMeetError(response.error, operation);
|
|
1832
2187
|
}
|
|
1833
2188
|
}
|
|
2189
|
+
async function readLinkRun(slug) {
|
|
2190
|
+
try {
|
|
2191
|
+
const response = await db.rpc(RPCS.linkState, { p_slug: slug });
|
|
2192
|
+
return projectLinkRun(response.error === null ? response.data : null);
|
|
2193
|
+
} catch {
|
|
2194
|
+
return void 0;
|
|
2195
|
+
}
|
|
2196
|
+
}
|
|
1834
2197
|
async function guard(operation, run) {
|
|
1835
2198
|
let response = await run();
|
|
1836
2199
|
if (response.error !== null) {
|
|
@@ -2232,11 +2595,12 @@ function createMeetRepository(options) {
|
|
|
2232
2595
|
return projectMeeting(row);
|
|
2233
2596
|
},
|
|
2234
2597
|
async meetingBySlug(slug) {
|
|
2235
|
-
const row = await
|
|
2236
|
-
"meet.meetingBySlug",
|
|
2237
|
-
(
|
|
2238
|
-
);
|
|
2239
|
-
|
|
2598
|
+
const [row, linkRun] = await Promise.all([
|
|
2599
|
+
guard("meet.meetingBySlug", () => db.rpc(RPCS.meetingBySlug, { p_slug: slug })),
|
|
2600
|
+
readLinkRun(slug)
|
|
2601
|
+
]);
|
|
2602
|
+
const record = withRunNoteTaker(projectMeeting(row));
|
|
2603
|
+
return linkRun === void 0 ? record : { ...record, linkRun };
|
|
2240
2604
|
},
|
|
2241
2605
|
async meeting(id) {
|
|
2242
2606
|
const response = await db.from(TABLES.meetings).select(MEETING_COLUMNS).eq("id", id).single();
|
|
@@ -2425,6 +2789,15 @@ function snippetRuns(snippet) {
|
|
|
2425
2789
|
if (last < snippet.length) runs.push({ text: snippet.slice(last), hit: false });
|
|
2426
2790
|
return runs;
|
|
2427
2791
|
}
|
|
2792
|
+
function projectLinkRun(value) {
|
|
2793
|
+
if (typeof value !== "object" || value === null) return void 0;
|
|
2794
|
+
const row = value;
|
|
2795
|
+
const state = row["state"];
|
|
2796
|
+
if (state !== "open" && state !== "not_started" && state !== "ended" && state !== "cancelled") return void 0;
|
|
2797
|
+
const reason = row["reason"] === "ended" || row["reason"] === "cancelled" ? row["reason"] : null;
|
|
2798
|
+
const nextStart = typeof row["next_start"] === "string" ? row["next_start"] : null;
|
|
2799
|
+
return { state, reason, nextStart };
|
|
2800
|
+
}
|
|
2428
2801
|
|
|
2429
2802
|
// src/core/app-panel.ts
|
|
2430
2803
|
function appPanelOf(meeting) {
|
|
@@ -3010,6 +3383,7 @@ var EMPTY = {
|
|
|
3010
3383
|
phaseNotice: null,
|
|
3011
3384
|
roomName: null,
|
|
3012
3385
|
connection: "stable",
|
|
3386
|
+
connectionBanner: false,
|
|
3013
3387
|
share: NO_SHARE,
|
|
3014
3388
|
call: null,
|
|
3015
3389
|
waiting: null,
|
|
@@ -3964,8 +4338,11 @@ function mapConnectionState(state) {
|
|
|
3964
4338
|
case "connecting":
|
|
3965
4339
|
return "connecting";
|
|
3966
4340
|
case "reconnecting":
|
|
3967
|
-
case "signalReconnecting":
|
|
3968
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";
|
|
3969
4346
|
default:
|
|
3970
4347
|
return "disconnected";
|
|
3971
4348
|
}
|
|
@@ -4046,8 +4423,12 @@ function createLiveKitDriver(handlers, options = {}) {
|
|
|
4046
4423
|
// R5: large meetings degrade gracefully instead of melting a laptop.
|
|
4047
4424
|
adaptiveStream: true,
|
|
4048
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(),
|
|
4049
4429
|
...options.roomOptions
|
|
4050
4430
|
});
|
|
4431
|
+
const reasonName = (reason) => reason === void 0 || reason === null ? null : module.DisconnectReason?.[reason] ?? String(reason);
|
|
4051
4432
|
const roster = () => {
|
|
4052
4433
|
const local = snapshotParticipant(created.localParticipant, true);
|
|
4053
4434
|
const remotes = [...created.remoteParticipants.values()].map(
|
|
@@ -4065,7 +4446,10 @@ function createLiveKitDriver(handlers, options = {}) {
|
|
|
4065
4446
|
if (track.kind !== Track.Kind.Audio) return;
|
|
4066
4447
|
for (const element of track.detach()) element.remove();
|
|
4067
4448
|
});
|
|
4068
|
-
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) => {
|
|
4069
4453
|
handlers.roomMetadata?.(metadata ?? "");
|
|
4070
4454
|
}).on(RoomEvent.ActiveSpeakersChanged, (speakers) => {
|
|
4071
4455
|
handlers.activeSpeakers(speakers.map((speaker) => speaker.identity));
|
|
@@ -4082,8 +4466,7 @@ function createLiveKitDriver(handlers, options = {}) {
|
|
|
4082
4466
|
}).on(RoomEvent.Disconnected, (reason) => {
|
|
4083
4467
|
audioContainer?.remove();
|
|
4084
4468
|
audioContainer = null;
|
|
4085
|
-
|
|
4086
|
-
handlers.disconnected(named);
|
|
4469
|
+
handlers.disconnected(reasonName(reason));
|
|
4087
4470
|
});
|
|
4088
4471
|
try {
|
|
4089
4472
|
created.registerTextStreamHandler(TRANSCRIPTION_TOPIC, (reader, participantInfo) => {
|
|
@@ -5456,6 +5839,18 @@ function createRoomEngine(options) {
|
|
|
5456
5839
|
let disposed = false;
|
|
5457
5840
|
let moving = false;
|
|
5458
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
|
+
}
|
|
5459
5854
|
async function dropRoom() {
|
|
5460
5855
|
const target = driver;
|
|
5461
5856
|
driver = null;
|
|
@@ -5466,6 +5861,7 @@ function createRoomEngine(options) {
|
|
|
5466
5861
|
store.setLocalTrack("microphone", IDLE_TRACK);
|
|
5467
5862
|
store.setLocalTrack("camera", IDLE_TRACK);
|
|
5468
5863
|
store.patch({ share: NO_SHARE, connection: "stable" });
|
|
5864
|
+
clearGhosts();
|
|
5469
5865
|
await target?.disconnect();
|
|
5470
5866
|
}
|
|
5471
5867
|
async function leaveRoom() {
|
|
@@ -5533,7 +5929,7 @@ function createRoomEngine(options) {
|
|
|
5533
5929
|
participants(list) {
|
|
5534
5930
|
const joinedAt = new Date(now()).toISOString();
|
|
5535
5931
|
const before = new Set(store.snapshot().participants.map((p) => p.identity));
|
|
5536
|
-
|
|
5932
|
+
projectRoster(list, joinedAt);
|
|
5537
5933
|
const snapshot = store.snapshot();
|
|
5538
5934
|
const newcomer = snapshot.participants.some(
|
|
5539
5935
|
(participant) => !participant.isLocal && !before.has(participant.identity)
|
|
@@ -5545,6 +5941,9 @@ function createRoomEngine(options) {
|
|
|
5545
5941
|
);
|
|
5546
5942
|
}
|
|
5547
5943
|
},
|
|
5944
|
+
participantLeft(who, reason) {
|
|
5945
|
+
departures.set(who, reason);
|
|
5946
|
+
},
|
|
5548
5947
|
roomMetadata(metadata) {
|
|
5549
5948
|
let spotlight = null;
|
|
5550
5949
|
if (metadata.length > 0) {
|
|
@@ -5571,12 +5970,8 @@ function createRoomEngine(options) {
|
|
|
5571
5970
|
store.setQuality(asParticipantIdentity(who), value);
|
|
5572
5971
|
},
|
|
5573
5972
|
connectionState(state) {
|
|
5574
|
-
|
|
5575
|
-
|
|
5576
|
-
store.dispatch({ type: "connected" });
|
|
5577
|
-
} else if (state === "reconnecting") {
|
|
5578
|
-
store.patch({ connection: "reconnecting" });
|
|
5579
|
-
}
|
|
5973
|
+
supervisor.driverState(state);
|
|
5974
|
+
if (state === "connected" && store.snapshot().phase === "joining") store.dispatch({ type: "connected" });
|
|
5580
5975
|
},
|
|
5581
5976
|
transcription(event) {
|
|
5582
5977
|
const snapshot = store.snapshot();
|
|
@@ -5709,6 +6104,7 @@ function createRoomEngine(options) {
|
|
|
5709
6104
|
};
|
|
5710
6105
|
async function resolveExitAndApply(hint) {
|
|
5711
6106
|
const connection = current?.identity ?? store.snapshot().localIdentity ?? null;
|
|
6107
|
+
lastRequest = requestAsNow();
|
|
5712
6108
|
await dropRoom();
|
|
5713
6109
|
let server;
|
|
5714
6110
|
if (options.readExit === void 0 || connection === null || store.snapshot().meeting === null) {
|
|
@@ -5722,7 +6118,7 @@ function createRoomEngine(options) {
|
|
|
5722
6118
|
}
|
|
5723
6119
|
if (disposed) return;
|
|
5724
6120
|
const resolved = resolveExit({ server, hint, frozen: options.wasFrozen?.() ?? false });
|
|
5725
|
-
store.patch({ connection: resolved.connection });
|
|
6121
|
+
store.patch({ connection: server === null ? supervisor.exitConnection() : resolved.connection });
|
|
5726
6122
|
if (resolved.to === "waiting:knocking") {
|
|
5727
6123
|
store.patch({
|
|
5728
6124
|
waiting: {
|
|
@@ -5754,6 +6150,8 @@ function createRoomEngine(options) {
|
|
|
5754
6150
|
...request.observe === true ? { observe: true } : {}
|
|
5755
6151
|
});
|
|
5756
6152
|
current = token;
|
|
6153
|
+
lastServerUrl = token.serverUrl;
|
|
6154
|
+
connectionPolicy = token.connectionPolicy ?? DEFAULT_CONNECTION_POLICY;
|
|
5757
6155
|
store.patch({
|
|
5758
6156
|
localIdentity: token.identity,
|
|
5759
6157
|
breakoutRoomId: token.breakoutRoomId ?? null,
|
|
@@ -5858,7 +6256,59 @@ function createRoomEngine(options) {
|
|
|
5858
6256
|
throw normalized;
|
|
5859
6257
|
}
|
|
5860
6258
|
}
|
|
5861
|
-
|
|
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 = {
|
|
5862
6312
|
join(request) {
|
|
5863
6313
|
if (joining !== null) return joining;
|
|
5864
6314
|
if (store.snapshot().phase !== "joining" && !store.dispatch({ type: "join", click: true })) {
|
|
@@ -5903,6 +6353,35 @@ function createRoomEngine(options) {
|
|
|
5903
6353
|
});
|
|
5904
6354
|
return joining;
|
|
5905
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
|
+
},
|
|
5906
6385
|
async moveToBreakout(roomId) {
|
|
5907
6386
|
const request = lastRequest;
|
|
5908
6387
|
if (request === null || driver === null) {
|
|
@@ -6070,6 +6549,8 @@ function createRoomEngine(options) {
|
|
|
6070
6549
|
token: () => current,
|
|
6071
6550
|
async dispose() {
|
|
6072
6551
|
disposed = true;
|
|
6552
|
+
supervisor.dispose();
|
|
6553
|
+
clearGhosts();
|
|
6073
6554
|
if (driver !== null) {
|
|
6074
6555
|
const target = driver;
|
|
6075
6556
|
driver = null;
|
|
@@ -6078,6 +6559,7 @@ function createRoomEngine(options) {
|
|
|
6078
6559
|
store.reset();
|
|
6079
6560
|
}
|
|
6080
6561
|
};
|
|
6562
|
+
return engine;
|
|
6081
6563
|
}
|
|
6082
6564
|
function canModerate(participant) {
|
|
6083
6565
|
return participant !== void 0 && isHostRole(participant.role);
|
|
@@ -7163,7 +7645,7 @@ function participantAttributes(p) {
|
|
|
7163
7645
|
"data-meet-participant-role": observedRole(p.role, p.standing),
|
|
7164
7646
|
"data-meet-participant-self": String(p.isLocal)
|
|
7165
7647
|
};
|
|
7166
|
-
const quality = observedQuality(p.quality);
|
|
7648
|
+
const quality = p.reconnecting === true ? "reconnecting" : observedQuality(p.quality);
|
|
7167
7649
|
if (quality !== null) attrs["data-meet-participant-connection"] = quality;
|
|
7168
7650
|
return attrs;
|
|
7169
7651
|
}
|
|
@@ -7179,6 +7661,7 @@ export {
|
|
|
7179
7661
|
CAPTION_LANGUAGES,
|
|
7180
7662
|
CONSENT_NOTICE_ATTRIBUTE,
|
|
7181
7663
|
DATA_TOPICS,
|
|
7664
|
+
DEFAULT_CONNECTION_POLICY,
|
|
7182
7665
|
DEFAULT_EFFECTS,
|
|
7183
7666
|
DEFAULT_PAGE_SIZE,
|
|
7184
7667
|
EMPTY_COLLAB,
|
|
@@ -7193,6 +7676,7 @@ export {
|
|
|
7193
7676
|
IDLE_TRACK,
|
|
7194
7677
|
JOIN_REFUSAL_REASONS,
|
|
7195
7678
|
LISTEN_ONLY_NOTICE,
|
|
7679
|
+
LIVEKIT_BASE_DELAYS_MS,
|
|
7196
7680
|
LIVE_PHASES,
|
|
7197
7681
|
MEETING_INVITE_KIND,
|
|
7198
7682
|
MEETING_INVITE_VERSION,
|
|
@@ -7216,6 +7700,7 @@ export {
|
|
|
7216
7700
|
PARTICIPANT_STANDING_ATTRIBUTE,
|
|
7217
7701
|
REACTION_TTL_MS,
|
|
7218
7702
|
REFUSED_ABSENCE,
|
|
7703
|
+
RESUME_WINDOW_MS,
|
|
7219
7704
|
ROLE_RANK,
|
|
7220
7705
|
ROOM_EVENTS,
|
|
7221
7706
|
ROOM_EVENT_PREFIX,
|
|
@@ -7245,6 +7730,7 @@ export {
|
|
|
7245
7730
|
attendanceTotals,
|
|
7246
7731
|
attendeeKey,
|
|
7247
7732
|
autoAssign,
|
|
7733
|
+
autoRejoinDelayMs,
|
|
7248
7734
|
availableActivities,
|
|
7249
7735
|
breakoutRemainingMs,
|
|
7250
7736
|
callInviteAction,
|
|
@@ -7259,9 +7745,11 @@ export {
|
|
|
7259
7745
|
clockPosition,
|
|
7260
7746
|
collabFromState,
|
|
7261
7747
|
computeStage,
|
|
7748
|
+
connectionPolicyFrom,
|
|
7262
7749
|
countLabel,
|
|
7263
7750
|
createCallCenter,
|
|
7264
7751
|
createCallInviteHandler,
|
|
7752
|
+
createConnectionSupervisor,
|
|
7265
7753
|
createDeviceManager,
|
|
7266
7754
|
createInRoomClient,
|
|
7267
7755
|
createLiveKitDriver,
|
|
@@ -7273,11 +7761,15 @@ export {
|
|
|
7273
7761
|
createMeetingRecordCache,
|
|
7274
7762
|
createMeetingSession,
|
|
7275
7763
|
createMemoryDeviceStorage,
|
|
7764
|
+
createReachabilityProbe,
|
|
7765
|
+
createReconnectPolicy,
|
|
7276
7766
|
createRecordingController,
|
|
7277
7767
|
createRoomEngine,
|
|
7278
7768
|
createTokenClient,
|
|
7279
7769
|
createWebDeviceStorage,
|
|
7280
7770
|
createWebEffectsStorage,
|
|
7771
|
+
createWebNetworkPort,
|
|
7772
|
+
createWebResumeStorage,
|
|
7281
7773
|
createWebRoomTokenStorage,
|
|
7282
7774
|
defaultCaptionLanguage,
|
|
7283
7775
|
displayNameFor,
|
|
@@ -7290,6 +7782,7 @@ export {
|
|
|
7290
7782
|
guestRecordAccessSetting,
|
|
7291
7783
|
hasEnded,
|
|
7292
7784
|
hostUserIds,
|
|
7785
|
+
httpUrlOf,
|
|
7293
7786
|
invalidResponse,
|
|
7294
7787
|
isGuest,
|
|
7295
7788
|
isGuestIdentity,
|
|
@@ -7303,6 +7796,7 @@ export {
|
|
|
7303
7796
|
isRealName,
|
|
7304
7797
|
isRefusedPhase,
|
|
7305
7798
|
isRoomEventTopic,
|
|
7799
|
+
isUnannouncedDeparture,
|
|
7306
7800
|
isWaitingPhase,
|
|
7307
7801
|
learnName,
|
|
7308
7802
|
learnNames,
|
|
@@ -7324,6 +7818,7 @@ export {
|
|
|
7324
7818
|
meetingTopic,
|
|
7325
7819
|
meetingWhen,
|
|
7326
7820
|
mergeCaption,
|
|
7821
|
+
mergeGhosts,
|
|
7327
7822
|
mergeRecord,
|
|
7328
7823
|
microphoneNoticeCode,
|
|
7329
7824
|
moveToRoom,
|