@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.cjs
CHANGED
|
@@ -55,6 +55,7 @@ __export(react_exports, {
|
|
|
55
55
|
LISTEN_ONLY_NOTICE: () => LISTEN_ONLY_NOTICE,
|
|
56
56
|
MEETING_INVITE_KIND: () => MEETING_INVITE_KIND,
|
|
57
57
|
MEETING_INVITE_VERSION: () => MEETING_INVITE_VERSION,
|
|
58
|
+
MEETING_PASS_HEADER: () => MEETING_PASS_HEADER,
|
|
58
59
|
MEETING_RECORD_GRACE_MS: () => MEETING_RECORD_GRACE_MS,
|
|
59
60
|
MEET_ANSWER_CONTEXT_EVENT: () => MEET_ANSWER_CONTEXT_EVENT,
|
|
60
61
|
MEET_ANSWER_EVENTS: () => MEET_ANSWER_EVENTS,
|
|
@@ -75,12 +76,12 @@ __export(react_exports, {
|
|
|
75
76
|
ORGANIZATION_HEADER: () => ORGANIZATION_HEADER,
|
|
76
77
|
PARTICIPANT_CAN_RECORD_ATTRIBUTE: () => PARTICIPANT_CAN_RECORD_ATTRIBUTE,
|
|
77
78
|
PARTICIPANT_ROLE_ATTRIBUTE: () => PARTICIPANT_ROLE_ATTRIBUTE,
|
|
79
|
+
PARTICIPANT_STANDING_ATTRIBUTE: () => PARTICIPANT_STANDING_ATTRIBUTE,
|
|
78
80
|
REACTION_TTL_MS: () => REACTION_TTL_MS,
|
|
79
81
|
REFUSED_ABSENCE: () => REFUSED_ABSENCE,
|
|
80
82
|
ROLE_RANK: () => ROLE_RANK,
|
|
81
83
|
ROOM_EVENTS: () => ROOM_EVENTS,
|
|
82
84
|
ROOM_EVENT_PREFIX: () => ROOM_EVENT_PREFIX,
|
|
83
|
-
ROOM_TOKEN_HEADER: () => ROOM_TOKEN_HEADER,
|
|
84
85
|
RPCS: () => RPCS,
|
|
85
86
|
RecordingPlayer: () => RecordingPlayer,
|
|
86
87
|
TABLES: () => TABLES,
|
|
@@ -152,6 +153,7 @@ __export(react_exports, {
|
|
|
152
153
|
isGuest: () => isGuest,
|
|
153
154
|
isGuestIdentity: () => isGuestIdentity,
|
|
154
155
|
isHostRole: () => isHostRole,
|
|
156
|
+
isLinkFollower: () => isLinkFollower,
|
|
155
157
|
isListenOnly: () => isListenOnly,
|
|
156
158
|
isNoteTaker: () => isNoteTaker,
|
|
157
159
|
isRealName: () => isRealName,
|
|
@@ -170,6 +172,7 @@ __export(react_exports, {
|
|
|
170
172
|
meetingInviteAction: () => meetingInviteAction,
|
|
171
173
|
meetingLength: () => meetingLength,
|
|
172
174
|
meetingLink: () => meetingLink,
|
|
175
|
+
meetingPassHeaders: () => meetingPassHeaders,
|
|
173
176
|
meetingPath: () => meetingPath,
|
|
174
177
|
meetingRecordCache: () => meetingRecordCache,
|
|
175
178
|
meetingSlug: () => meetingSlug,
|
|
@@ -214,6 +217,7 @@ __export(react_exports, {
|
|
|
214
217
|
recordingLabel: () => recordingLabel,
|
|
215
218
|
recordingNotice: () => recordingNotice,
|
|
216
219
|
recordingOffsetMs: () => recordingOffsetMs,
|
|
220
|
+
rememberedMeetingPass: () => rememberedMeetingPass,
|
|
217
221
|
roleOf: () => roleOf,
|
|
218
222
|
roomTopic: () => roomTopic,
|
|
219
223
|
rootAttributes: () => rootAttributes,
|
|
@@ -294,11 +298,18 @@ var MeetError = class extends Error {
|
|
|
294
298
|
/** What to DO. Rendered to the user for the device codes. */
|
|
295
299
|
remedy;
|
|
296
300
|
cause;
|
|
297
|
-
|
|
301
|
+
/**
|
|
302
|
+
* The join gate's reason code (CORE-DESIGN §2.5) when the server named one: `not_found`,
|
|
303
|
+
* `cancelled`, `ended`, `locked`, `removed`, `lobby_rejects`, `not_invited`… The phase
|
|
304
|
+
* renders `refused:<reason>` (or `ended`) from it — never from the HTTP status.
|
|
305
|
+
*/
|
|
306
|
+
reason;
|
|
307
|
+
constructor(code, message, remedy, cause, reason) {
|
|
298
308
|
super(message);
|
|
299
309
|
this.name = "MeetError";
|
|
300
310
|
this.code = code;
|
|
301
311
|
this.remedy = remedy;
|
|
312
|
+
this.reason = reason ?? null;
|
|
302
313
|
if (cause !== void 0) this.cause = cause;
|
|
303
314
|
}
|
|
304
315
|
/** True when retrying after the host re-establishes a session is the fix. */
|
|
@@ -516,6 +527,17 @@ function createWebRoomTokenStorage(onDiagnostic) {
|
|
|
516
527
|
}
|
|
517
528
|
};
|
|
518
529
|
}
|
|
530
|
+
function rememberedMeetingPass(roomName, storage = createWebRoomTokenStorage()) {
|
|
531
|
+
const raw = storage.read(roomName);
|
|
532
|
+
if (raw === null) return null;
|
|
533
|
+
try {
|
|
534
|
+
const parsed = JSON.parse(raw);
|
|
535
|
+
if (parsed.standing !== "guest") return null;
|
|
536
|
+
return typeof parsed.meetingPass === "string" && parsed.meetingPass.length > 0 ? parsed.meetingPass : null;
|
|
537
|
+
} catch {
|
|
538
|
+
return null;
|
|
539
|
+
}
|
|
540
|
+
}
|
|
519
541
|
var MEET_ROUTES = {
|
|
520
542
|
token: "/api/v1/meet/token",
|
|
521
543
|
recordingStart: "/api/v1/meet/recording/start",
|
|
@@ -529,7 +551,7 @@ var MEET_ROUTES = {
|
|
|
529
551
|
/**
|
|
530
552
|
* THE POST-MEETING RECORD FOR A READER WITH NO DATABASE (MRF-1). A `GET`,
|
|
531
553
|
* authorized by the room token this device already holds, carried in the
|
|
532
|
-
* `x-meet-
|
|
554
|
+
* `x-meet-pass` header — never a query parameter, because a bearer
|
|
533
555
|
* credential in a URL lands in access logs, referrers and history.
|
|
534
556
|
*/
|
|
535
557
|
record: "/api/v1/meet/record",
|
|
@@ -537,7 +559,7 @@ var MEET_ROUTES = {
|
|
|
537
559
|
recordAccess: "/api/v1/meet/record/access",
|
|
538
560
|
/**
|
|
539
561
|
* KEEP YOUR NOTES (2026-10-01): a guest who just created an account presents
|
|
540
|
-
* their kept pass in `x-meet-
|
|
562
|
+
* their kept pass in `x-meet-pass` with their new session, and the
|
|
541
563
|
* meeting they attended becomes theirs to read. Idempotent; never a grant.
|
|
542
564
|
*/
|
|
543
565
|
claim: "/api/v1/meet/claim",
|
|
@@ -559,7 +581,7 @@ var MEET_ROUTES = {
|
|
|
559
581
|
* Meet wave 2: EVERY in-meeting host power (mute, stop video, mute all, ask to
|
|
560
582
|
* unmute, spotlight, lower hands, co-host, hand over, rename, remove, report).
|
|
561
583
|
* Decided by the server from the meeting itself; a guest's pass rides
|
|
562
|
-
* `x-meet-
|
|
584
|
+
* `x-meet-pass`.
|
|
563
585
|
*/
|
|
564
586
|
moderate: "/api/v1/meet/moderate",
|
|
565
587
|
/** Meet wave 2: the SAVED chat — `POST` sends to everyone, `GET ?meeting_id=` reads history. */
|
|
@@ -570,12 +592,26 @@ var MEET_ROUTES = {
|
|
|
570
592
|
*/
|
|
571
593
|
collab: "/api/v1/meet/collab",
|
|
572
594
|
/** Meet wave 5: translate finished caption lines into this viewer's language. */
|
|
573
|
-
captionsTranslate: "/api/v1/meet/captions/translate"
|
|
595
|
+
captionsTranslate: "/api/v1/meet/captions/translate",
|
|
596
|
+
/**
|
|
597
|
+
* S4 — THE STATE READ (CORE-DESIGN §2.6): `GET ?meeting_id=` with `x-meet-pass`. This
|
|
598
|
+
* person's admission (lazy expiry applied), whether anyone who can admit is here, the run's
|
|
599
|
+
* phase. Read every `waiting_poll_seconds` while waiting and on every `mx.state.changed`.
|
|
600
|
+
*/
|
|
601
|
+
state: "/api/v1/meet/state",
|
|
602
|
+
/** S4: `withdraw` (stop waiting) or `leave` (close this tab); the pass may ride in the body. */
|
|
603
|
+
leave: "/api/v1/meet/leave",
|
|
604
|
+
/** S4: a pass holder (guests included) saw the recording notice, for this run. */
|
|
605
|
+
consent: "/api/v1/meet/consent"
|
|
574
606
|
};
|
|
575
607
|
function meetingPath(template, meetingId) {
|
|
576
608
|
return template.replace("{meeting_id}", encodeURIComponent(meetingId));
|
|
577
609
|
}
|
|
578
|
-
var
|
|
610
|
+
var MEETING_PASS_HEADER = "x-meet-pass";
|
|
611
|
+
function meetingPassHeaders(token) {
|
|
612
|
+
const pass = token?.meetingPass ?? null;
|
|
613
|
+
return pass !== null && pass.length > 0 ? { [MEETING_PASS_HEADER]: pass } : {};
|
|
614
|
+
}
|
|
579
615
|
var REFRESH_RATIO = 0.8;
|
|
580
616
|
function readString(source, key) {
|
|
581
617
|
const value = source[key];
|
|
@@ -585,7 +621,12 @@ function readBoolean(source, key, fallback) {
|
|
|
585
621
|
const value = source[key];
|
|
586
622
|
return typeof value === "boolean" ? value : fallback;
|
|
587
623
|
}
|
|
588
|
-
var
|
|
624
|
+
var TOKEN_STANDINGS = ["member", "invited", "outsider", "guest"];
|
|
625
|
+
function readStanding(source, key) {
|
|
626
|
+
const value = source[key];
|
|
627
|
+
return TOKEN_STANDINGS.includes(value) ? value : null;
|
|
628
|
+
}
|
|
629
|
+
var TOKEN_ROLES = ["host", "cohost", "presenter", "participant", "viewer"];
|
|
589
630
|
function readRole(source, key) {
|
|
590
631
|
const value = source[key];
|
|
591
632
|
return TOKEN_ROLES.includes(value) ? value : null;
|
|
@@ -628,8 +669,15 @@ function parseRoomToken(body, roomName) {
|
|
|
628
669
|
// Deriving the role from what DID arrive keeps a host a host against a
|
|
629
670
|
// server that has not landed MRI-A11 — and never invents a host from
|
|
630
671
|
// silence, because both fallbacks below grant nothing.
|
|
631
|
-
|
|
672
|
+
// A word this package does not know grants nothing: `participant`, never a host.
|
|
673
|
+
role: readRole(payload, "role") ?? (isHost ? "host" : "participant"),
|
|
674
|
+
standing: readStanding(payload, "standing"),
|
|
675
|
+
meetingPass: readString(payload, "meeting_pass"),
|
|
676
|
+
meetingPassExpiresAt: readString(payload, "meeting_pass_expires_at"),
|
|
677
|
+
sessionId: readString(payload, "session_id"),
|
|
632
678
|
lobby: readBoolean(payload, "lobby", false),
|
|
679
|
+
admissionState: readString(payload, "admission_state") ?? void 0,
|
|
680
|
+
pollSeconds: typeof payload["poll_seconds"] === "number" ? payload["poll_seconds"] : void 0,
|
|
633
681
|
breakoutRoomId: readString(payload, "breakout_room_id") ?? null
|
|
634
682
|
};
|
|
635
683
|
}
|
|
@@ -639,7 +687,7 @@ function createTokenClient(options) {
|
|
|
639
687
|
const storage = options.storage;
|
|
640
688
|
function remember(token) {
|
|
641
689
|
if (storage === void 0) return;
|
|
642
|
-
if (
|
|
690
|
+
if (token.standing !== "guest") return;
|
|
643
691
|
storage.write(token.roomName, JSON.stringify(token));
|
|
644
692
|
}
|
|
645
693
|
function recall(roomName) {
|
|
@@ -697,22 +745,28 @@ function createTokenClient(options) {
|
|
|
697
745
|
throw new MeetError(
|
|
698
746
|
"forbidden",
|
|
699
747
|
said?.message ?? "You can't join this meeting.",
|
|
700
|
-
said?.remedy ?? "Ask the person who shared the link to let you in, then open the link again."
|
|
748
|
+
said?.remedy ?? "Ask the person who shared the link to let you in, then open the link again.",
|
|
749
|
+
void 0,
|
|
750
|
+
said?.reason ?? null
|
|
701
751
|
);
|
|
702
752
|
}
|
|
703
753
|
if (response.status === 404) {
|
|
704
754
|
throw new MeetError(
|
|
705
755
|
"not-found",
|
|
706
756
|
"meet.token: no such meeting",
|
|
707
|
-
"The link is wrong or the meeting was deleted. Show the 'meeting not found' screen; a durable link that 404s is never a retry."
|
|
757
|
+
"The link is wrong or the meeting was deleted. Show the 'meeting not found' screen; a durable link that 404s is never a retry.",
|
|
758
|
+
void 0,
|
|
759
|
+
"not_found"
|
|
708
760
|
);
|
|
709
761
|
}
|
|
710
762
|
if (response.status === 410) {
|
|
711
|
-
const
|
|
763
|
+
const said = await serverSentence(response);
|
|
712
764
|
throw new MeetError(
|
|
713
765
|
"cancelled",
|
|
714
|
-
|
|
715
|
-
"Nobody can join a cancelled meeting. Show the cancelled screen with this sentence; ask the host to schedule a new one."
|
|
766
|
+
said?.message ?? "This meeting was cancelled.",
|
|
767
|
+
"Nobody can join a cancelled meeting. Show the cancelled screen with this sentence; ask the host to schedule a new one.",
|
|
768
|
+
void 0,
|
|
769
|
+
said?.reason ?? "cancelled"
|
|
716
770
|
);
|
|
717
771
|
}
|
|
718
772
|
if (response.status === 423) {
|
|
@@ -720,7 +774,9 @@ function createTokenClient(options) {
|
|
|
720
774
|
throw new MeetError(
|
|
721
775
|
"room-locked",
|
|
722
776
|
said?.message ?? "The meeting is locked.",
|
|
723
|
-
said?.remedy ?? "The host locked the room. Ask them to unlock it, then join again."
|
|
777
|
+
said?.remedy ?? "The host locked the room. Ask them to unlock it, then join again.",
|
|
778
|
+
void 0,
|
|
779
|
+
said?.reason ?? "locked"
|
|
724
780
|
);
|
|
725
781
|
}
|
|
726
782
|
if (response.status === 503) {
|
|
@@ -789,6 +845,10 @@ function placeholder(roomName) {
|
|
|
789
845
|
canRecord: false,
|
|
790
846
|
isHost: false,
|
|
791
847
|
role: "participant",
|
|
848
|
+
standing: null,
|
|
849
|
+
meetingPass: null,
|
|
850
|
+
meetingPassExpiresAt: null,
|
|
851
|
+
sessionId: null,
|
|
792
852
|
lobby: false,
|
|
793
853
|
breakoutRoomId: null
|
|
794
854
|
};
|
|
@@ -801,7 +861,8 @@ async function serverSentence(response) {
|
|
|
801
861
|
const message = typeof source.user_message === "string" && source.user_message.trim() !== "" ? source.user_message : typeof source.message === "string" && source.message.trim() !== "" ? source.message : null;
|
|
802
862
|
if (message === null) return null;
|
|
803
863
|
const remedy = typeof source.remedy === "string" && source.remedy.trim() !== "" ? source.remedy : void 0;
|
|
804
|
-
|
|
864
|
+
const reason = typeof source.reason === "string" && source.reason !== "" ? source.reason : null;
|
|
865
|
+
return { message, remedy, reason };
|
|
805
866
|
} catch {
|
|
806
867
|
return null;
|
|
807
868
|
}
|
|
@@ -990,7 +1051,7 @@ function createMeetAi(options) {
|
|
|
990
1051
|
operation: "meet.ai.ask",
|
|
991
1052
|
// A GUEST MAY ASK. The server decides whether it can answer.
|
|
992
1053
|
requireAuth: false,
|
|
993
|
-
...roomToken !== null ? { headers: { [
|
|
1054
|
+
...roomToken !== null ? { headers: { [MEETING_PASS_HEADER]: roomToken } } : {},
|
|
994
1055
|
...signal !== void 0 ? { signal } : {}
|
|
995
1056
|
});
|
|
996
1057
|
if (!response.ok) {
|
|
@@ -1204,14 +1265,15 @@ var MEET_EVENTS = {
|
|
|
1204
1265
|
cancel: "mx.call.cancel",
|
|
1205
1266
|
/** You answered (or declined) on another device. Stop ringing here. */
|
|
1206
1267
|
settled: "mx.call.settled",
|
|
1207
|
-
/**
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1268
|
+
/**
|
|
1269
|
+
* 🚨 S4 (CORE-DESIGN §2.6): the ONE room-channel event — payload-free "something changed,
|
|
1270
|
+
* read again". Nothing a person must see rides it: a waiting person learns admitted /
|
|
1271
|
+
* denied / expired / ended by READING their row (`GET /v1/meet/state`) on a timer and on
|
|
1272
|
+
* every hint. The four payload broadcasts (`mx.lobby.knock`, `mx.lobby.admission`,
|
|
1273
|
+
* `mx.room.lock`, `mx.room.recording`) are gone with their senders and receivers. The
|
|
1274
|
+
* server publishes the same event on the same topic (aidream `admission.room_topic`).
|
|
1275
|
+
*/
|
|
1276
|
+
stateChanged: "mx.state.changed"
|
|
1215
1277
|
};
|
|
1216
1278
|
|
|
1217
1279
|
// src/core/types.ts
|
|
@@ -1225,14 +1287,19 @@ function isGuest(identity) {
|
|
|
1225
1287
|
return identity.userId === null;
|
|
1226
1288
|
}
|
|
1227
1289
|
var ROLE_RANK = {
|
|
1228
|
-
host:
|
|
1229
|
-
cohost:
|
|
1290
|
+
host: 4,
|
|
1291
|
+
cohost: 3,
|
|
1292
|
+
presenter: 2,
|
|
1230
1293
|
participant: 1,
|
|
1231
|
-
|
|
1294
|
+
viewer: 0
|
|
1232
1295
|
};
|
|
1296
|
+
function isLinkFollower(standing) {
|
|
1297
|
+
return standing === "guest" || standing === "outsider";
|
|
1298
|
+
}
|
|
1233
1299
|
function isHostRole(role) {
|
|
1234
1300
|
return role === "host" || role === "cohost";
|
|
1235
1301
|
}
|
|
1302
|
+
var WAITING_ADMISSION_STATES = ["waiting_host", "knocking"];
|
|
1236
1303
|
|
|
1237
1304
|
// src/core/calls.ts
|
|
1238
1305
|
var TRANSITIONS = {
|
|
@@ -2136,15 +2203,15 @@ var CAPTION_LANGUAGES = [
|
|
|
2136
2203
|
{ tag: "id", name: "Indonesian" },
|
|
2137
2204
|
{ tag: "sv", name: "Swedish" }
|
|
2138
2205
|
];
|
|
2139
|
-
async function loadMeetingActivities(api, meetingId,
|
|
2206
|
+
async function loadMeetingActivities(api, meetingId, meetingPass = null) {
|
|
2140
2207
|
let response;
|
|
2141
2208
|
try {
|
|
2142
2209
|
response = await api.request({
|
|
2143
2210
|
path: `${MEET_ROUTES.collab}?meeting_id=${encodeURIComponent(meetingId)}`,
|
|
2144
2211
|
method: "GET",
|
|
2145
2212
|
operation: "meet.loadMeetingActivities",
|
|
2146
|
-
requireAuth:
|
|
2147
|
-
...
|
|
2213
|
+
requireAuth: meetingPass === null,
|
|
2214
|
+
...meetingPass !== null ? { headers: { [MEETING_PASS_HEADER]: meetingPass } } : {}
|
|
2148
2215
|
});
|
|
2149
2216
|
} catch (error) {
|
|
2150
2217
|
return {
|
|
@@ -2241,12 +2308,14 @@ var DEFAULT_EFFECTS = {
|
|
|
2241
2308
|
};
|
|
2242
2309
|
var NO_GRANTS = {
|
|
2243
2310
|
role: "participant",
|
|
2311
|
+
standing: null,
|
|
2244
2312
|
canRecord: false,
|
|
2245
2313
|
canPublish: false,
|
|
2246
2314
|
canSubscribe: false
|
|
2247
2315
|
};
|
|
2248
2316
|
var EMPTY = {
|
|
2249
2317
|
phase: "idle",
|
|
2318
|
+
waiting: null,
|
|
2250
2319
|
meeting: null,
|
|
2251
2320
|
localIdentity: null,
|
|
2252
2321
|
grants: NO_GRANTS,
|
|
@@ -2287,6 +2356,11 @@ function effectiveRole(snapshot) {
|
|
|
2287
2356
|
const fromToken = snapshot.grants.role;
|
|
2288
2357
|
return ROLE_RANK[fromRoster] >= ROLE_RANK[fromToken] ? fromRoster : fromToken;
|
|
2289
2358
|
}
|
|
2359
|
+
function effectiveStanding(snapshot) {
|
|
2360
|
+
if (snapshot.grants.standing != null) return snapshot.grants.standing;
|
|
2361
|
+
const me = snapshot.participants.find((p) => p.identity === snapshot.localIdentity);
|
|
2362
|
+
return me?.standing ?? null;
|
|
2363
|
+
}
|
|
2290
2364
|
var REACTION_TTL_MS = 5e3;
|
|
2291
2365
|
var CHAT_LIMIT = 200;
|
|
2292
2366
|
function createMeetStore(initial = {}) {
|
|
@@ -2498,19 +2572,25 @@ function roleOf(participants, identity) {
|
|
|
2498
2572
|
return participants.find((p) => p.identity === identity)?.role ?? "participant";
|
|
2499
2573
|
}
|
|
2500
2574
|
var END_MEETING_NOT_SERVED = "Ending a meeting is not available on this server yet";
|
|
2501
|
-
function lobbyEntryFrom(attendee) {
|
|
2502
|
-
if (attendee.admissionState
|
|
2575
|
+
function lobbyEntryFrom(attendee, now = Date.now()) {
|
|
2576
|
+
if (!WAITING_ADMISSION_STATES.includes(attendee.admissionState)) return null;
|
|
2577
|
+
const expires = attendee.knockExpiresAt ? Date.parse(attendee.knockExpiresAt) : Number.NaN;
|
|
2578
|
+
if (!Number.isNaN(expires) && expires <= now) return null;
|
|
2503
2579
|
return {
|
|
2504
2580
|
identity: attendee.identity,
|
|
2505
2581
|
displayName: attendee.displayName,
|
|
2506
2582
|
avatarUrl: attendee.avatarUrl,
|
|
2507
2583
|
userId: attendee.userId,
|
|
2508
2584
|
state: "knocking",
|
|
2509
|
-
knockedAt: attendee.joinedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
2585
|
+
knockedAt: attendee.knockedAt ?? attendee.joinedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
2586
|
+
knockExpiresAt: attendee.knockExpiresAt ?? null,
|
|
2510
2587
|
decidedAt: null,
|
|
2511
2588
|
decidedBy: null
|
|
2512
2589
|
};
|
|
2513
2590
|
}
|
|
2591
|
+
function decidedState(state) {
|
|
2592
|
+
return WAITING_ADMISSION_STATES.includes(state) ? "expired" : state;
|
|
2593
|
+
}
|
|
2514
2594
|
function createMeetingSession(options) {
|
|
2515
2595
|
const { manager, repository, store, identity } = options;
|
|
2516
2596
|
let channel = null;
|
|
@@ -2534,11 +2614,6 @@ function createMeetingSession(options) {
|
|
|
2534
2614
|
}
|
|
2535
2615
|
return identity.userId;
|
|
2536
2616
|
}
|
|
2537
|
-
function knockIdentity() {
|
|
2538
|
-
return asParticipantIdentity(
|
|
2539
|
-
identity.userId !== null ? `user:${identity.userId}` : `guest:${identity.deviceId}`
|
|
2540
|
-
);
|
|
2541
|
-
}
|
|
2542
2617
|
function requireMeeting(operation) {
|
|
2543
2618
|
const meeting = store.snapshot().meeting;
|
|
2544
2619
|
if (meeting === null) {
|
|
@@ -2550,6 +2625,191 @@ function createMeetingSession(options) {
|
|
|
2550
2625
|
}
|
|
2551
2626
|
return meeting;
|
|
2552
2627
|
}
|
|
2628
|
+
let polling = false;
|
|
2629
|
+
let pollTimer = null;
|
|
2630
|
+
let reading = null;
|
|
2631
|
+
let admittedOnce = false;
|
|
2632
|
+
let unlisten = null;
|
|
2633
|
+
function report(level, message, remedy) {
|
|
2634
|
+
options.onDiagnostic?.(remedy === void 0 ? { level, message } : { level, message, remedy });
|
|
2635
|
+
}
|
|
2636
|
+
function pollMs() {
|
|
2637
|
+
const seconds = store.snapshot().waiting?.pollSeconds ?? 3;
|
|
2638
|
+
return Math.max(1, Math.min(60, seconds)) * 1e3;
|
|
2639
|
+
}
|
|
2640
|
+
function schedule(ms2) {
|
|
2641
|
+
if (pollTimer !== null) clearTimeout(pollTimer);
|
|
2642
|
+
if (!polling) return;
|
|
2643
|
+
pollTimer = setTimeout(() => {
|
|
2644
|
+
pollTimer = null;
|
|
2645
|
+
void readState2().finally(() => schedule(pollMs()));
|
|
2646
|
+
}, ms2);
|
|
2647
|
+
}
|
|
2648
|
+
function listen() {
|
|
2649
|
+
if (unlisten !== null || typeof window === "undefined") return;
|
|
2650
|
+
const nudge = () => {
|
|
2651
|
+
if (typeof document !== "undefined" && document.visibilityState === "hidden") return;
|
|
2652
|
+
void readState2();
|
|
2653
|
+
};
|
|
2654
|
+
const goodbye = () => {
|
|
2655
|
+
const meeting = store.snapshot().meeting;
|
|
2656
|
+
const pass = options.resolvePass?.() ?? null;
|
|
2657
|
+
const waiting = store.snapshot().waiting;
|
|
2658
|
+
if (meeting === null || pass === null || waiting === null) return;
|
|
2659
|
+
if (!WAITING_ADMISSION_STATES.includes(waiting.admission)) return;
|
|
2660
|
+
try {
|
|
2661
|
+
navigator.sendBeacon?.(
|
|
2662
|
+
options.api.url(MEET_ROUTES.leave),
|
|
2663
|
+
new Blob([JSON.stringify({ meeting_id: meeting.id, intent: "withdraw", meeting_pass: pass })], {
|
|
2664
|
+
type: "application/json"
|
|
2665
|
+
})
|
|
2666
|
+
);
|
|
2667
|
+
} catch {
|
|
2668
|
+
}
|
|
2669
|
+
};
|
|
2670
|
+
window.addEventListener("online", nudge);
|
|
2671
|
+
window.addEventListener("pagehide", goodbye);
|
|
2672
|
+
document.addEventListener("visibilitychange", nudge);
|
|
2673
|
+
unlisten = () => {
|
|
2674
|
+
window.removeEventListener("online", nudge);
|
|
2675
|
+
window.removeEventListener("pagehide", goodbye);
|
|
2676
|
+
document.removeEventListener("visibilitychange", nudge);
|
|
2677
|
+
};
|
|
2678
|
+
}
|
|
2679
|
+
function stopWaiting() {
|
|
2680
|
+
polling = false;
|
|
2681
|
+
if (pollTimer !== null) clearTimeout(pollTimer);
|
|
2682
|
+
pollTimer = null;
|
|
2683
|
+
unlisten?.();
|
|
2684
|
+
unlisten = null;
|
|
2685
|
+
}
|
|
2686
|
+
function patchWaiting(next) {
|
|
2687
|
+
const current = store.snapshot().waiting;
|
|
2688
|
+
const base = current ?? {
|
|
2689
|
+
admission: "knocking",
|
|
2690
|
+
admittersPresent: null,
|
|
2691
|
+
reknockAllowed: true,
|
|
2692
|
+
knockExpiresAt: null,
|
|
2693
|
+
pollSeconds: 3,
|
|
2694
|
+
liveVersion: 0
|
|
2695
|
+
};
|
|
2696
|
+
store.patch({ waiting: { ...base, ...next } });
|
|
2697
|
+
}
|
|
2698
|
+
function apply(read) {
|
|
2699
|
+
const snapshot = store.snapshot();
|
|
2700
|
+
if (snapshot.phase !== "lobby") return;
|
|
2701
|
+
const admission = read.admission_state ?? null;
|
|
2702
|
+
const ended = admission === "ended" || read.session_phase === "ended" || read.meeting_phase === "ended";
|
|
2703
|
+
if (admission === "admitted" && !ended) {
|
|
2704
|
+
if (admittedOnce) return;
|
|
2705
|
+
admittedOnce = true;
|
|
2706
|
+
stopWaiting();
|
|
2707
|
+
options.onAdmitted?.();
|
|
2708
|
+
return;
|
|
2709
|
+
}
|
|
2710
|
+
if (ended) {
|
|
2711
|
+
stopWaiting();
|
|
2712
|
+
patchWaiting({ admission: "ended" });
|
|
2713
|
+
const meeting = snapshot.meeting;
|
|
2714
|
+
if (read.meeting_phase === "ended" && meeting !== null && meeting.endedAt === null) {
|
|
2715
|
+
store.patch({ meeting: { ...meeting, endedAt: (/* @__PURE__ */ new Date()).toISOString() } });
|
|
2716
|
+
}
|
|
2717
|
+
return;
|
|
2718
|
+
}
|
|
2719
|
+
if (admission === null) return;
|
|
2720
|
+
patchWaiting({
|
|
2721
|
+
admission,
|
|
2722
|
+
admittersPresent: read.admitters_present ?? null,
|
|
2723
|
+
reknockAllowed: read.reknock_allowed ?? true,
|
|
2724
|
+
knockExpiresAt: read.knock_expires_at ?? null,
|
|
2725
|
+
pollSeconds: read.poll_seconds ?? snapshot.waiting?.pollSeconds ?? 3,
|
|
2726
|
+
liveVersion: read.live_version ?? 0
|
|
2727
|
+
});
|
|
2728
|
+
if (admission === "denied" || admission === "expired" || admission === "withdrawn" || admission === "removed") {
|
|
2729
|
+
stopWaiting();
|
|
2730
|
+
}
|
|
2731
|
+
}
|
|
2732
|
+
async function readState2() {
|
|
2733
|
+
if (reading !== null) return reading;
|
|
2734
|
+
const meeting = store.snapshot().meeting;
|
|
2735
|
+
const pass = options.resolvePass?.() ?? null;
|
|
2736
|
+
if (meeting === null || pass === null) return;
|
|
2737
|
+
reading = (async () => {
|
|
2738
|
+
try {
|
|
2739
|
+
const response = await options.api.request({
|
|
2740
|
+
path: `${MEET_ROUTES.state}?meeting_id=${encodeURIComponent(meeting.id)}`,
|
|
2741
|
+
method: "GET",
|
|
2742
|
+
requireAuth: false,
|
|
2743
|
+
operation: "meet.state",
|
|
2744
|
+
headers: { [MEETING_PASS_HEADER]: pass }
|
|
2745
|
+
});
|
|
2746
|
+
if (!response.ok) {
|
|
2747
|
+
let body = null;
|
|
2748
|
+
try {
|
|
2749
|
+
body = await response.json();
|
|
2750
|
+
} catch {
|
|
2751
|
+
body = null;
|
|
2752
|
+
}
|
|
2753
|
+
const refusal2 = readServerRefusal(body);
|
|
2754
|
+
report(
|
|
2755
|
+
"warn",
|
|
2756
|
+
refusal2?.reason ?? `meet.state: the server answered ${response.status}`,
|
|
2757
|
+
refusal2?.remedy ?? "The waiting room keeps asking; nothing on this screen changes until the server answers."
|
|
2758
|
+
);
|
|
2759
|
+
return;
|
|
2760
|
+
}
|
|
2761
|
+
apply(await response.json());
|
|
2762
|
+
} catch (error) {
|
|
2763
|
+
report("info", `meet.state: ${String(error)}`, "Waiting continues; the state is read again shortly.");
|
|
2764
|
+
} finally {
|
|
2765
|
+
reading = null;
|
|
2766
|
+
}
|
|
2767
|
+
})();
|
|
2768
|
+
return reading;
|
|
2769
|
+
}
|
|
2770
|
+
async function postLeave(intent) {
|
|
2771
|
+
const meeting = requireMeeting(`meet.${intent}`);
|
|
2772
|
+
const pass = options.resolvePass?.() ?? null;
|
|
2773
|
+
const response = await options.api.request({
|
|
2774
|
+
path: MEET_ROUTES.leave,
|
|
2775
|
+
method: "POST",
|
|
2776
|
+
requireAuth: false,
|
|
2777
|
+
operation: `meet.${intent}`,
|
|
2778
|
+
body: { meeting_id: meeting.id, intent },
|
|
2779
|
+
...pass !== null ? { headers: { [MEETING_PASS_HEADER]: pass } } : {}
|
|
2780
|
+
});
|
|
2781
|
+
if (!response.ok) {
|
|
2782
|
+
let body = null;
|
|
2783
|
+
try {
|
|
2784
|
+
body = await response.json();
|
|
2785
|
+
} catch {
|
|
2786
|
+
body = null;
|
|
2787
|
+
}
|
|
2788
|
+
const refusal2 = readServerRefusal(body);
|
|
2789
|
+
throw new MeetError(
|
|
2790
|
+
"transport",
|
|
2791
|
+
refusal2?.reason ?? `meet.${intent}: the server answered ${response.status}`,
|
|
2792
|
+
refusal2?.remedy ?? "You are still in the waiting room. Try again."
|
|
2793
|
+
);
|
|
2794
|
+
}
|
|
2795
|
+
}
|
|
2796
|
+
function hint() {
|
|
2797
|
+
channel?.send(MEET_EVENTS.stateChanged, { v: null });
|
|
2798
|
+
}
|
|
2799
|
+
async function refreshQueue() {
|
|
2800
|
+
const meeting = store.snapshot().meeting;
|
|
2801
|
+
if (meeting === null || isGuest(identity) || !isHostRole(effectiveRole(store.snapshot()))) return;
|
|
2802
|
+
try {
|
|
2803
|
+
const attendees = await repository.attendees(meeting.id);
|
|
2804
|
+
store.learn(attendees);
|
|
2805
|
+
for (const attendee of attendees) {
|
|
2806
|
+
const entry = lobbyEntryFrom(attendee);
|
|
2807
|
+
if (entry !== null) store.upsertLobby(entry);
|
|
2808
|
+
else store.setAdmission(attendee.identity, decidedState(attendee.admissionState), null);
|
|
2809
|
+
}
|
|
2810
|
+
} catch {
|
|
2811
|
+
}
|
|
2812
|
+
}
|
|
2553
2813
|
return {
|
|
2554
2814
|
watch(roomName) {
|
|
2555
2815
|
channel?.close();
|
|
@@ -2557,58 +2817,22 @@ function createMeetingSession(options) {
|
|
|
2557
2817
|
topic: roomTopic(roomName),
|
|
2558
2818
|
broadcast: [
|
|
2559
2819
|
{
|
|
2560
|
-
|
|
2561
|
-
|
|
2562
|
-
|
|
2563
|
-
|
|
2564
|
-
|
|
2565
|
-
{
|
|
2566
|
-
event: MEET_EVENTS.admission,
|
|
2567
|
-
onMessage: (message) => {
|
|
2568
|
-
const verdict = message.data;
|
|
2569
|
-
const decided = asParticipantIdentity(verdict.identity);
|
|
2570
|
-
store.setAdmission(
|
|
2571
|
-
decided,
|
|
2572
|
-
verdict.admitted ? "admitted" : "denied",
|
|
2573
|
-
verdict.by === null ? null : verdict.by
|
|
2574
|
-
);
|
|
2575
|
-
if (decided === knockIdentity()) {
|
|
2576
|
-
if (verdict.admitted) options.onAdmitted?.();
|
|
2577
|
-
else options.onDenied?.();
|
|
2578
|
-
}
|
|
2579
|
-
}
|
|
2580
|
-
},
|
|
2581
|
-
{
|
|
2582
|
-
event: MEET_EVENTS.lock,
|
|
2583
|
-
onMessage: (message) => {
|
|
2584
|
-
const { locked } = message.data;
|
|
2585
|
-
const meeting = store.snapshot().meeting;
|
|
2586
|
-
store.patch({
|
|
2587
|
-
locked,
|
|
2588
|
-
...meeting !== null ? { meeting: { ...meeting, locked } } : {}
|
|
2589
|
-
});
|
|
2590
|
-
}
|
|
2591
|
-
},
|
|
2592
|
-
{
|
|
2593
|
-
event: MEET_EVENTS.recording,
|
|
2594
|
-
onMessage: (message) => {
|
|
2595
|
-
const payload = message.data;
|
|
2596
|
-
store.setRecording(
|
|
2597
|
-
payload.state,
|
|
2598
|
-
payload.by === null ? null : payload.by,
|
|
2599
|
-
"room"
|
|
2600
|
-
);
|
|
2820
|
+
// 🚨 PAYLOAD-FREE. Every receiver reads again; a spoofed hint only causes a read.
|
|
2821
|
+
event: MEET_EVENTS.stateChanged,
|
|
2822
|
+
onMessage: () => {
|
|
2823
|
+
if (store.snapshot().phase === "lobby") void readState2();
|
|
2824
|
+
void refreshQueue();
|
|
2601
2825
|
}
|
|
2602
2826
|
}
|
|
2603
2827
|
],
|
|
2604
|
-
// THE BACKFILL DOOR. A
|
|
2605
|
-
//
|
|
2606
|
-
// re-announce on their own timer.
|
|
2828
|
+
// THE BACKFILL DOOR. A socket blip must not lose a decision: re-read the meeting
|
|
2829
|
+
// row, and a waiting tab re-reads its own state.
|
|
2607
2830
|
onBackfill: async () => {
|
|
2608
2831
|
const meeting = store.snapshot().meeting;
|
|
2609
2832
|
if (meeting === null) return;
|
|
2610
2833
|
const fresh = isGuest(identity) ? await repository.meetingBySlug(meeting.slug) : await readMeeting(meeting.id);
|
|
2611
2834
|
store.patch({ meeting: fresh, locked: fresh.locked });
|
|
2835
|
+
if (store.snapshot().phase === "lobby") void readState2();
|
|
2612
2836
|
}
|
|
2613
2837
|
});
|
|
2614
2838
|
},
|
|
@@ -2669,7 +2893,7 @@ function createMeetingSession(options) {
|
|
|
2669
2893
|
store.upsertLobby(entry);
|
|
2670
2894
|
return;
|
|
2671
2895
|
}
|
|
2672
|
-
store.setAdmission(attendee.identity, attendee.admissionState, null);
|
|
2896
|
+
store.setAdmission(attendee.identity, decidedState(attendee.admissionState), null);
|
|
2673
2897
|
}
|
|
2674
2898
|
},
|
|
2675
2899
|
{
|
|
@@ -2721,62 +2945,75 @@ function createMeetingSession(options) {
|
|
|
2721
2945
|
if (entry !== null) {
|
|
2722
2946
|
store.upsertLobby(entry);
|
|
2723
2947
|
} else {
|
|
2724
|
-
store.setAdmission(attendee.identity, attendee.admissionState, null);
|
|
2948
|
+
store.setAdmission(attendee.identity, decidedState(attendee.admissionState), null);
|
|
2725
2949
|
}
|
|
2726
2950
|
}
|
|
2727
2951
|
}
|
|
2728
2952
|
});
|
|
2729
2953
|
meetingChannel.requestBackfill("manual");
|
|
2730
2954
|
},
|
|
2731
|
-
knock(roomName) {
|
|
2732
|
-
const handle = channel ?? manager.open({ topic: roomTopic(roomName) });
|
|
2733
|
-
channel = handle;
|
|
2734
|
-
const entry = {
|
|
2735
|
-
identity: knockIdentity(),
|
|
2736
|
-
displayName: identity.displayName,
|
|
2737
|
-
avatarUrl: identity.avatarUrl,
|
|
2738
|
-
userId: identity.userId,
|
|
2739
|
-
state: "knocking",
|
|
2740
|
-
knockedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2741
|
-
decidedAt: null,
|
|
2742
|
-
decidedBy: null
|
|
2743
|
-
};
|
|
2744
|
-
store.upsertLobby(entry);
|
|
2745
|
-
handle.send(MEET_EVENTS.knock, entry);
|
|
2746
|
-
},
|
|
2747
2955
|
async admit(entry, admitted) {
|
|
2748
2956
|
const by = requireHostUser("meet.admit");
|
|
2749
2957
|
const meeting = requireMeeting("meet.admit");
|
|
2750
|
-
await repository.
|
|
2958
|
+
const row = await repository.decideAdmission({
|
|
2751
2959
|
meetingId: meeting.id,
|
|
2752
2960
|
identity: entry.identity,
|
|
2753
|
-
admitted,
|
|
2961
|
+
decision: admitted ? "admit" : "deny",
|
|
2754
2962
|
byUserId: by
|
|
2755
2963
|
});
|
|
2756
2964
|
store.upsertLobby({
|
|
2757
2965
|
...entry,
|
|
2758
|
-
state:
|
|
2966
|
+
state: decidedState(row.admissionState),
|
|
2759
2967
|
decidedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2760
2968
|
decidedBy: by
|
|
2761
2969
|
});
|
|
2762
|
-
|
|
2970
|
+
hint();
|
|
2971
|
+
},
|
|
2972
|
+
async undeny(entry) {
|
|
2973
|
+
const by = requireHostUser("meet.undeny");
|
|
2974
|
+
const meeting = requireMeeting("meet.undeny");
|
|
2975
|
+
const row = await repository.decideAdmission({
|
|
2976
|
+
meetingId: meeting.id,
|
|
2763
2977
|
identity: entry.identity,
|
|
2764
|
-
|
|
2765
|
-
by
|
|
2978
|
+
decision: "undeny",
|
|
2979
|
+
byUserId: by
|
|
2766
2980
|
});
|
|
2981
|
+
store.upsertLobby({ ...entry, state: decidedState(row.admissionState), decidedAt: (/* @__PURE__ */ new Date()).toISOString(), decidedBy: by });
|
|
2982
|
+
hint();
|
|
2767
2983
|
},
|
|
2768
2984
|
async admitAll() {
|
|
2769
|
-
const
|
|
2770
|
-
|
|
2771
|
-
|
|
2772
|
-
|
|
2985
|
+
const by = requireHostUser("meet.admitAll");
|
|
2986
|
+
const meeting = requireMeeting("meet.admitAll");
|
|
2987
|
+
const rows = await repository.admitAll(meeting.id, by);
|
|
2988
|
+
for (const row of rows) {
|
|
2989
|
+
store.setAdmission(row.identity, row.admissionState, by);
|
|
2990
|
+
}
|
|
2991
|
+
hint();
|
|
2992
|
+
},
|
|
2993
|
+
startWaiting() {
|
|
2994
|
+
if (polling) return;
|
|
2995
|
+
polling = true;
|
|
2996
|
+
admittedOnce = false;
|
|
2997
|
+
listen();
|
|
2998
|
+
schedule(0);
|
|
2999
|
+
},
|
|
3000
|
+
stopWaiting,
|
|
3001
|
+
readState: readState2,
|
|
3002
|
+
async withdraw() {
|
|
3003
|
+
stopWaiting();
|
|
3004
|
+
await postLeave("withdraw");
|
|
3005
|
+
patchWaiting({ admission: "withdrawn" });
|
|
3006
|
+
hint();
|
|
3007
|
+
},
|
|
3008
|
+
async askAgain() {
|
|
3009
|
+
await options.rejoin?.();
|
|
2773
3010
|
},
|
|
2774
3011
|
async setLock(locked) {
|
|
2775
3012
|
const by = requireHostUser("meet.setLock");
|
|
2776
3013
|
const meeting = requireMeeting("meet.setLock");
|
|
2777
3014
|
const updated = await repository.setLock(meeting.id, locked, by);
|
|
2778
3015
|
store.patch({ meeting: updated, locked: updated.locked });
|
|
2779
|
-
|
|
3016
|
+
hint();
|
|
2780
3017
|
return updated;
|
|
2781
3018
|
},
|
|
2782
3019
|
/**
|
|
@@ -2834,6 +3071,7 @@ function createMeetingSession(options) {
|
|
|
2834
3071
|
return { endedAt, served: true };
|
|
2835
3072
|
},
|
|
2836
3073
|
dispose() {
|
|
3074
|
+
stopWaiting();
|
|
2837
3075
|
channel?.close();
|
|
2838
3076
|
channel = null;
|
|
2839
3077
|
meetingChannel?.close();
|
|
@@ -2878,9 +3116,15 @@ var RPCS = {
|
|
|
2878
3116
|
getOrCreateMeeting: "meet_get_or_create_meeting",
|
|
2879
3117
|
/** Resolve a durable public link, before/during/after its scheduled time. */
|
|
2880
3118
|
meetingBySlug: "meet_meeting_by_slug",
|
|
2881
|
-
|
|
3119
|
+
/*
|
|
3120
|
+
* ── S4 ADMISSION (CORE-DESIGN §2.4). Each returns the decided row; the caller then
|
|
3121
|
+
* sends the payload-free `mx.state.changed` hint. Authorized by `who_may_admit`.
|
|
3122
|
+
*/
|
|
3123
|
+
admit: "meet_admit",
|
|
3124
|
+
deny: "meet_deny",
|
|
3125
|
+
undeny: "meet_undeny",
|
|
3126
|
+
admitAll: "meet_admit_all",
|
|
2882
3127
|
setLock: "meet_set_lock",
|
|
2883
|
-
endMeeting: "meet_end_meeting",
|
|
2884
3128
|
recordConsent: "meet_record_consent",
|
|
2885
3129
|
/*
|
|
2886
3130
|
* ── MEET WAVE 1 — management (0.8). Every write is a SECURITY DEFINER door
|
|
@@ -2922,13 +3166,13 @@ var RPCS = {
|
|
|
2922
3166
|
/** Titles, agendas, current notes and final transcript lines; snippets mark hits with ⟦ ⟧. */
|
|
2923
3167
|
search: "meet_search"
|
|
2924
3168
|
};
|
|
2925
|
-
var VOID_RPCS = [RPCS.
|
|
3169
|
+
var VOID_RPCS = [RPCS.recordConsent];
|
|
2926
3170
|
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";
|
|
2927
3171
|
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";
|
|
2928
3172
|
var RECORDING_COLUMNS = "id,meeting_id,state,started_at,ended_at,started_by,file_id,failure_reason";
|
|
2929
3173
|
var SEGMENT_COLUMNS = "id,meeting_id,identity,speaker,text,started_at,is_final,metadata";
|
|
2930
3174
|
var NOTE_COLUMNS = "id,meeting_id,text,kind,assignee_user_id,created_at,metadata";
|
|
2931
|
-
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";
|
|
3175
|
+
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";
|
|
2932
3176
|
function projectStays(value) {
|
|
2933
3177
|
if (!Array.isArray(value)) return [];
|
|
2934
3178
|
const stays = [];
|
|
@@ -3294,8 +3538,17 @@ function createMeetRepository(options) {
|
|
|
3294
3538
|
const operation = "meet.projectAttendee";
|
|
3295
3539
|
const role = text(row, "role");
|
|
3296
3540
|
const admission = text(row, "admission_state");
|
|
3297
|
-
const known = ["host", "cohost", "participant", "
|
|
3298
|
-
const states = [
|
|
3541
|
+
const known = ["host", "cohost", "presenter", "participant", "viewer"];
|
|
3542
|
+
const states = [
|
|
3543
|
+
"waiting_host",
|
|
3544
|
+
"knocking",
|
|
3545
|
+
"admitted",
|
|
3546
|
+
"denied",
|
|
3547
|
+
"expired",
|
|
3548
|
+
"withdrawn",
|
|
3549
|
+
"removed",
|
|
3550
|
+
"ended"
|
|
3551
|
+
];
|
|
3299
3552
|
const userId = text(row, "participant_user_id");
|
|
3300
3553
|
const identity = requireText(row, "identity", operation);
|
|
3301
3554
|
return {
|
|
@@ -3309,6 +3562,8 @@ function createMeetRepository(options) {
|
|
|
3309
3562
|
userId: userId === null ? null : asUserId(userId),
|
|
3310
3563
|
role: known.includes(role) ? role : "participant",
|
|
3311
3564
|
admissionState: states.includes(admission) ? admission : "admitted",
|
|
3565
|
+
knockedAt: text(row, "knocked_at"),
|
|
3566
|
+
knockExpiresAt: text(row, "knock_expires_at"),
|
|
3312
3567
|
isAgent: bool(row, "is_agent", false),
|
|
3313
3568
|
joinedAt: text(row, "joined_at"),
|
|
3314
3569
|
leftAt: text(row, "left_at"),
|
|
@@ -3587,16 +3842,26 @@ function createMeetRepository(options) {
|
|
|
3587
3842
|
hasMore: rows.length > limit
|
|
3588
3843
|
};
|
|
3589
3844
|
},
|
|
3590
|
-
async
|
|
3591
|
-
|
|
3592
|
-
|
|
3593
|
-
|
|
3845
|
+
async decideAdmission({ meetingId, identity, decision, byUserId }) {
|
|
3846
|
+
const fn = decision === "admit" ? RPCS.admit : decision === "deny" ? RPCS.deny : RPCS.undeny;
|
|
3847
|
+
const row = await guard(
|
|
3848
|
+
`meet.${decision}`,
|
|
3849
|
+
() => db.rpc(fn, {
|
|
3594
3850
|
p_meeting_id: meetingId,
|
|
3595
3851
|
p_identity: identity,
|
|
3596
|
-
p_admitted: admitted,
|
|
3597
3852
|
p_by_user_id: byUserId
|
|
3598
3853
|
})
|
|
3599
3854
|
);
|
|
3855
|
+
return projectAttendee(row);
|
|
3856
|
+
},
|
|
3857
|
+
async admitAll(meetingId, byUserId) {
|
|
3858
|
+
const response = await db.rpc(RPCS.admitAll, {
|
|
3859
|
+
p_meeting_id: meetingId,
|
|
3860
|
+
p_by_user_id: byUserId
|
|
3861
|
+
});
|
|
3862
|
+
if (response.error !== null) throw normalizeMeetError(response.error, "meet.admitAll");
|
|
3863
|
+
const rows = Array.isArray(response.data) ? response.data : [];
|
|
3864
|
+
return rows.map(projectAttendee);
|
|
3600
3865
|
},
|
|
3601
3866
|
async setLock(meetingId, locked, byUserId) {
|
|
3602
3867
|
const row = await guard(
|
|
@@ -3609,16 +3874,6 @@ function createMeetRepository(options) {
|
|
|
3609
3874
|
);
|
|
3610
3875
|
return projectMeeting(row);
|
|
3611
3876
|
},
|
|
3612
|
-
async endMeeting(meetingId, byUserId) {
|
|
3613
|
-
const row = await guard(
|
|
3614
|
-
"meet.endMeeting",
|
|
3615
|
-
() => db.rpc(RPCS.endMeeting, {
|
|
3616
|
-
p_meeting_id: meetingId,
|
|
3617
|
-
p_by_user_id: byUserId
|
|
3618
|
-
})
|
|
3619
|
-
);
|
|
3620
|
-
return projectMeeting(row);
|
|
3621
|
-
},
|
|
3622
3877
|
async transcript(meetingId, limit = 1e3) {
|
|
3623
3878
|
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);
|
|
3624
3879
|
if (response.error !== null) throw normalizeMeetError(response.error, "meet.transcript");
|
|
@@ -4062,7 +4317,7 @@ function applyModeration(store, payload, now) {
|
|
|
4062
4317
|
return ignored(`a moderation kind this version does not know: ${String(kind)}`);
|
|
4063
4318
|
}
|
|
4064
4319
|
}
|
|
4065
|
-
var ROLES = ["host", "cohost", "participant", "
|
|
4320
|
+
var ROLES = ["host", "cohost", "presenter", "participant", "viewer"];
|
|
4066
4321
|
|
|
4067
4322
|
// src/core/note-taker.ts
|
|
4068
4323
|
var NOTE_TAKER_IDENTITY_PREFIX = "agent:note_taker:";
|
|
@@ -4147,7 +4402,12 @@ var TRANSCRIPTION_ATTRIBUTES = {
|
|
|
4147
4402
|
// src/core/room.ts
|
|
4148
4403
|
var PARTICIPANT_ROLE_ATTRIBUTE = "matrx.participant.role";
|
|
4149
4404
|
var PARTICIPANT_CAN_RECORD_ATTRIBUTE = "matrx.participant.can_record";
|
|
4150
|
-
var
|
|
4405
|
+
var PARTICIPANT_STANDING_ATTRIBUTE = "matrx.participant.standing";
|
|
4406
|
+
var KNOWN_STANDINGS = ["member", "invited", "outsider", "guest"];
|
|
4407
|
+
function parseStanding(value) {
|
|
4408
|
+
return KNOWN_STANDINGS.includes(value) ? value : null;
|
|
4409
|
+
}
|
|
4410
|
+
var KNOWN_ROLES = ["host", "cohost", "presenter", "participant", "viewer"];
|
|
4151
4411
|
function parseRole(value) {
|
|
4152
4412
|
return KNOWN_ROLES.includes(value) ? value : "participant";
|
|
4153
4413
|
}
|
|
@@ -4181,6 +4441,9 @@ function projectParticipant(driver, joinedAt) {
|
|
|
4181
4441
|
// ATTRIBUTE FIRST, metadata second — see the constants above. Both are the
|
|
4182
4442
|
// server's; the attribute is simply the one that always arrives.
|
|
4183
4443
|
role: parseRole(driver.attributes[PARTICIPANT_ROLE_ATTRIBUTE] ?? metadata.role),
|
|
4444
|
+
standing: parseStanding(
|
|
4445
|
+
driver.attributes[PARTICIPANT_STANDING_ATTRIBUTE] ?? metadata.standing
|
|
4446
|
+
),
|
|
4184
4447
|
canRecord: driver.attributes[PARTICIPANT_CAN_RECORD_ATTRIBUTE] === "true" || metadata.can_record === true,
|
|
4185
4448
|
isLocal: driver.isLocal,
|
|
4186
4449
|
isAgent: metadata.is_agent === true,
|
|
@@ -4253,7 +4516,7 @@ function createRoomEngine(options) {
|
|
|
4253
4516
|
const normalized = normalizeMeetError(error, operation);
|
|
4254
4517
|
store.patch({
|
|
4255
4518
|
phase: "failed",
|
|
4256
|
-
failure: { message: normalized.message, remedy: normalized.remedy }
|
|
4519
|
+
failure: { message: normalized.message, remedy: normalized.remedy, reason: normalized.reason }
|
|
4257
4520
|
});
|
|
4258
4521
|
report(
|
|
4259
4522
|
normalized.code === "session-unavailable" ? "warn" : "error",
|
|
@@ -4443,7 +4706,7 @@ function createRoomEngine(options) {
|
|
|
4443
4706
|
}
|
|
4444
4707
|
};
|
|
4445
4708
|
async function connect(request) {
|
|
4446
|
-
store.patch({ phase: "requesting-token", failure: null });
|
|
4709
|
+
store.patch({ phase: "requesting-token", failure: null, waiting: null });
|
|
4447
4710
|
const token = await tokens.get({
|
|
4448
4711
|
roomName: request.roomName,
|
|
4449
4712
|
identity,
|
|
@@ -4457,16 +4720,27 @@ function createRoomEngine(options) {
|
|
|
4457
4720
|
breakoutRoomId: token.breakoutRoomId ?? null,
|
|
4458
4721
|
grants: {
|
|
4459
4722
|
role: token.role,
|
|
4723
|
+
standing: token.standing,
|
|
4460
4724
|
canRecord: token.canRecord,
|
|
4461
4725
|
canPublish: token.canPublish,
|
|
4462
4726
|
canSubscribe: token.canSubscribe
|
|
4463
4727
|
}
|
|
4464
4728
|
});
|
|
4465
|
-
if (token.lobby) {
|
|
4466
|
-
store.patch({
|
|
4729
|
+
if (token.lobby || !token.canSubscribe) {
|
|
4730
|
+
store.patch({
|
|
4731
|
+
phase: "lobby",
|
|
4732
|
+
waiting: {
|
|
4733
|
+
admission: token.admissionState !== void 0 && token.admissionState !== "admitted" ? token.admissionState : "knocking",
|
|
4734
|
+
admittersPresent: null,
|
|
4735
|
+
reknockAllowed: true,
|
|
4736
|
+
knockExpiresAt: null,
|
|
4737
|
+
pollSeconds: token.pollSeconds ?? 3,
|
|
4738
|
+
liveVersion: 0
|
|
4739
|
+
}
|
|
4740
|
+
});
|
|
4467
4741
|
return;
|
|
4468
4742
|
}
|
|
4469
|
-
store.patch({ phase: "connecting" });
|
|
4743
|
+
store.patch({ phase: "connecting", waiting: null });
|
|
4470
4744
|
const created = await options.driverFactory(handlers);
|
|
4471
4745
|
if (disposed) {
|
|
4472
4746
|
await created.disconnect();
|
|
@@ -4802,7 +5076,7 @@ function createInRoomClient(options) {
|
|
|
4802
5076
|
}
|
|
4803
5077
|
function headers() {
|
|
4804
5078
|
const token = options.resolveRoomToken();
|
|
4805
|
-
return token === null ? void 0 : { [
|
|
5079
|
+
return token === null ? void 0 : { [MEETING_PASS_HEADER]: token };
|
|
4806
5080
|
}
|
|
4807
5081
|
function refusal2(response, operation, remedy) {
|
|
4808
5082
|
return refusalFromResponse(response, operation, remedy);
|
|
@@ -5549,25 +5823,37 @@ function MeetRuntime(props) {
|
|
|
5549
5823
|
api,
|
|
5550
5824
|
onDiagnostic: report,
|
|
5551
5825
|
// ADMISSION IS A JOIN, NOT A RE-RENDER. The lobby token grants neither
|
|
5552
|
-
// publish nor subscribe, so the host saying yes has to mint a new one.
|
|
5826
|
+
// publish nor subscribe, so the host saying yes has to mint a new one. The
|
|
5827
|
+
// "yes" is read from the server (the state read), never a broadcast (S4).
|
|
5553
5828
|
onAdmitted: () => {
|
|
5554
|
-
void engine.rejoin();
|
|
5555
|
-
},
|
|
5556
|
-
// A "no" is ALSO a mint: the server refuses it with its own sentence, which
|
|
5557
|
-
// turns the waiting room into the pre-join screen showing that sentence.
|
|
5558
|
-
onDenied: () => {
|
|
5559
5829
|
void engine.rejoin().catch(() => void 0);
|
|
5830
|
+
},
|
|
5831
|
+
rejoin: () => engine.rejoin(),
|
|
5832
|
+
// THE MEETING PASS this device holds — what the state read and `/leave` carry.
|
|
5833
|
+
resolvePass: () => {
|
|
5834
|
+
const roomName = store.snapshot().meeting?.roomName;
|
|
5835
|
+
const active = engine.token();
|
|
5836
|
+
if (active?.meetingPass) return active.meetingPass;
|
|
5837
|
+
return roomName === void 0 ? null : tokens.remembered(roomName)?.meetingPass ?? null;
|
|
5560
5838
|
}
|
|
5561
5839
|
});
|
|
5562
5840
|
const resolveRoomToken = () => {
|
|
5563
5841
|
const roomName = store.snapshot().meeting?.roomName;
|
|
5564
5842
|
if (roomName === void 0) return null;
|
|
5565
5843
|
const active = engine.token();
|
|
5566
|
-
if (active?.roomName === roomName && active.
|
|
5567
|
-
return tokens.remembered(roomName)?.
|
|
5844
|
+
if (active?.roomName === roomName && active.meetingPass) return active.meetingPass;
|
|
5845
|
+
return tokens.remembered(roomName)?.meetingPass ?? null;
|
|
5568
5846
|
};
|
|
5569
5847
|
const ai = createMeetAi({ api, store, resolveRoomToken });
|
|
5570
5848
|
const inRoom = createInRoomClient({ api, store, resolveRoomToken });
|
|
5849
|
+
store.subscribe(() => {
|
|
5850
|
+
const snapshot = store.snapshot();
|
|
5851
|
+
if (snapshot.phase === "lobby" && snapshot.waiting !== null && (snapshot.waiting.admission === "knocking" || snapshot.waiting.admission === "waiting_host")) {
|
|
5852
|
+
meetings.startWaiting();
|
|
5853
|
+
} else if (snapshot.phase !== "lobby") {
|
|
5854
|
+
meetings.stopWaiting();
|
|
5855
|
+
}
|
|
5856
|
+
});
|
|
5571
5857
|
let chatReadFor = null;
|
|
5572
5858
|
store.subscribe(() => {
|
|
5573
5859
|
const snapshot = store.snapshot();
|
|
@@ -5736,6 +6022,10 @@ function useLocalUserId() {
|
|
|
5736
6022
|
var import_react3 = require("react");
|
|
5737
6023
|
|
|
5738
6024
|
// src/core/observation.ts
|
|
6025
|
+
function observedRole(role, standing) {
|
|
6026
|
+
if (role === "host" || role === "cohost") return role;
|
|
6027
|
+
return isLinkFollower(standing) ? "guest" : "participant";
|
|
6028
|
+
}
|
|
5739
6029
|
function observedDevice(track) {
|
|
5740
6030
|
if (track.enabled) return "on";
|
|
5741
6031
|
const refused = track.unavailable;
|
|
@@ -5763,13 +6053,15 @@ function observedPhase(input) {
|
|
|
5763
6053
|
const raw = snapshot.phase;
|
|
5764
6054
|
const live = raw === "connected" || raw === "reconnecting";
|
|
5765
6055
|
if (endedRecord(snapshot, input.meeting ?? null) && !live) return "ended";
|
|
6056
|
+
const reason = raw === "failed" ? snapshot.failure?.reason ?? null : null;
|
|
6057
|
+
if (reason !== null) return reason === "ended" ? "ended" : `refused:${reason}`;
|
|
5766
6058
|
switch (raw) {
|
|
5767
6059
|
case "idle":
|
|
5768
6060
|
case "failed":
|
|
5769
6061
|
case "left":
|
|
5770
6062
|
return "prejoin";
|
|
5771
6063
|
case "lobby":
|
|
5772
|
-
return
|
|
6064
|
+
return waitingPhase(snapshot);
|
|
5773
6065
|
case "requesting-token":
|
|
5774
6066
|
case "connecting":
|
|
5775
6067
|
return "joining";
|
|
@@ -5778,20 +6070,39 @@ function observedPhase(input) {
|
|
|
5778
6070
|
return "in_call";
|
|
5779
6071
|
}
|
|
5780
6072
|
}
|
|
6073
|
+
function waitingPhase(snapshot) {
|
|
6074
|
+
switch (snapshot.waiting?.admission) {
|
|
6075
|
+
case "waiting_host":
|
|
6076
|
+
return "waiting:host_not_started";
|
|
6077
|
+
case "denied":
|
|
6078
|
+
return "denied";
|
|
6079
|
+
case "expired":
|
|
6080
|
+
return "knock_expired";
|
|
6081
|
+
case "withdrawn":
|
|
6082
|
+
return "left";
|
|
6083
|
+
case "ended":
|
|
6084
|
+
return "ended";
|
|
6085
|
+
case "removed":
|
|
6086
|
+
return "refused:removed";
|
|
6087
|
+
default:
|
|
6088
|
+
return "waiting:knocking";
|
|
6089
|
+
}
|
|
6090
|
+
}
|
|
5781
6091
|
var ADMITTER_ROLES = /* @__PURE__ */ new Set(["host", "cohost"]);
|
|
5782
6092
|
function observe(input) {
|
|
5783
6093
|
const { snapshot } = input;
|
|
5784
6094
|
const phase = observedPhase(input);
|
|
5785
6095
|
const inCall = phase === "in_call";
|
|
5786
|
-
const
|
|
5787
|
-
const canAdmit =
|
|
6096
|
+
const actual = inCall && snapshot !== null ? effectiveRole(snapshot) : "none";
|
|
6097
|
+
const canAdmit = actual !== "none" && ADMITTER_ROLES.has(actual);
|
|
6098
|
+
const role = actual === "none" || snapshot === null ? "none" : observedRole(actual, effectiveStanding(snapshot));
|
|
5788
6099
|
const deviceInPlay = phase === "joining" || inCall;
|
|
5789
6100
|
return {
|
|
5790
6101
|
phase,
|
|
5791
|
-
reason: null,
|
|
6102
|
+
reason: phase.startsWith("refused:") ? phase.slice("refused:".length) : null,
|
|
5792
6103
|
role,
|
|
5793
6104
|
connection: snapshot?.phase === "reconnecting" ? "reconnecting" : "stable",
|
|
5794
|
-
hostPresent: inCall && snapshot !== null ? snapshot.participants.some((p) => ADMITTER_ROLES.has(p.role)) : null,
|
|
6105
|
+
hostPresent: inCall && snapshot !== null ? snapshot.participants.some((p) => ADMITTER_ROLES.has(p.role)) : snapshot?.phase === "lobby" ? snapshot.waiting?.admittersPresent ?? null : null,
|
|
5795
6106
|
lobbyCount: canAdmit && snapshot !== null ? snapshot.lobby.filter((entry) => entry.state === "knocking").length : 0,
|
|
5796
6107
|
recording: snapshot?.recording === "recording" || snapshot?.recording === "stopping",
|
|
5797
6108
|
audioBlocked: null,
|
|
@@ -5854,7 +6165,7 @@ function participantAttributes(p) {
|
|
|
5854
6165
|
"data-meet-participant": "",
|
|
5855
6166
|
"data-meet-participant-identity": p.identity,
|
|
5856
6167
|
"data-meet-participant-name": p.displayName,
|
|
5857
|
-
"data-meet-participant-role": p.role,
|
|
6168
|
+
"data-meet-participant-role": observedRole(p.role, p.standing),
|
|
5858
6169
|
"data-meet-participant-self": String(p.isLocal)
|
|
5859
6170
|
};
|
|
5860
6171
|
const quality = observedQuality(p.quality);
|
|
@@ -6183,7 +6494,7 @@ function summarizeMeetingRecordRestrictions(restrictions) {
|
|
|
6183
6494
|
async function loadGuestMeetingRecord(args) {
|
|
6184
6495
|
const { api, repository, meetingId, meeting, roomToken } = args;
|
|
6185
6496
|
const session = args.session === true;
|
|
6186
|
-
if (roomToken
|
|
6497
|
+
if (roomToken?.meetingPass == null && !session) {
|
|
6187
6498
|
return refusedBundle(
|
|
6188
6499
|
meeting,
|
|
6189
6500
|
"This meeting's record is only readable by the people who were in it.",
|
|
@@ -6193,12 +6504,12 @@ async function loadGuestMeetingRecord(args) {
|
|
|
6193
6504
|
let response;
|
|
6194
6505
|
try {
|
|
6195
6506
|
response = await api.request({
|
|
6196
|
-
path: `${MEET_ROUTES.record}?meeting_id=${encodeURIComponent(meetingId)}
|
|
6507
|
+
path: `${MEET_ROUTES.record}?meeting_id=${encodeURIComponent(meetingId)}` + (args.sessionId ? `&session_id=${encodeURIComponent(args.sessionId)}` : ""),
|
|
6197
6508
|
method: "GET",
|
|
6198
6509
|
// A GUEST HAS NO SESSION AND IS STILL A FIRST-CLASS READER (D6).
|
|
6199
6510
|
requireAuth: session,
|
|
6200
6511
|
operation: "meet.record",
|
|
6201
|
-
headers: roomToken
|
|
6512
|
+
headers: roomToken?.meetingPass != null ? { [MEETING_PASS_HEADER]: roomToken.meetingPass } : {}
|
|
6202
6513
|
});
|
|
6203
6514
|
} catch (error) {
|
|
6204
6515
|
return refusedBundle(
|
|
@@ -6752,12 +7063,22 @@ function useLobby() {
|
|
|
6752
7063
|
async () => requireSession(session, "admitAll").admitAll(),
|
|
6753
7064
|
[session]
|
|
6754
7065
|
),
|
|
6755
|
-
|
|
6756
|
-
(
|
|
7066
|
+
undeny: (0, import_react4.useCallback)(
|
|
7067
|
+
async (entry) => requireSession(session, "undeny").undeny(entry),
|
|
6757
7068
|
[session]
|
|
6758
7069
|
)
|
|
6759
7070
|
};
|
|
6760
7071
|
}
|
|
7072
|
+
function useWaiting() {
|
|
7073
|
+
const host = useMeetHost();
|
|
7074
|
+
const snapshot = useMeetSnapshot();
|
|
7075
|
+
const session = host?.meetings ?? null;
|
|
7076
|
+
return {
|
|
7077
|
+
waiting: snapshot?.waiting ?? null,
|
|
7078
|
+
withdraw: (0, import_react4.useCallback)(async () => requireSession(session, "withdraw").withdraw(), [session]),
|
|
7079
|
+
askAgain: (0, import_react4.useCallback)(async () => requireSession(session, "askAgain").askAgain(), [session])
|
|
7080
|
+
};
|
|
7081
|
+
}
|
|
6761
7082
|
function useRecording() {
|
|
6762
7083
|
const host = useMeetHost();
|
|
6763
7084
|
const snapshot = useMeetSnapshot();
|
|
@@ -7405,7 +7726,6 @@ function kindOf(person) {
|
|
|
7405
7726
|
if (person.isAgent) return "assistant";
|
|
7406
7727
|
if (person.role === "host") return "host";
|
|
7407
7728
|
if (person.role === "cohost") return "cohost";
|
|
7408
|
-
if (person.role === "guest") return person.userId !== null ? "signed-in guest" : "guest";
|
|
7409
7729
|
if (person.userId === null) return "guest";
|
|
7410
7730
|
return "member";
|
|
7411
7731
|
}
|
|
@@ -7690,7 +8010,7 @@ function meetingLength(meeting) {
|
|
|
7690
8010
|
if (start === null || end === null || end < start) return null;
|
|
7691
8011
|
return (0, import_format2.formatDurationMs)(end - start, { style: "long" });
|
|
7692
8012
|
}
|
|
7693
|
-
async function loadMeetingChat(api, meetingId,
|
|
8013
|
+
async function loadMeetingChat(api, meetingId, meetingPass = null) {
|
|
7694
8014
|
const operation = "meet.loadMeetingChat";
|
|
7695
8015
|
let response;
|
|
7696
8016
|
try {
|
|
@@ -7698,8 +8018,8 @@ async function loadMeetingChat(api, meetingId, roomToken = null) {
|
|
|
7698
8018
|
path: `${MEET_ROUTES.chat}?meeting_id=${encodeURIComponent(meetingId)}`,
|
|
7699
8019
|
method: "GET",
|
|
7700
8020
|
operation,
|
|
7701
|
-
requireAuth:
|
|
7702
|
-
...
|
|
8021
|
+
requireAuth: meetingPass === null,
|
|
8022
|
+
...meetingPass !== null ? { headers: { [MEETING_PASS_HEADER]: meetingPass } } : {}
|
|
7703
8023
|
});
|
|
7704
8024
|
} catch (error) {
|
|
7705
8025
|
return {
|
|
@@ -9172,10 +9492,13 @@ function RecordingIndicator() {
|
|
|
9172
9492
|
}
|
|
9173
9493
|
);
|
|
9174
9494
|
}
|
|
9175
|
-
function RoleBadge({
|
|
9495
|
+
function RoleBadge({
|
|
9496
|
+
role,
|
|
9497
|
+
standing
|
|
9498
|
+
}) {
|
|
9176
9499
|
if (role === "host") return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "mx-meet__badge", children: "Host" });
|
|
9177
9500
|
if (role === "cohost") return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "mx-meet__badge", children: "Co-host" });
|
|
9178
|
-
if (
|
|
9501
|
+
if (isLinkFollower(standing)) return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "mx-meet__badge", children: "Guest" });
|
|
9179
9502
|
return null;
|
|
9180
9503
|
}
|
|
9181
9504
|
function ParticipantTile({
|
|
@@ -9225,7 +9548,7 @@ function ParticipantTile({
|
|
|
9225
9548
|
participant.displayName,
|
|
9226
9549
|
participant.isLocal ? " (you)" : "",
|
|
9227
9550
|
participant.isAgent ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "mx-meet__badge", children: "AI" }) : null,
|
|
9228
|
-
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(RoleBadge, { role: participant.role })
|
|
9551
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(RoleBadge, { role: participant.role, standing: participant.standing })
|
|
9229
9552
|
] }),
|
|
9230
9553
|
!participant.micEnabled ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(MicOffIcon, { width: 14, height: 14 }) : null,
|
|
9231
9554
|
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(QualityBars, { quality: participant.quality })
|
|
@@ -9964,7 +10287,13 @@ function PeoplePanel({
|
|
|
9964
10287
|
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("span", { className: "mx-meet__person-name", children: [
|
|
9965
10288
|
participant.displayName,
|
|
9966
10289
|
participant.identity === local ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "mx-meet__you", children: " (you)" }) : null,
|
|
9967
|
-
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
10290
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
10291
|
+
RoleBadge,
|
|
10292
|
+
{
|
|
10293
|
+
role: participant.identity === local ? myRole : participant.role,
|
|
10294
|
+
standing: participant.standing
|
|
10295
|
+
}
|
|
10296
|
+
),
|
|
9968
10297
|
isNoteTaker(participant) ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "mx-meet__badge", children: "Meeting assistant" }) : null,
|
|
9969
10298
|
snapshot?.spotlight === participant.identity ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "mx-meet__badge", children: "Spotlight" }) : null
|
|
9970
10299
|
] }),
|
|
@@ -10880,7 +11209,7 @@ function MeetSkinRoom({
|
|
|
10880
11209
|
) });
|
|
10881
11210
|
}
|
|
10882
11211
|
if (phase === "lobby") {
|
|
10883
|
-
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "mx-meet mx-meet--lobby", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(WaitingRoom, {
|
|
11212
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "mx-meet mx-meet--lobby", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(WaitingRoom, { host, ...onLeave !== void 0 ? { onLeave } : {} }) });
|
|
10884
11213
|
}
|
|
10885
11214
|
return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "mx-meet", children: [
|
|
10886
11215
|
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(ConsentNotice, {}),
|
|
@@ -10905,21 +11234,58 @@ function MeetSkinRoom({
|
|
|
10905
11234
|
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(ControlBar, { ...onLeave !== void 0 ? { onLeave } : {} })
|
|
10906
11235
|
] });
|
|
10907
11236
|
}
|
|
10908
|
-
function WaitingRoom({
|
|
10909
|
-
const
|
|
10910
|
-
(0, import_react9.
|
|
10911
|
-
|
|
10912
|
-
|
|
10913
|
-
|
|
10914
|
-
|
|
11237
|
+
function WaitingRoom({ host, onLeave }) {
|
|
11238
|
+
const { waiting, withdraw, askAgain } = useWaiting();
|
|
11239
|
+
const [busy, setBusy] = (0, import_react9.useState)(false);
|
|
11240
|
+
const admission = waiting?.admission ?? "knocking";
|
|
11241
|
+
const run = (action) => () => {
|
|
11242
|
+
setBusy(true);
|
|
11243
|
+
void action().catch(() => void 0).finally(() => setBusy(false));
|
|
11244
|
+
};
|
|
11245
|
+
const home = /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_design_system.Button, { type: "button", variant: "outline", onClick: () => onLeave !== void 0 ? onLeave() : window.location.assign("/"), children: "Back to home" });
|
|
11246
|
+
let title;
|
|
11247
|
+
let line = null;
|
|
11248
|
+
let actions;
|
|
11249
|
+
switch (admission) {
|
|
11250
|
+
case "waiting_host":
|
|
11251
|
+
title = "Waiting for the host to start";
|
|
11252
|
+
actions = /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_design_system.Button, { type: "button", variant: "outline", disabled: busy, onClick: run(withdraw), children: "Leave" });
|
|
11253
|
+
break;
|
|
11254
|
+
case "denied":
|
|
11255
|
+
title = "The host declined your request";
|
|
11256
|
+
actions = /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [
|
|
11257
|
+
waiting?.reknockAllowed === true ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_design_system.Button, { type: "button", disabled: busy, onClick: run(askAgain), children: "Ask to join again" }) : null,
|
|
11258
|
+
home
|
|
11259
|
+
] });
|
|
11260
|
+
break;
|
|
11261
|
+
case "expired":
|
|
11262
|
+
title = "No one responded";
|
|
11263
|
+
actions = /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [
|
|
11264
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_design_system.Button, { type: "button", disabled: busy, onClick: run(askAgain), children: "Ask again" }),
|
|
11265
|
+
home
|
|
11266
|
+
] });
|
|
11267
|
+
break;
|
|
11268
|
+
case "withdrawn":
|
|
11269
|
+
title = "You left the waiting room";
|
|
11270
|
+
actions = /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [
|
|
11271
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_design_system.Button, { type: "button", disabled: busy, onClick: run(askAgain), children: "Rejoin" }),
|
|
11272
|
+
home
|
|
11273
|
+
] });
|
|
11274
|
+
break;
|
|
11275
|
+
case "ended":
|
|
11276
|
+
title = "The meeting has ended";
|
|
11277
|
+
actions = home;
|
|
11278
|
+
break;
|
|
11279
|
+
default:
|
|
11280
|
+
title = "Waiting for the host to let you in";
|
|
11281
|
+
line = waiting?.admittersPresent === false ? "The host isn't here yet" : null;
|
|
11282
|
+
actions = /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_design_system.Button, { type: "button", variant: "outline", disabled: busy, onClick: run(withdraw), children: "Cancel request" });
|
|
11283
|
+
}
|
|
10915
11284
|
return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "mx-meet__waiting", children: [
|
|
10916
11285
|
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Avatar, { name: host.identity.displayName, url: host.identity.avatarUrl, size: 72 }),
|
|
10917
|
-
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("h1", { className: "mx-meet__prejoin-title", children:
|
|
10918
|
-
/* @__PURE__ */ (0, import_jsx_runtime5.
|
|
10919
|
-
|
|
10920
|
-
host.identity.displayName,
|
|
10921
|
-
". You will join automatically once you are admitted."
|
|
10922
|
-
] })
|
|
11286
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("h1", { className: "mx-meet__prejoin-title", children: title }),
|
|
11287
|
+
line !== null ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { className: "mx-meet__empty-body", role: "status", "data-meet-notice": "host-absent", children: line }) : null,
|
|
11288
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "mx-meet__waiting-actions", children: actions })
|
|
10923
11289
|
] });
|
|
10924
11290
|
}
|
|
10925
11291
|
function HostMenu() {
|
|
@@ -11126,7 +11492,7 @@ function MeetingRecordBody({
|
|
|
11126
11492
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { className: "mx-meet__person-name", children: [
|
|
11127
11493
|
displayNameFor(bundle.names, person.identity, person.displayName),
|
|
11128
11494
|
person.role === "host" ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "mx-meet__badge", children: "Host" }) : null,
|
|
11129
|
-
person.
|
|
11495
|
+
person.userId === null && !person.isAgent ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "mx-meet__badge", children: "Guest" }) : null,
|
|
11130
11496
|
person.isAgent ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "mx-meet__badge", children: "Meeting assistant" }) : null
|
|
11131
11497
|
] })
|
|
11132
11498
|
] }, person.id)) })
|