@ai-matrx/meet 0.7.3 → 0.7.6
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 +74 -0
- package/dist/index.cjs +544 -35
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +219 -24
- package/dist/index.d.ts +219 -24
- package/dist/index.js +544 -35
- package/dist/index.js.map +1 -1
- package/dist/react.cjs +1886 -162
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +136 -12
- package/dist/react.d.ts +136 -12
- package/dist/react.js +1887 -163
- package/dist/react.js.map +1 -1
- package/dist/styles.css +530 -0
- package/package.json +4 -3
package/dist/index.cjs
CHANGED
|
@@ -34,6 +34,7 @@ __export(src_exports, {
|
|
|
34
34
|
CALL_INVITE_VERSION: () => CALL_INVITE_VERSION,
|
|
35
35
|
CONSENT_NOTICE_ATTRIBUTE: () => CONSENT_NOTICE_ATTRIBUTE,
|
|
36
36
|
DATA_TOPICS: () => DATA_TOPICS,
|
|
37
|
+
DEFAULT_EFFECTS: () => DEFAULT_EFFECTS,
|
|
37
38
|
DEFAULT_PAGE_SIZE: () => DEFAULT_PAGE_SIZE,
|
|
38
39
|
END_MEETING_NOT_SERVED: () => END_MEETING_NOT_SERVED,
|
|
39
40
|
FILE_SESSION_PATH: () => FILE_SESSION_PATH,
|
|
@@ -51,6 +52,7 @@ __export(src_exports, {
|
|
|
51
52
|
MEET_EVENTS: () => MEET_EVENTS,
|
|
52
53
|
MEET_ROUTES: () => MEET_ROUTES,
|
|
53
54
|
MEET_SCHEMA: () => MEET_SCHEMA,
|
|
55
|
+
MODERATION_ACTIONS: () => MODERATION_ACTIONS,
|
|
54
56
|
MeetError: () => MeetError,
|
|
55
57
|
NOTE_TAKER_IDENTITY_PREFIX: () => NOTE_TAKER_IDENTITY_PREFIX,
|
|
56
58
|
NOTE_TAKER_METADATA_KEY: () => NOTE_TAKER_METADATA_KEY,
|
|
@@ -90,6 +92,7 @@ __export(src_exports, {
|
|
|
90
92
|
createCallCenter: () => createCallCenter,
|
|
91
93
|
createCallInviteHandler: () => createCallInviteHandler,
|
|
92
94
|
createDeviceManager: () => createDeviceManager,
|
|
95
|
+
createInRoomClient: () => createInRoomClient,
|
|
93
96
|
createLiveKitDriver: () => createLiveKitDriver,
|
|
94
97
|
createMeetAi: () => createMeetAi,
|
|
95
98
|
createMeetApi: () => createMeetApi,
|
|
@@ -102,6 +105,7 @@ __export(src_exports, {
|
|
|
102
105
|
createRoomEngine: () => createRoomEngine,
|
|
103
106
|
createTokenClient: () => createTokenClient,
|
|
104
107
|
createWebDeviceStorage: () => createWebDeviceStorage,
|
|
108
|
+
createWebEffectsStorage: () => createWebEffectsStorage,
|
|
105
109
|
createWebRoomTokenStorage: () => createWebRoomTokenStorage,
|
|
106
110
|
displayNameFor: () => displayNameFor,
|
|
107
111
|
fileDownloadPath: () => fileDownloadPath,
|
|
@@ -363,7 +367,16 @@ var MEET_ROUTES = {
|
|
|
363
367
|
*/
|
|
364
368
|
meetingAnnounce: "/api/v1/meet/meetings/{meeting_id}/announce",
|
|
365
369
|
/** Put a one-off meeting on the caller's own Google Calendar (gated by the server). */
|
|
366
|
-
meetingGoogleCalendar: "/api/v1/meet/meetings/{meeting_id}/google-calendar"
|
|
370
|
+
meetingGoogleCalendar: "/api/v1/meet/meetings/{meeting_id}/google-calendar",
|
|
371
|
+
/**
|
|
372
|
+
* Meet wave 2: EVERY in-meeting host power (mute, stop video, mute all, ask to
|
|
373
|
+
* unmute, spotlight, lower hands, co-host, hand over, rename, remove, report).
|
|
374
|
+
* Decided by the server from the meeting itself; a guest's pass rides
|
|
375
|
+
* `x-meet-room-token`.
|
|
376
|
+
*/
|
|
377
|
+
moderate: "/api/v1/meet/moderate",
|
|
378
|
+
/** Meet wave 2: the SAVED chat — `POST` sends to everyone, `GET ?meeting_id=` reads history. */
|
|
379
|
+
chat: "/api/v1/meet/chat"
|
|
367
380
|
};
|
|
368
381
|
function meetingPath(template, meetingId) {
|
|
369
382
|
return template.replace("{meeting_id}", encodeURIComponent(meetingId));
|
|
@@ -2081,7 +2094,19 @@ var ROOM_EVENTS = {
|
|
|
2081
2094
|
* production 2026-09-09 the guest lane showed no indicator at all while the
|
|
2082
2095
|
* host recorded (MRI-C8).
|
|
2083
2096
|
*/
|
|
2084
|
-
recording: "matrx.meet.recording"
|
|
2097
|
+
recording: "matrx.meet.recording",
|
|
2098
|
+
/**
|
|
2099
|
+
* A chat message to everyone was SAVED (Meet wave 2). Payload
|
|
2100
|
+
* `{ message: { id, meeting_id, sender_identity, sender_name, body, created_at } }`.
|
|
2101
|
+
*/
|
|
2102
|
+
chat: "matrx.meet.chat",
|
|
2103
|
+
/**
|
|
2104
|
+
* A host or co-host acted (Meet wave 2). Payload `{ meeting_id, kind, … }`:
|
|
2105
|
+
* `muted {source, by}` · `ask-unmute {by}` · `removed {by}` — addressed to the
|
|
2106
|
+
* person concerned; `lower-hands {identities: [...] | "*"}` · `role {identity,
|
|
2107
|
+
* role}` — to everyone.
|
|
2108
|
+
*/
|
|
2109
|
+
moderation: "matrx.meet.moderation"
|
|
2085
2110
|
};
|
|
2086
2111
|
function isRoomEventTopic(topic) {
|
|
2087
2112
|
return topic.startsWith(ROOM_EVENT_PREFIX);
|
|
@@ -2117,6 +2142,10 @@ function applyRoomEvent(args) {
|
|
|
2117
2142
|
return applyEnded(store, payload);
|
|
2118
2143
|
case ROOM_EVENTS.recording:
|
|
2119
2144
|
return applyRecording(store, payload);
|
|
2145
|
+
case ROOM_EVENTS.chat:
|
|
2146
|
+
return applyChat(store, payload);
|
|
2147
|
+
case ROOM_EVENTS.moderation:
|
|
2148
|
+
return applyModeration(store, payload, now);
|
|
2120
2149
|
default:
|
|
2121
2150
|
return ignored(`${topic} is not known to this version of @ai-matrx/meet`);
|
|
2122
2151
|
}
|
|
@@ -2208,6 +2237,101 @@ function applyEnded(store, payload) {
|
|
|
2208
2237
|
store.patch({ meeting: { ...meeting, endedAt } });
|
|
2209
2238
|
return HANDLED;
|
|
2210
2239
|
}
|
|
2240
|
+
function chatFromRow(row) {
|
|
2241
|
+
const id = text2(row["id"]);
|
|
2242
|
+
const identity = text2(row["sender_identity"]);
|
|
2243
|
+
const body = text2(row["body"]);
|
|
2244
|
+
if (id === null || identity === null || body === null) return null;
|
|
2245
|
+
return {
|
|
2246
|
+
id,
|
|
2247
|
+
identity: asParticipantIdentity(identity),
|
|
2248
|
+
displayName: text2(row["sender_name"]) ?? "Participant",
|
|
2249
|
+
text: body,
|
|
2250
|
+
at: text2(row["created_at"]) ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
2251
|
+
// `false` only from a server whose chat table has not landed: the room heard
|
|
2252
|
+
// it, nobody will see it later — and the line says so.
|
|
2253
|
+
saved: row["saved"] !== false
|
|
2254
|
+
};
|
|
2255
|
+
}
|
|
2256
|
+
function applyChat(store, payload) {
|
|
2257
|
+
const row = object(payload["message"]);
|
|
2258
|
+
if (row === null) return ignored("a chat event with no `message` row");
|
|
2259
|
+
const meeting = store.snapshot().meeting;
|
|
2260
|
+
if (meeting !== null && text2(row["meeting_id"]) !== null && row["meeting_id"] !== meeting.id) {
|
|
2261
|
+
return ignored("a chat message for a different meeting");
|
|
2262
|
+
}
|
|
2263
|
+
const message = chatFromRow(row);
|
|
2264
|
+
if (message === null) return ignored("a chat row with no id, sender or body");
|
|
2265
|
+
store.addChat(message);
|
|
2266
|
+
return HANDLED;
|
|
2267
|
+
}
|
|
2268
|
+
function moderationText(kind, payload) {
|
|
2269
|
+
const by = text2(payload["by"]) ?? "The host";
|
|
2270
|
+
switch (kind) {
|
|
2271
|
+
case "muted": {
|
|
2272
|
+
const source = text2(payload["source"]) ?? "microphone";
|
|
2273
|
+
return source === "microphone" ? `${by} muted you.` : source === "camera" ? `${by} turned off your camera.` : `${by} stopped your screen share.`;
|
|
2274
|
+
}
|
|
2275
|
+
case "ask-unmute":
|
|
2276
|
+
return `${by} is asking you to unmute.`;
|
|
2277
|
+
case "removed":
|
|
2278
|
+
return `${by} removed you from this meeting.`;
|
|
2279
|
+
default:
|
|
2280
|
+
return null;
|
|
2281
|
+
}
|
|
2282
|
+
}
|
|
2283
|
+
function applyModeration(store, payload, now) {
|
|
2284
|
+
const snapshot = store.snapshot();
|
|
2285
|
+
const meetingId = text2(payload["meeting_id"]);
|
|
2286
|
+
if (snapshot.meeting !== null && meetingId !== null && meetingId !== snapshot.meeting.id) {
|
|
2287
|
+
return ignored("a moderation event for a different meeting");
|
|
2288
|
+
}
|
|
2289
|
+
const kind = text2(payload["kind"]);
|
|
2290
|
+
switch (kind) {
|
|
2291
|
+
case "lower-hands": {
|
|
2292
|
+
const raw = payload["identities"];
|
|
2293
|
+
const everyone = raw === "*";
|
|
2294
|
+
const named = new Set(
|
|
2295
|
+
Array.isArray(raw) ? raw.filter((value) => typeof value === "string") : []
|
|
2296
|
+
);
|
|
2297
|
+
for (const participant of snapshot.participants) {
|
|
2298
|
+
if (participant.handRaised && (everyone || named.has(participant.identity))) {
|
|
2299
|
+
store.setHand(participant.identity, false);
|
|
2300
|
+
}
|
|
2301
|
+
}
|
|
2302
|
+
return HANDLED;
|
|
2303
|
+
}
|
|
2304
|
+
case "role": {
|
|
2305
|
+
const identity = text2(payload["identity"]);
|
|
2306
|
+
const role = text2(payload["role"]);
|
|
2307
|
+
if (identity === null || role === null || !ROLES.includes(role)) {
|
|
2308
|
+
return ignored("a role event with no identity or an unknown role");
|
|
2309
|
+
}
|
|
2310
|
+
if (identity === snapshot.localIdentity) {
|
|
2311
|
+
store.patch({
|
|
2312
|
+
grants: { ...snapshot.grants, role },
|
|
2313
|
+
moderationNotice: {
|
|
2314
|
+
kind: "role",
|
|
2315
|
+
text: role === "host" ? "You are now the host." : role === "cohost" ? "You are now a co-host." : "You are no longer a co-host.",
|
|
2316
|
+
at: now()
|
|
2317
|
+
}
|
|
2318
|
+
});
|
|
2319
|
+
}
|
|
2320
|
+
return HANDLED;
|
|
2321
|
+
}
|
|
2322
|
+
case "muted":
|
|
2323
|
+
case "ask-unmute":
|
|
2324
|
+
case "removed": {
|
|
2325
|
+
const sentence = moderationText(kind, payload);
|
|
2326
|
+
if (sentence === null) return ignored("a moderation event with no sentence");
|
|
2327
|
+
store.patch({ moderationNotice: { kind, text: sentence, at: now() } });
|
|
2328
|
+
return HANDLED;
|
|
2329
|
+
}
|
|
2330
|
+
default:
|
|
2331
|
+
return ignored(`a moderation kind this version does not know: ${String(kind)}`);
|
|
2332
|
+
}
|
|
2333
|
+
}
|
|
2334
|
+
var ROLES = ["host", "cohost", "participant", "guest"];
|
|
2211
2335
|
|
|
2212
2336
|
// src/core/devices.ts
|
|
2213
2337
|
var KINDS = ["audioinput", "videoinput", "audiooutput"];
|
|
@@ -2540,7 +2664,8 @@ function computeStage(args) {
|
|
|
2540
2664
|
const ordered = orderParticipants(args.participants, args.activeSpeakers);
|
|
2541
2665
|
const sharer = ordered.find((participant) => participant.screenSharing) ?? null;
|
|
2542
2666
|
const pinnedParticipant = args.pinned === null ? null : ordered.find((participant) => participant.identity === args.pinned) ?? null;
|
|
2543
|
-
const
|
|
2667
|
+
const spotlit = args.spotlight == null ? null : ordered.find((participant) => participant.identity === args.spotlight) ?? null;
|
|
2668
|
+
const feature = sharer !== null ? sharer : pinnedParticipant !== null && args.layout !== "grid" ? pinnedParticipant : spotlit !== null ? spotlit : args.layout === "grid" ? null : ordered[0] ?? null;
|
|
2544
2669
|
const rest = feature === null ? ordered : ordered.filter((participant) => participant.identity !== feature.identity);
|
|
2545
2670
|
const pageCount = Math.max(1, Math.ceil(rest.length / pageSize));
|
|
2546
2671
|
const page = Math.min(Math.max(0, args.page ?? 0), pageCount - 1);
|
|
@@ -2566,8 +2691,7 @@ function gridColumns(count2) {
|
|
|
2566
2691
|
var DATA_TOPICS = {
|
|
2567
2692
|
hand: "mx.hand",
|
|
2568
2693
|
reaction: "mx.reaction",
|
|
2569
|
-
chat: "mx.chat"
|
|
2570
|
-
hostAction: "mx.host"
|
|
2694
|
+
chat: "mx.chat"
|
|
2571
2695
|
};
|
|
2572
2696
|
var TRANSCRIPTION_TOPIC = "lk.transcription";
|
|
2573
2697
|
var TRANSCRIPTION_ATTRIBUTES = {
|
|
@@ -2628,11 +2752,48 @@ function snapshotParticipant(participant, isLocal) {
|
|
|
2628
2752
|
screenSharing: participant.isScreenShareEnabled
|
|
2629
2753
|
};
|
|
2630
2754
|
}
|
|
2755
|
+
var DEFAULT_AUDIO_PROCESSING = {
|
|
2756
|
+
noiseSuppression: true,
|
|
2757
|
+
echoCancellation: true,
|
|
2758
|
+
autoGainControl: true
|
|
2759
|
+
};
|
|
2760
|
+
function videoEffectsSupported() {
|
|
2761
|
+
const scope = globalThis;
|
|
2762
|
+
if (typeof scope.OffscreenCanvas === "undefined" || scope.document === void 0) return false;
|
|
2763
|
+
try {
|
|
2764
|
+
return scope.document.createElement("canvas").getContext("webgl2") !== null;
|
|
2765
|
+
} catch {
|
|
2766
|
+
return false;
|
|
2767
|
+
}
|
|
2768
|
+
}
|
|
2769
|
+
async function applyVideoEffect(target, effect) {
|
|
2770
|
+
const { Track } = await import("livekit-client");
|
|
2771
|
+
const track = target.localParticipant.getTrackPublication(Track.Source.Camera)?.track;
|
|
2772
|
+
if (track === void 0) return;
|
|
2773
|
+
try {
|
|
2774
|
+
if (effect.kind === "none") {
|
|
2775
|
+
await track.stopProcessor();
|
|
2776
|
+
return;
|
|
2777
|
+
}
|
|
2778
|
+
const processors = await import("@livekit/track-processors");
|
|
2779
|
+
const processor = effect.kind === "blur" ? processors.BackgroundBlur(effect.radius ?? 12) : processors.VirtualBackground(effect.imageUrl);
|
|
2780
|
+
await track.setProcessor(processor);
|
|
2781
|
+
} catch (error) {
|
|
2782
|
+
throw new MeetError(
|
|
2783
|
+
"media-unsupported",
|
|
2784
|
+
"The background effect could not start on this camera",
|
|
2785
|
+
"Background effects need a browser with WebGL2 (current Chrome, Edge, Firefox or Safari). Your video continues without the effect.",
|
|
2786
|
+
error
|
|
2787
|
+
);
|
|
2788
|
+
}
|
|
2789
|
+
}
|
|
2631
2790
|
var encoder = new TextEncoder();
|
|
2632
2791
|
var decoder = new TextDecoder();
|
|
2633
2792
|
function createLiveKitDriver(handlers, options = {}) {
|
|
2634
2793
|
let room = null;
|
|
2635
2794
|
let projectRoster = null;
|
|
2795
|
+
let audioCapture = DEFAULT_AUDIO_PROCESSING;
|
|
2796
|
+
let videoEffect = { kind: "none" };
|
|
2636
2797
|
async function load() {
|
|
2637
2798
|
if (room !== null) return room;
|
|
2638
2799
|
let module2;
|
|
@@ -2661,7 +2822,9 @@ function createLiveKitDriver(handlers, options = {}) {
|
|
|
2661
2822
|
handlers.participants([local, ...remotes]);
|
|
2662
2823
|
};
|
|
2663
2824
|
projectRoster = roster;
|
|
2664
|
-
created.on(RoomEvent.ParticipantConnected, roster).on(RoomEvent.ParticipantDisconnected, roster).on(RoomEvent.TrackSubscribed, roster).on(RoomEvent.TrackUnsubscribed, roster).on(RoomEvent.TrackMuted, roster).on(RoomEvent.TrackUnmuted, roster).on(RoomEvent.LocalTrackPublished, roster).on(RoomEvent.LocalTrackUnpublished, roster).on(RoomEvent.
|
|
2825
|
+
created.on(RoomEvent.ParticipantConnected, roster).on(RoomEvent.ParticipantDisconnected, roster).on(RoomEvent.TrackSubscribed, roster).on(RoomEvent.TrackUnsubscribed, roster).on(RoomEvent.TrackMuted, roster).on(RoomEvent.TrackUnmuted, roster).on(RoomEvent.LocalTrackPublished, roster).on(RoomEvent.LocalTrackUnpublished, roster).on(RoomEvent.ParticipantNameChanged, roster).on(RoomEvent.ParticipantAttributesChanged, roster).on(RoomEvent.ParticipantMetadataChanged, roster).on(RoomEvent.RoomMetadataChanged, (metadata) => {
|
|
2826
|
+
handlers.roomMetadata?.(metadata ?? "");
|
|
2827
|
+
}).on(RoomEvent.ActiveSpeakersChanged, (speakers) => {
|
|
2665
2828
|
handlers.activeSpeakers(speakers.map((speaker) => speaker.identity));
|
|
2666
2829
|
}).on(RoomEvent.ConnectionQualityChanged, (quality, participant) => {
|
|
2667
2830
|
handlers.quality(participant.identity, mapQuality(quality));
|
|
@@ -2738,6 +2901,7 @@ function createLiveKitDriver(handlers, options = {}) {
|
|
|
2738
2901
|
throw normalizeDeviceError(error, "meet.connect");
|
|
2739
2902
|
}
|
|
2740
2903
|
projectRoster?.();
|
|
2904
|
+
handlers.roomMetadata?.(target.metadata ?? "");
|
|
2741
2905
|
},
|
|
2742
2906
|
async disconnect() {
|
|
2743
2907
|
if (room === null) return;
|
|
@@ -2752,14 +2916,19 @@ function createLiveKitDriver(handlers, options = {}) {
|
|
|
2752
2916
|
},
|
|
2753
2917
|
async setMicrophoneEnabled(enabled) {
|
|
2754
2918
|
try {
|
|
2755
|
-
await requireRoom("setMicrophoneEnabled").localParticipant.setMicrophoneEnabled(
|
|
2919
|
+
await requireRoom("setMicrophoneEnabled").localParticipant.setMicrophoneEnabled(
|
|
2920
|
+
enabled,
|
|
2921
|
+
audioCapture
|
|
2922
|
+
);
|
|
2756
2923
|
} catch (error) {
|
|
2757
2924
|
throw normalizeDeviceError(error, "meet.setMicrophoneEnabled");
|
|
2758
2925
|
}
|
|
2759
2926
|
},
|
|
2760
2927
|
async setCameraEnabled(enabled) {
|
|
2761
2928
|
try {
|
|
2762
|
-
|
|
2929
|
+
const target = requireRoom("setCameraEnabled");
|
|
2930
|
+
await target.localParticipant.setCameraEnabled(enabled);
|
|
2931
|
+
if (enabled && videoEffect.kind !== "none") await applyVideoEffect(target, videoEffect);
|
|
2763
2932
|
} catch (error) {
|
|
2764
2933
|
throw normalizeDeviceError(error, "meet.setCameraEnabled");
|
|
2765
2934
|
}
|
|
@@ -2771,14 +2940,40 @@ function createLiveKitDriver(handlers, options = {}) {
|
|
|
2771
2940
|
throw normalizeDeviceError(error, "meet.setScreenShareEnabled");
|
|
2772
2941
|
}
|
|
2773
2942
|
},
|
|
2774
|
-
async publishData(topic, payload) {
|
|
2943
|
+
async publishData(topic, payload, to) {
|
|
2775
2944
|
await requireRoom("publishData").localParticipant.publishData(
|
|
2776
2945
|
// `reliable` is not optional for a control plane: a dropped
|
|
2777
2946
|
// "hand lowered" leaves a hand up on every other screen forever.
|
|
2778
2947
|
encoder.encode(payload),
|
|
2779
|
-
{ reliable: true, topic }
|
|
2948
|
+
to === void 0 || to.length === 0 ? { reliable: true, topic } : { reliable: true, topic, destinationIdentities: [...to] }
|
|
2780
2949
|
);
|
|
2781
2950
|
},
|
|
2951
|
+
async setAudioProcessing(processing) {
|
|
2952
|
+
audioCapture = { ...audioCapture, ...processing };
|
|
2953
|
+
const target = room;
|
|
2954
|
+
if (target === null) return;
|
|
2955
|
+
const { Track } = await import("livekit-client");
|
|
2956
|
+
const publication = target.localParticipant.getTrackPublication(Track.Source.Microphone);
|
|
2957
|
+
const track = publication?.track;
|
|
2958
|
+
if (track === void 0 || publication?.isMuted === true) return;
|
|
2959
|
+
try {
|
|
2960
|
+
await track.restartTrack({
|
|
2961
|
+
...audioCapture,
|
|
2962
|
+
...target.getActiveDevice("audioinput") !== void 0 ? { deviceId: target.getActiveDevice("audioinput") } : {}
|
|
2963
|
+
});
|
|
2964
|
+
} catch (error) {
|
|
2965
|
+
throw normalizeDeviceError(error, "meet.setAudioProcessing");
|
|
2966
|
+
}
|
|
2967
|
+
},
|
|
2968
|
+
async setVideoEffect(effect) {
|
|
2969
|
+
videoEffect = effect;
|
|
2970
|
+
const target = room;
|
|
2971
|
+
if (target === null) return;
|
|
2972
|
+
await applyVideoEffect(target, effect);
|
|
2973
|
+
},
|
|
2974
|
+
supportsVideoEffects() {
|
|
2975
|
+
return videoEffectsSupported();
|
|
2976
|
+
},
|
|
2782
2977
|
attach(identity, kind, element) {
|
|
2783
2978
|
const target = requireRoom("attach");
|
|
2784
2979
|
const source = sources?.[kind];
|
|
@@ -3001,6 +3196,11 @@ function listenOnlyNotice(media) {
|
|
|
3001
3196
|
}
|
|
3002
3197
|
|
|
3003
3198
|
// src/core/store.ts
|
|
3199
|
+
var DEFAULT_EFFECTS = {
|
|
3200
|
+
noiseSuppression: true,
|
|
3201
|
+
background: "none",
|
|
3202
|
+
backgroundImage: null
|
|
3203
|
+
};
|
|
3004
3204
|
var NO_GRANTS = {
|
|
3005
3205
|
role: "participant",
|
|
3006
3206
|
canRecord: false,
|
|
@@ -3023,7 +3223,13 @@ var EMPTY = {
|
|
|
3023
3223
|
recordingSource: "local",
|
|
3024
3224
|
recordingId: null,
|
|
3025
3225
|
chat: [],
|
|
3226
|
+
chatLoaded: false,
|
|
3227
|
+
chatHistoryError: null,
|
|
3026
3228
|
reactions: [],
|
|
3229
|
+
spotlight: null,
|
|
3230
|
+
moderationNotice: null,
|
|
3231
|
+
actionNotice: null,
|
|
3232
|
+
effects: DEFAULT_EFFECTS,
|
|
3027
3233
|
transcript: [],
|
|
3028
3234
|
notes: [],
|
|
3029
3235
|
names: {},
|
|
@@ -3062,6 +3268,7 @@ function createMeetStore(initial = {}) {
|
|
|
3062
3268
|
setParticipants(participants) {
|
|
3063
3269
|
const names = learnNames(state.names, participants);
|
|
3064
3270
|
const hands = new Map(state.participants.map((p) => [p.identity, p.handRaised]));
|
|
3271
|
+
const handTimes = new Map(state.participants.map((p) => [p.identity, p.handRaisedAt ?? null]));
|
|
3065
3272
|
const quality = new Map(state.participants.map((p) => [p.identity, p.quality]));
|
|
3066
3273
|
commit({
|
|
3067
3274
|
...state,
|
|
@@ -3075,6 +3282,7 @@ function createMeetStore(initial = {}) {
|
|
|
3075
3282
|
// name book. `roster-names.test.ts` fails if a token ever gets past it.
|
|
3076
3283
|
displayName: displayNameFor(names, participant.identity, participant.displayName),
|
|
3077
3284
|
handRaised: hands.get(participant.identity) ?? participant.handRaised,
|
|
3285
|
+
handRaisedAt: handTimes.get(participant.identity) ?? participant.handRaisedAt ?? null,
|
|
3078
3286
|
quality: participant.quality === "unknown" ? quality.get(participant.identity) ?? "unknown" : participant.quality
|
|
3079
3287
|
}))
|
|
3080
3288
|
});
|
|
@@ -3097,11 +3305,17 @@ function createMeetStore(initial = {}) {
|
|
|
3097
3305
|
)
|
|
3098
3306
|
});
|
|
3099
3307
|
},
|
|
3100
|
-
setHand(identity, raised) {
|
|
3308
|
+
setHand(identity, raised, at) {
|
|
3101
3309
|
commit({
|
|
3102
3310
|
...state,
|
|
3103
3311
|
participants: state.participants.map(
|
|
3104
|
-
(participant) => participant.identity === identity ? {
|
|
3312
|
+
(participant) => participant.identity === identity ? {
|
|
3313
|
+
...participant,
|
|
3314
|
+
handRaised: raised,
|
|
3315
|
+
// A hand that is ALREADY up keeps its place in the queue when
|
|
3316
|
+
// it is re-announced (to a late joiner): the first time wins.
|
|
3317
|
+
handRaisedAt: raised ? participant.handRaised && participant.handRaisedAt != null ? participant.handRaisedAt : at ?? Date.now() : null
|
|
3318
|
+
} : participant
|
|
3105
3319
|
)
|
|
3106
3320
|
});
|
|
3107
3321
|
},
|
|
@@ -3111,6 +3325,31 @@ function createMeetStore(initial = {}) {
|
|
|
3111
3325
|
const names = learnName(state.names, message.identity, message.displayName);
|
|
3112
3326
|
commit({ ...state, chat: chat.slice(-CHAT_LIMIT), names });
|
|
3113
3327
|
},
|
|
3328
|
+
loadChat(messages) {
|
|
3329
|
+
const known = new Set(state.chat.map((message) => message.id));
|
|
3330
|
+
const merged = [...messages.filter((message) => !known.has(message.id)), ...state.chat].sort(
|
|
3331
|
+
(a, b) => a.at < b.at ? -1 : a.at > b.at ? 1 : 0
|
|
3332
|
+
);
|
|
3333
|
+
const names = learnNames(
|
|
3334
|
+
state.names,
|
|
3335
|
+
messages.map((message) => ({ identity: message.identity, displayName: message.displayName }))
|
|
3336
|
+
);
|
|
3337
|
+
commit({ ...state, chat: merged.slice(-CHAT_LIMIT), chatLoaded: true, chatHistoryError: null, names });
|
|
3338
|
+
},
|
|
3339
|
+
settleChat(localId, next) {
|
|
3340
|
+
const index = state.chat.findIndex((message) => message.id === localId);
|
|
3341
|
+
if (index === -1) return;
|
|
3342
|
+
const current = state.chat[index];
|
|
3343
|
+
let replacement;
|
|
3344
|
+
if ("failed" in next && typeof next.failed === "string" && !("id" in next)) {
|
|
3345
|
+
replacement = { ...current, failed: next.failed };
|
|
3346
|
+
} else {
|
|
3347
|
+
replacement = next;
|
|
3348
|
+
}
|
|
3349
|
+
const duplicate = replacement.id !== localId && state.chat.some((message) => message.id === replacement.id);
|
|
3350
|
+
const chat = duplicate ? state.chat.filter((message) => message.id !== localId) : state.chat.map((message, position) => position === index ? replacement : message);
|
|
3351
|
+
commit({ ...state, chat });
|
|
3352
|
+
},
|
|
3114
3353
|
addReaction(reaction) {
|
|
3115
3354
|
commit({ ...state, reactions: [...state.reactions, reaction] });
|
|
3116
3355
|
},
|
|
@@ -3201,7 +3440,7 @@ function createMeetStore(initial = {}) {
|
|
|
3201
3440
|
commit({ ...state, media: { ...state.media, [kind]: next } });
|
|
3202
3441
|
},
|
|
3203
3442
|
reset() {
|
|
3204
|
-
commit({ ...EMPTY });
|
|
3443
|
+
commit({ ...EMPTY, effects: state.effects });
|
|
3205
3444
|
}
|
|
3206
3445
|
};
|
|
3207
3446
|
}
|
|
@@ -3833,7 +4072,37 @@ function createRoomEngine(options) {
|
|
|
3833
4072
|
const handlers = {
|
|
3834
4073
|
participants(list) {
|
|
3835
4074
|
const joinedAt = new Date(now()).toISOString();
|
|
4075
|
+
const before = new Set(store.snapshot().participants.map((p) => p.identity));
|
|
3836
4076
|
store.setParticipants(list.map((entry) => projectParticipant(entry, joinedAt)));
|
|
4077
|
+
const snapshot = store.snapshot();
|
|
4078
|
+
const newcomer = snapshot.participants.some(
|
|
4079
|
+
(participant) => !participant.isLocal && !before.has(participant.identity)
|
|
4080
|
+
);
|
|
4081
|
+
const me = snapshot.participants.find((p) => p.identity === snapshot.localIdentity);
|
|
4082
|
+
if (newcomer && before.size > 0 && me?.handRaised === true) {
|
|
4083
|
+
void publish(DATA_TOPICS.hand, { raised: true, raisedAt: me.handRaisedAt ?? now() }).catch(
|
|
4084
|
+
() => void 0
|
|
4085
|
+
);
|
|
4086
|
+
}
|
|
4087
|
+
},
|
|
4088
|
+
roomMetadata(metadata) {
|
|
4089
|
+
let spotlight = null;
|
|
4090
|
+
if (metadata.length > 0) {
|
|
4091
|
+
try {
|
|
4092
|
+
const parsed = JSON.parse(metadata);
|
|
4093
|
+
if (typeof parsed === "object" && parsed !== null) {
|
|
4094
|
+
const value = parsed.spotlight;
|
|
4095
|
+
if (typeof value === "string" && value.length > 0) spotlight = asParticipantIdentity(value);
|
|
4096
|
+
}
|
|
4097
|
+
} catch {
|
|
4098
|
+
report(
|
|
4099
|
+
"warn",
|
|
4100
|
+
"meet: the room's metadata is not JSON, so no spotlight was read from it",
|
|
4101
|
+
"Only aidream writes room metadata; check services/meet/moderation.py."
|
|
4102
|
+
);
|
|
4103
|
+
}
|
|
4104
|
+
}
|
|
4105
|
+
if (store.snapshot().spotlight !== spotlight) store.patch({ spotlight });
|
|
3837
4106
|
},
|
|
3838
4107
|
activeSpeakers(identities) {
|
|
3839
4108
|
store.setSpeaking(identities.map(asParticipantIdentity));
|
|
@@ -3937,7 +4206,11 @@ function createRoomEngine(options) {
|
|
|
3937
4206
|
const sender = asParticipantIdentity(from ?? "");
|
|
3938
4207
|
switch (topic) {
|
|
3939
4208
|
case DATA_TOPICS.hand:
|
|
3940
|
-
store.setHand(
|
|
4209
|
+
store.setHand(
|
|
4210
|
+
sender,
|
|
4211
|
+
envelope.raised === true,
|
|
4212
|
+
typeof envelope.raisedAt === "number" ? envelope.raisedAt : now()
|
|
4213
|
+
);
|
|
3941
4214
|
return;
|
|
3942
4215
|
case DATA_TOPICS.reaction:
|
|
3943
4216
|
if (typeof envelope.emoji === "string") {
|
|
@@ -3951,12 +4224,14 @@ function createRoomEngine(options) {
|
|
|
3951
4224
|
return;
|
|
3952
4225
|
case DATA_TOPICS.chat:
|
|
3953
4226
|
if (typeof envelope.text === "string") {
|
|
4227
|
+
const local = store.snapshot().localIdentity;
|
|
3954
4228
|
store.addChat({
|
|
3955
4229
|
id: envelope.id ?? `${sender}-${now()}`,
|
|
3956
4230
|
identity: sender,
|
|
3957
4231
|
displayName: envelope.name ?? sender,
|
|
3958
4232
|
text: envelope.text,
|
|
3959
|
-
at: envelope.at ?? new Date(now()).toISOString()
|
|
4233
|
+
at: envelope.at ?? new Date(now()).toISOString(),
|
|
4234
|
+
...typeof envelope.to === "string" && local !== null ? { to: local } : {}
|
|
3960
4235
|
});
|
|
3961
4236
|
}
|
|
3962
4237
|
return;
|
|
@@ -4002,6 +4277,24 @@ function createRoomEngine(options) {
|
|
|
4002
4277
|
store.patch({ phase: "connected", failure: null });
|
|
4003
4278
|
store.setLocalTrack("microphone", IDLE_TRACK);
|
|
4004
4279
|
store.setLocalTrack("camera", IDLE_TRACK);
|
|
4280
|
+
const effects = options.effectsStorage?.load() ?? store.snapshot().effects;
|
|
4281
|
+
store.patch({ effects });
|
|
4282
|
+
if (!effects.noiseSuppression || effects.background !== "none") {
|
|
4283
|
+
try {
|
|
4284
|
+
await applyEffects(created, effects);
|
|
4285
|
+
} catch {
|
|
4286
|
+
}
|
|
4287
|
+
}
|
|
4288
|
+
for (const kind of ["audioinput", "videoinput", "audiooutput"]) {
|
|
4289
|
+
const chosen = options.resolveDevice?.(kind) ?? null;
|
|
4290
|
+
if (chosen === null || chosen === "default") continue;
|
|
4291
|
+
try {
|
|
4292
|
+
await created.switchDevice(kind, chosen);
|
|
4293
|
+
} catch (error) {
|
|
4294
|
+
const normalized = normalizeDeviceError(error, `meet.switchDevice(${kind})`);
|
|
4295
|
+
report("warn", normalized.message, normalized.remedy);
|
|
4296
|
+
}
|
|
4297
|
+
}
|
|
4005
4298
|
if (request.mode === "video") {
|
|
4006
4299
|
await enableTrack(created, "camera", true);
|
|
4007
4300
|
}
|
|
@@ -4024,9 +4317,30 @@ function createRoomEngine(options) {
|
|
|
4024
4317
|
return state;
|
|
4025
4318
|
}
|
|
4026
4319
|
}
|
|
4027
|
-
async function publish(topic, body) {
|
|
4320
|
+
async function publish(topic, body, to) {
|
|
4028
4321
|
if (driver === null) return;
|
|
4029
|
-
await driver.publishData(topic, JSON.stringify(body));
|
|
4322
|
+
await driver.publishData(topic, JSON.stringify(body), to);
|
|
4323
|
+
}
|
|
4324
|
+
async function applyEffects(target, effects) {
|
|
4325
|
+
try {
|
|
4326
|
+
await target.setAudioProcessing?.({
|
|
4327
|
+
noiseSuppression: effects.noiseSuppression,
|
|
4328
|
+
echoCancellation: true,
|
|
4329
|
+
autoGainControl: true
|
|
4330
|
+
});
|
|
4331
|
+
} catch (error) {
|
|
4332
|
+
report("warn", `meet: noise suppression could not be applied: ${String(error)}`, "The microphone keeps working without it.");
|
|
4333
|
+
}
|
|
4334
|
+
if (target.setVideoEffect === void 0) return;
|
|
4335
|
+
const effect = effects.background === "blur" ? { kind: "blur" } : effects.background === "image" && effects.backgroundImage !== null ? { kind: "image", imageUrl: effects.backgroundImage } : { kind: "none" };
|
|
4336
|
+
if (effect.kind !== "none" && target.supportsVideoEffects?.() === false) return;
|
|
4337
|
+
try {
|
|
4338
|
+
await target.setVideoEffect(effect);
|
|
4339
|
+
} catch (error) {
|
|
4340
|
+
const normalized = normalizeMeetError(error, "meet.setVideoEffect");
|
|
4341
|
+
report("warn", normalized.message, normalized.remedy);
|
|
4342
|
+
throw normalized;
|
|
4343
|
+
}
|
|
4030
4344
|
}
|
|
4031
4345
|
return {
|
|
4032
4346
|
join(request) {
|
|
@@ -4082,7 +4396,7 @@ function createRoomEngine(options) {
|
|
|
4082
4396
|
async raiseHand(raised) {
|
|
4083
4397
|
const local = store.snapshot().localIdentity;
|
|
4084
4398
|
if (local !== null) store.setHand(local, raised);
|
|
4085
|
-
await publish(DATA_TOPICS.hand, { raised });
|
|
4399
|
+
await publish(DATA_TOPICS.hand, { raised, raisedAt: now() });
|
|
4086
4400
|
},
|
|
4087
4401
|
async react(emoji) {
|
|
4088
4402
|
const local = store.snapshot().localIdentity;
|
|
@@ -4092,32 +4406,41 @@ function createRoomEngine(options) {
|
|
|
4092
4406
|
}
|
|
4093
4407
|
await publish(DATA_TOPICS.reaction, { id, emoji });
|
|
4094
4408
|
},
|
|
4095
|
-
async
|
|
4409
|
+
async sendPrivateChat(to, text3) {
|
|
4096
4410
|
const trimmed = text3.trim();
|
|
4097
4411
|
if (trimmed.length === 0) return;
|
|
4098
4412
|
const local = store.snapshot().localIdentity;
|
|
4099
4413
|
const id = `${local ?? "local"}-${now()}`;
|
|
4100
4414
|
const at = new Date(now()).toISOString();
|
|
4101
4415
|
if (local !== null) {
|
|
4102
|
-
store.addChat({
|
|
4103
|
-
id,
|
|
4104
|
-
identity: local,
|
|
4105
|
-
displayName: identity.displayName,
|
|
4106
|
-
text: trimmed,
|
|
4107
|
-
at
|
|
4108
|
-
});
|
|
4416
|
+
store.addChat({ id, identity: local, displayName: identity.displayName, text: trimmed, at, to });
|
|
4109
4417
|
}
|
|
4110
|
-
await publish(
|
|
4418
|
+
await publish(
|
|
4419
|
+
DATA_TOPICS.chat,
|
|
4420
|
+
{ id, text: trimmed, at, name: identity.displayName, to },
|
|
4421
|
+
[to]
|
|
4422
|
+
);
|
|
4111
4423
|
},
|
|
4112
|
-
async
|
|
4113
|
-
|
|
4114
|
-
|
|
4115
|
-
|
|
4116
|
-
|
|
4117
|
-
|
|
4118
|
-
|
|
4424
|
+
async setEffects(next) {
|
|
4425
|
+
const previous = store.snapshot().effects;
|
|
4426
|
+
const merged = { ...previous, ...next };
|
|
4427
|
+
store.patch({ effects: merged });
|
|
4428
|
+
options.effectsStorage?.save(merged);
|
|
4429
|
+
if (driver === null) return;
|
|
4430
|
+
try {
|
|
4431
|
+
await applyEffects(driver, merged);
|
|
4432
|
+
} catch {
|
|
4433
|
+
const reverted = { ...merged, background: previous.background, backgroundImage: previous.backgroundImage };
|
|
4434
|
+
store.patch({ effects: reverted });
|
|
4435
|
+
options.effectsStorage?.save(reverted);
|
|
4119
4436
|
}
|
|
4120
|
-
|
|
4437
|
+
},
|
|
4438
|
+
supportsVideoEffects() {
|
|
4439
|
+
return driver?.supportsVideoEffects?.() ?? false;
|
|
4440
|
+
},
|
|
4441
|
+
async switchDevice(kind, deviceId) {
|
|
4442
|
+
if (driver === null) return;
|
|
4443
|
+
await driver.switchDevice(kind, deviceId);
|
|
4121
4444
|
},
|
|
4122
4445
|
pin(who) {
|
|
4123
4446
|
store.patch({ pinned: who, layout: who === null ? "grid" : "speaker" });
|
|
@@ -4148,6 +4471,192 @@ function hostUserIds(participants) {
|
|
|
4148
4471
|
return participants.filter((participant) => participant.role === "host" && participant.userId !== null).map((participant) => participant.userId);
|
|
4149
4472
|
}
|
|
4150
4473
|
|
|
4474
|
+
// src/core/effects.ts
|
|
4475
|
+
function coerce(value) {
|
|
4476
|
+
if (typeof value !== "object" || value === null) return DEFAULT_EFFECTS;
|
|
4477
|
+
const row = value;
|
|
4478
|
+
const background = row["background"] === "blur" || row["background"] === "image" ? row["background"] : "none";
|
|
4479
|
+
const backgroundImage = typeof row["backgroundImage"] === "string" ? row["backgroundImage"] : null;
|
|
4480
|
+
return {
|
|
4481
|
+
noiseSuppression: row["noiseSuppression"] !== false,
|
|
4482
|
+
background: background === "image" && backgroundImage === null ? "none" : background,
|
|
4483
|
+
backgroundImage
|
|
4484
|
+
};
|
|
4485
|
+
}
|
|
4486
|
+
function createWebEffectsStorage(key = "mx.meet.effects") {
|
|
4487
|
+
let memory = DEFAULT_EFFECTS;
|
|
4488
|
+
const storage = globalThis.localStorage;
|
|
4489
|
+
return {
|
|
4490
|
+
load() {
|
|
4491
|
+
if (storage === void 0) return memory;
|
|
4492
|
+
try {
|
|
4493
|
+
const raw = storage.getItem(key);
|
|
4494
|
+
return raw === null ? memory : coerce(JSON.parse(raw));
|
|
4495
|
+
} catch {
|
|
4496
|
+
return memory;
|
|
4497
|
+
}
|
|
4498
|
+
},
|
|
4499
|
+
save(effects) {
|
|
4500
|
+
memory = effects;
|
|
4501
|
+
try {
|
|
4502
|
+
storage?.setItem(key, JSON.stringify(effects));
|
|
4503
|
+
} catch {
|
|
4504
|
+
}
|
|
4505
|
+
}
|
|
4506
|
+
};
|
|
4507
|
+
}
|
|
4508
|
+
|
|
4509
|
+
// src/core/in-room.ts
|
|
4510
|
+
var MODERATION_ACTIONS = [
|
|
4511
|
+
"mute",
|
|
4512
|
+
"stop_video",
|
|
4513
|
+
"stop_screen",
|
|
4514
|
+
"mute_all",
|
|
4515
|
+
"ask_unmute",
|
|
4516
|
+
"ask_all_unmute",
|
|
4517
|
+
"spotlight",
|
|
4518
|
+
"lower_hand",
|
|
4519
|
+
"lower_all_hands",
|
|
4520
|
+
"make_cohost",
|
|
4521
|
+
"revoke_cohost",
|
|
4522
|
+
"transfer_host",
|
|
4523
|
+
"rename",
|
|
4524
|
+
"remove",
|
|
4525
|
+
"report"
|
|
4526
|
+
];
|
|
4527
|
+
function createInRoomClient(options) {
|
|
4528
|
+
const { api, store } = options;
|
|
4529
|
+
const now = options.now ?? (() => Date.now());
|
|
4530
|
+
function meetingId(operation) {
|
|
4531
|
+
const meeting = store.snapshot().meeting;
|
|
4532
|
+
if (meeting === null) {
|
|
4533
|
+
throw new MeetError(
|
|
4534
|
+
"not-found",
|
|
4535
|
+
`${operation}: this room has no meeting record`,
|
|
4536
|
+
"Moderation and saved chat belong to a meeting. An ad-hoc call has neither."
|
|
4537
|
+
);
|
|
4538
|
+
}
|
|
4539
|
+
return meeting.id;
|
|
4540
|
+
}
|
|
4541
|
+
function headers() {
|
|
4542
|
+
const token = options.resolveRoomToken();
|
|
4543
|
+
return token === null ? void 0 : { [ROOM_TOKEN_HEADER]: token };
|
|
4544
|
+
}
|
|
4545
|
+
async function refusal2(response, operation, remedy) {
|
|
4546
|
+
let body = null;
|
|
4547
|
+
try {
|
|
4548
|
+
body = await response.json();
|
|
4549
|
+
} catch {
|
|
4550
|
+
body = null;
|
|
4551
|
+
}
|
|
4552
|
+
const said = readServerRefusal(body);
|
|
4553
|
+
return new MeetError(
|
|
4554
|
+
response.status === 403 || response.status === 401 ? "forbidden" : response.status === 404 ? "not-found" : response.status === 409 ? "conflict" : "transport",
|
|
4555
|
+
said?.reason ?? `${operation}: the server answered ${response.status}`,
|
|
4556
|
+
said?.remedy !== void 0 && said.remedy.length > 0 ? said.remedy : remedy
|
|
4557
|
+
);
|
|
4558
|
+
}
|
|
4559
|
+
return {
|
|
4560
|
+
async moderate(request) {
|
|
4561
|
+
const operation = `meet.moderate(${request.action})`;
|
|
4562
|
+
const hdrs = headers();
|
|
4563
|
+
const response = await api.request({
|
|
4564
|
+
path: MEET_ROUTES.moderate,
|
|
4565
|
+
method: "POST",
|
|
4566
|
+
operation,
|
|
4567
|
+
body: {
|
|
4568
|
+
meeting_id: meetingId(operation),
|
|
4569
|
+
action: request.action,
|
|
4570
|
+
...request.identity != null ? { identity: request.identity } : {},
|
|
4571
|
+
...request.name !== void 0 ? { name: request.name } : {},
|
|
4572
|
+
...request.reason !== void 0 ? { reason: request.reason } : {}
|
|
4573
|
+
},
|
|
4574
|
+
...hdrs !== void 0 ? { headers: hdrs } : {}
|
|
4575
|
+
});
|
|
4576
|
+
if (!response.ok) {
|
|
4577
|
+
throw await refusal2(
|
|
4578
|
+
response,
|
|
4579
|
+
operation,
|
|
4580
|
+
"Nothing was changed. The server decides host powers from the meeting itself."
|
|
4581
|
+
);
|
|
4582
|
+
}
|
|
4583
|
+
const payload = await response.json();
|
|
4584
|
+
return {
|
|
4585
|
+
action: request.action,
|
|
4586
|
+
affected: Array.isArray(payload["affected"]) ? payload["affected"].filter((value) => typeof value === "string") : [],
|
|
4587
|
+
message: typeof payload["message"] === "string" ? payload["message"] : "Done."
|
|
4588
|
+
};
|
|
4589
|
+
},
|
|
4590
|
+
async sendChat(text3) {
|
|
4591
|
+
const body = text3.trim();
|
|
4592
|
+
if (body.length === 0) return;
|
|
4593
|
+
const operation = "meet.sendChat";
|
|
4594
|
+
const id = meetingId(operation);
|
|
4595
|
+
const snapshot = store.snapshot();
|
|
4596
|
+
const me = snapshot.participants.find((p) => p.identity === snapshot.localIdentity);
|
|
4597
|
+
const localId = `pending-${now()}`;
|
|
4598
|
+
const pending = {
|
|
4599
|
+
id: localId,
|
|
4600
|
+
identity: snapshot.localIdentity ?? "local",
|
|
4601
|
+
displayName: me?.displayName ?? "You",
|
|
4602
|
+
text: body,
|
|
4603
|
+
at: new Date(now()).toISOString(),
|
|
4604
|
+
saved: false
|
|
4605
|
+
};
|
|
4606
|
+
store.addChat(pending);
|
|
4607
|
+
const hdrs = headers();
|
|
4608
|
+
let response;
|
|
4609
|
+
try {
|
|
4610
|
+
response = await api.request({
|
|
4611
|
+
path: MEET_ROUTES.chat,
|
|
4612
|
+
method: "POST",
|
|
4613
|
+
operation,
|
|
4614
|
+
body: { meeting_id: id, body },
|
|
4615
|
+
...hdrs !== void 0 ? { headers: hdrs } : {}
|
|
4616
|
+
});
|
|
4617
|
+
} catch (error) {
|
|
4618
|
+
store.settleChat(localId, { failed: "Not sent \u2014 the server could not be reached." });
|
|
4619
|
+
throw error;
|
|
4620
|
+
}
|
|
4621
|
+
if (!response.ok) {
|
|
4622
|
+
const error = await refusal2(response, operation, "Your message was not sent.");
|
|
4623
|
+
store.settleChat(localId, { failed: error.message });
|
|
4624
|
+
throw error;
|
|
4625
|
+
}
|
|
4626
|
+
const saved = chatFromRow(await response.json());
|
|
4627
|
+
if (saved === null) {
|
|
4628
|
+
store.settleChat(localId, { failed: "The server's answer could not be read." });
|
|
4629
|
+
return;
|
|
4630
|
+
}
|
|
4631
|
+
store.settleChat(localId, saved);
|
|
4632
|
+
},
|
|
4633
|
+
async loadChat() {
|
|
4634
|
+
const operation = "meet.loadChat";
|
|
4635
|
+
const id = meetingId(operation);
|
|
4636
|
+
const hdrs = headers();
|
|
4637
|
+
const response = await api.request({
|
|
4638
|
+
path: `${MEET_ROUTES.chat}?meeting_id=${encodeURIComponent(id)}`,
|
|
4639
|
+
method: "GET",
|
|
4640
|
+
operation,
|
|
4641
|
+
...hdrs !== void 0 ? { headers: hdrs } : {}
|
|
4642
|
+
});
|
|
4643
|
+
if (!response.ok) {
|
|
4644
|
+
throw await refusal2(
|
|
4645
|
+
response,
|
|
4646
|
+
operation,
|
|
4647
|
+
"Messages sent before you joined could not be read. New messages still arrive."
|
|
4648
|
+
);
|
|
4649
|
+
}
|
|
4650
|
+
const payload = await response.json();
|
|
4651
|
+
const rows = Array.isArray(payload.messages) ? payload.messages : [];
|
|
4652
|
+
const messages = rows.map(
|
|
4653
|
+
(row) => typeof row === "object" && row !== null ? chatFromRow(row) : null
|
|
4654
|
+
).filter((message) => message !== null);
|
|
4655
|
+
store.loadChat(messages);
|
|
4656
|
+
}
|
|
4657
|
+
};
|
|
4658
|
+
}
|
|
4659
|
+
|
|
4151
4660
|
// src/core/record.ts
|
|
4152
4661
|
var import_format = require("@ai-matrx/kit/format");
|
|
4153
4662
|
function activeWrapUpSummary(notes) {
|