@ai-matrx/meet 0.7.1 → 0.7.3
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 +30 -0
- package/dist/index.cjs +471 -37
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +268 -3
- package/dist/index.d.ts +268 -3
- package/dist/index.js +471 -37
- package/dist/index.js.map +1 -1
- package/dist/react.cjs +474 -40
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +1 -1
- package/dist/react.d.ts +1 -1
- package/dist/react.js +474 -40
- package/dist/react.js.map +1 -1
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -25,7 +25,7 @@ var MeetError = class extends Error {
|
|
|
25
25
|
};
|
|
26
26
|
var FORBIDDEN_CODES = /* @__PURE__ */ new Set(["42501", "PGRST301", "PGRST302"]);
|
|
27
27
|
var NOT_FOUND_CODES = /* @__PURE__ */ new Set(["PGRST116", "PGRST205"]);
|
|
28
|
-
var CONFLICT_CODES = /* @__PURE__ */ new Set(["23505"]);
|
|
28
|
+
var CONFLICT_CODES = /* @__PURE__ */ new Set(["23505", "40001", "55000"]);
|
|
29
29
|
var SESSION_MARKERS = [
|
|
30
30
|
"auth session missing",
|
|
31
31
|
"jwt expired",
|
|
@@ -183,8 +183,19 @@ var MEET_ROUTES = {
|
|
|
183
183
|
*/
|
|
184
184
|
record: "/api/v1/meet/record",
|
|
185
185
|
/** A host chooses what guests may read of ONE meeting (MRF-1). */
|
|
186
|
-
recordAccess: "/api/v1/meet/record/access"
|
|
186
|
+
recordAccess: "/api/v1/meet/record/access",
|
|
187
|
+
/**
|
|
188
|
+
* Meet wave 1 (0.8): bring every invitee up to date after a write — sends the
|
|
189
|
+
* invitation / update / cancellation emails with their `.ics` and re-queues
|
|
190
|
+
* reminders. Idempotent. `{meeting_id}` is substituted by `meetingPath`.
|
|
191
|
+
*/
|
|
192
|
+
meetingAnnounce: "/api/v1/meet/meetings/{meeting_id}/announce",
|
|
193
|
+
/** Put a one-off meeting on the caller's own Google Calendar (gated by the server). */
|
|
194
|
+
meetingGoogleCalendar: "/api/v1/meet/meetings/{meeting_id}/google-calendar"
|
|
187
195
|
};
|
|
196
|
+
function meetingPath(template, meetingId) {
|
|
197
|
+
return template.replace("{meeting_id}", encodeURIComponent(meetingId));
|
|
198
|
+
}
|
|
188
199
|
var ROOM_TOKEN_HEADER = "x-meet-room-token";
|
|
189
200
|
var REFRESH_RATIO = 0.8;
|
|
190
201
|
function readString(source, key) {
|
|
@@ -314,6 +325,19 @@ function createTokenClient(options) {
|
|
|
314
325
|
"The link is wrong or the meeting was deleted. Show the 'meeting not found' screen; a durable link that 404s is never a retry."
|
|
315
326
|
);
|
|
316
327
|
}
|
|
328
|
+
if (response.status === 410) {
|
|
329
|
+
let reason = "This meeting was cancelled.";
|
|
330
|
+
try {
|
|
331
|
+
const body = await response.json();
|
|
332
|
+
if (typeof body?.detail?.message === "string") reason = body.detail.message;
|
|
333
|
+
} catch {
|
|
334
|
+
}
|
|
335
|
+
throw new MeetError(
|
|
336
|
+
"cancelled",
|
|
337
|
+
reason,
|
|
338
|
+
"Nobody can join a cancelled meeting. Show the cancelled screen with this sentence; ask the host to schedule a new one."
|
|
339
|
+
);
|
|
340
|
+
}
|
|
317
341
|
if (response.status === 423) {
|
|
318
342
|
throw new MeetError(
|
|
319
343
|
"room-locked",
|
|
@@ -515,12 +539,12 @@ function createMeetAi(options) {
|
|
|
515
539
|
async function dispatch(route, body, operation) {
|
|
516
540
|
const response = await send({ path: route, body, operation, requireAuth: true });
|
|
517
541
|
if (!response.ok) {
|
|
518
|
-
const
|
|
519
|
-
options.store.patch({ noteTakerRefusal:
|
|
542
|
+
const refusal2 = await refusalFor(response, operation);
|
|
543
|
+
options.store.patch({ noteTakerRefusal: refusal2 });
|
|
520
544
|
throw new MeetError(
|
|
521
545
|
response.status === 403 ? "forbidden" : "transport",
|
|
522
|
-
|
|
523
|
-
|
|
546
|
+
refusal2.reason,
|
|
547
|
+
refusal2.remedy
|
|
524
548
|
);
|
|
525
549
|
}
|
|
526
550
|
options.store.patch({ noteTakerRefusal: null });
|
|
@@ -558,11 +582,11 @@ function createMeetAi(options) {
|
|
|
558
582
|
...signal !== void 0 ? { signal } : {}
|
|
559
583
|
});
|
|
560
584
|
if (!response.ok) {
|
|
561
|
-
const
|
|
585
|
+
const refusal2 = await refusalFor(response, "meet.ai.ask");
|
|
562
586
|
throw new MeetError(
|
|
563
587
|
response.status === 403 || response.status === 401 ? "forbidden" : response.status === 404 ? "not-found" : "transport",
|
|
564
|
-
|
|
565
|
-
|
|
588
|
+
refusal2.reason,
|
|
589
|
+
refusal2.remedy
|
|
566
590
|
);
|
|
567
591
|
}
|
|
568
592
|
const body = response.body;
|
|
@@ -671,14 +695,14 @@ function noteTakerStatus(args) {
|
|
|
671
695
|
const row = typeof raw === "object" && raw !== null && !Array.isArray(raw) ? raw : {};
|
|
672
696
|
const recorded = readState(row["state"]);
|
|
673
697
|
const detail = typeof row["reason"] === "string" && row["reason"].length > 0 ? row["reason"] : null;
|
|
674
|
-
const
|
|
698
|
+
const refusal2 = args.refusal ?? null;
|
|
675
699
|
if (recorded === null) {
|
|
676
|
-
return { state: "absent", detail, present, refusal };
|
|
700
|
+
return { state: "absent", detail, present, refusal: refusal2 };
|
|
677
701
|
}
|
|
678
702
|
if (recorded === "transcribing" && !present) {
|
|
679
|
-
return { state: "handoff_pending", detail, present, refusal };
|
|
703
|
+
return { state: "handoff_pending", detail, present, refusal: refusal2 };
|
|
680
704
|
}
|
|
681
|
-
return { state: recorded, detail, present, refusal };
|
|
705
|
+
return { state: recorded, detail, present, refusal: refusal2 };
|
|
682
706
|
}
|
|
683
707
|
function noteTakerLabel(status) {
|
|
684
708
|
switch (status.state) {
|
|
@@ -1091,7 +1115,11 @@ var TABLES = {
|
|
|
1091
1115
|
invites: "meet_call_invites",
|
|
1092
1116
|
recordings: "meet_recordings",
|
|
1093
1117
|
transcripts: "meet_transcript_segments",
|
|
1094
|
-
notes: "meet_notes"
|
|
1118
|
+
notes: "meet_notes",
|
|
1119
|
+
/** Wave 1 (0.8): who a meeting was sent to, and what they answered. */
|
|
1120
|
+
invitees: "meet_invitees",
|
|
1121
|
+
/** Wave 1 (0.8): one occurrence of a series cancelled or moved. */
|
|
1122
|
+
occurrenceOverrides: "meet_occurrence_overrides"
|
|
1095
1123
|
};
|
|
1096
1124
|
var RPCS = {
|
|
1097
1125
|
/** Atomic: create the invite row and stamp its expiry in one statement. */
|
|
@@ -1107,10 +1135,41 @@ var RPCS = {
|
|
|
1107
1135
|
admit: "meet_admit_participant",
|
|
1108
1136
|
setLock: "meet_set_lock",
|
|
1109
1137
|
endMeeting: "meet_end_meeting",
|
|
1110
|
-
recordConsent: "meet_record_consent"
|
|
1138
|
+
recordConsent: "meet_record_consent",
|
|
1139
|
+
/*
|
|
1140
|
+
* ── MEET WAVE 1 — management (0.8). Every write is a SECURITY DEFINER door
|
|
1141
|
+
* authorized by the meeting's host or a real iam grant (editor to change,
|
|
1142
|
+
* admin = co-host to archive or name a co-host) — never an organization
|
|
1143
|
+
* match. After any of these writes, call `announceMeeting` so the server
|
|
1144
|
+
* sends invitations / updates / cancellations and re-queues reminders.
|
|
1145
|
+
*/
|
|
1146
|
+
/** Schedule with every setting at create; unset settings come from the knobs. */
|
|
1147
|
+
scheduleMeeting: "meet_schedule_meeting",
|
|
1148
|
+
/** Edit / reschedule — a JSON patch with a version compare-and-swap. */
|
|
1149
|
+
updateMeeting: "meet_update_meeting",
|
|
1150
|
+
/** Cancel is a state (`cancelled_at`); the link keeps resolving. */
|
|
1151
|
+
cancelMeeting: "meet_cancel_meeting",
|
|
1152
|
+
/** Archive (soft delete) — the invitations and exceptions cascade. */
|
|
1153
|
+
archiveMeeting: "meet_archive_meeting",
|
|
1154
|
+
restoreMeeting: "meet_restore_meeting",
|
|
1155
|
+
/** Cancel, move or restore ONE occurrence of a series by its original start. */
|
|
1156
|
+
setOccurrence: "meet_set_occurrence",
|
|
1157
|
+
addInvitees: "meet_add_invitees",
|
|
1158
|
+
setInviteeRole: "meet_set_invitee_role",
|
|
1159
|
+
removeInvitee: "meet_remove_invitee",
|
|
1160
|
+
/** A signed-in invitee answers for themselves. */
|
|
1161
|
+
respond: "meet_respond",
|
|
1162
|
+
/** The emailed RSVP link (no account): read and answer. */
|
|
1163
|
+
invitationByToken: "meet_invitation_by_token",
|
|
1164
|
+
respondByToken: "meet_respond_by_token",
|
|
1165
|
+
/** One meeting's occurrences between two instants (INVOKER — the reader's RLS). */
|
|
1166
|
+
meetingOccurrences: "meet_meeting_occurrences",
|
|
1167
|
+
/** Every occurrence the reader can see between two instants — the Upcoming list. */
|
|
1168
|
+
occurrencesBetween: "meet_occurrences_between"
|
|
1111
1169
|
};
|
|
1112
1170
|
var VOID_RPCS = [RPCS.admit, RPCS.recordConsent];
|
|
1113
|
-
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";
|
|
1171
|
+
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";
|
|
1172
|
+
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";
|
|
1114
1173
|
var RECORDING_COLUMNS = "id,meeting_id,state,started_at,ended_at,started_by,file_id,failure_reason";
|
|
1115
1174
|
var SEGMENT_COLUMNS = "id,meeting_id,identity,speaker,text,started_at,is_final,metadata";
|
|
1116
1175
|
var NOTE_COLUMNS = "id,meeting_id,text,kind,assignee_user_id,created_at,metadata";
|
|
@@ -1208,6 +1267,138 @@ function projectNote(row) {
|
|
|
1208
1267
|
source: "record"
|
|
1209
1268
|
};
|
|
1210
1269
|
}
|
|
1270
|
+
var RSVP_STATES = ["needs_action", "accepted", "declined", "tentative"];
|
|
1271
|
+
var OCCURRENCE_STATES = ["scheduled", "moved", "cancelled"];
|
|
1272
|
+
function rsvpState(value) {
|
|
1273
|
+
return RSVP_STATES.includes(value) ? value : "needs_action";
|
|
1274
|
+
}
|
|
1275
|
+
function num(row, key) {
|
|
1276
|
+
const value = row[key];
|
|
1277
|
+
return typeof value === "number" ? value : null;
|
|
1278
|
+
}
|
|
1279
|
+
function when(row, key) {
|
|
1280
|
+
const value = row[key];
|
|
1281
|
+
if (value instanceof Date) return value.toISOString();
|
|
1282
|
+
return text(row, key);
|
|
1283
|
+
}
|
|
1284
|
+
function projectInvitee(row) {
|
|
1285
|
+
const operation = "meet.projectInvitee";
|
|
1286
|
+
const userId = text(row, "invitee_user_id");
|
|
1287
|
+
return {
|
|
1288
|
+
id: requireText(row, "id", operation),
|
|
1289
|
+
meetingId: asMeetingId(requireText(row, "meeting_id", operation)),
|
|
1290
|
+
userId: userId === null ? null : asUserId(userId),
|
|
1291
|
+
email: text(row, "email"),
|
|
1292
|
+
displayName: text(row, "display_name"),
|
|
1293
|
+
role: text(row, "role") === "cohost" ? "cohost" : "invitee",
|
|
1294
|
+
rsvpState: rsvpState(text(row, "rsvp_state")),
|
|
1295
|
+
respondedAt: when(row, "responded_at"),
|
|
1296
|
+
rsvpNote: text(row, "rsvp_note"),
|
|
1297
|
+
lastSentSequence: num(row, "last_sent_sequence"),
|
|
1298
|
+
lastSentAt: when(row, "last_sent_at")
|
|
1299
|
+
};
|
|
1300
|
+
}
|
|
1301
|
+
function projectOccurrence(row) {
|
|
1302
|
+
const operation = "meet.projectOccurrence";
|
|
1303
|
+
const state = text(row, "state");
|
|
1304
|
+
return {
|
|
1305
|
+
meetingId: asMeetingId(requireText(row, "meeting_id", operation)),
|
|
1306
|
+
originalStart: when(row, "original_start") ?? requireText(row, "original_start", operation),
|
|
1307
|
+
occurrenceStart: when(row, "occurrence_start") ?? requireText(row, "occurrence_start", operation),
|
|
1308
|
+
durationMinutes: num(row, "duration_minutes"),
|
|
1309
|
+
// An unknown state is shown as scheduled, never dropped: the row is real.
|
|
1310
|
+
state: OCCURRENCE_STATES.includes(state) ? state : "scheduled",
|
|
1311
|
+
sequence: num(row, "sequence") ?? 0
|
|
1312
|
+
};
|
|
1313
|
+
}
|
|
1314
|
+
function projectUpcoming(row) {
|
|
1315
|
+
const operation = "meet.projectUpcoming";
|
|
1316
|
+
const kind = text(row, "kind");
|
|
1317
|
+
const myRole = text(row, "my_role");
|
|
1318
|
+
const myRsvp = text(row, "my_rsvp");
|
|
1319
|
+
return {
|
|
1320
|
+
...projectOccurrence(row),
|
|
1321
|
+
organizationId: asOrganizationId(requireText(row, "organization_id", operation)),
|
|
1322
|
+
title: text(row, "title") ?? "Meeting",
|
|
1323
|
+
slug: requireText(row, "slug", operation),
|
|
1324
|
+
kind: kind === "scheduled" || kind === "recurring" ? kind : "instant",
|
|
1325
|
+
hostUserId: asUserId(requireText(row, "host_user_id", operation)),
|
|
1326
|
+
timeZone: text(row, "time_zone") ?? "UTC",
|
|
1327
|
+
meetingCancelled: bool(row, "meeting_cancelled", false),
|
|
1328
|
+
myRsvp: myRsvp === null ? null : rsvpState(myRsvp),
|
|
1329
|
+
myRole: myRole === "host" || myRole === "cohost" || myRole === "invitee" ? myRole : null
|
|
1330
|
+
};
|
|
1331
|
+
}
|
|
1332
|
+
function projectOverride(row) {
|
|
1333
|
+
const operation = "meet.projectOverride";
|
|
1334
|
+
return {
|
|
1335
|
+
id: requireText(row, "id", operation),
|
|
1336
|
+
meetingId: asMeetingId(requireText(row, "meeting_id", operation)),
|
|
1337
|
+
originalStart: when(row, "original_start") ?? requireText(row, "original_start", operation),
|
|
1338
|
+
state: text(row, "state") === "moved" ? "moved" : "cancelled",
|
|
1339
|
+
newStart: when(row, "new_start"),
|
|
1340
|
+
newDurationMinutes: num(row, "new_duration_minutes"),
|
|
1341
|
+
reason: text(row, "reason"),
|
|
1342
|
+
calendarSequence: num(row, "calendar_sequence") ?? 0,
|
|
1343
|
+
deletedAt: when(row, "deleted_at")
|
|
1344
|
+
};
|
|
1345
|
+
}
|
|
1346
|
+
function projectRsvpInvitation(body) {
|
|
1347
|
+
const operation = "meet.projectRsvpInvitation";
|
|
1348
|
+
if (typeof body !== "object" || body === null) {
|
|
1349
|
+
throw invalidResponse(operation, "the RSVP door returned no object");
|
|
1350
|
+
}
|
|
1351
|
+
const row = body;
|
|
1352
|
+
if (row["ok"] !== true) {
|
|
1353
|
+
return {
|
|
1354
|
+
ok: false,
|
|
1355
|
+
message: text(row, "message") ?? "This invitation link is no longer valid \u2014 ask the host to send it again."
|
|
1356
|
+
};
|
|
1357
|
+
}
|
|
1358
|
+
const meeting = row["meeting"] ?? {};
|
|
1359
|
+
const invitee = row["invitee"] ?? {};
|
|
1360
|
+
return {
|
|
1361
|
+
ok: true,
|
|
1362
|
+
meeting: {
|
|
1363
|
+
title: text(meeting, "title") ?? "Meeting",
|
|
1364
|
+
slug: requireText(meeting, "slug", operation),
|
|
1365
|
+
scheduledFor: when(meeting, "scheduled_for"),
|
|
1366
|
+
durationMinutes: num(meeting, "duration_minutes"),
|
|
1367
|
+
timeZone: text(meeting, "time_zone") ?? "UTC",
|
|
1368
|
+
agenda: text(meeting, "agenda"),
|
|
1369
|
+
recurrenceRule: text(meeting, "recurrence_rule"),
|
|
1370
|
+
cancelledAt: when(meeting, "cancelled_at"),
|
|
1371
|
+
cancellationReason: text(meeting, "cancellation_reason"),
|
|
1372
|
+
endedAt: when(meeting, "ended_at")
|
|
1373
|
+
},
|
|
1374
|
+
invitee: {
|
|
1375
|
+
displayName: text(invitee, "display_name"),
|
|
1376
|
+
email: text(invitee, "email"),
|
|
1377
|
+
rsvpState: rsvpState(text(invitee, "rsvp_state")),
|
|
1378
|
+
respondedAt: when(invitee, "responded_at")
|
|
1379
|
+
}
|
|
1380
|
+
};
|
|
1381
|
+
}
|
|
1382
|
+
function settingsPatch(settings) {
|
|
1383
|
+
const patch = {};
|
|
1384
|
+
if (settings.lobbyEnabled !== void 0) patch["lobby_enabled"] = settings.lobbyEnabled;
|
|
1385
|
+
if (settings.recordingPolicy !== void 0) patch["recording_policy"] = settings.recordingPolicy;
|
|
1386
|
+
if (settings.aiEnabled !== void 0) patch["ai_enabled"] = settings.aiEnabled;
|
|
1387
|
+
if (settings.joinBeforeHost !== void 0) patch["join_before_host"] = settings.joinBeforeHost;
|
|
1388
|
+
return patch;
|
|
1389
|
+
}
|
|
1390
|
+
function changesPatch(changes) {
|
|
1391
|
+
const patch = settingsPatch(changes);
|
|
1392
|
+
if (changes.title !== void 0) patch["title"] = changes.title;
|
|
1393
|
+
if (changes.agenda !== void 0) patch["agenda"] = changes.agenda;
|
|
1394
|
+
if (changes.timeZone !== void 0) patch["time_zone"] = changes.timeZone;
|
|
1395
|
+
if (changes.scheduledFor !== void 0) patch["scheduled_for"] = changes.scheduledFor;
|
|
1396
|
+
if (changes.scheduledDurationMinutes !== void 0) {
|
|
1397
|
+
patch["scheduled_duration_minutes"] = changes.scheduledDurationMinutes;
|
|
1398
|
+
}
|
|
1399
|
+
if (changes.recurrenceRule !== void 0) patch["recurrence_rule"] = changes.recurrenceRule;
|
|
1400
|
+
return patch;
|
|
1401
|
+
}
|
|
1211
1402
|
function createMeetRepository(options) {
|
|
1212
1403
|
const db = options.client.schema(MEET_SCHEMA);
|
|
1213
1404
|
async function guardVoid(operation, run) {
|
|
@@ -1277,7 +1468,16 @@ function createMeetRepository(options) {
|
|
|
1277
1468
|
lobbyEnabled: bool(row, "lobby_enabled", true),
|
|
1278
1469
|
recordingPolicy: policy === "disabled" || policy === "always-on" ? policy : "host-controlled",
|
|
1279
1470
|
aiEnabled: bool(row, "ai_enabled", true),
|
|
1280
|
-
metadata: typeof metadata === "object" && metadata !== null && !Array.isArray(metadata) ? metadata : {}
|
|
1471
|
+
metadata: typeof metadata === "object" && metadata !== null && !Array.isArray(metadata) ? metadata : {},
|
|
1472
|
+
timeZone: text(row, "time_zone") ?? "UTC",
|
|
1473
|
+
agenda: text(row, "agenda"),
|
|
1474
|
+
recurrenceRule: text(row, "recurrence_rule"),
|
|
1475
|
+
joinBeforeHost: bool(row, "join_before_host", true),
|
|
1476
|
+
cancelledAt: text(row, "cancelled_at"),
|
|
1477
|
+
cancellationReason: text(row, "cancellation_reason"),
|
|
1478
|
+
deletedAt: text(row, "deleted_at"),
|
|
1479
|
+
calendarSequence: typeof row["calendar_sequence"] === "number" ? row["calendar_sequence"] : 0,
|
|
1480
|
+
version: typeof row["version"] === "number" ? row["version"] : 1
|
|
1281
1481
|
};
|
|
1282
1482
|
}
|
|
1283
1483
|
function projectRecording(row) {
|
|
@@ -1364,6 +1564,170 @@ function createMeetRepository(options) {
|
|
|
1364
1564
|
projectNote,
|
|
1365
1565
|
projectRecording,
|
|
1366
1566
|
projectAttendee,
|
|
1567
|
+
projectInvitee,
|
|
1568
|
+
async scheduleMeeting(input) {
|
|
1569
|
+
requireOrg(input.organizationId, "meet.scheduleMeeting");
|
|
1570
|
+
const row = await guard(
|
|
1571
|
+
"meet.scheduleMeeting",
|
|
1572
|
+
() => db.rpc(RPCS.scheduleMeeting, {
|
|
1573
|
+
p_organization_id: input.organizationId,
|
|
1574
|
+
p_host_user_id: input.hostUserId,
|
|
1575
|
+
p_title: input.title,
|
|
1576
|
+
p_scheduled_for: input.scheduledFor,
|
|
1577
|
+
p_time_zone: input.timeZone,
|
|
1578
|
+
p_duration_minutes: input.durationMinutes ?? null,
|
|
1579
|
+
p_agenda: input.agenda ?? null,
|
|
1580
|
+
p_recurrence_rule: input.recurrenceRule ?? null,
|
|
1581
|
+
p_settings: settingsPatch(input.settings ?? {})
|
|
1582
|
+
})
|
|
1583
|
+
);
|
|
1584
|
+
return projectMeeting(row);
|
|
1585
|
+
},
|
|
1586
|
+
async updateMeeting(meetingId, changes, options2 = {}) {
|
|
1587
|
+
const row = await guard(
|
|
1588
|
+
"meet.updateMeeting",
|
|
1589
|
+
() => db.rpc(RPCS.updateMeeting, {
|
|
1590
|
+
p_meeting_id: meetingId,
|
|
1591
|
+
p_changes: changesPatch(changes),
|
|
1592
|
+
p_expected_version: options2.expectedVersion ?? null,
|
|
1593
|
+
p_by_user_id: options2.byUserId ?? null
|
|
1594
|
+
})
|
|
1595
|
+
);
|
|
1596
|
+
return projectMeeting(row);
|
|
1597
|
+
},
|
|
1598
|
+
async cancelMeeting(meetingId, reason, byUserId = null) {
|
|
1599
|
+
const row = await guard(
|
|
1600
|
+
"meet.cancelMeeting",
|
|
1601
|
+
() => db.rpc(RPCS.cancelMeeting, {
|
|
1602
|
+
p_meeting_id: meetingId,
|
|
1603
|
+
p_reason: reason,
|
|
1604
|
+
p_by_user_id: byUserId
|
|
1605
|
+
})
|
|
1606
|
+
);
|
|
1607
|
+
return projectMeeting(row);
|
|
1608
|
+
},
|
|
1609
|
+
async archiveMeeting(meetingId, byUserId = null) {
|
|
1610
|
+
const row = await guard(
|
|
1611
|
+
"meet.archiveMeeting",
|
|
1612
|
+
() => db.rpc(RPCS.archiveMeeting, { p_meeting_id: meetingId, p_by_user_id: byUserId })
|
|
1613
|
+
);
|
|
1614
|
+
return projectMeeting(row);
|
|
1615
|
+
},
|
|
1616
|
+
async restoreMeeting(meetingId, byUserId = null) {
|
|
1617
|
+
const row = await guard(
|
|
1618
|
+
"meet.restoreMeeting",
|
|
1619
|
+
() => db.rpc(RPCS.restoreMeeting, { p_meeting_id: meetingId, p_by_user_id: byUserId })
|
|
1620
|
+
);
|
|
1621
|
+
return projectMeeting(row);
|
|
1622
|
+
},
|
|
1623
|
+
async setOccurrence(args) {
|
|
1624
|
+
const row = await guard(
|
|
1625
|
+
"meet.setOccurrence",
|
|
1626
|
+
() => db.rpc(RPCS.setOccurrence, {
|
|
1627
|
+
p_meeting_id: args.meetingId,
|
|
1628
|
+
p_original_start: args.originalStart,
|
|
1629
|
+
p_action: args.action,
|
|
1630
|
+
p_new_start: args.newStart ?? null,
|
|
1631
|
+
p_new_duration_minutes: args.newDurationMinutes ?? null,
|
|
1632
|
+
p_reason: args.reason ?? null,
|
|
1633
|
+
p_by_user_id: args.byUserId ?? null
|
|
1634
|
+
})
|
|
1635
|
+
);
|
|
1636
|
+
return projectOverride(row);
|
|
1637
|
+
},
|
|
1638
|
+
async meetingOccurrences(meetingId, { from, to, limit = 200 }) {
|
|
1639
|
+
const rows = await guard(
|
|
1640
|
+
"meet.meetingOccurrences",
|
|
1641
|
+
() => db.rpc(RPCS.meetingOccurrences, {
|
|
1642
|
+
p_meeting_id: meetingId,
|
|
1643
|
+
p_from: from,
|
|
1644
|
+
p_to: to,
|
|
1645
|
+
p_limit: limit
|
|
1646
|
+
})
|
|
1647
|
+
);
|
|
1648
|
+
return rows.map(projectOccurrence);
|
|
1649
|
+
},
|
|
1650
|
+
async occurrencesBetween({ from, to, organizationId = null, limit = 500 }) {
|
|
1651
|
+
const rows = await guard(
|
|
1652
|
+
"meet.occurrencesBetween",
|
|
1653
|
+
() => db.rpc(RPCS.occurrencesBetween, {
|
|
1654
|
+
p_from: from,
|
|
1655
|
+
p_to: to,
|
|
1656
|
+
p_organization_id: organizationId,
|
|
1657
|
+
p_limit: limit
|
|
1658
|
+
})
|
|
1659
|
+
);
|
|
1660
|
+
return rows.map(projectUpcoming);
|
|
1661
|
+
},
|
|
1662
|
+
async invitees(meetingId) {
|
|
1663
|
+
const response = await db.from(TABLES.invitees).select(INVITEE_COLUMNS).eq("meeting_id", meetingId).is("deleted_at", null).order("created_at", { ascending: true }).order("id", { ascending: true });
|
|
1664
|
+
if (response.error !== null) throw normalizeMeetError(response.error, "meet.invitees");
|
|
1665
|
+
return (response.data ?? []).map(projectInvitee);
|
|
1666
|
+
},
|
|
1667
|
+
async addInvitees(meetingId, people, byUserId = null) {
|
|
1668
|
+
const rows = await guard(
|
|
1669
|
+
"meet.addInvitees",
|
|
1670
|
+
() => db.rpc(RPCS.addInvitees, {
|
|
1671
|
+
p_meeting_id: meetingId,
|
|
1672
|
+
p_invitees: people.map((person) => ({
|
|
1673
|
+
...person.userId !== void 0 ? { user_id: person.userId } : {},
|
|
1674
|
+
...person.email !== void 0 ? { email: person.email } : {},
|
|
1675
|
+
...person.displayName !== void 0 ? { display_name: person.displayName } : {},
|
|
1676
|
+
...person.role !== void 0 ? { role: person.role } : {}
|
|
1677
|
+
})),
|
|
1678
|
+
p_by_user_id: byUserId
|
|
1679
|
+
})
|
|
1680
|
+
);
|
|
1681
|
+
return rows.map(projectInvitee);
|
|
1682
|
+
},
|
|
1683
|
+
async setInviteeRole(inviteeId, role, byUserId = null) {
|
|
1684
|
+
const row = await guard(
|
|
1685
|
+
"meet.setInviteeRole",
|
|
1686
|
+
() => db.rpc(RPCS.setInviteeRole, {
|
|
1687
|
+
p_invitee_id: inviteeId,
|
|
1688
|
+
p_role: role,
|
|
1689
|
+
p_by_user_id: byUserId
|
|
1690
|
+
})
|
|
1691
|
+
);
|
|
1692
|
+
return projectInvitee(row);
|
|
1693
|
+
},
|
|
1694
|
+
async removeInvitee(inviteeId, byUserId = null) {
|
|
1695
|
+
const row = await guard(
|
|
1696
|
+
"meet.removeInvitee",
|
|
1697
|
+
() => db.rpc(RPCS.removeInvitee, { p_invitee_id: inviteeId, p_by_user_id: byUserId })
|
|
1698
|
+
);
|
|
1699
|
+
return projectInvitee(row);
|
|
1700
|
+
},
|
|
1701
|
+
async respond(meetingId, answer, note = null) {
|
|
1702
|
+
const row = await guard(
|
|
1703
|
+
"meet.respond",
|
|
1704
|
+
() => db.rpc(RPCS.respond, {
|
|
1705
|
+
p_meeting_id: meetingId,
|
|
1706
|
+
p_response: answer,
|
|
1707
|
+
p_note: note,
|
|
1708
|
+
p_by_user_id: null
|
|
1709
|
+
})
|
|
1710
|
+
);
|
|
1711
|
+
return projectInvitee(row);
|
|
1712
|
+
},
|
|
1713
|
+
async invitationByToken(secret) {
|
|
1714
|
+
const body = await guard(
|
|
1715
|
+
"meet.invitationByToken",
|
|
1716
|
+
() => db.rpc(RPCS.invitationByToken, { p_secret: secret })
|
|
1717
|
+
);
|
|
1718
|
+
return projectRsvpInvitation(body);
|
|
1719
|
+
},
|
|
1720
|
+
async respondByToken(secret, answer, note = null) {
|
|
1721
|
+
const body = await guard(
|
|
1722
|
+
"meet.respondByToken",
|
|
1723
|
+
() => db.rpc(RPCS.respondByToken, {
|
|
1724
|
+
p_secret: secret,
|
|
1725
|
+
p_response: answer,
|
|
1726
|
+
p_note: note
|
|
1727
|
+
})
|
|
1728
|
+
);
|
|
1729
|
+
return projectRsvpInvitation(body);
|
|
1730
|
+
},
|
|
1367
1731
|
async attendees(meetingId) {
|
|
1368
1732
|
const response = await db.from(TABLES.participants).select(ATTENDEE_COLUMNS).eq("meeting_id", meetingId).order("joined_at", { ascending: true }).order("id", { ascending: true });
|
|
1369
1733
|
if (response.error !== null) throw normalizeMeetError(response.error, "meet.attendees");
|
|
@@ -1873,8 +2237,8 @@ function callStateLabel(state, outgoing) {
|
|
|
1873
2237
|
return "Call";
|
|
1874
2238
|
}
|
|
1875
2239
|
}
|
|
1876
|
-
function countLabel(
|
|
1877
|
-
return `${
|
|
2240
|
+
function countLabel(count2, singular, plural = `${singular}s`) {
|
|
2241
|
+
return `${count2} ${count2 === 1 ? singular : plural}`;
|
|
1878
2242
|
}
|
|
1879
2243
|
|
|
1880
2244
|
// src/core/invitations.ts
|
|
@@ -2018,11 +2382,11 @@ function computeStage(args) {
|
|
|
2018
2382
|
presenting: sharer !== null
|
|
2019
2383
|
};
|
|
2020
2384
|
}
|
|
2021
|
-
function gridColumns(
|
|
2022
|
-
if (
|
|
2023
|
-
if (
|
|
2024
|
-
if (
|
|
2025
|
-
if (
|
|
2385
|
+
function gridColumns(count2) {
|
|
2386
|
+
if (count2 <= 1) return 1;
|
|
2387
|
+
if (count2 <= 4) return 2;
|
|
2388
|
+
if (count2 <= 9) return 3;
|
|
2389
|
+
if (count2 <= 16) return 4;
|
|
2026
2390
|
return 5;
|
|
2027
2391
|
}
|
|
2028
2392
|
|
|
@@ -2699,6 +3063,15 @@ function createMeetingSession(options) {
|
|
|
2699
3063
|
const { manager, repository, store, identity } = options;
|
|
2700
3064
|
let channel = null;
|
|
2701
3065
|
let meetingChannel = null;
|
|
3066
|
+
async function readMeeting(meetingId) {
|
|
3067
|
+
try {
|
|
3068
|
+
return await repository.meeting(meetingId);
|
|
3069
|
+
} catch (error) {
|
|
3070
|
+
const slug = store.snapshot().meeting?.slug;
|
|
3071
|
+
if (slug === void 0) throw error;
|
|
3072
|
+
return repository.meetingBySlug(slug);
|
|
3073
|
+
}
|
|
3074
|
+
}
|
|
2702
3075
|
function requireHostUser(operation) {
|
|
2703
3076
|
if (identity.userId === null || !isHostRole(effectiveRole(store.snapshot()))) {
|
|
2704
3077
|
throw new MeetError(
|
|
@@ -2779,7 +3152,7 @@ function createMeetingSession(options) {
|
|
|
2779
3152
|
onBackfill: async () => {
|
|
2780
3153
|
const meeting = store.snapshot().meeting;
|
|
2781
3154
|
if (meeting === null) return;
|
|
2782
|
-
const fresh = isGuest(identity) ? await repository.meetingBySlug(meeting.slug) : await
|
|
3155
|
+
const fresh = isGuest(identity) ? await repository.meetingBySlug(meeting.slug) : await readMeeting(meeting.id);
|
|
2783
3156
|
store.patch({ meeting: fresh, locked: fresh.locked });
|
|
2784
3157
|
}
|
|
2785
3158
|
});
|
|
@@ -2874,7 +3247,7 @@ function createMeetingSession(options) {
|
|
|
2874
3247
|
// a recovered surface from looking healthy and being wrong.
|
|
2875
3248
|
onBackfill: async () => {
|
|
2876
3249
|
const [meeting, transcript, notes, recordings, attendees] = await Promise.all([
|
|
2877
|
-
|
|
3250
|
+
readMeeting(meetingId),
|
|
2878
3251
|
repository.transcript(meetingId),
|
|
2879
3252
|
repository.notes(meetingId),
|
|
2880
3253
|
repository.recordings(meetingId),
|
|
@@ -2986,11 +3359,11 @@ function createMeetingSession(options) {
|
|
|
2986
3359
|
} catch {
|
|
2987
3360
|
body = null;
|
|
2988
3361
|
}
|
|
2989
|
-
const
|
|
3362
|
+
const refusal2 = readServerRefusal(body);
|
|
2990
3363
|
throw new MeetError(
|
|
2991
3364
|
response.status === 403 ? "forbidden" : "transport",
|
|
2992
|
-
|
|
2993
|
-
|
|
3365
|
+
refusal2?.reason ?? `meet.endForEveryone: the server answered ${response.status}`,
|
|
3366
|
+
refusal2?.remedy !== void 0 && refusal2.remedy.length > 0 ? refusal2.remedy : "The meeting is still running. Only a host may end it, and the server re-checks that \u2014 being a host on this screen is not the decision."
|
|
2994
3367
|
);
|
|
2995
3368
|
}
|
|
2996
3369
|
let payload = null;
|
|
@@ -3118,6 +3491,57 @@ function fileDownloadPath(fileId) {
|
|
|
3118
3491
|
return `/files/${encodeURIComponent(fileId)}/download?inline=1`;
|
|
3119
3492
|
}
|
|
3120
3493
|
|
|
3494
|
+
// src/core/management.ts
|
|
3495
|
+
async function refusal(response, operation) {
|
|
3496
|
+
let body = null;
|
|
3497
|
+
try {
|
|
3498
|
+
body = await response.json();
|
|
3499
|
+
} catch {
|
|
3500
|
+
body = null;
|
|
3501
|
+
}
|
|
3502
|
+
const said = readServerRefusal(body);
|
|
3503
|
+
return new MeetError(
|
|
3504
|
+
response.status === 403 || response.status === 401 ? "forbidden" : response.status === 404 ? "not-found" : response.status === 409 ? "conflict" : "transport",
|
|
3505
|
+
`${operation}: ${said?.reason ?? `the server answered ${response.status}`}`,
|
|
3506
|
+
said?.remedy || "The meeting itself is saved \u2014 only the telling failed. Try again; the call is idempotent."
|
|
3507
|
+
);
|
|
3508
|
+
}
|
|
3509
|
+
function count(body, key) {
|
|
3510
|
+
const value = body[key];
|
|
3511
|
+
return typeof value === "number" ? value : 0;
|
|
3512
|
+
}
|
|
3513
|
+
async function announceMeeting(api, meetingId) {
|
|
3514
|
+
const operation = "meet.announceMeeting";
|
|
3515
|
+
const response = await api.request({
|
|
3516
|
+
path: meetingPath(MEET_ROUTES.meetingAnnounce, meetingId),
|
|
3517
|
+
method: "POST",
|
|
3518
|
+
requireAuth: true,
|
|
3519
|
+
operation
|
|
3520
|
+
});
|
|
3521
|
+
if (!response.ok) throw await refusal(response, operation);
|
|
3522
|
+
const body = await response.json();
|
|
3523
|
+
return {
|
|
3524
|
+
invitations: count(body, "invitations"),
|
|
3525
|
+
updates: count(body, "updates"),
|
|
3526
|
+
cancellations: count(body, "cancellations"),
|
|
3527
|
+
alreadyCurrent: count(body, "already_current"),
|
|
3528
|
+
remindersQueued: count(body, "reminders_queued"),
|
|
3529
|
+
remindersCancelled: count(body, "reminders_cancelled")
|
|
3530
|
+
};
|
|
3531
|
+
}
|
|
3532
|
+
async function pushMeetingToGoogleCalendar(api, meetingId, target) {
|
|
3533
|
+
const operation = "meet.pushMeetingToGoogleCalendar";
|
|
3534
|
+
const response = await api.request({
|
|
3535
|
+
path: meetingPath(MEET_ROUTES.meetingGoogleCalendar, meetingId),
|
|
3536
|
+
method: "POST",
|
|
3537
|
+
requireAuth: true,
|
|
3538
|
+
operation,
|
|
3539
|
+
body: { connection_id: target.connectionId, calendar_id: target.calendarId ?? "primary" }
|
|
3540
|
+
});
|
|
3541
|
+
if (!response.ok) throw await refusal(response, operation);
|
|
3542
|
+
return await response.json();
|
|
3543
|
+
}
|
|
3544
|
+
|
|
3121
3545
|
// src/core/room.ts
|
|
3122
3546
|
var PARTICIPANT_ROLE_ATTRIBUTE = "matrx.participant.role";
|
|
3123
3547
|
var PARTICIPANT_CAN_RECORD_ATTRIBUTE = "matrx.participant.can_record";
|
|
@@ -3692,7 +4116,7 @@ function meetingWhen(meeting, locale) {
|
|
|
3692
4116
|
if (start === null) return "Time not recorded";
|
|
3693
4117
|
const startedAt = new Date(start);
|
|
3694
4118
|
if (Number.isNaN(startedAt.getTime())) return "Time not recorded";
|
|
3695
|
-
const
|
|
4119
|
+
const when2 = startedAt.toLocaleString(locale, {
|
|
3696
4120
|
weekday: "long",
|
|
3697
4121
|
year: "numeric",
|
|
3698
4122
|
month: "long",
|
|
@@ -3700,11 +4124,11 @@ function meetingWhen(meeting, locale) {
|
|
|
3700
4124
|
hour: "numeric",
|
|
3701
4125
|
minute: "2-digit"
|
|
3702
4126
|
});
|
|
3703
|
-
if (meeting.endedAt === null || meeting.startedAt === null) return
|
|
4127
|
+
if (meeting.endedAt === null || meeting.startedAt === null) return when2;
|
|
3704
4128
|
const endedAt = new Date(meeting.endedAt);
|
|
3705
|
-
if (Number.isNaN(endedAt.getTime())) return
|
|
4129
|
+
if (Number.isNaN(endedAt.getTime())) return when2;
|
|
3706
4130
|
const elapsedMs = endedAt.getTime() - startedAt.getTime();
|
|
3707
|
-
return `${
|
|
4131
|
+
return `${when2} \xB7 ${formatDurationMs(elapsedMs, { style: "long" })}`;
|
|
3708
4132
|
}
|
|
3709
4133
|
var GUEST_RECORD_ACCESS_LEVELS = [
|
|
3710
4134
|
"none",
|
|
@@ -3780,8 +4204,8 @@ async function loadGuestMeetingRecord(args) {
|
|
|
3780
4204
|
);
|
|
3781
4205
|
}
|
|
3782
4206
|
if (!response.ok) {
|
|
3783
|
-
const
|
|
3784
|
-
return refusedBundle(meeting,
|
|
4207
|
+
const refusal2 = await readRefusal(response);
|
|
4208
|
+
return refusedBundle(meeting, refusal2.message, refusal2.remedy);
|
|
3785
4209
|
}
|
|
3786
4210
|
const body = await response.json();
|
|
3787
4211
|
const payload = typeof body["data"] === "object" && body["data"] !== null ? body["data"] : body;
|
|
@@ -3872,8 +4296,8 @@ async function setGuestRecordAccess(args) {
|
|
|
3872
4296
|
}
|
|
3873
4297
|
});
|
|
3874
4298
|
if (!response.ok) {
|
|
3875
|
-
const
|
|
3876
|
-
throw new MeetError("forbidden",
|
|
4299
|
+
const refusal2 = await readRefusal(response);
|
|
4300
|
+
throw new MeetError("forbidden", refusal2.message, refusal2.remedy);
|
|
3877
4301
|
}
|
|
3878
4302
|
const body = await response.json();
|
|
3879
4303
|
const payload = typeof body["data"] === "object" && body["data"] !== null ? body["data"] : body;
|
|
@@ -3923,6 +4347,7 @@ export {
|
|
|
3923
4347
|
TRANSCRIPTION_ATTRIBUTES,
|
|
3924
4348
|
TRANSCRIPTION_TOPIC,
|
|
3925
4349
|
absenceSentence,
|
|
4350
|
+
announceMeeting,
|
|
3926
4351
|
applyRoomEvent,
|
|
3927
4352
|
asCallId,
|
|
3928
4353
|
asMeetingId,
|
|
@@ -3937,6 +4362,7 @@ export {
|
|
|
3937
4362
|
canModerateParticipant,
|
|
3938
4363
|
canTransition,
|
|
3939
4364
|
captionSegment,
|
|
4365
|
+
changesPatch,
|
|
3940
4366
|
computeStage,
|
|
3941
4367
|
countLabel,
|
|
3942
4368
|
createCallCenter,
|
|
@@ -3981,6 +4407,7 @@ export {
|
|
|
3981
4407
|
mapQuality,
|
|
3982
4408
|
meetingInviteAction,
|
|
3983
4409
|
meetingLink,
|
|
4410
|
+
meetingPath,
|
|
3984
4411
|
meetingSlug,
|
|
3985
4412
|
meetingTopic,
|
|
3986
4413
|
meetingWhen,
|
|
@@ -3997,9 +4424,15 @@ export {
|
|
|
3997
4424
|
parseRoomToken,
|
|
3998
4425
|
participantSummary,
|
|
3999
4426
|
playableRecordingFileId,
|
|
4427
|
+
projectInvitee,
|
|
4000
4428
|
projectNote,
|
|
4429
|
+
projectOccurrence,
|
|
4430
|
+
projectOverride,
|
|
4001
4431
|
projectParticipant,
|
|
4432
|
+
projectRsvpInvitation,
|
|
4002
4433
|
projectSegment,
|
|
4434
|
+
projectUpcoming,
|
|
4435
|
+
pushMeetingToGoogleCalendar,
|
|
4003
4436
|
readServerRefusal,
|
|
4004
4437
|
readableTranscript,
|
|
4005
4438
|
recordingLabel,
|
|
@@ -4007,6 +4440,7 @@ export {
|
|
|
4007
4440
|
roleOf,
|
|
4008
4441
|
roomTopic,
|
|
4009
4442
|
setGuestRecordAccess,
|
|
4443
|
+
settingsPatch,
|
|
4010
4444
|
snapshotParticipant,
|
|
4011
4445
|
unavailableFrom,
|
|
4012
4446
|
userTopic,
|