@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.cjs
CHANGED
|
@@ -80,6 +80,7 @@ __export(src_exports, {
|
|
|
80
80
|
activeWrapUpSummary: () => activeWrapUpSummary,
|
|
81
81
|
activityAttention: () => activityAttention,
|
|
82
82
|
announceMeeting: () => announceMeeting,
|
|
83
|
+
appPanelOf: () => appPanelOf,
|
|
83
84
|
applyCollabPacket: () => applyCollabPacket,
|
|
84
85
|
applyRoomEvent: () => applyRoomEvent,
|
|
85
86
|
asCallId: () => asCallId,
|
|
@@ -131,6 +132,7 @@ __export(src_exports, {
|
|
|
131
132
|
defaultCaptionLanguage: () => defaultCaptionLanguage,
|
|
132
133
|
displayNameFor: () => displayNameFor,
|
|
133
134
|
fileDownloadPath: () => fileDownloadPath,
|
|
135
|
+
focusFromRow: () => focusFromRow,
|
|
134
136
|
gridColumns: () => gridColumns,
|
|
135
137
|
groupTranscript: () => groupTranscript,
|
|
136
138
|
guestRecordAccessExplanation: () => guestRecordAccessExplanation,
|
|
@@ -145,6 +147,7 @@ __export(src_exports, {
|
|
|
145
147
|
isLinkFollower: () => isLinkFollower,
|
|
146
148
|
isListenOnly: () => isListenOnly,
|
|
147
149
|
isNoteTaker: () => isNoteTaker,
|
|
150
|
+
isObserverRole: () => isObserverRole,
|
|
148
151
|
isRealName: () => isRealName,
|
|
149
152
|
isRoomEventTopic: () => isRoomEventTopic,
|
|
150
153
|
learnName: () => learnName,
|
|
@@ -209,6 +212,7 @@ __export(src_exports, {
|
|
|
209
212
|
roleOf: () => roleOf,
|
|
210
213
|
roomTopic: () => roomTopic,
|
|
211
214
|
rootAttributes: () => rootAttributes,
|
|
215
|
+
scheduleSettingsPatch: () => scheduleSettingsPatch,
|
|
212
216
|
searchTranscript: () => searchTranscript,
|
|
213
217
|
sendFollowUp: () => sendFollowUp,
|
|
214
218
|
setCaptionLanguage: () => setCaptionLanguage,
|
|
@@ -534,7 +538,17 @@ var MEET_ROUTES = {
|
|
|
534
538
|
*/
|
|
535
539
|
collab: "/api/v1/meet/collab",
|
|
536
540
|
/** Meet wave 5: translate finished caption lines into this viewer's language. */
|
|
537
|
-
captionsTranslate: "/api/v1/meet/captions/translate"
|
|
541
|
+
captionsTranslate: "/api/v1/meet/captions/translate",
|
|
542
|
+
/**
|
|
543
|
+
* S4 — THE STATE READ (CORE-DESIGN §2.6): `GET ?meeting_id=` with `x-meet-pass`. This
|
|
544
|
+
* person's admission (lazy expiry applied), whether anyone who can admit is here, the run's
|
|
545
|
+
* phase. Read every `waiting_poll_seconds` while waiting and on every `mx.state.changed`.
|
|
546
|
+
*/
|
|
547
|
+
state: "/api/v1/meet/state",
|
|
548
|
+
/** S4: `withdraw` (stop waiting) or `leave` (close this tab); the pass may ride in the body. */
|
|
549
|
+
leave: "/api/v1/meet/leave",
|
|
550
|
+
/** S4: a pass holder (guests included) saw the recording notice, for this run. */
|
|
551
|
+
consent: "/api/v1/meet/consent"
|
|
538
552
|
};
|
|
539
553
|
function meetingPath(template, meetingId) {
|
|
540
554
|
return template.replace("{meeting_id}", encodeURIComponent(meetingId));
|
|
@@ -608,7 +622,10 @@ function parseRoomToken(body, roomName) {
|
|
|
608
622
|
meetingPassExpiresAt: readString(payload, "meeting_pass_expires_at"),
|
|
609
623
|
sessionId: readString(payload, "session_id"),
|
|
610
624
|
lobby: readBoolean(payload, "lobby", false),
|
|
611
|
-
|
|
625
|
+
admissionState: readString(payload, "admission_state") ?? void 0,
|
|
626
|
+
pollSeconds: typeof payload["poll_seconds"] === "number" ? payload["poll_seconds"] : void 0,
|
|
627
|
+
breakoutRoomId: readString(payload, "breakout_room_id") ?? null,
|
|
628
|
+
observersVisibleTo: readString(payload, "observers_visible_to") === "hosts" ? "hosts" : "everyone"
|
|
612
629
|
};
|
|
613
630
|
}
|
|
614
631
|
function createTokenClient(options) {
|
|
@@ -657,6 +674,7 @@ function createTokenClient(options) {
|
|
|
657
674
|
...input.meetingId !== void 0 ? { meeting_id: input.meetingId } : {},
|
|
658
675
|
...input.slug !== void 0 ? { slug: input.slug } : {},
|
|
659
676
|
...input.breakoutRoomId != null ? { breakout_room_id: input.breakoutRoomId } : {},
|
|
677
|
+
...input.observe === true ? { observe: true } : {},
|
|
660
678
|
organization_id: input.identity.organizationId,
|
|
661
679
|
display_name: input.identity.displayName,
|
|
662
680
|
device_id: input.identity.deviceId,
|
|
@@ -728,7 +746,8 @@ function createTokenClient(options) {
|
|
|
728
746
|
return {
|
|
729
747
|
get(input) {
|
|
730
748
|
const entry = cache.get(input.roomName);
|
|
731
|
-
const sameRoom = entry !== void 0 && (entry.token.breakoutRoomId ?? null) === (input.breakoutRoomId ?? null)
|
|
749
|
+
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).
|
|
750
|
+
entry.token.role === "viewer" === (input.observe === true);
|
|
732
751
|
if (entry !== void 0 && sameRoom) {
|
|
733
752
|
if (entry.inFlight !== null) return entry.inFlight;
|
|
734
753
|
if (!expired(entry)) return Promise.resolve(entry.token);
|
|
@@ -1220,14 +1239,15 @@ var MEET_EVENTS = {
|
|
|
1220
1239
|
cancel: "mx.call.cancel",
|
|
1221
1240
|
/** You answered (or declined) on another device. Stop ringing here. */
|
|
1222
1241
|
settled: "mx.call.settled",
|
|
1223
|
-
/**
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1242
|
+
/**
|
|
1243
|
+
* 🚨 S4 (CORE-DESIGN §2.6): the ONE room-channel event — payload-free "something changed,
|
|
1244
|
+
* read again". Nothing a person must see rides it: a waiting person learns admitted /
|
|
1245
|
+
* denied / expired / ended by READING their row (`GET /v1/meet/state`) on a timer and on
|
|
1246
|
+
* every hint. The four payload broadcasts (`mx.lobby.knock`, `mx.lobby.admission`,
|
|
1247
|
+
* `mx.room.lock`, `mx.room.recording`) are gone with their senders and receivers. The
|
|
1248
|
+
* server publishes the same event on the same topic (aidream `admission.room_topic`).
|
|
1249
|
+
*/
|
|
1250
|
+
stateChanged: "mx.state.changed"
|
|
1231
1251
|
};
|
|
1232
1252
|
|
|
1233
1253
|
// src/core/types.ts
|
|
@@ -1253,6 +1273,10 @@ function isLinkFollower(standing) {
|
|
|
1253
1273
|
function isHostRole(role) {
|
|
1254
1274
|
return role === "host" || role === "cohost";
|
|
1255
1275
|
}
|
|
1276
|
+
function isObserverRole(role) {
|
|
1277
|
+
return role === "viewer";
|
|
1278
|
+
}
|
|
1279
|
+
var WAITING_ADMISSION_STATES = ["waiting_host", "knocking"];
|
|
1256
1280
|
|
|
1257
1281
|
// src/core/calls.ts
|
|
1258
1282
|
var TRANSITIONS = {
|
|
@@ -1501,813 +1525,492 @@ function createCallCenter(options) {
|
|
|
1501
1525
|
};
|
|
1502
1526
|
}
|
|
1503
1527
|
|
|
1504
|
-
// src/core/
|
|
1505
|
-
var
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
captionTranslations: {},
|
|
1518
|
-
captionTranslationError: null
|
|
1528
|
+
// src/core/repository.ts
|
|
1529
|
+
var MEET_SCHEMA = "communication";
|
|
1530
|
+
var TABLES = {
|
|
1531
|
+
meetings: "meet_meetings",
|
|
1532
|
+
participants: "meet_participants",
|
|
1533
|
+
invites: "meet_call_invites",
|
|
1534
|
+
recordings: "meet_recordings",
|
|
1535
|
+
transcripts: "meet_transcript_segments",
|
|
1536
|
+
notes: "meet_notes",
|
|
1537
|
+
/** Wave 1 (0.8): who a meeting was sent to, and what they answered. */
|
|
1538
|
+
invitees: "meet_invitees",
|
|
1539
|
+
/** Wave 1 (0.8): one occurrence of a series cancelled or moved. */
|
|
1540
|
+
occurrenceOverrides: "meet_occurrence_overrides"
|
|
1519
1541
|
};
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1542
|
+
var RPCS = {
|
|
1543
|
+
/** Atomic: create the invite row and stamp its expiry in one statement. */
|
|
1544
|
+
createCallInvite: "meet_create_call_invite",
|
|
1545
|
+
/** Atomic settle. Refuses a transition out of a terminal state. */
|
|
1546
|
+
settleCallInvite: "meet_settle_call_invite",
|
|
1547
|
+
/** Every invite still ringing this user — the reconnect backfill door. */
|
|
1548
|
+
pendingCallInvites: "meet_pending_call_invites",
|
|
1549
|
+
/** Instant meeting, or the durable one behind a recurring link. */
|
|
1550
|
+
getOrCreateMeeting: "meet_get_or_create_meeting",
|
|
1551
|
+
/** Resolve a durable public link, before/during/after its scheduled time. */
|
|
1552
|
+
meetingBySlug: "meet_meeting_by_slug",
|
|
1553
|
+
/*
|
|
1554
|
+
* ── S4 ADMISSION (CORE-DESIGN §2.4). Each returns the decided row; the caller then
|
|
1555
|
+
* sends the payload-free `mx.state.changed` hint. Authorized by `who_may_admit`.
|
|
1556
|
+
*/
|
|
1557
|
+
admit: "meet_admit",
|
|
1558
|
+
deny: "meet_deny",
|
|
1559
|
+
undeny: "meet_undeny",
|
|
1560
|
+
admitAll: "meet_admit_all",
|
|
1561
|
+
setLock: "meet_set_lock",
|
|
1562
|
+
recordConsent: "meet_record_consent",
|
|
1563
|
+
/*
|
|
1564
|
+
* ── MEET WAVE 1 — management (0.8). Every write is a SECURITY DEFINER door
|
|
1565
|
+
* authorized by the meeting's host or a real iam grant (editor to change,
|
|
1566
|
+
* admin = co-host to archive or name a co-host) — never an organization
|
|
1567
|
+
* match. After any of these writes, call `announceMeeting` so the server
|
|
1568
|
+
* sends invitations / updates / cancellations and re-queues reminders.
|
|
1569
|
+
*/
|
|
1570
|
+
/** Schedule with every setting at create; unset settings come from the knobs. */
|
|
1571
|
+
scheduleMeeting: "meet_schedule_meeting",
|
|
1572
|
+
/** Edit / reschedule — a JSON patch with a version compare-and-swap. */
|
|
1573
|
+
updateMeeting: "meet_update_meeting",
|
|
1574
|
+
/** Cancel is a state (`cancelled_at`); the link keeps resolving. */
|
|
1575
|
+
cancelMeeting: "meet_cancel_meeting",
|
|
1576
|
+
/** Archive (soft delete) — the invitations and exceptions cascade. */
|
|
1577
|
+
archiveMeeting: "meet_archive_meeting",
|
|
1578
|
+
restoreMeeting: "meet_restore_meeting",
|
|
1579
|
+
/** Cancel, move or restore ONE occurrence of a series by its original start. */
|
|
1580
|
+
setOccurrence: "meet_set_occurrence",
|
|
1581
|
+
addInvitees: "meet_add_invitees",
|
|
1582
|
+
setInviteeRole: "meet_set_invitee_role",
|
|
1583
|
+
removeInvitee: "meet_remove_invitee",
|
|
1584
|
+
/** A signed-in invitee answers for themselves. */
|
|
1585
|
+
respond: "meet_respond",
|
|
1586
|
+
/** The emailed RSVP link (no account): read and answer. */
|
|
1587
|
+
invitationByToken: "meet_invitation_by_token",
|
|
1588
|
+
respondByToken: "meet_respond_by_token",
|
|
1589
|
+
/** One meeting's occurrences between two instants (INVOKER — the reader's RLS). */
|
|
1590
|
+
meetingOccurrences: "meet_meeting_occurrences",
|
|
1591
|
+
/** Every occurrence the reader can see between two instants — the Upcoming list. */
|
|
1592
|
+
occurrencesBetween: "meet_occurrences_between",
|
|
1593
|
+
/*
|
|
1594
|
+
* ── MEET WAVE 3 — after the meeting. Both INVOKER (the reader's RLS) and both
|
|
1595
|
+
* scoped to the reader's OWN meetings (host, invitee, attended) — RLS is the
|
|
1596
|
+
* ceiling, never the view.
|
|
1597
|
+
*/
|
|
1598
|
+
/** The recordings library. */
|
|
1599
|
+
myRecordings: "meet_my_recordings",
|
|
1600
|
+
/** Titles, agendas, current notes and final transcript lines; snippets mark hits with ⟦ ⟧. */
|
|
1601
|
+
search: "meet_search"
|
|
1602
|
+
};
|
|
1603
|
+
var VOID_RPCS = [RPCS.recordConsent];
|
|
1604
|
+
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";
|
|
1605
|
+
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";
|
|
1606
|
+
var RECORDING_COLUMNS = "id,meeting_id,state,started_at,ended_at,started_by,file_id,failure_reason";
|
|
1607
|
+
var SEGMENT_COLUMNS = "id,meeting_id,identity,speaker,text,started_at,is_final,metadata";
|
|
1608
|
+
var NOTE_COLUMNS = "id,meeting_id,text,kind,assignee_user_id,created_at,metadata";
|
|
1609
|
+
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";
|
|
1610
|
+
function projectStays(value) {
|
|
1611
|
+
if (!Array.isArray(value)) return [];
|
|
1612
|
+
const stays = [];
|
|
1613
|
+
for (const item of value) {
|
|
1614
|
+
if (item === null || typeof item !== "object") continue;
|
|
1615
|
+
const joined = item["joined_at"];
|
|
1616
|
+
const left = item["left_at"];
|
|
1617
|
+
if (typeof joined !== "string" || joined.length === 0) continue;
|
|
1618
|
+
stays.push({ joinedAt: joined, leftAt: typeof left === "string" && left.length > 0 ? left : null });
|
|
1619
|
+
}
|
|
1620
|
+
return stays;
|
|
1528
1621
|
}
|
|
1529
|
-
function
|
|
1530
|
-
|
|
1622
|
+
function text(row, key) {
|
|
1623
|
+
const value = row[key];
|
|
1624
|
+
return typeof value === "string" && value.length > 0 ? value : null;
|
|
1531
1625
|
}
|
|
1532
|
-
function
|
|
1533
|
-
const
|
|
1534
|
-
if (
|
|
1535
|
-
|
|
1536
|
-
for (const raw of Array.isArray(row["options"]) ? row["options"] : []) {
|
|
1537
|
-
const option = obj(raw);
|
|
1538
|
-
const optionId = option === null ? null : str(option["id"]);
|
|
1539
|
-
if (option === null || optionId === null) continue;
|
|
1540
|
-
const votes = num(option["votes"]);
|
|
1541
|
-
options.push({
|
|
1542
|
-
id: optionId,
|
|
1543
|
-
text: str(option["text"]) ?? "",
|
|
1544
|
-
...votes !== null ? { votes } : {},
|
|
1545
|
-
...Array.isArray(option["voters"]) ? { voters: strings(option["voters"]) } : {}
|
|
1546
|
-
});
|
|
1626
|
+
function requireText(row, key, operation) {
|
|
1627
|
+
const value = text(row, key);
|
|
1628
|
+
if (value === null) {
|
|
1629
|
+
throw invalidResponse(operation, `the row is missing "${key}"`);
|
|
1547
1630
|
}
|
|
1548
|
-
|
|
1549
|
-
return {
|
|
1550
|
-
id,
|
|
1551
|
-
question: str(row["question"]) ?? "",
|
|
1552
|
-
options,
|
|
1553
|
-
multiple: row["multiple"] === true,
|
|
1554
|
-
anonymous: row["anonymous"] === true,
|
|
1555
|
-
state: state === "live" || state === "closed" ? state : "draft",
|
|
1556
|
-
resultsShared: row["results_shared"] === true,
|
|
1557
|
-
voterCount: num(row["voter_count"]),
|
|
1558
|
-
myVote: strings(row["my_vote"]),
|
|
1559
|
-
createdByName: str(row["created_by_name"]) ?? "",
|
|
1560
|
-
createdAt: str(row["created_at"])
|
|
1561
|
-
};
|
|
1631
|
+
return value;
|
|
1562
1632
|
}
|
|
1563
|
-
function
|
|
1564
|
-
const
|
|
1565
|
-
|
|
1566
|
-
const state = str(row["state"]);
|
|
1567
|
-
return {
|
|
1568
|
-
id,
|
|
1569
|
-
text: str(row["text"]) ?? "",
|
|
1570
|
-
askerName: str(row["asker_name"]) ?? "",
|
|
1571
|
-
anonymous: row["anonymous"] === true,
|
|
1572
|
-
state: state === "answered" || state === "dismissed" ? state : "open",
|
|
1573
|
-
answer: str(row["answer"]),
|
|
1574
|
-
answeredByName: str(row["answered_by_name"]),
|
|
1575
|
-
upvotes: num(row["upvotes"]) ?? 0,
|
|
1576
|
-
upvotedByMe: row["upvoted_by_me"] === true,
|
|
1577
|
-
mine: row["mine"] === true,
|
|
1578
|
-
createdAt: str(row["created_at"])
|
|
1579
|
-
};
|
|
1633
|
+
function bool(row, key, fallback) {
|
|
1634
|
+
const value = row[key];
|
|
1635
|
+
return typeof value === "boolean" ? value : fallback;
|
|
1580
1636
|
}
|
|
1581
|
-
function
|
|
1582
|
-
const
|
|
1583
|
-
if (
|
|
1584
|
-
|
|
1585
|
-
for (const raw of row["points"]) {
|
|
1586
|
-
if (Array.isArray(raw) && typeof raw[0] === "number" && typeof raw[1] === "number") {
|
|
1587
|
-
points.push([raw[0], raw[1]]);
|
|
1588
|
-
}
|
|
1589
|
-
}
|
|
1590
|
-
return {
|
|
1591
|
-
id,
|
|
1592
|
-
points,
|
|
1593
|
-
color: str(row["color"]) ?? "#111827",
|
|
1594
|
-
width: num(row["width"]) ?? 3,
|
|
1595
|
-
tool: row["tool"] === "highlighter" ? "highlighter" : "pen",
|
|
1596
|
-
by: str(row["by"]) ?? "",
|
|
1597
|
-
byName: str(row["by_name"]) ?? ""
|
|
1598
|
-
};
|
|
1637
|
+
function stringArray(row, key) {
|
|
1638
|
+
const value = row[key];
|
|
1639
|
+
if (!Array.isArray(value)) return [];
|
|
1640
|
+
return value.filter((entry) => typeof entry === "string");
|
|
1599
1641
|
}
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1642
|
+
var CALL_STATES = [
|
|
1643
|
+
"idle",
|
|
1644
|
+
"dialing",
|
|
1645
|
+
"ringing",
|
|
1646
|
+
"connecting",
|
|
1647
|
+
"active",
|
|
1648
|
+
"declined",
|
|
1649
|
+
"missed",
|
|
1650
|
+
"busy",
|
|
1651
|
+
"cancelled",
|
|
1652
|
+
"ended",
|
|
1653
|
+
"failed"
|
|
1654
|
+
];
|
|
1655
|
+
function callState(value) {
|
|
1656
|
+
return CALL_STATES.includes(value) ? value : "failed";
|
|
1605
1657
|
}
|
|
1606
|
-
function
|
|
1607
|
-
const
|
|
1608
|
-
|
|
1609
|
-
const rooms = [];
|
|
1610
|
-
for (const raw of Array.isArray(row["rooms"]) ? row["rooms"] : []) {
|
|
1611
|
-
const room = obj(raw);
|
|
1612
|
-
const roomId = room === null ? null : str(room["id"]);
|
|
1613
|
-
if (room === null || roomId === null) continue;
|
|
1614
|
-
rooms.push({ id: roomId, name: str(room["name"]) ?? roomId, identities: strings(room["identities"]) });
|
|
1615
|
-
}
|
|
1658
|
+
function projectSegment(row) {
|
|
1659
|
+
const operation = "meet.projectSegment";
|
|
1660
|
+
const id = requireText(row, "id", operation);
|
|
1616
1661
|
return {
|
|
1617
1662
|
id,
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1663
|
+
meetingId: asMeetingId(requireText(row, "meeting_id", operation)),
|
|
1664
|
+
identity: asParticipantIdentity(text(row, "identity") ?? "unknown"),
|
|
1665
|
+
// A nameless line is a line nobody can act on. The identity is a worse
|
|
1666
|
+
// name than a real one and a far better one than an empty string.
|
|
1667
|
+
speaker: text(row, "speaker") ?? text(row, "identity") ?? "Someone",
|
|
1668
|
+
text: text(row, "text") ?? "",
|
|
1669
|
+
startedAt: text(row, "started_at") ?? (/* @__PURE__ */ new Date(0)).toISOString(),
|
|
1670
|
+
// The table holds finals only; a row that somehow says otherwise is still
|
|
1671
|
+
// a durable record and is rendered as one.
|
|
1672
|
+
final: bool(row, "is_final", true),
|
|
1673
|
+
source: "record",
|
|
1674
|
+
// The worker publishes the caption under THIS row's id (`lk.segment_id` =
|
|
1675
|
+
// the durable segment id), which is what lets a record replace the exact
|
|
1676
|
+
// caption it supersedes instead of doubling the line (D16).
|
|
1677
|
+
captionId: id
|
|
1626
1678
|
};
|
|
1627
1679
|
}
|
|
1628
|
-
function
|
|
1629
|
-
|
|
1680
|
+
function projectNote(row) {
|
|
1681
|
+
const operation = "meet.projectNote";
|
|
1682
|
+
const kind = text(row, "kind");
|
|
1683
|
+
const rawMetadata = row["metadata"];
|
|
1684
|
+
const metadata = typeof rawMetadata === "object" && rawMetadata !== null && !Array.isArray(rawMetadata) ? rawMetadata : {};
|
|
1685
|
+
const assignee = metadata["assignee"];
|
|
1686
|
+
const assigneeRow = typeof assignee === "object" && assignee !== null && !Array.isArray(assignee) ? assignee : {};
|
|
1687
|
+
const windowValue = metadata["window"];
|
|
1688
|
+
const windowRow = typeof windowValue === "object" && windowValue !== null && !Array.isArray(windowValue) ? windowValue : {};
|
|
1689
|
+
const assigneeUserId = text(row, "assignee_user_id");
|
|
1690
|
+
const displayName = assigneeRow["display_name"];
|
|
1630
1691
|
return {
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1692
|
+
id: requireText(row, "id", operation),
|
|
1693
|
+
meetingId: asMeetingId(requireText(row, "meeting_id", operation)),
|
|
1694
|
+
text: (text(row, "text") ?? "").trim(),
|
|
1695
|
+
createdAt: text(row, "created_at") ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
1696
|
+
// An unknown kind is a NOTE, never dropped: a row the database accepted
|
|
1697
|
+
// is a row a person wrote down, whatever a future migration calls it.
|
|
1698
|
+
kind: kind === "decision" || kind === "action-item" ? kind : "note",
|
|
1699
|
+
assigneeUserId: assigneeUserId === null ? null : asUserId(assigneeUserId),
|
|
1700
|
+
// D18: a guest never gets an `assignee_user_id` (the FK targets
|
|
1701
|
+
// `auth.users`), so their name lives in the metadata and is read here —
|
|
1702
|
+
// an action item whose owner has no name is an action item nobody owns.
|
|
1703
|
+
assigneeDisplayName: typeof displayName === "string" && displayName.length > 0 ? displayName : null,
|
|
1704
|
+
role: metadata["role"] === "summary" ? "summary" : null,
|
|
1705
|
+
window: typeof windowRow["index"] === "number" ? windowRow["index"] : null,
|
|
1706
|
+
// D20. `superseded_by` is in `NOTE_PUBLIC_METADATA_KEYS` server-side, so a
|
|
1707
|
+
// session-less guest's room-borne copy carries it too — otherwise the guest
|
|
1708
|
+
// would be the one lane still reading a decision twice.
|
|
1709
|
+
supersededBy: typeof metadata["superseded_by"] === "string" && metadata["superseded_by"].length > 0 ? metadata["superseded_by"] : null,
|
|
1710
|
+
// THE DATABASE ROW. `core/room-events.ts` marks its copy of the same row
|
|
1711
|
+
// `"room"`, and the store's precedence rule reads this field (MRI-A12).
|
|
1712
|
+
source: "record"
|
|
1641
1713
|
};
|
|
1642
1714
|
}
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1715
|
+
var RSVP_STATES = ["needs_action", "accepted", "declined", "tentative"];
|
|
1716
|
+
var OCCURRENCE_STATES = ["scheduled", "moved", "cancelled"];
|
|
1717
|
+
function rsvpState(value) {
|
|
1718
|
+
return RSVP_STATES.includes(value) ? value : "needs_action";
|
|
1719
|
+
}
|
|
1720
|
+
function num(row, key) {
|
|
1721
|
+
const value = row[key];
|
|
1722
|
+
return typeof value === "number" ? value : null;
|
|
1723
|
+
}
|
|
1724
|
+
function when(row, key) {
|
|
1725
|
+
const value = row[key];
|
|
1726
|
+
if (value instanceof Date) return value.toISOString();
|
|
1727
|
+
return text(row, key);
|
|
1728
|
+
}
|
|
1729
|
+
function projectInvitee(row) {
|
|
1730
|
+
const operation = "meet.projectInvitee";
|
|
1731
|
+
const userId = text(row, "invitee_user_id");
|
|
1649
1732
|
return {
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1733
|
+
id: requireText(row, "id", operation),
|
|
1734
|
+
meetingId: asMeetingId(requireText(row, "meeting_id", operation)),
|
|
1735
|
+
userId: userId === null ? null : asUserId(userId),
|
|
1736
|
+
email: text(row, "email"),
|
|
1737
|
+
displayName: text(row, "display_name"),
|
|
1738
|
+
role: text(row, "role") === "cohost" ? "cohost" : "invitee",
|
|
1739
|
+
rsvpState: rsvpState(text(row, "rsvp_state")),
|
|
1740
|
+
respondedAt: when(row, "responded_at"),
|
|
1741
|
+
rsvpNote: text(row, "rsvp_note"),
|
|
1742
|
+
lastSentSequence: num(row, "last_sent_sequence"),
|
|
1743
|
+
lastSentAt: when(row, "last_sent_at")
|
|
1658
1744
|
};
|
|
1659
1745
|
}
|
|
1660
|
-
function
|
|
1661
|
-
const
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1746
|
+
function projectOccurrence(row) {
|
|
1747
|
+
const operation = "meet.projectOccurrence";
|
|
1748
|
+
const state = text(row, "state");
|
|
1749
|
+
return {
|
|
1750
|
+
meetingId: asMeetingId(requireText(row, "meeting_id", operation)),
|
|
1751
|
+
originalStart: when(row, "original_start") ?? requireText(row, "original_start", operation),
|
|
1752
|
+
occurrenceStart: when(row, "occurrence_start") ?? requireText(row, "occurrence_start", operation),
|
|
1753
|
+
durationMinutes: num(row, "duration_minutes"),
|
|
1754
|
+
// An unknown state is shown as scheduled, never dropped: the row is real.
|
|
1755
|
+
state: OCCURRENCE_STATES.includes(state) ? state : "scheduled",
|
|
1756
|
+
sequence: num(row, "sequence") ?? 0
|
|
1757
|
+
};
|
|
1666
1758
|
}
|
|
1667
|
-
function
|
|
1668
|
-
const
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1759
|
+
function projectUpcoming(row) {
|
|
1760
|
+
const operation = "meet.projectUpcoming";
|
|
1761
|
+
const kind = text(row, "kind");
|
|
1762
|
+
const myRole = text(row, "my_role");
|
|
1763
|
+
const myRsvp = text(row, "my_rsvp");
|
|
1764
|
+
return {
|
|
1765
|
+
...projectOccurrence(row),
|
|
1766
|
+
organizationId: asOrganizationId(requireText(row, "organization_id", operation)),
|
|
1767
|
+
title: text(row, "title") ?? "Meeting",
|
|
1768
|
+
slug: requireText(row, "slug", operation),
|
|
1769
|
+
kind: kind === "scheduled" || kind === "recurring" ? kind : "instant",
|
|
1770
|
+
hostUserId: asUserId(requireText(row, "host_user_id", operation)),
|
|
1771
|
+
timeZone: text(row, "time_zone") ?? "UTC",
|
|
1772
|
+
meetingCancelled: bool(row, "meeting_cancelled", false),
|
|
1773
|
+
myRsvp: myRsvp === null ? null : rsvpState(myRsvp),
|
|
1774
|
+
myRole: myRole === "host" || myRole === "cohost" || myRole === "invitee" ? myRole : null
|
|
1775
|
+
};
|
|
1776
|
+
}
|
|
1777
|
+
function projectOverride(row) {
|
|
1778
|
+
const operation = "meet.projectOverride";
|
|
1779
|
+
return {
|
|
1780
|
+
id: requireText(row, "id", operation),
|
|
1781
|
+
meetingId: asMeetingId(requireText(row, "meeting_id", operation)),
|
|
1782
|
+
originalStart: when(row, "original_start") ?? requireText(row, "original_start", operation),
|
|
1783
|
+
state: text(row, "state") === "moved" ? "moved" : "cancelled",
|
|
1784
|
+
newStart: when(row, "new_start"),
|
|
1785
|
+
newDurationMinutes: num(row, "new_duration_minutes"),
|
|
1786
|
+
reason: text(row, "reason"),
|
|
1787
|
+
calendarSequence: num(row, "calendar_sequence") ?? 0,
|
|
1788
|
+
deletedAt: when(row, "deleted_at")
|
|
1789
|
+
};
|
|
1790
|
+
}
|
|
1791
|
+
function projectRsvpInvitation(body) {
|
|
1792
|
+
const operation = "meet.projectRsvpInvitation";
|
|
1793
|
+
if (typeof body !== "object" || body === null) {
|
|
1794
|
+
throw invalidResponse(operation, "the RSVP door returned no object");
|
|
1698
1795
|
}
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1796
|
+
const row = body;
|
|
1797
|
+
if (row["ok"] !== true) {
|
|
1798
|
+
return {
|
|
1799
|
+
ok: false,
|
|
1800
|
+
message: text(row, "message") ?? "This invitation link is no longer valid \u2014 ask the host to send it again."
|
|
1801
|
+
};
|
|
1703
1802
|
}
|
|
1704
|
-
|
|
1705
|
-
}
|
|
1706
|
-
function without(collab, ids) {
|
|
1707
|
-
const gone = new Set(ids);
|
|
1803
|
+
const meeting = row["meeting"] ?? {};
|
|
1804
|
+
const invitee = row["invitee"] ?? {};
|
|
1708
1805
|
return {
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1806
|
+
ok: true,
|
|
1807
|
+
meeting: {
|
|
1808
|
+
title: text(meeting, "title") ?? "Meeting",
|
|
1809
|
+
slug: requireText(meeting, "slug", operation),
|
|
1810
|
+
scheduledFor: when(meeting, "scheduled_for"),
|
|
1811
|
+
durationMinutes: num(meeting, "duration_minutes"),
|
|
1812
|
+
timeZone: text(meeting, "time_zone") ?? "UTC",
|
|
1813
|
+
agenda: text(meeting, "agenda"),
|
|
1814
|
+
recurrenceRule: text(meeting, "recurrence_rule"),
|
|
1815
|
+
cancelledAt: when(meeting, "cancelled_at"),
|
|
1816
|
+
cancellationReason: text(meeting, "cancellation_reason"),
|
|
1817
|
+
endedAt: when(meeting, "ended_at")
|
|
1818
|
+
},
|
|
1819
|
+
invitee: {
|
|
1820
|
+
displayName: text(invitee, "display_name"),
|
|
1821
|
+
email: text(invitee, "email"),
|
|
1822
|
+
rsvpState: rsvpState(text(invitee, "rsvp_state")),
|
|
1823
|
+
respondedAt: when(invitee, "responded_at")
|
|
1824
|
+
}
|
|
1713
1825
|
};
|
|
1714
1826
|
}
|
|
1715
|
-
function
|
|
1716
|
-
const
|
|
1717
|
-
if (
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1827
|
+
function settingsPatch(settings) {
|
|
1828
|
+
const patch = {};
|
|
1829
|
+
if (settings.lobbyEnabled !== void 0) patch["lobby_enabled"] = settings.lobbyEnabled;
|
|
1830
|
+
if (settings.recordingPolicy !== void 0) patch["recording_policy"] = settings.recordingPolicy;
|
|
1831
|
+
if (settings.aiEnabled !== void 0) patch["ai_enabled"] = settings.aiEnabled;
|
|
1832
|
+
if (settings.joinBeforeHost !== void 0) patch["join_before_host"] = settings.joinBeforeHost;
|
|
1833
|
+
return patch;
|
|
1834
|
+
}
|
|
1835
|
+
function changesPatch(changes) {
|
|
1836
|
+
const patch = settingsPatch(changes);
|
|
1837
|
+
if (changes.title !== void 0) patch["title"] = changes.title;
|
|
1838
|
+
if (changes.agenda !== void 0) patch["agenda"] = changes.agenda;
|
|
1839
|
+
if (changes.timeZone !== void 0) patch["time_zone"] = changes.timeZone;
|
|
1840
|
+
if (changes.scheduledFor !== void 0) patch["scheduled_for"] = changes.scheduledFor;
|
|
1841
|
+
if (changes.scheduledDurationMinutes !== void 0) {
|
|
1842
|
+
patch["scheduled_duration_minutes"] = changes.scheduledDurationMinutes;
|
|
1843
|
+
}
|
|
1844
|
+
if (changes.recurrenceRule !== void 0) patch["recurrence_rule"] = changes.recurrenceRule;
|
|
1845
|
+
if (changes.keepExceptions !== void 0) patch["keep_exceptions"] = changes.keepExceptions;
|
|
1846
|
+
return patch;
|
|
1847
|
+
}
|
|
1848
|
+
function createMeetRepository(options) {
|
|
1849
|
+
const db = options.client.schema(MEET_SCHEMA);
|
|
1850
|
+
async function guardVoid(operation, run) {
|
|
1851
|
+
let response = await run();
|
|
1852
|
+
if (response.error !== null) {
|
|
1853
|
+
const normalized = normalizeMeetError(response.error, operation);
|
|
1854
|
+
if (normalized.code === "session-unavailable" && options.session !== void 0) {
|
|
1855
|
+
await options.session();
|
|
1856
|
+
response = await run();
|
|
1724
1857
|
}
|
|
1858
|
+
if (response.error !== null) throw normalizeMeetError(response.error, operation);
|
|
1725
1859
|
}
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1860
|
+
}
|
|
1861
|
+
async function guard(operation, run) {
|
|
1862
|
+
let response = await run();
|
|
1863
|
+
if (response.error !== null) {
|
|
1864
|
+
const normalized = normalizeMeetError(response.error, operation);
|
|
1865
|
+
if (normalized.code === "session-unavailable" && options.session !== void 0) {
|
|
1866
|
+
await options.session();
|
|
1867
|
+
response = await run();
|
|
1730
1868
|
}
|
|
1869
|
+
if (response.error !== null) throw normalizeMeetError(response.error, operation);
|
|
1731
1870
|
}
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
next = { ...next, moveRequest: { roomId: str(move["room_id"]), at: context.now } };
|
|
1871
|
+
if (response.data === null) {
|
|
1872
|
+
throw invalidResponse(operation, "the database returned no row");
|
|
1735
1873
|
}
|
|
1736
|
-
return
|
|
1737
|
-
}
|
|
1738
|
-
if (Array.isArray(payload["removed"])) {
|
|
1739
|
-
return { next: without(collab, strings(payload["removed"])) };
|
|
1740
|
-
}
|
|
1741
|
-
if (Array.isArray(payload["hidden"])) {
|
|
1742
|
-
return context.isHost ? { next: null, dropped: "hosts keep dismissed questions" } : { next: without(collab, strings(payload["hidden"])) };
|
|
1743
|
-
}
|
|
1744
|
-
if (payload["cleared"] === "whiteboard") {
|
|
1745
|
-
return { next: { ...collab, strokes: [] } };
|
|
1874
|
+
return response.data;
|
|
1746
1875
|
}
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
index += 1;
|
|
1876
|
+
function projectInvite(row) {
|
|
1877
|
+
const operation = "meet.projectInvite";
|
|
1878
|
+
const mode = text(row, "mode");
|
|
1879
|
+
return {
|
|
1880
|
+
id: asCallId(requireText(row, "id", operation)),
|
|
1881
|
+
organizationId: asOrganizationId(requireText(row, "organization_id", operation)),
|
|
1882
|
+
roomName: asRoomName(requireText(row, "room_name", operation)),
|
|
1883
|
+
mode: mode === "video" ? "video" : "audio",
|
|
1884
|
+
callerUserId: asUserId(requireText(row, "caller_user_id", operation)),
|
|
1885
|
+
callerName: text(row, "caller_name") ?? "Someone",
|
|
1886
|
+
callerAvatarUrl: text(row, "caller_avatar_url"),
|
|
1887
|
+
calleeUserIds: stringArray(row, "callee_ids").map(asUserId),
|
|
1888
|
+
conversationId: text(row, "conversation_id"),
|
|
1889
|
+
createdAt: requireText(row, "created_at", operation),
|
|
1890
|
+
expiresAt: text(row, "expires_at") ?? requireText(row, "created_at", operation),
|
|
1891
|
+
state: callState(text(row, "state")),
|
|
1892
|
+
declineMessage: text(row, "decline_message")
|
|
1893
|
+
};
|
|
1766
1894
|
}
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
}
|
|
1774
|
-
function defaultCaptionLanguage(settings, viewerLanguage) {
|
|
1775
|
-
const configured = settings?.captionTranslationLanguage.trim() ?? "";
|
|
1776
|
-
if (configured.length > 0) return configured;
|
|
1777
|
-
const own = (viewerLanguage ?? "").trim();
|
|
1778
|
-
return own.length > 0 ? own : "en";
|
|
1779
|
-
}
|
|
1780
|
-
var CAPTION_LANGUAGES = [
|
|
1781
|
-
{ tag: "en", name: "English" },
|
|
1782
|
-
{ tag: "es", name: "Spanish" },
|
|
1783
|
-
{ tag: "fr", name: "French" },
|
|
1784
|
-
{ tag: "de", name: "German" },
|
|
1785
|
-
{ tag: "it", name: "Italian" },
|
|
1786
|
-
{ tag: "pt", name: "Portuguese" },
|
|
1787
|
-
{ tag: "nl", name: "Dutch" },
|
|
1788
|
-
{ tag: "pl", name: "Polish" },
|
|
1789
|
-
{ tag: "ru", name: "Russian" },
|
|
1790
|
-
{ tag: "uk", name: "Ukrainian" },
|
|
1791
|
-
{ tag: "tr", name: "Turkish" },
|
|
1792
|
-
{ tag: "ar", name: "Arabic" },
|
|
1793
|
-
{ tag: "he", name: "Hebrew" },
|
|
1794
|
-
{ tag: "fa", name: "Persian" },
|
|
1795
|
-
{ tag: "hi", name: "Hindi" },
|
|
1796
|
-
{ tag: "bn", name: "Bengali" },
|
|
1797
|
-
{ tag: "zh", name: "Chinese" },
|
|
1798
|
-
{ tag: "ja", name: "Japanese" },
|
|
1799
|
-
{ tag: "ko", name: "Korean" },
|
|
1800
|
-
{ tag: "vi", name: "Vietnamese" },
|
|
1801
|
-
{ tag: "th", name: "Thai" },
|
|
1802
|
-
{ tag: "id", name: "Indonesian" },
|
|
1803
|
-
{ tag: "sv", name: "Swedish" }
|
|
1804
|
-
];
|
|
1805
|
-
async function loadMeetingActivities(api, meetingId, meetingPass = null) {
|
|
1806
|
-
let response;
|
|
1807
|
-
try {
|
|
1808
|
-
response = await api.request({
|
|
1809
|
-
path: `${MEET_ROUTES.collab}?meeting_id=${encodeURIComponent(meetingId)}`,
|
|
1810
|
-
method: "GET",
|
|
1811
|
-
operation: "meet.loadMeetingActivities",
|
|
1812
|
-
requireAuth: meetingPass === null,
|
|
1813
|
-
...meetingPass !== null ? { headers: { [MEETING_PASS_HEADER]: meetingPass } } : {}
|
|
1814
|
-
});
|
|
1815
|
-
} catch (error) {
|
|
1895
|
+
function projectMeeting(row) {
|
|
1896
|
+
const operation = "meet.projectMeeting";
|
|
1897
|
+
const kind = text(row, "kind");
|
|
1898
|
+
const policy = text(row, "recording_policy");
|
|
1899
|
+
const duration = row["scheduled_duration_minutes"];
|
|
1900
|
+
const metadata = row["metadata"];
|
|
1816
1901
|
return {
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1902
|
+
id: asMeetingId(requireText(row, "id", operation)),
|
|
1903
|
+
organizationId: asOrganizationId(requireText(row, "organization_id", operation)),
|
|
1904
|
+
roomName: asRoomName(requireText(row, "room_name", operation)),
|
|
1905
|
+
slug: requireText(row, "slug", operation),
|
|
1906
|
+
title: text(row, "title") ?? "Meeting",
|
|
1907
|
+
kind: kind === "scheduled" || kind === "recurring" ? kind : "instant",
|
|
1908
|
+
hostUserId: asUserId(requireText(row, "host_user_id", operation)),
|
|
1909
|
+
scheduledFor: text(row, "scheduled_for"),
|
|
1910
|
+
scheduledDurationMinutes: typeof duration === "number" ? duration : null,
|
|
1911
|
+
startedAt: text(row, "started_at"),
|
|
1912
|
+
endedAt: text(row, "ended_at"),
|
|
1913
|
+
locked: bool(row, "locked", false),
|
|
1914
|
+
lobbyEnabled: bool(row, "lobby_enabled", true),
|
|
1915
|
+
recordingPolicy: policy === "disabled" || policy === "always-on" ? policy : "host-controlled",
|
|
1916
|
+
aiEnabled: bool(row, "ai_enabled", true),
|
|
1917
|
+
metadata: typeof metadata === "object" && metadata !== null && !Array.isArray(metadata) ? metadata : {},
|
|
1918
|
+
timeZone: text(row, "time_zone") ?? "UTC",
|
|
1919
|
+
agenda: text(row, "agenda"),
|
|
1920
|
+
recurrenceRule: text(row, "recurrence_rule"),
|
|
1921
|
+
joinBeforeHost: bool(row, "join_before_host", true),
|
|
1922
|
+
cancelledAt: text(row, "cancelled_at"),
|
|
1923
|
+
cancellationReason: text(row, "cancellation_reason"),
|
|
1924
|
+
deletedAt: text(row, "deleted_at"),
|
|
1925
|
+
calendarSequence: typeof row["calendar_sequence"] === "number" ? row["calendar_sequence"] : 0,
|
|
1926
|
+
version: typeof row["version"] === "number" ? row["version"] : 1
|
|
1822
1927
|
};
|
|
1823
1928
|
}
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1929
|
+
function projectRecording(row) {
|
|
1930
|
+
const operation = "meet.projectRecording";
|
|
1931
|
+
const state = text(row, "state");
|
|
1932
|
+
const known = [
|
|
1933
|
+
"idle",
|
|
1934
|
+
"starting",
|
|
1935
|
+
"recording",
|
|
1936
|
+
"stopping",
|
|
1937
|
+
"processing",
|
|
1938
|
+
"available",
|
|
1939
|
+
"expired",
|
|
1940
|
+
"failed"
|
|
1941
|
+
];
|
|
1832
1942
|
return {
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1943
|
+
id: requireText(row, "id", operation),
|
|
1944
|
+
meetingId: asMeetingId(requireText(row, "meeting_id", operation)),
|
|
1945
|
+
state: known.includes(state) ? state : "failed",
|
|
1946
|
+
startedAt: text(row, "started_at"),
|
|
1947
|
+
endedAt: text(row, "ended_at"),
|
|
1948
|
+
startedBy: text(row, "started_by") === null ? null : asUserId(text(row, "started_by")),
|
|
1949
|
+
// A PLATFORM file id, never a provider URL (R3). A recording that only
|
|
1950
|
+
// exists as a LiveKit URL is orphaned the day the room is garbage
|
|
1951
|
+
// collected, which is the whole reason this column is not `url`.
|
|
1952
|
+
fileId: text(row, "file_id"),
|
|
1953
|
+
failureReason: text(row, "failure_reason"),
|
|
1954
|
+
retention: projectRecordingRetention(row["metadata"])
|
|
1838
1955
|
};
|
|
1839
1956
|
}
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
invitees: "meet_invitees",
|
|
1854
|
-
/** Wave 1 (0.8): one occurrence of a series cancelled or moved. */
|
|
1855
|
-
occurrenceOverrides: "meet_occurrence_overrides"
|
|
1856
|
-
};
|
|
1857
|
-
var RPCS = {
|
|
1858
|
-
/** Atomic: create the invite row and stamp its expiry in one statement. */
|
|
1859
|
-
createCallInvite: "meet_create_call_invite",
|
|
1860
|
-
/** Atomic settle. Refuses a transition out of a terminal state. */
|
|
1861
|
-
settleCallInvite: "meet_settle_call_invite",
|
|
1862
|
-
/** Every invite still ringing this user — the reconnect backfill door. */
|
|
1863
|
-
pendingCallInvites: "meet_pending_call_invites",
|
|
1864
|
-
/** Instant meeting, or the durable one behind a recurring link. */
|
|
1865
|
-
getOrCreateMeeting: "meet_get_or_create_meeting",
|
|
1866
|
-
/** Resolve a durable public link, before/during/after its scheduled time. */
|
|
1867
|
-
meetingBySlug: "meet_meeting_by_slug",
|
|
1868
|
-
admit: "meet_admit_participant",
|
|
1869
|
-
setLock: "meet_set_lock",
|
|
1870
|
-
recordConsent: "meet_record_consent",
|
|
1871
|
-
/*
|
|
1872
|
-
* ── MEET WAVE 1 — management (0.8). Every write is a SECURITY DEFINER door
|
|
1873
|
-
* authorized by the meeting's host or a real iam grant (editor to change,
|
|
1874
|
-
* admin = co-host to archive or name a co-host) — never an organization
|
|
1875
|
-
* match. After any of these writes, call `announceMeeting` so the server
|
|
1876
|
-
* sends invitations / updates / cancellations and re-queues reminders.
|
|
1877
|
-
*/
|
|
1878
|
-
/** Schedule with every setting at create; unset settings come from the knobs. */
|
|
1879
|
-
scheduleMeeting: "meet_schedule_meeting",
|
|
1880
|
-
/** Edit / reschedule — a JSON patch with a version compare-and-swap. */
|
|
1881
|
-
updateMeeting: "meet_update_meeting",
|
|
1882
|
-
/** Cancel is a state (`cancelled_at`); the link keeps resolving. */
|
|
1883
|
-
cancelMeeting: "meet_cancel_meeting",
|
|
1884
|
-
/** Archive (soft delete) — the invitations and exceptions cascade. */
|
|
1885
|
-
archiveMeeting: "meet_archive_meeting",
|
|
1886
|
-
restoreMeeting: "meet_restore_meeting",
|
|
1887
|
-
/** Cancel, move or restore ONE occurrence of a series by its original start. */
|
|
1888
|
-
setOccurrence: "meet_set_occurrence",
|
|
1889
|
-
addInvitees: "meet_add_invitees",
|
|
1890
|
-
setInviteeRole: "meet_set_invitee_role",
|
|
1891
|
-
removeInvitee: "meet_remove_invitee",
|
|
1892
|
-
/** A signed-in invitee answers for themselves. */
|
|
1893
|
-
respond: "meet_respond",
|
|
1894
|
-
/** The emailed RSVP link (no account): read and answer. */
|
|
1895
|
-
invitationByToken: "meet_invitation_by_token",
|
|
1896
|
-
respondByToken: "meet_respond_by_token",
|
|
1897
|
-
/** One meeting's occurrences between two instants (INVOKER — the reader's RLS). */
|
|
1898
|
-
meetingOccurrences: "meet_meeting_occurrences",
|
|
1899
|
-
/** Every occurrence the reader can see between two instants — the Upcoming list. */
|
|
1900
|
-
occurrencesBetween: "meet_occurrences_between",
|
|
1901
|
-
/*
|
|
1902
|
-
* ── MEET WAVE 3 — after the meeting. Both INVOKER (the reader's RLS) and both
|
|
1903
|
-
* scoped to the reader's OWN meetings (host, invitee, attended) — RLS is the
|
|
1904
|
-
* ceiling, never the view.
|
|
1905
|
-
*/
|
|
1906
|
-
/** The recordings library. */
|
|
1907
|
-
myRecordings: "meet_my_recordings",
|
|
1908
|
-
/** Titles, agendas, current notes and final transcript lines; snippets mark hits with ⟦ ⟧. */
|
|
1909
|
-
search: "meet_search"
|
|
1910
|
-
};
|
|
1911
|
-
var VOID_RPCS = [RPCS.admit, RPCS.recordConsent];
|
|
1912
|
-
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";
|
|
1913
|
-
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";
|
|
1914
|
-
var RECORDING_COLUMNS = "id,meeting_id,state,started_at,ended_at,started_by,file_id,failure_reason";
|
|
1915
|
-
var SEGMENT_COLUMNS = "id,meeting_id,identity,speaker,text,started_at,is_final,metadata";
|
|
1916
|
-
var NOTE_COLUMNS = "id,meeting_id,text,kind,assignee_user_id,created_at,metadata";
|
|
1917
|
-
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";
|
|
1918
|
-
function projectStays(value) {
|
|
1919
|
-
if (!Array.isArray(value)) return [];
|
|
1920
|
-
const stays = [];
|
|
1921
|
-
for (const item of value) {
|
|
1922
|
-
if (item === null || typeof item !== "object") continue;
|
|
1923
|
-
const joined = item["joined_at"];
|
|
1924
|
-
const left = item["left_at"];
|
|
1925
|
-
if (typeof joined !== "string" || joined.length === 0) continue;
|
|
1926
|
-
stays.push({ joinedAt: joined, leftAt: typeof left === "string" && left.length > 0 ? left : null });
|
|
1957
|
+
function projectRecordingRetention(value) {
|
|
1958
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return null;
|
|
1959
|
+
const retention = value["retention"];
|
|
1960
|
+
if (typeof retention !== "object" || retention === null || Array.isArray(retention)) return null;
|
|
1961
|
+
const row = retention;
|
|
1962
|
+
const phase = row["phase"];
|
|
1963
|
+
const operationId = row["operation_id"];
|
|
1964
|
+
const policyId = row["policy_id"];
|
|
1965
|
+
const policyUpdatedAt = row["policy_updated_at"];
|
|
1966
|
+
const dueAt = row["due_at"];
|
|
1967
|
+
const expiredAt = row["expired_at"];
|
|
1968
|
+
if (phase !== "warning" && phase !== "expired" || typeof operationId !== "string" || typeof policyId !== "string" || typeof policyUpdatedAt !== "string" || typeof dueAt !== "string" || expiredAt !== null && typeof expiredAt !== "string") return null;
|
|
1969
|
+
return { operationId, policyId, policyUpdatedAt, phase, dueAt, expiredAt };
|
|
1927
1970
|
}
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
function stringArray(row, key) {
|
|
1946
|
-
const value = row[key];
|
|
1947
|
-
if (!Array.isArray(value)) return [];
|
|
1948
|
-
return value.filter((entry) => typeof entry === "string");
|
|
1949
|
-
}
|
|
1950
|
-
var CALL_STATES = [
|
|
1951
|
-
"idle",
|
|
1952
|
-
"dialing",
|
|
1953
|
-
"ringing",
|
|
1954
|
-
"connecting",
|
|
1955
|
-
"active",
|
|
1956
|
-
"declined",
|
|
1957
|
-
"missed",
|
|
1958
|
-
"busy",
|
|
1959
|
-
"cancelled",
|
|
1960
|
-
"ended",
|
|
1961
|
-
"failed"
|
|
1962
|
-
];
|
|
1963
|
-
function callState(value) {
|
|
1964
|
-
return CALL_STATES.includes(value) ? value : "failed";
|
|
1965
|
-
}
|
|
1966
|
-
function projectSegment(row) {
|
|
1967
|
-
const operation = "meet.projectSegment";
|
|
1968
|
-
const id = requireText(row, "id", operation);
|
|
1969
|
-
return {
|
|
1970
|
-
id,
|
|
1971
|
-
meetingId: asMeetingId(requireText(row, "meeting_id", operation)),
|
|
1972
|
-
identity: asParticipantIdentity(text(row, "identity") ?? "unknown"),
|
|
1973
|
-
// A nameless line is a line nobody can act on. The identity is a worse
|
|
1974
|
-
// name than a real one and a far better one than an empty string.
|
|
1975
|
-
speaker: text(row, "speaker") ?? text(row, "identity") ?? "Someone",
|
|
1976
|
-
text: text(row, "text") ?? "",
|
|
1977
|
-
startedAt: text(row, "started_at") ?? (/* @__PURE__ */ new Date(0)).toISOString(),
|
|
1978
|
-
// The table holds finals only; a row that somehow says otherwise is still
|
|
1979
|
-
// a durable record and is rendered as one.
|
|
1980
|
-
final: bool(row, "is_final", true),
|
|
1981
|
-
source: "record",
|
|
1982
|
-
// The worker publishes the caption under THIS row's id (`lk.segment_id` =
|
|
1983
|
-
// the durable segment id), which is what lets a record replace the exact
|
|
1984
|
-
// caption it supersedes instead of doubling the line (D16).
|
|
1985
|
-
captionId: id
|
|
1986
|
-
};
|
|
1987
|
-
}
|
|
1988
|
-
function projectNote(row) {
|
|
1989
|
-
const operation = "meet.projectNote";
|
|
1990
|
-
const kind = text(row, "kind");
|
|
1991
|
-
const rawMetadata = row["metadata"];
|
|
1992
|
-
const metadata = typeof rawMetadata === "object" && rawMetadata !== null && !Array.isArray(rawMetadata) ? rawMetadata : {};
|
|
1993
|
-
const assignee = metadata["assignee"];
|
|
1994
|
-
const assigneeRow = typeof assignee === "object" && assignee !== null && !Array.isArray(assignee) ? assignee : {};
|
|
1995
|
-
const windowValue = metadata["window"];
|
|
1996
|
-
const windowRow = typeof windowValue === "object" && windowValue !== null && !Array.isArray(windowValue) ? windowValue : {};
|
|
1997
|
-
const assigneeUserId = text(row, "assignee_user_id");
|
|
1998
|
-
const displayName = assigneeRow["display_name"];
|
|
1999
|
-
return {
|
|
2000
|
-
id: requireText(row, "id", operation),
|
|
2001
|
-
meetingId: asMeetingId(requireText(row, "meeting_id", operation)),
|
|
2002
|
-
text: (text(row, "text") ?? "").trim(),
|
|
2003
|
-
createdAt: text(row, "created_at") ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
2004
|
-
// An unknown kind is a NOTE, never dropped: a row the database accepted
|
|
2005
|
-
// is a row a person wrote down, whatever a future migration calls it.
|
|
2006
|
-
kind: kind === "decision" || kind === "action-item" ? kind : "note",
|
|
2007
|
-
assigneeUserId: assigneeUserId === null ? null : asUserId(assigneeUserId),
|
|
2008
|
-
// D18: a guest never gets an `assignee_user_id` (the FK targets
|
|
2009
|
-
// `auth.users`), so their name lives in the metadata and is read here —
|
|
2010
|
-
// an action item whose owner has no name is an action item nobody owns.
|
|
2011
|
-
assigneeDisplayName: typeof displayName === "string" && displayName.length > 0 ? displayName : null,
|
|
2012
|
-
role: metadata["role"] === "summary" ? "summary" : null,
|
|
2013
|
-
window: typeof windowRow["index"] === "number" ? windowRow["index"] : null,
|
|
2014
|
-
// D20. `superseded_by` is in `NOTE_PUBLIC_METADATA_KEYS` server-side, so a
|
|
2015
|
-
// session-less guest's room-borne copy carries it too — otherwise the guest
|
|
2016
|
-
// would be the one lane still reading a decision twice.
|
|
2017
|
-
supersededBy: typeof metadata["superseded_by"] === "string" && metadata["superseded_by"].length > 0 ? metadata["superseded_by"] : null,
|
|
2018
|
-
// THE DATABASE ROW. `core/room-events.ts` marks its copy of the same row
|
|
2019
|
-
// `"room"`, and the store's precedence rule reads this field (MRI-A12).
|
|
2020
|
-
source: "record"
|
|
2021
|
-
};
|
|
2022
|
-
}
|
|
2023
|
-
var RSVP_STATES = ["needs_action", "accepted", "declined", "tentative"];
|
|
2024
|
-
var OCCURRENCE_STATES = ["scheduled", "moved", "cancelled"];
|
|
2025
|
-
function rsvpState(value) {
|
|
2026
|
-
return RSVP_STATES.includes(value) ? value : "needs_action";
|
|
2027
|
-
}
|
|
2028
|
-
function num2(row, key) {
|
|
2029
|
-
const value = row[key];
|
|
2030
|
-
return typeof value === "number" ? value : null;
|
|
2031
|
-
}
|
|
2032
|
-
function when(row, key) {
|
|
2033
|
-
const value = row[key];
|
|
2034
|
-
if (value instanceof Date) return value.toISOString();
|
|
2035
|
-
return text(row, key);
|
|
2036
|
-
}
|
|
2037
|
-
function projectInvitee(row) {
|
|
2038
|
-
const operation = "meet.projectInvitee";
|
|
2039
|
-
const userId = text(row, "invitee_user_id");
|
|
2040
|
-
return {
|
|
2041
|
-
id: requireText(row, "id", operation),
|
|
2042
|
-
meetingId: asMeetingId(requireText(row, "meeting_id", operation)),
|
|
2043
|
-
userId: userId === null ? null : asUserId(userId),
|
|
2044
|
-
email: text(row, "email"),
|
|
2045
|
-
displayName: text(row, "display_name"),
|
|
2046
|
-
role: text(row, "role") === "cohost" ? "cohost" : "invitee",
|
|
2047
|
-
rsvpState: rsvpState(text(row, "rsvp_state")),
|
|
2048
|
-
respondedAt: when(row, "responded_at"),
|
|
2049
|
-
rsvpNote: text(row, "rsvp_note"),
|
|
2050
|
-
lastSentSequence: num2(row, "last_sent_sequence"),
|
|
2051
|
-
lastSentAt: when(row, "last_sent_at")
|
|
2052
|
-
};
|
|
2053
|
-
}
|
|
2054
|
-
function projectOccurrence(row) {
|
|
2055
|
-
const operation = "meet.projectOccurrence";
|
|
2056
|
-
const state = text(row, "state");
|
|
2057
|
-
return {
|
|
2058
|
-
meetingId: asMeetingId(requireText(row, "meeting_id", operation)),
|
|
2059
|
-
originalStart: when(row, "original_start") ?? requireText(row, "original_start", operation),
|
|
2060
|
-
occurrenceStart: when(row, "occurrence_start") ?? requireText(row, "occurrence_start", operation),
|
|
2061
|
-
durationMinutes: num2(row, "duration_minutes"),
|
|
2062
|
-
// An unknown state is shown as scheduled, never dropped: the row is real.
|
|
2063
|
-
state: OCCURRENCE_STATES.includes(state) ? state : "scheduled",
|
|
2064
|
-
sequence: num2(row, "sequence") ?? 0
|
|
2065
|
-
};
|
|
2066
|
-
}
|
|
2067
|
-
function projectUpcoming(row) {
|
|
2068
|
-
const operation = "meet.projectUpcoming";
|
|
2069
|
-
const kind = text(row, "kind");
|
|
2070
|
-
const myRole = text(row, "my_role");
|
|
2071
|
-
const myRsvp = text(row, "my_rsvp");
|
|
2072
|
-
return {
|
|
2073
|
-
...projectOccurrence(row),
|
|
2074
|
-
organizationId: asOrganizationId(requireText(row, "organization_id", operation)),
|
|
2075
|
-
title: text(row, "title") ?? "Meeting",
|
|
2076
|
-
slug: requireText(row, "slug", operation),
|
|
2077
|
-
kind: kind === "scheduled" || kind === "recurring" ? kind : "instant",
|
|
2078
|
-
hostUserId: asUserId(requireText(row, "host_user_id", operation)),
|
|
2079
|
-
timeZone: text(row, "time_zone") ?? "UTC",
|
|
2080
|
-
meetingCancelled: bool(row, "meeting_cancelled", false),
|
|
2081
|
-
myRsvp: myRsvp === null ? null : rsvpState(myRsvp),
|
|
2082
|
-
myRole: myRole === "host" || myRole === "cohost" || myRole === "invitee" ? myRole : null
|
|
2083
|
-
};
|
|
2084
|
-
}
|
|
2085
|
-
function projectOverride(row) {
|
|
2086
|
-
const operation = "meet.projectOverride";
|
|
2087
|
-
return {
|
|
2088
|
-
id: requireText(row, "id", operation),
|
|
2089
|
-
meetingId: asMeetingId(requireText(row, "meeting_id", operation)),
|
|
2090
|
-
originalStart: when(row, "original_start") ?? requireText(row, "original_start", operation),
|
|
2091
|
-
state: text(row, "state") === "moved" ? "moved" : "cancelled",
|
|
2092
|
-
newStart: when(row, "new_start"),
|
|
2093
|
-
newDurationMinutes: num2(row, "new_duration_minutes"),
|
|
2094
|
-
reason: text(row, "reason"),
|
|
2095
|
-
calendarSequence: num2(row, "calendar_sequence") ?? 0,
|
|
2096
|
-
deletedAt: when(row, "deleted_at")
|
|
2097
|
-
};
|
|
2098
|
-
}
|
|
2099
|
-
function projectRsvpInvitation(body) {
|
|
2100
|
-
const operation = "meet.projectRsvpInvitation";
|
|
2101
|
-
if (typeof body !== "object" || body === null) {
|
|
2102
|
-
throw invalidResponse(operation, "the RSVP door returned no object");
|
|
2103
|
-
}
|
|
2104
|
-
const row = body;
|
|
2105
|
-
if (row["ok"] !== true) {
|
|
1971
|
+
function projectAttendee(row) {
|
|
1972
|
+
const operation = "meet.projectAttendee";
|
|
1973
|
+
const role = text(row, "role");
|
|
1974
|
+
const admission = text(row, "admission_state");
|
|
1975
|
+
const known = ["host", "cohost", "presenter", "participant", "viewer"];
|
|
1976
|
+
const states = [
|
|
1977
|
+
"waiting_host",
|
|
1978
|
+
"knocking",
|
|
1979
|
+
"admitted",
|
|
1980
|
+
"denied",
|
|
1981
|
+
"expired",
|
|
1982
|
+
"withdrawn",
|
|
1983
|
+
"removed",
|
|
1984
|
+
"ended"
|
|
1985
|
+
];
|
|
1986
|
+
const userId = text(row, "participant_user_id");
|
|
1987
|
+
const identity = requireText(row, "identity", operation);
|
|
2106
1988
|
return {
|
|
2107
|
-
|
|
2108
|
-
|
|
1989
|
+
id: requireText(row, "id", operation),
|
|
1990
|
+
meetingId: asMeetingId(requireText(row, "meeting_id", operation)),
|
|
1991
|
+
identity: asParticipantIdentity(identity),
|
|
1992
|
+
// A nameless attendee is a row nobody can read. The identity is a worse
|
|
1993
|
+
// name than a real one and an infinitely better one than a blank.
|
|
1994
|
+
displayName: text(row, "display_name") ?? identity,
|
|
1995
|
+
avatarUrl: text(row, "avatar_url"),
|
|
1996
|
+
userId: userId === null ? null : asUserId(userId),
|
|
1997
|
+
role: known.includes(role) ? role : "participant",
|
|
1998
|
+
admissionState: states.includes(admission) ? admission : "admitted",
|
|
1999
|
+
knockedAt: text(row, "knocked_at"),
|
|
2000
|
+
knockExpiresAt: text(row, "knock_expires_at"),
|
|
2001
|
+
isAgent: bool(row, "is_agent", false),
|
|
2002
|
+
joinedAt: text(row, "joined_at"),
|
|
2003
|
+
leftAt: text(row, "left_at"),
|
|
2004
|
+
sessions: projectStays(row["sessions"])
|
|
2109
2005
|
};
|
|
2110
2006
|
}
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
scheduledFor: when(meeting, "scheduled_for"),
|
|
2119
|
-
durationMinutes: num2(meeting, "duration_minutes"),
|
|
2120
|
-
timeZone: text(meeting, "time_zone") ?? "UTC",
|
|
2121
|
-
agenda: text(meeting, "agenda"),
|
|
2122
|
-
recurrenceRule: text(meeting, "recurrence_rule"),
|
|
2123
|
-
cancelledAt: when(meeting, "cancelled_at"),
|
|
2124
|
-
cancellationReason: text(meeting, "cancellation_reason"),
|
|
2125
|
-
endedAt: when(meeting, "ended_at")
|
|
2126
|
-
},
|
|
2127
|
-
invitee: {
|
|
2128
|
-
displayName: text(invitee, "display_name"),
|
|
2129
|
-
email: text(invitee, "email"),
|
|
2130
|
-
rsvpState: rsvpState(text(invitee, "rsvp_state")),
|
|
2131
|
-
respondedAt: when(invitee, "responded_at")
|
|
2132
|
-
}
|
|
2133
|
-
};
|
|
2134
|
-
}
|
|
2135
|
-
function settingsPatch(settings) {
|
|
2136
|
-
const patch = {};
|
|
2137
|
-
if (settings.lobbyEnabled !== void 0) patch["lobby_enabled"] = settings.lobbyEnabled;
|
|
2138
|
-
if (settings.recordingPolicy !== void 0) patch["recording_policy"] = settings.recordingPolicy;
|
|
2139
|
-
if (settings.aiEnabled !== void 0) patch["ai_enabled"] = settings.aiEnabled;
|
|
2140
|
-
if (settings.joinBeforeHost !== void 0) patch["join_before_host"] = settings.joinBeforeHost;
|
|
2141
|
-
return patch;
|
|
2142
|
-
}
|
|
2143
|
-
function changesPatch(changes) {
|
|
2144
|
-
const patch = settingsPatch(changes);
|
|
2145
|
-
if (changes.title !== void 0) patch["title"] = changes.title;
|
|
2146
|
-
if (changes.agenda !== void 0) patch["agenda"] = changes.agenda;
|
|
2147
|
-
if (changes.timeZone !== void 0) patch["time_zone"] = changes.timeZone;
|
|
2148
|
-
if (changes.scheduledFor !== void 0) patch["scheduled_for"] = changes.scheduledFor;
|
|
2149
|
-
if (changes.scheduledDurationMinutes !== void 0) {
|
|
2150
|
-
patch["scheduled_duration_minutes"] = changes.scheduledDurationMinutes;
|
|
2151
|
-
}
|
|
2152
|
-
if (changes.recurrenceRule !== void 0) patch["recurrence_rule"] = changes.recurrenceRule;
|
|
2153
|
-
if (changes.keepExceptions !== void 0) patch["keep_exceptions"] = changes.keepExceptions;
|
|
2154
|
-
return patch;
|
|
2155
|
-
}
|
|
2156
|
-
function createMeetRepository(options) {
|
|
2157
|
-
const db = options.client.schema(MEET_SCHEMA);
|
|
2158
|
-
async function guardVoid(operation, run) {
|
|
2159
|
-
let response = await run();
|
|
2160
|
-
if (response.error !== null) {
|
|
2161
|
-
const normalized = normalizeMeetError(response.error, operation);
|
|
2162
|
-
if (normalized.code === "session-unavailable" && options.session !== void 0) {
|
|
2163
|
-
await options.session();
|
|
2164
|
-
response = await run();
|
|
2165
|
-
}
|
|
2166
|
-
if (response.error !== null) throw normalizeMeetError(response.error, operation);
|
|
2167
|
-
}
|
|
2168
|
-
}
|
|
2169
|
-
async function guard(operation, run) {
|
|
2170
|
-
let response = await run();
|
|
2171
|
-
if (response.error !== null) {
|
|
2172
|
-
const normalized = normalizeMeetError(response.error, operation);
|
|
2173
|
-
if (normalized.code === "session-unavailable" && options.session !== void 0) {
|
|
2174
|
-
await options.session();
|
|
2175
|
-
response = await run();
|
|
2176
|
-
}
|
|
2177
|
-
if (response.error !== null) throw normalizeMeetError(response.error, operation);
|
|
2178
|
-
}
|
|
2179
|
-
if (response.data === null) {
|
|
2180
|
-
throw invalidResponse(operation, "the database returned no row");
|
|
2181
|
-
}
|
|
2182
|
-
return response.data;
|
|
2183
|
-
}
|
|
2184
|
-
function projectInvite(row) {
|
|
2185
|
-
const operation = "meet.projectInvite";
|
|
2186
|
-
const mode = text(row, "mode");
|
|
2187
|
-
return {
|
|
2188
|
-
id: asCallId(requireText(row, "id", operation)),
|
|
2189
|
-
organizationId: asOrganizationId(requireText(row, "organization_id", operation)),
|
|
2190
|
-
roomName: asRoomName(requireText(row, "room_name", operation)),
|
|
2191
|
-
mode: mode === "video" ? "video" : "audio",
|
|
2192
|
-
callerUserId: asUserId(requireText(row, "caller_user_id", operation)),
|
|
2193
|
-
callerName: text(row, "caller_name") ?? "Someone",
|
|
2194
|
-
callerAvatarUrl: text(row, "caller_avatar_url"),
|
|
2195
|
-
calleeUserIds: stringArray(row, "callee_ids").map(asUserId),
|
|
2196
|
-
conversationId: text(row, "conversation_id"),
|
|
2197
|
-
createdAt: requireText(row, "created_at", operation),
|
|
2198
|
-
expiresAt: text(row, "expires_at") ?? requireText(row, "created_at", operation),
|
|
2199
|
-
state: callState(text(row, "state")),
|
|
2200
|
-
declineMessage: text(row, "decline_message")
|
|
2201
|
-
};
|
|
2202
|
-
}
|
|
2203
|
-
function projectMeeting(row) {
|
|
2204
|
-
const operation = "meet.projectMeeting";
|
|
2205
|
-
const kind = text(row, "kind");
|
|
2206
|
-
const policy = text(row, "recording_policy");
|
|
2207
|
-
const duration = row["scheduled_duration_minutes"];
|
|
2208
|
-
const metadata = row["metadata"];
|
|
2209
|
-
return {
|
|
2210
|
-
id: asMeetingId(requireText(row, "id", operation)),
|
|
2211
|
-
organizationId: asOrganizationId(requireText(row, "organization_id", operation)),
|
|
2212
|
-
roomName: asRoomName(requireText(row, "room_name", operation)),
|
|
2213
|
-
slug: requireText(row, "slug", operation),
|
|
2214
|
-
title: text(row, "title") ?? "Meeting",
|
|
2215
|
-
kind: kind === "scheduled" || kind === "recurring" ? kind : "instant",
|
|
2216
|
-
hostUserId: asUserId(requireText(row, "host_user_id", operation)),
|
|
2217
|
-
scheduledFor: text(row, "scheduled_for"),
|
|
2218
|
-
scheduledDurationMinutes: typeof duration === "number" ? duration : null,
|
|
2219
|
-
startedAt: text(row, "started_at"),
|
|
2220
|
-
endedAt: text(row, "ended_at"),
|
|
2221
|
-
locked: bool(row, "locked", false),
|
|
2222
|
-
lobbyEnabled: bool(row, "lobby_enabled", true),
|
|
2223
|
-
recordingPolicy: policy === "disabled" || policy === "always-on" ? policy : "host-controlled",
|
|
2224
|
-
aiEnabled: bool(row, "ai_enabled", true),
|
|
2225
|
-
metadata: typeof metadata === "object" && metadata !== null && !Array.isArray(metadata) ? metadata : {},
|
|
2226
|
-
timeZone: text(row, "time_zone") ?? "UTC",
|
|
2227
|
-
agenda: text(row, "agenda"),
|
|
2228
|
-
recurrenceRule: text(row, "recurrence_rule"),
|
|
2229
|
-
joinBeforeHost: bool(row, "join_before_host", true),
|
|
2230
|
-
cancelledAt: text(row, "cancelled_at"),
|
|
2231
|
-
cancellationReason: text(row, "cancellation_reason"),
|
|
2232
|
-
deletedAt: text(row, "deleted_at"),
|
|
2233
|
-
calendarSequence: typeof row["calendar_sequence"] === "number" ? row["calendar_sequence"] : 0,
|
|
2234
|
-
version: typeof row["version"] === "number" ? row["version"] : 1
|
|
2235
|
-
};
|
|
2236
|
-
}
|
|
2237
|
-
function projectRecording(row) {
|
|
2238
|
-
const operation = "meet.projectRecording";
|
|
2239
|
-
const state = text(row, "state");
|
|
2240
|
-
const known = [
|
|
2241
|
-
"idle",
|
|
2242
|
-
"starting",
|
|
2243
|
-
"recording",
|
|
2244
|
-
"stopping",
|
|
2245
|
-
"processing",
|
|
2246
|
-
"available",
|
|
2247
|
-
"expired",
|
|
2248
|
-
"failed"
|
|
2249
|
-
];
|
|
2250
|
-
return {
|
|
2251
|
-
id: requireText(row, "id", operation),
|
|
2252
|
-
meetingId: asMeetingId(requireText(row, "meeting_id", operation)),
|
|
2253
|
-
state: known.includes(state) ? state : "failed",
|
|
2254
|
-
startedAt: text(row, "started_at"),
|
|
2255
|
-
endedAt: text(row, "ended_at"),
|
|
2256
|
-
startedBy: text(row, "started_by") === null ? null : asUserId(text(row, "started_by")),
|
|
2257
|
-
// A PLATFORM file id, never a provider URL (R3). A recording that only
|
|
2258
|
-
// exists as a LiveKit URL is orphaned the day the room is garbage
|
|
2259
|
-
// collected, which is the whole reason this column is not `url`.
|
|
2260
|
-
fileId: text(row, "file_id"),
|
|
2261
|
-
failureReason: text(row, "failure_reason"),
|
|
2262
|
-
retention: projectRecordingRetention(row["metadata"])
|
|
2263
|
-
};
|
|
2264
|
-
}
|
|
2265
|
-
function projectRecordingRetention(value) {
|
|
2266
|
-
if (typeof value !== "object" || value === null || Array.isArray(value)) return null;
|
|
2267
|
-
const retention = value["retention"];
|
|
2268
|
-
if (typeof retention !== "object" || retention === null || Array.isArray(retention)) return null;
|
|
2269
|
-
const row = retention;
|
|
2270
|
-
const phase = row["phase"];
|
|
2271
|
-
const operationId = row["operation_id"];
|
|
2272
|
-
const policyId = row["policy_id"];
|
|
2273
|
-
const policyUpdatedAt = row["policy_updated_at"];
|
|
2274
|
-
const dueAt = row["due_at"];
|
|
2275
|
-
const expiredAt = row["expired_at"];
|
|
2276
|
-
if (phase !== "warning" && phase !== "expired" || typeof operationId !== "string" || typeof policyId !== "string" || typeof policyUpdatedAt !== "string" || typeof dueAt !== "string" || expiredAt !== null && typeof expiredAt !== "string") return null;
|
|
2277
|
-
return { operationId, policyId, policyUpdatedAt, phase, dueAt, expiredAt };
|
|
2278
|
-
}
|
|
2279
|
-
function projectAttendee(row) {
|
|
2280
|
-
const operation = "meet.projectAttendee";
|
|
2281
|
-
const role = text(row, "role");
|
|
2282
|
-
const admission = text(row, "admission_state");
|
|
2283
|
-
const known = ["host", "cohost", "presenter", "participant", "viewer"];
|
|
2284
|
-
const states = ["knocking", "admitted", "denied", "removed"];
|
|
2285
|
-
const userId = text(row, "participant_user_id");
|
|
2286
|
-
const identity = requireText(row, "identity", operation);
|
|
2287
|
-
return {
|
|
2288
|
-
id: requireText(row, "id", operation),
|
|
2289
|
-
meetingId: asMeetingId(requireText(row, "meeting_id", operation)),
|
|
2290
|
-
identity: asParticipantIdentity(identity),
|
|
2291
|
-
// A nameless attendee is a row nobody can read. The identity is a worse
|
|
2292
|
-
// name than a real one and an infinitely better one than a blank.
|
|
2293
|
-
displayName: text(row, "display_name") ?? identity,
|
|
2294
|
-
avatarUrl: text(row, "avatar_url"),
|
|
2295
|
-
userId: userId === null ? null : asUserId(userId),
|
|
2296
|
-
role: known.includes(role) ? role : "participant",
|
|
2297
|
-
admissionState: states.includes(admission) ? admission : "admitted",
|
|
2298
|
-
isAgent: bool(row, "is_agent", false),
|
|
2299
|
-
joinedAt: text(row, "joined_at"),
|
|
2300
|
-
leftAt: text(row, "left_at"),
|
|
2301
|
-
sessions: projectStays(row["sessions"])
|
|
2302
|
-
};
|
|
2303
|
-
}
|
|
2304
|
-
function requireOrg(organizationId, operation) {
|
|
2305
|
-
if (typeof organizationId !== "string" || organizationId.length === 0) {
|
|
2306
|
-
throw new MeetError(
|
|
2307
|
-
"misconfigured",
|
|
2308
|
-
`${operation}: no organization_id`,
|
|
2309
|
-
"Every meet write carries an explicit organization_id (R5). The package refuses the write rather than letting an unscoped row reach the database."
|
|
2310
|
-
);
|
|
2007
|
+
function requireOrg(organizationId, operation) {
|
|
2008
|
+
if (typeof organizationId !== "string" || organizationId.length === 0) {
|
|
2009
|
+
throw new MeetError(
|
|
2010
|
+
"misconfigured",
|
|
2011
|
+
`${operation}: no organization_id`,
|
|
2012
|
+
"Every meet write carries an explicit organization_id (R5). The package refuses the write rather than letting an unscoped row reach the database."
|
|
2013
|
+
);
|
|
2311
2014
|
}
|
|
2312
2015
|
}
|
|
2313
2016
|
return {
|
|
@@ -2333,7 +2036,7 @@ function createMeetRepository(options) {
|
|
|
2333
2036
|
p_duration_minutes: input.durationMinutes ?? null,
|
|
2334
2037
|
p_agenda: input.agenda ?? null,
|
|
2335
2038
|
p_recurrence_rule: input.recurrenceRule ?? null,
|
|
2336
|
-
p_settings:
|
|
2039
|
+
p_settings: scheduleSettingsPatch(input.settings ?? {}, input.appPanel, input.observers)
|
|
2337
2040
|
})
|
|
2338
2041
|
);
|
|
2339
2042
|
return projectMeeting(row);
|
|
@@ -2573,16 +2276,26 @@ function createMeetRepository(options) {
|
|
|
2573
2276
|
hasMore: rows.length > limit
|
|
2574
2277
|
};
|
|
2575
2278
|
},
|
|
2576
|
-
async
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
|
|
2279
|
+
async decideAdmission({ meetingId, identity, decision, byUserId }) {
|
|
2280
|
+
const fn = decision === "admit" ? RPCS.admit : decision === "deny" ? RPCS.deny : RPCS.undeny;
|
|
2281
|
+
const row = await guard(
|
|
2282
|
+
`meet.${decision}`,
|
|
2283
|
+
() => db.rpc(fn, {
|
|
2580
2284
|
p_meeting_id: meetingId,
|
|
2581
2285
|
p_identity: identity,
|
|
2582
|
-
p_admitted: admitted,
|
|
2583
2286
|
p_by_user_id: byUserId
|
|
2584
2287
|
})
|
|
2585
2288
|
);
|
|
2289
|
+
return projectAttendee(row);
|
|
2290
|
+
},
|
|
2291
|
+
async admitAll(meetingId, byUserId) {
|
|
2292
|
+
const response = await db.rpc(RPCS.admitAll, {
|
|
2293
|
+
p_meeting_id: meetingId,
|
|
2294
|
+
p_by_user_id: byUserId
|
|
2295
|
+
});
|
|
2296
|
+
if (response.error !== null) throw normalizeMeetError(response.error, "meet.admitAll");
|
|
2297
|
+
const rows = Array.isArray(response.data) ? response.data : [];
|
|
2298
|
+
return rows.map(projectAttendee);
|
|
2586
2299
|
},
|
|
2587
2300
|
async setLock(meetingId, locked, byUserId) {
|
|
2588
2301
|
const row = await guard(
|
|
@@ -2656,76 +2369,449 @@ function createMeetRepository(options) {
|
|
|
2656
2369
|
);
|
|
2657
2370
|
}
|
|
2658
2371
|
}
|
|
2659
|
-
};
|
|
2372
|
+
};
|
|
2373
|
+
}
|
|
2374
|
+
var LIBRARY_STATES = [
|
|
2375
|
+
"idle",
|
|
2376
|
+
"starting",
|
|
2377
|
+
"recording",
|
|
2378
|
+
"stopping",
|
|
2379
|
+
"processing",
|
|
2380
|
+
"available",
|
|
2381
|
+
"expired",
|
|
2382
|
+
"failed"
|
|
2383
|
+
];
|
|
2384
|
+
function projectLibraryRecording(row) {
|
|
2385
|
+
const operation = "meet.projectLibraryRecording";
|
|
2386
|
+
const state = text(row, "state");
|
|
2387
|
+
const role = text(row, "my_role");
|
|
2388
|
+
return {
|
|
2389
|
+
id: requireText(row, "recording_id", operation),
|
|
2390
|
+
meetingId: asMeetingId(requireText(row, "meeting_id", operation)),
|
|
2391
|
+
meetingTitle: text(row, "meeting_title") ?? "Meeting",
|
|
2392
|
+
meetingSlug: requireText(row, "meeting_slug", operation),
|
|
2393
|
+
role: role === "host" || role === "cohost" || role === "invitee" ? role : "attendee",
|
|
2394
|
+
state: LIBRARY_STATES.includes(state) ? state : "failed",
|
|
2395
|
+
title: text(row, "recording_title"),
|
|
2396
|
+
fileId: text(row, "file_id"),
|
|
2397
|
+
startedAt: text(row, "started_at"),
|
|
2398
|
+
endedAt: text(row, "ended_at"),
|
|
2399
|
+
durationMs: num(row, "duration_ms"),
|
|
2400
|
+
sizeBytes: num(row, "size_bytes"),
|
|
2401
|
+
failureReason: text(row, "failure_reason"),
|
|
2402
|
+
archivedAt: text(row, "deleted_at"),
|
|
2403
|
+
organizationId: asOrganizationId(requireText(row, "organization_id", operation))
|
|
2404
|
+
};
|
|
2405
|
+
}
|
|
2406
|
+
var SEARCH_SOURCES = [
|
|
2407
|
+
"title",
|
|
2408
|
+
"summary",
|
|
2409
|
+
"decision",
|
|
2410
|
+
"action_item",
|
|
2411
|
+
"note",
|
|
2412
|
+
"transcript"
|
|
2413
|
+
];
|
|
2414
|
+
function projectSearchHit(row) {
|
|
2415
|
+
const operation = "meet.projectSearchHit";
|
|
2416
|
+
const source = text(row, "source");
|
|
2417
|
+
return {
|
|
2418
|
+
meetingId: asMeetingId(requireText(row, "meeting_id", operation)),
|
|
2419
|
+
meetingTitle: text(row, "meeting_title") ?? "Meeting",
|
|
2420
|
+
meetingSlug: requireText(row, "meeting_slug", operation),
|
|
2421
|
+
meetingAt: text(row, "meeting_at"),
|
|
2422
|
+
source: SEARCH_SOURCES.includes(source) ? source : "note",
|
|
2423
|
+
refId: requireText(row, "ref_id", operation),
|
|
2424
|
+
speaker: text(row, "speaker"),
|
|
2425
|
+
snippet: text(row, "snippet") ?? "",
|
|
2426
|
+
at: text(row, "said_at"),
|
|
2427
|
+
rank: num(row, "rank") ?? 0
|
|
2428
|
+
};
|
|
2429
|
+
}
|
|
2430
|
+
function snippetRuns(snippet) {
|
|
2431
|
+
const runs = [];
|
|
2432
|
+
const pattern = /⟦([^⟧]*)⟧/g;
|
|
2433
|
+
let last = 0;
|
|
2434
|
+
for (const match of snippet.matchAll(pattern)) {
|
|
2435
|
+
const start = match.index ?? 0;
|
|
2436
|
+
if (start > last) runs.push({ text: snippet.slice(last, start), hit: false });
|
|
2437
|
+
runs.push({ text: match[1] ?? "", hit: true });
|
|
2438
|
+
last = start + match[0].length;
|
|
2439
|
+
}
|
|
2440
|
+
if (last < snippet.length) runs.push({ text: snippet.slice(last), hit: false });
|
|
2441
|
+
return runs;
|
|
2442
|
+
}
|
|
2443
|
+
|
|
2444
|
+
// src/core/app-panel.ts
|
|
2445
|
+
function appPanelOf(meeting) {
|
|
2446
|
+
const raw = meeting?.metadata?.["app_panel"];
|
|
2447
|
+
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) return null;
|
|
2448
|
+
const key = raw["key"];
|
|
2449
|
+
const recordId = raw["record_id"];
|
|
2450
|
+
if (typeof key !== "string" || key.length === 0) return null;
|
|
2451
|
+
if (typeof recordId !== "string" || recordId.length === 0) return null;
|
|
2452
|
+
return { key, recordId };
|
|
2453
|
+
}
|
|
2454
|
+
function scheduleSettingsPatch(settings, appPanel, observers) {
|
|
2455
|
+
const patch = settingsPatch(settings);
|
|
2456
|
+
if (appPanel !== void 0) patch["app_panel"] = { key: appPanel.key, record_id: appPanel.recordId };
|
|
2457
|
+
if (observers !== void 0 && observers.length > 0) patch["observers"] = [...observers];
|
|
2458
|
+
return patch;
|
|
2459
|
+
}
|
|
2460
|
+
function focusFromRow(value) {
|
|
2461
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return null;
|
|
2462
|
+
const row = value;
|
|
2463
|
+
if (typeof row["panel"] !== "string") return null;
|
|
2464
|
+
return {
|
|
2465
|
+
panel: row["panel"],
|
|
2466
|
+
section: typeof row["section"] === "string" ? row["section"] : "",
|
|
2467
|
+
byName: typeof row["by_name"] === "string" ? row["by_name"] : "",
|
|
2468
|
+
at: typeof row["at"] === "string" ? row["at"] : ""
|
|
2469
|
+
};
|
|
2470
|
+
}
|
|
2471
|
+
|
|
2472
|
+
// src/core/collab.ts
|
|
2473
|
+
var EMPTY_COLLAB = {
|
|
2474
|
+
loaded: false,
|
|
2475
|
+
error: null,
|
|
2476
|
+
settings: null,
|
|
2477
|
+
polls: [],
|
|
2478
|
+
questions: [],
|
|
2479
|
+
strokes: [],
|
|
2480
|
+
breakout: null,
|
|
2481
|
+
broadcast: null,
|
|
2482
|
+
moveRequest: null,
|
|
2483
|
+
panelRequest: null,
|
|
2484
|
+
captionLanguage: null,
|
|
2485
|
+
captionTranslations: {},
|
|
2486
|
+
captionTranslationError: null,
|
|
2487
|
+
focus: null
|
|
2488
|
+
};
|
|
2489
|
+
function obj(value) {
|
|
2490
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
|
|
2491
|
+
}
|
|
2492
|
+
function str(value) {
|
|
2493
|
+
return typeof value === "string" ? value : null;
|
|
2494
|
+
}
|
|
2495
|
+
function num2(value) {
|
|
2496
|
+
return typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
2497
|
+
}
|
|
2498
|
+
function strings(value) {
|
|
2499
|
+
return Array.isArray(value) ? value.filter((v) => typeof v === "string") : [];
|
|
2500
|
+
}
|
|
2501
|
+
function pollFromRow(row) {
|
|
2502
|
+
const id = str(row["id"]);
|
|
2503
|
+
if (id === null) return null;
|
|
2504
|
+
const options = [];
|
|
2505
|
+
for (const raw of Array.isArray(row["options"]) ? row["options"] : []) {
|
|
2506
|
+
const option = obj(raw);
|
|
2507
|
+
const optionId = option === null ? null : str(option["id"]);
|
|
2508
|
+
if (option === null || optionId === null) continue;
|
|
2509
|
+
const votes = num2(option["votes"]);
|
|
2510
|
+
options.push({
|
|
2511
|
+
id: optionId,
|
|
2512
|
+
text: str(option["text"]) ?? "",
|
|
2513
|
+
...votes !== null ? { votes } : {},
|
|
2514
|
+
...Array.isArray(option["voters"]) ? { voters: strings(option["voters"]) } : {}
|
|
2515
|
+
});
|
|
2516
|
+
}
|
|
2517
|
+
const state = str(row["state"]);
|
|
2518
|
+
return {
|
|
2519
|
+
id,
|
|
2520
|
+
question: str(row["question"]) ?? "",
|
|
2521
|
+
options,
|
|
2522
|
+
multiple: row["multiple"] === true,
|
|
2523
|
+
anonymous: row["anonymous"] === true,
|
|
2524
|
+
state: state === "live" || state === "closed" ? state : "draft",
|
|
2525
|
+
resultsShared: row["results_shared"] === true,
|
|
2526
|
+
voterCount: num2(row["voter_count"]),
|
|
2527
|
+
myVote: strings(row["my_vote"]),
|
|
2528
|
+
createdByName: str(row["created_by_name"]) ?? "",
|
|
2529
|
+
createdAt: str(row["created_at"])
|
|
2530
|
+
};
|
|
2531
|
+
}
|
|
2532
|
+
function questionFromRow(row) {
|
|
2533
|
+
const id = str(row["id"]);
|
|
2534
|
+
if (id === null) return null;
|
|
2535
|
+
const state = str(row["state"]);
|
|
2536
|
+
return {
|
|
2537
|
+
id,
|
|
2538
|
+
text: str(row["text"]) ?? "",
|
|
2539
|
+
askerName: str(row["asker_name"]) ?? "",
|
|
2540
|
+
anonymous: row["anonymous"] === true,
|
|
2541
|
+
state: state === "answered" || state === "dismissed" ? state : "open",
|
|
2542
|
+
answer: str(row["answer"]),
|
|
2543
|
+
answeredByName: str(row["answered_by_name"]),
|
|
2544
|
+
upvotes: num2(row["upvotes"]) ?? 0,
|
|
2545
|
+
upvotedByMe: row["upvoted_by_me"] === true,
|
|
2546
|
+
mine: row["mine"] === true,
|
|
2547
|
+
createdAt: str(row["created_at"])
|
|
2548
|
+
};
|
|
2549
|
+
}
|
|
2550
|
+
function strokeFromRow(row) {
|
|
2551
|
+
const id = str(row["id"]);
|
|
2552
|
+
if (id === null || !Array.isArray(row["points"])) return null;
|
|
2553
|
+
const points = [];
|
|
2554
|
+
for (const raw of row["points"]) {
|
|
2555
|
+
if (Array.isArray(raw) && typeof raw[0] === "number" && typeof raw[1] === "number") {
|
|
2556
|
+
points.push([raw[0], raw[1]]);
|
|
2557
|
+
}
|
|
2558
|
+
}
|
|
2559
|
+
return {
|
|
2560
|
+
id,
|
|
2561
|
+
points,
|
|
2562
|
+
color: str(row["color"]) ?? "#111827",
|
|
2563
|
+
width: num2(row["width"]) ?? 3,
|
|
2564
|
+
tool: row["tool"] === "highlighter" ? "highlighter" : "pen",
|
|
2565
|
+
by: str(row["by"]) ?? "",
|
|
2566
|
+
byName: str(row["by_name"]) ?? ""
|
|
2567
|
+
};
|
|
2568
|
+
}
|
|
2569
|
+
function broadcastFrom(value) {
|
|
2570
|
+
const row = obj(value);
|
|
2571
|
+
const text3 = row === null ? null : str(row["text"]);
|
|
2572
|
+
if (row === null || text3 === null) return null;
|
|
2573
|
+
return { text: text3, by: str(row["by"]) ?? "", at: str(row["at"]) ?? "" };
|
|
2574
|
+
}
|
|
2575
|
+
function planFromRow(row) {
|
|
2576
|
+
const id = str(row["id"]);
|
|
2577
|
+
if (id === null) return null;
|
|
2578
|
+
const rooms = [];
|
|
2579
|
+
for (const raw of Array.isArray(row["rooms"]) ? row["rooms"] : []) {
|
|
2580
|
+
const room = obj(raw);
|
|
2581
|
+
const roomId = room === null ? null : str(room["id"]);
|
|
2582
|
+
if (room === null || roomId === null) continue;
|
|
2583
|
+
rooms.push({ id: roomId, name: str(room["name"]) ?? roomId, identities: strings(room["identities"]) });
|
|
2584
|
+
}
|
|
2585
|
+
return {
|
|
2586
|
+
id,
|
|
2587
|
+
state: row["state"] === "open" ? "open" : "closed",
|
|
2588
|
+
rooms,
|
|
2589
|
+
allowChoose: row["allow_choose"] === true,
|
|
2590
|
+
minutes: num2(row["minutes"]) ?? 0,
|
|
2591
|
+
openedAt: str(row["opened_at"]),
|
|
2592
|
+
endsAt: str(row["ends_at"]),
|
|
2593
|
+
closedAt: str(row["closed_at"]),
|
|
2594
|
+
broadcasts: (Array.isArray(row["broadcasts"]) ? row["broadcasts"] : []).map(broadcastFrom).filter((b) => b !== null)
|
|
2595
|
+
};
|
|
2596
|
+
}
|
|
2597
|
+
function settingsFromRow(row) {
|
|
2598
|
+
if (row === null) return null;
|
|
2599
|
+
return {
|
|
2600
|
+
pollsEnabled: row["polls_enabled"] === true,
|
|
2601
|
+
pollMaxOptions: num2(row["poll_max_options"]) ?? 10,
|
|
2602
|
+
qaEnabled: row["qa_enabled"] === true,
|
|
2603
|
+
qaAnonymousAllowed: row["qa_anonymous_allowed"] === true,
|
|
2604
|
+
whiteboardEnabled: row["whiteboard_enabled"] === true,
|
|
2605
|
+
breakoutsEnabled: row["breakouts_enabled"] === true,
|
|
2606
|
+
breakoutMaxRooms: num2(row["breakout_max_rooms"]) ?? 20,
|
|
2607
|
+
breakoutDefaultMinutes: num2(row["breakout_default_minutes"]) ?? 15,
|
|
2608
|
+
captionTranslationEnabled: row["caption_translation_enabled"] === true,
|
|
2609
|
+
captionTranslationLanguage: str(row["caption_translation_language"]) ?? ""
|
|
2610
|
+
};
|
|
2611
|
+
}
|
|
2612
|
+
function collabFromState(body, previous) {
|
|
2613
|
+
const list = (key, parse) => (Array.isArray(body[key]) ? body[key] : []).map((raw) => {
|
|
2614
|
+
const row = obj(raw);
|
|
2615
|
+
return row === null ? null : parse(row);
|
|
2616
|
+
}).filter((item) => item !== null);
|
|
2617
|
+
const breakoutRow = obj(body["breakout"]);
|
|
2618
|
+
return {
|
|
2619
|
+
...previous,
|
|
2620
|
+
loaded: true,
|
|
2621
|
+
error: null,
|
|
2622
|
+
settings: settingsFromRow(obj(body["settings"])),
|
|
2623
|
+
polls: list("polls", pollFromRow),
|
|
2624
|
+
questions: list("questions", questionFromRow),
|
|
2625
|
+
strokes: list("strokes", strokeFromRow),
|
|
2626
|
+
breakout: breakoutRow === null ? null : planFromRow(breakoutRow),
|
|
2627
|
+
focus: focusFromRow(body["focus"])
|
|
2628
|
+
};
|
|
2629
|
+
}
|
|
2630
|
+
function upsert(items, next) {
|
|
2631
|
+
const index = items.findIndex((item) => item.id === next.id);
|
|
2632
|
+
if (index === -1) return [...items, next];
|
|
2633
|
+
const copy = items.slice();
|
|
2634
|
+
copy[index] = next;
|
|
2635
|
+
return copy;
|
|
2636
|
+
}
|
|
2637
|
+
function withItem(collab, row, fromRoom = false) {
|
|
2638
|
+
const kind = str(row["kind"]);
|
|
2639
|
+
if (kind === "poll") {
|
|
2640
|
+
const poll = pollFromRow(row);
|
|
2641
|
+
if (poll === null) return collab;
|
|
2642
|
+
const known = collab.polls.find((p) => p.id === poll.id);
|
|
2643
|
+
let merged = fromRoom && known !== void 0 ? { ...poll, myVote: known.myVote } : poll;
|
|
2644
|
+
if (fromRoom && known !== void 0 && !poll.options.some((o) => o.votes !== void 0)) {
|
|
2645
|
+
const counted = new Map(known.options.map((o) => [o.id, o]));
|
|
2646
|
+
merged = {
|
|
2647
|
+
...merged,
|
|
2648
|
+
voterCount: known.voterCount,
|
|
2649
|
+
options: merged.options.map((o) => {
|
|
2650
|
+
const had = counted.get(o.id);
|
|
2651
|
+
return had === void 0 ? o : { ...o, votes: had.votes, voters: had.voters };
|
|
2652
|
+
})
|
|
2653
|
+
};
|
|
2654
|
+
}
|
|
2655
|
+
return { ...collab, polls: upsert(collab.polls, merged) };
|
|
2656
|
+
}
|
|
2657
|
+
if (kind === "question") {
|
|
2658
|
+
const question = questionFromRow(row);
|
|
2659
|
+
if (question === null) return collab;
|
|
2660
|
+
const known = collab.questions.find((q) => q.id === question.id);
|
|
2661
|
+
const merged = fromRoom && known !== void 0 ? { ...question, mine: known.mine, upvotedByMe: known.upvotedByMe } : question;
|
|
2662
|
+
return { ...collab, questions: upsert(collab.questions, merged) };
|
|
2663
|
+
}
|
|
2664
|
+
if (kind === "stroke") {
|
|
2665
|
+
const stroke = strokeFromRow(row);
|
|
2666
|
+
if (stroke === null) return collab;
|
|
2667
|
+
return { ...collab, strokes: upsert(collab.strokes, stroke) };
|
|
2668
|
+
}
|
|
2669
|
+
if (kind === "breakout") {
|
|
2670
|
+
const plan = planFromRow(row);
|
|
2671
|
+
if (plan === null) return collab;
|
|
2672
|
+
return { ...collab, breakout: plan };
|
|
2673
|
+
}
|
|
2674
|
+
return collab;
|
|
2675
|
+
}
|
|
2676
|
+
function without(collab, ids) {
|
|
2677
|
+
const gone = new Set(ids);
|
|
2678
|
+
return {
|
|
2679
|
+
...collab,
|
|
2680
|
+
polls: collab.polls.filter((p) => !gone.has(p.id)),
|
|
2681
|
+
questions: collab.questions.filter((q) => !gone.has(q.id)),
|
|
2682
|
+
strokes: collab.strokes.filter((s) => !gone.has(s.id))
|
|
2683
|
+
};
|
|
2684
|
+
}
|
|
2685
|
+
function applyCollabPacket(collab, payload, context) {
|
|
2686
|
+
const item = obj(payload["item"]);
|
|
2687
|
+
if (item !== null) {
|
|
2688
|
+
const kind = str(item["kind"]);
|
|
2689
|
+
let next = withItem(collab, item, true);
|
|
2690
|
+
if (kind === "poll" && str(item["state"]) === "live") {
|
|
2691
|
+
const before = collab.polls.find((p) => p.id === str(item["id"]));
|
|
2692
|
+
if (before === void 0 || before.state !== "live") {
|
|
2693
|
+
next = { ...next, panelRequest: { panel: "polls", at: context.now } };
|
|
2694
|
+
}
|
|
2695
|
+
}
|
|
2696
|
+
if (kind === "breakout") {
|
|
2697
|
+
const plan = planFromRow(item);
|
|
2698
|
+
if (plan !== null && plan.state === "closed") {
|
|
2699
|
+
next = { ...next, moveRequest: { roomId: null, at: context.now } };
|
|
2700
|
+
}
|
|
2701
|
+
}
|
|
2702
|
+
const move = obj(payload["move"]);
|
|
2703
|
+
if (move !== null && str(move["identity"]) === context.localIdentity) {
|
|
2704
|
+
next = { ...next, moveRequest: { roomId: str(move["room_id"]), at: context.now } };
|
|
2705
|
+
}
|
|
2706
|
+
return { next };
|
|
2707
|
+
}
|
|
2708
|
+
if (payload["focus"] !== void 0) {
|
|
2709
|
+
const focus = focusFromRow(payload["focus"]);
|
|
2710
|
+
return focus === null ? { next: null, dropped: "a focus packet without a panel" } : { next: { ...collab, focus } };
|
|
2711
|
+
}
|
|
2712
|
+
if (Array.isArray(payload["removed"])) {
|
|
2713
|
+
return { next: without(collab, strings(payload["removed"])) };
|
|
2714
|
+
}
|
|
2715
|
+
if (Array.isArray(payload["hidden"])) {
|
|
2716
|
+
return context.isHost ? { next: null, dropped: "hosts keep dismissed questions" } : { next: without(collab, strings(payload["hidden"])) };
|
|
2717
|
+
}
|
|
2718
|
+
if (payload["cleared"] === "whiteboard") {
|
|
2719
|
+
return { next: { ...collab, strokes: [] } };
|
|
2720
|
+
}
|
|
2721
|
+
const broadcast = broadcastFrom(payload["broadcast"]);
|
|
2722
|
+
if (broadcast !== null) {
|
|
2723
|
+
return { next: { ...collab, broadcast } };
|
|
2724
|
+
}
|
|
2725
|
+
return { next: null, dropped: "a collab packet with nothing this version understands" };
|
|
2660
2726
|
}
|
|
2661
|
-
|
|
2662
|
-
"
|
|
2663
|
-
|
|
2664
|
-
"recording",
|
|
2665
|
-
"stopping",
|
|
2666
|
-
"processing",
|
|
2667
|
-
"available",
|
|
2668
|
-
"expired",
|
|
2669
|
-
"failed"
|
|
2670
|
-
];
|
|
2671
|
-
function projectLibraryRecording(row) {
|
|
2672
|
-
const operation = "meet.projectLibraryRecording";
|
|
2673
|
-
const state = text(row, "state");
|
|
2674
|
-
const role = text(row, "my_role");
|
|
2675
|
-
return {
|
|
2676
|
-
id: requireText(row, "recording_id", operation),
|
|
2677
|
-
meetingId: asMeetingId(requireText(row, "meeting_id", operation)),
|
|
2678
|
-
meetingTitle: text(row, "meeting_title") ?? "Meeting",
|
|
2679
|
-
meetingSlug: requireText(row, "meeting_slug", operation),
|
|
2680
|
-
role: role === "host" || role === "cohost" || role === "invitee" ? role : "attendee",
|
|
2681
|
-
state: LIBRARY_STATES.includes(state) ? state : "failed",
|
|
2682
|
-
title: text(row, "recording_title"),
|
|
2683
|
-
fileId: text(row, "file_id"),
|
|
2684
|
-
startedAt: text(row, "started_at"),
|
|
2685
|
-
endedAt: text(row, "ended_at"),
|
|
2686
|
-
durationMs: num2(row, "duration_ms"),
|
|
2687
|
-
sizeBytes: num2(row, "size_bytes"),
|
|
2688
|
-
failureReason: text(row, "failure_reason"),
|
|
2689
|
-
archivedAt: text(row, "deleted_at"),
|
|
2690
|
-
organizationId: asOrganizationId(requireText(row, "organization_id", operation))
|
|
2691
|
-
};
|
|
2727
|
+
function assignedRoom(plan, identity) {
|
|
2728
|
+
if (plan === null || plan.state !== "open" || identity === null) return null;
|
|
2729
|
+
return plan.rooms.find((room) => room.identities.includes(identity)) ?? null;
|
|
2692
2730
|
}
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
];
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
return {
|
|
2705
|
-
meetingId: asMeetingId(requireText(row, "meeting_id", operation)),
|
|
2706
|
-
meetingTitle: text(row, "meeting_title") ?? "Meeting",
|
|
2707
|
-
meetingSlug: requireText(row, "meeting_slug", operation),
|
|
2708
|
-
meetingAt: text(row, "meeting_at"),
|
|
2709
|
-
source: SEARCH_SOURCES.includes(source) ? source : "note",
|
|
2710
|
-
refId: requireText(row, "ref_id", operation),
|
|
2711
|
-
speaker: text(row, "speaker"),
|
|
2712
|
-
snippet: text(row, "snippet") ?? "",
|
|
2713
|
-
at: text(row, "said_at"),
|
|
2714
|
-
rank: num2(row, "rank") ?? 0
|
|
2715
|
-
};
|
|
2731
|
+
function autoAssign(identities, rooms, exclude = []) {
|
|
2732
|
+
const count2 = Math.max(1, Math.floor(rooms));
|
|
2733
|
+
const out = Array.from({ length: count2 }, () => []);
|
|
2734
|
+
const skip = new Set(exclude);
|
|
2735
|
+
let index = 0;
|
|
2736
|
+
for (const identity of identities) {
|
|
2737
|
+
if (skip.has(identity)) continue;
|
|
2738
|
+
out[index % count2].push(identity);
|
|
2739
|
+
index += 1;
|
|
2740
|
+
}
|
|
2741
|
+
return out;
|
|
2716
2742
|
}
|
|
2717
|
-
function
|
|
2718
|
-
|
|
2719
|
-
const
|
|
2720
|
-
|
|
2721
|
-
|
|
2722
|
-
|
|
2723
|
-
|
|
2724
|
-
|
|
2725
|
-
|
|
2743
|
+
function breakoutRemainingMs(plan, now) {
|
|
2744
|
+
if (plan === null || plan.state !== "open" || plan.endsAt === null) return null;
|
|
2745
|
+
const end = Date.parse(plan.endsAt);
|
|
2746
|
+
return Number.isNaN(end) ? null : Math.max(0, end - now);
|
|
2747
|
+
}
|
|
2748
|
+
function defaultCaptionLanguage(settings, viewerLanguage) {
|
|
2749
|
+
const configured = settings?.captionTranslationLanguage.trim() ?? "";
|
|
2750
|
+
if (configured.length > 0) return configured;
|
|
2751
|
+
const own = (viewerLanguage ?? "").trim();
|
|
2752
|
+
return own.length > 0 ? own : "en";
|
|
2753
|
+
}
|
|
2754
|
+
var CAPTION_LANGUAGES = [
|
|
2755
|
+
{ tag: "en", name: "English" },
|
|
2756
|
+
{ tag: "es", name: "Spanish" },
|
|
2757
|
+
{ tag: "fr", name: "French" },
|
|
2758
|
+
{ tag: "de", name: "German" },
|
|
2759
|
+
{ tag: "it", name: "Italian" },
|
|
2760
|
+
{ tag: "pt", name: "Portuguese" },
|
|
2761
|
+
{ tag: "nl", name: "Dutch" },
|
|
2762
|
+
{ tag: "pl", name: "Polish" },
|
|
2763
|
+
{ tag: "ru", name: "Russian" },
|
|
2764
|
+
{ tag: "uk", name: "Ukrainian" },
|
|
2765
|
+
{ tag: "tr", name: "Turkish" },
|
|
2766
|
+
{ tag: "ar", name: "Arabic" },
|
|
2767
|
+
{ tag: "he", name: "Hebrew" },
|
|
2768
|
+
{ tag: "fa", name: "Persian" },
|
|
2769
|
+
{ tag: "hi", name: "Hindi" },
|
|
2770
|
+
{ tag: "bn", name: "Bengali" },
|
|
2771
|
+
{ tag: "zh", name: "Chinese" },
|
|
2772
|
+
{ tag: "ja", name: "Japanese" },
|
|
2773
|
+
{ tag: "ko", name: "Korean" },
|
|
2774
|
+
{ tag: "vi", name: "Vietnamese" },
|
|
2775
|
+
{ tag: "th", name: "Thai" },
|
|
2776
|
+
{ tag: "id", name: "Indonesian" },
|
|
2777
|
+
{ tag: "sv", name: "Swedish" }
|
|
2778
|
+
];
|
|
2779
|
+
async function loadMeetingActivities(api, meetingId, meetingPass = null) {
|
|
2780
|
+
let response;
|
|
2781
|
+
try {
|
|
2782
|
+
response = await api.request({
|
|
2783
|
+
path: `${MEET_ROUTES.collab}?meeting_id=${encodeURIComponent(meetingId)}`,
|
|
2784
|
+
method: "GET",
|
|
2785
|
+
operation: "meet.loadMeetingActivities",
|
|
2786
|
+
requireAuth: meetingPass === null,
|
|
2787
|
+
...meetingPass !== null ? { headers: { [MEETING_PASS_HEADER]: meetingPass } } : {}
|
|
2788
|
+
});
|
|
2789
|
+
} catch (error) {
|
|
2790
|
+
return {
|
|
2791
|
+
collab: null,
|
|
2792
|
+
refusal: {
|
|
2793
|
+
message: `Polls, questions and the whiteboard could not be read (${error.message}).`,
|
|
2794
|
+
remedy: error.remedy ?? "Reload the page."
|
|
2795
|
+
}
|
|
2796
|
+
};
|
|
2726
2797
|
}
|
|
2727
|
-
if (
|
|
2728
|
-
|
|
2798
|
+
if (!response.ok) {
|
|
2799
|
+
let body = null;
|
|
2800
|
+
try {
|
|
2801
|
+
body = await response.json();
|
|
2802
|
+
} catch {
|
|
2803
|
+
body = null;
|
|
2804
|
+
}
|
|
2805
|
+
const said = readServerRefusal(body);
|
|
2806
|
+
return {
|
|
2807
|
+
collab: null,
|
|
2808
|
+
refusal: {
|
|
2809
|
+
message: said?.reason ?? `Polls and questions could not be read (the server answered ${response.status}).`,
|
|
2810
|
+
remedy: said?.remedy || "Reload the page."
|
|
2811
|
+
}
|
|
2812
|
+
};
|
|
2813
|
+
}
|
|
2814
|
+
return { collab: collabFromState(await response.json(), EMPTY_COLLAB), refusal: null };
|
|
2729
2815
|
}
|
|
2730
2816
|
|
|
2731
2817
|
// src/core/names.ts
|
|
@@ -2930,6 +3016,7 @@ var NO_GRANTS = {
|
|
|
2930
3016
|
};
|
|
2931
3017
|
var EMPTY = {
|
|
2932
3018
|
phase: "idle",
|
|
3019
|
+
waiting: null,
|
|
2933
3020
|
meeting: null,
|
|
2934
3021
|
localIdentity: null,
|
|
2935
3022
|
grants: NO_GRANTS,
|
|
@@ -3797,7 +3884,10 @@ function orderParticipants(participants, activeSpeakers) {
|
|
|
3797
3884
|
}
|
|
3798
3885
|
function computeStage(args) {
|
|
3799
3886
|
const pageSize = Math.max(1, args.pageSize ?? DEFAULT_PAGE_SIZE);
|
|
3800
|
-
const ordered = orderParticipants(
|
|
3887
|
+
const ordered = orderParticipants(
|
|
3888
|
+
args.participants.filter((participant) => !isObserverRole(participant.role)),
|
|
3889
|
+
args.activeSpeakers
|
|
3890
|
+
);
|
|
3801
3891
|
const sharer = ordered.find((participant) => participant.screenSharing) ?? null;
|
|
3802
3892
|
const pinnedParticipant = args.pinned === null ? null : ordered.find((participant) => participant.identity === args.pinned) ?? null;
|
|
3803
3893
|
const spotlit = args.spotlight == null ? null : ordered.find((participant) => participant.identity === args.spotlight) ?? null;
|
|
@@ -4207,19 +4297,25 @@ function roleOf(participants, identity) {
|
|
|
4207
4297
|
return participants.find((p) => p.identity === identity)?.role ?? "participant";
|
|
4208
4298
|
}
|
|
4209
4299
|
var END_MEETING_NOT_SERVED = "Ending a meeting is not available on this server yet";
|
|
4210
|
-
function lobbyEntryFrom(attendee) {
|
|
4211
|
-
if (attendee.admissionState
|
|
4300
|
+
function lobbyEntryFrom(attendee, now = Date.now()) {
|
|
4301
|
+
if (!WAITING_ADMISSION_STATES.includes(attendee.admissionState)) return null;
|
|
4302
|
+
const expires = attendee.knockExpiresAt ? Date.parse(attendee.knockExpiresAt) : Number.NaN;
|
|
4303
|
+
if (!Number.isNaN(expires) && expires <= now) return null;
|
|
4212
4304
|
return {
|
|
4213
4305
|
identity: attendee.identity,
|
|
4214
4306
|
displayName: attendee.displayName,
|
|
4215
4307
|
avatarUrl: attendee.avatarUrl,
|
|
4216
4308
|
userId: attendee.userId,
|
|
4217
4309
|
state: "knocking",
|
|
4218
|
-
knockedAt: attendee.joinedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
4310
|
+
knockedAt: attendee.knockedAt ?? attendee.joinedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
4311
|
+
knockExpiresAt: attendee.knockExpiresAt ?? null,
|
|
4219
4312
|
decidedAt: null,
|
|
4220
4313
|
decidedBy: null
|
|
4221
4314
|
};
|
|
4222
4315
|
}
|
|
4316
|
+
function decidedState(state) {
|
|
4317
|
+
return WAITING_ADMISSION_STATES.includes(state) ? "expired" : state;
|
|
4318
|
+
}
|
|
4223
4319
|
function createMeetingSession(options) {
|
|
4224
4320
|
const { manager, repository, store, identity } = options;
|
|
4225
4321
|
let channel = null;
|
|
@@ -4243,11 +4339,6 @@ function createMeetingSession(options) {
|
|
|
4243
4339
|
}
|
|
4244
4340
|
return identity.userId;
|
|
4245
4341
|
}
|
|
4246
|
-
function knockIdentity() {
|
|
4247
|
-
return asParticipantIdentity(
|
|
4248
|
-
identity.userId !== null ? `user:${identity.userId}` : `guest:${identity.deviceId}`
|
|
4249
|
-
);
|
|
4250
|
-
}
|
|
4251
4342
|
function requireMeeting(operation) {
|
|
4252
4343
|
const meeting = store.snapshot().meeting;
|
|
4253
4344
|
if (meeting === null) {
|
|
@@ -4259,6 +4350,191 @@ function createMeetingSession(options) {
|
|
|
4259
4350
|
}
|
|
4260
4351
|
return meeting;
|
|
4261
4352
|
}
|
|
4353
|
+
let polling = false;
|
|
4354
|
+
let pollTimer = null;
|
|
4355
|
+
let reading = null;
|
|
4356
|
+
let admittedOnce = false;
|
|
4357
|
+
let unlisten = null;
|
|
4358
|
+
function report(level, message, remedy) {
|
|
4359
|
+
options.onDiagnostic?.(remedy === void 0 ? { level, message } : { level, message, remedy });
|
|
4360
|
+
}
|
|
4361
|
+
function pollMs() {
|
|
4362
|
+
const seconds = store.snapshot().waiting?.pollSeconds ?? 3;
|
|
4363
|
+
return Math.max(1, Math.min(60, seconds)) * 1e3;
|
|
4364
|
+
}
|
|
4365
|
+
function schedule(ms2) {
|
|
4366
|
+
if (pollTimer !== null) clearTimeout(pollTimer);
|
|
4367
|
+
if (!polling) return;
|
|
4368
|
+
pollTimer = setTimeout(() => {
|
|
4369
|
+
pollTimer = null;
|
|
4370
|
+
void readState2().finally(() => schedule(pollMs()));
|
|
4371
|
+
}, ms2);
|
|
4372
|
+
}
|
|
4373
|
+
function listen() {
|
|
4374
|
+
if (unlisten !== null || typeof window === "undefined") return;
|
|
4375
|
+
const nudge = () => {
|
|
4376
|
+
if (typeof document !== "undefined" && document.visibilityState === "hidden") return;
|
|
4377
|
+
void readState2();
|
|
4378
|
+
};
|
|
4379
|
+
const goodbye = () => {
|
|
4380
|
+
const meeting = store.snapshot().meeting;
|
|
4381
|
+
const pass = options.resolvePass?.() ?? null;
|
|
4382
|
+
const waiting = store.snapshot().waiting;
|
|
4383
|
+
if (meeting === null || pass === null || waiting === null) return;
|
|
4384
|
+
if (!WAITING_ADMISSION_STATES.includes(waiting.admission)) return;
|
|
4385
|
+
try {
|
|
4386
|
+
navigator.sendBeacon?.(
|
|
4387
|
+
options.api.url(MEET_ROUTES.leave),
|
|
4388
|
+
new Blob([JSON.stringify({ meeting_id: meeting.id, intent: "withdraw", meeting_pass: pass })], {
|
|
4389
|
+
type: "application/json"
|
|
4390
|
+
})
|
|
4391
|
+
);
|
|
4392
|
+
} catch {
|
|
4393
|
+
}
|
|
4394
|
+
};
|
|
4395
|
+
window.addEventListener("online", nudge);
|
|
4396
|
+
window.addEventListener("pagehide", goodbye);
|
|
4397
|
+
document.addEventListener("visibilitychange", nudge);
|
|
4398
|
+
unlisten = () => {
|
|
4399
|
+
window.removeEventListener("online", nudge);
|
|
4400
|
+
window.removeEventListener("pagehide", goodbye);
|
|
4401
|
+
document.removeEventListener("visibilitychange", nudge);
|
|
4402
|
+
};
|
|
4403
|
+
}
|
|
4404
|
+
function stopWaiting() {
|
|
4405
|
+
polling = false;
|
|
4406
|
+
if (pollTimer !== null) clearTimeout(pollTimer);
|
|
4407
|
+
pollTimer = null;
|
|
4408
|
+
unlisten?.();
|
|
4409
|
+
unlisten = null;
|
|
4410
|
+
}
|
|
4411
|
+
function patchWaiting(next) {
|
|
4412
|
+
const current = store.snapshot().waiting;
|
|
4413
|
+
const base = current ?? {
|
|
4414
|
+
admission: "knocking",
|
|
4415
|
+
admittersPresent: null,
|
|
4416
|
+
reknockAllowed: true,
|
|
4417
|
+
knockExpiresAt: null,
|
|
4418
|
+
pollSeconds: 3,
|
|
4419
|
+
liveVersion: 0
|
|
4420
|
+
};
|
|
4421
|
+
store.patch({ waiting: { ...base, ...next } });
|
|
4422
|
+
}
|
|
4423
|
+
function apply(read) {
|
|
4424
|
+
const snapshot = store.snapshot();
|
|
4425
|
+
if (snapshot.phase !== "lobby") return;
|
|
4426
|
+
const admission = read.admission_state ?? null;
|
|
4427
|
+
const ended = admission === "ended" || read.session_phase === "ended" || read.meeting_phase === "ended";
|
|
4428
|
+
if (admission === "admitted" && !ended) {
|
|
4429
|
+
if (admittedOnce) return;
|
|
4430
|
+
admittedOnce = true;
|
|
4431
|
+
stopWaiting();
|
|
4432
|
+
options.onAdmitted?.();
|
|
4433
|
+
return;
|
|
4434
|
+
}
|
|
4435
|
+
if (ended) {
|
|
4436
|
+
stopWaiting();
|
|
4437
|
+
patchWaiting({ admission: "ended" });
|
|
4438
|
+
const meeting = snapshot.meeting;
|
|
4439
|
+
if (read.meeting_phase === "ended" && meeting !== null && meeting.endedAt === null) {
|
|
4440
|
+
store.patch({ meeting: { ...meeting, endedAt: (/* @__PURE__ */ new Date()).toISOString() } });
|
|
4441
|
+
}
|
|
4442
|
+
return;
|
|
4443
|
+
}
|
|
4444
|
+
if (admission === null) return;
|
|
4445
|
+
patchWaiting({
|
|
4446
|
+
admission,
|
|
4447
|
+
admittersPresent: read.admitters_present ?? null,
|
|
4448
|
+
reknockAllowed: read.reknock_allowed ?? true,
|
|
4449
|
+
knockExpiresAt: read.knock_expires_at ?? null,
|
|
4450
|
+
pollSeconds: read.poll_seconds ?? snapshot.waiting?.pollSeconds ?? 3,
|
|
4451
|
+
liveVersion: read.live_version ?? 0
|
|
4452
|
+
});
|
|
4453
|
+
if (admission === "denied" || admission === "expired" || admission === "withdrawn" || admission === "removed") {
|
|
4454
|
+
stopWaiting();
|
|
4455
|
+
}
|
|
4456
|
+
}
|
|
4457
|
+
async function readState2() {
|
|
4458
|
+
if (reading !== null) return reading;
|
|
4459
|
+
const meeting = store.snapshot().meeting;
|
|
4460
|
+
const pass = options.resolvePass?.() ?? null;
|
|
4461
|
+
if (meeting === null || pass === null) return;
|
|
4462
|
+
reading = (async () => {
|
|
4463
|
+
try {
|
|
4464
|
+
const response = await options.api.request({
|
|
4465
|
+
path: `${MEET_ROUTES.state}?meeting_id=${encodeURIComponent(meeting.id)}`,
|
|
4466
|
+
method: "GET",
|
|
4467
|
+
requireAuth: false,
|
|
4468
|
+
operation: "meet.state",
|
|
4469
|
+
headers: { [MEETING_PASS_HEADER]: pass }
|
|
4470
|
+
});
|
|
4471
|
+
if (!response.ok) {
|
|
4472
|
+
let body = null;
|
|
4473
|
+
try {
|
|
4474
|
+
body = await response.json();
|
|
4475
|
+
} catch {
|
|
4476
|
+
body = null;
|
|
4477
|
+
}
|
|
4478
|
+
const refusal2 = readServerRefusal(body);
|
|
4479
|
+
report(
|
|
4480
|
+
"warn",
|
|
4481
|
+
refusal2?.reason ?? `meet.state: the server answered ${response.status}`,
|
|
4482
|
+
refusal2?.remedy ?? "The waiting room keeps asking; nothing on this screen changes until the server answers."
|
|
4483
|
+
);
|
|
4484
|
+
return;
|
|
4485
|
+
}
|
|
4486
|
+
apply(await response.json());
|
|
4487
|
+
} catch (error) {
|
|
4488
|
+
report("info", `meet.state: ${String(error)}`, "Waiting continues; the state is read again shortly.");
|
|
4489
|
+
} finally {
|
|
4490
|
+
reading = null;
|
|
4491
|
+
}
|
|
4492
|
+
})();
|
|
4493
|
+
return reading;
|
|
4494
|
+
}
|
|
4495
|
+
async function postLeave(intent) {
|
|
4496
|
+
const meeting = requireMeeting(`meet.${intent}`);
|
|
4497
|
+
const pass = options.resolvePass?.() ?? null;
|
|
4498
|
+
const response = await options.api.request({
|
|
4499
|
+
path: MEET_ROUTES.leave,
|
|
4500
|
+
method: "POST",
|
|
4501
|
+
requireAuth: false,
|
|
4502
|
+
operation: `meet.${intent}`,
|
|
4503
|
+
body: { meeting_id: meeting.id, intent },
|
|
4504
|
+
...pass !== null ? { headers: { [MEETING_PASS_HEADER]: pass } } : {}
|
|
4505
|
+
});
|
|
4506
|
+
if (!response.ok) {
|
|
4507
|
+
let body = null;
|
|
4508
|
+
try {
|
|
4509
|
+
body = await response.json();
|
|
4510
|
+
} catch {
|
|
4511
|
+
body = null;
|
|
4512
|
+
}
|
|
4513
|
+
const refusal2 = readServerRefusal(body);
|
|
4514
|
+
throw new MeetError(
|
|
4515
|
+
"transport",
|
|
4516
|
+
refusal2?.reason ?? `meet.${intent}: the server answered ${response.status}`,
|
|
4517
|
+
refusal2?.remedy ?? "You are still in the waiting room. Try again."
|
|
4518
|
+
);
|
|
4519
|
+
}
|
|
4520
|
+
}
|
|
4521
|
+
function hint() {
|
|
4522
|
+
channel?.send(MEET_EVENTS.stateChanged, { v: null });
|
|
4523
|
+
}
|
|
4524
|
+
async function refreshQueue() {
|
|
4525
|
+
const meeting = store.snapshot().meeting;
|
|
4526
|
+
if (meeting === null || isGuest(identity) || !isHostRole(effectiveRole(store.snapshot()))) return;
|
|
4527
|
+
try {
|
|
4528
|
+
const attendees = await repository.attendees(meeting.id);
|
|
4529
|
+
store.learn(attendees);
|
|
4530
|
+
for (const attendee of attendees) {
|
|
4531
|
+
const entry = lobbyEntryFrom(attendee);
|
|
4532
|
+
if (entry !== null) store.upsertLobby(entry);
|
|
4533
|
+
else store.setAdmission(attendee.identity, decidedState(attendee.admissionState), null);
|
|
4534
|
+
}
|
|
4535
|
+
} catch {
|
|
4536
|
+
}
|
|
4537
|
+
}
|
|
4262
4538
|
return {
|
|
4263
4539
|
watch(roomName) {
|
|
4264
4540
|
channel?.close();
|
|
@@ -4266,58 +4542,22 @@ function createMeetingSession(options) {
|
|
|
4266
4542
|
topic: roomTopic(roomName),
|
|
4267
4543
|
broadcast: [
|
|
4268
4544
|
{
|
|
4269
|
-
|
|
4270
|
-
|
|
4271
|
-
|
|
4272
|
-
|
|
4273
|
-
|
|
4274
|
-
{
|
|
4275
|
-
event: MEET_EVENTS.admission,
|
|
4276
|
-
onMessage: (message) => {
|
|
4277
|
-
const verdict = message.data;
|
|
4278
|
-
const decided = asParticipantIdentity(verdict.identity);
|
|
4279
|
-
store.setAdmission(
|
|
4280
|
-
decided,
|
|
4281
|
-
verdict.admitted ? "admitted" : "denied",
|
|
4282
|
-
verdict.by === null ? null : verdict.by
|
|
4283
|
-
);
|
|
4284
|
-
if (decided === knockIdentity()) {
|
|
4285
|
-
if (verdict.admitted) options.onAdmitted?.();
|
|
4286
|
-
else options.onDenied?.();
|
|
4287
|
-
}
|
|
4288
|
-
}
|
|
4289
|
-
},
|
|
4290
|
-
{
|
|
4291
|
-
event: MEET_EVENTS.lock,
|
|
4292
|
-
onMessage: (message) => {
|
|
4293
|
-
const { locked } = message.data;
|
|
4294
|
-
const meeting = store.snapshot().meeting;
|
|
4295
|
-
store.patch({
|
|
4296
|
-
locked,
|
|
4297
|
-
...meeting !== null ? { meeting: { ...meeting, locked } } : {}
|
|
4298
|
-
});
|
|
4299
|
-
}
|
|
4300
|
-
},
|
|
4301
|
-
{
|
|
4302
|
-
event: MEET_EVENTS.recording,
|
|
4303
|
-
onMessage: (message) => {
|
|
4304
|
-
const payload = message.data;
|
|
4305
|
-
store.setRecording(
|
|
4306
|
-
payload.state,
|
|
4307
|
-
payload.by === null ? null : payload.by,
|
|
4308
|
-
"room"
|
|
4309
|
-
);
|
|
4545
|
+
// 🚨 PAYLOAD-FREE. Every receiver reads again; a spoofed hint only causes a read.
|
|
4546
|
+
event: MEET_EVENTS.stateChanged,
|
|
4547
|
+
onMessage: () => {
|
|
4548
|
+
if (store.snapshot().phase === "lobby") void readState2();
|
|
4549
|
+
void refreshQueue();
|
|
4310
4550
|
}
|
|
4311
4551
|
}
|
|
4312
4552
|
],
|
|
4313
|
-
// THE BACKFILL DOOR. A
|
|
4314
|
-
//
|
|
4315
|
-
// re-announce on their own timer.
|
|
4553
|
+
// THE BACKFILL DOOR. A socket blip must not lose a decision: re-read the meeting
|
|
4554
|
+
// row, and a waiting tab re-reads its own state.
|
|
4316
4555
|
onBackfill: async () => {
|
|
4317
4556
|
const meeting = store.snapshot().meeting;
|
|
4318
4557
|
if (meeting === null) return;
|
|
4319
4558
|
const fresh = isGuest(identity) ? await repository.meetingBySlug(meeting.slug) : await readMeeting(meeting.id);
|
|
4320
4559
|
store.patch({ meeting: fresh, locked: fresh.locked });
|
|
4560
|
+
if (store.snapshot().phase === "lobby") void readState2();
|
|
4321
4561
|
}
|
|
4322
4562
|
});
|
|
4323
4563
|
},
|
|
@@ -4378,7 +4618,7 @@ function createMeetingSession(options) {
|
|
|
4378
4618
|
store.upsertLobby(entry);
|
|
4379
4619
|
return;
|
|
4380
4620
|
}
|
|
4381
|
-
store.setAdmission(attendee.identity, attendee.admissionState, null);
|
|
4621
|
+
store.setAdmission(attendee.identity, decidedState(attendee.admissionState), null);
|
|
4382
4622
|
}
|
|
4383
4623
|
},
|
|
4384
4624
|
{
|
|
@@ -4430,62 +4670,75 @@ function createMeetingSession(options) {
|
|
|
4430
4670
|
if (entry !== null) {
|
|
4431
4671
|
store.upsertLobby(entry);
|
|
4432
4672
|
} else {
|
|
4433
|
-
store.setAdmission(attendee.identity, attendee.admissionState, null);
|
|
4673
|
+
store.setAdmission(attendee.identity, decidedState(attendee.admissionState), null);
|
|
4434
4674
|
}
|
|
4435
4675
|
}
|
|
4436
4676
|
}
|
|
4437
4677
|
});
|
|
4438
4678
|
meetingChannel.requestBackfill("manual");
|
|
4439
4679
|
},
|
|
4440
|
-
knock(roomName) {
|
|
4441
|
-
const handle = channel ?? manager.open({ topic: roomTopic(roomName) });
|
|
4442
|
-
channel = handle;
|
|
4443
|
-
const entry = {
|
|
4444
|
-
identity: knockIdentity(),
|
|
4445
|
-
displayName: identity.displayName,
|
|
4446
|
-
avatarUrl: identity.avatarUrl,
|
|
4447
|
-
userId: identity.userId,
|
|
4448
|
-
state: "knocking",
|
|
4449
|
-
knockedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4450
|
-
decidedAt: null,
|
|
4451
|
-
decidedBy: null
|
|
4452
|
-
};
|
|
4453
|
-
store.upsertLobby(entry);
|
|
4454
|
-
handle.send(MEET_EVENTS.knock, entry);
|
|
4455
|
-
},
|
|
4456
4680
|
async admit(entry, admitted) {
|
|
4457
4681
|
const by = requireHostUser("meet.admit");
|
|
4458
4682
|
const meeting = requireMeeting("meet.admit");
|
|
4459
|
-
await repository.
|
|
4683
|
+
const row = await repository.decideAdmission({
|
|
4460
4684
|
meetingId: meeting.id,
|
|
4461
4685
|
identity: entry.identity,
|
|
4462
|
-
admitted,
|
|
4686
|
+
decision: admitted ? "admit" : "deny",
|
|
4463
4687
|
byUserId: by
|
|
4464
4688
|
});
|
|
4465
4689
|
store.upsertLobby({
|
|
4466
4690
|
...entry,
|
|
4467
|
-
state:
|
|
4691
|
+
state: decidedState(row.admissionState),
|
|
4468
4692
|
decidedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4469
4693
|
decidedBy: by
|
|
4470
4694
|
});
|
|
4471
|
-
|
|
4695
|
+
hint();
|
|
4696
|
+
},
|
|
4697
|
+
async undeny(entry) {
|
|
4698
|
+
const by = requireHostUser("meet.undeny");
|
|
4699
|
+
const meeting = requireMeeting("meet.undeny");
|
|
4700
|
+
const row = await repository.decideAdmission({
|
|
4701
|
+
meetingId: meeting.id,
|
|
4472
4702
|
identity: entry.identity,
|
|
4473
|
-
|
|
4474
|
-
by
|
|
4703
|
+
decision: "undeny",
|
|
4704
|
+
byUserId: by
|
|
4475
4705
|
});
|
|
4706
|
+
store.upsertLobby({ ...entry, state: decidedState(row.admissionState), decidedAt: (/* @__PURE__ */ new Date()).toISOString(), decidedBy: by });
|
|
4707
|
+
hint();
|
|
4476
4708
|
},
|
|
4477
4709
|
async admitAll() {
|
|
4478
|
-
const
|
|
4479
|
-
|
|
4480
|
-
|
|
4710
|
+
const by = requireHostUser("meet.admitAll");
|
|
4711
|
+
const meeting = requireMeeting("meet.admitAll");
|
|
4712
|
+
const rows = await repository.admitAll(meeting.id, by);
|
|
4713
|
+
for (const row of rows) {
|
|
4714
|
+
store.setAdmission(row.identity, row.admissionState, by);
|
|
4481
4715
|
}
|
|
4716
|
+
hint();
|
|
4717
|
+
},
|
|
4718
|
+
startWaiting() {
|
|
4719
|
+
if (polling) return;
|
|
4720
|
+
polling = true;
|
|
4721
|
+
admittedOnce = false;
|
|
4722
|
+
listen();
|
|
4723
|
+
schedule(0);
|
|
4724
|
+
},
|
|
4725
|
+
stopWaiting,
|
|
4726
|
+
readState: readState2,
|
|
4727
|
+
async withdraw() {
|
|
4728
|
+
stopWaiting();
|
|
4729
|
+
await postLeave("withdraw");
|
|
4730
|
+
patchWaiting({ admission: "withdrawn" });
|
|
4731
|
+
hint();
|
|
4732
|
+
},
|
|
4733
|
+
async askAgain() {
|
|
4734
|
+
await options.rejoin?.();
|
|
4482
4735
|
},
|
|
4483
4736
|
async setLock(locked) {
|
|
4484
4737
|
const by = requireHostUser("meet.setLock");
|
|
4485
4738
|
const meeting = requireMeeting("meet.setLock");
|
|
4486
4739
|
const updated = await repository.setLock(meeting.id, locked, by);
|
|
4487
4740
|
store.patch({ meeting: updated, locked: updated.locked });
|
|
4488
|
-
|
|
4741
|
+
hint();
|
|
4489
4742
|
return updated;
|
|
4490
4743
|
},
|
|
4491
4744
|
/**
|
|
@@ -4543,6 +4796,7 @@ function createMeetingSession(options) {
|
|
|
4543
4796
|
return { endedAt, served: true };
|
|
4544
4797
|
},
|
|
4545
4798
|
dispose() {
|
|
4799
|
+
stopWaiting();
|
|
4546
4800
|
channel?.close();
|
|
4547
4801
|
channel = null;
|
|
4548
4802
|
meetingChannel?.close();
|
|
@@ -5403,13 +5657,14 @@ function createRoomEngine(options) {
|
|
|
5403
5657
|
}
|
|
5404
5658
|
};
|
|
5405
5659
|
async function connect(request) {
|
|
5406
|
-
store.patch({ phase: "requesting-token", failure: null });
|
|
5660
|
+
store.patch({ phase: "requesting-token", failure: null, waiting: null });
|
|
5407
5661
|
const token = await tokens.get({
|
|
5408
5662
|
roomName: request.roomName,
|
|
5409
5663
|
identity,
|
|
5410
5664
|
...request.meetingId !== void 0 ? { meetingId: request.meetingId } : {},
|
|
5411
5665
|
...request.slug !== void 0 ? { slug: request.slug } : {},
|
|
5412
|
-
...request.breakoutRoomId != null ? { breakoutRoomId: request.breakoutRoomId } : {}
|
|
5666
|
+
...request.breakoutRoomId != null ? { breakoutRoomId: request.breakoutRoomId } : {},
|
|
5667
|
+
...request.observe === true ? { observe: true } : {}
|
|
5413
5668
|
});
|
|
5414
5669
|
current = token;
|
|
5415
5670
|
store.patch({
|
|
@@ -5420,14 +5675,25 @@ function createRoomEngine(options) {
|
|
|
5420
5675
|
standing: token.standing,
|
|
5421
5676
|
canRecord: token.canRecord,
|
|
5422
5677
|
canPublish: token.canPublish,
|
|
5423
|
-
canSubscribe: token.canSubscribe
|
|
5678
|
+
canSubscribe: token.canSubscribe,
|
|
5679
|
+
observersVisibleTo: token.observersVisibleTo ?? "everyone"
|
|
5424
5680
|
}
|
|
5425
5681
|
});
|
|
5426
|
-
if (token.lobby) {
|
|
5427
|
-
store.patch({
|
|
5682
|
+
if (token.lobby || !token.canSubscribe) {
|
|
5683
|
+
store.patch({
|
|
5684
|
+
phase: "lobby",
|
|
5685
|
+
waiting: {
|
|
5686
|
+
admission: token.admissionState !== void 0 && token.admissionState !== "admitted" ? token.admissionState : "knocking",
|
|
5687
|
+
admittersPresent: null,
|
|
5688
|
+
reknockAllowed: true,
|
|
5689
|
+
knockExpiresAt: null,
|
|
5690
|
+
pollSeconds: token.pollSeconds ?? 3,
|
|
5691
|
+
liveVersion: 0
|
|
5692
|
+
}
|
|
5693
|
+
});
|
|
5428
5694
|
return;
|
|
5429
5695
|
}
|
|
5430
|
-
store.patch({ phase: "connecting" });
|
|
5696
|
+
store.patch({ phase: "connecting", waiting: null });
|
|
5431
5697
|
const created = await options.driverFactory(handlers);
|
|
5432
5698
|
if (disposed) {
|
|
5433
5699
|
await created.disconnect();
|
|
@@ -5910,7 +6176,9 @@ function createInRoomClient(options) {
|
|
|
5910
6176
|
...request.allowChoose !== void 0 ? { allow_choose: request.allowChoose } : {},
|
|
5911
6177
|
...request.minutes !== void 0 ? { minutes: request.minutes } : {},
|
|
5912
6178
|
...request.identity !== void 0 ? { identity: request.identity } : {},
|
|
5913
|
-
...request.roomId !== void 0 ? { room_id: request.roomId } : {}
|
|
6179
|
+
...request.roomId !== void 0 ? { room_id: request.roomId } : {},
|
|
6180
|
+
...request.panel !== void 0 ? { panel: request.panel } : {},
|
|
6181
|
+
...request.section !== void 0 ? { section: request.section } : {}
|
|
5914
6182
|
},
|
|
5915
6183
|
...hdrs !== void 0 ? { headers: hdrs } : {}
|
|
5916
6184
|
});
|
|
@@ -6686,7 +6954,7 @@ function observedPhase(input) {
|
|
|
6686
6954
|
case "left":
|
|
6687
6955
|
return "prejoin";
|
|
6688
6956
|
case "lobby":
|
|
6689
|
-
return
|
|
6957
|
+
return waitingPhase(snapshot);
|
|
6690
6958
|
case "requesting-token":
|
|
6691
6959
|
case "connecting":
|
|
6692
6960
|
return "joining";
|
|
@@ -6695,6 +6963,24 @@ function observedPhase(input) {
|
|
|
6695
6963
|
return "in_call";
|
|
6696
6964
|
}
|
|
6697
6965
|
}
|
|
6966
|
+
function waitingPhase(snapshot) {
|
|
6967
|
+
switch (snapshot.waiting?.admission) {
|
|
6968
|
+
case "waiting_host":
|
|
6969
|
+
return "waiting:host_not_started";
|
|
6970
|
+
case "denied":
|
|
6971
|
+
return "denied";
|
|
6972
|
+
case "expired":
|
|
6973
|
+
return "knock_expired";
|
|
6974
|
+
case "withdrawn":
|
|
6975
|
+
return "left";
|
|
6976
|
+
case "ended":
|
|
6977
|
+
return "ended";
|
|
6978
|
+
case "removed":
|
|
6979
|
+
return "refused:removed";
|
|
6980
|
+
default:
|
|
6981
|
+
return "waiting:knocking";
|
|
6982
|
+
}
|
|
6983
|
+
}
|
|
6698
6984
|
var ADMITTER_ROLES = /* @__PURE__ */ new Set(["host", "cohost"]);
|
|
6699
6985
|
function observe(input) {
|
|
6700
6986
|
const { snapshot } = input;
|
|
@@ -6709,7 +6995,7 @@ function observe(input) {
|
|
|
6709
6995
|
reason: phase.startsWith("refused:") ? phase.slice("refused:".length) : null,
|
|
6710
6996
|
role,
|
|
6711
6997
|
connection: snapshot?.phase === "reconnecting" ? "reconnecting" : "stable",
|
|
6712
|
-
hostPresent: inCall && snapshot !== null ? snapshot.participants.some((p) => ADMITTER_ROLES.has(p.role)) : null,
|
|
6998
|
+
hostPresent: inCall && snapshot !== null ? snapshot.participants.some((p) => ADMITTER_ROLES.has(p.role)) : snapshot?.phase === "lobby" ? snapshot.waiting?.admittersPresent ?? null : null,
|
|
6713
6999
|
lobbyCount: canAdmit && snapshot !== null ? snapshot.lobby.filter((entry) => entry.state === "knocking").length : 0,
|
|
6714
7000
|
recording: snapshot?.recording === "recording" || snapshot?.recording === "stopping",
|
|
6715
7001
|
audioBlocked: null,
|