@ai-matrx/meet 0.11.32 → 0.11.34
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 +35 -0
- package/dist/core.cjs +137 -48
- package/dist/core.cjs.map +1 -1
- package/dist/core.d.cts +75 -6
- package/dist/core.d.ts +75 -6
- package/dist/core.js +137 -48
- package/dist/core.js.map +1 -1
- package/dist/index.cjs +137 -48
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +137 -48
- package/dist/index.js.map +1 -1
- package/dist/react.cjs +481 -79
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +6 -0
- package/dist/react.d.ts +6 -0
- package/dist/react.js +481 -79
- package/dist/react.js.map +1 -1
- package/dist/skins/meet.cjs +217 -33
- package/dist/skins/meet.cjs.map +1 -1
- package/dist/skins/meet.js +217 -33
- package/dist/skins/meet.js.map +1 -1
- package/dist/styles.css +19 -0
- package/package.json +2 -2
package/dist/react.cjs
CHANGED
|
@@ -330,6 +330,139 @@ __export(react_exports, {
|
|
|
330
330
|
});
|
|
331
331
|
module.exports = __toCommonJS(react_exports);
|
|
332
332
|
|
|
333
|
+
// src/core/tab-claim.ts
|
|
334
|
+
var TAB_ID_KEY = "mx.meet.tab";
|
|
335
|
+
var TAB_ANSWER_MS = 250;
|
|
336
|
+
function validTabId(value) {
|
|
337
|
+
return value !== null && /^[A-Za-z0-9_-]{4,64}$/.test(value);
|
|
338
|
+
}
|
|
339
|
+
function wait(ms2) {
|
|
340
|
+
return new Promise((resolve) => setTimeout(resolve, ms2));
|
|
341
|
+
}
|
|
342
|
+
function createTabClaim(platform = webTabPlatform()) {
|
|
343
|
+
let mine = null;
|
|
344
|
+
let current = null;
|
|
345
|
+
const self = platform.randomId();
|
|
346
|
+
const unsubscribe = platform.bus?.subscribe((message) => {
|
|
347
|
+
if (message.kind === "tab-alive?" && mine !== null && message.tabId === mine && message.from !== self) {
|
|
348
|
+
platform.bus?.post({ kind: "tab-alive", tabId: mine });
|
|
349
|
+
} else if (message.kind === "who-is-in?" && current !== null && mine !== null && message.from !== self && message.room === current.room && message.person === current.person) {
|
|
350
|
+
platform.bus?.post({ kind: "in-room", room: current.room, person: current.person, tabId: mine, to: message.from });
|
|
351
|
+
}
|
|
352
|
+
}) ?? (() => {
|
|
353
|
+
});
|
|
354
|
+
async function someoneAnswersFor(tabId2) {
|
|
355
|
+
const bus = platform.bus;
|
|
356
|
+
if (bus === null) return false;
|
|
357
|
+
let answered = false;
|
|
358
|
+
const stop = bus.subscribe((message) => {
|
|
359
|
+
if (message.kind === "tab-alive" && message.tabId === tabId2) answered = true;
|
|
360
|
+
});
|
|
361
|
+
bus.post({ kind: "tab-alive?", tabId: tabId2, from: self });
|
|
362
|
+
await wait(TAB_ANSWER_MS);
|
|
363
|
+
stop();
|
|
364
|
+
return answered;
|
|
365
|
+
}
|
|
366
|
+
async function claim() {
|
|
367
|
+
const stored = platform.storage?.get(TAB_ID_KEY) ?? null;
|
|
368
|
+
let candidate = validTabId(stored) ? stored : platform.randomId();
|
|
369
|
+
for (let attempt = 0; attempt < 4; attempt++) {
|
|
370
|
+
const held = await platform.tryLock(`${TAB_ID_KEY}:${candidate}`);
|
|
371
|
+
if (held) break;
|
|
372
|
+
if (!await someoneAnswersFor(candidate)) break;
|
|
373
|
+
candidate = platform.randomId();
|
|
374
|
+
}
|
|
375
|
+
mine = candidate;
|
|
376
|
+
platform.storage?.set(TAB_ID_KEY, candidate);
|
|
377
|
+
return candidate;
|
|
378
|
+
}
|
|
379
|
+
const tabId = claim();
|
|
380
|
+
return {
|
|
381
|
+
tabId,
|
|
382
|
+
inRoom(room, person) {
|
|
383
|
+
current = { room, person };
|
|
384
|
+
},
|
|
385
|
+
left() {
|
|
386
|
+
current = null;
|
|
387
|
+
},
|
|
388
|
+
async presentElsewhere(room, person) {
|
|
389
|
+
const bus = platform.bus;
|
|
390
|
+
if (bus === null) return false;
|
|
391
|
+
let seen = false;
|
|
392
|
+
const stop = bus.subscribe((message) => {
|
|
393
|
+
if (message.kind === "in-room" && message.to === self && message.room === room && message.person === person) {
|
|
394
|
+
seen = true;
|
|
395
|
+
}
|
|
396
|
+
});
|
|
397
|
+
bus.post({ kind: "who-is-in?", room, person, from: self });
|
|
398
|
+
await wait(TAB_ANSWER_MS);
|
|
399
|
+
stop();
|
|
400
|
+
return seen;
|
|
401
|
+
},
|
|
402
|
+
dispose() {
|
|
403
|
+
unsubscribe();
|
|
404
|
+
}
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
function randomTabId() {
|
|
408
|
+
const crypto = globalThis.crypto;
|
|
409
|
+
const raw = crypto?.randomUUID?.() ?? `${Math.random().toString(36).slice(2)}${Date.now().toString(36)}`;
|
|
410
|
+
return raw.replace(/[^A-Za-z0-9]/g, "").slice(0, 16);
|
|
411
|
+
}
|
|
412
|
+
function webTabPlatform() {
|
|
413
|
+
const g = globalThis;
|
|
414
|
+
const session = g.sessionStorage;
|
|
415
|
+
const storage = session === void 0 ? null : {
|
|
416
|
+
get(key) {
|
|
417
|
+
try {
|
|
418
|
+
return session.getItem(key);
|
|
419
|
+
} catch {
|
|
420
|
+
return null;
|
|
421
|
+
}
|
|
422
|
+
},
|
|
423
|
+
set(key, value) {
|
|
424
|
+
try {
|
|
425
|
+
session.setItem(key, value);
|
|
426
|
+
} catch {
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
};
|
|
430
|
+
const channel = g.BroadcastChannel !== void 0 ? new g.BroadcastChannel("mx.meet.tabs") : null;
|
|
431
|
+
const bus = channel === null ? null : {
|
|
432
|
+
post(message) {
|
|
433
|
+
channel.postMessage(message);
|
|
434
|
+
},
|
|
435
|
+
subscribe(listener) {
|
|
436
|
+
const handler = (event) => {
|
|
437
|
+
const data = event.data;
|
|
438
|
+
if (data !== null && typeof data === "object" && typeof data.kind === "string") listener(data);
|
|
439
|
+
};
|
|
440
|
+
channel.addEventListener("message", handler);
|
|
441
|
+
return () => channel.removeEventListener("message", handler);
|
|
442
|
+
}
|
|
443
|
+
};
|
|
444
|
+
return {
|
|
445
|
+
storage,
|
|
446
|
+
bus,
|
|
447
|
+
randomId: randomTabId,
|
|
448
|
+
tryLock(name) {
|
|
449
|
+
const locks = g.navigator?.locks;
|
|
450
|
+
if (locks === void 0) return Promise.resolve(false);
|
|
451
|
+
return new Promise((resolve) => {
|
|
452
|
+
locks.request(name, { ifAvailable: true }, (lock) => {
|
|
453
|
+
if (lock === null) {
|
|
454
|
+
resolve(false);
|
|
455
|
+
return Promise.resolve();
|
|
456
|
+
}
|
|
457
|
+
resolve(true);
|
|
458
|
+
return new Promise(() => {
|
|
459
|
+
});
|
|
460
|
+
}).catch(() => resolve(false));
|
|
461
|
+
});
|
|
462
|
+
}
|
|
463
|
+
};
|
|
464
|
+
}
|
|
465
|
+
|
|
333
466
|
// src/core/connection-supervisor.ts
|
|
334
467
|
var DEFAULT_CONNECTION_POLICY = {
|
|
335
468
|
bannerDelayMs: 2e3,
|
|
@@ -696,12 +829,18 @@ var MeetError = class extends Error {
|
|
|
696
829
|
* renders `refused:<reason>` (or `ended`) from it — never from the HTTP status.
|
|
697
830
|
*/
|
|
698
831
|
reason;
|
|
699
|
-
|
|
832
|
+
/**
|
|
833
|
+
* A refusal that lifts by itself says when: whole seconds from the moment the server answered
|
|
834
|
+
* (a denial's cooldown, `reknock_cooldown_seconds`). Null for every refusal that does not.
|
|
835
|
+
*/
|
|
836
|
+
retryAfterSeconds;
|
|
837
|
+
constructor(code, message, remedy, cause, reason, retryAfterSeconds) {
|
|
700
838
|
super(message);
|
|
701
839
|
this.name = "MeetError";
|
|
702
840
|
this.code = code;
|
|
703
841
|
this.remedy = remedy;
|
|
704
842
|
this.reason = reason ?? null;
|
|
843
|
+
this.retryAfterSeconds = retryAfterSeconds ?? null;
|
|
705
844
|
if (cause !== void 0) this.cause = cause;
|
|
706
845
|
}
|
|
707
846
|
/** True when retrying after the host re-establishes a session is the fix. */
|
|
@@ -1035,7 +1174,8 @@ function parseRoomToken(body, roomName) {
|
|
|
1035
1174
|
const serverUrl = readString(payload, "server_url") ?? readString(payload, "serverUrl");
|
|
1036
1175
|
const identity = readString(payload, "identity");
|
|
1037
1176
|
const expiresAt = readString(payload, "expires_at") ?? readString(payload, "expiresAt");
|
|
1038
|
-
|
|
1177
|
+
const secondDeviceChoice = readString(payload, "second_device_choice");
|
|
1178
|
+
if (token === null && secondDeviceChoice === null || serverUrl === null || identity === null) {
|
|
1039
1179
|
throw invalidResponse(
|
|
1040
1180
|
"meet.token",
|
|
1041
1181
|
"the response is missing token, server_url, or identity \u2014 joining with a partial token would fail inside LiveKit with an opaque error instead of here"
|
|
@@ -1047,11 +1187,15 @@ function parseRoomToken(body, roomName) {
|
|
|
1047
1187
|
"the response carries no expires_at. Without it the package cannot refresh before the token dies, and a long meeting silently loses its ability to reconnect"
|
|
1048
1188
|
);
|
|
1049
1189
|
}
|
|
1190
|
+
const personKey = readString(payload, "person_key") ?? identity;
|
|
1050
1191
|
return {
|
|
1051
|
-
token,
|
|
1192
|
+
token: token ?? "",
|
|
1052
1193
|
serverUrl,
|
|
1053
1194
|
roomName,
|
|
1054
|
-
identity,
|
|
1195
|
+
identity: personKey,
|
|
1196
|
+
connectionIdentity: readString(payload, "connection_identity") ?? personKey,
|
|
1197
|
+
connectionKind: readString(payload, "connection_kind") === "companion" ? "companion" : "main",
|
|
1198
|
+
secondDeviceChoice,
|
|
1055
1199
|
displayName: readString(payload, "display_name") ?? readString(payload, "displayName") ?? identity,
|
|
1056
1200
|
expiresAt,
|
|
1057
1201
|
canPublish: readBoolean(payload, "can_publish", true),
|
|
@@ -1126,7 +1270,9 @@ function createTokenClient(options) {
|
|
|
1126
1270
|
organization_id: input.identity.organizationId,
|
|
1127
1271
|
display_name: input.identity.displayName,
|
|
1128
1272
|
device_id: input.identity.deviceId,
|
|
1129
|
-
guest
|
|
1273
|
+
guest,
|
|
1274
|
+
...input.tabId !== void 0 ? { tab_id: input.tabId } : {},
|
|
1275
|
+
...input.secondDevice !== void 0 ? { second_device: input.secondDevice } : {}
|
|
1130
1276
|
}
|
|
1131
1277
|
});
|
|
1132
1278
|
} catch (error) {
|
|
@@ -1143,7 +1289,8 @@ function createTokenClient(options) {
|
|
|
1143
1289
|
said?.message ?? "You can't join this meeting.",
|
|
1144
1290
|
said?.remedy ?? "Ask the person who shared the link to let you in, then open the link again.",
|
|
1145
1291
|
void 0,
|
|
1146
|
-
said?.reason ?? null
|
|
1292
|
+
said?.reason ?? null,
|
|
1293
|
+
said?.retryAfterSeconds ?? null
|
|
1147
1294
|
);
|
|
1148
1295
|
}
|
|
1149
1296
|
if (response.status === 404) {
|
|
@@ -1201,6 +1348,10 @@ function createTokenClient(options) {
|
|
|
1201
1348
|
if (!expired(entry)) return Promise.resolve(entry.token);
|
|
1202
1349
|
}
|
|
1203
1350
|
const pending = request(input, true).then((token) => {
|
|
1351
|
+
if (token.secondDeviceChoice != null) {
|
|
1352
|
+
cache.delete(input.roomName);
|
|
1353
|
+
return token;
|
|
1354
|
+
}
|
|
1204
1355
|
cache.set(input.roomName, { token, mintedAt: now(), inFlight: null });
|
|
1205
1356
|
remember(token);
|
|
1206
1357
|
return token;
|
|
@@ -1235,6 +1386,9 @@ function placeholder(roomName) {
|
|
|
1235
1386
|
serverUrl: "",
|
|
1236
1387
|
roomName,
|
|
1237
1388
|
identity: "",
|
|
1389
|
+
connectionIdentity: "",
|
|
1390
|
+
connectionKind: "main",
|
|
1391
|
+
secondDeviceChoice: null,
|
|
1238
1392
|
displayName: "",
|
|
1239
1393
|
expiresAt: (/* @__PURE__ */ new Date(0)).toISOString(),
|
|
1240
1394
|
canPublish: false,
|
|
@@ -1259,7 +1413,9 @@ async function serverSentence(response) {
|
|
|
1259
1413
|
if (message === null) return null;
|
|
1260
1414
|
const remedy = typeof source.remedy === "string" && source.remedy.trim() !== "" ? source.remedy : void 0;
|
|
1261
1415
|
const reason = typeof source.reason === "string" && source.reason !== "" ? source.reason : null;
|
|
1262
|
-
|
|
1416
|
+
const retry = source.retry_after_seconds;
|
|
1417
|
+
const retryAfterSeconds = typeof retry === "number" && Number.isFinite(retry) && retry > 0 ? Math.ceil(retry) : null;
|
|
1418
|
+
return { message, remedy, reason, retryAfterSeconds };
|
|
1263
1419
|
} catch {
|
|
1264
1420
|
return null;
|
|
1265
1421
|
}
|
|
@@ -1710,7 +1866,10 @@ var JOINABLE_FROM = /* @__PURE__ */ new Set([
|
|
|
1710
1866
|
"superseded",
|
|
1711
1867
|
"inactive",
|
|
1712
1868
|
"denied",
|
|
1713
|
-
"knock_expired"
|
|
1869
|
+
"knock_expired",
|
|
1870
|
+
// A refused knock whose rule lets the person ask again (a cooldown that ran out): the machine
|
|
1871
|
+
// allows the press; the server's gate decides whether it knocks or is refused again.
|
|
1872
|
+
"refused:lobby_rejects"
|
|
1714
1873
|
]);
|
|
1715
1874
|
var NOT_ENDABLE = /* @__PURE__ */ new Set([
|
|
1716
1875
|
"ended",
|
|
@@ -1794,6 +1953,8 @@ function nextPhase(from, event) {
|
|
|
1794
1953
|
return WAITING_PHASES.has(from) ? "joining" : null;
|
|
1795
1954
|
case "connected":
|
|
1796
1955
|
return from === "joining" ? "in_call" : null;
|
|
1956
|
+
case "second_device":
|
|
1957
|
+
return from === "joining" ? "prejoin" : null;
|
|
1797
1958
|
case "join_failed":
|
|
1798
1959
|
return from === "joining" ? refusalPhase(event.reason) : null;
|
|
1799
1960
|
case "exit":
|
|
@@ -1811,7 +1972,7 @@ function nextPhase(from, event) {
|
|
|
1811
1972
|
case "call_outcome":
|
|
1812
1973
|
return RINGING.has(from) ? event.outcome : null;
|
|
1813
1974
|
case "reset":
|
|
1814
|
-
return LIVE_PHASES.has(from) ? null : "resolving";
|
|
1975
|
+
return LIVE_PHASES.has(from) && event.teardown !== true ? null : "resolving";
|
|
1815
1976
|
}
|
|
1816
1977
|
}
|
|
1817
1978
|
function resolveExit(input) {
|
|
@@ -2117,8 +2278,8 @@ function createCallCenter(options) {
|
|
|
2117
2278
|
"A late accept must not resurrect a settled call. Close the ringing UI; the caller has already moved on."
|
|
2118
2279
|
);
|
|
2119
2280
|
}
|
|
2120
|
-
if (options.store.snapshot().phase === "ringing_in") options.store.dispatch({ type: "call_answered" });
|
|
2121
2281
|
const settled = await repository.settleCallInvite(id, "connecting", null);
|
|
2282
|
+
if (options.store.snapshot().phase === "ringing_in") options.store.dispatch({ type: "call_answered" });
|
|
2122
2283
|
options.store.patch({ call: { invite: settled, direction: "incoming" } });
|
|
2123
2284
|
settleLocal(settled);
|
|
2124
2285
|
channel?.send(MEET_EVENTS.settled, settled);
|
|
@@ -2329,11 +2490,17 @@ function createDeviceManager(options) {
|
|
|
2329
2490
|
}
|
|
2330
2491
|
|
|
2331
2492
|
// src/core/names.ts
|
|
2493
|
+
var CONNECTION_SEPARATOR = "~";
|
|
2494
|
+
function personOf(identity) {
|
|
2495
|
+
const value = String(identity ?? "").trim();
|
|
2496
|
+
const cut = value.indexOf(CONNECTION_SEPARATOR);
|
|
2497
|
+
return cut === -1 ? value : value.slice(0, cut);
|
|
2498
|
+
}
|
|
2332
2499
|
function isGuestIdentity(identity) {
|
|
2333
|
-
return
|
|
2500
|
+
return personOf(identity).startsWith("guest:");
|
|
2334
2501
|
}
|
|
2335
2502
|
function isAgentIdentity(identity) {
|
|
2336
|
-
return
|
|
2503
|
+
return personOf(identity).startsWith("agent:");
|
|
2337
2504
|
}
|
|
2338
2505
|
var NOTE_TAKER_PRODUCT_NAME = "Matrx note-taker";
|
|
2339
2506
|
var AGENT_PRODUCT_NAME = "Matrx agent";
|
|
@@ -3933,6 +4100,7 @@ var EMPTY = {
|
|
|
3933
4100
|
waiting: null,
|
|
3934
4101
|
meeting: null,
|
|
3935
4102
|
localIdentity: null,
|
|
4103
|
+
secondDevice: null,
|
|
3936
4104
|
grants: NO_GRANTS,
|
|
3937
4105
|
participants: [],
|
|
3938
4106
|
activeSpeakers: [],
|
|
@@ -4183,7 +4351,8 @@ function createMeetStore(initial = {}) {
|
|
|
4183
4351
|
commit({ ...state, media: { ...state.media, [kind]: next } });
|
|
4184
4352
|
},
|
|
4185
4353
|
reset() {
|
|
4186
|
-
|
|
4354
|
+
const phase = nextPhase(state.phase, { type: "reset", teardown: true }) ?? EMPTY.phase;
|
|
4355
|
+
commit({ ...EMPTY, effects: state.effects, phase, reason: reasonOf(phase), phaseNotice: null });
|
|
4187
4356
|
}
|
|
4188
4357
|
};
|
|
4189
4358
|
}
|
|
@@ -4216,6 +4385,28 @@ function lobbyEntryFrom(attendee, now = Date.now()) {
|
|
|
4216
4385
|
decidedBy: null
|
|
4217
4386
|
};
|
|
4218
4387
|
}
|
|
4388
|
+
function deniedEntryFrom(attendee) {
|
|
4389
|
+
if (attendee.admissionState !== "denied") return null;
|
|
4390
|
+
return {
|
|
4391
|
+
identity: attendee.identity,
|
|
4392
|
+
displayName: attendee.displayName,
|
|
4393
|
+
avatarUrl: attendee.avatarUrl,
|
|
4394
|
+
userId: attendee.userId,
|
|
4395
|
+
state: "denied",
|
|
4396
|
+
knockedAt: attendee.knockedAt ?? attendee.joinedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
4397
|
+
knockExpiresAt: null,
|
|
4398
|
+
decidedAt: null,
|
|
4399
|
+
decidedBy: null
|
|
4400
|
+
};
|
|
4401
|
+
}
|
|
4402
|
+
function applyDoorRow(store, attendee) {
|
|
4403
|
+
const entry = lobbyEntryFrom(attendee) ?? deniedEntryFrom(attendee);
|
|
4404
|
+
if (entry !== null) {
|
|
4405
|
+
store.upsertLobby(entry);
|
|
4406
|
+
return;
|
|
4407
|
+
}
|
|
4408
|
+
store.setAdmission(attendee.identity, decidedState(attendee.admissionState), null);
|
|
4409
|
+
}
|
|
4219
4410
|
function decidedState(state) {
|
|
4220
4411
|
return WAITING_ADMISSION_STATES.includes(state) ? "expired" : state;
|
|
4221
4412
|
}
|
|
@@ -4350,6 +4541,7 @@ function createMeetingSession(options) {
|
|
|
4350
4541
|
admission,
|
|
4351
4542
|
admittersPresent: read.admitters_present ?? null,
|
|
4352
4543
|
reknockAllowed: read.reknock_allowed ?? true,
|
|
4544
|
+
reknockAt: typeof read.reknock_in_seconds === "number" && read.reknock_in_seconds > 0 ? Date.now() + read.reknock_in_seconds * 1e3 : null,
|
|
4353
4545
|
knockExpiresAt: read.knock_expires_at ?? null,
|
|
4354
4546
|
pollSeconds: read.poll_seconds ?? snapshot.waiting?.pollSeconds ?? 3,
|
|
4355
4547
|
liveVersion: read.live_version ?? 0
|
|
@@ -4432,11 +4624,7 @@ function createMeetingSession(options) {
|
|
|
4432
4624
|
try {
|
|
4433
4625
|
const attendees = await repository.attendees(meeting.id);
|
|
4434
4626
|
store.learn(attendees);
|
|
4435
|
-
for (const attendee of attendees)
|
|
4436
|
-
const entry = lobbyEntryFrom(attendee);
|
|
4437
|
-
if (entry !== null) store.upsertLobby(entry);
|
|
4438
|
-
else store.setAdmission(attendee.identity, decidedState(attendee.admissionState), null);
|
|
4439
|
-
}
|
|
4627
|
+
for (const attendee of attendees) applyDoorRow(store, attendee);
|
|
4440
4628
|
} catch {
|
|
4441
4629
|
}
|
|
4442
4630
|
}
|
|
@@ -4518,12 +4706,7 @@ function createMeetingSession(options) {
|
|
|
4518
4706
|
if (row === null) return;
|
|
4519
4707
|
const attendee = repository.projectAttendee(row);
|
|
4520
4708
|
store.learn([attendee]);
|
|
4521
|
-
|
|
4522
|
-
if (entry !== null) {
|
|
4523
|
-
store.upsertLobby(entry);
|
|
4524
|
-
return;
|
|
4525
|
-
}
|
|
4526
|
-
store.setAdmission(attendee.identity, decidedState(attendee.admissionState), null);
|
|
4709
|
+
applyDoorRow(store, attendee);
|
|
4527
4710
|
}
|
|
4528
4711
|
},
|
|
4529
4712
|
{
|
|
@@ -4570,14 +4753,7 @@ function createMeetingSession(options) {
|
|
|
4570
4753
|
store.setRecording(latest.state, latest.startedBy, "record", latest.id);
|
|
4571
4754
|
}
|
|
4572
4755
|
store.learn(attendees);
|
|
4573
|
-
for (const attendee of attendees)
|
|
4574
|
-
const entry = lobbyEntryFrom(attendee);
|
|
4575
|
-
if (entry !== null) {
|
|
4576
|
-
store.upsertLobby(entry);
|
|
4577
|
-
} else {
|
|
4578
|
-
store.setAdmission(attendee.identity, decidedState(attendee.admissionState), null);
|
|
4579
|
-
}
|
|
4580
|
-
}
|
|
4756
|
+
for (const attendee of attendees) applyDoorRow(store, attendee);
|
|
4581
4757
|
}
|
|
4582
4758
|
});
|
|
4583
4759
|
meetingChannel.requestBackfill("manual");
|
|
@@ -4629,9 +4805,9 @@ function createMeetingSession(options) {
|
|
|
4629
4805
|
},
|
|
4630
4806
|
stopWaiting,
|
|
4631
4807
|
readState: readState2,
|
|
4632
|
-
async readExit(connection) {
|
|
4808
|
+
async readExit(connection, held) {
|
|
4633
4809
|
const meeting = store.snapshot().meeting;
|
|
4634
|
-
const pass = options.resolvePass?.() ?? null;
|
|
4810
|
+
const pass = held ?? options.resolvePass?.() ?? null;
|
|
4635
4811
|
if (meeting === null || pass === null) return void 0;
|
|
4636
4812
|
try {
|
|
4637
4813
|
const response = await options.api.request({
|
|
@@ -5479,6 +5655,7 @@ function SCREEN_SHARE_BLOCKED_REMEDY_FOR(remedy) {
|
|
|
5479
5655
|
}
|
|
5480
5656
|
var PARTICIPANT_ROLE_ATTRIBUTE = "matrx.participant.role";
|
|
5481
5657
|
var PARTICIPANT_CAN_RECORD_ATTRIBUTE = "matrx.participant.can_record";
|
|
5658
|
+
var CONNECTION_KIND_ATTRIBUTE = "matrx.participant.connection_kind";
|
|
5482
5659
|
var PARTICIPANT_STANDING_ATTRIBUTE = "matrx.participant.standing";
|
|
5483
5660
|
var KNOWN_STANDINGS = ["member", "invited", "outsider", "guest"];
|
|
5484
5661
|
function parseStanding(value) {
|
|
@@ -5502,7 +5679,8 @@ function projectParticipant(driver, joinedAt) {
|
|
|
5502
5679
|
}
|
|
5503
5680
|
const userId = typeof metadata.user_id === "string" ? asUserId(metadata.user_id) : null;
|
|
5504
5681
|
return {
|
|
5505
|
-
|
|
5682
|
+
// S7: a tile is a PERSON; LiveKit names the tab's connection (`<person>~<tab>`).
|
|
5683
|
+
identity: personOf(driver.identity),
|
|
5506
5684
|
userId,
|
|
5507
5685
|
// 🚨 A RAW LIVEKIT IDENTITY IS NEVER A NAME (MRI-A18). Through 0.4.12 this
|
|
5508
5686
|
// line read `driver.name.length > 0 ? driver.name : driver.identity`, and
|
|
@@ -5549,6 +5727,11 @@ function createRoomEngine(options) {
|
|
|
5549
5727
|
let ghosts = /* @__PURE__ */ new Map();
|
|
5550
5728
|
let ghostTimer = null;
|
|
5551
5729
|
let liveRoster = [];
|
|
5730
|
+
let personConnections = /* @__PURE__ */ new Map();
|
|
5731
|
+
let mainConnection = /* @__PURE__ */ new Map();
|
|
5732
|
+
function connectionsOf(who) {
|
|
5733
|
+
return personConnections.get(personOf(who)) ?? [who];
|
|
5734
|
+
}
|
|
5552
5735
|
const departures = /* @__PURE__ */ new Map();
|
|
5553
5736
|
function requestAsNow() {
|
|
5554
5737
|
if (lastRequest === null) return null;
|
|
@@ -5613,7 +5796,12 @@ function createRoomEngine(options) {
|
|
|
5613
5796
|
function failJoin(error, operation) {
|
|
5614
5797
|
const normalized = normalizeMeetError(error, operation);
|
|
5615
5798
|
store.patch({
|
|
5616
|
-
failure: {
|
|
5799
|
+
failure: {
|
|
5800
|
+
message: normalized.message,
|
|
5801
|
+
remedy: normalized.remedy,
|
|
5802
|
+
reason: normalized.reason,
|
|
5803
|
+
retryAt: normalized.retryAfterSeconds === null ? null : now() + normalized.retryAfterSeconds * 1e3
|
|
5804
|
+
}
|
|
5617
5805
|
});
|
|
5618
5806
|
store.dispatch({ type: "join_failed", reason: normalized.reason ?? null });
|
|
5619
5807
|
report(
|
|
@@ -5685,10 +5873,10 @@ function createRoomEngine(options) {
|
|
|
5685
5873
|
if (store.snapshot().spotlight !== spotlight) store.patch({ spotlight });
|
|
5686
5874
|
},
|
|
5687
5875
|
activeSpeakers(identities) {
|
|
5688
|
-
store.setSpeaking(identities.map(
|
|
5876
|
+
store.setSpeaking([...new Set(identities.map((who) => personOf(who)))]);
|
|
5689
5877
|
},
|
|
5690
5878
|
quality(who, value) {
|
|
5691
|
-
store.setQuality(
|
|
5879
|
+
store.setQuality(personOf(who), value);
|
|
5692
5880
|
},
|
|
5693
5881
|
connectionState(state) {
|
|
5694
5882
|
supervisor.driverState(state);
|
|
@@ -5708,7 +5896,7 @@ function createRoomEngine(options) {
|
|
|
5708
5896
|
);
|
|
5709
5897
|
return;
|
|
5710
5898
|
}
|
|
5711
|
-
const speakerIdentity =
|
|
5899
|
+
const speakerIdentity = personOf(event.speakerIdentity);
|
|
5712
5900
|
const speaker = snapshot.participants.find(
|
|
5713
5901
|
(participant) => participant.identity === speakerIdentity
|
|
5714
5902
|
);
|
|
@@ -5782,7 +5970,7 @@ function createRoomEngine(options) {
|
|
|
5782
5970
|
);
|
|
5783
5971
|
return;
|
|
5784
5972
|
}
|
|
5785
|
-
const sender =
|
|
5973
|
+
const sender = personOf(from ?? "");
|
|
5786
5974
|
switch (topic) {
|
|
5787
5975
|
case DATA_TOPICS.hand:
|
|
5788
5976
|
store.setHand(
|
|
@@ -5824,7 +6012,8 @@ function createRoomEngine(options) {
|
|
|
5824
6012
|
}
|
|
5825
6013
|
};
|
|
5826
6014
|
async function resolveExitAndApply(hint) {
|
|
5827
|
-
const connection = current?.identity ?? store.snapshot().localIdentity ?? null;
|
|
6015
|
+
const connection = current?.connectionIdentity ?? current?.identity ?? store.snapshot().localIdentity ?? null;
|
|
6016
|
+
const heldPass = current?.meetingPass ?? null;
|
|
5828
6017
|
lastRequest = requestAsNow();
|
|
5829
6018
|
await dropRoom();
|
|
5830
6019
|
let server;
|
|
@@ -5832,7 +6021,7 @@ function createRoomEngine(options) {
|
|
|
5832
6021
|
server = void 0;
|
|
5833
6022
|
} else {
|
|
5834
6023
|
try {
|
|
5835
|
-
server = await options.readExit(connection);
|
|
6024
|
+
server = await options.readExit(connection, heldPass);
|
|
5836
6025
|
} catch {
|
|
5837
6026
|
server = null;
|
|
5838
6027
|
}
|
|
@@ -5862,14 +6051,29 @@ function createRoomEngine(options) {
|
|
|
5862
6051
|
}
|
|
5863
6052
|
async function connect(request) {
|
|
5864
6053
|
store.patch({ failure: null, waiting: null, roomName: request.roomName, share: NO_SHARE, connection: "stable" });
|
|
6054
|
+
const tabId = options.tabClaim !== void 0 ? await options.tabClaim.tabId : null;
|
|
5865
6055
|
const token = await tokens.get({
|
|
5866
6056
|
roomName: request.roomName,
|
|
5867
6057
|
identity,
|
|
5868
6058
|
...request.meetingId !== void 0 ? { meetingId: request.meetingId } : {},
|
|
5869
6059
|
...request.slug !== void 0 ? { slug: request.slug } : {},
|
|
5870
6060
|
...request.breakoutRoomId != null ? { breakoutRoomId: request.breakoutRoomId } : {},
|
|
5871
|
-
...request.observe === true ? { observe: true } : {}
|
|
6061
|
+
...request.observe === true ? { observe: true } : {},
|
|
6062
|
+
...tabId !== null ? { tabId } : {},
|
|
6063
|
+
...request.secondDevice !== void 0 ? { secondDevice: request.secondDevice } : {}
|
|
5872
6064
|
});
|
|
6065
|
+
if (token.secondDeviceChoice != null) {
|
|
6066
|
+
store.patch({ secondDevice: { rule: token.secondDeviceChoice }, localIdentity: token.identity });
|
|
6067
|
+
store.dispatch({ type: "second_device" });
|
|
6068
|
+
report("info", `meet: already in this meeting elsewhere \u2014 offering the choice (${token.secondDeviceChoice})`);
|
|
6069
|
+
return;
|
|
6070
|
+
}
|
|
6071
|
+
if (store.snapshot().secondDevice !== null) store.patch({ secondDevice: null });
|
|
6072
|
+
if (request.secondDevice === "switch_here" && lastRequest !== null) {
|
|
6073
|
+
const { secondDevice: _answered, ...rest } = lastRequest;
|
|
6074
|
+
lastRequest = rest;
|
|
6075
|
+
}
|
|
6076
|
+
options.tabClaim?.inRoom(String(request.roomName), String(token.identity));
|
|
5873
6077
|
current = token;
|
|
5874
6078
|
lastServerUrl = token.serverUrl;
|
|
5875
6079
|
connectionPolicy = token.connectionPolicy ?? DEFAULT_CONNECTION_POLICY;
|
|
@@ -5934,7 +6138,7 @@ function createRoomEngine(options) {
|
|
|
5934
6138
|
if (request.mode === "video") {
|
|
5935
6139
|
await enableTrack(created, "camera", true);
|
|
5936
6140
|
}
|
|
5937
|
-
if (request.microphone !== false) {
|
|
6141
|
+
if (request.microphone !== false && current?.connectionKind !== "companion") {
|
|
5938
6142
|
await enableTrack(created, "microphone", true);
|
|
5939
6143
|
}
|
|
5940
6144
|
media.onConnected();
|
|
@@ -5956,7 +6160,8 @@ function createRoomEngine(options) {
|
|
|
5956
6160
|
}
|
|
5957
6161
|
async function publish(topic, body, to) {
|
|
5958
6162
|
if (driver === null) return;
|
|
5959
|
-
|
|
6163
|
+
const addresses = to === void 0 ? void 0 : to.flatMap((who) => connectionsOf(who));
|
|
6164
|
+
await driver.publishData(topic, JSON.stringify(body), addresses);
|
|
5960
6165
|
}
|
|
5961
6166
|
async function applyEffects(target, effects) {
|
|
5962
6167
|
try {
|
|
@@ -5994,8 +6199,19 @@ function createRoomEngine(options) {
|
|
|
5994
6199
|
liveRoster = list;
|
|
5995
6200
|
const merged = mergeGhosts(list, ghosts, at);
|
|
5996
6201
|
ghosts = merged.kept;
|
|
6202
|
+
const people = /* @__PURE__ */ new Map();
|
|
6203
|
+
const connections = /* @__PURE__ */ new Map();
|
|
6204
|
+
for (const entry of merged.roster) {
|
|
6205
|
+
const person = personOf(entry.identity);
|
|
6206
|
+
connections.set(person, [...connections.get(person) ?? [], entry.identity]);
|
|
6207
|
+
const rank = (entry.isLocal ? 8 : 0) + (entry.reconnecting === true ? 0 : 4) + (entry.attributes[CONNECTION_KIND_ATTRIBUTE] === "companion" ? 0 : 2) + (entry.cameraEnabled || entry.micEnabled ? 1 : 0);
|
|
6208
|
+
const held = people.get(person);
|
|
6209
|
+
if (held === void 0 || rank > held.rank) people.set(person, { entry, rank });
|
|
6210
|
+
}
|
|
6211
|
+
personConnections = connections;
|
|
6212
|
+
mainConnection = new Map([...people].map(([person, { entry }]) => [person, entry.identity]));
|
|
5997
6213
|
store.setParticipants(
|
|
5998
|
-
|
|
6214
|
+
[...people.values()].map(({ entry }) => {
|
|
5999
6215
|
const projected = projectParticipant(entry, joinedAt);
|
|
6000
6216
|
return entry.reconnecting === true ? { ...projected, reconnecting: true, quality: "lost" } : projected;
|
|
6001
6217
|
})
|
|
@@ -6172,7 +6388,13 @@ function createRoomEngine(options) {
|
|
|
6172
6388
|
},
|
|
6173
6389
|
async setScreenShareEnabled(enabled) {
|
|
6174
6390
|
if (driver === null) return;
|
|
6175
|
-
|
|
6391
|
+
let unsupported = false;
|
|
6392
|
+
try {
|
|
6393
|
+
unsupported = enabled && driver.supportsScreenShare?.() === false;
|
|
6394
|
+
} catch {
|
|
6395
|
+
unsupported = false;
|
|
6396
|
+
}
|
|
6397
|
+
if (unsupported) {
|
|
6176
6398
|
store.patch({ share: { state: "refused", reason: "unsupported", remedy: null, at: now() } });
|
|
6177
6399
|
report("warn", "meet: this browser cannot share a screen", "Share from a desktop browser (Chrome, Edge, Firefox or Safari).");
|
|
6178
6400
|
return;
|
|
@@ -6271,7 +6493,7 @@ function createRoomEngine(options) {
|
|
|
6271
6493
|
},
|
|
6272
6494
|
attach(who, kind, element) {
|
|
6273
6495
|
if (driver === null) return () => void 0;
|
|
6274
|
-
return driver.attach(who, kind, element);
|
|
6496
|
+
return driver.attach(mainConnection.get(personOf(who)) ?? who, kind, element);
|
|
6275
6497
|
},
|
|
6276
6498
|
token: () => current,
|
|
6277
6499
|
async dispose() {
|
|
@@ -7005,6 +7227,12 @@ function defaultDiagnostics(event) {
|
|
|
7005
7227
|
else if (event.level === "warn") console.warn(line);
|
|
7006
7228
|
else console.info(line);
|
|
7007
7229
|
}
|
|
7230
|
+
var claimedTab = null;
|
|
7231
|
+
function pageTabClaim() {
|
|
7232
|
+
if (typeof window === "undefined") return void 0;
|
|
7233
|
+
claimedTab ??= createTabClaim();
|
|
7234
|
+
return claimedTab;
|
|
7235
|
+
}
|
|
7008
7236
|
function deviceId() {
|
|
7009
7237
|
const storage = globalThis.localStorage;
|
|
7010
7238
|
const key = "mx.meet.device";
|
|
@@ -7122,13 +7350,15 @@ function MeetRuntime(props) {
|
|
|
7122
7350
|
store,
|
|
7123
7351
|
tokens,
|
|
7124
7352
|
identity,
|
|
7353
|
+
// S7: this page's tab id (one per page, claimed at load) — the LiveKit identity's tab half.
|
|
7354
|
+
tabClaim: pageTabClaim(),
|
|
7125
7355
|
resolveMeeting: (slug) => repository.meetingBySlug(slug),
|
|
7126
7356
|
driverFactory: (handlers) => (driverRef.current ?? createLiveKitDriver)(handlers),
|
|
7127
7357
|
onDiagnostic: report,
|
|
7128
7358
|
effectsStorage,
|
|
7129
7359
|
resolveDevice: (kind) => devicesHolder.current?.resolve(kind) ?? null,
|
|
7130
7360
|
// §3.4: on every LiveKit disconnect, ask the server what happened to this connection.
|
|
7131
|
-
readExit: (connection) => meetings.readExit(connection),
|
|
7361
|
+
readExit: (connection, pass) => meetings.readExit(connection, pass),
|
|
7132
7362
|
// S6b: the connection supervisor's ports — the device's network, whether OUR servers (the
|
|
7133
7363
|
// API and the media server) answer, and the per-tab reload-after-offline marker.
|
|
7134
7364
|
connection: {
|
|
@@ -8197,6 +8427,7 @@ function useRoom() {
|
|
|
8197
8427
|
phase: snapshot?.phase ?? "resolving",
|
|
8198
8428
|
participants: snapshot?.participants ?? [],
|
|
8199
8429
|
failure: snapshot?.failure ?? null,
|
|
8430
|
+
secondDevice: snapshot?.secondDevice ?? null,
|
|
8200
8431
|
local,
|
|
8201
8432
|
canShareScreen: engine?.supportsScreenShare() ?? true,
|
|
8202
8433
|
join: (0, import_react4.useCallback)(
|
|
@@ -8342,6 +8573,7 @@ function useLobby() {
|
|
|
8342
8573
|
const session = host?.meetings ?? null;
|
|
8343
8574
|
return {
|
|
8344
8575
|
waiting: (snapshot?.lobby ?? []).filter((entry) => entry.state === "knocking"),
|
|
8576
|
+
declined: (snapshot?.lobby ?? []).filter((entry) => entry.state === "denied"),
|
|
8345
8577
|
// No runtime means no host powers — `<LobbyPanel>` renders nothing rather
|
|
8346
8578
|
// than an admit button that would throw.
|
|
8347
8579
|
canAdmit: session !== null && isHost,
|
|
@@ -8363,6 +8595,37 @@ function useLobby() {
|
|
|
8363
8595
|
)
|
|
8364
8596
|
};
|
|
8365
8597
|
}
|
|
8598
|
+
function useCountdown(untilMs) {
|
|
8599
|
+
const [now, setNow] = (0, import_react4.useState)(() => Date.now());
|
|
8600
|
+
const active = typeof untilMs === "number" && untilMs > now;
|
|
8601
|
+
(0, import_react4.useEffect)(() => {
|
|
8602
|
+
if (!active) return void 0;
|
|
8603
|
+
setNow(Date.now());
|
|
8604
|
+
const timer = setInterval(() => setNow(Date.now()), 1e3);
|
|
8605
|
+
return () => clearInterval(timer);
|
|
8606
|
+
}, [active, untilMs]);
|
|
8607
|
+
if (typeof untilMs !== "number") return null;
|
|
8608
|
+
return Math.max(0, untilMs - now);
|
|
8609
|
+
}
|
|
8610
|
+
function useKnockNotice() {
|
|
8611
|
+
const lobby = useLobby();
|
|
8612
|
+
const [dismissed, setDismissed] = (0, import_react4.useState)([]);
|
|
8613
|
+
const keyOf = (entry) => `${entry.identity}@${entry.knockedAt}`;
|
|
8614
|
+
const open = lobby.canAdmit ? lobby.waiting.filter((entry) => !dismissed.includes(keyOf(entry))) : [];
|
|
8615
|
+
const knocker = open[0] ?? null;
|
|
8616
|
+
return {
|
|
8617
|
+
knocker,
|
|
8618
|
+
others: Math.max(0, open.length - 1),
|
|
8619
|
+
admit: (0, import_react4.useCallback)(async () => {
|
|
8620
|
+
if (knocker === null) return;
|
|
8621
|
+
setDismissed((list) => [...list, keyOf(knocker)]);
|
|
8622
|
+
await lobby.admit(knocker);
|
|
8623
|
+
}, [knocker, lobby.admit]),
|
|
8624
|
+
dismiss: (0, import_react4.useCallback)(() => {
|
|
8625
|
+
if (knocker !== null) setDismissed((list) => [...list, keyOf(knocker)]);
|
|
8626
|
+
}, [knocker])
|
|
8627
|
+
};
|
|
8628
|
+
}
|
|
8366
8629
|
function useWaiting() {
|
|
8367
8630
|
const host = useMeetHost();
|
|
8368
8631
|
const snapshot = useMeetSnapshot();
|
|
@@ -11112,6 +11375,7 @@ function ControlBar({ onLeave }) {
|
|
|
11112
11375
|
}
|
|
11113
11376
|
) }) }) : null,
|
|
11114
11377
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(BreakoutBanner, {}),
|
|
11378
|
+
panel !== "people" ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(KnockNotice, { onView: () => setPanel("people") }) : null,
|
|
11115
11379
|
controls.notice !== null && panel !== "people" ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Toast, { text: controls.notice.text, failed: controls.notice.failed, onDone: controls.clearNotice }) : null,
|
|
11116
11380
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "mx-meet__controls", children: [
|
|
11117
11381
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
@@ -11442,10 +11706,21 @@ function MediaNotices() {
|
|
|
11442
11706
|
const name = (kind) => kind === "microphone" ? "Microphone" : "Camera";
|
|
11443
11707
|
if (chips.length === 0 && region.audioOutput !== "blocked" && notice === null && offer === null) return null;
|
|
11444
11708
|
return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "mx-meet__media-notices", children: [
|
|
11445
|
-
region.audioOutput === "blocked" ? /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
|
|
11446
|
-
|
|
11447
|
-
|
|
11448
|
-
|
|
11709
|
+
region.audioOutput === "blocked" ? /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
|
|
11710
|
+
import_design_system.ErrorBox,
|
|
11711
|
+
{
|
|
11712
|
+
as: "div",
|
|
11713
|
+
className: "mx-meet__media-pill",
|
|
11714
|
+
"data-meet-notice": "audio-blocked",
|
|
11715
|
+
message: "Sound is paused in this tab",
|
|
11716
|
+
operation: "Enable meeting sound",
|
|
11717
|
+
origin: "meet/MediaNotices",
|
|
11718
|
+
children: [
|
|
11719
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { children: "Sound is paused in this tab" }),
|
|
11720
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { type: "button", className: "mx-meet__primary mx-meet__primary--small", onClick: () => void enableAudio(), children: "Enable sound" })
|
|
11721
|
+
]
|
|
11722
|
+
}
|
|
11723
|
+
) : null,
|
|
11449
11724
|
chips.map(({ kind, reason }) => /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "mx-meet__media-pill", role: "status", "data-meet-notice": deviceNoticeCode(kind, reason), children: [
|
|
11450
11725
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { children: deviceChipLabel(kind, reason) }),
|
|
11451
11726
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
@@ -12245,9 +12520,68 @@ function useMicrophoneStream(deviceId2) {
|
|
|
12245
12520
|
}, [deviceId2]);
|
|
12246
12521
|
return stream;
|
|
12247
12522
|
}
|
|
12523
|
+
function KnockNotice({ onView }) {
|
|
12524
|
+
const { knocker, others, admit, dismiss } = useKnockNotice();
|
|
12525
|
+
if (knocker === null) return null;
|
|
12526
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "mx-meet__notice-bar", role: "status", "data-meet-notice": "knock-received", "data-meet-participant-name": knocker.displayName, children: [
|
|
12527
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { children: [
|
|
12528
|
+
knocker.displayName,
|
|
12529
|
+
" wants to join",
|
|
12530
|
+
others > 0 ? ` (+${others} more)` : ""
|
|
12531
|
+
] }),
|
|
12532
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { type: "button", className: "mx-meet__link", onClick: () => void admit().catch(() => void 0), children: "Admit" }),
|
|
12533
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
12534
|
+
"button",
|
|
12535
|
+
{
|
|
12536
|
+
type: "button",
|
|
12537
|
+
className: "mx-meet__link",
|
|
12538
|
+
onClick: () => {
|
|
12539
|
+
dismiss();
|
|
12540
|
+
onView();
|
|
12541
|
+
},
|
|
12542
|
+
children: "View"
|
|
12543
|
+
}
|
|
12544
|
+
),
|
|
12545
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { type: "button", className: "mx-meet__toast-close", "aria-label": "Dismiss", onClick: dismiss, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(CloseIcon, { width: 14, height: 14 }) })
|
|
12546
|
+
] });
|
|
12547
|
+
}
|
|
12248
12548
|
function LobbyPanel() {
|
|
12249
12549
|
const lobby = useLobby();
|
|
12250
|
-
if (!lobby.canAdmit || lobby.waiting.length === 0) return null;
|
|
12550
|
+
if (!lobby.canAdmit || lobby.waiting.length === 0 && lobby.declined.length === 0) return null;
|
|
12551
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
|
|
12552
|
+
lobby.waiting.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(WaitingList, { lobby }) : null,
|
|
12553
|
+
lobby.declined.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(DeclinedList, { lobby }) : null
|
|
12554
|
+
] });
|
|
12555
|
+
}
|
|
12556
|
+
function DeclinedList({ lobby }) {
|
|
12557
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("section", { className: "mx-meet__lobby", "aria-label": "Declined requests", "data-meet-declined": "", children: [
|
|
12558
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("header", { className: "mx-meet__lobby-head", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("h3", { className: "mx-meet__lobby-title", children: "Declined" }) }),
|
|
12559
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("ul", { className: "mx-meet__lobby-list", children: lobby.declined.map((entry) => /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
|
|
12560
|
+
"li",
|
|
12561
|
+
{
|
|
12562
|
+
className: "mx-meet__lobby-row",
|
|
12563
|
+
"data-meet-declined-entry": "",
|
|
12564
|
+
"data-meet-participant-name": entry.displayName,
|
|
12565
|
+
children: [
|
|
12566
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Avatar, { name: entry.displayName, url: entry.avatarUrl, size: 32 }),
|
|
12567
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "mx-meet__person-name", children: entry.displayName }),
|
|
12568
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
12569
|
+
"button",
|
|
12570
|
+
{
|
|
12571
|
+
type: "button",
|
|
12572
|
+
className: "mx-meet__link",
|
|
12573
|
+
"aria-label": `Let ${entry.displayName} ask again`,
|
|
12574
|
+
onClick: () => void lobby.undeny(entry),
|
|
12575
|
+
children: "Let them ask again"
|
|
12576
|
+
}
|
|
12577
|
+
)
|
|
12578
|
+
]
|
|
12579
|
+
},
|
|
12580
|
+
entry.identity
|
|
12581
|
+
)) })
|
|
12582
|
+
] });
|
|
12583
|
+
}
|
|
12584
|
+
function WaitingList({ lobby }) {
|
|
12251
12585
|
return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("section", { className: "mx-meet__lobby", "aria-label": "Waiting room", children: [
|
|
12252
12586
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("header", { className: "mx-meet__lobby-head", children: [
|
|
12253
12587
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("h3", { className: "mx-meet__lobby-title", children: lobby.waiting.length === 1 ? "1 person is waiting" : `${lobby.waiting.length} people are waiting` }),
|
|
@@ -12511,6 +12845,21 @@ function PreJoinBody({
|
|
|
12511
12845
|
(0, import_react10.useEffect)(() => {
|
|
12512
12846
|
void host.devices.refresh();
|
|
12513
12847
|
}, [host]);
|
|
12848
|
+
const joinWith = (secondDevice) => {
|
|
12849
|
+
setJoining(true);
|
|
12850
|
+
preview.release();
|
|
12851
|
+
void room.join({
|
|
12852
|
+
roomName,
|
|
12853
|
+
// An observer publishes nothing: no camera, no microphone (MD-16). A companion
|
|
12854
|
+
// ("join here too") never opens the microphone (S7).
|
|
12855
|
+
mode: observe2 ? "audio" : mode,
|
|
12856
|
+
microphone: observe2 || secondDevice === "join_too" ? false : microphone,
|
|
12857
|
+
...meetingId !== void 0 ? { meetingId } : {},
|
|
12858
|
+
...slug !== void 0 ? { slug } : {},
|
|
12859
|
+
...observe2 ? { observe: true } : {},
|
|
12860
|
+
...secondDevice !== void 0 ? { secondDevice } : {}
|
|
12861
|
+
}).then(() => onJoined?.()).catch(() => void 0).finally(() => setJoining(false));
|
|
12862
|
+
};
|
|
12514
12863
|
return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "mx-meet__prejoin", children: [
|
|
12515
12864
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "mx-meet__preview", children: [
|
|
12516
12865
|
mode === "video" && preview.preview !== null ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
@@ -12595,27 +12944,34 @@ function PreJoinBody({
|
|
|
12595
12944
|
),
|
|
12596
12945
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_design_system.Label, { htmlFor: "mx-meet-join-mic", children: "Join with microphone" })
|
|
12597
12946
|
] }),
|
|
12598
|
-
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
12599
|
-
|
|
12600
|
-
|
|
12601
|
-
|
|
12602
|
-
className: "mx-
|
|
12603
|
-
|
|
12604
|
-
|
|
12605
|
-
|
|
12606
|
-
|
|
12607
|
-
|
|
12608
|
-
|
|
12609
|
-
|
|
12610
|
-
|
|
12611
|
-
|
|
12612
|
-
|
|
12613
|
-
|
|
12614
|
-
|
|
12615
|
-
|
|
12616
|
-
|
|
12617
|
-
|
|
12618
|
-
|
|
12947
|
+
room.secondDevice === null ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_design_system.Button, { type: "button", className: "mx-meet__join", disabled: joining, onClick: () => joinWith(), children: joining ? "Joining\u2026" : observe2 ? "Observe" : "Join now" }) : (
|
|
12948
|
+
// S7: ALREADY IN THIS MEETING on another tab or device — the meeting's choice
|
|
12949
|
+
// (Meet: Switch here / Join here too; Teams: Transfer here / Add this device).
|
|
12950
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "mx-meet__second-device", "data-meet-region": "second-device", children: [
|
|
12951
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { className: "mx-meet__hint", children: "You're in this meeting on another device" }),
|
|
12952
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
12953
|
+
import_design_system.Button,
|
|
12954
|
+
{
|
|
12955
|
+
type: "button",
|
|
12956
|
+
className: "mx-meet__join",
|
|
12957
|
+
"data-meet-control": "switch-here",
|
|
12958
|
+
disabled: joining,
|
|
12959
|
+
onClick: () => joinWith("switch_here"),
|
|
12960
|
+
children: room.secondDevice.rule === "ask_transfer_or_add" ? "Transfer here" : "Switch here"
|
|
12961
|
+
}
|
|
12962
|
+
),
|
|
12963
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
12964
|
+
import_design_system.Button,
|
|
12965
|
+
{
|
|
12966
|
+
type: "button",
|
|
12967
|
+
variant: "outline",
|
|
12968
|
+
"data-meet-control": "join-too",
|
|
12969
|
+
disabled: joining,
|
|
12970
|
+
onClick: () => joinWith("join_too"),
|
|
12971
|
+
children: room.secondDevice.rule === "ask_transfer_or_add" ? "Add this device" : "Join here too"
|
|
12972
|
+
}
|
|
12973
|
+
)
|
|
12974
|
+
] })
|
|
12619
12975
|
)
|
|
12620
12976
|
] }),
|
|
12621
12977
|
room.failure !== null ? /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("p", { className: "mx-meet__error", children: [
|
|
@@ -12813,16 +13169,21 @@ var REFUSED_TITLES = {
|
|
|
12813
13169
|
not_invited: "You aren't invited to this meeting",
|
|
12814
13170
|
locked: "This meeting is locked",
|
|
12815
13171
|
removed: "You can't rejoin this meeting",
|
|
12816
|
-
lobby_rejects: "
|
|
13172
|
+
lobby_rejects: "The host declined your request",
|
|
12817
13173
|
meeting_full: "This meeting is full",
|
|
12818
13174
|
platform_capacity: "Meetings are busy right now",
|
|
12819
13175
|
verification_required: "Verify your account to join",
|
|
12820
13176
|
registration_required: "Register to join this meeting"
|
|
12821
13177
|
};
|
|
13178
|
+
function clockText(ms2) {
|
|
13179
|
+
const total = Math.ceil(ms2 / 1e3);
|
|
13180
|
+
return `${Math.floor(total / 60)}:${String(total % 60).padStart(2, "0")}`;
|
|
13181
|
+
}
|
|
12822
13182
|
function ExitScreen({ onLeave }) {
|
|
12823
13183
|
const { phase, reason, host, snapshot, actions, call, connection } = useMeetCore();
|
|
12824
13184
|
const [busy, setBusy] = (0, import_react10.useState)(false);
|
|
12825
13185
|
const failure = snapshot?.failure ?? null;
|
|
13186
|
+
const retryLeft = useCountdown(phase === "refused:lobby_rejects" ? failure?.retryAt ?? null : null);
|
|
12826
13187
|
const rejoin = /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
12827
13188
|
import_design_system.Button,
|
|
12828
13189
|
{
|
|
@@ -12865,6 +13226,33 @@ function ExitScreen({ onLeave }) {
|
|
|
12865
13226
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_design_system.Button, { type: "button", variant: "outline", onClick: actions.retry, children: "Try again" }),
|
|
12866
13227
|
home
|
|
12867
13228
|
] });
|
|
13229
|
+
if (code === "lobby_rejects") {
|
|
13230
|
+
if (retryLeft !== null && retryLeft > 0) {
|
|
13231
|
+
line = `You can ask again in ${clockText(retryLeft)}`;
|
|
13232
|
+
notice = "reknock-cooldown";
|
|
13233
|
+
actionsNode = home;
|
|
13234
|
+
} else if (retryLeft === 0) {
|
|
13235
|
+
line = "You can ask to join again";
|
|
13236
|
+
actionsNode = /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
|
|
13237
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
13238
|
+
import_design_system.Button,
|
|
13239
|
+
{
|
|
13240
|
+
type: "button",
|
|
13241
|
+
disabled: busy,
|
|
13242
|
+
onClick: () => {
|
|
13243
|
+
setBusy(true);
|
|
13244
|
+
void actions.rejoin().catch(() => void 0).finally(() => setBusy(false));
|
|
13245
|
+
},
|
|
13246
|
+
children: "Ask to join again"
|
|
13247
|
+
}
|
|
13248
|
+
),
|
|
13249
|
+
home
|
|
13250
|
+
] });
|
|
13251
|
+
} else {
|
|
13252
|
+
line = "Ask the host to let you in, then open the link again";
|
|
13253
|
+
actionsNode = home;
|
|
13254
|
+
}
|
|
13255
|
+
}
|
|
12868
13256
|
} else {
|
|
12869
13257
|
switch (phase) {
|
|
12870
13258
|
case "left":
|
|
@@ -12925,7 +13313,7 @@ function ExitScreen({ onLeave }) {
|
|
|
12925
13313
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Avatar, { name: host?.identity.displayName ?? "", url: host?.identity.avatarUrl ?? null, size: 72 }),
|
|
12926
13314
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("h1", { className: "mx-meet__prejoin-title", children: title }),
|
|
12927
13315
|
line !== null ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { className: "mx-meet__empty-body", children: line }) : null,
|
|
12928
|
-
notice !== null ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { role: "status", "data-meet-notice": notice, className: "mx-meet__sr-only", children: title }) : null,
|
|
13316
|
+
notice !== null ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { role: "status", "data-meet-notice": notice, className: "mx-meet__sr-only", children: notice === "reknock-cooldown" && line !== null ? line : title }) : null,
|
|
12929
13317
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "mx-meet__waiting-actions", children: actionsNode })
|
|
12930
13318
|
] });
|
|
12931
13319
|
}
|
|
@@ -12934,6 +13322,7 @@ function WaitingRoom({ host, onLeave }) {
|
|
|
12934
13322
|
const { phaseNotice } = useMeetCore();
|
|
12935
13323
|
const [busy, setBusy] = (0, import_react10.useState)(false);
|
|
12936
13324
|
const admission = waiting?.admission ?? "knocking";
|
|
13325
|
+
const reknockLeft = useCountdown(admission === "denied" ? waiting?.reknockAt ?? null : null);
|
|
12937
13326
|
const run = (action) => () => {
|
|
12938
13327
|
setBusy(true);
|
|
12939
13328
|
void action().catch(() => void 0).finally(() => setBusy(false));
|
|
@@ -12941,6 +13330,7 @@ function WaitingRoom({ host, onLeave }) {
|
|
|
12941
13330
|
const home = /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_design_system.Button, { type: "button", variant: "outline", onClick: () => onLeave !== void 0 ? onLeave() : window.location.assign("/"), children: "Back to home" });
|
|
12942
13331
|
let title;
|
|
12943
13332
|
let line = null;
|
|
13333
|
+
let cooldownNotice = false;
|
|
12944
13334
|
let actions;
|
|
12945
13335
|
switch (admission) {
|
|
12946
13336
|
case "waiting_host":
|
|
@@ -12949,8 +13339,12 @@ function WaitingRoom({ host, onLeave }) {
|
|
|
12949
13339
|
break;
|
|
12950
13340
|
case "denied":
|
|
12951
13341
|
title = "The host declined your request";
|
|
13342
|
+
if (reknockLeft !== null && reknockLeft > 0) {
|
|
13343
|
+
line = `You can ask again in ${clockText(reknockLeft)}`;
|
|
13344
|
+
cooldownNotice = true;
|
|
13345
|
+
}
|
|
12952
13346
|
actions = /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
|
|
12953
|
-
waiting?.reknockAllowed === true ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_design_system.Button, { type: "button", disabled: busy, onClick: run(askAgain), children: "Ask to join again" }) : null,
|
|
13347
|
+
waiting?.reknockAllowed === true || waiting?.reknockAt != null && reknockLeft === 0 ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_design_system.Button, { type: "button", disabled: busy, onClick: run(askAgain), children: "Ask to join again" }) : null,
|
|
12954
13348
|
home
|
|
12955
13349
|
] });
|
|
12956
13350
|
break;
|
|
@@ -12981,7 +13375,15 @@ function WaitingRoom({ host, onLeave }) {
|
|
|
12981
13375
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Avatar, { name: host.identity.displayName, url: host.identity.avatarUrl, size: 72 }),
|
|
12982
13376
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("h1", { className: "mx-meet__prejoin-title", children: title }),
|
|
12983
13377
|
phaseNotice === "sent-to-waiting" ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { className: "mx-meet__empty-body", role: "status", "data-meet-notice": "sent-to-waiting", children: "The host moved you back to the waiting room" }) : null,
|
|
12984
|
-
line !== null ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
13378
|
+
line !== null ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
13379
|
+
"p",
|
|
13380
|
+
{
|
|
13381
|
+
className: "mx-meet__empty-body",
|
|
13382
|
+
role: "status",
|
|
13383
|
+
"data-meet-notice": cooldownNotice ? "reknock-cooldown" : "host-absent",
|
|
13384
|
+
children: line
|
|
13385
|
+
}
|
|
13386
|
+
) : null,
|
|
12985
13387
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "mx-meet__waiting-actions", children: actions })
|
|
12986
13388
|
] });
|
|
12987
13389
|
}
|