@ai-matrx/meet 0.7.8 → 0.7.10
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 +77 -18
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +77 -18
- package/dist/index.js.map +1 -1
- package/dist/react.cjs +77 -18
- package/dist/react.cjs.map +1 -1
- package/dist/react.js +77 -18
- package/dist/react.js.map +1 -1
- package/package.json +4 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,39 @@
|
|
|
1
1
|
# Changelog — `@ai-matrx/meet`
|
|
2
2
|
|
|
3
|
+
## 0.7.10
|
|
4
|
+
### Unreleased
|
|
5
|
+
### (next patch)
|
|
6
|
+
|
|
7
|
+
- **Fix — people in a meeting can HEAR each other** (production re-verification, 2026-09-29):
|
|
8
|
+
`livekit-client` never plays a remote audio track by itself, and nothing in this package
|
|
9
|
+
attached one, so every meeting received everyone's audio and played none of it. The LiveKit
|
|
10
|
+
driver now attaches every subscribed remote microphone to a hidden audio element in the
|
|
11
|
+
document (not tied to tiles, so a person on stage page two or in the Board overflow is still
|
|
12
|
+
heard) and removes it on unsubscribe and on disconnect.
|
|
13
|
+
- **Fix — a person admitted from the waiting room is no longer a black tile** for everyone
|
|
14
|
+
already inside: a tile rendered before its track was subscribed attached nothing and never
|
|
15
|
+
retried. `attach` now keeps listening for that participant's track and (re)attaches when it
|
|
16
|
+
is subscribed or republished; the returned cleanup stops listening.
|
|
17
|
+
- **Fix — link guests read "Guest" on the attendance report**, not "Member": the durable row
|
|
18
|
+
stores them as `participant` with no account, so "no account" now decides it.
|
|
19
|
+
- Guards: `src/core/livekit-driver.media.test.ts` (4 of 6 failed before the fix),
|
|
20
|
+
`after.test.ts` › "a link guest is a GUEST as the database stores them".
|
|
21
|
+
- **Consumer action:** none.
|
|
22
|
+
|
|
23
|
+
## 0.7.9
|
|
24
|
+
### (next patch)
|
|
25
|
+
|
|
26
|
+
- **Fix — a database refusal reads as a sentence for a person** (verifier, 2026-09-27, the
|
|
27
|
+
class behind raw toast text): `normalizeMeetError`'s `message` was
|
|
28
|
+
"meet.x: denied by the database (42501: …)" with a remedy about RLS and service-role
|
|
29
|
+
clients, and hosts render `message` + `remedy` in toasts. Now our RPC's own sentence
|
|
30
|
+
(`RAISE EXCEPTION 'Only the host …'`) is the message; Postgres wording, SQLSTATEs and class
|
|
31
|
+
names become a plain sentence ("You don't have access to do that in this meeting.", "This
|
|
32
|
+
meeting changed a moment ago…", "The server could not be reached."); the raw error stays on
|
|
33
|
+
`cause`. `SERVER_FAILED_SENTENCE` / `isRawServerText` now live in `core/errors.ts`
|
|
34
|
+
(still re-exported from the root).
|
|
35
|
+
- **Consumer action:** none.
|
|
36
|
+
|
|
3
37
|
## 0.7.8
|
|
4
38
|
### (next patch)
|
|
5
39
|
|
package/dist/index.cjs
CHANGED
|
@@ -220,6 +220,20 @@ var MeetError = class extends Error {
|
|
|
220
220
|
return this.code === "permission-denied" || this.code === "device-not-found" || this.code === "device-in-use" || this.code === "insecure-context";
|
|
221
221
|
}
|
|
222
222
|
};
|
|
223
|
+
var SERVER_FAILED_SENTENCE = "The server hit an unexpected problem and nothing was changed.";
|
|
224
|
+
function isRawServerText(value) {
|
|
225
|
+
const text3 = value.trim();
|
|
226
|
+
return /^internal server error\b/i.test(text3) || /^[A-Z][A-Za-z0-9]*(Error|Exception)$/.test(text3) || /^[a-z][a-z0-9]*(_[a-z0-9]+)+$/.test(text3);
|
|
227
|
+
}
|
|
228
|
+
function humanDatabaseSentence(message) {
|
|
229
|
+
const text3 = message.trim();
|
|
230
|
+
if (text3.length === 0 || isRawServerText(text3)) return null;
|
|
231
|
+
if (/permission denied for|violates|relation "|does not exist|syntax error|column "|jwt|pgrst|duplicate key|null value in/i.test(
|
|
232
|
+
text3
|
|
233
|
+
))
|
|
234
|
+
return null;
|
|
235
|
+
return text3;
|
|
236
|
+
}
|
|
223
237
|
var FORBIDDEN_CODES = /* @__PURE__ */ new Set(["42501", "PGRST301", "PGRST302"]);
|
|
224
238
|
var NOT_FOUND_CODES = /* @__PURE__ */ new Set(["PGRST116", "PGRST205"]);
|
|
225
239
|
var CONFLICT_CODES = /* @__PURE__ */ new Set(["23505", "40001", "55000"]);
|
|
@@ -247,8 +261,8 @@ function normalizeMeetError(error, operation) {
|
|
|
247
261
|
if (code !== void 0 && FORBIDDEN_CODES.has(code)) {
|
|
248
262
|
return new MeetError(
|
|
249
263
|
"forbidden",
|
|
250
|
-
|
|
251
|
-
"
|
|
264
|
+
humanDatabaseSentence(message) ?? "You don't have access to do that in this meeting.",
|
|
265
|
+
"Ask the meeting's host to do it, or to give you access.",
|
|
252
266
|
error
|
|
253
267
|
);
|
|
254
268
|
}
|
|
@@ -263,15 +277,16 @@ function normalizeMeetError(error, operation) {
|
|
|
263
277
|
if (code !== void 0 && CONFLICT_CODES.has(code)) {
|
|
264
278
|
return new MeetError(
|
|
265
279
|
"conflict",
|
|
266
|
-
|
|
267
|
-
"
|
|
280
|
+
humanDatabaseSentence(message) ?? "This meeting changed a moment ago, so nothing was saved.",
|
|
281
|
+
"Reload to see the latest, then try again.",
|
|
268
282
|
error
|
|
269
283
|
);
|
|
270
284
|
}
|
|
285
|
+
const hint = error?.hint;
|
|
271
286
|
return new MeetError(
|
|
272
287
|
"transport",
|
|
273
|
-
|
|
274
|
-
|
|
288
|
+
/failed to fetch|networkerror|load failed|network request failed/i.test(message) ? "The server could not be reached." : humanDatabaseSentence(message) ?? SERVER_FAILED_SENTENCE,
|
|
289
|
+
typeof hint === "string" && humanDatabaseSentence(hint) !== null ? hint.trim() : "Nothing was changed \u2014 retry in a moment; if it keeps happening, reload the page.",
|
|
275
290
|
error
|
|
276
291
|
);
|
|
277
292
|
}
|
|
@@ -679,11 +694,6 @@ function readText(data) {
|
|
|
679
694
|
}
|
|
680
695
|
return "";
|
|
681
696
|
}
|
|
682
|
-
var SERVER_FAILED_SENTENCE = "The server hit an unexpected problem and nothing was changed.";
|
|
683
|
-
function isRawServerText(value) {
|
|
684
|
-
const text3 = value.trim();
|
|
685
|
-
return /^internal server error\b/i.test(text3) || /^[A-Z][A-Za-z0-9]*(Error|Exception)$/.test(text3) || /^[a-z][a-z0-9]*(_[a-z0-9]+)+$/.test(text3);
|
|
686
|
-
}
|
|
687
697
|
async function refusalFromResponse(response, operation, fallbackRemedy) {
|
|
688
698
|
let body = null;
|
|
689
699
|
try {
|
|
@@ -2997,6 +3007,15 @@ function createLiveKitDriver(handlers, options = {}) {
|
|
|
2997
3007
|
handlers.participants([local, ...remotes]);
|
|
2998
3008
|
};
|
|
2999
3009
|
projectRoster = roster;
|
|
3010
|
+
created.on(RoomEvent.TrackSubscribed, (track) => {
|
|
3011
|
+
if (track.kind !== Track.Kind.Audio || typeof document === "undefined") return;
|
|
3012
|
+
const element = track.attach();
|
|
3013
|
+
element.setAttribute("data-mx-meet-audio", "");
|
|
3014
|
+
audioSink().appendChild(element);
|
|
3015
|
+
}).on(RoomEvent.TrackUnsubscribed, (track) => {
|
|
3016
|
+
if (track.kind !== Track.Kind.Audio) return;
|
|
3017
|
+
for (const element of track.detach()) element.remove();
|
|
3018
|
+
});
|
|
3000
3019
|
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) => {
|
|
3001
3020
|
handlers.roomMetadata?.(metadata ?? "");
|
|
3002
3021
|
}).on(RoomEvent.ActiveSpeakersChanged, (speakers) => {
|
|
@@ -3012,6 +3031,8 @@ function createLiveKitDriver(handlers, options = {}) {
|
|
|
3012
3031
|
participant?.identity ?? null
|
|
3013
3032
|
);
|
|
3014
3033
|
}).on(RoomEvent.Disconnected, (reason) => {
|
|
3034
|
+
audioContainer?.remove();
|
|
3035
|
+
audioContainer = null;
|
|
3015
3036
|
handlers.disconnected(reason === void 0 ? null : String(reason));
|
|
3016
3037
|
});
|
|
3017
3038
|
try {
|
|
@@ -3053,10 +3074,26 @@ function createLiveKitDriver(handlers, options = {}) {
|
|
|
3053
3074
|
microphone: Track.Source.Microphone,
|
|
3054
3075
|
screen: Track.Source.ScreenShare
|
|
3055
3076
|
};
|
|
3077
|
+
trackEvents = {
|
|
3078
|
+
subscribed: RoomEvent.TrackSubscribed,
|
|
3079
|
+
localPublished: RoomEvent.LocalTrackPublished
|
|
3080
|
+
};
|
|
3056
3081
|
room = created;
|
|
3057
3082
|
return created;
|
|
3058
3083
|
}
|
|
3059
3084
|
let sources = null;
|
|
3085
|
+
let trackEvents = null;
|
|
3086
|
+
let audioContainer = null;
|
|
3087
|
+
function audioSink() {
|
|
3088
|
+
if (audioContainer !== null && audioContainer.isConnected) return audioContainer;
|
|
3089
|
+
const container = document.createElement("div");
|
|
3090
|
+
container.setAttribute("data-mx-meet-audio-sink", "");
|
|
3091
|
+
container.setAttribute("aria-hidden", "true");
|
|
3092
|
+
container.style.display = "none";
|
|
3093
|
+
document.body.appendChild(container);
|
|
3094
|
+
audioContainer = container;
|
|
3095
|
+
return container;
|
|
3096
|
+
}
|
|
3060
3097
|
function requireRoom(operation) {
|
|
3061
3098
|
if (room === null) {
|
|
3062
3099
|
throw new MeetError(
|
|
@@ -3087,6 +3124,8 @@ function createLiveKitDriver(handlers, options = {}) {
|
|
|
3087
3124
|
} catch {
|
|
3088
3125
|
}
|
|
3089
3126
|
projectRoster = null;
|
|
3127
|
+
audioContainer?.remove();
|
|
3128
|
+
audioContainer = null;
|
|
3090
3129
|
await target.disconnect();
|
|
3091
3130
|
},
|
|
3092
3131
|
async setMicrophoneEnabled(enabled) {
|
|
@@ -3152,14 +3191,33 @@ function createLiveKitDriver(handlers, options = {}) {
|
|
|
3152
3191
|
attach(identity, kind, element) {
|
|
3153
3192
|
const target = requireRoom("attach");
|
|
3154
3193
|
const source = sources?.[kind];
|
|
3155
|
-
|
|
3156
|
-
|
|
3157
|
-
const
|
|
3158
|
-
|
|
3159
|
-
|
|
3160
|
-
|
|
3194
|
+
if (source === void 0) return () => void 0;
|
|
3195
|
+
let current = null;
|
|
3196
|
+
const sync = () => {
|
|
3197
|
+
const participant = target.localParticipant.identity === identity ? target.localParticipant : target.remoteParticipants.get(identity);
|
|
3198
|
+
const track = participant?.getTrackPublication(source)?.track;
|
|
3199
|
+
if (track === void 0 || track === current) return;
|
|
3200
|
+
current?.detach(element);
|
|
3201
|
+
track.attach(element);
|
|
3202
|
+
current = track;
|
|
3203
|
+
};
|
|
3204
|
+
const onTrack = (...args) => {
|
|
3205
|
+
const who = args[args.length - 1];
|
|
3206
|
+
if (who?.identity === identity) sync();
|
|
3207
|
+
};
|
|
3208
|
+
sync();
|
|
3209
|
+
const events = trackEvents;
|
|
3210
|
+
if (events !== null) {
|
|
3211
|
+
target.on(events.subscribed, onTrack);
|
|
3212
|
+
target.on(events.localPublished, onTrack);
|
|
3213
|
+
}
|
|
3161
3214
|
return () => {
|
|
3162
|
-
|
|
3215
|
+
if (events !== null) {
|
|
3216
|
+
target.off(events.subscribed, onTrack);
|
|
3217
|
+
target.off(events.localPublished, onTrack);
|
|
3218
|
+
}
|
|
3219
|
+
current?.detach(element);
|
|
3220
|
+
current = null;
|
|
3163
3221
|
};
|
|
3164
3222
|
},
|
|
3165
3223
|
activeDevices() {
|
|
@@ -4088,6 +4146,7 @@ function kindOf(person) {
|
|
|
4088
4146
|
if (person.role === "host") return "host";
|
|
4089
4147
|
if (person.role === "cohost") return "cohost";
|
|
4090
4148
|
if (person.role === "guest") return person.userId !== null ? "signed-in guest" : "guest";
|
|
4149
|
+
if (person.userId === null) return "guest";
|
|
4091
4150
|
return "member";
|
|
4092
4151
|
}
|
|
4093
4152
|
function ms(iso) {
|