@ai-matrx/meet 0.11.32 → 0.11.33
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 +27 -0
- package/dist/core.cjs +133 -45
- package/dist/core.cjs.map +1 -1
- package/dist/core.d.cts +73 -4
- package/dist/core.d.ts +73 -4
- package/dist/core.js +133 -45
- package/dist/core.js.map +1 -1
- package/dist/index.cjs +133 -45
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +133 -45
- package/dist/index.js.map +1 -1
- package/dist/react.cjs +476 -75
- 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 +476 -75
- 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/package.json +3 -3
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");
|
|
@@ -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,7 @@ 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;
|
|
5828
6016
|
lastRequest = requestAsNow();
|
|
5829
6017
|
await dropRoom();
|
|
5830
6018
|
let server;
|
|
@@ -5862,14 +6050,29 @@ function createRoomEngine(options) {
|
|
|
5862
6050
|
}
|
|
5863
6051
|
async function connect(request) {
|
|
5864
6052
|
store.patch({ failure: null, waiting: null, roomName: request.roomName, share: NO_SHARE, connection: "stable" });
|
|
6053
|
+
const tabId = options.tabClaim !== void 0 ? await options.tabClaim.tabId : null;
|
|
5865
6054
|
const token = await tokens.get({
|
|
5866
6055
|
roomName: request.roomName,
|
|
5867
6056
|
identity,
|
|
5868
6057
|
...request.meetingId !== void 0 ? { meetingId: request.meetingId } : {},
|
|
5869
6058
|
...request.slug !== void 0 ? { slug: request.slug } : {},
|
|
5870
6059
|
...request.breakoutRoomId != null ? { breakoutRoomId: request.breakoutRoomId } : {},
|
|
5871
|
-
...request.observe === true ? { observe: true } : {}
|
|
6060
|
+
...request.observe === true ? { observe: true } : {},
|
|
6061
|
+
...tabId !== null ? { tabId } : {},
|
|
6062
|
+
...request.secondDevice !== void 0 ? { secondDevice: request.secondDevice } : {}
|
|
5872
6063
|
});
|
|
6064
|
+
if (token.secondDeviceChoice != null) {
|
|
6065
|
+
store.patch({ secondDevice: { rule: token.secondDeviceChoice }, localIdentity: token.identity });
|
|
6066
|
+
store.dispatch({ type: "second_device" });
|
|
6067
|
+
report("info", `meet: already in this meeting elsewhere \u2014 offering the choice (${token.secondDeviceChoice})`);
|
|
6068
|
+
return;
|
|
6069
|
+
}
|
|
6070
|
+
if (store.snapshot().secondDevice !== null) store.patch({ secondDevice: null });
|
|
6071
|
+
if (request.secondDevice === "switch_here" && lastRequest !== null) {
|
|
6072
|
+
const { secondDevice: _answered, ...rest } = lastRequest;
|
|
6073
|
+
lastRequest = rest;
|
|
6074
|
+
}
|
|
6075
|
+
options.tabClaim?.inRoom(String(request.roomName), String(token.identity));
|
|
5873
6076
|
current = token;
|
|
5874
6077
|
lastServerUrl = token.serverUrl;
|
|
5875
6078
|
connectionPolicy = token.connectionPolicy ?? DEFAULT_CONNECTION_POLICY;
|
|
@@ -5934,7 +6137,7 @@ function createRoomEngine(options) {
|
|
|
5934
6137
|
if (request.mode === "video") {
|
|
5935
6138
|
await enableTrack(created, "camera", true);
|
|
5936
6139
|
}
|
|
5937
|
-
if (request.microphone !== false) {
|
|
6140
|
+
if (request.microphone !== false && current?.connectionKind !== "companion") {
|
|
5938
6141
|
await enableTrack(created, "microphone", true);
|
|
5939
6142
|
}
|
|
5940
6143
|
media.onConnected();
|
|
@@ -5956,7 +6159,8 @@ function createRoomEngine(options) {
|
|
|
5956
6159
|
}
|
|
5957
6160
|
async function publish(topic, body, to) {
|
|
5958
6161
|
if (driver === null) return;
|
|
5959
|
-
|
|
6162
|
+
const addresses = to === void 0 ? void 0 : to.flatMap((who) => connectionsOf(who));
|
|
6163
|
+
await driver.publishData(topic, JSON.stringify(body), addresses);
|
|
5960
6164
|
}
|
|
5961
6165
|
async function applyEffects(target, effects) {
|
|
5962
6166
|
try {
|
|
@@ -5994,8 +6198,19 @@ function createRoomEngine(options) {
|
|
|
5994
6198
|
liveRoster = list;
|
|
5995
6199
|
const merged = mergeGhosts(list, ghosts, at);
|
|
5996
6200
|
ghosts = merged.kept;
|
|
6201
|
+
const people = /* @__PURE__ */ new Map();
|
|
6202
|
+
const connections = /* @__PURE__ */ new Map();
|
|
6203
|
+
for (const entry of merged.roster) {
|
|
6204
|
+
const person = personOf(entry.identity);
|
|
6205
|
+
connections.set(person, [...connections.get(person) ?? [], entry.identity]);
|
|
6206
|
+
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);
|
|
6207
|
+
const held = people.get(person);
|
|
6208
|
+
if (held === void 0 || rank > held.rank) people.set(person, { entry, rank });
|
|
6209
|
+
}
|
|
6210
|
+
personConnections = connections;
|
|
6211
|
+
mainConnection = new Map([...people].map(([person, { entry }]) => [person, entry.identity]));
|
|
5997
6212
|
store.setParticipants(
|
|
5998
|
-
|
|
6213
|
+
[...people.values()].map(({ entry }) => {
|
|
5999
6214
|
const projected = projectParticipant(entry, joinedAt);
|
|
6000
6215
|
return entry.reconnecting === true ? { ...projected, reconnecting: true, quality: "lost" } : projected;
|
|
6001
6216
|
})
|
|
@@ -6172,7 +6387,13 @@ function createRoomEngine(options) {
|
|
|
6172
6387
|
},
|
|
6173
6388
|
async setScreenShareEnabled(enabled) {
|
|
6174
6389
|
if (driver === null) return;
|
|
6175
|
-
|
|
6390
|
+
let unsupported = false;
|
|
6391
|
+
try {
|
|
6392
|
+
unsupported = enabled && driver.supportsScreenShare?.() === false;
|
|
6393
|
+
} catch {
|
|
6394
|
+
unsupported = false;
|
|
6395
|
+
}
|
|
6396
|
+
if (unsupported) {
|
|
6176
6397
|
store.patch({ share: { state: "refused", reason: "unsupported", remedy: null, at: now() } });
|
|
6177
6398
|
report("warn", "meet: this browser cannot share a screen", "Share from a desktop browser (Chrome, Edge, Firefox or Safari).");
|
|
6178
6399
|
return;
|
|
@@ -6271,7 +6492,7 @@ function createRoomEngine(options) {
|
|
|
6271
6492
|
},
|
|
6272
6493
|
attach(who, kind, element) {
|
|
6273
6494
|
if (driver === null) return () => void 0;
|
|
6274
|
-
return driver.attach(who, kind, element);
|
|
6495
|
+
return driver.attach(mainConnection.get(personOf(who)) ?? who, kind, element);
|
|
6275
6496
|
},
|
|
6276
6497
|
token: () => current,
|
|
6277
6498
|
async dispose() {
|
|
@@ -7005,6 +7226,12 @@ function defaultDiagnostics(event) {
|
|
|
7005
7226
|
else if (event.level === "warn") console.warn(line);
|
|
7006
7227
|
else console.info(line);
|
|
7007
7228
|
}
|
|
7229
|
+
var claimedTab = null;
|
|
7230
|
+
function pageTabClaim() {
|
|
7231
|
+
if (typeof window === "undefined") return void 0;
|
|
7232
|
+
claimedTab ??= createTabClaim();
|
|
7233
|
+
return claimedTab;
|
|
7234
|
+
}
|
|
7008
7235
|
function deviceId() {
|
|
7009
7236
|
const storage = globalThis.localStorage;
|
|
7010
7237
|
const key = "mx.meet.device";
|
|
@@ -7122,6 +7349,8 @@ function MeetRuntime(props) {
|
|
|
7122
7349
|
store,
|
|
7123
7350
|
tokens,
|
|
7124
7351
|
identity,
|
|
7352
|
+
// S7: this page's tab id (one per page, claimed at load) — the LiveKit identity's tab half.
|
|
7353
|
+
tabClaim: pageTabClaim(),
|
|
7125
7354
|
resolveMeeting: (slug) => repository.meetingBySlug(slug),
|
|
7126
7355
|
driverFactory: (handlers) => (driverRef.current ?? createLiveKitDriver)(handlers),
|
|
7127
7356
|
onDiagnostic: report,
|
|
@@ -8197,6 +8426,7 @@ function useRoom() {
|
|
|
8197
8426
|
phase: snapshot?.phase ?? "resolving",
|
|
8198
8427
|
participants: snapshot?.participants ?? [],
|
|
8199
8428
|
failure: snapshot?.failure ?? null,
|
|
8429
|
+
secondDevice: snapshot?.secondDevice ?? null,
|
|
8200
8430
|
local,
|
|
8201
8431
|
canShareScreen: engine?.supportsScreenShare() ?? true,
|
|
8202
8432
|
join: (0, import_react4.useCallback)(
|
|
@@ -8342,6 +8572,7 @@ function useLobby() {
|
|
|
8342
8572
|
const session = host?.meetings ?? null;
|
|
8343
8573
|
return {
|
|
8344
8574
|
waiting: (snapshot?.lobby ?? []).filter((entry) => entry.state === "knocking"),
|
|
8575
|
+
declined: (snapshot?.lobby ?? []).filter((entry) => entry.state === "denied"),
|
|
8345
8576
|
// No runtime means no host powers — `<LobbyPanel>` renders nothing rather
|
|
8346
8577
|
// than an admit button that would throw.
|
|
8347
8578
|
canAdmit: session !== null && isHost,
|
|
@@ -8363,6 +8594,37 @@ function useLobby() {
|
|
|
8363
8594
|
)
|
|
8364
8595
|
};
|
|
8365
8596
|
}
|
|
8597
|
+
function useCountdown(untilMs) {
|
|
8598
|
+
const [now, setNow] = (0, import_react4.useState)(() => Date.now());
|
|
8599
|
+
const active = typeof untilMs === "number" && untilMs > now;
|
|
8600
|
+
(0, import_react4.useEffect)(() => {
|
|
8601
|
+
if (!active) return void 0;
|
|
8602
|
+
setNow(Date.now());
|
|
8603
|
+
const timer = setInterval(() => setNow(Date.now()), 1e3);
|
|
8604
|
+
return () => clearInterval(timer);
|
|
8605
|
+
}, [active, untilMs]);
|
|
8606
|
+
if (typeof untilMs !== "number") return null;
|
|
8607
|
+
return Math.max(0, untilMs - now);
|
|
8608
|
+
}
|
|
8609
|
+
function useKnockNotice() {
|
|
8610
|
+
const lobby = useLobby();
|
|
8611
|
+
const [dismissed, setDismissed] = (0, import_react4.useState)([]);
|
|
8612
|
+
const keyOf = (entry) => `${entry.identity}@${entry.knockedAt}`;
|
|
8613
|
+
const open = lobby.canAdmit ? lobby.waiting.filter((entry) => !dismissed.includes(keyOf(entry))) : [];
|
|
8614
|
+
const knocker = open[0] ?? null;
|
|
8615
|
+
return {
|
|
8616
|
+
knocker,
|
|
8617
|
+
others: Math.max(0, open.length - 1),
|
|
8618
|
+
admit: (0, import_react4.useCallback)(async () => {
|
|
8619
|
+
if (knocker === null) return;
|
|
8620
|
+
setDismissed((list) => [...list, keyOf(knocker)]);
|
|
8621
|
+
await lobby.admit(knocker);
|
|
8622
|
+
}, [knocker, lobby.admit]),
|
|
8623
|
+
dismiss: (0, import_react4.useCallback)(() => {
|
|
8624
|
+
if (knocker !== null) setDismissed((list) => [...list, keyOf(knocker)]);
|
|
8625
|
+
}, [knocker])
|
|
8626
|
+
};
|
|
8627
|
+
}
|
|
8366
8628
|
function useWaiting() {
|
|
8367
8629
|
const host = useMeetHost();
|
|
8368
8630
|
const snapshot = useMeetSnapshot();
|
|
@@ -11112,6 +11374,7 @@ function ControlBar({ onLeave }) {
|
|
|
11112
11374
|
}
|
|
11113
11375
|
) }) }) : null,
|
|
11114
11376
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(BreakoutBanner, {}),
|
|
11377
|
+
panel !== "people" ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(KnockNotice, { onView: () => setPanel("people") }) : null,
|
|
11115
11378
|
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
11379
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "mx-meet__controls", children: [
|
|
11117
11380
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
@@ -11442,10 +11705,21 @@ function MediaNotices() {
|
|
|
11442
11705
|
const name = (kind) => kind === "microphone" ? "Microphone" : "Camera";
|
|
11443
11706
|
if (chips.length === 0 && region.audioOutput !== "blocked" && notice === null && offer === null) return null;
|
|
11444
11707
|
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
|
-
|
|
11708
|
+
region.audioOutput === "blocked" ? /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
|
|
11709
|
+
import_design_system.ErrorBox,
|
|
11710
|
+
{
|
|
11711
|
+
as: "div",
|
|
11712
|
+
className: "mx-meet__media-pill",
|
|
11713
|
+
"data-meet-notice": "audio-blocked",
|
|
11714
|
+
message: "Sound is paused in this tab",
|
|
11715
|
+
operation: "Enable meeting sound",
|
|
11716
|
+
origin: "meet/MediaNotices",
|
|
11717
|
+
children: [
|
|
11718
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { children: "Sound is paused in this tab" }),
|
|
11719
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { type: "button", className: "mx-meet__primary mx-meet__primary--small", onClick: () => void enableAudio(), children: "Enable sound" })
|
|
11720
|
+
]
|
|
11721
|
+
}
|
|
11722
|
+
) : null,
|
|
11449
11723
|
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
11724
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { children: deviceChipLabel(kind, reason) }),
|
|
11451
11725
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
@@ -12245,9 +12519,68 @@ function useMicrophoneStream(deviceId2) {
|
|
|
12245
12519
|
}, [deviceId2]);
|
|
12246
12520
|
return stream;
|
|
12247
12521
|
}
|
|
12522
|
+
function KnockNotice({ onView }) {
|
|
12523
|
+
const { knocker, others, admit, dismiss } = useKnockNotice();
|
|
12524
|
+
if (knocker === null) return null;
|
|
12525
|
+
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: [
|
|
12526
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { children: [
|
|
12527
|
+
knocker.displayName,
|
|
12528
|
+
" wants to join",
|
|
12529
|
+
others > 0 ? ` (+${others} more)` : ""
|
|
12530
|
+
] }),
|
|
12531
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { type: "button", className: "mx-meet__link", onClick: () => void admit().catch(() => void 0), children: "Admit" }),
|
|
12532
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
12533
|
+
"button",
|
|
12534
|
+
{
|
|
12535
|
+
type: "button",
|
|
12536
|
+
className: "mx-meet__link",
|
|
12537
|
+
onClick: () => {
|
|
12538
|
+
dismiss();
|
|
12539
|
+
onView();
|
|
12540
|
+
},
|
|
12541
|
+
children: "View"
|
|
12542
|
+
}
|
|
12543
|
+
),
|
|
12544
|
+
/* @__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 }) })
|
|
12545
|
+
] });
|
|
12546
|
+
}
|
|
12248
12547
|
function LobbyPanel() {
|
|
12249
12548
|
const lobby = useLobby();
|
|
12250
|
-
if (!lobby.canAdmit || lobby.waiting.length === 0) return null;
|
|
12549
|
+
if (!lobby.canAdmit || lobby.waiting.length === 0 && lobby.declined.length === 0) return null;
|
|
12550
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
|
|
12551
|
+
lobby.waiting.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(WaitingList, { lobby }) : null,
|
|
12552
|
+
lobby.declined.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(DeclinedList, { lobby }) : null
|
|
12553
|
+
] });
|
|
12554
|
+
}
|
|
12555
|
+
function DeclinedList({ lobby }) {
|
|
12556
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("section", { className: "mx-meet__lobby", "aria-label": "Declined requests", "data-meet-declined": "", children: [
|
|
12557
|
+
/* @__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" }) }),
|
|
12558
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("ul", { className: "mx-meet__lobby-list", children: lobby.declined.map((entry) => /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
|
|
12559
|
+
"li",
|
|
12560
|
+
{
|
|
12561
|
+
className: "mx-meet__lobby-row",
|
|
12562
|
+
"data-meet-declined-entry": "",
|
|
12563
|
+
"data-meet-participant-name": entry.displayName,
|
|
12564
|
+
children: [
|
|
12565
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Avatar, { name: entry.displayName, url: entry.avatarUrl, size: 32 }),
|
|
12566
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "mx-meet__person-name", children: entry.displayName }),
|
|
12567
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
12568
|
+
"button",
|
|
12569
|
+
{
|
|
12570
|
+
type: "button",
|
|
12571
|
+
className: "mx-meet__link",
|
|
12572
|
+
"aria-label": `Let ${entry.displayName} ask again`,
|
|
12573
|
+
onClick: () => void lobby.undeny(entry),
|
|
12574
|
+
children: "Let them ask again"
|
|
12575
|
+
}
|
|
12576
|
+
)
|
|
12577
|
+
]
|
|
12578
|
+
},
|
|
12579
|
+
entry.identity
|
|
12580
|
+
)) })
|
|
12581
|
+
] });
|
|
12582
|
+
}
|
|
12583
|
+
function WaitingList({ lobby }) {
|
|
12251
12584
|
return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("section", { className: "mx-meet__lobby", "aria-label": "Waiting room", children: [
|
|
12252
12585
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("header", { className: "mx-meet__lobby-head", children: [
|
|
12253
12586
|
/* @__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 +12844,21 @@ function PreJoinBody({
|
|
|
12511
12844
|
(0, import_react10.useEffect)(() => {
|
|
12512
12845
|
void host.devices.refresh();
|
|
12513
12846
|
}, [host]);
|
|
12847
|
+
const joinWith = (secondDevice) => {
|
|
12848
|
+
setJoining(true);
|
|
12849
|
+
preview.release();
|
|
12850
|
+
void room.join({
|
|
12851
|
+
roomName,
|
|
12852
|
+
// An observer publishes nothing: no camera, no microphone (MD-16). A companion
|
|
12853
|
+
// ("join here too") never opens the microphone (S7).
|
|
12854
|
+
mode: observe2 ? "audio" : mode,
|
|
12855
|
+
microphone: observe2 || secondDevice === "join_too" ? false : microphone,
|
|
12856
|
+
...meetingId !== void 0 ? { meetingId } : {},
|
|
12857
|
+
...slug !== void 0 ? { slug } : {},
|
|
12858
|
+
...observe2 ? { observe: true } : {},
|
|
12859
|
+
...secondDevice !== void 0 ? { secondDevice } : {}
|
|
12860
|
+
}).then(() => onJoined?.()).catch(() => void 0).finally(() => setJoining(false));
|
|
12861
|
+
};
|
|
12514
12862
|
return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "mx-meet__prejoin", children: [
|
|
12515
12863
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "mx-meet__preview", children: [
|
|
12516
12864
|
mode === "video" && preview.preview !== null ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
@@ -12595,27 +12943,34 @@ function PreJoinBody({
|
|
|
12595
12943
|
),
|
|
12596
12944
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_design_system.Label, { htmlFor: "mx-meet-join-mic", children: "Join with microphone" })
|
|
12597
12945
|
] }),
|
|
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
|
-
|
|
12946
|
+
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" }) : (
|
|
12947
|
+
// S7: ALREADY IN THIS MEETING on another tab or device — the meeting's choice
|
|
12948
|
+
// (Meet: Switch here / Join here too; Teams: Transfer here / Add this device).
|
|
12949
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "mx-meet__second-device", "data-meet-region": "second-device", children: [
|
|
12950
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { className: "mx-meet__hint", children: "You're in this meeting on another device" }),
|
|
12951
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
12952
|
+
import_design_system.Button,
|
|
12953
|
+
{
|
|
12954
|
+
type: "button",
|
|
12955
|
+
className: "mx-meet__join",
|
|
12956
|
+
"data-meet-control": "switch-here",
|
|
12957
|
+
disabled: joining,
|
|
12958
|
+
onClick: () => joinWith("switch_here"),
|
|
12959
|
+
children: room.secondDevice.rule === "ask_transfer_or_add" ? "Transfer here" : "Switch here"
|
|
12960
|
+
}
|
|
12961
|
+
),
|
|
12962
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
12963
|
+
import_design_system.Button,
|
|
12964
|
+
{
|
|
12965
|
+
type: "button",
|
|
12966
|
+
variant: "outline",
|
|
12967
|
+
"data-meet-control": "join-too",
|
|
12968
|
+
disabled: joining,
|
|
12969
|
+
onClick: () => joinWith("join_too"),
|
|
12970
|
+
children: room.secondDevice.rule === "ask_transfer_or_add" ? "Add this device" : "Join here too"
|
|
12971
|
+
}
|
|
12972
|
+
)
|
|
12973
|
+
] })
|
|
12619
12974
|
)
|
|
12620
12975
|
] }),
|
|
12621
12976
|
room.failure !== null ? /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("p", { className: "mx-meet__error", children: [
|
|
@@ -12813,16 +13168,21 @@ var REFUSED_TITLES = {
|
|
|
12813
13168
|
not_invited: "You aren't invited to this meeting",
|
|
12814
13169
|
locked: "This meeting is locked",
|
|
12815
13170
|
removed: "You can't rejoin this meeting",
|
|
12816
|
-
lobby_rejects: "
|
|
13171
|
+
lobby_rejects: "The host declined your request",
|
|
12817
13172
|
meeting_full: "This meeting is full",
|
|
12818
13173
|
platform_capacity: "Meetings are busy right now",
|
|
12819
13174
|
verification_required: "Verify your account to join",
|
|
12820
13175
|
registration_required: "Register to join this meeting"
|
|
12821
13176
|
};
|
|
13177
|
+
function clockText(ms2) {
|
|
13178
|
+
const total = Math.ceil(ms2 / 1e3);
|
|
13179
|
+
return `${Math.floor(total / 60)}:${String(total % 60).padStart(2, "0")}`;
|
|
13180
|
+
}
|
|
12822
13181
|
function ExitScreen({ onLeave }) {
|
|
12823
13182
|
const { phase, reason, host, snapshot, actions, call, connection } = useMeetCore();
|
|
12824
13183
|
const [busy, setBusy] = (0, import_react10.useState)(false);
|
|
12825
13184
|
const failure = snapshot?.failure ?? null;
|
|
13185
|
+
const retryLeft = useCountdown(phase === "refused:lobby_rejects" ? failure?.retryAt ?? null : null);
|
|
12826
13186
|
const rejoin = /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
12827
13187
|
import_design_system.Button,
|
|
12828
13188
|
{
|
|
@@ -12865,6 +13225,33 @@ function ExitScreen({ onLeave }) {
|
|
|
12865
13225
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_design_system.Button, { type: "button", variant: "outline", onClick: actions.retry, children: "Try again" }),
|
|
12866
13226
|
home
|
|
12867
13227
|
] });
|
|
13228
|
+
if (code === "lobby_rejects") {
|
|
13229
|
+
if (retryLeft !== null && retryLeft > 0) {
|
|
13230
|
+
line = `You can ask again in ${clockText(retryLeft)}`;
|
|
13231
|
+
notice = "reknock-cooldown";
|
|
13232
|
+
actionsNode = home;
|
|
13233
|
+
} else if (retryLeft === 0) {
|
|
13234
|
+
line = "You can ask to join again";
|
|
13235
|
+
actionsNode = /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
|
|
13236
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
13237
|
+
import_design_system.Button,
|
|
13238
|
+
{
|
|
13239
|
+
type: "button",
|
|
13240
|
+
disabled: busy,
|
|
13241
|
+
onClick: () => {
|
|
13242
|
+
setBusy(true);
|
|
13243
|
+
void actions.rejoin().catch(() => void 0).finally(() => setBusy(false));
|
|
13244
|
+
},
|
|
13245
|
+
children: "Ask to join again"
|
|
13246
|
+
}
|
|
13247
|
+
),
|
|
13248
|
+
home
|
|
13249
|
+
] });
|
|
13250
|
+
} else {
|
|
13251
|
+
line = "Ask the host to let you in, then open the link again";
|
|
13252
|
+
actionsNode = home;
|
|
13253
|
+
}
|
|
13254
|
+
}
|
|
12868
13255
|
} else {
|
|
12869
13256
|
switch (phase) {
|
|
12870
13257
|
case "left":
|
|
@@ -12925,7 +13312,7 @@ function ExitScreen({ onLeave }) {
|
|
|
12925
13312
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Avatar, { name: host?.identity.displayName ?? "", url: host?.identity.avatarUrl ?? null, size: 72 }),
|
|
12926
13313
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("h1", { className: "mx-meet__prejoin-title", children: title }),
|
|
12927
13314
|
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,
|
|
13315
|
+
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
13316
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "mx-meet__waiting-actions", children: actionsNode })
|
|
12930
13317
|
] });
|
|
12931
13318
|
}
|
|
@@ -12934,6 +13321,7 @@ function WaitingRoom({ host, onLeave }) {
|
|
|
12934
13321
|
const { phaseNotice } = useMeetCore();
|
|
12935
13322
|
const [busy, setBusy] = (0, import_react10.useState)(false);
|
|
12936
13323
|
const admission = waiting?.admission ?? "knocking";
|
|
13324
|
+
const reknockLeft = useCountdown(admission === "denied" ? waiting?.reknockAt ?? null : null);
|
|
12937
13325
|
const run = (action) => () => {
|
|
12938
13326
|
setBusy(true);
|
|
12939
13327
|
void action().catch(() => void 0).finally(() => setBusy(false));
|
|
@@ -12941,6 +13329,7 @@ function WaitingRoom({ host, onLeave }) {
|
|
|
12941
13329
|
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
13330
|
let title;
|
|
12943
13331
|
let line = null;
|
|
13332
|
+
let cooldownNotice = false;
|
|
12944
13333
|
let actions;
|
|
12945
13334
|
switch (admission) {
|
|
12946
13335
|
case "waiting_host":
|
|
@@ -12949,8 +13338,12 @@ function WaitingRoom({ host, onLeave }) {
|
|
|
12949
13338
|
break;
|
|
12950
13339
|
case "denied":
|
|
12951
13340
|
title = "The host declined your request";
|
|
13341
|
+
if (reknockLeft !== null && reknockLeft > 0) {
|
|
13342
|
+
line = `You can ask again in ${clockText(reknockLeft)}`;
|
|
13343
|
+
cooldownNotice = true;
|
|
13344
|
+
}
|
|
12952
13345
|
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,
|
|
13346
|
+
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
13347
|
home
|
|
12955
13348
|
] });
|
|
12956
13349
|
break;
|
|
@@ -12981,7 +13374,15 @@ function WaitingRoom({ host, onLeave }) {
|
|
|
12981
13374
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Avatar, { name: host.identity.displayName, url: host.identity.avatarUrl, size: 72 }),
|
|
12982
13375
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("h1", { className: "mx-meet__prejoin-title", children: title }),
|
|
12983
13376
|
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)(
|
|
13377
|
+
line !== null ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
13378
|
+
"p",
|
|
13379
|
+
{
|
|
13380
|
+
className: "mx-meet__empty-body",
|
|
13381
|
+
role: "status",
|
|
13382
|
+
"data-meet-notice": cooldownNotice ? "reknock-cooldown" : "host-absent",
|
|
13383
|
+
children: line
|
|
13384
|
+
}
|
|
13385
|
+
) : null,
|
|
12985
13386
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "mx-meet__waiting-actions", children: actions })
|
|
12986
13387
|
] });
|
|
12987
13388
|
}
|