@ai-matrx/meet 0.8.1 → 0.10.0
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 +40 -0
- package/dist/{collab-Bss-_Jfp.d.cts → collab-C2R-kQnV.d.cts} +24 -13
- package/dist/{collab-Bss-_Jfp.d.ts → collab-C2R-kQnV.d.ts} +24 -13
- package/dist/{components-BmgstFx_.d.ts → components-CMtSekZS.d.ts} +1 -1
- package/dist/{components-BOUmOiPj.d.cts → components-DKHfkEVB.d.cts} +1 -1
- package/dist/core.cjs +452 -154
- package/dist/core.cjs.map +1 -1
- package/dist/core.d.cts +132 -41
- package/dist/core.d.ts +132 -41
- package/dist/core.js +452 -154
- package/dist/core.js.map +1 -1
- package/dist/index.cjs +452 -154
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +452 -154
- package/dist/index.js.map +1 -1
- package/dist/react.cjs +550 -184
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +6 -5
- package/dist/react.d.ts +6 -5
- package/dist/react.js +550 -184
- package/dist/react.js.map +1 -1
- package/dist/skins/meet.cjs +141 -34
- package/dist/skins/meet.cjs.map +1 -1
- package/dist/skins/meet.d.cts +2 -2
- package/dist/skins/meet.d.ts +2 -2
- package/dist/skins/meet.js +141 -34
- package/dist/skins/meet.js.map +1 -1
- package/dist/styles.css +8 -0
- package/package.json +5 -5
package/dist/react.js
CHANGED
|
@@ -28,11 +28,18 @@ var MeetError = class extends Error {
|
|
|
28
28
|
/** What to DO. Rendered to the user for the device codes. */
|
|
29
29
|
remedy;
|
|
30
30
|
cause;
|
|
31
|
-
|
|
31
|
+
/**
|
|
32
|
+
* The join gate's reason code (CORE-DESIGN §2.5) when the server named one: `not_found`,
|
|
33
|
+
* `cancelled`, `ended`, `locked`, `removed`, `lobby_rejects`, `not_invited`… The phase
|
|
34
|
+
* renders `refused:<reason>` (or `ended`) from it — never from the HTTP status.
|
|
35
|
+
*/
|
|
36
|
+
reason;
|
|
37
|
+
constructor(code, message, remedy, cause, reason) {
|
|
32
38
|
super(message);
|
|
33
39
|
this.name = "MeetError";
|
|
34
40
|
this.code = code;
|
|
35
41
|
this.remedy = remedy;
|
|
42
|
+
this.reason = reason ?? null;
|
|
36
43
|
if (cause !== void 0) this.cause = cause;
|
|
37
44
|
}
|
|
38
45
|
/** True when retrying after the host re-establishes a session is the fix. */
|
|
@@ -250,6 +257,17 @@ function createWebRoomTokenStorage(onDiagnostic) {
|
|
|
250
257
|
}
|
|
251
258
|
};
|
|
252
259
|
}
|
|
260
|
+
function rememberedMeetingPass(roomName, storage = createWebRoomTokenStorage()) {
|
|
261
|
+
const raw = storage.read(roomName);
|
|
262
|
+
if (raw === null) return null;
|
|
263
|
+
try {
|
|
264
|
+
const parsed = JSON.parse(raw);
|
|
265
|
+
if (parsed.standing !== "guest") return null;
|
|
266
|
+
return typeof parsed.meetingPass === "string" && parsed.meetingPass.length > 0 ? parsed.meetingPass : null;
|
|
267
|
+
} catch {
|
|
268
|
+
return null;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
253
271
|
var MEET_ROUTES = {
|
|
254
272
|
token: "/api/v1/meet/token",
|
|
255
273
|
recordingStart: "/api/v1/meet/recording/start",
|
|
@@ -263,7 +281,7 @@ var MEET_ROUTES = {
|
|
|
263
281
|
/**
|
|
264
282
|
* THE POST-MEETING RECORD FOR A READER WITH NO DATABASE (MRF-1). A `GET`,
|
|
265
283
|
* authorized by the room token this device already holds, carried in the
|
|
266
|
-
* `x-meet-
|
|
284
|
+
* `x-meet-pass` header — never a query parameter, because a bearer
|
|
267
285
|
* credential in a URL lands in access logs, referrers and history.
|
|
268
286
|
*/
|
|
269
287
|
record: "/api/v1/meet/record",
|
|
@@ -271,7 +289,7 @@ var MEET_ROUTES = {
|
|
|
271
289
|
recordAccess: "/api/v1/meet/record/access",
|
|
272
290
|
/**
|
|
273
291
|
* KEEP YOUR NOTES (2026-10-01): a guest who just created an account presents
|
|
274
|
-
* their kept pass in `x-meet-
|
|
292
|
+
* their kept pass in `x-meet-pass` with their new session, and the
|
|
275
293
|
* meeting they attended becomes theirs to read. Idempotent; never a grant.
|
|
276
294
|
*/
|
|
277
295
|
claim: "/api/v1/meet/claim",
|
|
@@ -293,7 +311,7 @@ var MEET_ROUTES = {
|
|
|
293
311
|
* Meet wave 2: EVERY in-meeting host power (mute, stop video, mute all, ask to
|
|
294
312
|
* unmute, spotlight, lower hands, co-host, hand over, rename, remove, report).
|
|
295
313
|
* Decided by the server from the meeting itself; a guest's pass rides
|
|
296
|
-
* `x-meet-
|
|
314
|
+
* `x-meet-pass`.
|
|
297
315
|
*/
|
|
298
316
|
moderate: "/api/v1/meet/moderate",
|
|
299
317
|
/** Meet wave 2: the SAVED chat — `POST` sends to everyone, `GET ?meeting_id=` reads history. */
|
|
@@ -304,12 +322,26 @@ var MEET_ROUTES = {
|
|
|
304
322
|
*/
|
|
305
323
|
collab: "/api/v1/meet/collab",
|
|
306
324
|
/** Meet wave 5: translate finished caption lines into this viewer's language. */
|
|
307
|
-
captionsTranslate: "/api/v1/meet/captions/translate"
|
|
325
|
+
captionsTranslate: "/api/v1/meet/captions/translate",
|
|
326
|
+
/**
|
|
327
|
+
* S4 — THE STATE READ (CORE-DESIGN §2.6): `GET ?meeting_id=` with `x-meet-pass`. This
|
|
328
|
+
* person's admission (lazy expiry applied), whether anyone who can admit is here, the run's
|
|
329
|
+
* phase. Read every `waiting_poll_seconds` while waiting and on every `mx.state.changed`.
|
|
330
|
+
*/
|
|
331
|
+
state: "/api/v1/meet/state",
|
|
332
|
+
/** S4: `withdraw` (stop waiting) or `leave` (close this tab); the pass may ride in the body. */
|
|
333
|
+
leave: "/api/v1/meet/leave",
|
|
334
|
+
/** S4: a pass holder (guests included) saw the recording notice, for this run. */
|
|
335
|
+
consent: "/api/v1/meet/consent"
|
|
308
336
|
};
|
|
309
337
|
function meetingPath(template, meetingId) {
|
|
310
338
|
return template.replace("{meeting_id}", encodeURIComponent(meetingId));
|
|
311
339
|
}
|
|
312
|
-
var
|
|
340
|
+
var MEETING_PASS_HEADER = "x-meet-pass";
|
|
341
|
+
function meetingPassHeaders(token) {
|
|
342
|
+
const pass = token?.meetingPass ?? null;
|
|
343
|
+
return pass !== null && pass.length > 0 ? { [MEETING_PASS_HEADER]: pass } : {};
|
|
344
|
+
}
|
|
313
345
|
var REFRESH_RATIO = 0.8;
|
|
314
346
|
function readString(source, key) {
|
|
315
347
|
const value = source[key];
|
|
@@ -319,7 +351,12 @@ function readBoolean(source, key, fallback) {
|
|
|
319
351
|
const value = source[key];
|
|
320
352
|
return typeof value === "boolean" ? value : fallback;
|
|
321
353
|
}
|
|
322
|
-
var
|
|
354
|
+
var TOKEN_STANDINGS = ["member", "invited", "outsider", "guest"];
|
|
355
|
+
function readStanding(source, key) {
|
|
356
|
+
const value = source[key];
|
|
357
|
+
return TOKEN_STANDINGS.includes(value) ? value : null;
|
|
358
|
+
}
|
|
359
|
+
var TOKEN_ROLES = ["host", "cohost", "presenter", "participant", "viewer"];
|
|
323
360
|
function readRole(source, key) {
|
|
324
361
|
const value = source[key];
|
|
325
362
|
return TOKEN_ROLES.includes(value) ? value : null;
|
|
@@ -362,8 +399,15 @@ function parseRoomToken(body, roomName) {
|
|
|
362
399
|
// Deriving the role from what DID arrive keeps a host a host against a
|
|
363
400
|
// server that has not landed MRI-A11 — and never invents a host from
|
|
364
401
|
// silence, because both fallbacks below grant nothing.
|
|
365
|
-
|
|
402
|
+
// A word this package does not know grants nothing: `participant`, never a host.
|
|
403
|
+
role: readRole(payload, "role") ?? (isHost ? "host" : "participant"),
|
|
404
|
+
standing: readStanding(payload, "standing"),
|
|
405
|
+
meetingPass: readString(payload, "meeting_pass"),
|
|
406
|
+
meetingPassExpiresAt: readString(payload, "meeting_pass_expires_at"),
|
|
407
|
+
sessionId: readString(payload, "session_id"),
|
|
366
408
|
lobby: readBoolean(payload, "lobby", false),
|
|
409
|
+
admissionState: readString(payload, "admission_state") ?? void 0,
|
|
410
|
+
pollSeconds: typeof payload["poll_seconds"] === "number" ? payload["poll_seconds"] : void 0,
|
|
367
411
|
breakoutRoomId: readString(payload, "breakout_room_id") ?? null
|
|
368
412
|
};
|
|
369
413
|
}
|
|
@@ -373,7 +417,7 @@ function createTokenClient(options) {
|
|
|
373
417
|
const storage = options.storage;
|
|
374
418
|
function remember(token) {
|
|
375
419
|
if (storage === void 0) return;
|
|
376
|
-
if (
|
|
420
|
+
if (token.standing !== "guest") return;
|
|
377
421
|
storage.write(token.roomName, JSON.stringify(token));
|
|
378
422
|
}
|
|
379
423
|
function recall(roomName) {
|
|
@@ -431,22 +475,28 @@ function createTokenClient(options) {
|
|
|
431
475
|
throw new MeetError(
|
|
432
476
|
"forbidden",
|
|
433
477
|
said?.message ?? "You can't join this meeting.",
|
|
434
|
-
said?.remedy ?? "Ask the person who shared the link to let you in, then open the link again."
|
|
478
|
+
said?.remedy ?? "Ask the person who shared the link to let you in, then open the link again.",
|
|
479
|
+
void 0,
|
|
480
|
+
said?.reason ?? null
|
|
435
481
|
);
|
|
436
482
|
}
|
|
437
483
|
if (response.status === 404) {
|
|
438
484
|
throw new MeetError(
|
|
439
485
|
"not-found",
|
|
440
486
|
"meet.token: no such meeting",
|
|
441
|
-
"The link is wrong or the meeting was deleted. Show the 'meeting not found' screen; a durable link that 404s is never a retry."
|
|
487
|
+
"The link is wrong or the meeting was deleted. Show the 'meeting not found' screen; a durable link that 404s is never a retry.",
|
|
488
|
+
void 0,
|
|
489
|
+
"not_found"
|
|
442
490
|
);
|
|
443
491
|
}
|
|
444
492
|
if (response.status === 410) {
|
|
445
|
-
const
|
|
493
|
+
const said = await serverSentence(response);
|
|
446
494
|
throw new MeetError(
|
|
447
495
|
"cancelled",
|
|
448
|
-
|
|
449
|
-
"Nobody can join a cancelled meeting. Show the cancelled screen with this sentence; ask the host to schedule a new one."
|
|
496
|
+
said?.message ?? "This meeting was cancelled.",
|
|
497
|
+
"Nobody can join a cancelled meeting. Show the cancelled screen with this sentence; ask the host to schedule a new one.",
|
|
498
|
+
void 0,
|
|
499
|
+
said?.reason ?? "cancelled"
|
|
450
500
|
);
|
|
451
501
|
}
|
|
452
502
|
if (response.status === 423) {
|
|
@@ -454,7 +504,9 @@ function createTokenClient(options) {
|
|
|
454
504
|
throw new MeetError(
|
|
455
505
|
"room-locked",
|
|
456
506
|
said?.message ?? "The meeting is locked.",
|
|
457
|
-
said?.remedy ?? "The host locked the room. Ask them to unlock it, then join again."
|
|
507
|
+
said?.remedy ?? "The host locked the room. Ask them to unlock it, then join again.",
|
|
508
|
+
void 0,
|
|
509
|
+
said?.reason ?? "locked"
|
|
458
510
|
);
|
|
459
511
|
}
|
|
460
512
|
if (response.status === 503) {
|
|
@@ -523,6 +575,10 @@ function placeholder(roomName) {
|
|
|
523
575
|
canRecord: false,
|
|
524
576
|
isHost: false,
|
|
525
577
|
role: "participant",
|
|
578
|
+
standing: null,
|
|
579
|
+
meetingPass: null,
|
|
580
|
+
meetingPassExpiresAt: null,
|
|
581
|
+
sessionId: null,
|
|
526
582
|
lobby: false,
|
|
527
583
|
breakoutRoomId: null
|
|
528
584
|
};
|
|
@@ -535,7 +591,8 @@ async function serverSentence(response) {
|
|
|
535
591
|
const message = typeof source.user_message === "string" && source.user_message.trim() !== "" ? source.user_message : typeof source.message === "string" && source.message.trim() !== "" ? source.message : null;
|
|
536
592
|
if (message === null) return null;
|
|
537
593
|
const remedy = typeof source.remedy === "string" && source.remedy.trim() !== "" ? source.remedy : void 0;
|
|
538
|
-
|
|
594
|
+
const reason = typeof source.reason === "string" && source.reason !== "" ? source.reason : null;
|
|
595
|
+
return { message, remedy, reason };
|
|
539
596
|
} catch {
|
|
540
597
|
return null;
|
|
541
598
|
}
|
|
@@ -724,7 +781,7 @@ function createMeetAi(options) {
|
|
|
724
781
|
operation: "meet.ai.ask",
|
|
725
782
|
// A GUEST MAY ASK. The server decides whether it can answer.
|
|
726
783
|
requireAuth: false,
|
|
727
|
-
...roomToken !== null ? { headers: { [
|
|
784
|
+
...roomToken !== null ? { headers: { [MEETING_PASS_HEADER]: roomToken } } : {},
|
|
728
785
|
...signal !== void 0 ? { signal } : {}
|
|
729
786
|
});
|
|
730
787
|
if (!response.ok) {
|
|
@@ -938,14 +995,15 @@ var MEET_EVENTS = {
|
|
|
938
995
|
cancel: "mx.call.cancel",
|
|
939
996
|
/** You answered (or declined) on another device. Stop ringing here. */
|
|
940
997
|
settled: "mx.call.settled",
|
|
941
|
-
/**
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
998
|
+
/**
|
|
999
|
+
* 🚨 S4 (CORE-DESIGN §2.6): the ONE room-channel event — payload-free "something changed,
|
|
1000
|
+
* read again". Nothing a person must see rides it: a waiting person learns admitted /
|
|
1001
|
+
* denied / expired / ended by READING their row (`GET /v1/meet/state`) on a timer and on
|
|
1002
|
+
* every hint. The four payload broadcasts (`mx.lobby.knock`, `mx.lobby.admission`,
|
|
1003
|
+
* `mx.room.lock`, `mx.room.recording`) are gone with their senders and receivers. The
|
|
1004
|
+
* server publishes the same event on the same topic (aidream `admission.room_topic`).
|
|
1005
|
+
*/
|
|
1006
|
+
stateChanged: "mx.state.changed"
|
|
949
1007
|
};
|
|
950
1008
|
|
|
951
1009
|
// src/core/types.ts
|
|
@@ -959,14 +1017,19 @@ function isGuest(identity) {
|
|
|
959
1017
|
return identity.userId === null;
|
|
960
1018
|
}
|
|
961
1019
|
var ROLE_RANK = {
|
|
962
|
-
host:
|
|
963
|
-
cohost:
|
|
1020
|
+
host: 4,
|
|
1021
|
+
cohost: 3,
|
|
1022
|
+
presenter: 2,
|
|
964
1023
|
participant: 1,
|
|
965
|
-
|
|
1024
|
+
viewer: 0
|
|
966
1025
|
};
|
|
1026
|
+
function isLinkFollower(standing) {
|
|
1027
|
+
return standing === "guest" || standing === "outsider";
|
|
1028
|
+
}
|
|
967
1029
|
function isHostRole(role) {
|
|
968
1030
|
return role === "host" || role === "cohost";
|
|
969
1031
|
}
|
|
1032
|
+
var WAITING_ADMISSION_STATES = ["waiting_host", "knocking"];
|
|
970
1033
|
|
|
971
1034
|
// src/core/calls.ts
|
|
972
1035
|
var TRANSITIONS = {
|
|
@@ -1870,15 +1933,15 @@ var CAPTION_LANGUAGES = [
|
|
|
1870
1933
|
{ tag: "id", name: "Indonesian" },
|
|
1871
1934
|
{ tag: "sv", name: "Swedish" }
|
|
1872
1935
|
];
|
|
1873
|
-
async function loadMeetingActivities(api, meetingId,
|
|
1936
|
+
async function loadMeetingActivities(api, meetingId, meetingPass = null) {
|
|
1874
1937
|
let response;
|
|
1875
1938
|
try {
|
|
1876
1939
|
response = await api.request({
|
|
1877
1940
|
path: `${MEET_ROUTES.collab}?meeting_id=${encodeURIComponent(meetingId)}`,
|
|
1878
1941
|
method: "GET",
|
|
1879
1942
|
operation: "meet.loadMeetingActivities",
|
|
1880
|
-
requireAuth:
|
|
1881
|
-
...
|
|
1943
|
+
requireAuth: meetingPass === null,
|
|
1944
|
+
...meetingPass !== null ? { headers: { [MEETING_PASS_HEADER]: meetingPass } } : {}
|
|
1882
1945
|
});
|
|
1883
1946
|
} catch (error) {
|
|
1884
1947
|
return {
|
|
@@ -1975,12 +2038,14 @@ var DEFAULT_EFFECTS = {
|
|
|
1975
2038
|
};
|
|
1976
2039
|
var NO_GRANTS = {
|
|
1977
2040
|
role: "participant",
|
|
2041
|
+
standing: null,
|
|
1978
2042
|
canRecord: false,
|
|
1979
2043
|
canPublish: false,
|
|
1980
2044
|
canSubscribe: false
|
|
1981
2045
|
};
|
|
1982
2046
|
var EMPTY = {
|
|
1983
2047
|
phase: "idle",
|
|
2048
|
+
waiting: null,
|
|
1984
2049
|
meeting: null,
|
|
1985
2050
|
localIdentity: null,
|
|
1986
2051
|
grants: NO_GRANTS,
|
|
@@ -2021,6 +2086,11 @@ function effectiveRole(snapshot) {
|
|
|
2021
2086
|
const fromToken = snapshot.grants.role;
|
|
2022
2087
|
return ROLE_RANK[fromRoster] >= ROLE_RANK[fromToken] ? fromRoster : fromToken;
|
|
2023
2088
|
}
|
|
2089
|
+
function effectiveStanding(snapshot) {
|
|
2090
|
+
if (snapshot.grants.standing != null) return snapshot.grants.standing;
|
|
2091
|
+
const me = snapshot.participants.find((p) => p.identity === snapshot.localIdentity);
|
|
2092
|
+
return me?.standing ?? null;
|
|
2093
|
+
}
|
|
2024
2094
|
var REACTION_TTL_MS = 5e3;
|
|
2025
2095
|
var CHAT_LIMIT = 200;
|
|
2026
2096
|
function createMeetStore(initial = {}) {
|
|
@@ -2232,19 +2302,25 @@ function roleOf(participants, identity) {
|
|
|
2232
2302
|
return participants.find((p) => p.identity === identity)?.role ?? "participant";
|
|
2233
2303
|
}
|
|
2234
2304
|
var END_MEETING_NOT_SERVED = "Ending a meeting is not available on this server yet";
|
|
2235
|
-
function lobbyEntryFrom(attendee) {
|
|
2236
|
-
if (attendee.admissionState
|
|
2305
|
+
function lobbyEntryFrom(attendee, now = Date.now()) {
|
|
2306
|
+
if (!WAITING_ADMISSION_STATES.includes(attendee.admissionState)) return null;
|
|
2307
|
+
const expires = attendee.knockExpiresAt ? Date.parse(attendee.knockExpiresAt) : Number.NaN;
|
|
2308
|
+
if (!Number.isNaN(expires) && expires <= now) return null;
|
|
2237
2309
|
return {
|
|
2238
2310
|
identity: attendee.identity,
|
|
2239
2311
|
displayName: attendee.displayName,
|
|
2240
2312
|
avatarUrl: attendee.avatarUrl,
|
|
2241
2313
|
userId: attendee.userId,
|
|
2242
2314
|
state: "knocking",
|
|
2243
|
-
knockedAt: attendee.joinedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
2315
|
+
knockedAt: attendee.knockedAt ?? attendee.joinedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
2316
|
+
knockExpiresAt: attendee.knockExpiresAt ?? null,
|
|
2244
2317
|
decidedAt: null,
|
|
2245
2318
|
decidedBy: null
|
|
2246
2319
|
};
|
|
2247
2320
|
}
|
|
2321
|
+
function decidedState(state) {
|
|
2322
|
+
return WAITING_ADMISSION_STATES.includes(state) ? "expired" : state;
|
|
2323
|
+
}
|
|
2248
2324
|
function createMeetingSession(options) {
|
|
2249
2325
|
const { manager, repository, store, identity } = options;
|
|
2250
2326
|
let channel = null;
|
|
@@ -2268,11 +2344,6 @@ function createMeetingSession(options) {
|
|
|
2268
2344
|
}
|
|
2269
2345
|
return identity.userId;
|
|
2270
2346
|
}
|
|
2271
|
-
function knockIdentity() {
|
|
2272
|
-
return asParticipantIdentity(
|
|
2273
|
-
identity.userId !== null ? `user:${identity.userId}` : `guest:${identity.deviceId}`
|
|
2274
|
-
);
|
|
2275
|
-
}
|
|
2276
2347
|
function requireMeeting(operation) {
|
|
2277
2348
|
const meeting = store.snapshot().meeting;
|
|
2278
2349
|
if (meeting === null) {
|
|
@@ -2284,6 +2355,191 @@ function createMeetingSession(options) {
|
|
|
2284
2355
|
}
|
|
2285
2356
|
return meeting;
|
|
2286
2357
|
}
|
|
2358
|
+
let polling = false;
|
|
2359
|
+
let pollTimer = null;
|
|
2360
|
+
let reading = null;
|
|
2361
|
+
let admittedOnce = false;
|
|
2362
|
+
let unlisten = null;
|
|
2363
|
+
function report(level, message, remedy) {
|
|
2364
|
+
options.onDiagnostic?.(remedy === void 0 ? { level, message } : { level, message, remedy });
|
|
2365
|
+
}
|
|
2366
|
+
function pollMs() {
|
|
2367
|
+
const seconds = store.snapshot().waiting?.pollSeconds ?? 3;
|
|
2368
|
+
return Math.max(1, Math.min(60, seconds)) * 1e3;
|
|
2369
|
+
}
|
|
2370
|
+
function schedule(ms2) {
|
|
2371
|
+
if (pollTimer !== null) clearTimeout(pollTimer);
|
|
2372
|
+
if (!polling) return;
|
|
2373
|
+
pollTimer = setTimeout(() => {
|
|
2374
|
+
pollTimer = null;
|
|
2375
|
+
void readState2().finally(() => schedule(pollMs()));
|
|
2376
|
+
}, ms2);
|
|
2377
|
+
}
|
|
2378
|
+
function listen() {
|
|
2379
|
+
if (unlisten !== null || typeof window === "undefined") return;
|
|
2380
|
+
const nudge = () => {
|
|
2381
|
+
if (typeof document !== "undefined" && document.visibilityState === "hidden") return;
|
|
2382
|
+
void readState2();
|
|
2383
|
+
};
|
|
2384
|
+
const goodbye = () => {
|
|
2385
|
+
const meeting = store.snapshot().meeting;
|
|
2386
|
+
const pass = options.resolvePass?.() ?? null;
|
|
2387
|
+
const waiting = store.snapshot().waiting;
|
|
2388
|
+
if (meeting === null || pass === null || waiting === null) return;
|
|
2389
|
+
if (!WAITING_ADMISSION_STATES.includes(waiting.admission)) return;
|
|
2390
|
+
try {
|
|
2391
|
+
navigator.sendBeacon?.(
|
|
2392
|
+
options.api.url(MEET_ROUTES.leave),
|
|
2393
|
+
new Blob([JSON.stringify({ meeting_id: meeting.id, intent: "withdraw", meeting_pass: pass })], {
|
|
2394
|
+
type: "application/json"
|
|
2395
|
+
})
|
|
2396
|
+
);
|
|
2397
|
+
} catch {
|
|
2398
|
+
}
|
|
2399
|
+
};
|
|
2400
|
+
window.addEventListener("online", nudge);
|
|
2401
|
+
window.addEventListener("pagehide", goodbye);
|
|
2402
|
+
document.addEventListener("visibilitychange", nudge);
|
|
2403
|
+
unlisten = () => {
|
|
2404
|
+
window.removeEventListener("online", nudge);
|
|
2405
|
+
window.removeEventListener("pagehide", goodbye);
|
|
2406
|
+
document.removeEventListener("visibilitychange", nudge);
|
|
2407
|
+
};
|
|
2408
|
+
}
|
|
2409
|
+
function stopWaiting() {
|
|
2410
|
+
polling = false;
|
|
2411
|
+
if (pollTimer !== null) clearTimeout(pollTimer);
|
|
2412
|
+
pollTimer = null;
|
|
2413
|
+
unlisten?.();
|
|
2414
|
+
unlisten = null;
|
|
2415
|
+
}
|
|
2416
|
+
function patchWaiting(next) {
|
|
2417
|
+
const current = store.snapshot().waiting;
|
|
2418
|
+
const base = current ?? {
|
|
2419
|
+
admission: "knocking",
|
|
2420
|
+
admittersPresent: null,
|
|
2421
|
+
reknockAllowed: true,
|
|
2422
|
+
knockExpiresAt: null,
|
|
2423
|
+
pollSeconds: 3,
|
|
2424
|
+
liveVersion: 0
|
|
2425
|
+
};
|
|
2426
|
+
store.patch({ waiting: { ...base, ...next } });
|
|
2427
|
+
}
|
|
2428
|
+
function apply(read) {
|
|
2429
|
+
const snapshot = store.snapshot();
|
|
2430
|
+
if (snapshot.phase !== "lobby") return;
|
|
2431
|
+
const admission = read.admission_state ?? null;
|
|
2432
|
+
const ended = admission === "ended" || read.session_phase === "ended" || read.meeting_phase === "ended";
|
|
2433
|
+
if (admission === "admitted" && !ended) {
|
|
2434
|
+
if (admittedOnce) return;
|
|
2435
|
+
admittedOnce = true;
|
|
2436
|
+
stopWaiting();
|
|
2437
|
+
options.onAdmitted?.();
|
|
2438
|
+
return;
|
|
2439
|
+
}
|
|
2440
|
+
if (ended) {
|
|
2441
|
+
stopWaiting();
|
|
2442
|
+
patchWaiting({ admission: "ended" });
|
|
2443
|
+
const meeting = snapshot.meeting;
|
|
2444
|
+
if (read.meeting_phase === "ended" && meeting !== null && meeting.endedAt === null) {
|
|
2445
|
+
store.patch({ meeting: { ...meeting, endedAt: (/* @__PURE__ */ new Date()).toISOString() } });
|
|
2446
|
+
}
|
|
2447
|
+
return;
|
|
2448
|
+
}
|
|
2449
|
+
if (admission === null) return;
|
|
2450
|
+
patchWaiting({
|
|
2451
|
+
admission,
|
|
2452
|
+
admittersPresent: read.admitters_present ?? null,
|
|
2453
|
+
reknockAllowed: read.reknock_allowed ?? true,
|
|
2454
|
+
knockExpiresAt: read.knock_expires_at ?? null,
|
|
2455
|
+
pollSeconds: read.poll_seconds ?? snapshot.waiting?.pollSeconds ?? 3,
|
|
2456
|
+
liveVersion: read.live_version ?? 0
|
|
2457
|
+
});
|
|
2458
|
+
if (admission === "denied" || admission === "expired" || admission === "withdrawn" || admission === "removed") {
|
|
2459
|
+
stopWaiting();
|
|
2460
|
+
}
|
|
2461
|
+
}
|
|
2462
|
+
async function readState2() {
|
|
2463
|
+
if (reading !== null) return reading;
|
|
2464
|
+
const meeting = store.snapshot().meeting;
|
|
2465
|
+
const pass = options.resolvePass?.() ?? null;
|
|
2466
|
+
if (meeting === null || pass === null) return;
|
|
2467
|
+
reading = (async () => {
|
|
2468
|
+
try {
|
|
2469
|
+
const response = await options.api.request({
|
|
2470
|
+
path: `${MEET_ROUTES.state}?meeting_id=${encodeURIComponent(meeting.id)}`,
|
|
2471
|
+
method: "GET",
|
|
2472
|
+
requireAuth: false,
|
|
2473
|
+
operation: "meet.state",
|
|
2474
|
+
headers: { [MEETING_PASS_HEADER]: pass }
|
|
2475
|
+
});
|
|
2476
|
+
if (!response.ok) {
|
|
2477
|
+
let body = null;
|
|
2478
|
+
try {
|
|
2479
|
+
body = await response.json();
|
|
2480
|
+
} catch {
|
|
2481
|
+
body = null;
|
|
2482
|
+
}
|
|
2483
|
+
const refusal2 = readServerRefusal(body);
|
|
2484
|
+
report(
|
|
2485
|
+
"warn",
|
|
2486
|
+
refusal2?.reason ?? `meet.state: the server answered ${response.status}`,
|
|
2487
|
+
refusal2?.remedy ?? "The waiting room keeps asking; nothing on this screen changes until the server answers."
|
|
2488
|
+
);
|
|
2489
|
+
return;
|
|
2490
|
+
}
|
|
2491
|
+
apply(await response.json());
|
|
2492
|
+
} catch (error) {
|
|
2493
|
+
report("info", `meet.state: ${String(error)}`, "Waiting continues; the state is read again shortly.");
|
|
2494
|
+
} finally {
|
|
2495
|
+
reading = null;
|
|
2496
|
+
}
|
|
2497
|
+
})();
|
|
2498
|
+
return reading;
|
|
2499
|
+
}
|
|
2500
|
+
async function postLeave(intent) {
|
|
2501
|
+
const meeting = requireMeeting(`meet.${intent}`);
|
|
2502
|
+
const pass = options.resolvePass?.() ?? null;
|
|
2503
|
+
const response = await options.api.request({
|
|
2504
|
+
path: MEET_ROUTES.leave,
|
|
2505
|
+
method: "POST",
|
|
2506
|
+
requireAuth: false,
|
|
2507
|
+
operation: `meet.${intent}`,
|
|
2508
|
+
body: { meeting_id: meeting.id, intent },
|
|
2509
|
+
...pass !== null ? { headers: { [MEETING_PASS_HEADER]: pass } } : {}
|
|
2510
|
+
});
|
|
2511
|
+
if (!response.ok) {
|
|
2512
|
+
let body = null;
|
|
2513
|
+
try {
|
|
2514
|
+
body = await response.json();
|
|
2515
|
+
} catch {
|
|
2516
|
+
body = null;
|
|
2517
|
+
}
|
|
2518
|
+
const refusal2 = readServerRefusal(body);
|
|
2519
|
+
throw new MeetError(
|
|
2520
|
+
"transport",
|
|
2521
|
+
refusal2?.reason ?? `meet.${intent}: the server answered ${response.status}`,
|
|
2522
|
+
refusal2?.remedy ?? "You are still in the waiting room. Try again."
|
|
2523
|
+
);
|
|
2524
|
+
}
|
|
2525
|
+
}
|
|
2526
|
+
function hint() {
|
|
2527
|
+
channel?.send(MEET_EVENTS.stateChanged, { v: null });
|
|
2528
|
+
}
|
|
2529
|
+
async function refreshQueue() {
|
|
2530
|
+
const meeting = store.snapshot().meeting;
|
|
2531
|
+
if (meeting === null || isGuest(identity) || !isHostRole(effectiveRole(store.snapshot()))) return;
|
|
2532
|
+
try {
|
|
2533
|
+
const attendees = await repository.attendees(meeting.id);
|
|
2534
|
+
store.learn(attendees);
|
|
2535
|
+
for (const attendee of attendees) {
|
|
2536
|
+
const entry = lobbyEntryFrom(attendee);
|
|
2537
|
+
if (entry !== null) store.upsertLobby(entry);
|
|
2538
|
+
else store.setAdmission(attendee.identity, decidedState(attendee.admissionState), null);
|
|
2539
|
+
}
|
|
2540
|
+
} catch {
|
|
2541
|
+
}
|
|
2542
|
+
}
|
|
2287
2543
|
return {
|
|
2288
2544
|
watch(roomName) {
|
|
2289
2545
|
channel?.close();
|
|
@@ -2291,58 +2547,22 @@ function createMeetingSession(options) {
|
|
|
2291
2547
|
topic: roomTopic(roomName),
|
|
2292
2548
|
broadcast: [
|
|
2293
2549
|
{
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
{
|
|
2300
|
-
event: MEET_EVENTS.admission,
|
|
2301
|
-
onMessage: (message) => {
|
|
2302
|
-
const verdict = message.data;
|
|
2303
|
-
const decided = asParticipantIdentity(verdict.identity);
|
|
2304
|
-
store.setAdmission(
|
|
2305
|
-
decided,
|
|
2306
|
-
verdict.admitted ? "admitted" : "denied",
|
|
2307
|
-
verdict.by === null ? null : verdict.by
|
|
2308
|
-
);
|
|
2309
|
-
if (decided === knockIdentity()) {
|
|
2310
|
-
if (verdict.admitted) options.onAdmitted?.();
|
|
2311
|
-
else options.onDenied?.();
|
|
2312
|
-
}
|
|
2313
|
-
}
|
|
2314
|
-
},
|
|
2315
|
-
{
|
|
2316
|
-
event: MEET_EVENTS.lock,
|
|
2317
|
-
onMessage: (message) => {
|
|
2318
|
-
const { locked } = message.data;
|
|
2319
|
-
const meeting = store.snapshot().meeting;
|
|
2320
|
-
store.patch({
|
|
2321
|
-
locked,
|
|
2322
|
-
...meeting !== null ? { meeting: { ...meeting, locked } } : {}
|
|
2323
|
-
});
|
|
2324
|
-
}
|
|
2325
|
-
},
|
|
2326
|
-
{
|
|
2327
|
-
event: MEET_EVENTS.recording,
|
|
2328
|
-
onMessage: (message) => {
|
|
2329
|
-
const payload = message.data;
|
|
2330
|
-
store.setRecording(
|
|
2331
|
-
payload.state,
|
|
2332
|
-
payload.by === null ? null : payload.by,
|
|
2333
|
-
"room"
|
|
2334
|
-
);
|
|
2550
|
+
// 🚨 PAYLOAD-FREE. Every receiver reads again; a spoofed hint only causes a read.
|
|
2551
|
+
event: MEET_EVENTS.stateChanged,
|
|
2552
|
+
onMessage: () => {
|
|
2553
|
+
if (store.snapshot().phase === "lobby") void readState2();
|
|
2554
|
+
void refreshQueue();
|
|
2335
2555
|
}
|
|
2336
2556
|
}
|
|
2337
2557
|
],
|
|
2338
|
-
// THE BACKFILL DOOR. A
|
|
2339
|
-
//
|
|
2340
|
-
// re-announce on their own timer.
|
|
2558
|
+
// THE BACKFILL DOOR. A socket blip must not lose a decision: re-read the meeting
|
|
2559
|
+
// row, and a waiting tab re-reads its own state.
|
|
2341
2560
|
onBackfill: async () => {
|
|
2342
2561
|
const meeting = store.snapshot().meeting;
|
|
2343
2562
|
if (meeting === null) return;
|
|
2344
2563
|
const fresh = isGuest(identity) ? await repository.meetingBySlug(meeting.slug) : await readMeeting(meeting.id);
|
|
2345
2564
|
store.patch({ meeting: fresh, locked: fresh.locked });
|
|
2565
|
+
if (store.snapshot().phase === "lobby") void readState2();
|
|
2346
2566
|
}
|
|
2347
2567
|
});
|
|
2348
2568
|
},
|
|
@@ -2403,7 +2623,7 @@ function createMeetingSession(options) {
|
|
|
2403
2623
|
store.upsertLobby(entry);
|
|
2404
2624
|
return;
|
|
2405
2625
|
}
|
|
2406
|
-
store.setAdmission(attendee.identity, attendee.admissionState, null);
|
|
2626
|
+
store.setAdmission(attendee.identity, decidedState(attendee.admissionState), null);
|
|
2407
2627
|
}
|
|
2408
2628
|
},
|
|
2409
2629
|
{
|
|
@@ -2455,62 +2675,75 @@ function createMeetingSession(options) {
|
|
|
2455
2675
|
if (entry !== null) {
|
|
2456
2676
|
store.upsertLobby(entry);
|
|
2457
2677
|
} else {
|
|
2458
|
-
store.setAdmission(attendee.identity, attendee.admissionState, null);
|
|
2678
|
+
store.setAdmission(attendee.identity, decidedState(attendee.admissionState), null);
|
|
2459
2679
|
}
|
|
2460
2680
|
}
|
|
2461
2681
|
}
|
|
2462
2682
|
});
|
|
2463
2683
|
meetingChannel.requestBackfill("manual");
|
|
2464
2684
|
},
|
|
2465
|
-
knock(roomName) {
|
|
2466
|
-
const handle = channel ?? manager.open({ topic: roomTopic(roomName) });
|
|
2467
|
-
channel = handle;
|
|
2468
|
-
const entry = {
|
|
2469
|
-
identity: knockIdentity(),
|
|
2470
|
-
displayName: identity.displayName,
|
|
2471
|
-
avatarUrl: identity.avatarUrl,
|
|
2472
|
-
userId: identity.userId,
|
|
2473
|
-
state: "knocking",
|
|
2474
|
-
knockedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2475
|
-
decidedAt: null,
|
|
2476
|
-
decidedBy: null
|
|
2477
|
-
};
|
|
2478
|
-
store.upsertLobby(entry);
|
|
2479
|
-
handle.send(MEET_EVENTS.knock, entry);
|
|
2480
|
-
},
|
|
2481
2685
|
async admit(entry, admitted) {
|
|
2482
2686
|
const by = requireHostUser("meet.admit");
|
|
2483
2687
|
const meeting = requireMeeting("meet.admit");
|
|
2484
|
-
await repository.
|
|
2688
|
+
const row = await repository.decideAdmission({
|
|
2485
2689
|
meetingId: meeting.id,
|
|
2486
2690
|
identity: entry.identity,
|
|
2487
|
-
admitted,
|
|
2691
|
+
decision: admitted ? "admit" : "deny",
|
|
2488
2692
|
byUserId: by
|
|
2489
2693
|
});
|
|
2490
2694
|
store.upsertLobby({
|
|
2491
2695
|
...entry,
|
|
2492
|
-
state:
|
|
2696
|
+
state: decidedState(row.admissionState),
|
|
2493
2697
|
decidedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2494
2698
|
decidedBy: by
|
|
2495
2699
|
});
|
|
2496
|
-
|
|
2700
|
+
hint();
|
|
2701
|
+
},
|
|
2702
|
+
async undeny(entry) {
|
|
2703
|
+
const by = requireHostUser("meet.undeny");
|
|
2704
|
+
const meeting = requireMeeting("meet.undeny");
|
|
2705
|
+
const row = await repository.decideAdmission({
|
|
2706
|
+
meetingId: meeting.id,
|
|
2497
2707
|
identity: entry.identity,
|
|
2498
|
-
|
|
2499
|
-
by
|
|
2708
|
+
decision: "undeny",
|
|
2709
|
+
byUserId: by
|
|
2500
2710
|
});
|
|
2711
|
+
store.upsertLobby({ ...entry, state: decidedState(row.admissionState), decidedAt: (/* @__PURE__ */ new Date()).toISOString(), decidedBy: by });
|
|
2712
|
+
hint();
|
|
2501
2713
|
},
|
|
2502
2714
|
async admitAll() {
|
|
2503
|
-
const
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
|
|
2715
|
+
const by = requireHostUser("meet.admitAll");
|
|
2716
|
+
const meeting = requireMeeting("meet.admitAll");
|
|
2717
|
+
const rows = await repository.admitAll(meeting.id, by);
|
|
2718
|
+
for (const row of rows) {
|
|
2719
|
+
store.setAdmission(row.identity, row.admissionState, by);
|
|
2720
|
+
}
|
|
2721
|
+
hint();
|
|
2722
|
+
},
|
|
2723
|
+
startWaiting() {
|
|
2724
|
+
if (polling) return;
|
|
2725
|
+
polling = true;
|
|
2726
|
+
admittedOnce = false;
|
|
2727
|
+
listen();
|
|
2728
|
+
schedule(0);
|
|
2729
|
+
},
|
|
2730
|
+
stopWaiting,
|
|
2731
|
+
readState: readState2,
|
|
2732
|
+
async withdraw() {
|
|
2733
|
+
stopWaiting();
|
|
2734
|
+
await postLeave("withdraw");
|
|
2735
|
+
patchWaiting({ admission: "withdrawn" });
|
|
2736
|
+
hint();
|
|
2737
|
+
},
|
|
2738
|
+
async askAgain() {
|
|
2739
|
+
await options.rejoin?.();
|
|
2507
2740
|
},
|
|
2508
2741
|
async setLock(locked) {
|
|
2509
2742
|
const by = requireHostUser("meet.setLock");
|
|
2510
2743
|
const meeting = requireMeeting("meet.setLock");
|
|
2511
2744
|
const updated = await repository.setLock(meeting.id, locked, by);
|
|
2512
2745
|
store.patch({ meeting: updated, locked: updated.locked });
|
|
2513
|
-
|
|
2746
|
+
hint();
|
|
2514
2747
|
return updated;
|
|
2515
2748
|
},
|
|
2516
2749
|
/**
|
|
@@ -2568,6 +2801,7 @@ function createMeetingSession(options) {
|
|
|
2568
2801
|
return { endedAt, served: true };
|
|
2569
2802
|
},
|
|
2570
2803
|
dispose() {
|
|
2804
|
+
stopWaiting();
|
|
2571
2805
|
channel?.close();
|
|
2572
2806
|
channel = null;
|
|
2573
2807
|
meetingChannel?.close();
|
|
@@ -2612,9 +2846,15 @@ var RPCS = {
|
|
|
2612
2846
|
getOrCreateMeeting: "meet_get_or_create_meeting",
|
|
2613
2847
|
/** Resolve a durable public link, before/during/after its scheduled time. */
|
|
2614
2848
|
meetingBySlug: "meet_meeting_by_slug",
|
|
2615
|
-
|
|
2849
|
+
/*
|
|
2850
|
+
* ── S4 ADMISSION (CORE-DESIGN §2.4). Each returns the decided row; the caller then
|
|
2851
|
+
* sends the payload-free `mx.state.changed` hint. Authorized by `who_may_admit`.
|
|
2852
|
+
*/
|
|
2853
|
+
admit: "meet_admit",
|
|
2854
|
+
deny: "meet_deny",
|
|
2855
|
+
undeny: "meet_undeny",
|
|
2856
|
+
admitAll: "meet_admit_all",
|
|
2616
2857
|
setLock: "meet_set_lock",
|
|
2617
|
-
endMeeting: "meet_end_meeting",
|
|
2618
2858
|
recordConsent: "meet_record_consent",
|
|
2619
2859
|
/*
|
|
2620
2860
|
* ── MEET WAVE 1 — management (0.8). Every write is a SECURITY DEFINER door
|
|
@@ -2656,13 +2896,13 @@ var RPCS = {
|
|
|
2656
2896
|
/** Titles, agendas, current notes and final transcript lines; snippets mark hits with ⟦ ⟧. */
|
|
2657
2897
|
search: "meet_search"
|
|
2658
2898
|
};
|
|
2659
|
-
var VOID_RPCS = [RPCS.
|
|
2899
|
+
var VOID_RPCS = [RPCS.recordConsent];
|
|
2660
2900
|
var MEETING_COLUMNS = "id,organization_id,room_name,slug,title,kind,host_user_id,scheduled_for,scheduled_duration_minutes,started_at,ended_at,locked,lobby_enabled,recording_policy,ai_enabled,metadata,time_zone,agenda,recurrence_rule,join_before_host,cancelled_at,cancellation_reason,deleted_at,calendar_sequence,version";
|
|
2661
2901
|
var INVITEE_COLUMNS = "id,meeting_id,invitee_user_id,email,display_name,role,rsvp_state,responded_at,rsvp_note,last_sent_sequence,last_sent_at";
|
|
2662
2902
|
var RECORDING_COLUMNS = "id,meeting_id,state,started_at,ended_at,started_by,file_id,failure_reason";
|
|
2663
2903
|
var SEGMENT_COLUMNS = "id,meeting_id,identity,speaker,text,started_at,is_final,metadata";
|
|
2664
2904
|
var NOTE_COLUMNS = "id,meeting_id,text,kind,assignee_user_id,created_at,metadata";
|
|
2665
|
-
var ATTENDEE_COLUMNS = "id,meeting_id,identity,participant_user_id,display_name,avatar_url,role,admission_state,is_agent,joined_at,left_at,sessions:metadata->sessions";
|
|
2905
|
+
var ATTENDEE_COLUMNS = "id,meeting_id,identity,participant_user_id,display_name,avatar_url,role,admission_state,knocked_at,knock_expires_at,is_agent,joined_at,left_at,sessions:metadata->sessions";
|
|
2666
2906
|
function projectStays(value) {
|
|
2667
2907
|
if (!Array.isArray(value)) return [];
|
|
2668
2908
|
const stays = [];
|
|
@@ -3028,8 +3268,17 @@ function createMeetRepository(options) {
|
|
|
3028
3268
|
const operation = "meet.projectAttendee";
|
|
3029
3269
|
const role = text(row, "role");
|
|
3030
3270
|
const admission = text(row, "admission_state");
|
|
3031
|
-
const known = ["host", "cohost", "participant", "
|
|
3032
|
-
const states = [
|
|
3271
|
+
const known = ["host", "cohost", "presenter", "participant", "viewer"];
|
|
3272
|
+
const states = [
|
|
3273
|
+
"waiting_host",
|
|
3274
|
+
"knocking",
|
|
3275
|
+
"admitted",
|
|
3276
|
+
"denied",
|
|
3277
|
+
"expired",
|
|
3278
|
+
"withdrawn",
|
|
3279
|
+
"removed",
|
|
3280
|
+
"ended"
|
|
3281
|
+
];
|
|
3033
3282
|
const userId = text(row, "participant_user_id");
|
|
3034
3283
|
const identity = requireText(row, "identity", operation);
|
|
3035
3284
|
return {
|
|
@@ -3043,6 +3292,8 @@ function createMeetRepository(options) {
|
|
|
3043
3292
|
userId: userId === null ? null : asUserId(userId),
|
|
3044
3293
|
role: known.includes(role) ? role : "participant",
|
|
3045
3294
|
admissionState: states.includes(admission) ? admission : "admitted",
|
|
3295
|
+
knockedAt: text(row, "knocked_at"),
|
|
3296
|
+
knockExpiresAt: text(row, "knock_expires_at"),
|
|
3046
3297
|
isAgent: bool(row, "is_agent", false),
|
|
3047
3298
|
joinedAt: text(row, "joined_at"),
|
|
3048
3299
|
leftAt: text(row, "left_at"),
|
|
@@ -3321,16 +3572,26 @@ function createMeetRepository(options) {
|
|
|
3321
3572
|
hasMore: rows.length > limit
|
|
3322
3573
|
};
|
|
3323
3574
|
},
|
|
3324
|
-
async
|
|
3325
|
-
|
|
3326
|
-
|
|
3327
|
-
|
|
3575
|
+
async decideAdmission({ meetingId, identity, decision, byUserId }) {
|
|
3576
|
+
const fn = decision === "admit" ? RPCS.admit : decision === "deny" ? RPCS.deny : RPCS.undeny;
|
|
3577
|
+
const row = await guard(
|
|
3578
|
+
`meet.${decision}`,
|
|
3579
|
+
() => db.rpc(fn, {
|
|
3328
3580
|
p_meeting_id: meetingId,
|
|
3329
3581
|
p_identity: identity,
|
|
3330
|
-
p_admitted: admitted,
|
|
3331
3582
|
p_by_user_id: byUserId
|
|
3332
3583
|
})
|
|
3333
3584
|
);
|
|
3585
|
+
return projectAttendee(row);
|
|
3586
|
+
},
|
|
3587
|
+
async admitAll(meetingId, byUserId) {
|
|
3588
|
+
const response = await db.rpc(RPCS.admitAll, {
|
|
3589
|
+
p_meeting_id: meetingId,
|
|
3590
|
+
p_by_user_id: byUserId
|
|
3591
|
+
});
|
|
3592
|
+
if (response.error !== null) throw normalizeMeetError(response.error, "meet.admitAll");
|
|
3593
|
+
const rows = Array.isArray(response.data) ? response.data : [];
|
|
3594
|
+
return rows.map(projectAttendee);
|
|
3334
3595
|
},
|
|
3335
3596
|
async setLock(meetingId, locked, byUserId) {
|
|
3336
3597
|
const row = await guard(
|
|
@@ -3343,16 +3604,6 @@ function createMeetRepository(options) {
|
|
|
3343
3604
|
);
|
|
3344
3605
|
return projectMeeting(row);
|
|
3345
3606
|
},
|
|
3346
|
-
async endMeeting(meetingId, byUserId) {
|
|
3347
|
-
const row = await guard(
|
|
3348
|
-
"meet.endMeeting",
|
|
3349
|
-
() => db.rpc(RPCS.endMeeting, {
|
|
3350
|
-
p_meeting_id: meetingId,
|
|
3351
|
-
p_by_user_id: byUserId
|
|
3352
|
-
})
|
|
3353
|
-
);
|
|
3354
|
-
return projectMeeting(row);
|
|
3355
|
-
},
|
|
3356
3607
|
async transcript(meetingId, limit = 1e3) {
|
|
3357
3608
|
const response = await db.from(TABLES.transcripts).select(SEGMENT_COLUMNS).eq("meeting_id", meetingId).order("started_at", { ascending: true }).order("id", { ascending: true }).limit(limit);
|
|
3358
3609
|
if (response.error !== null) throw normalizeMeetError(response.error, "meet.transcript");
|
|
@@ -3798,7 +4049,7 @@ function applyModeration(store, payload, now) {
|
|
|
3798
4049
|
return ignored(`a moderation kind this version does not know: ${String(kind)}`);
|
|
3799
4050
|
}
|
|
3800
4051
|
}
|
|
3801
|
-
var ROLES = ["host", "cohost", "participant", "
|
|
4052
|
+
var ROLES = ["host", "cohost", "presenter", "participant", "viewer"];
|
|
3802
4053
|
|
|
3803
4054
|
// src/core/note-taker.ts
|
|
3804
4055
|
var NOTE_TAKER_IDENTITY_PREFIX = "agent:note_taker:";
|
|
@@ -3883,7 +4134,12 @@ var TRANSCRIPTION_ATTRIBUTES = {
|
|
|
3883
4134
|
// src/core/room.ts
|
|
3884
4135
|
var PARTICIPANT_ROLE_ATTRIBUTE = "matrx.participant.role";
|
|
3885
4136
|
var PARTICIPANT_CAN_RECORD_ATTRIBUTE = "matrx.participant.can_record";
|
|
3886
|
-
var
|
|
4137
|
+
var PARTICIPANT_STANDING_ATTRIBUTE = "matrx.participant.standing";
|
|
4138
|
+
var KNOWN_STANDINGS = ["member", "invited", "outsider", "guest"];
|
|
4139
|
+
function parseStanding(value) {
|
|
4140
|
+
return KNOWN_STANDINGS.includes(value) ? value : null;
|
|
4141
|
+
}
|
|
4142
|
+
var KNOWN_ROLES = ["host", "cohost", "presenter", "participant", "viewer"];
|
|
3887
4143
|
function parseRole(value) {
|
|
3888
4144
|
return KNOWN_ROLES.includes(value) ? value : "participant";
|
|
3889
4145
|
}
|
|
@@ -3917,6 +4173,9 @@ function projectParticipant(driver, joinedAt) {
|
|
|
3917
4173
|
// ATTRIBUTE FIRST, metadata second — see the constants above. Both are the
|
|
3918
4174
|
// server's; the attribute is simply the one that always arrives.
|
|
3919
4175
|
role: parseRole(driver.attributes[PARTICIPANT_ROLE_ATTRIBUTE] ?? metadata.role),
|
|
4176
|
+
standing: parseStanding(
|
|
4177
|
+
driver.attributes[PARTICIPANT_STANDING_ATTRIBUTE] ?? metadata.standing
|
|
4178
|
+
),
|
|
3920
4179
|
canRecord: driver.attributes[PARTICIPANT_CAN_RECORD_ATTRIBUTE] === "true" || metadata.can_record === true,
|
|
3921
4180
|
isLocal: driver.isLocal,
|
|
3922
4181
|
isAgent: metadata.is_agent === true,
|
|
@@ -3989,7 +4248,7 @@ function createRoomEngine(options) {
|
|
|
3989
4248
|
const normalized = normalizeMeetError(error, operation);
|
|
3990
4249
|
store.patch({
|
|
3991
4250
|
phase: "failed",
|
|
3992
|
-
failure: { message: normalized.message, remedy: normalized.remedy }
|
|
4251
|
+
failure: { message: normalized.message, remedy: normalized.remedy, reason: normalized.reason }
|
|
3993
4252
|
});
|
|
3994
4253
|
report(
|
|
3995
4254
|
normalized.code === "session-unavailable" ? "warn" : "error",
|
|
@@ -4179,7 +4438,7 @@ function createRoomEngine(options) {
|
|
|
4179
4438
|
}
|
|
4180
4439
|
};
|
|
4181
4440
|
async function connect(request) {
|
|
4182
|
-
store.patch({ phase: "requesting-token", failure: null });
|
|
4441
|
+
store.patch({ phase: "requesting-token", failure: null, waiting: null });
|
|
4183
4442
|
const token = await tokens.get({
|
|
4184
4443
|
roomName: request.roomName,
|
|
4185
4444
|
identity,
|
|
@@ -4193,16 +4452,27 @@ function createRoomEngine(options) {
|
|
|
4193
4452
|
breakoutRoomId: token.breakoutRoomId ?? null,
|
|
4194
4453
|
grants: {
|
|
4195
4454
|
role: token.role,
|
|
4455
|
+
standing: token.standing,
|
|
4196
4456
|
canRecord: token.canRecord,
|
|
4197
4457
|
canPublish: token.canPublish,
|
|
4198
4458
|
canSubscribe: token.canSubscribe
|
|
4199
4459
|
}
|
|
4200
4460
|
});
|
|
4201
|
-
if (token.lobby) {
|
|
4202
|
-
store.patch({
|
|
4461
|
+
if (token.lobby || !token.canSubscribe) {
|
|
4462
|
+
store.patch({
|
|
4463
|
+
phase: "lobby",
|
|
4464
|
+
waiting: {
|
|
4465
|
+
admission: token.admissionState !== void 0 && token.admissionState !== "admitted" ? token.admissionState : "knocking",
|
|
4466
|
+
admittersPresent: null,
|
|
4467
|
+
reknockAllowed: true,
|
|
4468
|
+
knockExpiresAt: null,
|
|
4469
|
+
pollSeconds: token.pollSeconds ?? 3,
|
|
4470
|
+
liveVersion: 0
|
|
4471
|
+
}
|
|
4472
|
+
});
|
|
4203
4473
|
return;
|
|
4204
4474
|
}
|
|
4205
|
-
store.patch({ phase: "connecting" });
|
|
4475
|
+
store.patch({ phase: "connecting", waiting: null });
|
|
4206
4476
|
const created = await options.driverFactory(handlers);
|
|
4207
4477
|
if (disposed) {
|
|
4208
4478
|
await created.disconnect();
|
|
@@ -4538,7 +4808,7 @@ function createInRoomClient(options) {
|
|
|
4538
4808
|
}
|
|
4539
4809
|
function headers() {
|
|
4540
4810
|
const token = options.resolveRoomToken();
|
|
4541
|
-
return token === null ? void 0 : { [
|
|
4811
|
+
return token === null ? void 0 : { [MEETING_PASS_HEADER]: token };
|
|
4542
4812
|
}
|
|
4543
4813
|
function refusal2(response, operation, remedy) {
|
|
4544
4814
|
return refusalFromResponse(response, operation, remedy);
|
|
@@ -5285,25 +5555,37 @@ function MeetRuntime(props) {
|
|
|
5285
5555
|
api,
|
|
5286
5556
|
onDiagnostic: report,
|
|
5287
5557
|
// ADMISSION IS A JOIN, NOT A RE-RENDER. The lobby token grants neither
|
|
5288
|
-
// publish nor subscribe, so the host saying yes has to mint a new one.
|
|
5558
|
+
// publish nor subscribe, so the host saying yes has to mint a new one. The
|
|
5559
|
+
// "yes" is read from the server (the state read), never a broadcast (S4).
|
|
5289
5560
|
onAdmitted: () => {
|
|
5290
|
-
void engine.rejoin();
|
|
5291
|
-
},
|
|
5292
|
-
// A "no" is ALSO a mint: the server refuses it with its own sentence, which
|
|
5293
|
-
// turns the waiting room into the pre-join screen showing that sentence.
|
|
5294
|
-
onDenied: () => {
|
|
5295
5561
|
void engine.rejoin().catch(() => void 0);
|
|
5562
|
+
},
|
|
5563
|
+
rejoin: () => engine.rejoin(),
|
|
5564
|
+
// THE MEETING PASS this device holds — what the state read and `/leave` carry.
|
|
5565
|
+
resolvePass: () => {
|
|
5566
|
+
const roomName = store.snapshot().meeting?.roomName;
|
|
5567
|
+
const active = engine.token();
|
|
5568
|
+
if (active?.meetingPass) return active.meetingPass;
|
|
5569
|
+
return roomName === void 0 ? null : tokens.remembered(roomName)?.meetingPass ?? null;
|
|
5296
5570
|
}
|
|
5297
5571
|
});
|
|
5298
5572
|
const resolveRoomToken = () => {
|
|
5299
5573
|
const roomName = store.snapshot().meeting?.roomName;
|
|
5300
5574
|
if (roomName === void 0) return null;
|
|
5301
5575
|
const active = engine.token();
|
|
5302
|
-
if (active?.roomName === roomName && active.
|
|
5303
|
-
return tokens.remembered(roomName)?.
|
|
5576
|
+
if (active?.roomName === roomName && active.meetingPass) return active.meetingPass;
|
|
5577
|
+
return tokens.remembered(roomName)?.meetingPass ?? null;
|
|
5304
5578
|
};
|
|
5305
5579
|
const ai = createMeetAi({ api, store, resolveRoomToken });
|
|
5306
5580
|
const inRoom = createInRoomClient({ api, store, resolveRoomToken });
|
|
5581
|
+
store.subscribe(() => {
|
|
5582
|
+
const snapshot = store.snapshot();
|
|
5583
|
+
if (snapshot.phase === "lobby" && snapshot.waiting !== null && (snapshot.waiting.admission === "knocking" || snapshot.waiting.admission === "waiting_host")) {
|
|
5584
|
+
meetings.startWaiting();
|
|
5585
|
+
} else if (snapshot.phase !== "lobby") {
|
|
5586
|
+
meetings.stopWaiting();
|
|
5587
|
+
}
|
|
5588
|
+
});
|
|
5307
5589
|
let chatReadFor = null;
|
|
5308
5590
|
store.subscribe(() => {
|
|
5309
5591
|
const snapshot = store.snapshot();
|
|
@@ -5472,6 +5754,10 @@ function useLocalUserId() {
|
|
|
5472
5754
|
import { useEffect as useEffect2, useMemo as useMemo2, useRef as useRef2 } from "react";
|
|
5473
5755
|
|
|
5474
5756
|
// src/core/observation.ts
|
|
5757
|
+
function observedRole(role, standing) {
|
|
5758
|
+
if (role === "host" || role === "cohost") return role;
|
|
5759
|
+
return isLinkFollower(standing) ? "guest" : "participant";
|
|
5760
|
+
}
|
|
5475
5761
|
function observedDevice(track) {
|
|
5476
5762
|
if (track.enabled) return "on";
|
|
5477
5763
|
const refused = track.unavailable;
|
|
@@ -5499,13 +5785,15 @@ function observedPhase(input) {
|
|
|
5499
5785
|
const raw = snapshot.phase;
|
|
5500
5786
|
const live = raw === "connected" || raw === "reconnecting";
|
|
5501
5787
|
if (endedRecord(snapshot, input.meeting ?? null) && !live) return "ended";
|
|
5788
|
+
const reason = raw === "failed" ? snapshot.failure?.reason ?? null : null;
|
|
5789
|
+
if (reason !== null) return reason === "ended" ? "ended" : `refused:${reason}`;
|
|
5502
5790
|
switch (raw) {
|
|
5503
5791
|
case "idle":
|
|
5504
5792
|
case "failed":
|
|
5505
5793
|
case "left":
|
|
5506
5794
|
return "prejoin";
|
|
5507
5795
|
case "lobby":
|
|
5508
|
-
return
|
|
5796
|
+
return waitingPhase(snapshot);
|
|
5509
5797
|
case "requesting-token":
|
|
5510
5798
|
case "connecting":
|
|
5511
5799
|
return "joining";
|
|
@@ -5514,20 +5802,39 @@ function observedPhase(input) {
|
|
|
5514
5802
|
return "in_call";
|
|
5515
5803
|
}
|
|
5516
5804
|
}
|
|
5805
|
+
function waitingPhase(snapshot) {
|
|
5806
|
+
switch (snapshot.waiting?.admission) {
|
|
5807
|
+
case "waiting_host":
|
|
5808
|
+
return "waiting:host_not_started";
|
|
5809
|
+
case "denied":
|
|
5810
|
+
return "denied";
|
|
5811
|
+
case "expired":
|
|
5812
|
+
return "knock_expired";
|
|
5813
|
+
case "withdrawn":
|
|
5814
|
+
return "left";
|
|
5815
|
+
case "ended":
|
|
5816
|
+
return "ended";
|
|
5817
|
+
case "removed":
|
|
5818
|
+
return "refused:removed";
|
|
5819
|
+
default:
|
|
5820
|
+
return "waiting:knocking";
|
|
5821
|
+
}
|
|
5822
|
+
}
|
|
5517
5823
|
var ADMITTER_ROLES = /* @__PURE__ */ new Set(["host", "cohost"]);
|
|
5518
5824
|
function observe(input) {
|
|
5519
5825
|
const { snapshot } = input;
|
|
5520
5826
|
const phase = observedPhase(input);
|
|
5521
5827
|
const inCall = phase === "in_call";
|
|
5522
|
-
const
|
|
5523
|
-
const canAdmit =
|
|
5828
|
+
const actual = inCall && snapshot !== null ? effectiveRole(snapshot) : "none";
|
|
5829
|
+
const canAdmit = actual !== "none" && ADMITTER_ROLES.has(actual);
|
|
5830
|
+
const role = actual === "none" || snapshot === null ? "none" : observedRole(actual, effectiveStanding(snapshot));
|
|
5524
5831
|
const deviceInPlay = phase === "joining" || inCall;
|
|
5525
5832
|
return {
|
|
5526
5833
|
phase,
|
|
5527
|
-
reason: null,
|
|
5834
|
+
reason: phase.startsWith("refused:") ? phase.slice("refused:".length) : null,
|
|
5528
5835
|
role,
|
|
5529
5836
|
connection: snapshot?.phase === "reconnecting" ? "reconnecting" : "stable",
|
|
5530
|
-
hostPresent: inCall && snapshot !== null ? snapshot.participants.some((p) => ADMITTER_ROLES.has(p.role)) : null,
|
|
5837
|
+
hostPresent: inCall && snapshot !== null ? snapshot.participants.some((p) => ADMITTER_ROLES.has(p.role)) : snapshot?.phase === "lobby" ? snapshot.waiting?.admittersPresent ?? null : null,
|
|
5531
5838
|
lobbyCount: canAdmit && snapshot !== null ? snapshot.lobby.filter((entry) => entry.state === "knocking").length : 0,
|
|
5532
5839
|
recording: snapshot?.recording === "recording" || snapshot?.recording === "stopping",
|
|
5533
5840
|
audioBlocked: null,
|
|
@@ -5590,7 +5897,7 @@ function participantAttributes(p) {
|
|
|
5590
5897
|
"data-meet-participant": "",
|
|
5591
5898
|
"data-meet-participant-identity": p.identity,
|
|
5592
5899
|
"data-meet-participant-name": p.displayName,
|
|
5593
|
-
"data-meet-participant-role": p.role,
|
|
5900
|
+
"data-meet-participant-role": observedRole(p.role, p.standing),
|
|
5594
5901
|
"data-meet-participant-self": String(p.isLocal)
|
|
5595
5902
|
};
|
|
5596
5903
|
const quality = observedQuality(p.quality);
|
|
@@ -5919,7 +6226,7 @@ function summarizeMeetingRecordRestrictions(restrictions) {
|
|
|
5919
6226
|
async function loadGuestMeetingRecord(args) {
|
|
5920
6227
|
const { api, repository, meetingId, meeting, roomToken } = args;
|
|
5921
6228
|
const session = args.session === true;
|
|
5922
|
-
if (roomToken
|
|
6229
|
+
if (roomToken?.meetingPass == null && !session) {
|
|
5923
6230
|
return refusedBundle(
|
|
5924
6231
|
meeting,
|
|
5925
6232
|
"This meeting's record is only readable by the people who were in it.",
|
|
@@ -5929,12 +6236,12 @@ async function loadGuestMeetingRecord(args) {
|
|
|
5929
6236
|
let response;
|
|
5930
6237
|
try {
|
|
5931
6238
|
response = await api.request({
|
|
5932
|
-
path: `${MEET_ROUTES.record}?meeting_id=${encodeURIComponent(meetingId)}
|
|
6239
|
+
path: `${MEET_ROUTES.record}?meeting_id=${encodeURIComponent(meetingId)}` + (args.sessionId ? `&session_id=${encodeURIComponent(args.sessionId)}` : ""),
|
|
5933
6240
|
method: "GET",
|
|
5934
6241
|
// A GUEST HAS NO SESSION AND IS STILL A FIRST-CLASS READER (D6).
|
|
5935
6242
|
requireAuth: session,
|
|
5936
6243
|
operation: "meet.record",
|
|
5937
|
-
headers: roomToken
|
|
6244
|
+
headers: roomToken?.meetingPass != null ? { [MEETING_PASS_HEADER]: roomToken.meetingPass } : {}
|
|
5938
6245
|
});
|
|
5939
6246
|
} catch (error) {
|
|
5940
6247
|
return refusedBundle(
|
|
@@ -6488,12 +6795,22 @@ function useLobby() {
|
|
|
6488
6795
|
async () => requireSession(session, "admitAll").admitAll(),
|
|
6489
6796
|
[session]
|
|
6490
6797
|
),
|
|
6491
|
-
|
|
6492
|
-
(
|
|
6798
|
+
undeny: useCallback2(
|
|
6799
|
+
async (entry) => requireSession(session, "undeny").undeny(entry),
|
|
6493
6800
|
[session]
|
|
6494
6801
|
)
|
|
6495
6802
|
};
|
|
6496
6803
|
}
|
|
6804
|
+
function useWaiting() {
|
|
6805
|
+
const host = useMeetHost();
|
|
6806
|
+
const snapshot = useMeetSnapshot();
|
|
6807
|
+
const session = host?.meetings ?? null;
|
|
6808
|
+
return {
|
|
6809
|
+
waiting: snapshot?.waiting ?? null,
|
|
6810
|
+
withdraw: useCallback2(async () => requireSession(session, "withdraw").withdraw(), [session]),
|
|
6811
|
+
askAgain: useCallback2(async () => requireSession(session, "askAgain").askAgain(), [session])
|
|
6812
|
+
};
|
|
6813
|
+
}
|
|
6497
6814
|
function useRecording() {
|
|
6498
6815
|
const host = useMeetHost();
|
|
6499
6816
|
const snapshot = useMeetSnapshot();
|
|
@@ -7141,7 +7458,6 @@ function kindOf(person) {
|
|
|
7141
7458
|
if (person.isAgent) return "assistant";
|
|
7142
7459
|
if (person.role === "host") return "host";
|
|
7143
7460
|
if (person.role === "cohost") return "cohost";
|
|
7144
|
-
if (person.role === "guest") return person.userId !== null ? "signed-in guest" : "guest";
|
|
7145
7461
|
if (person.userId === null) return "guest";
|
|
7146
7462
|
return "member";
|
|
7147
7463
|
}
|
|
@@ -7426,7 +7742,7 @@ function meetingLength(meeting) {
|
|
|
7426
7742
|
if (start === null || end === null || end < start) return null;
|
|
7427
7743
|
return formatDurationMs2(end - start, { style: "long" });
|
|
7428
7744
|
}
|
|
7429
|
-
async function loadMeetingChat(api, meetingId,
|
|
7745
|
+
async function loadMeetingChat(api, meetingId, meetingPass = null) {
|
|
7430
7746
|
const operation = "meet.loadMeetingChat";
|
|
7431
7747
|
let response;
|
|
7432
7748
|
try {
|
|
@@ -7434,8 +7750,8 @@ async function loadMeetingChat(api, meetingId, roomToken = null) {
|
|
|
7434
7750
|
path: `${MEET_ROUTES.chat}?meeting_id=${encodeURIComponent(meetingId)}`,
|
|
7435
7751
|
method: "GET",
|
|
7436
7752
|
operation,
|
|
7437
|
-
requireAuth:
|
|
7438
|
-
...
|
|
7753
|
+
requireAuth: meetingPass === null,
|
|
7754
|
+
...meetingPass !== null ? { headers: { [MEETING_PASS_HEADER]: meetingPass } } : {}
|
|
7439
7755
|
});
|
|
7440
7756
|
} catch (error) {
|
|
7441
7757
|
return {
|
|
@@ -8908,10 +9224,13 @@ function RecordingIndicator() {
|
|
|
8908
9224
|
}
|
|
8909
9225
|
);
|
|
8910
9226
|
}
|
|
8911
|
-
function RoleBadge({
|
|
9227
|
+
function RoleBadge({
|
|
9228
|
+
role,
|
|
9229
|
+
standing
|
|
9230
|
+
}) {
|
|
8912
9231
|
if (role === "host") return /* @__PURE__ */ jsx5("span", { className: "mx-meet__badge", children: "Host" });
|
|
8913
9232
|
if (role === "cohost") return /* @__PURE__ */ jsx5("span", { className: "mx-meet__badge", children: "Co-host" });
|
|
8914
|
-
if (
|
|
9233
|
+
if (isLinkFollower(standing)) return /* @__PURE__ */ jsx5("span", { className: "mx-meet__badge", children: "Guest" });
|
|
8915
9234
|
return null;
|
|
8916
9235
|
}
|
|
8917
9236
|
function ParticipantTile({
|
|
@@ -8961,7 +9280,7 @@ function ParticipantTile({
|
|
|
8961
9280
|
participant.displayName,
|
|
8962
9281
|
participant.isLocal ? " (you)" : "",
|
|
8963
9282
|
participant.isAgent ? /* @__PURE__ */ jsx5("span", { className: "mx-meet__badge", children: "AI" }) : null,
|
|
8964
|
-
/* @__PURE__ */ jsx5(RoleBadge, { role: participant.role })
|
|
9283
|
+
/* @__PURE__ */ jsx5(RoleBadge, { role: participant.role, standing: participant.standing })
|
|
8965
9284
|
] }),
|
|
8966
9285
|
!participant.micEnabled ? /* @__PURE__ */ jsx5(MicOffIcon, { width: 14, height: 14 }) : null,
|
|
8967
9286
|
/* @__PURE__ */ jsx5(QualityBars, { quality: participant.quality })
|
|
@@ -9700,7 +10019,13 @@ function PeoplePanel({
|
|
|
9700
10019
|
/* @__PURE__ */ jsxs3("span", { className: "mx-meet__person-name", children: [
|
|
9701
10020
|
participant.displayName,
|
|
9702
10021
|
participant.identity === local ? /* @__PURE__ */ jsx5("span", { className: "mx-meet__you", children: " (you)" }) : null,
|
|
9703
|
-
/* @__PURE__ */ jsx5(
|
|
10022
|
+
/* @__PURE__ */ jsx5(
|
|
10023
|
+
RoleBadge,
|
|
10024
|
+
{
|
|
10025
|
+
role: participant.identity === local ? myRole : participant.role,
|
|
10026
|
+
standing: participant.standing
|
|
10027
|
+
}
|
|
10028
|
+
),
|
|
9704
10029
|
isNoteTaker(participant) ? /* @__PURE__ */ jsx5("span", { className: "mx-meet__badge", children: "Meeting assistant" }) : null,
|
|
9705
10030
|
snapshot?.spotlight === participant.identity ? /* @__PURE__ */ jsx5("span", { className: "mx-meet__badge", children: "Spotlight" }) : null
|
|
9706
10031
|
] }),
|
|
@@ -10616,7 +10941,7 @@ function MeetSkinRoom({
|
|
|
10616
10941
|
) });
|
|
10617
10942
|
}
|
|
10618
10943
|
if (phase === "lobby") {
|
|
10619
|
-
return /* @__PURE__ */ jsx5("div", { className: "mx-meet mx-meet--lobby", children: /* @__PURE__ */ jsx5(WaitingRoom, {
|
|
10944
|
+
return /* @__PURE__ */ jsx5("div", { className: "mx-meet mx-meet--lobby", children: /* @__PURE__ */ jsx5(WaitingRoom, { host, ...onLeave !== void 0 ? { onLeave } : {} }) });
|
|
10620
10945
|
}
|
|
10621
10946
|
return /* @__PURE__ */ jsxs3("div", { className: "mx-meet", children: [
|
|
10622
10947
|
/* @__PURE__ */ jsx5(ConsentNotice, {}),
|
|
@@ -10641,21 +10966,58 @@ function MeetSkinRoom({
|
|
|
10641
10966
|
/* @__PURE__ */ jsx5(ControlBar, { ...onLeave !== void 0 ? { onLeave } : {} })
|
|
10642
10967
|
] });
|
|
10643
10968
|
}
|
|
10644
|
-
function WaitingRoom({
|
|
10645
|
-
const
|
|
10646
|
-
|
|
10647
|
-
|
|
10648
|
-
|
|
10649
|
-
|
|
10650
|
-
|
|
10969
|
+
function WaitingRoom({ host, onLeave }) {
|
|
10970
|
+
const { waiting, withdraw, askAgain } = useWaiting();
|
|
10971
|
+
const [busy, setBusy] = useState4(false);
|
|
10972
|
+
const admission = waiting?.admission ?? "knocking";
|
|
10973
|
+
const run = (action) => () => {
|
|
10974
|
+
setBusy(true);
|
|
10975
|
+
void action().catch(() => void 0).finally(() => setBusy(false));
|
|
10976
|
+
};
|
|
10977
|
+
const home = /* @__PURE__ */ jsx5(Button, { type: "button", variant: "outline", onClick: () => onLeave !== void 0 ? onLeave() : window.location.assign("/"), children: "Back to home" });
|
|
10978
|
+
let title;
|
|
10979
|
+
let line = null;
|
|
10980
|
+
let actions;
|
|
10981
|
+
switch (admission) {
|
|
10982
|
+
case "waiting_host":
|
|
10983
|
+
title = "Waiting for the host to start";
|
|
10984
|
+
actions = /* @__PURE__ */ jsx5(Button, { type: "button", variant: "outline", disabled: busy, onClick: run(withdraw), children: "Leave" });
|
|
10985
|
+
break;
|
|
10986
|
+
case "denied":
|
|
10987
|
+
title = "The host declined your request";
|
|
10988
|
+
actions = /* @__PURE__ */ jsxs3(Fragment, { children: [
|
|
10989
|
+
waiting?.reknockAllowed === true ? /* @__PURE__ */ jsx5(Button, { type: "button", disabled: busy, onClick: run(askAgain), children: "Ask to join again" }) : null,
|
|
10990
|
+
home
|
|
10991
|
+
] });
|
|
10992
|
+
break;
|
|
10993
|
+
case "expired":
|
|
10994
|
+
title = "No one responded";
|
|
10995
|
+
actions = /* @__PURE__ */ jsxs3(Fragment, { children: [
|
|
10996
|
+
/* @__PURE__ */ jsx5(Button, { type: "button", disabled: busy, onClick: run(askAgain), children: "Ask again" }),
|
|
10997
|
+
home
|
|
10998
|
+
] });
|
|
10999
|
+
break;
|
|
11000
|
+
case "withdrawn":
|
|
11001
|
+
title = "You left the waiting room";
|
|
11002
|
+
actions = /* @__PURE__ */ jsxs3(Fragment, { children: [
|
|
11003
|
+
/* @__PURE__ */ jsx5(Button, { type: "button", disabled: busy, onClick: run(askAgain), children: "Rejoin" }),
|
|
11004
|
+
home
|
|
11005
|
+
] });
|
|
11006
|
+
break;
|
|
11007
|
+
case "ended":
|
|
11008
|
+
title = "The meeting has ended";
|
|
11009
|
+
actions = home;
|
|
11010
|
+
break;
|
|
11011
|
+
default:
|
|
11012
|
+
title = "Waiting for the host to let you in";
|
|
11013
|
+
line = waiting?.admittersPresent === false ? "The host isn't here yet" : null;
|
|
11014
|
+
actions = /* @__PURE__ */ jsx5(Button, { type: "button", variant: "outline", disabled: busy, onClick: run(withdraw), children: "Cancel request" });
|
|
11015
|
+
}
|
|
10651
11016
|
return /* @__PURE__ */ jsxs3("div", { className: "mx-meet__waiting", children: [
|
|
10652
11017
|
/* @__PURE__ */ jsx5(Avatar, { name: host.identity.displayName, url: host.identity.avatarUrl, size: 72 }),
|
|
10653
|
-
/* @__PURE__ */ jsx5("h1", { className: "mx-meet__prejoin-title", children:
|
|
10654
|
-
/* @__PURE__ */
|
|
10655
|
-
|
|
10656
|
-
host.identity.displayName,
|
|
10657
|
-
". You will join automatically once you are admitted."
|
|
10658
|
-
] })
|
|
11018
|
+
/* @__PURE__ */ jsx5("h1", { className: "mx-meet__prejoin-title", children: title }),
|
|
11019
|
+
line !== null ? /* @__PURE__ */ jsx5("p", { className: "mx-meet__empty-body", role: "status", "data-meet-notice": "host-absent", children: line }) : null,
|
|
11020
|
+
/* @__PURE__ */ jsx5("div", { className: "mx-meet__waiting-actions", children: actions })
|
|
10659
11021
|
] });
|
|
10660
11022
|
}
|
|
10661
11023
|
function HostMenu() {
|
|
@@ -10862,7 +11224,7 @@ function MeetingRecordBody({
|
|
|
10862
11224
|
/* @__PURE__ */ jsxs4("span", { className: "mx-meet__person-name", children: [
|
|
10863
11225
|
displayNameFor(bundle.names, person.identity, person.displayName),
|
|
10864
11226
|
person.role === "host" ? /* @__PURE__ */ jsx6("span", { className: "mx-meet__badge", children: "Host" }) : null,
|
|
10865
|
-
person.
|
|
11227
|
+
person.userId === null && !person.isAgent ? /* @__PURE__ */ jsx6("span", { className: "mx-meet__badge", children: "Guest" }) : null,
|
|
10866
11228
|
person.isAgent ? /* @__PURE__ */ jsx6("span", { className: "mx-meet__badge", children: "Meeting assistant" }) : null
|
|
10867
11229
|
] })
|
|
10868
11230
|
] }, person.id)) })
|
|
@@ -11381,6 +11743,7 @@ export {
|
|
|
11381
11743
|
LISTEN_ONLY_NOTICE,
|
|
11382
11744
|
MEETING_INVITE_KIND,
|
|
11383
11745
|
MEETING_INVITE_VERSION,
|
|
11746
|
+
MEETING_PASS_HEADER,
|
|
11384
11747
|
MEETING_RECORD_GRACE_MS,
|
|
11385
11748
|
MEET_ANSWER_CONTEXT_EVENT,
|
|
11386
11749
|
MEET_ANSWER_EVENTS,
|
|
@@ -11401,12 +11764,12 @@ export {
|
|
|
11401
11764
|
ORGANIZATION_HEADER,
|
|
11402
11765
|
PARTICIPANT_CAN_RECORD_ATTRIBUTE,
|
|
11403
11766
|
PARTICIPANT_ROLE_ATTRIBUTE,
|
|
11767
|
+
PARTICIPANT_STANDING_ATTRIBUTE,
|
|
11404
11768
|
REACTION_TTL_MS,
|
|
11405
11769
|
REFUSED_ABSENCE,
|
|
11406
11770
|
ROLE_RANK,
|
|
11407
11771
|
ROOM_EVENTS,
|
|
11408
11772
|
ROOM_EVENT_PREFIX,
|
|
11409
|
-
ROOM_TOKEN_HEADER,
|
|
11410
11773
|
RPCS,
|
|
11411
11774
|
RecordingPlayer,
|
|
11412
11775
|
TABLES,
|
|
@@ -11478,6 +11841,7 @@ export {
|
|
|
11478
11841
|
isGuest,
|
|
11479
11842
|
isGuestIdentity,
|
|
11480
11843
|
isHostRole,
|
|
11844
|
+
isLinkFollower,
|
|
11481
11845
|
isListenOnly,
|
|
11482
11846
|
isNoteTaker,
|
|
11483
11847
|
isRealName,
|
|
@@ -11496,6 +11860,7 @@ export {
|
|
|
11496
11860
|
meetingInviteAction,
|
|
11497
11861
|
meetingLength,
|
|
11498
11862
|
meetingLink,
|
|
11863
|
+
meetingPassHeaders,
|
|
11499
11864
|
meetingPath,
|
|
11500
11865
|
meetingRecordCache,
|
|
11501
11866
|
meetingSlug,
|
|
@@ -11540,6 +11905,7 @@ export {
|
|
|
11540
11905
|
recordingLabel,
|
|
11541
11906
|
recordingNotice,
|
|
11542
11907
|
recordingOffsetMs,
|
|
11908
|
+
rememberedMeetingPass,
|
|
11543
11909
|
roleOf,
|
|
11544
11910
|
roomTopic,
|
|
11545
11911
|
rootAttributes,
|