@ai-matrx/meet 0.7.5 → 0.7.7
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 +34 -0
- package/dist/index.cjs +569 -20
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +540 -232
- package/dist/index.d.ts +540 -232
- package/dist/index.js +569 -20
- package/dist/index.js.map +1 -1
- package/dist/react.cjs +543 -38
- 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 +543 -38
- package/dist/react.js.map +1 -1
- package/dist/styles.css +8 -0
- package/package.json +4 -4
package/dist/react.cjs
CHANGED
|
@@ -32,6 +32,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
32
32
|
// src/react.ts
|
|
33
33
|
var react_exports = {};
|
|
34
34
|
__export(react_exports, {
|
|
35
|
+
ATTENDANCE_CSV_HEADER: () => ATTENDANCE_CSV_HEADER,
|
|
35
36
|
AiPanel: () => AiPanel,
|
|
36
37
|
AttendanceNotice: () => AttendanceNotice,
|
|
37
38
|
Avatar: () => Avatar,
|
|
@@ -135,6 +136,7 @@ __export(react_exports, {
|
|
|
135
136
|
TRANSCRIPTION_TOPIC: () => TRANSCRIPTION_TOPIC,
|
|
136
137
|
WifiLowIcon: () => WifiLowIcon,
|
|
137
138
|
absenceSentence: () => absenceSentence,
|
|
139
|
+
activeWrapUpSummary: () => activeWrapUpSummary,
|
|
138
140
|
announceMeeting: () => announceMeeting,
|
|
139
141
|
applyRoomEvent: () => applyRoomEvent,
|
|
140
142
|
asCallId: () => asCallId,
|
|
@@ -143,6 +145,9 @@ __export(react_exports, {
|
|
|
143
145
|
asParticipantIdentity: () => asParticipantIdentity,
|
|
144
146
|
asRoomName: () => asRoomName,
|
|
145
147
|
asUserId: () => asUserId,
|
|
148
|
+
attendanceCsv: () => attendanceCsv,
|
|
149
|
+
attendanceReport: () => attendanceReport,
|
|
150
|
+
attendanceTotals: () => attendanceTotals,
|
|
146
151
|
callInviteAction: () => callInviteAction,
|
|
147
152
|
callInviteText: () => callInviteText,
|
|
148
153
|
callStateLabel: () => callStateLabel,
|
|
@@ -151,6 +156,7 @@ __export(react_exports, {
|
|
|
151
156
|
canTransition: () => canTransition,
|
|
152
157
|
captionSegment: () => captionSegment,
|
|
153
158
|
changesPatch: () => changesPatch,
|
|
159
|
+
clockPosition: () => clockPosition,
|
|
154
160
|
computeStage: () => computeStage,
|
|
155
161
|
countLabel: () => countLabel,
|
|
156
162
|
createCallCenter: () => createCallCenter,
|
|
@@ -191,11 +197,14 @@ __export(react_exports, {
|
|
|
191
197
|
learnName: () => learnName,
|
|
192
198
|
learnNames: () => learnNames,
|
|
193
199
|
listenOnlyNotice: () => listenOnlyNotice,
|
|
200
|
+
loadFollowUpDraft: () => loadFollowUpDraft,
|
|
194
201
|
loadGuestMeetingRecord: () => loadGuestMeetingRecord,
|
|
202
|
+
loadMeetingChat: () => loadMeetingChat,
|
|
195
203
|
loadMeetingRecord: () => loadMeetingRecord,
|
|
196
204
|
mapConnectionState: () => mapConnectionState,
|
|
197
205
|
mapQuality: () => mapQuality,
|
|
198
206
|
meetingInviteAction: () => meetingInviteAction,
|
|
207
|
+
meetingLength: () => meetingLength,
|
|
199
208
|
meetingLink: () => meetingLink,
|
|
200
209
|
meetingPath: () => meetingPath,
|
|
201
210
|
meetingSlug: () => meetingSlug,
|
|
@@ -210,16 +219,19 @@ __export(react_exports, {
|
|
|
210
219
|
noteTakerLabel: () => noteTakerLabel,
|
|
211
220
|
noteTakerParticipant: () => noteTakerParticipant,
|
|
212
221
|
noteTakerStatus: () => noteTakerStatus,
|
|
222
|
+
notesDocument: () => notesDocument,
|
|
213
223
|
orderParticipants: () => orderParticipants,
|
|
214
224
|
parseRoomToken: () => parseRoomToken,
|
|
215
225
|
participantSummary: () => participantSummary,
|
|
216
226
|
playableRecordingFileId: () => playableRecordingFileId,
|
|
217
227
|
projectInvitee: () => projectInvitee,
|
|
228
|
+
projectLibraryRecording: () => projectLibraryRecording,
|
|
218
229
|
projectNote: () => projectNote,
|
|
219
230
|
projectOccurrence: () => projectOccurrence,
|
|
220
231
|
projectOverride: () => projectOverride,
|
|
221
232
|
projectParticipant: () => projectParticipant,
|
|
222
233
|
projectRsvpInvitation: () => projectRsvpInvitation,
|
|
234
|
+
projectSearchHit: () => projectSearchHit,
|
|
223
235
|
projectSegment: () => projectSegment,
|
|
224
236
|
projectUpcoming: () => projectUpcoming,
|
|
225
237
|
pushMeetingToGoogleCalendar: () => pushMeetingToGoogleCalendar,
|
|
@@ -227,11 +239,17 @@ __export(react_exports, {
|
|
|
227
239
|
readableTranscript: () => readableTranscript,
|
|
228
240
|
recordingLabel: () => recordingLabel,
|
|
229
241
|
recordingNotice: () => recordingNotice,
|
|
242
|
+
recordingOffsetMs: () => recordingOffsetMs,
|
|
230
243
|
roleOf: () => roleOf,
|
|
231
244
|
roomTopic: () => roomTopic,
|
|
245
|
+
searchTranscript: () => searchTranscript,
|
|
246
|
+
sendFollowUp: () => sendFollowUp,
|
|
232
247
|
setGuestRecordAccess: () => setGuestRecordAccess,
|
|
233
248
|
settingsPatch: () => settingsPatch,
|
|
234
249
|
snapshotParticipant: () => snapshotParticipant,
|
|
250
|
+
snippetRuns: () => snippetRuns,
|
|
251
|
+
summarizeMeetingRecordRestrictions: () => summarizeMeetingRecordRestrictions,
|
|
252
|
+
transcriptDocument: () => transcriptDocument,
|
|
235
253
|
unavailableFrom: () => unavailableFrom,
|
|
236
254
|
useAudioLevel: () => useAudioLevel,
|
|
237
255
|
useCalls: () => useCalls,
|
|
@@ -267,7 +285,8 @@ __export(react_exports, {
|
|
|
267
285
|
useStage: () => useStage,
|
|
268
286
|
useTrack: () => useTrack,
|
|
269
287
|
userTopic: () => userTopic,
|
|
270
|
-
wasRefused: () => wasRefused
|
|
288
|
+
wasRefused: () => wasRefused,
|
|
289
|
+
wrapUpStatus: () => wrapUpStatus
|
|
271
290
|
});
|
|
272
291
|
module.exports = __toCommonJS(react_exports);
|
|
273
292
|
|
|
@@ -469,6 +488,12 @@ var MEET_ROUTES = {
|
|
|
469
488
|
meetingAnnounce: "/api/v1/meet/meetings/{meeting_id}/announce",
|
|
470
489
|
/** Put a one-off meeting on the caller's own Google Calendar (gated by the server). */
|
|
471
490
|
meetingGoogleCalendar: "/api/v1/meet/meetings/{meeting_id}/google-calendar",
|
|
491
|
+
/**
|
|
492
|
+
* Meet wave 3: the recap email after the meeting. `GET` = the draft (subject,
|
|
493
|
+
* body from the wrap-up, who it can go to); `POST` = send what the host
|
|
494
|
+
* reviewed (`dry_run` renders and queues nothing). Never sent automatically.
|
|
495
|
+
*/
|
|
496
|
+
meetingFollowUp: "/api/v1/meet/meetings/{meeting_id}/follow-up",
|
|
472
497
|
/**
|
|
473
498
|
* Meet wave 2: EVERY in-meeting host power (mute, stop video, mute all, ask to
|
|
474
499
|
* unmute, spotlight, lower hands, co-host, hand over, rename, remove, report).
|
|
@@ -598,10 +623,11 @@ function createTokenClient(options) {
|
|
|
598
623
|
throw error;
|
|
599
624
|
}
|
|
600
625
|
if (response.status === 401 || response.status === 403) {
|
|
626
|
+
const said = await serverSentence(response);
|
|
601
627
|
throw new MeetError(
|
|
602
628
|
"forbidden",
|
|
603
|
-
|
|
604
|
-
"
|
|
629
|
+
said?.message ?? "You can't join this meeting.",
|
|
630
|
+
said?.remedy ?? "Ask the person who shared the link to let you in, then open the link again."
|
|
605
631
|
);
|
|
606
632
|
}
|
|
607
633
|
if (response.status === 404) {
|
|
@@ -612,12 +638,7 @@ function createTokenClient(options) {
|
|
|
612
638
|
);
|
|
613
639
|
}
|
|
614
640
|
if (response.status === 410) {
|
|
615
|
-
|
|
616
|
-
try {
|
|
617
|
-
const body = await response.json();
|
|
618
|
-
if (typeof body?.detail?.message === "string") reason = body.detail.message;
|
|
619
|
-
} catch {
|
|
620
|
-
}
|
|
641
|
+
const reason = (await serverSentence(response))?.message ?? "This meeting was cancelled.";
|
|
621
642
|
throw new MeetError(
|
|
622
643
|
"cancelled",
|
|
623
644
|
reason,
|
|
@@ -625,10 +646,11 @@ function createTokenClient(options) {
|
|
|
625
646
|
);
|
|
626
647
|
}
|
|
627
648
|
if (response.status === 423) {
|
|
649
|
+
const said = await serverSentence(response);
|
|
628
650
|
throw new MeetError(
|
|
629
651
|
"room-locked",
|
|
630
|
-
"
|
|
631
|
-
"The host locked the room. Ask them to unlock
|
|
652
|
+
said?.message ?? "The meeting is locked.",
|
|
653
|
+
said?.remedy ?? "The host locked the room. Ask them to unlock it, then join again."
|
|
632
654
|
);
|
|
633
655
|
}
|
|
634
656
|
if (response.status === 503) {
|
|
@@ -699,6 +721,19 @@ function placeholder(roomName) {
|
|
|
699
721
|
lobby: false
|
|
700
722
|
};
|
|
701
723
|
}
|
|
724
|
+
async function serverSentence(response) {
|
|
725
|
+
try {
|
|
726
|
+
const body = await response.json();
|
|
727
|
+
const source = body !== null && typeof body.detail === "object" && body.detail !== null ? body.detail : body;
|
|
728
|
+
if (source === null) return null;
|
|
729
|
+
const message = typeof source.user_message === "string" && source.user_message.trim() !== "" ? source.user_message : typeof source.message === "string" && source.message.trim() !== "" ? source.message : null;
|
|
730
|
+
if (message === null) return null;
|
|
731
|
+
const remedy = typeof source.remedy === "string" && source.remedy.trim() !== "" ? source.remedy : void 0;
|
|
732
|
+
return { message, remedy };
|
|
733
|
+
} catch {
|
|
734
|
+
return null;
|
|
735
|
+
}
|
|
736
|
+
}
|
|
702
737
|
|
|
703
738
|
// src/core/ai.ts
|
|
704
739
|
var MEET_ANSWER_EVENTS = {
|
|
@@ -1142,7 +1177,7 @@ var DEFAULT_RING_TIMEOUT_MS = 45e3;
|
|
|
1142
1177
|
function createCallCenter(options) {
|
|
1143
1178
|
const { manager, repository, identity } = options;
|
|
1144
1179
|
const timers = options.timers ?? {
|
|
1145
|
-
setTimeout: (run,
|
|
1180
|
+
setTimeout: (run, ms2) => setTimeout(run, ms2),
|
|
1146
1181
|
clearTimeout: (handle) => clearTimeout(handle),
|
|
1147
1182
|
now: () => Date.now()
|
|
1148
1183
|
};
|
|
@@ -1810,6 +1845,7 @@ var EMPTY = {
|
|
|
1810
1845
|
recordingId: null,
|
|
1811
1846
|
chat: [],
|
|
1812
1847
|
chatLoaded: false,
|
|
1848
|
+
chatHistoryError: null,
|
|
1813
1849
|
reactions: [],
|
|
1814
1850
|
spotlight: null,
|
|
1815
1851
|
moderationNotice: null,
|
|
@@ -1919,7 +1955,7 @@ function createMeetStore(initial = {}) {
|
|
|
1919
1955
|
state.names,
|
|
1920
1956
|
messages.map((message) => ({ identity: message.identity, displayName: message.displayName }))
|
|
1921
1957
|
);
|
|
1922
|
-
commit({ ...state, chat: merged.slice(-CHAT_LIMIT), chatLoaded: true, names });
|
|
1958
|
+
commit({ ...state, chat: merged.slice(-CHAT_LIMIT), chatLoaded: true, chatHistoryError: null, names });
|
|
1923
1959
|
},
|
|
1924
1960
|
settleChat(localId, next) {
|
|
1925
1961
|
const index = state.chat.findIndex((message) => message.id === localId);
|
|
@@ -2079,7 +2115,9 @@ function createMeetingSession(options) {
|
|
|
2079
2115
|
return identity.userId;
|
|
2080
2116
|
}
|
|
2081
2117
|
function knockIdentity() {
|
|
2082
|
-
return asParticipantIdentity(
|
|
2118
|
+
return asParticipantIdentity(
|
|
2119
|
+
identity.userId !== null ? `user:${identity.userId}` : `guest:${identity.deviceId}`
|
|
2120
|
+
);
|
|
2083
2121
|
}
|
|
2084
2122
|
function requireMeeting(operation) {
|
|
2085
2123
|
const meeting = store.snapshot().meeting;
|
|
@@ -2114,8 +2152,9 @@ function createMeetingSession(options) {
|
|
|
2114
2152
|
verdict.admitted ? "admitted" : "denied",
|
|
2115
2153
|
verdict.by === null ? null : verdict.by
|
|
2116
2154
|
);
|
|
2117
|
-
if (
|
|
2118
|
-
options.onAdmitted?.();
|
|
2155
|
+
if (decided === knockIdentity()) {
|
|
2156
|
+
if (verdict.admitted) options.onAdmitted?.();
|
|
2157
|
+
else options.onDenied?.();
|
|
2119
2158
|
}
|
|
2120
2159
|
}
|
|
2121
2160
|
},
|
|
@@ -2452,7 +2491,16 @@ var RPCS = {
|
|
|
2452
2491
|
/** One meeting's occurrences between two instants (INVOKER — the reader's RLS). */
|
|
2453
2492
|
meetingOccurrences: "meet_meeting_occurrences",
|
|
2454
2493
|
/** Every occurrence the reader can see between two instants — the Upcoming list. */
|
|
2455
|
-
occurrencesBetween: "meet_occurrences_between"
|
|
2494
|
+
occurrencesBetween: "meet_occurrences_between",
|
|
2495
|
+
/*
|
|
2496
|
+
* ── MEET WAVE 3 — after the meeting. Both INVOKER (the reader's RLS) and both
|
|
2497
|
+
* scoped to the reader's OWN meetings (host, invitee, attended) — RLS is the
|
|
2498
|
+
* ceiling, never the view.
|
|
2499
|
+
*/
|
|
2500
|
+
/** The recordings library. */
|
|
2501
|
+
myRecordings: "meet_my_recordings",
|
|
2502
|
+
/** Titles, agendas, current notes and final transcript lines; snippets mark hits with ⟦ ⟧. */
|
|
2503
|
+
search: "meet_search"
|
|
2456
2504
|
};
|
|
2457
2505
|
var VOID_RPCS = [RPCS.admit, RPCS.recordConsent];
|
|
2458
2506
|
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";
|
|
@@ -3157,12 +3205,117 @@ function createMeetRepository(options) {
|
|
|
3157
3205
|
);
|
|
3158
3206
|
},
|
|
3159
3207
|
async recordings(meetingId) {
|
|
3160
|
-
const response = await db.from(TABLES.recordings).select(RECORDING_COLUMNS).eq("meeting_id", meetingId).order("started_at", { ascending: false });
|
|
3208
|
+
const response = await db.from(TABLES.recordings).select(RECORDING_COLUMNS).eq("meeting_id", meetingId).is("deleted_at", null).order("started_at", { ascending: false });
|
|
3161
3209
|
if (response.error !== null) throw normalizeMeetError(response.error, "meet.recordings");
|
|
3162
3210
|
return (response.data ?? []).map(projectRecording);
|
|
3211
|
+
},
|
|
3212
|
+
async myRecordings({ includeArchived = false, limit = 200 } = {}) {
|
|
3213
|
+
const rows = await guard(
|
|
3214
|
+
"meet.myRecordings",
|
|
3215
|
+
() => db.rpc(RPCS.myRecordings, { p_include_archived: includeArchived, p_limit: limit })
|
|
3216
|
+
);
|
|
3217
|
+
return rows.map(projectLibraryRecording);
|
|
3218
|
+
},
|
|
3219
|
+
async searchMeetings(query, limit = 60) {
|
|
3220
|
+
if (query.trim().length === 0) return [];
|
|
3221
|
+
const rows = await guard(
|
|
3222
|
+
"meet.searchMeetings",
|
|
3223
|
+
() => db.rpc(RPCS.search, { p_query: query, p_limit: limit })
|
|
3224
|
+
);
|
|
3225
|
+
return rows.map(projectSearchHit);
|
|
3226
|
+
},
|
|
3227
|
+
async renameRecording(recordingId, title) {
|
|
3228
|
+
const clean = title === null ? null : title.trim().slice(0, 200) || null;
|
|
3229
|
+
const response = await db.from(TABLES.recordings).update({ title: clean }).eq("id", recordingId).select("id");
|
|
3230
|
+
if (response.error !== null) throw normalizeMeetError(response.error, "meet.renameRecording");
|
|
3231
|
+
if ((response.data ?? []).length === 0) {
|
|
3232
|
+
throw normalizeMeetError(
|
|
3233
|
+
{ message: "permission denied: only the meeting's host or an editor can rename it", code: "42501" },
|
|
3234
|
+
"meet.renameRecording"
|
|
3235
|
+
);
|
|
3236
|
+
}
|
|
3237
|
+
},
|
|
3238
|
+
async setRecordingArchived(recordingId, archived) {
|
|
3239
|
+
const response = await db.from(TABLES.recordings).update({ deleted_at: archived ? (/* @__PURE__ */ new Date()).toISOString() : null }).eq("id", recordingId).select("id");
|
|
3240
|
+
if (response.error !== null) throw normalizeMeetError(response.error, "meet.setRecordingArchived");
|
|
3241
|
+
if ((response.data ?? []).length === 0) {
|
|
3242
|
+
throw normalizeMeetError(
|
|
3243
|
+
{ message: "permission denied: only the meeting's host or an editor can archive it", code: "42501" },
|
|
3244
|
+
"meet.setRecordingArchived"
|
|
3245
|
+
);
|
|
3246
|
+
}
|
|
3163
3247
|
}
|
|
3164
3248
|
};
|
|
3165
3249
|
}
|
|
3250
|
+
var LIBRARY_STATES = [
|
|
3251
|
+
"idle",
|
|
3252
|
+
"starting",
|
|
3253
|
+
"recording",
|
|
3254
|
+
"stopping",
|
|
3255
|
+
"processing",
|
|
3256
|
+
"available",
|
|
3257
|
+
"expired",
|
|
3258
|
+
"failed"
|
|
3259
|
+
];
|
|
3260
|
+
function projectLibraryRecording(row) {
|
|
3261
|
+
const operation = "meet.projectLibraryRecording";
|
|
3262
|
+
const state = text(row, "state");
|
|
3263
|
+
const role = text(row, "my_role");
|
|
3264
|
+
return {
|
|
3265
|
+
id: requireText(row, "recording_id", operation),
|
|
3266
|
+
meetingId: asMeetingId(requireText(row, "meeting_id", operation)),
|
|
3267
|
+
meetingTitle: text(row, "meeting_title") ?? "Meeting",
|
|
3268
|
+
meetingSlug: requireText(row, "meeting_slug", operation),
|
|
3269
|
+
role: role === "host" || role === "cohost" || role === "invitee" ? role : "attendee",
|
|
3270
|
+
state: LIBRARY_STATES.includes(state) ? state : "failed",
|
|
3271
|
+
title: text(row, "recording_title"),
|
|
3272
|
+
fileId: text(row, "file_id"),
|
|
3273
|
+
startedAt: text(row, "started_at"),
|
|
3274
|
+
endedAt: text(row, "ended_at"),
|
|
3275
|
+
durationMs: num(row, "duration_ms"),
|
|
3276
|
+
sizeBytes: num(row, "size_bytes"),
|
|
3277
|
+
failureReason: text(row, "failure_reason"),
|
|
3278
|
+
archivedAt: text(row, "deleted_at"),
|
|
3279
|
+
organizationId: asOrganizationId(requireText(row, "organization_id", operation))
|
|
3280
|
+
};
|
|
3281
|
+
}
|
|
3282
|
+
var SEARCH_SOURCES = [
|
|
3283
|
+
"title",
|
|
3284
|
+
"summary",
|
|
3285
|
+
"decision",
|
|
3286
|
+
"action_item",
|
|
3287
|
+
"note",
|
|
3288
|
+
"transcript"
|
|
3289
|
+
];
|
|
3290
|
+
function projectSearchHit(row) {
|
|
3291
|
+
const operation = "meet.projectSearchHit";
|
|
3292
|
+
const source = text(row, "source");
|
|
3293
|
+
return {
|
|
3294
|
+
meetingId: asMeetingId(requireText(row, "meeting_id", operation)),
|
|
3295
|
+
meetingTitle: text(row, "meeting_title") ?? "Meeting",
|
|
3296
|
+
meetingSlug: requireText(row, "meeting_slug", operation),
|
|
3297
|
+
meetingAt: text(row, "meeting_at"),
|
|
3298
|
+
source: SEARCH_SOURCES.includes(source) ? source : "note",
|
|
3299
|
+
refId: requireText(row, "ref_id", operation),
|
|
3300
|
+
speaker: text(row, "speaker"),
|
|
3301
|
+
snippet: text(row, "snippet") ?? "",
|
|
3302
|
+
at: text(row, "said_at"),
|
|
3303
|
+
rank: num(row, "rank") ?? 0
|
|
3304
|
+
};
|
|
3305
|
+
}
|
|
3306
|
+
function snippetRuns(snippet) {
|
|
3307
|
+
const runs = [];
|
|
3308
|
+
const pattern = /⟦([^⟧]*)⟧/g;
|
|
3309
|
+
let last = 0;
|
|
3310
|
+
for (const match of snippet.matchAll(pattern)) {
|
|
3311
|
+
const start = match.index ?? 0;
|
|
3312
|
+
if (start > last) runs.push({ text: snippet.slice(last, start), hit: false });
|
|
3313
|
+
runs.push({ text: match[1] ?? "", hit: true });
|
|
3314
|
+
last = start + match[0].length;
|
|
3315
|
+
}
|
|
3316
|
+
if (last < snippet.length) runs.push({ text: snippet.slice(last), hit: false });
|
|
3317
|
+
return runs;
|
|
3318
|
+
}
|
|
3166
3319
|
|
|
3167
3320
|
// src/core/session-identity.ts
|
|
3168
3321
|
var RESOLVING = Object.freeze({
|
|
@@ -4793,6 +4946,11 @@ function MeetRuntime(props) {
|
|
|
4793
4946
|
// publish nor subscribe, so the host saying yes has to mint a new one.
|
|
4794
4947
|
onAdmitted: () => {
|
|
4795
4948
|
void engine.rejoin();
|
|
4949
|
+
},
|
|
4950
|
+
// A "no" is ALSO a mint: the server refuses it with its own sentence, which
|
|
4951
|
+
// turns the waiting room into the pre-join screen showing that sentence.
|
|
4952
|
+
onDenied: () => {
|
|
4953
|
+
void engine.rejoin().catch(() => void 0);
|
|
4796
4954
|
}
|
|
4797
4955
|
});
|
|
4798
4956
|
const resolveRoomToken = () => {
|
|
@@ -4815,6 +4973,10 @@ function MeetRuntime(props) {
|
|
|
4815
4973
|
chatReadFor = snapshot.meeting.id;
|
|
4816
4974
|
void inRoom.loadChat().catch((error) => {
|
|
4817
4975
|
const normalized = error;
|
|
4976
|
+
store.patch({
|
|
4977
|
+
chatLoaded: true,
|
|
4978
|
+
chatHistoryError: normalized.remedy ?? "Messages sent before you joined could not be read. New messages still arrive."
|
|
4979
|
+
});
|
|
4818
4980
|
report({
|
|
4819
4981
|
level: "warn",
|
|
4820
4982
|
message: normalized.message ?? "meet: the saved chat could not be read",
|
|
@@ -6914,22 +7076,27 @@ function MeetingOverlays() {
|
|
|
6914
7076
|
const room = useRoom();
|
|
6915
7077
|
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
|
|
6916
7078
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(ReactionOverlay, { reactions }),
|
|
6917
|
-
notice !== null && notice.kind !== "removed" ?
|
|
6918
|
-
|
|
6919
|
-
|
|
6920
|
-
|
|
6921
|
-
|
|
6922
|
-
|
|
6923
|
-
|
|
6924
|
-
|
|
6925
|
-
|
|
6926
|
-
|
|
6927
|
-
|
|
6928
|
-
|
|
6929
|
-
|
|
6930
|
-
|
|
6931
|
-
|
|
6932
|
-
|
|
7079
|
+
notice !== null && notice.kind !== "removed" ? (
|
|
7080
|
+
// A host's request ("the host is asking you to unmute"), not an error:
|
|
7081
|
+
// an assertive status, so it is announced at once without being filed
|
|
7082
|
+
// as a failure (design-system error-box census).
|
|
7083
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "mx-meet__notice-bar", role: "status", "aria-live": "assertive", children: [
|
|
7084
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: notice.text }),
|
|
7085
|
+
notice.kind === "ask-unmute" && room.local?.micEnabled !== true ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
7086
|
+
"button",
|
|
7087
|
+
{
|
|
7088
|
+
type: "button",
|
|
7089
|
+
className: "mx-meet__primary mx-meet__primary--small",
|
|
7090
|
+
onClick: () => {
|
|
7091
|
+
dismiss();
|
|
7092
|
+
void room.toggleMicrophone();
|
|
7093
|
+
},
|
|
7094
|
+
children: "Unmute"
|
|
7095
|
+
}
|
|
7096
|
+
) : null,
|
|
7097
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { type: "button", className: "mx-meet__toast-close", "aria-label": "Dismiss", onClick: dismiss, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(CloseIcon, { width: 14, height: 14 }) })
|
|
7098
|
+
] })
|
|
7099
|
+
) : null
|
|
6933
7100
|
] });
|
|
6934
7101
|
}
|
|
6935
7102
|
function ShortcutsSheet({ onClose }) {
|
|
@@ -7006,7 +7173,8 @@ function ChatPanel({
|
|
|
7006
7173
|
};
|
|
7007
7174
|
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(PanelShell, { title: "Chat", onClose, children: [
|
|
7008
7175
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("ul", { className: "mx-meet__chat", ref: listRef, children: [
|
|
7009
|
-
snapshot?.
|
|
7176
|
+
snapshot?.chatHistoryError != null ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("li", { className: "mx-meet__panel-note", children: snapshot.chatHistoryError }) : null,
|
|
7177
|
+
snapshot?.chatLoaded !== true && messages.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("li", { className: "mx-meet__panel-note", children: "Reading earlier messages\u2026" }) : messages.length === 0 && snapshot?.chatHistoryError == null ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("li", { className: "mx-meet__panel-note", children: "No messages yet. Messages to everyone stay with the meeting, so people who join later see them." }) : null,
|
|
7010
7178
|
messages.map((message) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(ChatLine, { message, local, people }, message.id))
|
|
7011
7179
|
] }),
|
|
7012
7180
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
@@ -7472,7 +7640,7 @@ function EffectsSettings({ preview }) {
|
|
|
7472
7640
|
const effects = useMediaEffects();
|
|
7473
7641
|
const [error, setError] = (0, import_react4.useState)(null);
|
|
7474
7642
|
const fileRef = (0, import_react4.useRef)(null);
|
|
7475
|
-
const supported = effects.backgroundsSupported || preview != null;
|
|
7643
|
+
const supported = effects.backgroundsSupported || preview != null || videoEffectsSupported();
|
|
7476
7644
|
const apply = (next) => {
|
|
7477
7645
|
setError(null);
|
|
7478
7646
|
void effects.set(next);
|
|
@@ -7867,7 +8035,7 @@ function PreJoinBody({
|
|
|
7867
8035
|
}
|
|
7868
8036
|
) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "mx-meet__tile-placeholder", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Avatar, { name: host.identity.displayName, url: host.identity.avatarUrl, size: 96 }) }),
|
|
7869
8037
|
mode === "video" && preview.error !== null ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "mx-meet__preview-note", children: preview.error }) : null,
|
|
7870
|
-
microphone ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "mx-meet__meter mx-meet__meter--preview", role: "meter", "aria-label": "Microphone level", "aria-valuemin": 0, "aria-valuemax": 100, "aria-valuenow": Math.round(level * 100), children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { style: { width: `${Math.min(100, Math.round(level * 160))}%` } }) }) : null
|
|
8038
|
+
microphone && micStream !== null ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "mx-meet__meter mx-meet__meter--preview", role: "meter", "aria-label": "Microphone level", "aria-valuemin": 0, "aria-valuemax": 100, "aria-valuenow": Math.round(level * 100), children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { style: { width: `${Math.min(100, Math.round(level * 160))}%` } }) }) : null
|
|
7871
8039
|
] }),
|
|
7872
8040
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "mx-meet__prejoin-panel", children: [
|
|
7873
8041
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("h1", { className: "mx-meet__prejoin-title", children: "Ready to join?" }),
|
|
@@ -7983,7 +8151,9 @@ function useCameraPreview(deviceId2, effects) {
|
|
|
7983
8151
|
}).catch((thrown) => {
|
|
7984
8152
|
if (!live) return;
|
|
7985
8153
|
const failure = thrown;
|
|
7986
|
-
|
|
8154
|
+
const message = (failure.message ?? "").replace(/^meet\.[A-Za-z.()]+:\s*/, "");
|
|
8155
|
+
const sentence = message.length > 0 ? message.charAt(0).toUpperCase() + message.slice(1) : "";
|
|
8156
|
+
setError([sentence, failure.remedy].filter(Boolean).join(". ") || "The camera did not start.");
|
|
7987
8157
|
});
|
|
7988
8158
|
return () => {
|
|
7989
8159
|
live = false;
|
|
@@ -8859,6 +9029,341 @@ function createMeetingInviteHandler(options) {
|
|
|
8859
9029
|
};
|
|
8860
9030
|
}
|
|
8861
9031
|
|
|
9032
|
+
// src/core/after.ts
|
|
9033
|
+
var import_format4 = require("@ai-matrx/kit/format");
|
|
9034
|
+
function kindOf(person) {
|
|
9035
|
+
if (person.isAgent) return "assistant";
|
|
9036
|
+
if (person.role === "host") return "host";
|
|
9037
|
+
if (person.role === "cohost") return "cohost";
|
|
9038
|
+
if (person.role === "guest") return person.userId !== null ? "signed-in guest" : "guest";
|
|
9039
|
+
return "member";
|
|
9040
|
+
}
|
|
9041
|
+
function ms(iso) {
|
|
9042
|
+
if (iso === null) return null;
|
|
9043
|
+
const at = new Date(iso).getTime();
|
|
9044
|
+
return Number.isNaN(at) ? null : at;
|
|
9045
|
+
}
|
|
9046
|
+
function attendanceReport(meeting, attendees, names = {}) {
|
|
9047
|
+
const end = ms(meeting.endedAt);
|
|
9048
|
+
const rows = attendees.map((person) => {
|
|
9049
|
+
const joined = ms(person.joinedAt);
|
|
9050
|
+
const left = ms(person.leftAt);
|
|
9051
|
+
const until = left ?? end;
|
|
9052
|
+
const duration = joined !== null && until !== null && until >= joined ? until - joined : null;
|
|
9053
|
+
return {
|
|
9054
|
+
id: person.id,
|
|
9055
|
+
identity: person.identity,
|
|
9056
|
+
name: displayNameFor(names, person.identity, person.displayName),
|
|
9057
|
+
userId: person.userId,
|
|
9058
|
+
kind: kindOf(person),
|
|
9059
|
+
joinedAt: person.joinedAt,
|
|
9060
|
+
leftAt: person.leftAt,
|
|
9061
|
+
durationMs: duration,
|
|
9062
|
+
durationToEnd: duration !== null && left === null,
|
|
9063
|
+
admission: person.admissionState
|
|
9064
|
+
};
|
|
9065
|
+
});
|
|
9066
|
+
const rank = {
|
|
9067
|
+
host: 0,
|
|
9068
|
+
cohost: 1,
|
|
9069
|
+
member: 2,
|
|
9070
|
+
"signed-in guest": 3,
|
|
9071
|
+
guest: 4,
|
|
9072
|
+
assistant: 5
|
|
9073
|
+
};
|
|
9074
|
+
return rows.sort((a, b) => {
|
|
9075
|
+
if (rank[a.kind] !== rank[b.kind]) return rank[a.kind] - rank[b.kind];
|
|
9076
|
+
return (ms(a.joinedAt) ?? Number.MAX_SAFE_INTEGER) - (ms(b.joinedAt) ?? Number.MAX_SAFE_INTEGER);
|
|
9077
|
+
});
|
|
9078
|
+
}
|
|
9079
|
+
function attendanceTotals(rows) {
|
|
9080
|
+
const humans = rows.filter((row) => row.kind !== "assistant");
|
|
9081
|
+
const attended = humans.filter((row) => row.joinedAt !== null);
|
|
9082
|
+
const timed = attended.filter((row) => row.durationMs !== null);
|
|
9083
|
+
return {
|
|
9084
|
+
people: attended.length,
|
|
9085
|
+
guests: attended.filter((row) => row.kind === "guest" || row.kind === "signed-in guest").length,
|
|
9086
|
+
neverAdmitted: humans.filter((row) => row.joinedAt === null).length,
|
|
9087
|
+
averageMs: timed.length === 0 ? null : Math.round(timed.reduce((sum, row) => sum + (row.durationMs ?? 0), 0) / timed.length)
|
|
9088
|
+
};
|
|
9089
|
+
}
|
|
9090
|
+
function csvCell(value) {
|
|
9091
|
+
if (value === null) return "";
|
|
9092
|
+
const text3 = String(value);
|
|
9093
|
+
return /[",\n\r]/.test(text3) ? `"${text3.replace(/"/g, '""')}"` : text3;
|
|
9094
|
+
}
|
|
9095
|
+
var ATTENDANCE_CSV_HEADER = [
|
|
9096
|
+
"Meeting",
|
|
9097
|
+
"Meeting started",
|
|
9098
|
+
"Name",
|
|
9099
|
+
"Attended as",
|
|
9100
|
+
"Joined",
|
|
9101
|
+
"Left",
|
|
9102
|
+
"Minutes in the meeting",
|
|
9103
|
+
"Admission"
|
|
9104
|
+
];
|
|
9105
|
+
function attendanceCsv(reports) {
|
|
9106
|
+
const lines = [ATTENDANCE_CSV_HEADER.join(",")];
|
|
9107
|
+
for (const { meeting, rows } of reports) {
|
|
9108
|
+
for (const row of rows) {
|
|
9109
|
+
lines.push(
|
|
9110
|
+
[
|
|
9111
|
+
meeting.title,
|
|
9112
|
+
meeting.startedAt ?? meeting.scheduledFor,
|
|
9113
|
+
row.name,
|
|
9114
|
+
row.kind,
|
|
9115
|
+
row.joinedAt,
|
|
9116
|
+
row.leftAt,
|
|
9117
|
+
row.durationMs === null ? null : Math.round(row.durationMs / 6e3) / 10,
|
|
9118
|
+
row.admission
|
|
9119
|
+
].map(csvCell).join(",")
|
|
9120
|
+
);
|
|
9121
|
+
}
|
|
9122
|
+
}
|
|
9123
|
+
return `${lines.join("\r\n")}\r
|
|
9124
|
+
`;
|
|
9125
|
+
}
|
|
9126
|
+
function recordingOffsetMs(at, recording) {
|
|
9127
|
+
if (recording === null || recording.state !== "available") return null;
|
|
9128
|
+
const start = ms(recording.startedAt);
|
|
9129
|
+
const moment = ms(at);
|
|
9130
|
+
if (start === null || moment === null || moment < start) return null;
|
|
9131
|
+
const end = ms(recording.endedAt);
|
|
9132
|
+
if (end !== null && moment > end) return null;
|
|
9133
|
+
return moment - start;
|
|
9134
|
+
}
|
|
9135
|
+
function clockPosition(offsetMs) {
|
|
9136
|
+
const total = Math.max(0, Math.floor(offsetMs / 1e3));
|
|
9137
|
+
const h = Math.floor(total / 3600);
|
|
9138
|
+
const m = Math.floor(total % 3600 / 60);
|
|
9139
|
+
const s = total % 60;
|
|
9140
|
+
const ss = String(s).padStart(2, "0");
|
|
9141
|
+
return h > 0 ? `${h}:${String(m).padStart(2, "0")}:${ss}` : `${m}:${ss}`;
|
|
9142
|
+
}
|
|
9143
|
+
function searchTranscript(segments, query, names = {}) {
|
|
9144
|
+
const needle = fold(query.trim());
|
|
9145
|
+
if (needle.length === 0) return [];
|
|
9146
|
+
const out = [];
|
|
9147
|
+
for (const segment of segments) {
|
|
9148
|
+
const hay = fold(segment.text);
|
|
9149
|
+
const ranges = [];
|
|
9150
|
+
let from = 0;
|
|
9151
|
+
for (; ; ) {
|
|
9152
|
+
const at = hay.indexOf(needle, from);
|
|
9153
|
+
if (at < 0) break;
|
|
9154
|
+
ranges.push([at, at + needle.length]);
|
|
9155
|
+
from = at + needle.length;
|
|
9156
|
+
}
|
|
9157
|
+
const speaker = fold(displayNameFor(names, segment.identity, segment.speaker));
|
|
9158
|
+
if (ranges.length > 0 || speaker.includes(needle)) {
|
|
9159
|
+
out.push({ segmentId: segment.id, ranges });
|
|
9160
|
+
}
|
|
9161
|
+
}
|
|
9162
|
+
return out;
|
|
9163
|
+
}
|
|
9164
|
+
function fold(value) {
|
|
9165
|
+
return value.normalize("NFD").replace(/[̀-ͯ]/g, (mark) => mark.length === 1 ? "" : mark).toLowerCase();
|
|
9166
|
+
}
|
|
9167
|
+
function clockOfDay(iso, options) {
|
|
9168
|
+
const at = new Date(iso);
|
|
9169
|
+
if (Number.isNaN(at.getTime())) return "";
|
|
9170
|
+
return at.toLocaleTimeString(options.locale, {
|
|
9171
|
+
hour: "numeric",
|
|
9172
|
+
minute: "2-digit",
|
|
9173
|
+
second: "2-digit",
|
|
9174
|
+
...options.timeZone !== void 0 ? { timeZone: options.timeZone } : {}
|
|
9175
|
+
});
|
|
9176
|
+
}
|
|
9177
|
+
function vttStamp(offsetMs) {
|
|
9178
|
+
const total = Math.max(0, Math.round(offsetMs));
|
|
9179
|
+
const h = Math.floor(total / 36e5);
|
|
9180
|
+
const m = Math.floor(total % 36e5 / 6e4);
|
|
9181
|
+
const s = Math.floor(total % 6e4 / 1e3);
|
|
9182
|
+
const milli = total % 1e3;
|
|
9183
|
+
return `${String(h).padStart(2, "0")}:${String(m).padStart(2, "0")}:${String(s).padStart(2, "0")}.${String(milli).padStart(3, "0")}`;
|
|
9184
|
+
}
|
|
9185
|
+
function transcriptDocument(bundle, format, options = {}) {
|
|
9186
|
+
const names = options.names ?? bundle.names;
|
|
9187
|
+
const lines = bundle.transcript.filter((line) => line.text.trim().length > 0);
|
|
9188
|
+
const speaker = (line) => displayNameFor(names, line.identity, line.speaker);
|
|
9189
|
+
if (format === "vtt") {
|
|
9190
|
+
const zeroIso = bundle.recording?.state === "available" && bundle.recording.startedAt !== null ? bundle.recording.startedAt : bundle.meeting.startedAt ?? lines[0]?.startedAt ?? null;
|
|
9191
|
+
const zero = ms(zeroIso) ?? 0;
|
|
9192
|
+
const cues = ["WEBVTT", ""];
|
|
9193
|
+
lines.forEach((line, index) => {
|
|
9194
|
+
const start = (ms(line.startedAt) ?? zero) - zero;
|
|
9195
|
+
if (start < 0) return;
|
|
9196
|
+
const next = lines[index + 1];
|
|
9197
|
+
const nextStart = next !== void 0 ? (ms(next.startedAt) ?? zero) - zero : start + 4e3;
|
|
9198
|
+
const end = Math.max(start + 500, Math.min(nextStart, start + 1e4));
|
|
9199
|
+
cues.push(`${vttStamp(start)} --> ${vttStamp(end)}`);
|
|
9200
|
+
cues.push(`<v ${speaker(line).replace(/[<>]/g, "")}>${line.text.replace(/-->/g, "->")}`);
|
|
9201
|
+
cues.push("");
|
|
9202
|
+
});
|
|
9203
|
+
return cues.join("\n");
|
|
9204
|
+
}
|
|
9205
|
+
const out = [];
|
|
9206
|
+
const title = bundle.meeting.title;
|
|
9207
|
+
out.push(format === "md" ? `# ${title} \u2014 transcript` : `${title} \u2014 transcript`);
|
|
9208
|
+
out.push("");
|
|
9209
|
+
let last = null;
|
|
9210
|
+
for (const line of lines) {
|
|
9211
|
+
if (line.identity !== last) {
|
|
9212
|
+
if (last !== null) out.push("");
|
|
9213
|
+
const who = speaker(line);
|
|
9214
|
+
const clock = clockOfDay(line.startedAt, options);
|
|
9215
|
+
out.push(format === "md" ? `**${who}** \xB7 ${clock}` : `${who} (${clock})`);
|
|
9216
|
+
last = line.identity;
|
|
9217
|
+
}
|
|
9218
|
+
out.push(line.text.trim());
|
|
9219
|
+
}
|
|
9220
|
+
if (lines.length === 0) out.push("No transcript was captured for this meeting.");
|
|
9221
|
+
return `${out.join("\n")}
|
|
9222
|
+
`;
|
|
9223
|
+
}
|
|
9224
|
+
function bullet(note) {
|
|
9225
|
+
const owner = note.assigneeDisplayName !== null ? ` \u2014 ${note.assigneeDisplayName}` : "";
|
|
9226
|
+
return `- ${note.text.trim()}${owner}`;
|
|
9227
|
+
}
|
|
9228
|
+
function notesDocument(bundle, options = {}) {
|
|
9229
|
+
const out = [`# ${bundle.meeting.title}`, ""];
|
|
9230
|
+
if (options.when) out.push(`_${options.when}_`, "");
|
|
9231
|
+
const people = bundle.attendees.filter((person) => !person.isAgent && person.joinedAt !== null).map((person) => displayNameFor(bundle.names, person.identity, person.displayName));
|
|
9232
|
+
if (people.length > 0) out.push(`**Attended:** ${people.join(", ")}`, "");
|
|
9233
|
+
out.push("## Summary", "", bundle.summary?.text.trim() || "No summary was written.", "");
|
|
9234
|
+
out.push("## Decisions", "");
|
|
9235
|
+
out.push(...bundle.decisions.length > 0 ? bundle.decisions.map(bullet) : ["None recorded."], "");
|
|
9236
|
+
out.push("## Action items", "");
|
|
9237
|
+
out.push(
|
|
9238
|
+
...bundle.actionItems.length > 0 ? bundle.actionItems.map(bullet) : ["None recorded."],
|
|
9239
|
+
""
|
|
9240
|
+
);
|
|
9241
|
+
if (options.link) out.push(`[Open the meeting record](${options.link})`, "");
|
|
9242
|
+
return out.join("\n");
|
|
9243
|
+
}
|
|
9244
|
+
function meetingLength(meeting) {
|
|
9245
|
+
const start = ms(meeting.startedAt);
|
|
9246
|
+
const end = ms(meeting.endedAt);
|
|
9247
|
+
if (start === null || end === null || end < start) return null;
|
|
9248
|
+
return (0, import_format4.formatDurationMs)(end - start, { style: "long" });
|
|
9249
|
+
}
|
|
9250
|
+
async function loadMeetingChat(api, meetingId, roomToken = null) {
|
|
9251
|
+
const operation = "meet.loadMeetingChat";
|
|
9252
|
+
let response;
|
|
9253
|
+
try {
|
|
9254
|
+
response = await api.request({
|
|
9255
|
+
path: `${MEET_ROUTES.chat}?meeting_id=${encodeURIComponent(meetingId)}`,
|
|
9256
|
+
method: "GET",
|
|
9257
|
+
operation,
|
|
9258
|
+
requireAuth: roomToken === null,
|
|
9259
|
+
...roomToken !== null ? { headers: { [ROOM_TOKEN_HEADER]: roomToken } } : {}
|
|
9260
|
+
});
|
|
9261
|
+
} catch (error) {
|
|
9262
|
+
return {
|
|
9263
|
+
messages: [],
|
|
9264
|
+
refusal: {
|
|
9265
|
+
message: `The chat could not be read (${error.message}).`,
|
|
9266
|
+
remedy: error.remedy ?? "Reload the page."
|
|
9267
|
+
}
|
|
9268
|
+
};
|
|
9269
|
+
}
|
|
9270
|
+
if (!response.ok) {
|
|
9271
|
+
let body = null;
|
|
9272
|
+
try {
|
|
9273
|
+
body = await response.json();
|
|
9274
|
+
} catch {
|
|
9275
|
+
body = null;
|
|
9276
|
+
}
|
|
9277
|
+
const said = readServerRefusal(body);
|
|
9278
|
+
return {
|
|
9279
|
+
messages: [],
|
|
9280
|
+
refusal: {
|
|
9281
|
+
message: said?.reason ?? `The chat could not be read (the server answered ${response.status}).`,
|
|
9282
|
+
remedy: said?.remedy || "Reload the page."
|
|
9283
|
+
}
|
|
9284
|
+
};
|
|
9285
|
+
}
|
|
9286
|
+
const payload = await response.json();
|
|
9287
|
+
const rows = Array.isArray(payload.messages) ? payload.messages : [];
|
|
9288
|
+
return {
|
|
9289
|
+
messages: rows.map(
|
|
9290
|
+
(row) => typeof row === "object" && row !== null ? chatFromRow(row) : null
|
|
9291
|
+
).filter((message) => message !== null),
|
|
9292
|
+
refusal: null
|
|
9293
|
+
};
|
|
9294
|
+
}
|
|
9295
|
+
async function refusalOf(response, operation) {
|
|
9296
|
+
let body = null;
|
|
9297
|
+
try {
|
|
9298
|
+
body = await response.json();
|
|
9299
|
+
} catch {
|
|
9300
|
+
body = null;
|
|
9301
|
+
}
|
|
9302
|
+
const said = readServerRefusal(body);
|
|
9303
|
+
return new MeetError(
|
|
9304
|
+
response.status === 403 || response.status === 401 ? "forbidden" : response.status === 404 ? "not-found" : response.status === 409 ? "conflict" : "transport",
|
|
9305
|
+
`${operation}: ${said?.reason ?? `the server answered ${response.status}`}`,
|
|
9306
|
+
said?.remedy || "Nothing was sent. Try again."
|
|
9307
|
+
);
|
|
9308
|
+
}
|
|
9309
|
+
function asRecipient(row) {
|
|
9310
|
+
const sources = Array.isArray(row["sources"]) ? row["sources"] : [];
|
|
9311
|
+
return {
|
|
9312
|
+
key: String(row["key"] ?? ""),
|
|
9313
|
+
name: String(row["name"] ?? ""),
|
|
9314
|
+
email: typeof row["email"] === "string" ? row["email"] : null,
|
|
9315
|
+
userId: typeof row["user_id"] === "string" ? row["user_id"] : null,
|
|
9316
|
+
sources: sources.filter((s) => s === "invitee" || s === "attendee"),
|
|
9317
|
+
reachable: row["reachable"] !== false
|
|
9318
|
+
};
|
|
9319
|
+
}
|
|
9320
|
+
async function loadFollowUpDraft(api, meetingId) {
|
|
9321
|
+
const operation = "meet.loadFollowUpDraft";
|
|
9322
|
+
const response = await api.request({
|
|
9323
|
+
path: meetingPath(MEET_ROUTES.meetingFollowUp, meetingId),
|
|
9324
|
+
method: "GET",
|
|
9325
|
+
requireAuth: true,
|
|
9326
|
+
operation
|
|
9327
|
+
});
|
|
9328
|
+
if (!response.ok) throw await refusalOf(response, operation);
|
|
9329
|
+
const body = await response.json();
|
|
9330
|
+
const last = body["last_sent"];
|
|
9331
|
+
return {
|
|
9332
|
+
subject: String(body["subject"] ?? ""),
|
|
9333
|
+
body: String(body["body"] ?? ""),
|
|
9334
|
+
recipients: (Array.isArray(body["recipients"]) ? body["recipients"] : []).map(
|
|
9335
|
+
(row) => asRecipient(row)
|
|
9336
|
+
),
|
|
9337
|
+
wrapUpWritten: body["wrap_up_written"] === true,
|
|
9338
|
+
lastSent: last && typeof last["at"] === "string" ? { at: last["at"], count: typeof last["count"] === "number" ? last["count"] : 0 } : null
|
|
9339
|
+
};
|
|
9340
|
+
}
|
|
9341
|
+
async function sendFollowUp(api, meetingId, input) {
|
|
9342
|
+
const operation = "meet.sendFollowUp";
|
|
9343
|
+
const response = await api.request({
|
|
9344
|
+
path: meetingPath(MEET_ROUTES.meetingFollowUp, meetingId),
|
|
9345
|
+
method: "POST",
|
|
9346
|
+
requireAuth: true,
|
|
9347
|
+
operation,
|
|
9348
|
+
body: {
|
|
9349
|
+
subject: input.subject,
|
|
9350
|
+
body: input.body,
|
|
9351
|
+
recipient_keys: [...input.recipientKeys],
|
|
9352
|
+
dry_run: input.dryRun === true
|
|
9353
|
+
}
|
|
9354
|
+
});
|
|
9355
|
+
if (!response.ok) throw await refusalOf(response, operation);
|
|
9356
|
+
const body = await response.json();
|
|
9357
|
+
return {
|
|
9358
|
+
dryRun: body["dry_run"] === true,
|
|
9359
|
+
queued: Array.isArray(body["queued"]) ? body["queued"] : [],
|
|
9360
|
+
skipped: Array.isArray(body["skipped"]) ? body["skipped"].map((row) => ({
|
|
9361
|
+
key: String(row["key"] ?? ""),
|
|
9362
|
+
reason: String(row["reason"] ?? "")
|
|
9363
|
+
})) : []
|
|
9364
|
+
};
|
|
9365
|
+
}
|
|
9366
|
+
|
|
8862
9367
|
// src/core/management.ts
|
|
8863
9368
|
async function refusal(response, operation) {
|
|
8864
9369
|
let body = null;
|