@ai-matrx/meet 0.9.0 → 0.10.1
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 +26 -0
- package/dist/{collab-B6scmSST.d.cts → collab-BBQxcyRQ.d.cts} +53 -2
- package/dist/{collab-B6scmSST.d.ts → collab-BBQxcyRQ.d.ts} +53 -2
- package/dist/{components-BeCQ9h0h.d.cts → components-1XBHgMYK.d.ts} +6 -2
- package/dist/{components-D0TqyeiY.d.ts → components-CK0EyqGL.d.cts} +6 -2
- package/dist/core.cjs +1225 -939
- package/dist/core.cjs.map +1 -1
- package/dist/core.d.cts +101 -26
- package/dist/core.d.ts +101 -26
- package/dist/core.js +1225 -939
- package/dist/core.js.map +1 -1
- package/dist/index.cjs +1225 -939
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1225 -939
- package/dist/index.js.map +1 -1
- package/dist/react.cjs +3120 -2694
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +69 -6
- package/dist/react.d.ts +69 -6
- package/dist/react.js +2775 -2349
- package/dist/react.js.map +1 -1
- package/dist/skins/meet.cjs +1051 -911
- 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 +702 -562
- package/dist/skins/meet.js.map +1 -1
- package/dist/styles.css +8 -0
- package/package.json +5 -5
package/dist/index.js
CHANGED
|
@@ -301,7 +301,17 @@ var MEET_ROUTES = {
|
|
|
301
301
|
*/
|
|
302
302
|
collab: "/api/v1/meet/collab",
|
|
303
303
|
/** Meet wave 5: translate finished caption lines into this viewer's language. */
|
|
304
|
-
captionsTranslate: "/api/v1/meet/captions/translate"
|
|
304
|
+
captionsTranslate: "/api/v1/meet/captions/translate",
|
|
305
|
+
/**
|
|
306
|
+
* S4 — THE STATE READ (CORE-DESIGN §2.6): `GET ?meeting_id=` with `x-meet-pass`. This
|
|
307
|
+
* person's admission (lazy expiry applied), whether anyone who can admit is here, the run's
|
|
308
|
+
* phase. Read every `waiting_poll_seconds` while waiting and on every `mx.state.changed`.
|
|
309
|
+
*/
|
|
310
|
+
state: "/api/v1/meet/state",
|
|
311
|
+
/** S4: `withdraw` (stop waiting) or `leave` (close this tab); the pass may ride in the body. */
|
|
312
|
+
leave: "/api/v1/meet/leave",
|
|
313
|
+
/** S4: a pass holder (guests included) saw the recording notice, for this run. */
|
|
314
|
+
consent: "/api/v1/meet/consent"
|
|
305
315
|
};
|
|
306
316
|
function meetingPath(template, meetingId) {
|
|
307
317
|
return template.replace("{meeting_id}", encodeURIComponent(meetingId));
|
|
@@ -375,7 +385,10 @@ function parseRoomToken(body, roomName) {
|
|
|
375
385
|
meetingPassExpiresAt: readString(payload, "meeting_pass_expires_at"),
|
|
376
386
|
sessionId: readString(payload, "session_id"),
|
|
377
387
|
lobby: readBoolean(payload, "lobby", false),
|
|
378
|
-
|
|
388
|
+
admissionState: readString(payload, "admission_state") ?? void 0,
|
|
389
|
+
pollSeconds: typeof payload["poll_seconds"] === "number" ? payload["poll_seconds"] : void 0,
|
|
390
|
+
breakoutRoomId: readString(payload, "breakout_room_id") ?? null,
|
|
391
|
+
observersVisibleTo: readString(payload, "observers_visible_to") === "hosts" ? "hosts" : "everyone"
|
|
379
392
|
};
|
|
380
393
|
}
|
|
381
394
|
function createTokenClient(options) {
|
|
@@ -424,6 +437,7 @@ function createTokenClient(options) {
|
|
|
424
437
|
...input.meetingId !== void 0 ? { meeting_id: input.meetingId } : {},
|
|
425
438
|
...input.slug !== void 0 ? { slug: input.slug } : {},
|
|
426
439
|
...input.breakoutRoomId != null ? { breakout_room_id: input.breakoutRoomId } : {},
|
|
440
|
+
...input.observe === true ? { observe: true } : {},
|
|
427
441
|
organization_id: input.identity.organizationId,
|
|
428
442
|
display_name: input.identity.displayName,
|
|
429
443
|
device_id: input.identity.deviceId,
|
|
@@ -495,7 +509,8 @@ function createTokenClient(options) {
|
|
|
495
509
|
return {
|
|
496
510
|
get(input) {
|
|
497
511
|
const entry = cache.get(input.roomName);
|
|
498
|
-
const sameRoom = entry !== void 0 && (entry.token.breakoutRoomId ?? null) === (input.breakoutRoomId ?? null)
|
|
512
|
+
const sameRoom = entry !== void 0 && (entry.token.breakoutRoomId ?? null) === (input.breakoutRoomId ?? null) && // An observer's pass is not a participant's, and back (MD-16).
|
|
513
|
+
entry.token.role === "viewer" === (input.observe === true);
|
|
499
514
|
if (entry !== void 0 && sameRoom) {
|
|
500
515
|
if (entry.inFlight !== null) return entry.inFlight;
|
|
501
516
|
if (!expired(entry)) return Promise.resolve(entry.token);
|
|
@@ -987,14 +1002,15 @@ var MEET_EVENTS = {
|
|
|
987
1002
|
cancel: "mx.call.cancel",
|
|
988
1003
|
/** You answered (or declined) on another device. Stop ringing here. */
|
|
989
1004
|
settled: "mx.call.settled",
|
|
990
|
-
/**
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
1005
|
+
/**
|
|
1006
|
+
* 🚨 S4 (CORE-DESIGN §2.6): the ONE room-channel event — payload-free "something changed,
|
|
1007
|
+
* read again". Nothing a person must see rides it: a waiting person learns admitted /
|
|
1008
|
+
* denied / expired / ended by READING their row (`GET /v1/meet/state`) on a timer and on
|
|
1009
|
+
* every hint. The four payload broadcasts (`mx.lobby.knock`, `mx.lobby.admission`,
|
|
1010
|
+
* `mx.room.lock`, `mx.room.recording`) are gone with their senders and receivers. The
|
|
1011
|
+
* server publishes the same event on the same topic (aidream `admission.room_topic`).
|
|
1012
|
+
*/
|
|
1013
|
+
stateChanged: "mx.state.changed"
|
|
998
1014
|
};
|
|
999
1015
|
|
|
1000
1016
|
// src/core/types.ts
|
|
@@ -1020,6 +1036,10 @@ function isLinkFollower(standing) {
|
|
|
1020
1036
|
function isHostRole(role) {
|
|
1021
1037
|
return role === "host" || role === "cohost";
|
|
1022
1038
|
}
|
|
1039
|
+
function isObserverRole(role) {
|
|
1040
|
+
return role === "viewer";
|
|
1041
|
+
}
|
|
1042
|
+
var WAITING_ADMISSION_STATES = ["waiting_host", "knocking"];
|
|
1023
1043
|
|
|
1024
1044
|
// src/core/calls.ts
|
|
1025
1045
|
var TRANSITIONS = {
|
|
@@ -1268,813 +1288,492 @@ function createCallCenter(options) {
|
|
|
1268
1288
|
};
|
|
1269
1289
|
}
|
|
1270
1290
|
|
|
1271
|
-
// src/core/
|
|
1272
|
-
var
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
captionTranslations: {},
|
|
1285
|
-
captionTranslationError: null
|
|
1291
|
+
// src/core/repository.ts
|
|
1292
|
+
var MEET_SCHEMA = "communication";
|
|
1293
|
+
var TABLES = {
|
|
1294
|
+
meetings: "meet_meetings",
|
|
1295
|
+
participants: "meet_participants",
|
|
1296
|
+
invites: "meet_call_invites",
|
|
1297
|
+
recordings: "meet_recordings",
|
|
1298
|
+
transcripts: "meet_transcript_segments",
|
|
1299
|
+
notes: "meet_notes",
|
|
1300
|
+
/** Wave 1 (0.8): who a meeting was sent to, and what they answered. */
|
|
1301
|
+
invitees: "meet_invitees",
|
|
1302
|
+
/** Wave 1 (0.8): one occurrence of a series cancelled or moved. */
|
|
1303
|
+
occurrenceOverrides: "meet_occurrence_overrides"
|
|
1286
1304
|
};
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1305
|
+
var RPCS = {
|
|
1306
|
+
/** Atomic: create the invite row and stamp its expiry in one statement. */
|
|
1307
|
+
createCallInvite: "meet_create_call_invite",
|
|
1308
|
+
/** Atomic settle. Refuses a transition out of a terminal state. */
|
|
1309
|
+
settleCallInvite: "meet_settle_call_invite",
|
|
1310
|
+
/** Every invite still ringing this user — the reconnect backfill door. */
|
|
1311
|
+
pendingCallInvites: "meet_pending_call_invites",
|
|
1312
|
+
/** Instant meeting, or the durable one behind a recurring link. */
|
|
1313
|
+
getOrCreateMeeting: "meet_get_or_create_meeting",
|
|
1314
|
+
/** Resolve a durable public link, before/during/after its scheduled time. */
|
|
1315
|
+
meetingBySlug: "meet_meeting_by_slug",
|
|
1316
|
+
/*
|
|
1317
|
+
* ── S4 ADMISSION (CORE-DESIGN §2.4). Each returns the decided row; the caller then
|
|
1318
|
+
* sends the payload-free `mx.state.changed` hint. Authorized by `who_may_admit`.
|
|
1319
|
+
*/
|
|
1320
|
+
admit: "meet_admit",
|
|
1321
|
+
deny: "meet_deny",
|
|
1322
|
+
undeny: "meet_undeny",
|
|
1323
|
+
admitAll: "meet_admit_all",
|
|
1324
|
+
setLock: "meet_set_lock",
|
|
1325
|
+
recordConsent: "meet_record_consent",
|
|
1326
|
+
/*
|
|
1327
|
+
* ── MEET WAVE 1 — management (0.8). Every write is a SECURITY DEFINER door
|
|
1328
|
+
* authorized by the meeting's host or a real iam grant (editor to change,
|
|
1329
|
+
* admin = co-host to archive or name a co-host) — never an organization
|
|
1330
|
+
* match. After any of these writes, call `announceMeeting` so the server
|
|
1331
|
+
* sends invitations / updates / cancellations and re-queues reminders.
|
|
1332
|
+
*/
|
|
1333
|
+
/** Schedule with every setting at create; unset settings come from the knobs. */
|
|
1334
|
+
scheduleMeeting: "meet_schedule_meeting",
|
|
1335
|
+
/** Edit / reschedule — a JSON patch with a version compare-and-swap. */
|
|
1336
|
+
updateMeeting: "meet_update_meeting",
|
|
1337
|
+
/** Cancel is a state (`cancelled_at`); the link keeps resolving. */
|
|
1338
|
+
cancelMeeting: "meet_cancel_meeting",
|
|
1339
|
+
/** Archive (soft delete) — the invitations and exceptions cascade. */
|
|
1340
|
+
archiveMeeting: "meet_archive_meeting",
|
|
1341
|
+
restoreMeeting: "meet_restore_meeting",
|
|
1342
|
+
/** Cancel, move or restore ONE occurrence of a series by its original start. */
|
|
1343
|
+
setOccurrence: "meet_set_occurrence",
|
|
1344
|
+
addInvitees: "meet_add_invitees",
|
|
1345
|
+
setInviteeRole: "meet_set_invitee_role",
|
|
1346
|
+
removeInvitee: "meet_remove_invitee",
|
|
1347
|
+
/** A signed-in invitee answers for themselves. */
|
|
1348
|
+
respond: "meet_respond",
|
|
1349
|
+
/** The emailed RSVP link (no account): read and answer. */
|
|
1350
|
+
invitationByToken: "meet_invitation_by_token",
|
|
1351
|
+
respondByToken: "meet_respond_by_token",
|
|
1352
|
+
/** One meeting's occurrences between two instants (INVOKER — the reader's RLS). */
|
|
1353
|
+
meetingOccurrences: "meet_meeting_occurrences",
|
|
1354
|
+
/** Every occurrence the reader can see between two instants — the Upcoming list. */
|
|
1355
|
+
occurrencesBetween: "meet_occurrences_between",
|
|
1356
|
+
/*
|
|
1357
|
+
* ── MEET WAVE 3 — after the meeting. Both INVOKER (the reader's RLS) and both
|
|
1358
|
+
* scoped to the reader's OWN meetings (host, invitee, attended) — RLS is the
|
|
1359
|
+
* ceiling, never the view.
|
|
1360
|
+
*/
|
|
1361
|
+
/** The recordings library. */
|
|
1362
|
+
myRecordings: "meet_my_recordings",
|
|
1363
|
+
/** Titles, agendas, current notes and final transcript lines; snippets mark hits with ⟦ ⟧. */
|
|
1364
|
+
search: "meet_search"
|
|
1365
|
+
};
|
|
1366
|
+
var VOID_RPCS = [RPCS.recordConsent];
|
|
1367
|
+
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";
|
|
1368
|
+
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";
|
|
1369
|
+
var RECORDING_COLUMNS = "id,meeting_id,state,started_at,ended_at,started_by,file_id,failure_reason";
|
|
1370
|
+
var SEGMENT_COLUMNS = "id,meeting_id,identity,speaker,text,started_at,is_final,metadata";
|
|
1371
|
+
var NOTE_COLUMNS = "id,meeting_id,text,kind,assignee_user_id,created_at,metadata";
|
|
1372
|
+
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";
|
|
1373
|
+
function projectStays(value) {
|
|
1374
|
+
if (!Array.isArray(value)) return [];
|
|
1375
|
+
const stays = [];
|
|
1376
|
+
for (const item of value) {
|
|
1377
|
+
if (item === null || typeof item !== "object") continue;
|
|
1378
|
+
const joined = item["joined_at"];
|
|
1379
|
+
const left = item["left_at"];
|
|
1380
|
+
if (typeof joined !== "string" || joined.length === 0) continue;
|
|
1381
|
+
stays.push({ joinedAt: joined, leftAt: typeof left === "string" && left.length > 0 ? left : null });
|
|
1382
|
+
}
|
|
1383
|
+
return stays;
|
|
1295
1384
|
}
|
|
1296
|
-
function
|
|
1297
|
-
|
|
1385
|
+
function text(row, key) {
|
|
1386
|
+
const value = row[key];
|
|
1387
|
+
return typeof value === "string" && value.length > 0 ? value : null;
|
|
1298
1388
|
}
|
|
1299
|
-
function
|
|
1300
|
-
const
|
|
1301
|
-
if (
|
|
1302
|
-
|
|
1303
|
-
for (const raw of Array.isArray(row["options"]) ? row["options"] : []) {
|
|
1304
|
-
const option = obj(raw);
|
|
1305
|
-
const optionId = option === null ? null : str(option["id"]);
|
|
1306
|
-
if (option === null || optionId === null) continue;
|
|
1307
|
-
const votes = num(option["votes"]);
|
|
1308
|
-
options.push({
|
|
1309
|
-
id: optionId,
|
|
1310
|
-
text: str(option["text"]) ?? "",
|
|
1311
|
-
...votes !== null ? { votes } : {},
|
|
1312
|
-
...Array.isArray(option["voters"]) ? { voters: strings(option["voters"]) } : {}
|
|
1313
|
-
});
|
|
1389
|
+
function requireText(row, key, operation) {
|
|
1390
|
+
const value = text(row, key);
|
|
1391
|
+
if (value === null) {
|
|
1392
|
+
throw invalidResponse(operation, `the row is missing "${key}"`);
|
|
1314
1393
|
}
|
|
1315
|
-
|
|
1316
|
-
return {
|
|
1317
|
-
id,
|
|
1318
|
-
question: str(row["question"]) ?? "",
|
|
1319
|
-
options,
|
|
1320
|
-
multiple: row["multiple"] === true,
|
|
1321
|
-
anonymous: row["anonymous"] === true,
|
|
1322
|
-
state: state === "live" || state === "closed" ? state : "draft",
|
|
1323
|
-
resultsShared: row["results_shared"] === true,
|
|
1324
|
-
voterCount: num(row["voter_count"]),
|
|
1325
|
-
myVote: strings(row["my_vote"]),
|
|
1326
|
-
createdByName: str(row["created_by_name"]) ?? "",
|
|
1327
|
-
createdAt: str(row["created_at"])
|
|
1328
|
-
};
|
|
1394
|
+
return value;
|
|
1329
1395
|
}
|
|
1330
|
-
function
|
|
1331
|
-
const
|
|
1332
|
-
|
|
1333
|
-
const state = str(row["state"]);
|
|
1334
|
-
return {
|
|
1335
|
-
id,
|
|
1336
|
-
text: str(row["text"]) ?? "",
|
|
1337
|
-
askerName: str(row["asker_name"]) ?? "",
|
|
1338
|
-
anonymous: row["anonymous"] === true,
|
|
1339
|
-
state: state === "answered" || state === "dismissed" ? state : "open",
|
|
1340
|
-
answer: str(row["answer"]),
|
|
1341
|
-
answeredByName: str(row["answered_by_name"]),
|
|
1342
|
-
upvotes: num(row["upvotes"]) ?? 0,
|
|
1343
|
-
upvotedByMe: row["upvoted_by_me"] === true,
|
|
1344
|
-
mine: row["mine"] === true,
|
|
1345
|
-
createdAt: str(row["created_at"])
|
|
1346
|
-
};
|
|
1396
|
+
function bool(row, key, fallback) {
|
|
1397
|
+
const value = row[key];
|
|
1398
|
+
return typeof value === "boolean" ? value : fallback;
|
|
1347
1399
|
}
|
|
1348
|
-
function
|
|
1349
|
-
const
|
|
1350
|
-
if (
|
|
1351
|
-
|
|
1352
|
-
for (const raw of row["points"]) {
|
|
1353
|
-
if (Array.isArray(raw) && typeof raw[0] === "number" && typeof raw[1] === "number") {
|
|
1354
|
-
points.push([raw[0], raw[1]]);
|
|
1355
|
-
}
|
|
1356
|
-
}
|
|
1357
|
-
return {
|
|
1358
|
-
id,
|
|
1359
|
-
points,
|
|
1360
|
-
color: str(row["color"]) ?? "#111827",
|
|
1361
|
-
width: num(row["width"]) ?? 3,
|
|
1362
|
-
tool: row["tool"] === "highlighter" ? "highlighter" : "pen",
|
|
1363
|
-
by: str(row["by"]) ?? "",
|
|
1364
|
-
byName: str(row["by_name"]) ?? ""
|
|
1365
|
-
};
|
|
1400
|
+
function stringArray(row, key) {
|
|
1401
|
+
const value = row[key];
|
|
1402
|
+
if (!Array.isArray(value)) return [];
|
|
1403
|
+
return value.filter((entry) => typeof entry === "string");
|
|
1366
1404
|
}
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1405
|
+
var CALL_STATES = [
|
|
1406
|
+
"idle",
|
|
1407
|
+
"dialing",
|
|
1408
|
+
"ringing",
|
|
1409
|
+
"connecting",
|
|
1410
|
+
"active",
|
|
1411
|
+
"declined",
|
|
1412
|
+
"missed",
|
|
1413
|
+
"busy",
|
|
1414
|
+
"cancelled",
|
|
1415
|
+
"ended",
|
|
1416
|
+
"failed"
|
|
1417
|
+
];
|
|
1418
|
+
function callState(value) {
|
|
1419
|
+
return CALL_STATES.includes(value) ? value : "failed";
|
|
1372
1420
|
}
|
|
1373
|
-
function
|
|
1374
|
-
const
|
|
1375
|
-
|
|
1376
|
-
const rooms = [];
|
|
1377
|
-
for (const raw of Array.isArray(row["rooms"]) ? row["rooms"] : []) {
|
|
1378
|
-
const room = obj(raw);
|
|
1379
|
-
const roomId = room === null ? null : str(room["id"]);
|
|
1380
|
-
if (room === null || roomId === null) continue;
|
|
1381
|
-
rooms.push({ id: roomId, name: str(room["name"]) ?? roomId, identities: strings(room["identities"]) });
|
|
1382
|
-
}
|
|
1421
|
+
function projectSegment(row) {
|
|
1422
|
+
const operation = "meet.projectSegment";
|
|
1423
|
+
const id = requireText(row, "id", operation);
|
|
1383
1424
|
return {
|
|
1384
1425
|
id,
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1426
|
+
meetingId: asMeetingId(requireText(row, "meeting_id", operation)),
|
|
1427
|
+
identity: asParticipantIdentity(text(row, "identity") ?? "unknown"),
|
|
1428
|
+
// A nameless line is a line nobody can act on. The identity is a worse
|
|
1429
|
+
// name than a real one and a far better one than an empty string.
|
|
1430
|
+
speaker: text(row, "speaker") ?? text(row, "identity") ?? "Someone",
|
|
1431
|
+
text: text(row, "text") ?? "",
|
|
1432
|
+
startedAt: text(row, "started_at") ?? (/* @__PURE__ */ new Date(0)).toISOString(),
|
|
1433
|
+
// The table holds finals only; a row that somehow says otherwise is still
|
|
1434
|
+
// a durable record and is rendered as one.
|
|
1435
|
+
final: bool(row, "is_final", true),
|
|
1436
|
+
source: "record",
|
|
1437
|
+
// The worker publishes the caption under THIS row's id (`lk.segment_id` =
|
|
1438
|
+
// the durable segment id), which is what lets a record replace the exact
|
|
1439
|
+
// caption it supersedes instead of doubling the line (D16).
|
|
1440
|
+
captionId: id
|
|
1393
1441
|
};
|
|
1394
1442
|
}
|
|
1395
|
-
function
|
|
1396
|
-
|
|
1443
|
+
function projectNote(row) {
|
|
1444
|
+
const operation = "meet.projectNote";
|
|
1445
|
+
const kind = text(row, "kind");
|
|
1446
|
+
const rawMetadata = row["metadata"];
|
|
1447
|
+
const metadata = typeof rawMetadata === "object" && rawMetadata !== null && !Array.isArray(rawMetadata) ? rawMetadata : {};
|
|
1448
|
+
const assignee = metadata["assignee"];
|
|
1449
|
+
const assigneeRow = typeof assignee === "object" && assignee !== null && !Array.isArray(assignee) ? assignee : {};
|
|
1450
|
+
const windowValue = metadata["window"];
|
|
1451
|
+
const windowRow = typeof windowValue === "object" && windowValue !== null && !Array.isArray(windowValue) ? windowValue : {};
|
|
1452
|
+
const assigneeUserId = text(row, "assignee_user_id");
|
|
1453
|
+
const displayName = assigneeRow["display_name"];
|
|
1397
1454
|
return {
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1455
|
+
id: requireText(row, "id", operation),
|
|
1456
|
+
meetingId: asMeetingId(requireText(row, "meeting_id", operation)),
|
|
1457
|
+
text: (text(row, "text") ?? "").trim(),
|
|
1458
|
+
createdAt: text(row, "created_at") ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
1459
|
+
// An unknown kind is a NOTE, never dropped: a row the database accepted
|
|
1460
|
+
// is a row a person wrote down, whatever a future migration calls it.
|
|
1461
|
+
kind: kind === "decision" || kind === "action-item" ? kind : "note",
|
|
1462
|
+
assigneeUserId: assigneeUserId === null ? null : asUserId(assigneeUserId),
|
|
1463
|
+
// D18: a guest never gets an `assignee_user_id` (the FK targets
|
|
1464
|
+
// `auth.users`), so their name lives in the metadata and is read here —
|
|
1465
|
+
// an action item whose owner has no name is an action item nobody owns.
|
|
1466
|
+
assigneeDisplayName: typeof displayName === "string" && displayName.length > 0 ? displayName : null,
|
|
1467
|
+
role: metadata["role"] === "summary" ? "summary" : null,
|
|
1468
|
+
window: typeof windowRow["index"] === "number" ? windowRow["index"] : null,
|
|
1469
|
+
// D20. `superseded_by` is in `NOTE_PUBLIC_METADATA_KEYS` server-side, so a
|
|
1470
|
+
// session-less guest's room-borne copy carries it too — otherwise the guest
|
|
1471
|
+
// would be the one lane still reading a decision twice.
|
|
1472
|
+
supersededBy: typeof metadata["superseded_by"] === "string" && metadata["superseded_by"].length > 0 ? metadata["superseded_by"] : null,
|
|
1473
|
+
// THE DATABASE ROW. `core/room-events.ts` marks its copy of the same row
|
|
1474
|
+
// `"room"`, and the store's precedence rule reads this field (MRI-A12).
|
|
1475
|
+
source: "record"
|
|
1408
1476
|
};
|
|
1409
1477
|
}
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1478
|
+
var RSVP_STATES = ["needs_action", "accepted", "declined", "tentative"];
|
|
1479
|
+
var OCCURRENCE_STATES = ["scheduled", "moved", "cancelled"];
|
|
1480
|
+
function rsvpState(value) {
|
|
1481
|
+
return RSVP_STATES.includes(value) ? value : "needs_action";
|
|
1482
|
+
}
|
|
1483
|
+
function num(row, key) {
|
|
1484
|
+
const value = row[key];
|
|
1485
|
+
return typeof value === "number" ? value : null;
|
|
1486
|
+
}
|
|
1487
|
+
function when(row, key) {
|
|
1488
|
+
const value = row[key];
|
|
1489
|
+
if (value instanceof Date) return value.toISOString();
|
|
1490
|
+
return text(row, key);
|
|
1491
|
+
}
|
|
1492
|
+
function projectInvitee(row) {
|
|
1493
|
+
const operation = "meet.projectInvitee";
|
|
1494
|
+
const userId = text(row, "invitee_user_id");
|
|
1416
1495
|
return {
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1496
|
+
id: requireText(row, "id", operation),
|
|
1497
|
+
meetingId: asMeetingId(requireText(row, "meeting_id", operation)),
|
|
1498
|
+
userId: userId === null ? null : asUserId(userId),
|
|
1499
|
+
email: text(row, "email"),
|
|
1500
|
+
displayName: text(row, "display_name"),
|
|
1501
|
+
role: text(row, "role") === "cohost" ? "cohost" : "invitee",
|
|
1502
|
+
rsvpState: rsvpState(text(row, "rsvp_state")),
|
|
1503
|
+
respondedAt: when(row, "responded_at"),
|
|
1504
|
+
rsvpNote: text(row, "rsvp_note"),
|
|
1505
|
+
lastSentSequence: num(row, "last_sent_sequence"),
|
|
1506
|
+
lastSentAt: when(row, "last_sent_at")
|
|
1425
1507
|
};
|
|
1426
1508
|
}
|
|
1427
|
-
function
|
|
1428
|
-
const
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1509
|
+
function projectOccurrence(row) {
|
|
1510
|
+
const operation = "meet.projectOccurrence";
|
|
1511
|
+
const state = text(row, "state");
|
|
1512
|
+
return {
|
|
1513
|
+
meetingId: asMeetingId(requireText(row, "meeting_id", operation)),
|
|
1514
|
+
originalStart: when(row, "original_start") ?? requireText(row, "original_start", operation),
|
|
1515
|
+
occurrenceStart: when(row, "occurrence_start") ?? requireText(row, "occurrence_start", operation),
|
|
1516
|
+
durationMinutes: num(row, "duration_minutes"),
|
|
1517
|
+
// An unknown state is shown as scheduled, never dropped: the row is real.
|
|
1518
|
+
state: OCCURRENCE_STATES.includes(state) ? state : "scheduled",
|
|
1519
|
+
sequence: num(row, "sequence") ?? 0
|
|
1520
|
+
};
|
|
1433
1521
|
}
|
|
1434
|
-
function
|
|
1435
|
-
const
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1522
|
+
function projectUpcoming(row) {
|
|
1523
|
+
const operation = "meet.projectUpcoming";
|
|
1524
|
+
const kind = text(row, "kind");
|
|
1525
|
+
const myRole = text(row, "my_role");
|
|
1526
|
+
const myRsvp = text(row, "my_rsvp");
|
|
1527
|
+
return {
|
|
1528
|
+
...projectOccurrence(row),
|
|
1529
|
+
organizationId: asOrganizationId(requireText(row, "organization_id", operation)),
|
|
1530
|
+
title: text(row, "title") ?? "Meeting",
|
|
1531
|
+
slug: requireText(row, "slug", operation),
|
|
1532
|
+
kind: kind === "scheduled" || kind === "recurring" ? kind : "instant",
|
|
1533
|
+
hostUserId: asUserId(requireText(row, "host_user_id", operation)),
|
|
1534
|
+
timeZone: text(row, "time_zone") ?? "UTC",
|
|
1535
|
+
meetingCancelled: bool(row, "meeting_cancelled", false),
|
|
1536
|
+
myRsvp: myRsvp === null ? null : rsvpState(myRsvp),
|
|
1537
|
+
myRole: myRole === "host" || myRole === "cohost" || myRole === "invitee" ? myRole : null
|
|
1538
|
+
};
|
|
1539
|
+
}
|
|
1540
|
+
function projectOverride(row) {
|
|
1541
|
+
const operation = "meet.projectOverride";
|
|
1542
|
+
return {
|
|
1543
|
+
id: requireText(row, "id", operation),
|
|
1544
|
+
meetingId: asMeetingId(requireText(row, "meeting_id", operation)),
|
|
1545
|
+
originalStart: when(row, "original_start") ?? requireText(row, "original_start", operation),
|
|
1546
|
+
state: text(row, "state") === "moved" ? "moved" : "cancelled",
|
|
1547
|
+
newStart: when(row, "new_start"),
|
|
1548
|
+
newDurationMinutes: num(row, "new_duration_minutes"),
|
|
1549
|
+
reason: text(row, "reason"),
|
|
1550
|
+
calendarSequence: num(row, "calendar_sequence") ?? 0,
|
|
1551
|
+
deletedAt: when(row, "deleted_at")
|
|
1552
|
+
};
|
|
1553
|
+
}
|
|
1554
|
+
function projectRsvpInvitation(body) {
|
|
1555
|
+
const operation = "meet.projectRsvpInvitation";
|
|
1556
|
+
if (typeof body !== "object" || body === null) {
|
|
1557
|
+
throw invalidResponse(operation, "the RSVP door returned no object");
|
|
1465
1558
|
}
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1559
|
+
const row = body;
|
|
1560
|
+
if (row["ok"] !== true) {
|
|
1561
|
+
return {
|
|
1562
|
+
ok: false,
|
|
1563
|
+
message: text(row, "message") ?? "This invitation link is no longer valid \u2014 ask the host to send it again."
|
|
1564
|
+
};
|
|
1470
1565
|
}
|
|
1471
|
-
|
|
1472
|
-
}
|
|
1473
|
-
function without(collab, ids) {
|
|
1474
|
-
const gone = new Set(ids);
|
|
1566
|
+
const meeting = row["meeting"] ?? {};
|
|
1567
|
+
const invitee = row["invitee"] ?? {};
|
|
1475
1568
|
return {
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1569
|
+
ok: true,
|
|
1570
|
+
meeting: {
|
|
1571
|
+
title: text(meeting, "title") ?? "Meeting",
|
|
1572
|
+
slug: requireText(meeting, "slug", operation),
|
|
1573
|
+
scheduledFor: when(meeting, "scheduled_for"),
|
|
1574
|
+
durationMinutes: num(meeting, "duration_minutes"),
|
|
1575
|
+
timeZone: text(meeting, "time_zone") ?? "UTC",
|
|
1576
|
+
agenda: text(meeting, "agenda"),
|
|
1577
|
+
recurrenceRule: text(meeting, "recurrence_rule"),
|
|
1578
|
+
cancelledAt: when(meeting, "cancelled_at"),
|
|
1579
|
+
cancellationReason: text(meeting, "cancellation_reason"),
|
|
1580
|
+
endedAt: when(meeting, "ended_at")
|
|
1581
|
+
},
|
|
1582
|
+
invitee: {
|
|
1583
|
+
displayName: text(invitee, "display_name"),
|
|
1584
|
+
email: text(invitee, "email"),
|
|
1585
|
+
rsvpState: rsvpState(text(invitee, "rsvp_state")),
|
|
1586
|
+
respondedAt: when(invitee, "responded_at")
|
|
1587
|
+
}
|
|
1480
1588
|
};
|
|
1481
1589
|
}
|
|
1482
|
-
function
|
|
1483
|
-
const
|
|
1484
|
-
if (
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1590
|
+
function settingsPatch(settings) {
|
|
1591
|
+
const patch = {};
|
|
1592
|
+
if (settings.lobbyEnabled !== void 0) patch["lobby_enabled"] = settings.lobbyEnabled;
|
|
1593
|
+
if (settings.recordingPolicy !== void 0) patch["recording_policy"] = settings.recordingPolicy;
|
|
1594
|
+
if (settings.aiEnabled !== void 0) patch["ai_enabled"] = settings.aiEnabled;
|
|
1595
|
+
if (settings.joinBeforeHost !== void 0) patch["join_before_host"] = settings.joinBeforeHost;
|
|
1596
|
+
return patch;
|
|
1597
|
+
}
|
|
1598
|
+
function changesPatch(changes) {
|
|
1599
|
+
const patch = settingsPatch(changes);
|
|
1600
|
+
if (changes.title !== void 0) patch["title"] = changes.title;
|
|
1601
|
+
if (changes.agenda !== void 0) patch["agenda"] = changes.agenda;
|
|
1602
|
+
if (changes.timeZone !== void 0) patch["time_zone"] = changes.timeZone;
|
|
1603
|
+
if (changes.scheduledFor !== void 0) patch["scheduled_for"] = changes.scheduledFor;
|
|
1604
|
+
if (changes.scheduledDurationMinutes !== void 0) {
|
|
1605
|
+
patch["scheduled_duration_minutes"] = changes.scheduledDurationMinutes;
|
|
1606
|
+
}
|
|
1607
|
+
if (changes.recurrenceRule !== void 0) patch["recurrence_rule"] = changes.recurrenceRule;
|
|
1608
|
+
if (changes.keepExceptions !== void 0) patch["keep_exceptions"] = changes.keepExceptions;
|
|
1609
|
+
return patch;
|
|
1610
|
+
}
|
|
1611
|
+
function createMeetRepository(options) {
|
|
1612
|
+
const db = options.client.schema(MEET_SCHEMA);
|
|
1613
|
+
async function guardVoid(operation, run) {
|
|
1614
|
+
let response = await run();
|
|
1615
|
+
if (response.error !== null) {
|
|
1616
|
+
const normalized = normalizeMeetError(response.error, operation);
|
|
1617
|
+
if (normalized.code === "session-unavailable" && options.session !== void 0) {
|
|
1618
|
+
await options.session();
|
|
1619
|
+
response = await run();
|
|
1491
1620
|
}
|
|
1621
|
+
if (response.error !== null) throw normalizeMeetError(response.error, operation);
|
|
1492
1622
|
}
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1623
|
+
}
|
|
1624
|
+
async function guard(operation, run) {
|
|
1625
|
+
let response = await run();
|
|
1626
|
+
if (response.error !== null) {
|
|
1627
|
+
const normalized = normalizeMeetError(response.error, operation);
|
|
1628
|
+
if (normalized.code === "session-unavailable" && options.session !== void 0) {
|
|
1629
|
+
await options.session();
|
|
1630
|
+
response = await run();
|
|
1497
1631
|
}
|
|
1632
|
+
if (response.error !== null) throw normalizeMeetError(response.error, operation);
|
|
1498
1633
|
}
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
next = { ...next, moveRequest: { roomId: str(move["room_id"]), at: context.now } };
|
|
1634
|
+
if (response.data === null) {
|
|
1635
|
+
throw invalidResponse(operation, "the database returned no row");
|
|
1502
1636
|
}
|
|
1503
|
-
return
|
|
1504
|
-
}
|
|
1505
|
-
if (Array.isArray(payload["removed"])) {
|
|
1506
|
-
return { next: without(collab, strings(payload["removed"])) };
|
|
1507
|
-
}
|
|
1508
|
-
if (Array.isArray(payload["hidden"])) {
|
|
1509
|
-
return context.isHost ? { next: null, dropped: "hosts keep dismissed questions" } : { next: without(collab, strings(payload["hidden"])) };
|
|
1510
|
-
}
|
|
1511
|
-
if (payload["cleared"] === "whiteboard") {
|
|
1512
|
-
return { next: { ...collab, strokes: [] } };
|
|
1637
|
+
return response.data;
|
|
1513
1638
|
}
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
index += 1;
|
|
1639
|
+
function projectInvite(row) {
|
|
1640
|
+
const operation = "meet.projectInvite";
|
|
1641
|
+
const mode = text(row, "mode");
|
|
1642
|
+
return {
|
|
1643
|
+
id: asCallId(requireText(row, "id", operation)),
|
|
1644
|
+
organizationId: asOrganizationId(requireText(row, "organization_id", operation)),
|
|
1645
|
+
roomName: asRoomName(requireText(row, "room_name", operation)),
|
|
1646
|
+
mode: mode === "video" ? "video" : "audio",
|
|
1647
|
+
callerUserId: asUserId(requireText(row, "caller_user_id", operation)),
|
|
1648
|
+
callerName: text(row, "caller_name") ?? "Someone",
|
|
1649
|
+
callerAvatarUrl: text(row, "caller_avatar_url"),
|
|
1650
|
+
calleeUserIds: stringArray(row, "callee_ids").map(asUserId),
|
|
1651
|
+
conversationId: text(row, "conversation_id"),
|
|
1652
|
+
createdAt: requireText(row, "created_at", operation),
|
|
1653
|
+
expiresAt: text(row, "expires_at") ?? requireText(row, "created_at", operation),
|
|
1654
|
+
state: callState(text(row, "state")),
|
|
1655
|
+
declineMessage: text(row, "decline_message")
|
|
1656
|
+
};
|
|
1533
1657
|
}
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
}
|
|
1541
|
-
function defaultCaptionLanguage(settings, viewerLanguage) {
|
|
1542
|
-
const configured = settings?.captionTranslationLanguage.trim() ?? "";
|
|
1543
|
-
if (configured.length > 0) return configured;
|
|
1544
|
-
const own = (viewerLanguage ?? "").trim();
|
|
1545
|
-
return own.length > 0 ? own : "en";
|
|
1546
|
-
}
|
|
1547
|
-
var CAPTION_LANGUAGES = [
|
|
1548
|
-
{ tag: "en", name: "English" },
|
|
1549
|
-
{ tag: "es", name: "Spanish" },
|
|
1550
|
-
{ tag: "fr", name: "French" },
|
|
1551
|
-
{ tag: "de", name: "German" },
|
|
1552
|
-
{ tag: "it", name: "Italian" },
|
|
1553
|
-
{ tag: "pt", name: "Portuguese" },
|
|
1554
|
-
{ tag: "nl", name: "Dutch" },
|
|
1555
|
-
{ tag: "pl", name: "Polish" },
|
|
1556
|
-
{ tag: "ru", name: "Russian" },
|
|
1557
|
-
{ tag: "uk", name: "Ukrainian" },
|
|
1558
|
-
{ tag: "tr", name: "Turkish" },
|
|
1559
|
-
{ tag: "ar", name: "Arabic" },
|
|
1560
|
-
{ tag: "he", name: "Hebrew" },
|
|
1561
|
-
{ tag: "fa", name: "Persian" },
|
|
1562
|
-
{ tag: "hi", name: "Hindi" },
|
|
1563
|
-
{ tag: "bn", name: "Bengali" },
|
|
1564
|
-
{ tag: "zh", name: "Chinese" },
|
|
1565
|
-
{ tag: "ja", name: "Japanese" },
|
|
1566
|
-
{ tag: "ko", name: "Korean" },
|
|
1567
|
-
{ tag: "vi", name: "Vietnamese" },
|
|
1568
|
-
{ tag: "th", name: "Thai" },
|
|
1569
|
-
{ tag: "id", name: "Indonesian" },
|
|
1570
|
-
{ tag: "sv", name: "Swedish" }
|
|
1571
|
-
];
|
|
1572
|
-
async function loadMeetingActivities(api, meetingId, meetingPass = null) {
|
|
1573
|
-
let response;
|
|
1574
|
-
try {
|
|
1575
|
-
response = await api.request({
|
|
1576
|
-
path: `${MEET_ROUTES.collab}?meeting_id=${encodeURIComponent(meetingId)}`,
|
|
1577
|
-
method: "GET",
|
|
1578
|
-
operation: "meet.loadMeetingActivities",
|
|
1579
|
-
requireAuth: meetingPass === null,
|
|
1580
|
-
...meetingPass !== null ? { headers: { [MEETING_PASS_HEADER]: meetingPass } } : {}
|
|
1581
|
-
});
|
|
1582
|
-
} catch (error) {
|
|
1658
|
+
function projectMeeting(row) {
|
|
1659
|
+
const operation = "meet.projectMeeting";
|
|
1660
|
+
const kind = text(row, "kind");
|
|
1661
|
+
const policy = text(row, "recording_policy");
|
|
1662
|
+
const duration = row["scheduled_duration_minutes"];
|
|
1663
|
+
const metadata = row["metadata"];
|
|
1583
1664
|
return {
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1665
|
+
id: asMeetingId(requireText(row, "id", operation)),
|
|
1666
|
+
organizationId: asOrganizationId(requireText(row, "organization_id", operation)),
|
|
1667
|
+
roomName: asRoomName(requireText(row, "room_name", operation)),
|
|
1668
|
+
slug: requireText(row, "slug", operation),
|
|
1669
|
+
title: text(row, "title") ?? "Meeting",
|
|
1670
|
+
kind: kind === "scheduled" || kind === "recurring" ? kind : "instant",
|
|
1671
|
+
hostUserId: asUserId(requireText(row, "host_user_id", operation)),
|
|
1672
|
+
scheduledFor: text(row, "scheduled_for"),
|
|
1673
|
+
scheduledDurationMinutes: typeof duration === "number" ? duration : null,
|
|
1674
|
+
startedAt: text(row, "started_at"),
|
|
1675
|
+
endedAt: text(row, "ended_at"),
|
|
1676
|
+
locked: bool(row, "locked", false),
|
|
1677
|
+
lobbyEnabled: bool(row, "lobby_enabled", true),
|
|
1678
|
+
recordingPolicy: policy === "disabled" || policy === "always-on" ? policy : "host-controlled",
|
|
1679
|
+
aiEnabled: bool(row, "ai_enabled", true),
|
|
1680
|
+
metadata: typeof metadata === "object" && metadata !== null && !Array.isArray(metadata) ? metadata : {},
|
|
1681
|
+
timeZone: text(row, "time_zone") ?? "UTC",
|
|
1682
|
+
agenda: text(row, "agenda"),
|
|
1683
|
+
recurrenceRule: text(row, "recurrence_rule"),
|
|
1684
|
+
joinBeforeHost: bool(row, "join_before_host", true),
|
|
1685
|
+
cancelledAt: text(row, "cancelled_at"),
|
|
1686
|
+
cancellationReason: text(row, "cancellation_reason"),
|
|
1687
|
+
deletedAt: text(row, "deleted_at"),
|
|
1688
|
+
calendarSequence: typeof row["calendar_sequence"] === "number" ? row["calendar_sequence"] : 0,
|
|
1689
|
+
version: typeof row["version"] === "number" ? row["version"] : 1
|
|
1589
1690
|
};
|
|
1590
1691
|
}
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1692
|
+
function projectRecording(row) {
|
|
1693
|
+
const operation = "meet.projectRecording";
|
|
1694
|
+
const state = text(row, "state");
|
|
1695
|
+
const known = [
|
|
1696
|
+
"idle",
|
|
1697
|
+
"starting",
|
|
1698
|
+
"recording",
|
|
1699
|
+
"stopping",
|
|
1700
|
+
"processing",
|
|
1701
|
+
"available",
|
|
1702
|
+
"expired",
|
|
1703
|
+
"failed"
|
|
1704
|
+
];
|
|
1599
1705
|
return {
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1706
|
+
id: requireText(row, "id", operation),
|
|
1707
|
+
meetingId: asMeetingId(requireText(row, "meeting_id", operation)),
|
|
1708
|
+
state: known.includes(state) ? state : "failed",
|
|
1709
|
+
startedAt: text(row, "started_at"),
|
|
1710
|
+
endedAt: text(row, "ended_at"),
|
|
1711
|
+
startedBy: text(row, "started_by") === null ? null : asUserId(text(row, "started_by")),
|
|
1712
|
+
// A PLATFORM file id, never a provider URL (R3). A recording that only
|
|
1713
|
+
// exists as a LiveKit URL is orphaned the day the room is garbage
|
|
1714
|
+
// collected, which is the whole reason this column is not `url`.
|
|
1715
|
+
fileId: text(row, "file_id"),
|
|
1716
|
+
failureReason: text(row, "failure_reason"),
|
|
1717
|
+
retention: projectRecordingRetention(row["metadata"])
|
|
1605
1718
|
};
|
|
1606
1719
|
}
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
invitees: "meet_invitees",
|
|
1621
|
-
/** Wave 1 (0.8): one occurrence of a series cancelled or moved. */
|
|
1622
|
-
occurrenceOverrides: "meet_occurrence_overrides"
|
|
1623
|
-
};
|
|
1624
|
-
var RPCS = {
|
|
1625
|
-
/** Atomic: create the invite row and stamp its expiry in one statement. */
|
|
1626
|
-
createCallInvite: "meet_create_call_invite",
|
|
1627
|
-
/** Atomic settle. Refuses a transition out of a terminal state. */
|
|
1628
|
-
settleCallInvite: "meet_settle_call_invite",
|
|
1629
|
-
/** Every invite still ringing this user — the reconnect backfill door. */
|
|
1630
|
-
pendingCallInvites: "meet_pending_call_invites",
|
|
1631
|
-
/** Instant meeting, or the durable one behind a recurring link. */
|
|
1632
|
-
getOrCreateMeeting: "meet_get_or_create_meeting",
|
|
1633
|
-
/** Resolve a durable public link, before/during/after its scheduled time. */
|
|
1634
|
-
meetingBySlug: "meet_meeting_by_slug",
|
|
1635
|
-
admit: "meet_admit_participant",
|
|
1636
|
-
setLock: "meet_set_lock",
|
|
1637
|
-
recordConsent: "meet_record_consent",
|
|
1638
|
-
/*
|
|
1639
|
-
* ── MEET WAVE 1 — management (0.8). Every write is a SECURITY DEFINER door
|
|
1640
|
-
* authorized by the meeting's host or a real iam grant (editor to change,
|
|
1641
|
-
* admin = co-host to archive or name a co-host) — never an organization
|
|
1642
|
-
* match. After any of these writes, call `announceMeeting` so the server
|
|
1643
|
-
* sends invitations / updates / cancellations and re-queues reminders.
|
|
1644
|
-
*/
|
|
1645
|
-
/** Schedule with every setting at create; unset settings come from the knobs. */
|
|
1646
|
-
scheduleMeeting: "meet_schedule_meeting",
|
|
1647
|
-
/** Edit / reschedule — a JSON patch with a version compare-and-swap. */
|
|
1648
|
-
updateMeeting: "meet_update_meeting",
|
|
1649
|
-
/** Cancel is a state (`cancelled_at`); the link keeps resolving. */
|
|
1650
|
-
cancelMeeting: "meet_cancel_meeting",
|
|
1651
|
-
/** Archive (soft delete) — the invitations and exceptions cascade. */
|
|
1652
|
-
archiveMeeting: "meet_archive_meeting",
|
|
1653
|
-
restoreMeeting: "meet_restore_meeting",
|
|
1654
|
-
/** Cancel, move or restore ONE occurrence of a series by its original start. */
|
|
1655
|
-
setOccurrence: "meet_set_occurrence",
|
|
1656
|
-
addInvitees: "meet_add_invitees",
|
|
1657
|
-
setInviteeRole: "meet_set_invitee_role",
|
|
1658
|
-
removeInvitee: "meet_remove_invitee",
|
|
1659
|
-
/** A signed-in invitee answers for themselves. */
|
|
1660
|
-
respond: "meet_respond",
|
|
1661
|
-
/** The emailed RSVP link (no account): read and answer. */
|
|
1662
|
-
invitationByToken: "meet_invitation_by_token",
|
|
1663
|
-
respondByToken: "meet_respond_by_token",
|
|
1664
|
-
/** One meeting's occurrences between two instants (INVOKER — the reader's RLS). */
|
|
1665
|
-
meetingOccurrences: "meet_meeting_occurrences",
|
|
1666
|
-
/** Every occurrence the reader can see between two instants — the Upcoming list. */
|
|
1667
|
-
occurrencesBetween: "meet_occurrences_between",
|
|
1668
|
-
/*
|
|
1669
|
-
* ── MEET WAVE 3 — after the meeting. Both INVOKER (the reader's RLS) and both
|
|
1670
|
-
* scoped to the reader's OWN meetings (host, invitee, attended) — RLS is the
|
|
1671
|
-
* ceiling, never the view.
|
|
1672
|
-
*/
|
|
1673
|
-
/** The recordings library. */
|
|
1674
|
-
myRecordings: "meet_my_recordings",
|
|
1675
|
-
/** Titles, agendas, current notes and final transcript lines; snippets mark hits with ⟦ ⟧. */
|
|
1676
|
-
search: "meet_search"
|
|
1677
|
-
};
|
|
1678
|
-
var VOID_RPCS = [RPCS.admit, RPCS.recordConsent];
|
|
1679
|
-
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";
|
|
1680
|
-
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";
|
|
1681
|
-
var RECORDING_COLUMNS = "id,meeting_id,state,started_at,ended_at,started_by,file_id,failure_reason";
|
|
1682
|
-
var SEGMENT_COLUMNS = "id,meeting_id,identity,speaker,text,started_at,is_final,metadata";
|
|
1683
|
-
var NOTE_COLUMNS = "id,meeting_id,text,kind,assignee_user_id,created_at,metadata";
|
|
1684
|
-
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";
|
|
1685
|
-
function projectStays(value) {
|
|
1686
|
-
if (!Array.isArray(value)) return [];
|
|
1687
|
-
const stays = [];
|
|
1688
|
-
for (const item of value) {
|
|
1689
|
-
if (item === null || typeof item !== "object") continue;
|
|
1690
|
-
const joined = item["joined_at"];
|
|
1691
|
-
const left = item["left_at"];
|
|
1692
|
-
if (typeof joined !== "string" || joined.length === 0) continue;
|
|
1693
|
-
stays.push({ joinedAt: joined, leftAt: typeof left === "string" && left.length > 0 ? left : null });
|
|
1720
|
+
function projectRecordingRetention(value) {
|
|
1721
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return null;
|
|
1722
|
+
const retention = value["retention"];
|
|
1723
|
+
if (typeof retention !== "object" || retention === null || Array.isArray(retention)) return null;
|
|
1724
|
+
const row = retention;
|
|
1725
|
+
const phase = row["phase"];
|
|
1726
|
+
const operationId = row["operation_id"];
|
|
1727
|
+
const policyId = row["policy_id"];
|
|
1728
|
+
const policyUpdatedAt = row["policy_updated_at"];
|
|
1729
|
+
const dueAt = row["due_at"];
|
|
1730
|
+
const expiredAt = row["expired_at"];
|
|
1731
|
+
if (phase !== "warning" && phase !== "expired" || typeof operationId !== "string" || typeof policyId !== "string" || typeof policyUpdatedAt !== "string" || typeof dueAt !== "string" || expiredAt !== null && typeof expiredAt !== "string") return null;
|
|
1732
|
+
return { operationId, policyId, policyUpdatedAt, phase, dueAt, expiredAt };
|
|
1694
1733
|
}
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
function stringArray(row, key) {
|
|
1713
|
-
const value = row[key];
|
|
1714
|
-
if (!Array.isArray(value)) return [];
|
|
1715
|
-
return value.filter((entry) => typeof entry === "string");
|
|
1716
|
-
}
|
|
1717
|
-
var CALL_STATES = [
|
|
1718
|
-
"idle",
|
|
1719
|
-
"dialing",
|
|
1720
|
-
"ringing",
|
|
1721
|
-
"connecting",
|
|
1722
|
-
"active",
|
|
1723
|
-
"declined",
|
|
1724
|
-
"missed",
|
|
1725
|
-
"busy",
|
|
1726
|
-
"cancelled",
|
|
1727
|
-
"ended",
|
|
1728
|
-
"failed"
|
|
1729
|
-
];
|
|
1730
|
-
function callState(value) {
|
|
1731
|
-
return CALL_STATES.includes(value) ? value : "failed";
|
|
1732
|
-
}
|
|
1733
|
-
function projectSegment(row) {
|
|
1734
|
-
const operation = "meet.projectSegment";
|
|
1735
|
-
const id = requireText(row, "id", operation);
|
|
1736
|
-
return {
|
|
1737
|
-
id,
|
|
1738
|
-
meetingId: asMeetingId(requireText(row, "meeting_id", operation)),
|
|
1739
|
-
identity: asParticipantIdentity(text(row, "identity") ?? "unknown"),
|
|
1740
|
-
// A nameless line is a line nobody can act on. The identity is a worse
|
|
1741
|
-
// name than a real one and a far better one than an empty string.
|
|
1742
|
-
speaker: text(row, "speaker") ?? text(row, "identity") ?? "Someone",
|
|
1743
|
-
text: text(row, "text") ?? "",
|
|
1744
|
-
startedAt: text(row, "started_at") ?? (/* @__PURE__ */ new Date(0)).toISOString(),
|
|
1745
|
-
// The table holds finals only; a row that somehow says otherwise is still
|
|
1746
|
-
// a durable record and is rendered as one.
|
|
1747
|
-
final: bool(row, "is_final", true),
|
|
1748
|
-
source: "record",
|
|
1749
|
-
// The worker publishes the caption under THIS row's id (`lk.segment_id` =
|
|
1750
|
-
// the durable segment id), which is what lets a record replace the exact
|
|
1751
|
-
// caption it supersedes instead of doubling the line (D16).
|
|
1752
|
-
captionId: id
|
|
1753
|
-
};
|
|
1754
|
-
}
|
|
1755
|
-
function projectNote(row) {
|
|
1756
|
-
const operation = "meet.projectNote";
|
|
1757
|
-
const kind = text(row, "kind");
|
|
1758
|
-
const rawMetadata = row["metadata"];
|
|
1759
|
-
const metadata = typeof rawMetadata === "object" && rawMetadata !== null && !Array.isArray(rawMetadata) ? rawMetadata : {};
|
|
1760
|
-
const assignee = metadata["assignee"];
|
|
1761
|
-
const assigneeRow = typeof assignee === "object" && assignee !== null && !Array.isArray(assignee) ? assignee : {};
|
|
1762
|
-
const windowValue = metadata["window"];
|
|
1763
|
-
const windowRow = typeof windowValue === "object" && windowValue !== null && !Array.isArray(windowValue) ? windowValue : {};
|
|
1764
|
-
const assigneeUserId = text(row, "assignee_user_id");
|
|
1765
|
-
const displayName = assigneeRow["display_name"];
|
|
1766
|
-
return {
|
|
1767
|
-
id: requireText(row, "id", operation),
|
|
1768
|
-
meetingId: asMeetingId(requireText(row, "meeting_id", operation)),
|
|
1769
|
-
text: (text(row, "text") ?? "").trim(),
|
|
1770
|
-
createdAt: text(row, "created_at") ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
1771
|
-
// An unknown kind is a NOTE, never dropped: a row the database accepted
|
|
1772
|
-
// is a row a person wrote down, whatever a future migration calls it.
|
|
1773
|
-
kind: kind === "decision" || kind === "action-item" ? kind : "note",
|
|
1774
|
-
assigneeUserId: assigneeUserId === null ? null : asUserId(assigneeUserId),
|
|
1775
|
-
// D18: a guest never gets an `assignee_user_id` (the FK targets
|
|
1776
|
-
// `auth.users`), so their name lives in the metadata and is read here —
|
|
1777
|
-
// an action item whose owner has no name is an action item nobody owns.
|
|
1778
|
-
assigneeDisplayName: typeof displayName === "string" && displayName.length > 0 ? displayName : null,
|
|
1779
|
-
role: metadata["role"] === "summary" ? "summary" : null,
|
|
1780
|
-
window: typeof windowRow["index"] === "number" ? windowRow["index"] : null,
|
|
1781
|
-
// D20. `superseded_by` is in `NOTE_PUBLIC_METADATA_KEYS` server-side, so a
|
|
1782
|
-
// session-less guest's room-borne copy carries it too — otherwise the guest
|
|
1783
|
-
// would be the one lane still reading a decision twice.
|
|
1784
|
-
supersededBy: typeof metadata["superseded_by"] === "string" && metadata["superseded_by"].length > 0 ? metadata["superseded_by"] : null,
|
|
1785
|
-
// THE DATABASE ROW. `core/room-events.ts` marks its copy of the same row
|
|
1786
|
-
// `"room"`, and the store's precedence rule reads this field (MRI-A12).
|
|
1787
|
-
source: "record"
|
|
1788
|
-
};
|
|
1789
|
-
}
|
|
1790
|
-
var RSVP_STATES = ["needs_action", "accepted", "declined", "tentative"];
|
|
1791
|
-
var OCCURRENCE_STATES = ["scheduled", "moved", "cancelled"];
|
|
1792
|
-
function rsvpState(value) {
|
|
1793
|
-
return RSVP_STATES.includes(value) ? value : "needs_action";
|
|
1794
|
-
}
|
|
1795
|
-
function num2(row, key) {
|
|
1796
|
-
const value = row[key];
|
|
1797
|
-
return typeof value === "number" ? value : null;
|
|
1798
|
-
}
|
|
1799
|
-
function when(row, key) {
|
|
1800
|
-
const value = row[key];
|
|
1801
|
-
if (value instanceof Date) return value.toISOString();
|
|
1802
|
-
return text(row, key);
|
|
1803
|
-
}
|
|
1804
|
-
function projectInvitee(row) {
|
|
1805
|
-
const operation = "meet.projectInvitee";
|
|
1806
|
-
const userId = text(row, "invitee_user_id");
|
|
1807
|
-
return {
|
|
1808
|
-
id: requireText(row, "id", operation),
|
|
1809
|
-
meetingId: asMeetingId(requireText(row, "meeting_id", operation)),
|
|
1810
|
-
userId: userId === null ? null : asUserId(userId),
|
|
1811
|
-
email: text(row, "email"),
|
|
1812
|
-
displayName: text(row, "display_name"),
|
|
1813
|
-
role: text(row, "role") === "cohost" ? "cohost" : "invitee",
|
|
1814
|
-
rsvpState: rsvpState(text(row, "rsvp_state")),
|
|
1815
|
-
respondedAt: when(row, "responded_at"),
|
|
1816
|
-
rsvpNote: text(row, "rsvp_note"),
|
|
1817
|
-
lastSentSequence: num2(row, "last_sent_sequence"),
|
|
1818
|
-
lastSentAt: when(row, "last_sent_at")
|
|
1819
|
-
};
|
|
1820
|
-
}
|
|
1821
|
-
function projectOccurrence(row) {
|
|
1822
|
-
const operation = "meet.projectOccurrence";
|
|
1823
|
-
const state = text(row, "state");
|
|
1824
|
-
return {
|
|
1825
|
-
meetingId: asMeetingId(requireText(row, "meeting_id", operation)),
|
|
1826
|
-
originalStart: when(row, "original_start") ?? requireText(row, "original_start", operation),
|
|
1827
|
-
occurrenceStart: when(row, "occurrence_start") ?? requireText(row, "occurrence_start", operation),
|
|
1828
|
-
durationMinutes: num2(row, "duration_minutes"),
|
|
1829
|
-
// An unknown state is shown as scheduled, never dropped: the row is real.
|
|
1830
|
-
state: OCCURRENCE_STATES.includes(state) ? state : "scheduled",
|
|
1831
|
-
sequence: num2(row, "sequence") ?? 0
|
|
1832
|
-
};
|
|
1833
|
-
}
|
|
1834
|
-
function projectUpcoming(row) {
|
|
1835
|
-
const operation = "meet.projectUpcoming";
|
|
1836
|
-
const kind = text(row, "kind");
|
|
1837
|
-
const myRole = text(row, "my_role");
|
|
1838
|
-
const myRsvp = text(row, "my_rsvp");
|
|
1839
|
-
return {
|
|
1840
|
-
...projectOccurrence(row),
|
|
1841
|
-
organizationId: asOrganizationId(requireText(row, "organization_id", operation)),
|
|
1842
|
-
title: text(row, "title") ?? "Meeting",
|
|
1843
|
-
slug: requireText(row, "slug", operation),
|
|
1844
|
-
kind: kind === "scheduled" || kind === "recurring" ? kind : "instant",
|
|
1845
|
-
hostUserId: asUserId(requireText(row, "host_user_id", operation)),
|
|
1846
|
-
timeZone: text(row, "time_zone") ?? "UTC",
|
|
1847
|
-
meetingCancelled: bool(row, "meeting_cancelled", false),
|
|
1848
|
-
myRsvp: myRsvp === null ? null : rsvpState(myRsvp),
|
|
1849
|
-
myRole: myRole === "host" || myRole === "cohost" || myRole === "invitee" ? myRole : null
|
|
1850
|
-
};
|
|
1851
|
-
}
|
|
1852
|
-
function projectOverride(row) {
|
|
1853
|
-
const operation = "meet.projectOverride";
|
|
1854
|
-
return {
|
|
1855
|
-
id: requireText(row, "id", operation),
|
|
1856
|
-
meetingId: asMeetingId(requireText(row, "meeting_id", operation)),
|
|
1857
|
-
originalStart: when(row, "original_start") ?? requireText(row, "original_start", operation),
|
|
1858
|
-
state: text(row, "state") === "moved" ? "moved" : "cancelled",
|
|
1859
|
-
newStart: when(row, "new_start"),
|
|
1860
|
-
newDurationMinutes: num2(row, "new_duration_minutes"),
|
|
1861
|
-
reason: text(row, "reason"),
|
|
1862
|
-
calendarSequence: num2(row, "calendar_sequence") ?? 0,
|
|
1863
|
-
deletedAt: when(row, "deleted_at")
|
|
1864
|
-
};
|
|
1865
|
-
}
|
|
1866
|
-
function projectRsvpInvitation(body) {
|
|
1867
|
-
const operation = "meet.projectRsvpInvitation";
|
|
1868
|
-
if (typeof body !== "object" || body === null) {
|
|
1869
|
-
throw invalidResponse(operation, "the RSVP door returned no object");
|
|
1870
|
-
}
|
|
1871
|
-
const row = body;
|
|
1872
|
-
if (row["ok"] !== true) {
|
|
1734
|
+
function projectAttendee(row) {
|
|
1735
|
+
const operation = "meet.projectAttendee";
|
|
1736
|
+
const role = text(row, "role");
|
|
1737
|
+
const admission = text(row, "admission_state");
|
|
1738
|
+
const known = ["host", "cohost", "presenter", "participant", "viewer"];
|
|
1739
|
+
const states = [
|
|
1740
|
+
"waiting_host",
|
|
1741
|
+
"knocking",
|
|
1742
|
+
"admitted",
|
|
1743
|
+
"denied",
|
|
1744
|
+
"expired",
|
|
1745
|
+
"withdrawn",
|
|
1746
|
+
"removed",
|
|
1747
|
+
"ended"
|
|
1748
|
+
];
|
|
1749
|
+
const userId = text(row, "participant_user_id");
|
|
1750
|
+
const identity = requireText(row, "identity", operation);
|
|
1873
1751
|
return {
|
|
1874
|
-
|
|
1875
|
-
|
|
1752
|
+
id: requireText(row, "id", operation),
|
|
1753
|
+
meetingId: asMeetingId(requireText(row, "meeting_id", operation)),
|
|
1754
|
+
identity: asParticipantIdentity(identity),
|
|
1755
|
+
// A nameless attendee is a row nobody can read. The identity is a worse
|
|
1756
|
+
// name than a real one and an infinitely better one than a blank.
|
|
1757
|
+
displayName: text(row, "display_name") ?? identity,
|
|
1758
|
+
avatarUrl: text(row, "avatar_url"),
|
|
1759
|
+
userId: userId === null ? null : asUserId(userId),
|
|
1760
|
+
role: known.includes(role) ? role : "participant",
|
|
1761
|
+
admissionState: states.includes(admission) ? admission : "admitted",
|
|
1762
|
+
knockedAt: text(row, "knocked_at"),
|
|
1763
|
+
knockExpiresAt: text(row, "knock_expires_at"),
|
|
1764
|
+
isAgent: bool(row, "is_agent", false),
|
|
1765
|
+
joinedAt: text(row, "joined_at"),
|
|
1766
|
+
leftAt: text(row, "left_at"),
|
|
1767
|
+
sessions: projectStays(row["sessions"])
|
|
1876
1768
|
};
|
|
1877
1769
|
}
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
scheduledFor: when(meeting, "scheduled_for"),
|
|
1886
|
-
durationMinutes: num2(meeting, "duration_minutes"),
|
|
1887
|
-
timeZone: text(meeting, "time_zone") ?? "UTC",
|
|
1888
|
-
agenda: text(meeting, "agenda"),
|
|
1889
|
-
recurrenceRule: text(meeting, "recurrence_rule"),
|
|
1890
|
-
cancelledAt: when(meeting, "cancelled_at"),
|
|
1891
|
-
cancellationReason: text(meeting, "cancellation_reason"),
|
|
1892
|
-
endedAt: when(meeting, "ended_at")
|
|
1893
|
-
},
|
|
1894
|
-
invitee: {
|
|
1895
|
-
displayName: text(invitee, "display_name"),
|
|
1896
|
-
email: text(invitee, "email"),
|
|
1897
|
-
rsvpState: rsvpState(text(invitee, "rsvp_state")),
|
|
1898
|
-
respondedAt: when(invitee, "responded_at")
|
|
1899
|
-
}
|
|
1900
|
-
};
|
|
1901
|
-
}
|
|
1902
|
-
function settingsPatch(settings) {
|
|
1903
|
-
const patch = {};
|
|
1904
|
-
if (settings.lobbyEnabled !== void 0) patch["lobby_enabled"] = settings.lobbyEnabled;
|
|
1905
|
-
if (settings.recordingPolicy !== void 0) patch["recording_policy"] = settings.recordingPolicy;
|
|
1906
|
-
if (settings.aiEnabled !== void 0) patch["ai_enabled"] = settings.aiEnabled;
|
|
1907
|
-
if (settings.joinBeforeHost !== void 0) patch["join_before_host"] = settings.joinBeforeHost;
|
|
1908
|
-
return patch;
|
|
1909
|
-
}
|
|
1910
|
-
function changesPatch(changes) {
|
|
1911
|
-
const patch = settingsPatch(changes);
|
|
1912
|
-
if (changes.title !== void 0) patch["title"] = changes.title;
|
|
1913
|
-
if (changes.agenda !== void 0) patch["agenda"] = changes.agenda;
|
|
1914
|
-
if (changes.timeZone !== void 0) patch["time_zone"] = changes.timeZone;
|
|
1915
|
-
if (changes.scheduledFor !== void 0) patch["scheduled_for"] = changes.scheduledFor;
|
|
1916
|
-
if (changes.scheduledDurationMinutes !== void 0) {
|
|
1917
|
-
patch["scheduled_duration_minutes"] = changes.scheduledDurationMinutes;
|
|
1918
|
-
}
|
|
1919
|
-
if (changes.recurrenceRule !== void 0) patch["recurrence_rule"] = changes.recurrenceRule;
|
|
1920
|
-
if (changes.keepExceptions !== void 0) patch["keep_exceptions"] = changes.keepExceptions;
|
|
1921
|
-
return patch;
|
|
1922
|
-
}
|
|
1923
|
-
function createMeetRepository(options) {
|
|
1924
|
-
const db = options.client.schema(MEET_SCHEMA);
|
|
1925
|
-
async function guardVoid(operation, run) {
|
|
1926
|
-
let response = await run();
|
|
1927
|
-
if (response.error !== null) {
|
|
1928
|
-
const normalized = normalizeMeetError(response.error, operation);
|
|
1929
|
-
if (normalized.code === "session-unavailable" && options.session !== void 0) {
|
|
1930
|
-
await options.session();
|
|
1931
|
-
response = await run();
|
|
1932
|
-
}
|
|
1933
|
-
if (response.error !== null) throw normalizeMeetError(response.error, operation);
|
|
1934
|
-
}
|
|
1935
|
-
}
|
|
1936
|
-
async function guard(operation, run) {
|
|
1937
|
-
let response = await run();
|
|
1938
|
-
if (response.error !== null) {
|
|
1939
|
-
const normalized = normalizeMeetError(response.error, operation);
|
|
1940
|
-
if (normalized.code === "session-unavailable" && options.session !== void 0) {
|
|
1941
|
-
await options.session();
|
|
1942
|
-
response = await run();
|
|
1943
|
-
}
|
|
1944
|
-
if (response.error !== null) throw normalizeMeetError(response.error, operation);
|
|
1945
|
-
}
|
|
1946
|
-
if (response.data === null) {
|
|
1947
|
-
throw invalidResponse(operation, "the database returned no row");
|
|
1948
|
-
}
|
|
1949
|
-
return response.data;
|
|
1950
|
-
}
|
|
1951
|
-
function projectInvite(row) {
|
|
1952
|
-
const operation = "meet.projectInvite";
|
|
1953
|
-
const mode = text(row, "mode");
|
|
1954
|
-
return {
|
|
1955
|
-
id: asCallId(requireText(row, "id", operation)),
|
|
1956
|
-
organizationId: asOrganizationId(requireText(row, "organization_id", operation)),
|
|
1957
|
-
roomName: asRoomName(requireText(row, "room_name", operation)),
|
|
1958
|
-
mode: mode === "video" ? "video" : "audio",
|
|
1959
|
-
callerUserId: asUserId(requireText(row, "caller_user_id", operation)),
|
|
1960
|
-
callerName: text(row, "caller_name") ?? "Someone",
|
|
1961
|
-
callerAvatarUrl: text(row, "caller_avatar_url"),
|
|
1962
|
-
calleeUserIds: stringArray(row, "callee_ids").map(asUserId),
|
|
1963
|
-
conversationId: text(row, "conversation_id"),
|
|
1964
|
-
createdAt: requireText(row, "created_at", operation),
|
|
1965
|
-
expiresAt: text(row, "expires_at") ?? requireText(row, "created_at", operation),
|
|
1966
|
-
state: callState(text(row, "state")),
|
|
1967
|
-
declineMessage: text(row, "decline_message")
|
|
1968
|
-
};
|
|
1969
|
-
}
|
|
1970
|
-
function projectMeeting(row) {
|
|
1971
|
-
const operation = "meet.projectMeeting";
|
|
1972
|
-
const kind = text(row, "kind");
|
|
1973
|
-
const policy = text(row, "recording_policy");
|
|
1974
|
-
const duration = row["scheduled_duration_minutes"];
|
|
1975
|
-
const metadata = row["metadata"];
|
|
1976
|
-
return {
|
|
1977
|
-
id: asMeetingId(requireText(row, "id", operation)),
|
|
1978
|
-
organizationId: asOrganizationId(requireText(row, "organization_id", operation)),
|
|
1979
|
-
roomName: asRoomName(requireText(row, "room_name", operation)),
|
|
1980
|
-
slug: requireText(row, "slug", operation),
|
|
1981
|
-
title: text(row, "title") ?? "Meeting",
|
|
1982
|
-
kind: kind === "scheduled" || kind === "recurring" ? kind : "instant",
|
|
1983
|
-
hostUserId: asUserId(requireText(row, "host_user_id", operation)),
|
|
1984
|
-
scheduledFor: text(row, "scheduled_for"),
|
|
1985
|
-
scheduledDurationMinutes: typeof duration === "number" ? duration : null,
|
|
1986
|
-
startedAt: text(row, "started_at"),
|
|
1987
|
-
endedAt: text(row, "ended_at"),
|
|
1988
|
-
locked: bool(row, "locked", false),
|
|
1989
|
-
lobbyEnabled: bool(row, "lobby_enabled", true),
|
|
1990
|
-
recordingPolicy: policy === "disabled" || policy === "always-on" ? policy : "host-controlled",
|
|
1991
|
-
aiEnabled: bool(row, "ai_enabled", true),
|
|
1992
|
-
metadata: typeof metadata === "object" && metadata !== null && !Array.isArray(metadata) ? metadata : {},
|
|
1993
|
-
timeZone: text(row, "time_zone") ?? "UTC",
|
|
1994
|
-
agenda: text(row, "agenda"),
|
|
1995
|
-
recurrenceRule: text(row, "recurrence_rule"),
|
|
1996
|
-
joinBeforeHost: bool(row, "join_before_host", true),
|
|
1997
|
-
cancelledAt: text(row, "cancelled_at"),
|
|
1998
|
-
cancellationReason: text(row, "cancellation_reason"),
|
|
1999
|
-
deletedAt: text(row, "deleted_at"),
|
|
2000
|
-
calendarSequence: typeof row["calendar_sequence"] === "number" ? row["calendar_sequence"] : 0,
|
|
2001
|
-
version: typeof row["version"] === "number" ? row["version"] : 1
|
|
2002
|
-
};
|
|
2003
|
-
}
|
|
2004
|
-
function projectRecording(row) {
|
|
2005
|
-
const operation = "meet.projectRecording";
|
|
2006
|
-
const state = text(row, "state");
|
|
2007
|
-
const known = [
|
|
2008
|
-
"idle",
|
|
2009
|
-
"starting",
|
|
2010
|
-
"recording",
|
|
2011
|
-
"stopping",
|
|
2012
|
-
"processing",
|
|
2013
|
-
"available",
|
|
2014
|
-
"expired",
|
|
2015
|
-
"failed"
|
|
2016
|
-
];
|
|
2017
|
-
return {
|
|
2018
|
-
id: requireText(row, "id", operation),
|
|
2019
|
-
meetingId: asMeetingId(requireText(row, "meeting_id", operation)),
|
|
2020
|
-
state: known.includes(state) ? state : "failed",
|
|
2021
|
-
startedAt: text(row, "started_at"),
|
|
2022
|
-
endedAt: text(row, "ended_at"),
|
|
2023
|
-
startedBy: text(row, "started_by") === null ? null : asUserId(text(row, "started_by")),
|
|
2024
|
-
// A PLATFORM file id, never a provider URL (R3). A recording that only
|
|
2025
|
-
// exists as a LiveKit URL is orphaned the day the room is garbage
|
|
2026
|
-
// collected, which is the whole reason this column is not `url`.
|
|
2027
|
-
fileId: text(row, "file_id"),
|
|
2028
|
-
failureReason: text(row, "failure_reason"),
|
|
2029
|
-
retention: projectRecordingRetention(row["metadata"])
|
|
2030
|
-
};
|
|
2031
|
-
}
|
|
2032
|
-
function projectRecordingRetention(value) {
|
|
2033
|
-
if (typeof value !== "object" || value === null || Array.isArray(value)) return null;
|
|
2034
|
-
const retention = value["retention"];
|
|
2035
|
-
if (typeof retention !== "object" || retention === null || Array.isArray(retention)) return null;
|
|
2036
|
-
const row = retention;
|
|
2037
|
-
const phase = row["phase"];
|
|
2038
|
-
const operationId = row["operation_id"];
|
|
2039
|
-
const policyId = row["policy_id"];
|
|
2040
|
-
const policyUpdatedAt = row["policy_updated_at"];
|
|
2041
|
-
const dueAt = row["due_at"];
|
|
2042
|
-
const expiredAt = row["expired_at"];
|
|
2043
|
-
if (phase !== "warning" && phase !== "expired" || typeof operationId !== "string" || typeof policyId !== "string" || typeof policyUpdatedAt !== "string" || typeof dueAt !== "string" || expiredAt !== null && typeof expiredAt !== "string") return null;
|
|
2044
|
-
return { operationId, policyId, policyUpdatedAt, phase, dueAt, expiredAt };
|
|
2045
|
-
}
|
|
2046
|
-
function projectAttendee(row) {
|
|
2047
|
-
const operation = "meet.projectAttendee";
|
|
2048
|
-
const role = text(row, "role");
|
|
2049
|
-
const admission = text(row, "admission_state");
|
|
2050
|
-
const known = ["host", "cohost", "presenter", "participant", "viewer"];
|
|
2051
|
-
const states = ["knocking", "admitted", "denied", "removed"];
|
|
2052
|
-
const userId = text(row, "participant_user_id");
|
|
2053
|
-
const identity = requireText(row, "identity", operation);
|
|
2054
|
-
return {
|
|
2055
|
-
id: requireText(row, "id", operation),
|
|
2056
|
-
meetingId: asMeetingId(requireText(row, "meeting_id", operation)),
|
|
2057
|
-
identity: asParticipantIdentity(identity),
|
|
2058
|
-
// A nameless attendee is a row nobody can read. The identity is a worse
|
|
2059
|
-
// name than a real one and an infinitely better one than a blank.
|
|
2060
|
-
displayName: text(row, "display_name") ?? identity,
|
|
2061
|
-
avatarUrl: text(row, "avatar_url"),
|
|
2062
|
-
userId: userId === null ? null : asUserId(userId),
|
|
2063
|
-
role: known.includes(role) ? role : "participant",
|
|
2064
|
-
admissionState: states.includes(admission) ? admission : "admitted",
|
|
2065
|
-
isAgent: bool(row, "is_agent", false),
|
|
2066
|
-
joinedAt: text(row, "joined_at"),
|
|
2067
|
-
leftAt: text(row, "left_at"),
|
|
2068
|
-
sessions: projectStays(row["sessions"])
|
|
2069
|
-
};
|
|
2070
|
-
}
|
|
2071
|
-
function requireOrg(organizationId, operation) {
|
|
2072
|
-
if (typeof organizationId !== "string" || organizationId.length === 0) {
|
|
2073
|
-
throw new MeetError(
|
|
2074
|
-
"misconfigured",
|
|
2075
|
-
`${operation}: no organization_id`,
|
|
2076
|
-
"Every meet write carries an explicit organization_id (R5). The package refuses the write rather than letting an unscoped row reach the database."
|
|
2077
|
-
);
|
|
1770
|
+
function requireOrg(organizationId, operation) {
|
|
1771
|
+
if (typeof organizationId !== "string" || organizationId.length === 0) {
|
|
1772
|
+
throw new MeetError(
|
|
1773
|
+
"misconfigured",
|
|
1774
|
+
`${operation}: no organization_id`,
|
|
1775
|
+
"Every meet write carries an explicit organization_id (R5). The package refuses the write rather than letting an unscoped row reach the database."
|
|
1776
|
+
);
|
|
2078
1777
|
}
|
|
2079
1778
|
}
|
|
2080
1779
|
return {
|
|
@@ -2100,7 +1799,7 @@ function createMeetRepository(options) {
|
|
|
2100
1799
|
p_duration_minutes: input.durationMinutes ?? null,
|
|
2101
1800
|
p_agenda: input.agenda ?? null,
|
|
2102
1801
|
p_recurrence_rule: input.recurrenceRule ?? null,
|
|
2103
|
-
p_settings:
|
|
1802
|
+
p_settings: scheduleSettingsPatch(input.settings ?? {}, input.appPanel, input.observers)
|
|
2104
1803
|
})
|
|
2105
1804
|
);
|
|
2106
1805
|
return projectMeeting(row);
|
|
@@ -2340,16 +2039,26 @@ function createMeetRepository(options) {
|
|
|
2340
2039
|
hasMore: rows.length > limit
|
|
2341
2040
|
};
|
|
2342
2041
|
},
|
|
2343
|
-
async
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2042
|
+
async decideAdmission({ meetingId, identity, decision, byUserId }) {
|
|
2043
|
+
const fn = decision === "admit" ? RPCS.admit : decision === "deny" ? RPCS.deny : RPCS.undeny;
|
|
2044
|
+
const row = await guard(
|
|
2045
|
+
`meet.${decision}`,
|
|
2046
|
+
() => db.rpc(fn, {
|
|
2347
2047
|
p_meeting_id: meetingId,
|
|
2348
2048
|
p_identity: identity,
|
|
2349
|
-
p_admitted: admitted,
|
|
2350
2049
|
p_by_user_id: byUserId
|
|
2351
2050
|
})
|
|
2352
2051
|
);
|
|
2052
|
+
return projectAttendee(row);
|
|
2053
|
+
},
|
|
2054
|
+
async admitAll(meetingId, byUserId) {
|
|
2055
|
+
const response = await db.rpc(RPCS.admitAll, {
|
|
2056
|
+
p_meeting_id: meetingId,
|
|
2057
|
+
p_by_user_id: byUserId
|
|
2058
|
+
});
|
|
2059
|
+
if (response.error !== null) throw normalizeMeetError(response.error, "meet.admitAll");
|
|
2060
|
+
const rows = Array.isArray(response.data) ? response.data : [];
|
|
2061
|
+
return rows.map(projectAttendee);
|
|
2353
2062
|
},
|
|
2354
2063
|
async setLock(meetingId, locked, byUserId) {
|
|
2355
2064
|
const row = await guard(
|
|
@@ -2423,76 +2132,449 @@ function createMeetRepository(options) {
|
|
|
2423
2132
|
);
|
|
2424
2133
|
}
|
|
2425
2134
|
}
|
|
2426
|
-
};
|
|
2135
|
+
};
|
|
2136
|
+
}
|
|
2137
|
+
var LIBRARY_STATES = [
|
|
2138
|
+
"idle",
|
|
2139
|
+
"starting",
|
|
2140
|
+
"recording",
|
|
2141
|
+
"stopping",
|
|
2142
|
+
"processing",
|
|
2143
|
+
"available",
|
|
2144
|
+
"expired",
|
|
2145
|
+
"failed"
|
|
2146
|
+
];
|
|
2147
|
+
function projectLibraryRecording(row) {
|
|
2148
|
+
const operation = "meet.projectLibraryRecording";
|
|
2149
|
+
const state = text(row, "state");
|
|
2150
|
+
const role = text(row, "my_role");
|
|
2151
|
+
return {
|
|
2152
|
+
id: requireText(row, "recording_id", operation),
|
|
2153
|
+
meetingId: asMeetingId(requireText(row, "meeting_id", operation)),
|
|
2154
|
+
meetingTitle: text(row, "meeting_title") ?? "Meeting",
|
|
2155
|
+
meetingSlug: requireText(row, "meeting_slug", operation),
|
|
2156
|
+
role: role === "host" || role === "cohost" || role === "invitee" ? role : "attendee",
|
|
2157
|
+
state: LIBRARY_STATES.includes(state) ? state : "failed",
|
|
2158
|
+
title: text(row, "recording_title"),
|
|
2159
|
+
fileId: text(row, "file_id"),
|
|
2160
|
+
startedAt: text(row, "started_at"),
|
|
2161
|
+
endedAt: text(row, "ended_at"),
|
|
2162
|
+
durationMs: num(row, "duration_ms"),
|
|
2163
|
+
sizeBytes: num(row, "size_bytes"),
|
|
2164
|
+
failureReason: text(row, "failure_reason"),
|
|
2165
|
+
archivedAt: text(row, "deleted_at"),
|
|
2166
|
+
organizationId: asOrganizationId(requireText(row, "organization_id", operation))
|
|
2167
|
+
};
|
|
2168
|
+
}
|
|
2169
|
+
var SEARCH_SOURCES = [
|
|
2170
|
+
"title",
|
|
2171
|
+
"summary",
|
|
2172
|
+
"decision",
|
|
2173
|
+
"action_item",
|
|
2174
|
+
"note",
|
|
2175
|
+
"transcript"
|
|
2176
|
+
];
|
|
2177
|
+
function projectSearchHit(row) {
|
|
2178
|
+
const operation = "meet.projectSearchHit";
|
|
2179
|
+
const source = text(row, "source");
|
|
2180
|
+
return {
|
|
2181
|
+
meetingId: asMeetingId(requireText(row, "meeting_id", operation)),
|
|
2182
|
+
meetingTitle: text(row, "meeting_title") ?? "Meeting",
|
|
2183
|
+
meetingSlug: requireText(row, "meeting_slug", operation),
|
|
2184
|
+
meetingAt: text(row, "meeting_at"),
|
|
2185
|
+
source: SEARCH_SOURCES.includes(source) ? source : "note",
|
|
2186
|
+
refId: requireText(row, "ref_id", operation),
|
|
2187
|
+
speaker: text(row, "speaker"),
|
|
2188
|
+
snippet: text(row, "snippet") ?? "",
|
|
2189
|
+
at: text(row, "said_at"),
|
|
2190
|
+
rank: num(row, "rank") ?? 0
|
|
2191
|
+
};
|
|
2192
|
+
}
|
|
2193
|
+
function snippetRuns(snippet) {
|
|
2194
|
+
const runs = [];
|
|
2195
|
+
const pattern = /⟦([^⟧]*)⟧/g;
|
|
2196
|
+
let last = 0;
|
|
2197
|
+
for (const match of snippet.matchAll(pattern)) {
|
|
2198
|
+
const start = match.index ?? 0;
|
|
2199
|
+
if (start > last) runs.push({ text: snippet.slice(last, start), hit: false });
|
|
2200
|
+
runs.push({ text: match[1] ?? "", hit: true });
|
|
2201
|
+
last = start + match[0].length;
|
|
2202
|
+
}
|
|
2203
|
+
if (last < snippet.length) runs.push({ text: snippet.slice(last), hit: false });
|
|
2204
|
+
return runs;
|
|
2205
|
+
}
|
|
2206
|
+
|
|
2207
|
+
// src/core/app-panel.ts
|
|
2208
|
+
function appPanelOf(meeting) {
|
|
2209
|
+
const raw = meeting?.metadata?.["app_panel"];
|
|
2210
|
+
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) return null;
|
|
2211
|
+
const key = raw["key"];
|
|
2212
|
+
const recordId = raw["record_id"];
|
|
2213
|
+
if (typeof key !== "string" || key.length === 0) return null;
|
|
2214
|
+
if (typeof recordId !== "string" || recordId.length === 0) return null;
|
|
2215
|
+
return { key, recordId };
|
|
2216
|
+
}
|
|
2217
|
+
function scheduleSettingsPatch(settings, appPanel, observers) {
|
|
2218
|
+
const patch = settingsPatch(settings);
|
|
2219
|
+
if (appPanel !== void 0) patch["app_panel"] = { key: appPanel.key, record_id: appPanel.recordId };
|
|
2220
|
+
if (observers !== void 0 && observers.length > 0) patch["observers"] = [...observers];
|
|
2221
|
+
return patch;
|
|
2222
|
+
}
|
|
2223
|
+
function focusFromRow(value) {
|
|
2224
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return null;
|
|
2225
|
+
const row = value;
|
|
2226
|
+
if (typeof row["panel"] !== "string") return null;
|
|
2227
|
+
return {
|
|
2228
|
+
panel: row["panel"],
|
|
2229
|
+
section: typeof row["section"] === "string" ? row["section"] : "",
|
|
2230
|
+
byName: typeof row["by_name"] === "string" ? row["by_name"] : "",
|
|
2231
|
+
at: typeof row["at"] === "string" ? row["at"] : ""
|
|
2232
|
+
};
|
|
2233
|
+
}
|
|
2234
|
+
|
|
2235
|
+
// src/core/collab.ts
|
|
2236
|
+
var EMPTY_COLLAB = {
|
|
2237
|
+
loaded: false,
|
|
2238
|
+
error: null,
|
|
2239
|
+
settings: null,
|
|
2240
|
+
polls: [],
|
|
2241
|
+
questions: [],
|
|
2242
|
+
strokes: [],
|
|
2243
|
+
breakout: null,
|
|
2244
|
+
broadcast: null,
|
|
2245
|
+
moveRequest: null,
|
|
2246
|
+
panelRequest: null,
|
|
2247
|
+
captionLanguage: null,
|
|
2248
|
+
captionTranslations: {},
|
|
2249
|
+
captionTranslationError: null,
|
|
2250
|
+
focus: null
|
|
2251
|
+
};
|
|
2252
|
+
function obj(value) {
|
|
2253
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
|
|
2254
|
+
}
|
|
2255
|
+
function str(value) {
|
|
2256
|
+
return typeof value === "string" ? value : null;
|
|
2257
|
+
}
|
|
2258
|
+
function num2(value) {
|
|
2259
|
+
return typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
2260
|
+
}
|
|
2261
|
+
function strings(value) {
|
|
2262
|
+
return Array.isArray(value) ? value.filter((v) => typeof v === "string") : [];
|
|
2263
|
+
}
|
|
2264
|
+
function pollFromRow(row) {
|
|
2265
|
+
const id = str(row["id"]);
|
|
2266
|
+
if (id === null) return null;
|
|
2267
|
+
const options = [];
|
|
2268
|
+
for (const raw of Array.isArray(row["options"]) ? row["options"] : []) {
|
|
2269
|
+
const option = obj(raw);
|
|
2270
|
+
const optionId = option === null ? null : str(option["id"]);
|
|
2271
|
+
if (option === null || optionId === null) continue;
|
|
2272
|
+
const votes = num2(option["votes"]);
|
|
2273
|
+
options.push({
|
|
2274
|
+
id: optionId,
|
|
2275
|
+
text: str(option["text"]) ?? "",
|
|
2276
|
+
...votes !== null ? { votes } : {},
|
|
2277
|
+
...Array.isArray(option["voters"]) ? { voters: strings(option["voters"]) } : {}
|
|
2278
|
+
});
|
|
2279
|
+
}
|
|
2280
|
+
const state = str(row["state"]);
|
|
2281
|
+
return {
|
|
2282
|
+
id,
|
|
2283
|
+
question: str(row["question"]) ?? "",
|
|
2284
|
+
options,
|
|
2285
|
+
multiple: row["multiple"] === true,
|
|
2286
|
+
anonymous: row["anonymous"] === true,
|
|
2287
|
+
state: state === "live" || state === "closed" ? state : "draft",
|
|
2288
|
+
resultsShared: row["results_shared"] === true,
|
|
2289
|
+
voterCount: num2(row["voter_count"]),
|
|
2290
|
+
myVote: strings(row["my_vote"]),
|
|
2291
|
+
createdByName: str(row["created_by_name"]) ?? "",
|
|
2292
|
+
createdAt: str(row["created_at"])
|
|
2293
|
+
};
|
|
2294
|
+
}
|
|
2295
|
+
function questionFromRow(row) {
|
|
2296
|
+
const id = str(row["id"]);
|
|
2297
|
+
if (id === null) return null;
|
|
2298
|
+
const state = str(row["state"]);
|
|
2299
|
+
return {
|
|
2300
|
+
id,
|
|
2301
|
+
text: str(row["text"]) ?? "",
|
|
2302
|
+
askerName: str(row["asker_name"]) ?? "",
|
|
2303
|
+
anonymous: row["anonymous"] === true,
|
|
2304
|
+
state: state === "answered" || state === "dismissed" ? state : "open",
|
|
2305
|
+
answer: str(row["answer"]),
|
|
2306
|
+
answeredByName: str(row["answered_by_name"]),
|
|
2307
|
+
upvotes: num2(row["upvotes"]) ?? 0,
|
|
2308
|
+
upvotedByMe: row["upvoted_by_me"] === true,
|
|
2309
|
+
mine: row["mine"] === true,
|
|
2310
|
+
createdAt: str(row["created_at"])
|
|
2311
|
+
};
|
|
2312
|
+
}
|
|
2313
|
+
function strokeFromRow(row) {
|
|
2314
|
+
const id = str(row["id"]);
|
|
2315
|
+
if (id === null || !Array.isArray(row["points"])) return null;
|
|
2316
|
+
const points = [];
|
|
2317
|
+
for (const raw of row["points"]) {
|
|
2318
|
+
if (Array.isArray(raw) && typeof raw[0] === "number" && typeof raw[1] === "number") {
|
|
2319
|
+
points.push([raw[0], raw[1]]);
|
|
2320
|
+
}
|
|
2321
|
+
}
|
|
2322
|
+
return {
|
|
2323
|
+
id,
|
|
2324
|
+
points,
|
|
2325
|
+
color: str(row["color"]) ?? "#111827",
|
|
2326
|
+
width: num2(row["width"]) ?? 3,
|
|
2327
|
+
tool: row["tool"] === "highlighter" ? "highlighter" : "pen",
|
|
2328
|
+
by: str(row["by"]) ?? "",
|
|
2329
|
+
byName: str(row["by_name"]) ?? ""
|
|
2330
|
+
};
|
|
2331
|
+
}
|
|
2332
|
+
function broadcastFrom(value) {
|
|
2333
|
+
const row = obj(value);
|
|
2334
|
+
const text3 = row === null ? null : str(row["text"]);
|
|
2335
|
+
if (row === null || text3 === null) return null;
|
|
2336
|
+
return { text: text3, by: str(row["by"]) ?? "", at: str(row["at"]) ?? "" };
|
|
2337
|
+
}
|
|
2338
|
+
function planFromRow(row) {
|
|
2339
|
+
const id = str(row["id"]);
|
|
2340
|
+
if (id === null) return null;
|
|
2341
|
+
const rooms = [];
|
|
2342
|
+
for (const raw of Array.isArray(row["rooms"]) ? row["rooms"] : []) {
|
|
2343
|
+
const room = obj(raw);
|
|
2344
|
+
const roomId = room === null ? null : str(room["id"]);
|
|
2345
|
+
if (room === null || roomId === null) continue;
|
|
2346
|
+
rooms.push({ id: roomId, name: str(room["name"]) ?? roomId, identities: strings(room["identities"]) });
|
|
2347
|
+
}
|
|
2348
|
+
return {
|
|
2349
|
+
id,
|
|
2350
|
+
state: row["state"] === "open" ? "open" : "closed",
|
|
2351
|
+
rooms,
|
|
2352
|
+
allowChoose: row["allow_choose"] === true,
|
|
2353
|
+
minutes: num2(row["minutes"]) ?? 0,
|
|
2354
|
+
openedAt: str(row["opened_at"]),
|
|
2355
|
+
endsAt: str(row["ends_at"]),
|
|
2356
|
+
closedAt: str(row["closed_at"]),
|
|
2357
|
+
broadcasts: (Array.isArray(row["broadcasts"]) ? row["broadcasts"] : []).map(broadcastFrom).filter((b) => b !== null)
|
|
2358
|
+
};
|
|
2359
|
+
}
|
|
2360
|
+
function settingsFromRow(row) {
|
|
2361
|
+
if (row === null) return null;
|
|
2362
|
+
return {
|
|
2363
|
+
pollsEnabled: row["polls_enabled"] === true,
|
|
2364
|
+
pollMaxOptions: num2(row["poll_max_options"]) ?? 10,
|
|
2365
|
+
qaEnabled: row["qa_enabled"] === true,
|
|
2366
|
+
qaAnonymousAllowed: row["qa_anonymous_allowed"] === true,
|
|
2367
|
+
whiteboardEnabled: row["whiteboard_enabled"] === true,
|
|
2368
|
+
breakoutsEnabled: row["breakouts_enabled"] === true,
|
|
2369
|
+
breakoutMaxRooms: num2(row["breakout_max_rooms"]) ?? 20,
|
|
2370
|
+
breakoutDefaultMinutes: num2(row["breakout_default_minutes"]) ?? 15,
|
|
2371
|
+
captionTranslationEnabled: row["caption_translation_enabled"] === true,
|
|
2372
|
+
captionTranslationLanguage: str(row["caption_translation_language"]) ?? ""
|
|
2373
|
+
};
|
|
2374
|
+
}
|
|
2375
|
+
function collabFromState(body, previous) {
|
|
2376
|
+
const list = (key, parse) => (Array.isArray(body[key]) ? body[key] : []).map((raw) => {
|
|
2377
|
+
const row = obj(raw);
|
|
2378
|
+
return row === null ? null : parse(row);
|
|
2379
|
+
}).filter((item) => item !== null);
|
|
2380
|
+
const breakoutRow = obj(body["breakout"]);
|
|
2381
|
+
return {
|
|
2382
|
+
...previous,
|
|
2383
|
+
loaded: true,
|
|
2384
|
+
error: null,
|
|
2385
|
+
settings: settingsFromRow(obj(body["settings"])),
|
|
2386
|
+
polls: list("polls", pollFromRow),
|
|
2387
|
+
questions: list("questions", questionFromRow),
|
|
2388
|
+
strokes: list("strokes", strokeFromRow),
|
|
2389
|
+
breakout: breakoutRow === null ? null : planFromRow(breakoutRow),
|
|
2390
|
+
focus: focusFromRow(body["focus"])
|
|
2391
|
+
};
|
|
2392
|
+
}
|
|
2393
|
+
function upsert(items, next) {
|
|
2394
|
+
const index = items.findIndex((item) => item.id === next.id);
|
|
2395
|
+
if (index === -1) return [...items, next];
|
|
2396
|
+
const copy = items.slice();
|
|
2397
|
+
copy[index] = next;
|
|
2398
|
+
return copy;
|
|
2399
|
+
}
|
|
2400
|
+
function withItem(collab, row, fromRoom = false) {
|
|
2401
|
+
const kind = str(row["kind"]);
|
|
2402
|
+
if (kind === "poll") {
|
|
2403
|
+
const poll = pollFromRow(row);
|
|
2404
|
+
if (poll === null) return collab;
|
|
2405
|
+
const known = collab.polls.find((p) => p.id === poll.id);
|
|
2406
|
+
let merged = fromRoom && known !== void 0 ? { ...poll, myVote: known.myVote } : poll;
|
|
2407
|
+
if (fromRoom && known !== void 0 && !poll.options.some((o) => o.votes !== void 0)) {
|
|
2408
|
+
const counted = new Map(known.options.map((o) => [o.id, o]));
|
|
2409
|
+
merged = {
|
|
2410
|
+
...merged,
|
|
2411
|
+
voterCount: known.voterCount,
|
|
2412
|
+
options: merged.options.map((o) => {
|
|
2413
|
+
const had = counted.get(o.id);
|
|
2414
|
+
return had === void 0 ? o : { ...o, votes: had.votes, voters: had.voters };
|
|
2415
|
+
})
|
|
2416
|
+
};
|
|
2417
|
+
}
|
|
2418
|
+
return { ...collab, polls: upsert(collab.polls, merged) };
|
|
2419
|
+
}
|
|
2420
|
+
if (kind === "question") {
|
|
2421
|
+
const question = questionFromRow(row);
|
|
2422
|
+
if (question === null) return collab;
|
|
2423
|
+
const known = collab.questions.find((q) => q.id === question.id);
|
|
2424
|
+
const merged = fromRoom && known !== void 0 ? { ...question, mine: known.mine, upvotedByMe: known.upvotedByMe } : question;
|
|
2425
|
+
return { ...collab, questions: upsert(collab.questions, merged) };
|
|
2426
|
+
}
|
|
2427
|
+
if (kind === "stroke") {
|
|
2428
|
+
const stroke = strokeFromRow(row);
|
|
2429
|
+
if (stroke === null) return collab;
|
|
2430
|
+
return { ...collab, strokes: upsert(collab.strokes, stroke) };
|
|
2431
|
+
}
|
|
2432
|
+
if (kind === "breakout") {
|
|
2433
|
+
const plan = planFromRow(row);
|
|
2434
|
+
if (plan === null) return collab;
|
|
2435
|
+
return { ...collab, breakout: plan };
|
|
2436
|
+
}
|
|
2437
|
+
return collab;
|
|
2438
|
+
}
|
|
2439
|
+
function without(collab, ids) {
|
|
2440
|
+
const gone = new Set(ids);
|
|
2441
|
+
return {
|
|
2442
|
+
...collab,
|
|
2443
|
+
polls: collab.polls.filter((p) => !gone.has(p.id)),
|
|
2444
|
+
questions: collab.questions.filter((q) => !gone.has(q.id)),
|
|
2445
|
+
strokes: collab.strokes.filter((s) => !gone.has(s.id))
|
|
2446
|
+
};
|
|
2447
|
+
}
|
|
2448
|
+
function applyCollabPacket(collab, payload, context) {
|
|
2449
|
+
const item = obj(payload["item"]);
|
|
2450
|
+
if (item !== null) {
|
|
2451
|
+
const kind = str(item["kind"]);
|
|
2452
|
+
let next = withItem(collab, item, true);
|
|
2453
|
+
if (kind === "poll" && str(item["state"]) === "live") {
|
|
2454
|
+
const before = collab.polls.find((p) => p.id === str(item["id"]));
|
|
2455
|
+
if (before === void 0 || before.state !== "live") {
|
|
2456
|
+
next = { ...next, panelRequest: { panel: "polls", at: context.now } };
|
|
2457
|
+
}
|
|
2458
|
+
}
|
|
2459
|
+
if (kind === "breakout") {
|
|
2460
|
+
const plan = planFromRow(item);
|
|
2461
|
+
if (plan !== null && plan.state === "closed") {
|
|
2462
|
+
next = { ...next, moveRequest: { roomId: null, at: context.now } };
|
|
2463
|
+
}
|
|
2464
|
+
}
|
|
2465
|
+
const move = obj(payload["move"]);
|
|
2466
|
+
if (move !== null && str(move["identity"]) === context.localIdentity) {
|
|
2467
|
+
next = { ...next, moveRequest: { roomId: str(move["room_id"]), at: context.now } };
|
|
2468
|
+
}
|
|
2469
|
+
return { next };
|
|
2470
|
+
}
|
|
2471
|
+
if (payload["focus"] !== void 0) {
|
|
2472
|
+
const focus = focusFromRow(payload["focus"]);
|
|
2473
|
+
return focus === null ? { next: null, dropped: "a focus packet without a panel" } : { next: { ...collab, focus } };
|
|
2474
|
+
}
|
|
2475
|
+
if (Array.isArray(payload["removed"])) {
|
|
2476
|
+
return { next: without(collab, strings(payload["removed"])) };
|
|
2477
|
+
}
|
|
2478
|
+
if (Array.isArray(payload["hidden"])) {
|
|
2479
|
+
return context.isHost ? { next: null, dropped: "hosts keep dismissed questions" } : { next: without(collab, strings(payload["hidden"])) };
|
|
2480
|
+
}
|
|
2481
|
+
if (payload["cleared"] === "whiteboard") {
|
|
2482
|
+
return { next: { ...collab, strokes: [] } };
|
|
2483
|
+
}
|
|
2484
|
+
const broadcast = broadcastFrom(payload["broadcast"]);
|
|
2485
|
+
if (broadcast !== null) {
|
|
2486
|
+
return { next: { ...collab, broadcast } };
|
|
2487
|
+
}
|
|
2488
|
+
return { next: null, dropped: "a collab packet with nothing this version understands" };
|
|
2427
2489
|
}
|
|
2428
|
-
|
|
2429
|
-
"
|
|
2430
|
-
|
|
2431
|
-
"recording",
|
|
2432
|
-
"stopping",
|
|
2433
|
-
"processing",
|
|
2434
|
-
"available",
|
|
2435
|
-
"expired",
|
|
2436
|
-
"failed"
|
|
2437
|
-
];
|
|
2438
|
-
function projectLibraryRecording(row) {
|
|
2439
|
-
const operation = "meet.projectLibraryRecording";
|
|
2440
|
-
const state = text(row, "state");
|
|
2441
|
-
const role = text(row, "my_role");
|
|
2442
|
-
return {
|
|
2443
|
-
id: requireText(row, "recording_id", operation),
|
|
2444
|
-
meetingId: asMeetingId(requireText(row, "meeting_id", operation)),
|
|
2445
|
-
meetingTitle: text(row, "meeting_title") ?? "Meeting",
|
|
2446
|
-
meetingSlug: requireText(row, "meeting_slug", operation),
|
|
2447
|
-
role: role === "host" || role === "cohost" || role === "invitee" ? role : "attendee",
|
|
2448
|
-
state: LIBRARY_STATES.includes(state) ? state : "failed",
|
|
2449
|
-
title: text(row, "recording_title"),
|
|
2450
|
-
fileId: text(row, "file_id"),
|
|
2451
|
-
startedAt: text(row, "started_at"),
|
|
2452
|
-
endedAt: text(row, "ended_at"),
|
|
2453
|
-
durationMs: num2(row, "duration_ms"),
|
|
2454
|
-
sizeBytes: num2(row, "size_bytes"),
|
|
2455
|
-
failureReason: text(row, "failure_reason"),
|
|
2456
|
-
archivedAt: text(row, "deleted_at"),
|
|
2457
|
-
organizationId: asOrganizationId(requireText(row, "organization_id", operation))
|
|
2458
|
-
};
|
|
2490
|
+
function assignedRoom(plan, identity) {
|
|
2491
|
+
if (plan === null || plan.state !== "open" || identity === null) return null;
|
|
2492
|
+
return plan.rooms.find((room) => room.identities.includes(identity)) ?? null;
|
|
2459
2493
|
}
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
];
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
return {
|
|
2472
|
-
meetingId: asMeetingId(requireText(row, "meeting_id", operation)),
|
|
2473
|
-
meetingTitle: text(row, "meeting_title") ?? "Meeting",
|
|
2474
|
-
meetingSlug: requireText(row, "meeting_slug", operation),
|
|
2475
|
-
meetingAt: text(row, "meeting_at"),
|
|
2476
|
-
source: SEARCH_SOURCES.includes(source) ? source : "note",
|
|
2477
|
-
refId: requireText(row, "ref_id", operation),
|
|
2478
|
-
speaker: text(row, "speaker"),
|
|
2479
|
-
snippet: text(row, "snippet") ?? "",
|
|
2480
|
-
at: text(row, "said_at"),
|
|
2481
|
-
rank: num2(row, "rank") ?? 0
|
|
2482
|
-
};
|
|
2494
|
+
function autoAssign(identities, rooms, exclude = []) {
|
|
2495
|
+
const count2 = Math.max(1, Math.floor(rooms));
|
|
2496
|
+
const out = Array.from({ length: count2 }, () => []);
|
|
2497
|
+
const skip = new Set(exclude);
|
|
2498
|
+
let index = 0;
|
|
2499
|
+
for (const identity of identities) {
|
|
2500
|
+
if (skip.has(identity)) continue;
|
|
2501
|
+
out[index % count2].push(identity);
|
|
2502
|
+
index += 1;
|
|
2503
|
+
}
|
|
2504
|
+
return out;
|
|
2483
2505
|
}
|
|
2484
|
-
function
|
|
2485
|
-
|
|
2486
|
-
const
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
|
|
2506
|
+
function breakoutRemainingMs(plan, now) {
|
|
2507
|
+
if (plan === null || plan.state !== "open" || plan.endsAt === null) return null;
|
|
2508
|
+
const end = Date.parse(plan.endsAt);
|
|
2509
|
+
return Number.isNaN(end) ? null : Math.max(0, end - now);
|
|
2510
|
+
}
|
|
2511
|
+
function defaultCaptionLanguage(settings, viewerLanguage) {
|
|
2512
|
+
const configured = settings?.captionTranslationLanguage.trim() ?? "";
|
|
2513
|
+
if (configured.length > 0) return configured;
|
|
2514
|
+
const own = (viewerLanguage ?? "").trim();
|
|
2515
|
+
return own.length > 0 ? own : "en";
|
|
2516
|
+
}
|
|
2517
|
+
var CAPTION_LANGUAGES = [
|
|
2518
|
+
{ tag: "en", name: "English" },
|
|
2519
|
+
{ tag: "es", name: "Spanish" },
|
|
2520
|
+
{ tag: "fr", name: "French" },
|
|
2521
|
+
{ tag: "de", name: "German" },
|
|
2522
|
+
{ tag: "it", name: "Italian" },
|
|
2523
|
+
{ tag: "pt", name: "Portuguese" },
|
|
2524
|
+
{ tag: "nl", name: "Dutch" },
|
|
2525
|
+
{ tag: "pl", name: "Polish" },
|
|
2526
|
+
{ tag: "ru", name: "Russian" },
|
|
2527
|
+
{ tag: "uk", name: "Ukrainian" },
|
|
2528
|
+
{ tag: "tr", name: "Turkish" },
|
|
2529
|
+
{ tag: "ar", name: "Arabic" },
|
|
2530
|
+
{ tag: "he", name: "Hebrew" },
|
|
2531
|
+
{ tag: "fa", name: "Persian" },
|
|
2532
|
+
{ tag: "hi", name: "Hindi" },
|
|
2533
|
+
{ tag: "bn", name: "Bengali" },
|
|
2534
|
+
{ tag: "zh", name: "Chinese" },
|
|
2535
|
+
{ tag: "ja", name: "Japanese" },
|
|
2536
|
+
{ tag: "ko", name: "Korean" },
|
|
2537
|
+
{ tag: "vi", name: "Vietnamese" },
|
|
2538
|
+
{ tag: "th", name: "Thai" },
|
|
2539
|
+
{ tag: "id", name: "Indonesian" },
|
|
2540
|
+
{ tag: "sv", name: "Swedish" }
|
|
2541
|
+
];
|
|
2542
|
+
async function loadMeetingActivities(api, meetingId, meetingPass = null) {
|
|
2543
|
+
let response;
|
|
2544
|
+
try {
|
|
2545
|
+
response = await api.request({
|
|
2546
|
+
path: `${MEET_ROUTES.collab}?meeting_id=${encodeURIComponent(meetingId)}`,
|
|
2547
|
+
method: "GET",
|
|
2548
|
+
operation: "meet.loadMeetingActivities",
|
|
2549
|
+
requireAuth: meetingPass === null,
|
|
2550
|
+
...meetingPass !== null ? { headers: { [MEETING_PASS_HEADER]: meetingPass } } : {}
|
|
2551
|
+
});
|
|
2552
|
+
} catch (error) {
|
|
2553
|
+
return {
|
|
2554
|
+
collab: null,
|
|
2555
|
+
refusal: {
|
|
2556
|
+
message: `Polls, questions and the whiteboard could not be read (${error.message}).`,
|
|
2557
|
+
remedy: error.remedy ?? "Reload the page."
|
|
2558
|
+
}
|
|
2559
|
+
};
|
|
2493
2560
|
}
|
|
2494
|
-
if (
|
|
2495
|
-
|
|
2561
|
+
if (!response.ok) {
|
|
2562
|
+
let body = null;
|
|
2563
|
+
try {
|
|
2564
|
+
body = await response.json();
|
|
2565
|
+
} catch {
|
|
2566
|
+
body = null;
|
|
2567
|
+
}
|
|
2568
|
+
const said = readServerRefusal(body);
|
|
2569
|
+
return {
|
|
2570
|
+
collab: null,
|
|
2571
|
+
refusal: {
|
|
2572
|
+
message: said?.reason ?? `Polls and questions could not be read (the server answered ${response.status}).`,
|
|
2573
|
+
remedy: said?.remedy || "Reload the page."
|
|
2574
|
+
}
|
|
2575
|
+
};
|
|
2576
|
+
}
|
|
2577
|
+
return { collab: collabFromState(await response.json(), EMPTY_COLLAB), refusal: null };
|
|
2496
2578
|
}
|
|
2497
2579
|
|
|
2498
2580
|
// src/core/names.ts
|
|
@@ -2697,6 +2779,7 @@ var NO_GRANTS = {
|
|
|
2697
2779
|
};
|
|
2698
2780
|
var EMPTY = {
|
|
2699
2781
|
phase: "idle",
|
|
2782
|
+
waiting: null,
|
|
2700
2783
|
meeting: null,
|
|
2701
2784
|
localIdentity: null,
|
|
2702
2785
|
grants: NO_GRANTS,
|
|
@@ -3564,7 +3647,10 @@ function orderParticipants(participants, activeSpeakers) {
|
|
|
3564
3647
|
}
|
|
3565
3648
|
function computeStage(args) {
|
|
3566
3649
|
const pageSize = Math.max(1, args.pageSize ?? DEFAULT_PAGE_SIZE);
|
|
3567
|
-
const ordered = orderParticipants(
|
|
3650
|
+
const ordered = orderParticipants(
|
|
3651
|
+
args.participants.filter((participant) => !isObserverRole(participant.role)),
|
|
3652
|
+
args.activeSpeakers
|
|
3653
|
+
);
|
|
3568
3654
|
const sharer = ordered.find((participant) => participant.screenSharing) ?? null;
|
|
3569
3655
|
const pinnedParticipant = args.pinned === null ? null : ordered.find((participant) => participant.identity === args.pinned) ?? null;
|
|
3570
3656
|
const spotlit = args.spotlight == null ? null : ordered.find((participant) => participant.identity === args.spotlight) ?? null;
|
|
@@ -3974,19 +4060,25 @@ function roleOf(participants, identity) {
|
|
|
3974
4060
|
return participants.find((p) => p.identity === identity)?.role ?? "participant";
|
|
3975
4061
|
}
|
|
3976
4062
|
var END_MEETING_NOT_SERVED = "Ending a meeting is not available on this server yet";
|
|
3977
|
-
function lobbyEntryFrom(attendee) {
|
|
3978
|
-
if (attendee.admissionState
|
|
4063
|
+
function lobbyEntryFrom(attendee, now = Date.now()) {
|
|
4064
|
+
if (!WAITING_ADMISSION_STATES.includes(attendee.admissionState)) return null;
|
|
4065
|
+
const expires = attendee.knockExpiresAt ? Date.parse(attendee.knockExpiresAt) : Number.NaN;
|
|
4066
|
+
if (!Number.isNaN(expires) && expires <= now) return null;
|
|
3979
4067
|
return {
|
|
3980
4068
|
identity: attendee.identity,
|
|
3981
4069
|
displayName: attendee.displayName,
|
|
3982
4070
|
avatarUrl: attendee.avatarUrl,
|
|
3983
4071
|
userId: attendee.userId,
|
|
3984
4072
|
state: "knocking",
|
|
3985
|
-
knockedAt: attendee.joinedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
4073
|
+
knockedAt: attendee.knockedAt ?? attendee.joinedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
4074
|
+
knockExpiresAt: attendee.knockExpiresAt ?? null,
|
|
3986
4075
|
decidedAt: null,
|
|
3987
4076
|
decidedBy: null
|
|
3988
4077
|
};
|
|
3989
4078
|
}
|
|
4079
|
+
function decidedState(state) {
|
|
4080
|
+
return WAITING_ADMISSION_STATES.includes(state) ? "expired" : state;
|
|
4081
|
+
}
|
|
3990
4082
|
function createMeetingSession(options) {
|
|
3991
4083
|
const { manager, repository, store, identity } = options;
|
|
3992
4084
|
let channel = null;
|
|
@@ -4010,11 +4102,6 @@ function createMeetingSession(options) {
|
|
|
4010
4102
|
}
|
|
4011
4103
|
return identity.userId;
|
|
4012
4104
|
}
|
|
4013
|
-
function knockIdentity() {
|
|
4014
|
-
return asParticipantIdentity(
|
|
4015
|
-
identity.userId !== null ? `user:${identity.userId}` : `guest:${identity.deviceId}`
|
|
4016
|
-
);
|
|
4017
|
-
}
|
|
4018
4105
|
function requireMeeting(operation) {
|
|
4019
4106
|
const meeting = store.snapshot().meeting;
|
|
4020
4107
|
if (meeting === null) {
|
|
@@ -4026,6 +4113,191 @@ function createMeetingSession(options) {
|
|
|
4026
4113
|
}
|
|
4027
4114
|
return meeting;
|
|
4028
4115
|
}
|
|
4116
|
+
let polling = false;
|
|
4117
|
+
let pollTimer = null;
|
|
4118
|
+
let reading = null;
|
|
4119
|
+
let admittedOnce = false;
|
|
4120
|
+
let unlisten = null;
|
|
4121
|
+
function report(level, message, remedy) {
|
|
4122
|
+
options.onDiagnostic?.(remedy === void 0 ? { level, message } : { level, message, remedy });
|
|
4123
|
+
}
|
|
4124
|
+
function pollMs() {
|
|
4125
|
+
const seconds = store.snapshot().waiting?.pollSeconds ?? 3;
|
|
4126
|
+
return Math.max(1, Math.min(60, seconds)) * 1e3;
|
|
4127
|
+
}
|
|
4128
|
+
function schedule(ms2) {
|
|
4129
|
+
if (pollTimer !== null) clearTimeout(pollTimer);
|
|
4130
|
+
if (!polling) return;
|
|
4131
|
+
pollTimer = setTimeout(() => {
|
|
4132
|
+
pollTimer = null;
|
|
4133
|
+
void readState2().finally(() => schedule(pollMs()));
|
|
4134
|
+
}, ms2);
|
|
4135
|
+
}
|
|
4136
|
+
function listen() {
|
|
4137
|
+
if (unlisten !== null || typeof window === "undefined") return;
|
|
4138
|
+
const nudge = () => {
|
|
4139
|
+
if (typeof document !== "undefined" && document.visibilityState === "hidden") return;
|
|
4140
|
+
void readState2();
|
|
4141
|
+
};
|
|
4142
|
+
const goodbye = () => {
|
|
4143
|
+
const meeting = store.snapshot().meeting;
|
|
4144
|
+
const pass = options.resolvePass?.() ?? null;
|
|
4145
|
+
const waiting = store.snapshot().waiting;
|
|
4146
|
+
if (meeting === null || pass === null || waiting === null) return;
|
|
4147
|
+
if (!WAITING_ADMISSION_STATES.includes(waiting.admission)) return;
|
|
4148
|
+
try {
|
|
4149
|
+
navigator.sendBeacon?.(
|
|
4150
|
+
options.api.url(MEET_ROUTES.leave),
|
|
4151
|
+
new Blob([JSON.stringify({ meeting_id: meeting.id, intent: "withdraw", meeting_pass: pass })], {
|
|
4152
|
+
type: "application/json"
|
|
4153
|
+
})
|
|
4154
|
+
);
|
|
4155
|
+
} catch {
|
|
4156
|
+
}
|
|
4157
|
+
};
|
|
4158
|
+
window.addEventListener("online", nudge);
|
|
4159
|
+
window.addEventListener("pagehide", goodbye);
|
|
4160
|
+
document.addEventListener("visibilitychange", nudge);
|
|
4161
|
+
unlisten = () => {
|
|
4162
|
+
window.removeEventListener("online", nudge);
|
|
4163
|
+
window.removeEventListener("pagehide", goodbye);
|
|
4164
|
+
document.removeEventListener("visibilitychange", nudge);
|
|
4165
|
+
};
|
|
4166
|
+
}
|
|
4167
|
+
function stopWaiting() {
|
|
4168
|
+
polling = false;
|
|
4169
|
+
if (pollTimer !== null) clearTimeout(pollTimer);
|
|
4170
|
+
pollTimer = null;
|
|
4171
|
+
unlisten?.();
|
|
4172
|
+
unlisten = null;
|
|
4173
|
+
}
|
|
4174
|
+
function patchWaiting(next) {
|
|
4175
|
+
const current = store.snapshot().waiting;
|
|
4176
|
+
const base = current ?? {
|
|
4177
|
+
admission: "knocking",
|
|
4178
|
+
admittersPresent: null,
|
|
4179
|
+
reknockAllowed: true,
|
|
4180
|
+
knockExpiresAt: null,
|
|
4181
|
+
pollSeconds: 3,
|
|
4182
|
+
liveVersion: 0
|
|
4183
|
+
};
|
|
4184
|
+
store.patch({ waiting: { ...base, ...next } });
|
|
4185
|
+
}
|
|
4186
|
+
function apply(read) {
|
|
4187
|
+
const snapshot = store.snapshot();
|
|
4188
|
+
if (snapshot.phase !== "lobby") return;
|
|
4189
|
+
const admission = read.admission_state ?? null;
|
|
4190
|
+
const ended = admission === "ended" || read.session_phase === "ended" || read.meeting_phase === "ended";
|
|
4191
|
+
if (admission === "admitted" && !ended) {
|
|
4192
|
+
if (admittedOnce) return;
|
|
4193
|
+
admittedOnce = true;
|
|
4194
|
+
stopWaiting();
|
|
4195
|
+
options.onAdmitted?.();
|
|
4196
|
+
return;
|
|
4197
|
+
}
|
|
4198
|
+
if (ended) {
|
|
4199
|
+
stopWaiting();
|
|
4200
|
+
patchWaiting({ admission: "ended" });
|
|
4201
|
+
const meeting = snapshot.meeting;
|
|
4202
|
+
if (read.meeting_phase === "ended" && meeting !== null && meeting.endedAt === null) {
|
|
4203
|
+
store.patch({ meeting: { ...meeting, endedAt: (/* @__PURE__ */ new Date()).toISOString() } });
|
|
4204
|
+
}
|
|
4205
|
+
return;
|
|
4206
|
+
}
|
|
4207
|
+
if (admission === null) return;
|
|
4208
|
+
patchWaiting({
|
|
4209
|
+
admission,
|
|
4210
|
+
admittersPresent: read.admitters_present ?? null,
|
|
4211
|
+
reknockAllowed: read.reknock_allowed ?? true,
|
|
4212
|
+
knockExpiresAt: read.knock_expires_at ?? null,
|
|
4213
|
+
pollSeconds: read.poll_seconds ?? snapshot.waiting?.pollSeconds ?? 3,
|
|
4214
|
+
liveVersion: read.live_version ?? 0
|
|
4215
|
+
});
|
|
4216
|
+
if (admission === "denied" || admission === "expired" || admission === "withdrawn" || admission === "removed") {
|
|
4217
|
+
stopWaiting();
|
|
4218
|
+
}
|
|
4219
|
+
}
|
|
4220
|
+
async function readState2() {
|
|
4221
|
+
if (reading !== null) return reading;
|
|
4222
|
+
const meeting = store.snapshot().meeting;
|
|
4223
|
+
const pass = options.resolvePass?.() ?? null;
|
|
4224
|
+
if (meeting === null || pass === null) return;
|
|
4225
|
+
reading = (async () => {
|
|
4226
|
+
try {
|
|
4227
|
+
const response = await options.api.request({
|
|
4228
|
+
path: `${MEET_ROUTES.state}?meeting_id=${encodeURIComponent(meeting.id)}`,
|
|
4229
|
+
method: "GET",
|
|
4230
|
+
requireAuth: false,
|
|
4231
|
+
operation: "meet.state",
|
|
4232
|
+
headers: { [MEETING_PASS_HEADER]: pass }
|
|
4233
|
+
});
|
|
4234
|
+
if (!response.ok) {
|
|
4235
|
+
let body = null;
|
|
4236
|
+
try {
|
|
4237
|
+
body = await response.json();
|
|
4238
|
+
} catch {
|
|
4239
|
+
body = null;
|
|
4240
|
+
}
|
|
4241
|
+
const refusal2 = readServerRefusal(body);
|
|
4242
|
+
report(
|
|
4243
|
+
"warn",
|
|
4244
|
+
refusal2?.reason ?? `meet.state: the server answered ${response.status}`,
|
|
4245
|
+
refusal2?.remedy ?? "The waiting room keeps asking; nothing on this screen changes until the server answers."
|
|
4246
|
+
);
|
|
4247
|
+
return;
|
|
4248
|
+
}
|
|
4249
|
+
apply(await response.json());
|
|
4250
|
+
} catch (error) {
|
|
4251
|
+
report("info", `meet.state: ${String(error)}`, "Waiting continues; the state is read again shortly.");
|
|
4252
|
+
} finally {
|
|
4253
|
+
reading = null;
|
|
4254
|
+
}
|
|
4255
|
+
})();
|
|
4256
|
+
return reading;
|
|
4257
|
+
}
|
|
4258
|
+
async function postLeave(intent) {
|
|
4259
|
+
const meeting = requireMeeting(`meet.${intent}`);
|
|
4260
|
+
const pass = options.resolvePass?.() ?? null;
|
|
4261
|
+
const response = await options.api.request({
|
|
4262
|
+
path: MEET_ROUTES.leave,
|
|
4263
|
+
method: "POST",
|
|
4264
|
+
requireAuth: false,
|
|
4265
|
+
operation: `meet.${intent}`,
|
|
4266
|
+
body: { meeting_id: meeting.id, intent },
|
|
4267
|
+
...pass !== null ? { headers: { [MEETING_PASS_HEADER]: pass } } : {}
|
|
4268
|
+
});
|
|
4269
|
+
if (!response.ok) {
|
|
4270
|
+
let body = null;
|
|
4271
|
+
try {
|
|
4272
|
+
body = await response.json();
|
|
4273
|
+
} catch {
|
|
4274
|
+
body = null;
|
|
4275
|
+
}
|
|
4276
|
+
const refusal2 = readServerRefusal(body);
|
|
4277
|
+
throw new MeetError(
|
|
4278
|
+
"transport",
|
|
4279
|
+
refusal2?.reason ?? `meet.${intent}: the server answered ${response.status}`,
|
|
4280
|
+
refusal2?.remedy ?? "You are still in the waiting room. Try again."
|
|
4281
|
+
);
|
|
4282
|
+
}
|
|
4283
|
+
}
|
|
4284
|
+
function hint() {
|
|
4285
|
+
channel?.send(MEET_EVENTS.stateChanged, { v: null });
|
|
4286
|
+
}
|
|
4287
|
+
async function refreshQueue() {
|
|
4288
|
+
const meeting = store.snapshot().meeting;
|
|
4289
|
+
if (meeting === null || isGuest(identity) || !isHostRole(effectiveRole(store.snapshot()))) return;
|
|
4290
|
+
try {
|
|
4291
|
+
const attendees = await repository.attendees(meeting.id);
|
|
4292
|
+
store.learn(attendees);
|
|
4293
|
+
for (const attendee of attendees) {
|
|
4294
|
+
const entry = lobbyEntryFrom(attendee);
|
|
4295
|
+
if (entry !== null) store.upsertLobby(entry);
|
|
4296
|
+
else store.setAdmission(attendee.identity, decidedState(attendee.admissionState), null);
|
|
4297
|
+
}
|
|
4298
|
+
} catch {
|
|
4299
|
+
}
|
|
4300
|
+
}
|
|
4029
4301
|
return {
|
|
4030
4302
|
watch(roomName) {
|
|
4031
4303
|
channel?.close();
|
|
@@ -4033,58 +4305,22 @@ function createMeetingSession(options) {
|
|
|
4033
4305
|
topic: roomTopic(roomName),
|
|
4034
4306
|
broadcast: [
|
|
4035
4307
|
{
|
|
4036
|
-
|
|
4037
|
-
|
|
4038
|
-
|
|
4039
|
-
|
|
4040
|
-
|
|
4041
|
-
{
|
|
4042
|
-
event: MEET_EVENTS.admission,
|
|
4043
|
-
onMessage: (message) => {
|
|
4044
|
-
const verdict = message.data;
|
|
4045
|
-
const decided = asParticipantIdentity(verdict.identity);
|
|
4046
|
-
store.setAdmission(
|
|
4047
|
-
decided,
|
|
4048
|
-
verdict.admitted ? "admitted" : "denied",
|
|
4049
|
-
verdict.by === null ? null : verdict.by
|
|
4050
|
-
);
|
|
4051
|
-
if (decided === knockIdentity()) {
|
|
4052
|
-
if (verdict.admitted) options.onAdmitted?.();
|
|
4053
|
-
else options.onDenied?.();
|
|
4054
|
-
}
|
|
4055
|
-
}
|
|
4056
|
-
},
|
|
4057
|
-
{
|
|
4058
|
-
event: MEET_EVENTS.lock,
|
|
4059
|
-
onMessage: (message) => {
|
|
4060
|
-
const { locked } = message.data;
|
|
4061
|
-
const meeting = store.snapshot().meeting;
|
|
4062
|
-
store.patch({
|
|
4063
|
-
locked,
|
|
4064
|
-
...meeting !== null ? { meeting: { ...meeting, locked } } : {}
|
|
4065
|
-
});
|
|
4066
|
-
}
|
|
4067
|
-
},
|
|
4068
|
-
{
|
|
4069
|
-
event: MEET_EVENTS.recording,
|
|
4070
|
-
onMessage: (message) => {
|
|
4071
|
-
const payload = message.data;
|
|
4072
|
-
store.setRecording(
|
|
4073
|
-
payload.state,
|
|
4074
|
-
payload.by === null ? null : payload.by,
|
|
4075
|
-
"room"
|
|
4076
|
-
);
|
|
4308
|
+
// 🚨 PAYLOAD-FREE. Every receiver reads again; a spoofed hint only causes a read.
|
|
4309
|
+
event: MEET_EVENTS.stateChanged,
|
|
4310
|
+
onMessage: () => {
|
|
4311
|
+
if (store.snapshot().phase === "lobby") void readState2();
|
|
4312
|
+
void refreshQueue();
|
|
4077
4313
|
}
|
|
4078
4314
|
}
|
|
4079
4315
|
],
|
|
4080
|
-
// THE BACKFILL DOOR. A
|
|
4081
|
-
//
|
|
4082
|
-
// re-announce on their own timer.
|
|
4316
|
+
// THE BACKFILL DOOR. A socket blip must not lose a decision: re-read the meeting
|
|
4317
|
+
// row, and a waiting tab re-reads its own state.
|
|
4083
4318
|
onBackfill: async () => {
|
|
4084
4319
|
const meeting = store.snapshot().meeting;
|
|
4085
4320
|
if (meeting === null) return;
|
|
4086
4321
|
const fresh = isGuest(identity) ? await repository.meetingBySlug(meeting.slug) : await readMeeting(meeting.id);
|
|
4087
4322
|
store.patch({ meeting: fresh, locked: fresh.locked });
|
|
4323
|
+
if (store.snapshot().phase === "lobby") void readState2();
|
|
4088
4324
|
}
|
|
4089
4325
|
});
|
|
4090
4326
|
},
|
|
@@ -4145,7 +4381,7 @@ function createMeetingSession(options) {
|
|
|
4145
4381
|
store.upsertLobby(entry);
|
|
4146
4382
|
return;
|
|
4147
4383
|
}
|
|
4148
|
-
store.setAdmission(attendee.identity, attendee.admissionState, null);
|
|
4384
|
+
store.setAdmission(attendee.identity, decidedState(attendee.admissionState), null);
|
|
4149
4385
|
}
|
|
4150
4386
|
},
|
|
4151
4387
|
{
|
|
@@ -4197,62 +4433,75 @@ function createMeetingSession(options) {
|
|
|
4197
4433
|
if (entry !== null) {
|
|
4198
4434
|
store.upsertLobby(entry);
|
|
4199
4435
|
} else {
|
|
4200
|
-
store.setAdmission(attendee.identity, attendee.admissionState, null);
|
|
4436
|
+
store.setAdmission(attendee.identity, decidedState(attendee.admissionState), null);
|
|
4201
4437
|
}
|
|
4202
4438
|
}
|
|
4203
4439
|
}
|
|
4204
4440
|
});
|
|
4205
4441
|
meetingChannel.requestBackfill("manual");
|
|
4206
4442
|
},
|
|
4207
|
-
knock(roomName) {
|
|
4208
|
-
const handle = channel ?? manager.open({ topic: roomTopic(roomName) });
|
|
4209
|
-
channel = handle;
|
|
4210
|
-
const entry = {
|
|
4211
|
-
identity: knockIdentity(),
|
|
4212
|
-
displayName: identity.displayName,
|
|
4213
|
-
avatarUrl: identity.avatarUrl,
|
|
4214
|
-
userId: identity.userId,
|
|
4215
|
-
state: "knocking",
|
|
4216
|
-
knockedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4217
|
-
decidedAt: null,
|
|
4218
|
-
decidedBy: null
|
|
4219
|
-
};
|
|
4220
|
-
store.upsertLobby(entry);
|
|
4221
|
-
handle.send(MEET_EVENTS.knock, entry);
|
|
4222
|
-
},
|
|
4223
4443
|
async admit(entry, admitted) {
|
|
4224
4444
|
const by = requireHostUser("meet.admit");
|
|
4225
4445
|
const meeting = requireMeeting("meet.admit");
|
|
4226
|
-
await repository.
|
|
4446
|
+
const row = await repository.decideAdmission({
|
|
4227
4447
|
meetingId: meeting.id,
|
|
4228
4448
|
identity: entry.identity,
|
|
4229
|
-
admitted,
|
|
4449
|
+
decision: admitted ? "admit" : "deny",
|
|
4230
4450
|
byUserId: by
|
|
4231
4451
|
});
|
|
4232
4452
|
store.upsertLobby({
|
|
4233
4453
|
...entry,
|
|
4234
|
-
state:
|
|
4454
|
+
state: decidedState(row.admissionState),
|
|
4235
4455
|
decidedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4236
4456
|
decidedBy: by
|
|
4237
4457
|
});
|
|
4238
|
-
|
|
4458
|
+
hint();
|
|
4459
|
+
},
|
|
4460
|
+
async undeny(entry) {
|
|
4461
|
+
const by = requireHostUser("meet.undeny");
|
|
4462
|
+
const meeting = requireMeeting("meet.undeny");
|
|
4463
|
+
const row = await repository.decideAdmission({
|
|
4464
|
+
meetingId: meeting.id,
|
|
4239
4465
|
identity: entry.identity,
|
|
4240
|
-
|
|
4241
|
-
by
|
|
4466
|
+
decision: "undeny",
|
|
4467
|
+
byUserId: by
|
|
4242
4468
|
});
|
|
4469
|
+
store.upsertLobby({ ...entry, state: decidedState(row.admissionState), decidedAt: (/* @__PURE__ */ new Date()).toISOString(), decidedBy: by });
|
|
4470
|
+
hint();
|
|
4243
4471
|
},
|
|
4244
4472
|
async admitAll() {
|
|
4245
|
-
const
|
|
4246
|
-
|
|
4247
|
-
|
|
4473
|
+
const by = requireHostUser("meet.admitAll");
|
|
4474
|
+
const meeting = requireMeeting("meet.admitAll");
|
|
4475
|
+
const rows = await repository.admitAll(meeting.id, by);
|
|
4476
|
+
for (const row of rows) {
|
|
4477
|
+
store.setAdmission(row.identity, row.admissionState, by);
|
|
4248
4478
|
}
|
|
4479
|
+
hint();
|
|
4480
|
+
},
|
|
4481
|
+
startWaiting() {
|
|
4482
|
+
if (polling) return;
|
|
4483
|
+
polling = true;
|
|
4484
|
+
admittedOnce = false;
|
|
4485
|
+
listen();
|
|
4486
|
+
schedule(0);
|
|
4487
|
+
},
|
|
4488
|
+
stopWaiting,
|
|
4489
|
+
readState: readState2,
|
|
4490
|
+
async withdraw() {
|
|
4491
|
+
stopWaiting();
|
|
4492
|
+
await postLeave("withdraw");
|
|
4493
|
+
patchWaiting({ admission: "withdrawn" });
|
|
4494
|
+
hint();
|
|
4495
|
+
},
|
|
4496
|
+
async askAgain() {
|
|
4497
|
+
await options.rejoin?.();
|
|
4249
4498
|
},
|
|
4250
4499
|
async setLock(locked) {
|
|
4251
4500
|
const by = requireHostUser("meet.setLock");
|
|
4252
4501
|
const meeting = requireMeeting("meet.setLock");
|
|
4253
4502
|
const updated = await repository.setLock(meeting.id, locked, by);
|
|
4254
4503
|
store.patch({ meeting: updated, locked: updated.locked });
|
|
4255
|
-
|
|
4504
|
+
hint();
|
|
4256
4505
|
return updated;
|
|
4257
4506
|
},
|
|
4258
4507
|
/**
|
|
@@ -4310,6 +4559,7 @@ function createMeetingSession(options) {
|
|
|
4310
4559
|
return { endedAt, served: true };
|
|
4311
4560
|
},
|
|
4312
4561
|
dispose() {
|
|
4562
|
+
stopWaiting();
|
|
4313
4563
|
channel?.close();
|
|
4314
4564
|
channel = null;
|
|
4315
4565
|
meetingChannel?.close();
|
|
@@ -5170,13 +5420,14 @@ function createRoomEngine(options) {
|
|
|
5170
5420
|
}
|
|
5171
5421
|
};
|
|
5172
5422
|
async function connect(request) {
|
|
5173
|
-
store.patch({ phase: "requesting-token", failure: null });
|
|
5423
|
+
store.patch({ phase: "requesting-token", failure: null, waiting: null });
|
|
5174
5424
|
const token = await tokens.get({
|
|
5175
5425
|
roomName: request.roomName,
|
|
5176
5426
|
identity,
|
|
5177
5427
|
...request.meetingId !== void 0 ? { meetingId: request.meetingId } : {},
|
|
5178
5428
|
...request.slug !== void 0 ? { slug: request.slug } : {},
|
|
5179
|
-
...request.breakoutRoomId != null ? { breakoutRoomId: request.breakoutRoomId } : {}
|
|
5429
|
+
...request.breakoutRoomId != null ? { breakoutRoomId: request.breakoutRoomId } : {},
|
|
5430
|
+
...request.observe === true ? { observe: true } : {}
|
|
5180
5431
|
});
|
|
5181
5432
|
current = token;
|
|
5182
5433
|
store.patch({
|
|
@@ -5187,14 +5438,25 @@ function createRoomEngine(options) {
|
|
|
5187
5438
|
standing: token.standing,
|
|
5188
5439
|
canRecord: token.canRecord,
|
|
5189
5440
|
canPublish: token.canPublish,
|
|
5190
|
-
canSubscribe: token.canSubscribe
|
|
5441
|
+
canSubscribe: token.canSubscribe,
|
|
5442
|
+
observersVisibleTo: token.observersVisibleTo ?? "everyone"
|
|
5191
5443
|
}
|
|
5192
5444
|
});
|
|
5193
|
-
if (token.lobby) {
|
|
5194
|
-
store.patch({
|
|
5445
|
+
if (token.lobby || !token.canSubscribe) {
|
|
5446
|
+
store.patch({
|
|
5447
|
+
phase: "lobby",
|
|
5448
|
+
waiting: {
|
|
5449
|
+
admission: token.admissionState !== void 0 && token.admissionState !== "admitted" ? token.admissionState : "knocking",
|
|
5450
|
+
admittersPresent: null,
|
|
5451
|
+
reknockAllowed: true,
|
|
5452
|
+
knockExpiresAt: null,
|
|
5453
|
+
pollSeconds: token.pollSeconds ?? 3,
|
|
5454
|
+
liveVersion: 0
|
|
5455
|
+
}
|
|
5456
|
+
});
|
|
5195
5457
|
return;
|
|
5196
5458
|
}
|
|
5197
|
-
store.patch({ phase: "connecting" });
|
|
5459
|
+
store.patch({ phase: "connecting", waiting: null });
|
|
5198
5460
|
const created = await options.driverFactory(handlers);
|
|
5199
5461
|
if (disposed) {
|
|
5200
5462
|
await created.disconnect();
|
|
@@ -5677,7 +5939,9 @@ function createInRoomClient(options) {
|
|
|
5677
5939
|
...request.allowChoose !== void 0 ? { allow_choose: request.allowChoose } : {},
|
|
5678
5940
|
...request.minutes !== void 0 ? { minutes: request.minutes } : {},
|
|
5679
5941
|
...request.identity !== void 0 ? { identity: request.identity } : {},
|
|
5680
|
-
...request.roomId !== void 0 ? { room_id: request.roomId } : {}
|
|
5942
|
+
...request.roomId !== void 0 ? { room_id: request.roomId } : {},
|
|
5943
|
+
...request.panel !== void 0 ? { panel: request.panel } : {},
|
|
5944
|
+
...request.section !== void 0 ? { section: request.section } : {}
|
|
5681
5945
|
},
|
|
5682
5946
|
...hdrs !== void 0 ? { headers: hdrs } : {}
|
|
5683
5947
|
});
|
|
@@ -6453,7 +6717,7 @@ function observedPhase(input) {
|
|
|
6453
6717
|
case "left":
|
|
6454
6718
|
return "prejoin";
|
|
6455
6719
|
case "lobby":
|
|
6456
|
-
return
|
|
6720
|
+
return waitingPhase(snapshot);
|
|
6457
6721
|
case "requesting-token":
|
|
6458
6722
|
case "connecting":
|
|
6459
6723
|
return "joining";
|
|
@@ -6462,6 +6726,24 @@ function observedPhase(input) {
|
|
|
6462
6726
|
return "in_call";
|
|
6463
6727
|
}
|
|
6464
6728
|
}
|
|
6729
|
+
function waitingPhase(snapshot) {
|
|
6730
|
+
switch (snapshot.waiting?.admission) {
|
|
6731
|
+
case "waiting_host":
|
|
6732
|
+
return "waiting:host_not_started";
|
|
6733
|
+
case "denied":
|
|
6734
|
+
return "denied";
|
|
6735
|
+
case "expired":
|
|
6736
|
+
return "knock_expired";
|
|
6737
|
+
case "withdrawn":
|
|
6738
|
+
return "left";
|
|
6739
|
+
case "ended":
|
|
6740
|
+
return "ended";
|
|
6741
|
+
case "removed":
|
|
6742
|
+
return "refused:removed";
|
|
6743
|
+
default:
|
|
6744
|
+
return "waiting:knocking";
|
|
6745
|
+
}
|
|
6746
|
+
}
|
|
6465
6747
|
var ADMITTER_ROLES = /* @__PURE__ */ new Set(["host", "cohost"]);
|
|
6466
6748
|
function observe(input) {
|
|
6467
6749
|
const { snapshot } = input;
|
|
@@ -6476,7 +6758,7 @@ function observe(input) {
|
|
|
6476
6758
|
reason: phase.startsWith("refused:") ? phase.slice("refused:".length) : null,
|
|
6477
6759
|
role,
|
|
6478
6760
|
connection: snapshot?.phase === "reconnecting" ? "reconnecting" : "stable",
|
|
6479
|
-
hostPresent: inCall && snapshot !== null ? snapshot.participants.some((p) => ADMITTER_ROLES.has(p.role)) : null,
|
|
6761
|
+
hostPresent: inCall && snapshot !== null ? snapshot.participants.some((p) => ADMITTER_ROLES.has(p.role)) : snapshot?.phase === "lobby" ? snapshot.waiting?.admittersPresent ?? null : null,
|
|
6480
6762
|
lobbyCount: canAdmit && snapshot !== null ? snapshot.lobby.filter((entry) => entry.state === "knocking").length : 0,
|
|
6481
6763
|
recording: snapshot?.recording === "recording" || snapshot?.recording === "stopping",
|
|
6482
6764
|
audioBlocked: null,
|
|
@@ -6602,6 +6884,7 @@ export {
|
|
|
6602
6884
|
activeWrapUpSummary,
|
|
6603
6885
|
activityAttention,
|
|
6604
6886
|
announceMeeting,
|
|
6887
|
+
appPanelOf,
|
|
6605
6888
|
applyCollabPacket,
|
|
6606
6889
|
applyRoomEvent,
|
|
6607
6890
|
asCallId,
|
|
@@ -6653,6 +6936,7 @@ export {
|
|
|
6653
6936
|
defaultCaptionLanguage,
|
|
6654
6937
|
displayNameFor,
|
|
6655
6938
|
fileDownloadPath,
|
|
6939
|
+
focusFromRow,
|
|
6656
6940
|
gridColumns,
|
|
6657
6941
|
groupTranscript,
|
|
6658
6942
|
guestRecordAccessExplanation,
|
|
@@ -6667,6 +6951,7 @@ export {
|
|
|
6667
6951
|
isLinkFollower,
|
|
6668
6952
|
isListenOnly,
|
|
6669
6953
|
isNoteTaker,
|
|
6954
|
+
isObserverRole,
|
|
6670
6955
|
isRealName,
|
|
6671
6956
|
isRoomEventTopic,
|
|
6672
6957
|
learnName,
|
|
@@ -6731,6 +7016,7 @@ export {
|
|
|
6731
7016
|
roleOf,
|
|
6732
7017
|
roomTopic,
|
|
6733
7018
|
rootAttributes,
|
|
7019
|
+
scheduleSettingsPatch,
|
|
6734
7020
|
searchTranscript,
|
|
6735
7021
|
sendFollowUp,
|
|
6736
7022
|
setCaptionLanguage,
|