@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/react.js
CHANGED
|
@@ -45,7 +45,7 @@ var MeetError = class extends Error {
|
|
|
45
45
|
};
|
|
46
46
|
var FORBIDDEN_CODES = /* @__PURE__ */ new Set(["42501", "PGRST301", "PGRST302"]);
|
|
47
47
|
var NOT_FOUND_CODES = /* @__PURE__ */ new Set(["PGRST116", "PGRST205"]);
|
|
48
|
-
var CONFLICT_CODES = /* @__PURE__ */ new Set(["23505"]);
|
|
48
|
+
var CONFLICT_CODES = /* @__PURE__ */ new Set(["23505", "40001", "55000"]);
|
|
49
49
|
var SESSION_MARKERS = [
|
|
50
50
|
"auth session missing",
|
|
51
51
|
"jwt expired",
|
|
@@ -203,8 +203,19 @@ var MEET_ROUTES = {
|
|
|
203
203
|
*/
|
|
204
204
|
record: "/api/v1/meet/record",
|
|
205
205
|
/** A host chooses what guests may read of ONE meeting (MRF-1). */
|
|
206
|
-
recordAccess: "/api/v1/meet/record/access"
|
|
206
|
+
recordAccess: "/api/v1/meet/record/access",
|
|
207
|
+
/**
|
|
208
|
+
* Meet wave 1 (0.8): bring every invitee up to date after a write — sends the
|
|
209
|
+
* invitation / update / cancellation emails with their `.ics` and re-queues
|
|
210
|
+
* reminders. Idempotent. `{meeting_id}` is substituted by `meetingPath`.
|
|
211
|
+
*/
|
|
212
|
+
meetingAnnounce: "/api/v1/meet/meetings/{meeting_id}/announce",
|
|
213
|
+
/** Put a one-off meeting on the caller's own Google Calendar (gated by the server). */
|
|
214
|
+
meetingGoogleCalendar: "/api/v1/meet/meetings/{meeting_id}/google-calendar"
|
|
207
215
|
};
|
|
216
|
+
function meetingPath(template, meetingId) {
|
|
217
|
+
return template.replace("{meeting_id}", encodeURIComponent(meetingId));
|
|
218
|
+
}
|
|
208
219
|
var ROOM_TOKEN_HEADER = "x-meet-room-token";
|
|
209
220
|
var REFRESH_RATIO = 0.8;
|
|
210
221
|
function readString(source, key) {
|
|
@@ -334,6 +345,19 @@ function createTokenClient(options) {
|
|
|
334
345
|
"The link is wrong or the meeting was deleted. Show the 'meeting not found' screen; a durable link that 404s is never a retry."
|
|
335
346
|
);
|
|
336
347
|
}
|
|
348
|
+
if (response.status === 410) {
|
|
349
|
+
let reason = "This meeting was cancelled.";
|
|
350
|
+
try {
|
|
351
|
+
const body = await response.json();
|
|
352
|
+
if (typeof body?.detail?.message === "string") reason = body.detail.message;
|
|
353
|
+
} catch {
|
|
354
|
+
}
|
|
355
|
+
throw new MeetError(
|
|
356
|
+
"cancelled",
|
|
357
|
+
reason,
|
|
358
|
+
"Nobody can join a cancelled meeting. Show the cancelled screen with this sentence; ask the host to schedule a new one."
|
|
359
|
+
);
|
|
360
|
+
}
|
|
337
361
|
if (response.status === 423) {
|
|
338
362
|
throw new MeetError(
|
|
339
363
|
"room-locked",
|
|
@@ -535,12 +559,12 @@ function createMeetAi(options) {
|
|
|
535
559
|
async function dispatch(route, body, operation) {
|
|
536
560
|
const response = await send({ path: route, body, operation, requireAuth: true });
|
|
537
561
|
if (!response.ok) {
|
|
538
|
-
const
|
|
539
|
-
options.store.patch({ noteTakerRefusal:
|
|
562
|
+
const refusal2 = await refusalFor(response, operation);
|
|
563
|
+
options.store.patch({ noteTakerRefusal: refusal2 });
|
|
540
564
|
throw new MeetError(
|
|
541
565
|
response.status === 403 ? "forbidden" : "transport",
|
|
542
|
-
|
|
543
|
-
|
|
566
|
+
refusal2.reason,
|
|
567
|
+
refusal2.remedy
|
|
544
568
|
);
|
|
545
569
|
}
|
|
546
570
|
options.store.patch({ noteTakerRefusal: null });
|
|
@@ -578,11 +602,11 @@ function createMeetAi(options) {
|
|
|
578
602
|
...signal !== void 0 ? { signal } : {}
|
|
579
603
|
});
|
|
580
604
|
if (!response.ok) {
|
|
581
|
-
const
|
|
605
|
+
const refusal2 = await refusalFor(response, "meet.ai.ask");
|
|
582
606
|
throw new MeetError(
|
|
583
607
|
response.status === 403 || response.status === 401 ? "forbidden" : response.status === 404 ? "not-found" : "transport",
|
|
584
|
-
|
|
585
|
-
|
|
608
|
+
refusal2.reason,
|
|
609
|
+
refusal2.remedy
|
|
586
610
|
);
|
|
587
611
|
}
|
|
588
612
|
const body = response.body;
|
|
@@ -1726,6 +1750,15 @@ function createMeetingSession(options) {
|
|
|
1726
1750
|
const { manager, repository, store, identity } = options;
|
|
1727
1751
|
let channel = null;
|
|
1728
1752
|
let meetingChannel = null;
|
|
1753
|
+
async function readMeeting(meetingId) {
|
|
1754
|
+
try {
|
|
1755
|
+
return await repository.meeting(meetingId);
|
|
1756
|
+
} catch (error) {
|
|
1757
|
+
const slug = store.snapshot().meeting?.slug;
|
|
1758
|
+
if (slug === void 0) throw error;
|
|
1759
|
+
return repository.meetingBySlug(slug);
|
|
1760
|
+
}
|
|
1761
|
+
}
|
|
1729
1762
|
function requireHostUser(operation) {
|
|
1730
1763
|
if (identity.userId === null || !isHostRole(effectiveRole(store.snapshot()))) {
|
|
1731
1764
|
throw new MeetError(
|
|
@@ -1806,7 +1839,7 @@ function createMeetingSession(options) {
|
|
|
1806
1839
|
onBackfill: async () => {
|
|
1807
1840
|
const meeting = store.snapshot().meeting;
|
|
1808
1841
|
if (meeting === null) return;
|
|
1809
|
-
const fresh = isGuest(identity) ? await repository.meetingBySlug(meeting.slug) : await
|
|
1842
|
+
const fresh = isGuest(identity) ? await repository.meetingBySlug(meeting.slug) : await readMeeting(meeting.id);
|
|
1810
1843
|
store.patch({ meeting: fresh, locked: fresh.locked });
|
|
1811
1844
|
}
|
|
1812
1845
|
});
|
|
@@ -1901,7 +1934,7 @@ function createMeetingSession(options) {
|
|
|
1901
1934
|
// a recovered surface from looking healthy and being wrong.
|
|
1902
1935
|
onBackfill: async () => {
|
|
1903
1936
|
const [meeting, transcript, notes, recordings, attendees] = await Promise.all([
|
|
1904
|
-
|
|
1937
|
+
readMeeting(meetingId),
|
|
1905
1938
|
repository.transcript(meetingId),
|
|
1906
1939
|
repository.notes(meetingId),
|
|
1907
1940
|
repository.recordings(meetingId),
|
|
@@ -2013,11 +2046,11 @@ function createMeetingSession(options) {
|
|
|
2013
2046
|
} catch {
|
|
2014
2047
|
body = null;
|
|
2015
2048
|
}
|
|
2016
|
-
const
|
|
2049
|
+
const refusal2 = readServerRefusal(body);
|
|
2017
2050
|
throw new MeetError(
|
|
2018
2051
|
response.status === 403 ? "forbidden" : "transport",
|
|
2019
|
-
|
|
2020
|
-
|
|
2052
|
+
refusal2?.reason ?? `meet.endForEveryone: the server answered ${response.status}`,
|
|
2053
|
+
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."
|
|
2021
2054
|
);
|
|
2022
2055
|
}
|
|
2023
2056
|
let payload = null;
|
|
@@ -2060,7 +2093,11 @@ var TABLES = {
|
|
|
2060
2093
|
invites: "meet_call_invites",
|
|
2061
2094
|
recordings: "meet_recordings",
|
|
2062
2095
|
transcripts: "meet_transcript_segments",
|
|
2063
|
-
notes: "meet_notes"
|
|
2096
|
+
notes: "meet_notes",
|
|
2097
|
+
/** Wave 1 (0.8): who a meeting was sent to, and what they answered. */
|
|
2098
|
+
invitees: "meet_invitees",
|
|
2099
|
+
/** Wave 1 (0.8): one occurrence of a series cancelled or moved. */
|
|
2100
|
+
occurrenceOverrides: "meet_occurrence_overrides"
|
|
2064
2101
|
};
|
|
2065
2102
|
var RPCS = {
|
|
2066
2103
|
/** Atomic: create the invite row and stamp its expiry in one statement. */
|
|
@@ -2076,10 +2113,41 @@ var RPCS = {
|
|
|
2076
2113
|
admit: "meet_admit_participant",
|
|
2077
2114
|
setLock: "meet_set_lock",
|
|
2078
2115
|
endMeeting: "meet_end_meeting",
|
|
2079
|
-
recordConsent: "meet_record_consent"
|
|
2116
|
+
recordConsent: "meet_record_consent",
|
|
2117
|
+
/*
|
|
2118
|
+
* ── MEET WAVE 1 — management (0.8). Every write is a SECURITY DEFINER door
|
|
2119
|
+
* authorized by the meeting's host or a real iam grant (editor to change,
|
|
2120
|
+
* admin = co-host to archive or name a co-host) — never an organization
|
|
2121
|
+
* match. After any of these writes, call `announceMeeting` so the server
|
|
2122
|
+
* sends invitations / updates / cancellations and re-queues reminders.
|
|
2123
|
+
*/
|
|
2124
|
+
/** Schedule with every setting at create; unset settings come from the knobs. */
|
|
2125
|
+
scheduleMeeting: "meet_schedule_meeting",
|
|
2126
|
+
/** Edit / reschedule — a JSON patch with a version compare-and-swap. */
|
|
2127
|
+
updateMeeting: "meet_update_meeting",
|
|
2128
|
+
/** Cancel is a state (`cancelled_at`); the link keeps resolving. */
|
|
2129
|
+
cancelMeeting: "meet_cancel_meeting",
|
|
2130
|
+
/** Archive (soft delete) — the invitations and exceptions cascade. */
|
|
2131
|
+
archiveMeeting: "meet_archive_meeting",
|
|
2132
|
+
restoreMeeting: "meet_restore_meeting",
|
|
2133
|
+
/** Cancel, move or restore ONE occurrence of a series by its original start. */
|
|
2134
|
+
setOccurrence: "meet_set_occurrence",
|
|
2135
|
+
addInvitees: "meet_add_invitees",
|
|
2136
|
+
setInviteeRole: "meet_set_invitee_role",
|
|
2137
|
+
removeInvitee: "meet_remove_invitee",
|
|
2138
|
+
/** A signed-in invitee answers for themselves. */
|
|
2139
|
+
respond: "meet_respond",
|
|
2140
|
+
/** The emailed RSVP link (no account): read and answer. */
|
|
2141
|
+
invitationByToken: "meet_invitation_by_token",
|
|
2142
|
+
respondByToken: "meet_respond_by_token",
|
|
2143
|
+
/** One meeting's occurrences between two instants (INVOKER — the reader's RLS). */
|
|
2144
|
+
meetingOccurrences: "meet_meeting_occurrences",
|
|
2145
|
+
/** Every occurrence the reader can see between two instants — the Upcoming list. */
|
|
2146
|
+
occurrencesBetween: "meet_occurrences_between"
|
|
2080
2147
|
};
|
|
2081
2148
|
var VOID_RPCS = [RPCS.admit, RPCS.recordConsent];
|
|
2082
|
-
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";
|
|
2149
|
+
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";
|
|
2150
|
+
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";
|
|
2083
2151
|
var RECORDING_COLUMNS = "id,meeting_id,state,started_at,ended_at,started_by,file_id,failure_reason";
|
|
2084
2152
|
var SEGMENT_COLUMNS = "id,meeting_id,identity,speaker,text,started_at,is_final,metadata";
|
|
2085
2153
|
var NOTE_COLUMNS = "id,meeting_id,text,kind,assignee_user_id,created_at,metadata";
|
|
@@ -2177,6 +2245,138 @@ function projectNote(row) {
|
|
|
2177
2245
|
source: "record"
|
|
2178
2246
|
};
|
|
2179
2247
|
}
|
|
2248
|
+
var RSVP_STATES = ["needs_action", "accepted", "declined", "tentative"];
|
|
2249
|
+
var OCCURRENCE_STATES = ["scheduled", "moved", "cancelled"];
|
|
2250
|
+
function rsvpState(value) {
|
|
2251
|
+
return RSVP_STATES.includes(value) ? value : "needs_action";
|
|
2252
|
+
}
|
|
2253
|
+
function num(row, key) {
|
|
2254
|
+
const value = row[key];
|
|
2255
|
+
return typeof value === "number" ? value : null;
|
|
2256
|
+
}
|
|
2257
|
+
function when(row, key) {
|
|
2258
|
+
const value = row[key];
|
|
2259
|
+
if (value instanceof Date) return value.toISOString();
|
|
2260
|
+
return text(row, key);
|
|
2261
|
+
}
|
|
2262
|
+
function projectInvitee(row) {
|
|
2263
|
+
const operation = "meet.projectInvitee";
|
|
2264
|
+
const userId = text(row, "invitee_user_id");
|
|
2265
|
+
return {
|
|
2266
|
+
id: requireText(row, "id", operation),
|
|
2267
|
+
meetingId: asMeetingId(requireText(row, "meeting_id", operation)),
|
|
2268
|
+
userId: userId === null ? null : asUserId(userId),
|
|
2269
|
+
email: text(row, "email"),
|
|
2270
|
+
displayName: text(row, "display_name"),
|
|
2271
|
+
role: text(row, "role") === "cohost" ? "cohost" : "invitee",
|
|
2272
|
+
rsvpState: rsvpState(text(row, "rsvp_state")),
|
|
2273
|
+
respondedAt: when(row, "responded_at"),
|
|
2274
|
+
rsvpNote: text(row, "rsvp_note"),
|
|
2275
|
+
lastSentSequence: num(row, "last_sent_sequence"),
|
|
2276
|
+
lastSentAt: when(row, "last_sent_at")
|
|
2277
|
+
};
|
|
2278
|
+
}
|
|
2279
|
+
function projectOccurrence(row) {
|
|
2280
|
+
const operation = "meet.projectOccurrence";
|
|
2281
|
+
const state = text(row, "state");
|
|
2282
|
+
return {
|
|
2283
|
+
meetingId: asMeetingId(requireText(row, "meeting_id", operation)),
|
|
2284
|
+
originalStart: when(row, "original_start") ?? requireText(row, "original_start", operation),
|
|
2285
|
+
occurrenceStart: when(row, "occurrence_start") ?? requireText(row, "occurrence_start", operation),
|
|
2286
|
+
durationMinutes: num(row, "duration_minutes"),
|
|
2287
|
+
// An unknown state is shown as scheduled, never dropped: the row is real.
|
|
2288
|
+
state: OCCURRENCE_STATES.includes(state) ? state : "scheduled",
|
|
2289
|
+
sequence: num(row, "sequence") ?? 0
|
|
2290
|
+
};
|
|
2291
|
+
}
|
|
2292
|
+
function projectUpcoming(row) {
|
|
2293
|
+
const operation = "meet.projectUpcoming";
|
|
2294
|
+
const kind = text(row, "kind");
|
|
2295
|
+
const myRole = text(row, "my_role");
|
|
2296
|
+
const myRsvp = text(row, "my_rsvp");
|
|
2297
|
+
return {
|
|
2298
|
+
...projectOccurrence(row),
|
|
2299
|
+
organizationId: asOrganizationId(requireText(row, "organization_id", operation)),
|
|
2300
|
+
title: text(row, "title") ?? "Meeting",
|
|
2301
|
+
slug: requireText(row, "slug", operation),
|
|
2302
|
+
kind: kind === "scheduled" || kind === "recurring" ? kind : "instant",
|
|
2303
|
+
hostUserId: asUserId(requireText(row, "host_user_id", operation)),
|
|
2304
|
+
timeZone: text(row, "time_zone") ?? "UTC",
|
|
2305
|
+
meetingCancelled: bool(row, "meeting_cancelled", false),
|
|
2306
|
+
myRsvp: myRsvp === null ? null : rsvpState(myRsvp),
|
|
2307
|
+
myRole: myRole === "host" || myRole === "cohost" || myRole === "invitee" ? myRole : null
|
|
2308
|
+
};
|
|
2309
|
+
}
|
|
2310
|
+
function projectOverride(row) {
|
|
2311
|
+
const operation = "meet.projectOverride";
|
|
2312
|
+
return {
|
|
2313
|
+
id: requireText(row, "id", operation),
|
|
2314
|
+
meetingId: asMeetingId(requireText(row, "meeting_id", operation)),
|
|
2315
|
+
originalStart: when(row, "original_start") ?? requireText(row, "original_start", operation),
|
|
2316
|
+
state: text(row, "state") === "moved" ? "moved" : "cancelled",
|
|
2317
|
+
newStart: when(row, "new_start"),
|
|
2318
|
+
newDurationMinutes: num(row, "new_duration_minutes"),
|
|
2319
|
+
reason: text(row, "reason"),
|
|
2320
|
+
calendarSequence: num(row, "calendar_sequence") ?? 0,
|
|
2321
|
+
deletedAt: when(row, "deleted_at")
|
|
2322
|
+
};
|
|
2323
|
+
}
|
|
2324
|
+
function projectRsvpInvitation(body) {
|
|
2325
|
+
const operation = "meet.projectRsvpInvitation";
|
|
2326
|
+
if (typeof body !== "object" || body === null) {
|
|
2327
|
+
throw invalidResponse(operation, "the RSVP door returned no object");
|
|
2328
|
+
}
|
|
2329
|
+
const row = body;
|
|
2330
|
+
if (row["ok"] !== true) {
|
|
2331
|
+
return {
|
|
2332
|
+
ok: false,
|
|
2333
|
+
message: text(row, "message") ?? "This invitation link is no longer valid \u2014 ask the host to send it again."
|
|
2334
|
+
};
|
|
2335
|
+
}
|
|
2336
|
+
const meeting = row["meeting"] ?? {};
|
|
2337
|
+
const invitee = row["invitee"] ?? {};
|
|
2338
|
+
return {
|
|
2339
|
+
ok: true,
|
|
2340
|
+
meeting: {
|
|
2341
|
+
title: text(meeting, "title") ?? "Meeting",
|
|
2342
|
+
slug: requireText(meeting, "slug", operation),
|
|
2343
|
+
scheduledFor: when(meeting, "scheduled_for"),
|
|
2344
|
+
durationMinutes: num(meeting, "duration_minutes"),
|
|
2345
|
+
timeZone: text(meeting, "time_zone") ?? "UTC",
|
|
2346
|
+
agenda: text(meeting, "agenda"),
|
|
2347
|
+
recurrenceRule: text(meeting, "recurrence_rule"),
|
|
2348
|
+
cancelledAt: when(meeting, "cancelled_at"),
|
|
2349
|
+
cancellationReason: text(meeting, "cancellation_reason"),
|
|
2350
|
+
endedAt: when(meeting, "ended_at")
|
|
2351
|
+
},
|
|
2352
|
+
invitee: {
|
|
2353
|
+
displayName: text(invitee, "display_name"),
|
|
2354
|
+
email: text(invitee, "email"),
|
|
2355
|
+
rsvpState: rsvpState(text(invitee, "rsvp_state")),
|
|
2356
|
+
respondedAt: when(invitee, "responded_at")
|
|
2357
|
+
}
|
|
2358
|
+
};
|
|
2359
|
+
}
|
|
2360
|
+
function settingsPatch(settings) {
|
|
2361
|
+
const patch = {};
|
|
2362
|
+
if (settings.lobbyEnabled !== void 0) patch["lobby_enabled"] = settings.lobbyEnabled;
|
|
2363
|
+
if (settings.recordingPolicy !== void 0) patch["recording_policy"] = settings.recordingPolicy;
|
|
2364
|
+
if (settings.aiEnabled !== void 0) patch["ai_enabled"] = settings.aiEnabled;
|
|
2365
|
+
if (settings.joinBeforeHost !== void 0) patch["join_before_host"] = settings.joinBeforeHost;
|
|
2366
|
+
return patch;
|
|
2367
|
+
}
|
|
2368
|
+
function changesPatch(changes) {
|
|
2369
|
+
const patch = settingsPatch(changes);
|
|
2370
|
+
if (changes.title !== void 0) patch["title"] = changes.title;
|
|
2371
|
+
if (changes.agenda !== void 0) patch["agenda"] = changes.agenda;
|
|
2372
|
+
if (changes.timeZone !== void 0) patch["time_zone"] = changes.timeZone;
|
|
2373
|
+
if (changes.scheduledFor !== void 0) patch["scheduled_for"] = changes.scheduledFor;
|
|
2374
|
+
if (changes.scheduledDurationMinutes !== void 0) {
|
|
2375
|
+
patch["scheduled_duration_minutes"] = changes.scheduledDurationMinutes;
|
|
2376
|
+
}
|
|
2377
|
+
if (changes.recurrenceRule !== void 0) patch["recurrence_rule"] = changes.recurrenceRule;
|
|
2378
|
+
return patch;
|
|
2379
|
+
}
|
|
2180
2380
|
function createMeetRepository(options) {
|
|
2181
2381
|
const db = options.client.schema(MEET_SCHEMA);
|
|
2182
2382
|
async function guardVoid(operation, run) {
|
|
@@ -2246,7 +2446,16 @@ function createMeetRepository(options) {
|
|
|
2246
2446
|
lobbyEnabled: bool(row, "lobby_enabled", true),
|
|
2247
2447
|
recordingPolicy: policy === "disabled" || policy === "always-on" ? policy : "host-controlled",
|
|
2248
2448
|
aiEnabled: bool(row, "ai_enabled", true),
|
|
2249
|
-
metadata: typeof metadata === "object" && metadata !== null && !Array.isArray(metadata) ? metadata : {}
|
|
2449
|
+
metadata: typeof metadata === "object" && metadata !== null && !Array.isArray(metadata) ? metadata : {},
|
|
2450
|
+
timeZone: text(row, "time_zone") ?? "UTC",
|
|
2451
|
+
agenda: text(row, "agenda"),
|
|
2452
|
+
recurrenceRule: text(row, "recurrence_rule"),
|
|
2453
|
+
joinBeforeHost: bool(row, "join_before_host", true),
|
|
2454
|
+
cancelledAt: text(row, "cancelled_at"),
|
|
2455
|
+
cancellationReason: text(row, "cancellation_reason"),
|
|
2456
|
+
deletedAt: text(row, "deleted_at"),
|
|
2457
|
+
calendarSequence: typeof row["calendar_sequence"] === "number" ? row["calendar_sequence"] : 0,
|
|
2458
|
+
version: typeof row["version"] === "number" ? row["version"] : 1
|
|
2250
2459
|
};
|
|
2251
2460
|
}
|
|
2252
2461
|
function projectRecording(row) {
|
|
@@ -2333,6 +2542,170 @@ function createMeetRepository(options) {
|
|
|
2333
2542
|
projectNote,
|
|
2334
2543
|
projectRecording,
|
|
2335
2544
|
projectAttendee,
|
|
2545
|
+
projectInvitee,
|
|
2546
|
+
async scheduleMeeting(input) {
|
|
2547
|
+
requireOrg(input.organizationId, "meet.scheduleMeeting");
|
|
2548
|
+
const row = await guard(
|
|
2549
|
+
"meet.scheduleMeeting",
|
|
2550
|
+
() => db.rpc(RPCS.scheduleMeeting, {
|
|
2551
|
+
p_organization_id: input.organizationId,
|
|
2552
|
+
p_host_user_id: input.hostUserId,
|
|
2553
|
+
p_title: input.title,
|
|
2554
|
+
p_scheduled_for: input.scheduledFor,
|
|
2555
|
+
p_time_zone: input.timeZone,
|
|
2556
|
+
p_duration_minutes: input.durationMinutes ?? null,
|
|
2557
|
+
p_agenda: input.agenda ?? null,
|
|
2558
|
+
p_recurrence_rule: input.recurrenceRule ?? null,
|
|
2559
|
+
p_settings: settingsPatch(input.settings ?? {})
|
|
2560
|
+
})
|
|
2561
|
+
);
|
|
2562
|
+
return projectMeeting(row);
|
|
2563
|
+
},
|
|
2564
|
+
async updateMeeting(meetingId, changes, options2 = {}) {
|
|
2565
|
+
const row = await guard(
|
|
2566
|
+
"meet.updateMeeting",
|
|
2567
|
+
() => db.rpc(RPCS.updateMeeting, {
|
|
2568
|
+
p_meeting_id: meetingId,
|
|
2569
|
+
p_changes: changesPatch(changes),
|
|
2570
|
+
p_expected_version: options2.expectedVersion ?? null,
|
|
2571
|
+
p_by_user_id: options2.byUserId ?? null
|
|
2572
|
+
})
|
|
2573
|
+
);
|
|
2574
|
+
return projectMeeting(row);
|
|
2575
|
+
},
|
|
2576
|
+
async cancelMeeting(meetingId, reason, byUserId = null) {
|
|
2577
|
+
const row = await guard(
|
|
2578
|
+
"meet.cancelMeeting",
|
|
2579
|
+
() => db.rpc(RPCS.cancelMeeting, {
|
|
2580
|
+
p_meeting_id: meetingId,
|
|
2581
|
+
p_reason: reason,
|
|
2582
|
+
p_by_user_id: byUserId
|
|
2583
|
+
})
|
|
2584
|
+
);
|
|
2585
|
+
return projectMeeting(row);
|
|
2586
|
+
},
|
|
2587
|
+
async archiveMeeting(meetingId, byUserId = null) {
|
|
2588
|
+
const row = await guard(
|
|
2589
|
+
"meet.archiveMeeting",
|
|
2590
|
+
() => db.rpc(RPCS.archiveMeeting, { p_meeting_id: meetingId, p_by_user_id: byUserId })
|
|
2591
|
+
);
|
|
2592
|
+
return projectMeeting(row);
|
|
2593
|
+
},
|
|
2594
|
+
async restoreMeeting(meetingId, byUserId = null) {
|
|
2595
|
+
const row = await guard(
|
|
2596
|
+
"meet.restoreMeeting",
|
|
2597
|
+
() => db.rpc(RPCS.restoreMeeting, { p_meeting_id: meetingId, p_by_user_id: byUserId })
|
|
2598
|
+
);
|
|
2599
|
+
return projectMeeting(row);
|
|
2600
|
+
},
|
|
2601
|
+
async setOccurrence(args) {
|
|
2602
|
+
const row = await guard(
|
|
2603
|
+
"meet.setOccurrence",
|
|
2604
|
+
() => db.rpc(RPCS.setOccurrence, {
|
|
2605
|
+
p_meeting_id: args.meetingId,
|
|
2606
|
+
p_original_start: args.originalStart,
|
|
2607
|
+
p_action: args.action,
|
|
2608
|
+
p_new_start: args.newStart ?? null,
|
|
2609
|
+
p_new_duration_minutes: args.newDurationMinutes ?? null,
|
|
2610
|
+
p_reason: args.reason ?? null,
|
|
2611
|
+
p_by_user_id: args.byUserId ?? null
|
|
2612
|
+
})
|
|
2613
|
+
);
|
|
2614
|
+
return projectOverride(row);
|
|
2615
|
+
},
|
|
2616
|
+
async meetingOccurrences(meetingId, { from, to, limit = 200 }) {
|
|
2617
|
+
const rows = await guard(
|
|
2618
|
+
"meet.meetingOccurrences",
|
|
2619
|
+
() => db.rpc(RPCS.meetingOccurrences, {
|
|
2620
|
+
p_meeting_id: meetingId,
|
|
2621
|
+
p_from: from,
|
|
2622
|
+
p_to: to,
|
|
2623
|
+
p_limit: limit
|
|
2624
|
+
})
|
|
2625
|
+
);
|
|
2626
|
+
return rows.map(projectOccurrence);
|
|
2627
|
+
},
|
|
2628
|
+
async occurrencesBetween({ from, to, organizationId = null, limit = 500 }) {
|
|
2629
|
+
const rows = await guard(
|
|
2630
|
+
"meet.occurrencesBetween",
|
|
2631
|
+
() => db.rpc(RPCS.occurrencesBetween, {
|
|
2632
|
+
p_from: from,
|
|
2633
|
+
p_to: to,
|
|
2634
|
+
p_organization_id: organizationId,
|
|
2635
|
+
p_limit: limit
|
|
2636
|
+
})
|
|
2637
|
+
);
|
|
2638
|
+
return rows.map(projectUpcoming);
|
|
2639
|
+
},
|
|
2640
|
+
async invitees(meetingId) {
|
|
2641
|
+
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 });
|
|
2642
|
+
if (response.error !== null) throw normalizeMeetError(response.error, "meet.invitees");
|
|
2643
|
+
return (response.data ?? []).map(projectInvitee);
|
|
2644
|
+
},
|
|
2645
|
+
async addInvitees(meetingId, people, byUserId = null) {
|
|
2646
|
+
const rows = await guard(
|
|
2647
|
+
"meet.addInvitees",
|
|
2648
|
+
() => db.rpc(RPCS.addInvitees, {
|
|
2649
|
+
p_meeting_id: meetingId,
|
|
2650
|
+
p_invitees: people.map((person) => ({
|
|
2651
|
+
...person.userId !== void 0 ? { user_id: person.userId } : {},
|
|
2652
|
+
...person.email !== void 0 ? { email: person.email } : {},
|
|
2653
|
+
...person.displayName !== void 0 ? { display_name: person.displayName } : {},
|
|
2654
|
+
...person.role !== void 0 ? { role: person.role } : {}
|
|
2655
|
+
})),
|
|
2656
|
+
p_by_user_id: byUserId
|
|
2657
|
+
})
|
|
2658
|
+
);
|
|
2659
|
+
return rows.map(projectInvitee);
|
|
2660
|
+
},
|
|
2661
|
+
async setInviteeRole(inviteeId, role, byUserId = null) {
|
|
2662
|
+
const row = await guard(
|
|
2663
|
+
"meet.setInviteeRole",
|
|
2664
|
+
() => db.rpc(RPCS.setInviteeRole, {
|
|
2665
|
+
p_invitee_id: inviteeId,
|
|
2666
|
+
p_role: role,
|
|
2667
|
+
p_by_user_id: byUserId
|
|
2668
|
+
})
|
|
2669
|
+
);
|
|
2670
|
+
return projectInvitee(row);
|
|
2671
|
+
},
|
|
2672
|
+
async removeInvitee(inviteeId, byUserId = null) {
|
|
2673
|
+
const row = await guard(
|
|
2674
|
+
"meet.removeInvitee",
|
|
2675
|
+
() => db.rpc(RPCS.removeInvitee, { p_invitee_id: inviteeId, p_by_user_id: byUserId })
|
|
2676
|
+
);
|
|
2677
|
+
return projectInvitee(row);
|
|
2678
|
+
},
|
|
2679
|
+
async respond(meetingId, answer, note = null) {
|
|
2680
|
+
const row = await guard(
|
|
2681
|
+
"meet.respond",
|
|
2682
|
+
() => db.rpc(RPCS.respond, {
|
|
2683
|
+
p_meeting_id: meetingId,
|
|
2684
|
+
p_response: answer,
|
|
2685
|
+
p_note: note,
|
|
2686
|
+
p_by_user_id: null
|
|
2687
|
+
})
|
|
2688
|
+
);
|
|
2689
|
+
return projectInvitee(row);
|
|
2690
|
+
},
|
|
2691
|
+
async invitationByToken(secret) {
|
|
2692
|
+
const body = await guard(
|
|
2693
|
+
"meet.invitationByToken",
|
|
2694
|
+
() => db.rpc(RPCS.invitationByToken, { p_secret: secret })
|
|
2695
|
+
);
|
|
2696
|
+
return projectRsvpInvitation(body);
|
|
2697
|
+
},
|
|
2698
|
+
async respondByToken(secret, answer, note = null) {
|
|
2699
|
+
const body = await guard(
|
|
2700
|
+
"meet.respondByToken",
|
|
2701
|
+
() => db.rpc(RPCS.respondByToken, {
|
|
2702
|
+
p_secret: secret,
|
|
2703
|
+
p_response: answer,
|
|
2704
|
+
p_note: note
|
|
2705
|
+
})
|
|
2706
|
+
);
|
|
2707
|
+
return projectRsvpInvitation(body);
|
|
2708
|
+
},
|
|
2336
2709
|
async attendees(meetingId) {
|
|
2337
2710
|
const response = await db.from(TABLES.participants).select(ATTENDEE_COLUMNS).eq("meeting_id", meetingId).order("joined_at", { ascending: true }).order("id", { ascending: true });
|
|
2338
2711
|
if (response.error !== null) throw normalizeMeetError(response.error, "meet.attendees");
|
|
@@ -2760,14 +3133,14 @@ function noteTakerStatus(args) {
|
|
|
2760
3133
|
const row = typeof raw === "object" && raw !== null && !Array.isArray(raw) ? raw : {};
|
|
2761
3134
|
const recorded = readState(row["state"]);
|
|
2762
3135
|
const detail = typeof row["reason"] === "string" && row["reason"].length > 0 ? row["reason"] : null;
|
|
2763
|
-
const
|
|
3136
|
+
const refusal2 = args.refusal ?? null;
|
|
2764
3137
|
if (recorded === null) {
|
|
2765
|
-
return { state: "absent", detail, present, refusal };
|
|
3138
|
+
return { state: "absent", detail, present, refusal: refusal2 };
|
|
2766
3139
|
}
|
|
2767
3140
|
if (recorded === "transcribing" && !present) {
|
|
2768
|
-
return { state: "handoff_pending", detail, present, refusal };
|
|
3141
|
+
return { state: "handoff_pending", detail, present, refusal: refusal2 };
|
|
2769
3142
|
}
|
|
2770
|
-
return { state: recorded, detail, present, refusal };
|
|
3143
|
+
return { state: recorded, detail, present, refusal: refusal2 };
|
|
2771
3144
|
}
|
|
2772
3145
|
function noteTakerLabel(status) {
|
|
2773
3146
|
switch (status.state) {
|
|
@@ -3792,11 +4165,11 @@ function computeStage(args) {
|
|
|
3792
4165
|
presenting: sharer !== null
|
|
3793
4166
|
};
|
|
3794
4167
|
}
|
|
3795
|
-
function gridColumns(
|
|
3796
|
-
if (
|
|
3797
|
-
if (
|
|
3798
|
-
if (
|
|
3799
|
-
if (
|
|
4168
|
+
function gridColumns(count2) {
|
|
4169
|
+
if (count2 <= 1) return 1;
|
|
4170
|
+
if (count2 <= 4) return 2;
|
|
4171
|
+
if (count2 <= 9) return 3;
|
|
4172
|
+
if (count2 <= 16) return 4;
|
|
3800
4173
|
return 5;
|
|
3801
4174
|
}
|
|
3802
4175
|
|
|
@@ -3970,7 +4343,7 @@ function meetingWhen(meeting, locale) {
|
|
|
3970
4343
|
if (start === null) return "Time not recorded";
|
|
3971
4344
|
const startedAt = new Date(start);
|
|
3972
4345
|
if (Number.isNaN(startedAt.getTime())) return "Time not recorded";
|
|
3973
|
-
const
|
|
4346
|
+
const when2 = startedAt.toLocaleString(locale, {
|
|
3974
4347
|
weekday: "long",
|
|
3975
4348
|
year: "numeric",
|
|
3976
4349
|
month: "long",
|
|
@@ -3978,11 +4351,11 @@ function meetingWhen(meeting, locale) {
|
|
|
3978
4351
|
hour: "numeric",
|
|
3979
4352
|
minute: "2-digit"
|
|
3980
4353
|
});
|
|
3981
|
-
if (meeting.endedAt === null || meeting.startedAt === null) return
|
|
4354
|
+
if (meeting.endedAt === null || meeting.startedAt === null) return when2;
|
|
3982
4355
|
const endedAt = new Date(meeting.endedAt);
|
|
3983
|
-
if (Number.isNaN(endedAt.getTime())) return
|
|
4356
|
+
if (Number.isNaN(endedAt.getTime())) return when2;
|
|
3984
4357
|
const elapsedMs = endedAt.getTime() - startedAt.getTime();
|
|
3985
|
-
return `${
|
|
4358
|
+
return `${when2} \xB7 ${formatDurationMs(elapsedMs, { style: "long" })}`;
|
|
3986
4359
|
}
|
|
3987
4360
|
var GUEST_RECORD_ACCESS_LEVELS = [
|
|
3988
4361
|
"none",
|
|
@@ -4089,8 +4462,8 @@ async function loadGuestMeetingRecord(args) {
|
|
|
4089
4462
|
);
|
|
4090
4463
|
}
|
|
4091
4464
|
if (!response.ok) {
|
|
4092
|
-
const
|
|
4093
|
-
return refusedBundle(meeting,
|
|
4465
|
+
const refusal2 = await readRefusal(response);
|
|
4466
|
+
return refusedBundle(meeting, refusal2.message, refusal2.remedy);
|
|
4094
4467
|
}
|
|
4095
4468
|
const body = await response.json();
|
|
4096
4469
|
const payload = typeof body["data"] === "object" && body["data"] !== null ? body["data"] : body;
|
|
@@ -4181,8 +4554,8 @@ async function setGuestRecordAccess(args) {
|
|
|
4181
4554
|
}
|
|
4182
4555
|
});
|
|
4183
4556
|
if (!response.ok) {
|
|
4184
|
-
const
|
|
4185
|
-
throw new MeetError("forbidden",
|
|
4557
|
+
const refusal2 = await readRefusal(response);
|
|
4558
|
+
throw new MeetError("forbidden", refusal2.message, refusal2.remedy);
|
|
4186
4559
|
}
|
|
4187
4560
|
const body = await response.json();
|
|
4188
4561
|
const payload = typeof body["data"] === "object" && body["data"] !== null ? body["data"] : body;
|
|
@@ -4849,8 +5222,8 @@ function callStateLabel(state, outgoing) {
|
|
|
4849
5222
|
return "Call";
|
|
4850
5223
|
}
|
|
4851
5224
|
}
|
|
4852
|
-
function countLabel(
|
|
4853
|
-
return `${
|
|
5225
|
+
function countLabel(count2, singular, plural = `${singular}s`) {
|
|
5226
|
+
return `${count2} ${count2 === 1 ? singular : plural}`;
|
|
4854
5227
|
}
|
|
4855
5228
|
|
|
4856
5229
|
// src/react/icons.tsx
|
|
@@ -5908,7 +6281,7 @@ function ScheduleMeetingBody({
|
|
|
5908
6281
|
host
|
|
5909
6282
|
}) {
|
|
5910
6283
|
const [title, setTitle] = useState3("");
|
|
5911
|
-
const [
|
|
6284
|
+
const [when2, setWhen] = useState3("");
|
|
5912
6285
|
const [kind, setKind] = useState3("scheduled");
|
|
5913
6286
|
const [busy, setBusy] = useState3(false);
|
|
5914
6287
|
const [link, setLink] = useState3(null);
|
|
@@ -5928,7 +6301,7 @@ function ScheduleMeetingBody({
|
|
|
5928
6301
|
hostUserId: host.identity.userId,
|
|
5929
6302
|
title: title.trim(),
|
|
5930
6303
|
kind,
|
|
5931
|
-
scheduledFor:
|
|
6304
|
+
scheduledFor: when2.length > 0 ? new Date(when2).toISOString() : null
|
|
5932
6305
|
}).then((meeting) => {
|
|
5933
6306
|
setLink(meetingLink(appBaseUrl, meeting.slug));
|
|
5934
6307
|
onScheduled?.(meeting.slug);
|
|
@@ -5958,7 +6331,7 @@ function ScheduleMeetingBody({
|
|
|
5958
6331
|
{
|
|
5959
6332
|
className: "mx-meet__input",
|
|
5960
6333
|
type: "datetime-local",
|
|
5961
|
-
value:
|
|
6334
|
+
value: when2,
|
|
5962
6335
|
onChange: (event) => setWhen(event.target.value)
|
|
5963
6336
|
}
|
|
5964
6337
|
)
|
|
@@ -6538,6 +6911,57 @@ function createMeetingInviteHandler(options) {
|
|
|
6538
6911
|
}
|
|
6539
6912
|
};
|
|
6540
6913
|
}
|
|
6914
|
+
|
|
6915
|
+
// src/core/management.ts
|
|
6916
|
+
async function refusal(response, operation) {
|
|
6917
|
+
let body = null;
|
|
6918
|
+
try {
|
|
6919
|
+
body = await response.json();
|
|
6920
|
+
} catch {
|
|
6921
|
+
body = null;
|
|
6922
|
+
}
|
|
6923
|
+
const said = readServerRefusal(body);
|
|
6924
|
+
return new MeetError(
|
|
6925
|
+
response.status === 403 || response.status === 401 ? "forbidden" : response.status === 404 ? "not-found" : response.status === 409 ? "conflict" : "transport",
|
|
6926
|
+
`${operation}: ${said?.reason ?? `the server answered ${response.status}`}`,
|
|
6927
|
+
said?.remedy || "The meeting itself is saved \u2014 only the telling failed. Try again; the call is idempotent."
|
|
6928
|
+
);
|
|
6929
|
+
}
|
|
6930
|
+
function count(body, key) {
|
|
6931
|
+
const value = body[key];
|
|
6932
|
+
return typeof value === "number" ? value : 0;
|
|
6933
|
+
}
|
|
6934
|
+
async function announceMeeting(api, meetingId) {
|
|
6935
|
+
const operation = "meet.announceMeeting";
|
|
6936
|
+
const response = await api.request({
|
|
6937
|
+
path: meetingPath(MEET_ROUTES.meetingAnnounce, meetingId),
|
|
6938
|
+
method: "POST",
|
|
6939
|
+
requireAuth: true,
|
|
6940
|
+
operation
|
|
6941
|
+
});
|
|
6942
|
+
if (!response.ok) throw await refusal(response, operation);
|
|
6943
|
+
const body = await response.json();
|
|
6944
|
+
return {
|
|
6945
|
+
invitations: count(body, "invitations"),
|
|
6946
|
+
updates: count(body, "updates"),
|
|
6947
|
+
cancellations: count(body, "cancellations"),
|
|
6948
|
+
alreadyCurrent: count(body, "already_current"),
|
|
6949
|
+
remindersQueued: count(body, "reminders_queued"),
|
|
6950
|
+
remindersCancelled: count(body, "reminders_cancelled")
|
|
6951
|
+
};
|
|
6952
|
+
}
|
|
6953
|
+
async function pushMeetingToGoogleCalendar(api, meetingId, target) {
|
|
6954
|
+
const operation = "meet.pushMeetingToGoogleCalendar";
|
|
6955
|
+
const response = await api.request({
|
|
6956
|
+
path: meetingPath(MEET_ROUTES.meetingGoogleCalendar, meetingId),
|
|
6957
|
+
method: "POST",
|
|
6958
|
+
requireAuth: true,
|
|
6959
|
+
operation,
|
|
6960
|
+
body: { connection_id: target.connectionId, calendar_id: target.calendarId ?? "primary" }
|
|
6961
|
+
});
|
|
6962
|
+
if (!response.ok) throw await refusal(response, operation);
|
|
6963
|
+
return await response.json();
|
|
6964
|
+
}
|
|
6541
6965
|
export {
|
|
6542
6966
|
AiPanel,
|
|
6543
6967
|
AttendanceNotice,
|
|
@@ -6623,6 +7047,7 @@ export {
|
|
|
6623
7047
|
TRANSCRIPTION_TOPIC,
|
|
6624
7048
|
WifiLowIcon,
|
|
6625
7049
|
absenceSentence,
|
|
7050
|
+
announceMeeting,
|
|
6626
7051
|
applyRoomEvent,
|
|
6627
7052
|
asCallId,
|
|
6628
7053
|
asMeetingId,
|
|
@@ -6637,6 +7062,7 @@ export {
|
|
|
6637
7062
|
canModerateParticipant,
|
|
6638
7063
|
canTransition,
|
|
6639
7064
|
captionSegment,
|
|
7065
|
+
changesPatch,
|
|
6640
7066
|
computeStage,
|
|
6641
7067
|
countLabel,
|
|
6642
7068
|
createCallCenter,
|
|
@@ -6681,6 +7107,7 @@ export {
|
|
|
6681
7107
|
mapQuality,
|
|
6682
7108
|
meetingInviteAction,
|
|
6683
7109
|
meetingLink,
|
|
7110
|
+
meetingPath,
|
|
6684
7111
|
meetingSlug,
|
|
6685
7112
|
meetingTopic,
|
|
6686
7113
|
meetingWhen,
|
|
@@ -6697,9 +7124,15 @@ export {
|
|
|
6697
7124
|
parseRoomToken,
|
|
6698
7125
|
participantSummary,
|
|
6699
7126
|
playableRecordingFileId,
|
|
7127
|
+
projectInvitee,
|
|
6700
7128
|
projectNote,
|
|
7129
|
+
projectOccurrence,
|
|
7130
|
+
projectOverride,
|
|
6701
7131
|
projectParticipant,
|
|
7132
|
+
projectRsvpInvitation,
|
|
6702
7133
|
projectSegment,
|
|
7134
|
+
projectUpcoming,
|
|
7135
|
+
pushMeetingToGoogleCalendar,
|
|
6703
7136
|
readServerRefusal,
|
|
6704
7137
|
readableTranscript,
|
|
6705
7138
|
recordingLabel,
|
|
@@ -6707,6 +7140,7 @@ export {
|
|
|
6707
7140
|
roleOf,
|
|
6708
7141
|
roomTopic,
|
|
6709
7142
|
setGuestRecordAccess,
|
|
7143
|
+
settingsPatch,
|
|
6710
7144
|
snapshotParticipant,
|
|
6711
7145
|
unavailableFrom,
|
|
6712
7146
|
useCalls,
|