@ai-matrx/meet 0.11.36 → 0.11.38
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 +12 -0
- package/dist/core.cjs +129 -87
- package/dist/core.cjs.map +1 -1
- package/dist/core.d.cts +66 -22
- package/dist/core.d.ts +66 -22
- package/dist/core.js +129 -87
- package/dist/core.js.map +1 -1
- package/dist/index.cjs +129 -87
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +129 -87
- package/dist/index.js.map +1 -1
- package/dist/react.cjs +231 -107
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +17 -3
- package/dist/react.d.ts +17 -3
- package/dist/react.js +231 -107
- package/dist/react.js.map +1 -1
- package/dist/skins/meet.cjs +120 -20
- package/dist/skins/meet.cjs.map +1 -1
- package/dist/skins/meet.js +120 -20
- package/dist/skins/meet.js.map +1 -1
- package/dist/styles.css +7 -0
- package/package.json +5 -5
package/dist/react.cjs
CHANGED
|
@@ -40,6 +40,7 @@ __export(react_exports, {
|
|
|
40
40
|
DATA_TOPICS: () => DATA_TOPICS,
|
|
41
41
|
DEFAULT_CONNECTION_POLICY: () => DEFAULT_CONNECTION_POLICY,
|
|
42
42
|
DEFAULT_EFFECTS: () => DEFAULT_EFFECTS,
|
|
43
|
+
DEFAULT_LEAVE_POLICY: () => DEFAULT_LEAVE_POLICY,
|
|
43
44
|
DEFAULT_PAGE_SIZE: () => DEFAULT_PAGE_SIZE,
|
|
44
45
|
EMPTY_COLLAB: () => EMPTY_COLLAB,
|
|
45
46
|
END_MEETING_NOT_SERVED: () => END_MEETING_NOT_SERVED,
|
|
@@ -83,6 +84,7 @@ __export(react_exports, {
|
|
|
83
84
|
NO_SHARE: () => NO_SHARE,
|
|
84
85
|
ORGANIZATION_HEADER: () => ORGANIZATION_HEADER,
|
|
85
86
|
PARTICIPANT_CAN_RECORD_ATTRIBUTE: () => PARTICIPANT_CAN_RECORD_ATTRIBUTE,
|
|
87
|
+
PARTICIPANT_HAND_ATTRIBUTE: () => PARTICIPANT_HAND_ATTRIBUTE,
|
|
86
88
|
PARTICIPANT_ROLE_ATTRIBUTE: () => PARTICIPANT_ROLE_ATTRIBUTE,
|
|
87
89
|
PARTICIPANT_STANDING_ATTRIBUTE: () => PARTICIPANT_STANDING_ATTRIBUTE,
|
|
88
90
|
REACTION_TTL_MS: () => REACTION_TTL_MS,
|
|
@@ -196,6 +198,8 @@ __export(react_exports, {
|
|
|
196
198
|
isWaitingPhase: () => isWaitingPhase,
|
|
197
199
|
learnName: () => learnName,
|
|
198
200
|
learnNames: () => learnNames,
|
|
201
|
+
leaveChoices: () => leaveChoices,
|
|
202
|
+
leavePolicyFrom: () => leavePolicyFrom,
|
|
199
203
|
listenOnlyNotice: () => listenOnlyNotice,
|
|
200
204
|
loadFollowUpDraft: () => loadFollowUpDraft,
|
|
201
205
|
loadGuestMeetingRecord: () => loadGuestMeetingRecord,
|
|
@@ -297,6 +301,7 @@ __export(react_exports, {
|
|
|
297
301
|
useHandQueue: () => useHandQueue,
|
|
298
302
|
useHostControls: () => useHostControls,
|
|
299
303
|
useIsHost: () => useIsHost,
|
|
304
|
+
useLeave: () => useLeave,
|
|
300
305
|
useListenOnlyNotice: () => useListenOnlyNotice,
|
|
301
306
|
useLobby: () => useLobby,
|
|
302
307
|
useLocalMedia: () => useLocalMedia,
|
|
@@ -1026,6 +1031,30 @@ function invalidResponse(operation, detail) {
|
|
|
1026
1031
|
);
|
|
1027
1032
|
}
|
|
1028
1033
|
|
|
1034
|
+
// src/core/leave.ts
|
|
1035
|
+
var DEFAULT_LEAVE_POLICY = { hostLeavePrompt: "leave_or_end", endConfirm: false };
|
|
1036
|
+
var PROMPTS = ["leave_or_end", "must_assign_or_end", "leave_only"];
|
|
1037
|
+
function leavePolicyFrom(raw) {
|
|
1038
|
+
if (typeof raw !== "object" || raw === null) return DEFAULT_LEAVE_POLICY;
|
|
1039
|
+
const value = raw;
|
|
1040
|
+
const prompt = value["host_leave_prompt"];
|
|
1041
|
+
const confirm = value["end_confirm"];
|
|
1042
|
+
return {
|
|
1043
|
+
hostLeavePrompt: PROMPTS.includes(prompt) ? prompt : DEFAULT_LEAVE_POLICY.hostLeavePrompt,
|
|
1044
|
+
endConfirm: typeof confirm === "boolean" ? confirm : DEFAULT_LEAVE_POLICY.endConfirm
|
|
1045
|
+
};
|
|
1046
|
+
}
|
|
1047
|
+
function leaveChoices(args) {
|
|
1048
|
+
if (args.role !== "host") return ["leave"];
|
|
1049
|
+
switch (args.policy.hostLeavePrompt) {
|
|
1050
|
+
case "must_assign_or_end":
|
|
1051
|
+
return args.othersPresent ? ["assign_and_leave", "end_for_all"] : ["leave", "end_for_all"];
|
|
1052
|
+
case "leave_only":
|
|
1053
|
+
case "leave_or_end":
|
|
1054
|
+
return ["leave", "end_for_all"];
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1029
1058
|
// src/core/tokens.ts
|
|
1030
1059
|
function createWebRoomTokenStorage(onDiagnostic) {
|
|
1031
1060
|
const store = globalThis.localStorage;
|
|
@@ -1133,6 +1162,12 @@ var MEET_ROUTES = {
|
|
|
1133
1162
|
state: "/api/v1/meet/state",
|
|
1134
1163
|
/** S4: `withdraw` (stop waiting) or `leave` (close this tab); the pass may ride in the body. */
|
|
1135
1164
|
leave: "/api/v1/meet/leave",
|
|
1165
|
+
/**
|
|
1166
|
+
* S8: a host's browser just called an admission DOOR (`meet_admit` · `meet_deny` ·
|
|
1167
|
+
* `meet_undeny` · `meet_admit_all`). Payload-free: the server republishes the room's
|
|
1168
|
+
* replicated state (the People badge's waiting count) from the database truth.
|
|
1169
|
+
*/
|
|
1170
|
+
admissionChanged: "/api/v1/meet/admission/changed",
|
|
1136
1171
|
/** S4: a pass holder (guests included) saw the recording notice, for this run. */
|
|
1137
1172
|
consent: "/api/v1/meet/consent"
|
|
1138
1173
|
};
|
|
@@ -1217,7 +1252,8 @@ function parseRoomToken(body, roomName) {
|
|
|
1217
1252
|
pollSeconds: typeof payload["poll_seconds"] === "number" ? payload["poll_seconds"] : void 0,
|
|
1218
1253
|
breakoutRoomId: readString(payload, "breakout_room_id") ?? null,
|
|
1219
1254
|
observersVisibleTo: readString(payload, "observers_visible_to") === "hosts" ? "hosts" : "everyone",
|
|
1220
|
-
connectionPolicy: connectionPolicyFrom(payload["connection_policy"])
|
|
1255
|
+
connectionPolicy: connectionPolicyFrom(payload["connection_policy"]),
|
|
1256
|
+
leavePolicy: leavePolicyFrom(payload["leave_policy"])
|
|
1221
1257
|
};
|
|
1222
1258
|
}
|
|
1223
1259
|
function createTokenClient(options) {
|
|
@@ -4117,6 +4153,10 @@ var EMPTY = {
|
|
|
4117
4153
|
chatHistoryError: null,
|
|
4118
4154
|
reactions: [],
|
|
4119
4155
|
spotlight: null,
|
|
4156
|
+
actingHost: null,
|
|
4157
|
+
presenter: null,
|
|
4158
|
+
waitingCount: 0,
|
|
4159
|
+
leavePolicy: DEFAULT_LEAVE_POLICY,
|
|
4120
4160
|
moderationNotice: null,
|
|
4121
4161
|
actionNotice: null,
|
|
4122
4162
|
privateChatRequest: null,
|
|
@@ -4178,8 +4218,6 @@ function createMeetStore(initial = {}) {
|
|
|
4178
4218
|
},
|
|
4179
4219
|
setParticipants(participants) {
|
|
4180
4220
|
const names = learnNames(state.names, participants);
|
|
4181
|
-
const hands = new Map(state.participants.map((p) => [p.identity, p.handRaised]));
|
|
4182
|
-
const handTimes = new Map(state.participants.map((p) => [p.identity, p.handRaisedAt ?? null]));
|
|
4183
4221
|
const quality = new Map(state.participants.map((p) => [p.identity, p.quality]));
|
|
4184
4222
|
commit({
|
|
4185
4223
|
...state,
|
|
@@ -4192,8 +4230,6 @@ function createMeetStore(initial = {}) {
|
|
|
4192
4230
|
// identity could reach a screen, and it is the place that asks the
|
|
4193
4231
|
// name book. `roster-names.test.ts` fails if a token ever gets past it.
|
|
4194
4232
|
displayName: displayNameFor(names, participant.identity, participant.displayName),
|
|
4195
|
-
handRaised: hands.get(participant.identity) ?? participant.handRaised,
|
|
4196
|
-
handRaisedAt: handTimes.get(participant.identity) ?? participant.handRaisedAt ?? null,
|
|
4197
4233
|
quality: participant.quality === "unknown" ? quality.get(participant.identity) ?? "unknown" : participant.quality
|
|
4198
4234
|
}))
|
|
4199
4235
|
});
|
|
@@ -4216,20 +4252,6 @@ function createMeetStore(initial = {}) {
|
|
|
4216
4252
|
)
|
|
4217
4253
|
});
|
|
4218
4254
|
},
|
|
4219
|
-
setHand(identity, raised, at) {
|
|
4220
|
-
commit({
|
|
4221
|
-
...state,
|
|
4222
|
-
participants: state.participants.map(
|
|
4223
|
-
(participant) => participant.identity === identity ? {
|
|
4224
|
-
...participant,
|
|
4225
|
-
handRaised: raised,
|
|
4226
|
-
// A hand that is ALREADY up keeps its place in the queue when
|
|
4227
|
-
// it is re-announced (to a late joiner): the first time wins.
|
|
4228
|
-
handRaisedAt: raised ? participant.handRaised && participant.handRaisedAt != null ? participant.handRaisedAt : at ?? Date.now() : null
|
|
4229
|
-
} : participant
|
|
4230
|
-
)
|
|
4231
|
-
});
|
|
4232
|
-
},
|
|
4233
4255
|
addChat(message) {
|
|
4234
4256
|
if (state.chat.some((existing) => existing.id === message.id)) return;
|
|
4235
4257
|
const chat = [...state.chat, message];
|
|
@@ -4589,7 +4611,7 @@ function createMeetingSession(options) {
|
|
|
4589
4611
|
})();
|
|
4590
4612
|
return reading;
|
|
4591
4613
|
}
|
|
4592
|
-
async function postLeave(intent) {
|
|
4614
|
+
async function postLeave(intent, extra = {}) {
|
|
4593
4615
|
const meeting = requireMeeting(`meet.${intent}`);
|
|
4594
4616
|
const pass = options.resolvePass?.() ?? null;
|
|
4595
4617
|
const response = await options.api.request({
|
|
@@ -4597,7 +4619,7 @@ function createMeetingSession(options) {
|
|
|
4597
4619
|
method: "POST",
|
|
4598
4620
|
requireAuth: false,
|
|
4599
4621
|
operation: `meet.${intent}`,
|
|
4600
|
-
body: { meeting_id: meeting.id, intent },
|
|
4622
|
+
body: { meeting_id: meeting.id, intent, ...extra },
|
|
4601
4623
|
...pass !== null ? { headers: { [MEETING_PASS_HEADER]: pass } } : {}
|
|
4602
4624
|
});
|
|
4603
4625
|
if (!response.ok) {
|
|
@@ -4611,13 +4633,41 @@ function createMeetingSession(options) {
|
|
|
4611
4633
|
throw new MeetError(
|
|
4612
4634
|
"transport",
|
|
4613
4635
|
refusal2?.reason ?? `meet.${intent}: the server answered ${response.status}`,
|
|
4614
|
-
refusal2?.remedy ?? "You are still in the waiting room. Try again."
|
|
4636
|
+
refusal2?.remedy ?? (intent === "withdraw" ? "You are still in the waiting room. Try again." : "Nothing was changed. Try again.")
|
|
4615
4637
|
);
|
|
4616
4638
|
}
|
|
4617
4639
|
}
|
|
4618
4640
|
function hint() {
|
|
4619
4641
|
channel?.send(MEET_EVENTS.stateChanged, { v: null });
|
|
4620
4642
|
}
|
|
4643
|
+
async function tellServerAdmissionChanged(operation) {
|
|
4644
|
+
const meeting = store.snapshot().meeting;
|
|
4645
|
+
if (meeting === null) return;
|
|
4646
|
+
const pass = options.resolvePass?.() ?? null;
|
|
4647
|
+
try {
|
|
4648
|
+
const response = await options.api.request({
|
|
4649
|
+
path: MEET_ROUTES.admissionChanged,
|
|
4650
|
+
method: "POST",
|
|
4651
|
+
requireAuth: false,
|
|
4652
|
+
operation,
|
|
4653
|
+
body: { meeting_id: meeting.id },
|
|
4654
|
+
...pass !== null ? { headers: { [MEETING_PASS_HEADER]: pass } } : {}
|
|
4655
|
+
});
|
|
4656
|
+
if (!response.ok) {
|
|
4657
|
+
report(
|
|
4658
|
+
"warn",
|
|
4659
|
+
`${operation}: the server answered ${response.status} when told about the admission change`,
|
|
4660
|
+
"The decision was saved. The waiting count in the room may lag until the next change."
|
|
4661
|
+
);
|
|
4662
|
+
}
|
|
4663
|
+
} catch (error) {
|
|
4664
|
+
report(
|
|
4665
|
+
"warn",
|
|
4666
|
+
`${operation}: ${String(error)}`,
|
|
4667
|
+
"The decision was saved. The waiting count in the room may lag until the next change."
|
|
4668
|
+
);
|
|
4669
|
+
}
|
|
4670
|
+
}
|
|
4621
4671
|
async function refreshQueue() {
|
|
4622
4672
|
const meeting = store.snapshot().meeting;
|
|
4623
4673
|
if (meeting === null || isGuest(identity) || !isHostRole(effectiveRole(store.snapshot()))) return;
|
|
@@ -4773,6 +4823,7 @@ function createMeetingSession(options) {
|
|
|
4773
4823
|
decidedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4774
4824
|
decidedBy: by
|
|
4775
4825
|
});
|
|
4826
|
+
await tellServerAdmissionChanged("meet.admit");
|
|
4776
4827
|
hint();
|
|
4777
4828
|
},
|
|
4778
4829
|
async undeny(entry) {
|
|
@@ -4785,6 +4836,7 @@ function createMeetingSession(options) {
|
|
|
4785
4836
|
byUserId: by
|
|
4786
4837
|
});
|
|
4787
4838
|
store.upsertLobby({ ...entry, state: decidedState(row.admissionState), decidedAt: (/* @__PURE__ */ new Date()).toISOString(), decidedBy: by });
|
|
4839
|
+
await tellServerAdmissionChanged("meet.undeny");
|
|
4788
4840
|
hint();
|
|
4789
4841
|
},
|
|
4790
4842
|
async admitAll() {
|
|
@@ -4794,6 +4846,7 @@ function createMeetingSession(options) {
|
|
|
4794
4846
|
for (const row of rows) {
|
|
4795
4847
|
store.setAdmission(row.identity, row.admissionState, by);
|
|
4796
4848
|
}
|
|
4849
|
+
await tellServerAdmissionChanged("meet.admitAll");
|
|
4797
4850
|
hint();
|
|
4798
4851
|
},
|
|
4799
4852
|
startWaiting() {
|
|
@@ -4835,6 +4888,10 @@ function createMeetingSession(options) {
|
|
|
4835
4888
|
store.dispatch({ type: "admission", admission: "withdrawn" });
|
|
4836
4889
|
hint();
|
|
4837
4890
|
},
|
|
4891
|
+
async hostLeave(intent) {
|
|
4892
|
+
if (intent === "end_for_all") await postLeave("end_for_all");
|
|
4893
|
+
else await postLeave("assign_and_leave", { assign_to: intent.assignTo });
|
|
4894
|
+
},
|
|
4838
4895
|
async askAgain() {
|
|
4839
4896
|
await options.rejoin?.();
|
|
4840
4897
|
},
|
|
@@ -5295,18 +5352,6 @@ var ROOM_EVENTS = {
|
|
|
5295
5352
|
wrapUp: "matrx.meet.wrap_up",
|
|
5296
5353
|
/** The meeting ended for everyone. Payload `{ meeting_id, ended_at, trigger }`. */
|
|
5297
5354
|
ended: "matrx.meet.ended",
|
|
5298
|
-
/**
|
|
5299
|
-
* A `meet_recordings` row changed state. Payload
|
|
5300
|
-
* `{ meeting_id, recording: { id, state, started_by } }`.
|
|
5301
|
-
*
|
|
5302
|
-
* 🚨 THIS IS THE ONLY WAY A GUEST LEARNS A RECORDING IS RUNNING. PreJoin
|
|
5303
|
-
* promises every joiner an indicator, and `anon` holds no SELECT grant on
|
|
5304
|
-
* `meet_recordings` (MRI-A10 settled that widening it is forbidden), so
|
|
5305
|
-
* without this packet the promise was a sentence with nothing behind it: on
|
|
5306
|
-
* production 2026-09-09 the guest lane showed no indicator at all while the
|
|
5307
|
-
* host recorded (MRI-C8).
|
|
5308
|
-
*/
|
|
5309
|
-
recording: "matrx.meet.recording",
|
|
5310
5355
|
/**
|
|
5311
5356
|
* A chat message to everyone was SAVED (Meet wave 2). Payload
|
|
5312
5357
|
* `{ message: { id, meeting_id, sender_identity, sender_name, body, created_at } }`.
|
|
@@ -5359,8 +5404,6 @@ function applyRoomEvent(args) {
|
|
|
5359
5404
|
return applyWrapUp(store, payload, now);
|
|
5360
5405
|
case ROOM_EVENTS.ended:
|
|
5361
5406
|
return applyEnded(store, payload);
|
|
5362
|
-
case ROOM_EVENTS.recording:
|
|
5363
|
-
return applyRecording(store, payload);
|
|
5364
5407
|
case ROOM_EVENTS.chat:
|
|
5365
5408
|
return applyChat(store, payload);
|
|
5366
5409
|
case ROOM_EVENTS.moderation:
|
|
@@ -5440,9 +5483,9 @@ function applyWrapUp(store, payload, now) {
|
|
|
5440
5483
|
store.announceWrapUp(announcement);
|
|
5441
5484
|
return HANDLED;
|
|
5442
5485
|
}
|
|
5443
|
-
function
|
|
5486
|
+
function applyRecordingState(store, payload) {
|
|
5444
5487
|
const row = object(payload["recording"]);
|
|
5445
|
-
if (row === null) return ignored("
|
|
5488
|
+
if (row === null) return ignored("room state carries no recording");
|
|
5446
5489
|
const meetingId = text2(payload["meeting_id"]);
|
|
5447
5490
|
const meeting = store.snapshot().meeting;
|
|
5448
5491
|
if (meeting !== null && meetingId !== null && meetingId !== meeting.id) {
|
|
@@ -5524,19 +5567,6 @@ function applyModeration(store, payload, now) {
|
|
|
5524
5567
|
}
|
|
5525
5568
|
const kind = text2(payload["kind"]);
|
|
5526
5569
|
switch (kind) {
|
|
5527
|
-
case "lower-hands": {
|
|
5528
|
-
const raw = payload["identities"];
|
|
5529
|
-
const everyone = raw === "*";
|
|
5530
|
-
const named = new Set(
|
|
5531
|
-
Array.isArray(raw) ? raw.filter((value) => typeof value === "string") : []
|
|
5532
|
-
);
|
|
5533
|
-
for (const participant of snapshot.participants) {
|
|
5534
|
-
if (participant.handRaised && (everyone || named.has(participant.identity))) {
|
|
5535
|
-
store.setHand(participant.identity, false);
|
|
5536
|
-
}
|
|
5537
|
-
}
|
|
5538
|
-
return HANDLED;
|
|
5539
|
-
}
|
|
5540
5570
|
case "role": {
|
|
5541
5571
|
const identity = text2(payload["identity"]);
|
|
5542
5572
|
const role = text2(payload["role"]);
|
|
@@ -5630,7 +5660,6 @@ function noteTakerLabel(status) {
|
|
|
5630
5660
|
|
|
5631
5661
|
// src/core/room-driver.ts
|
|
5632
5662
|
var DATA_TOPICS = {
|
|
5633
|
-
hand: "mx.hand",
|
|
5634
5663
|
reaction: "mx.reaction",
|
|
5635
5664
|
chat: "mx.chat"
|
|
5636
5665
|
};
|
|
@@ -5654,6 +5683,11 @@ function SCREEN_SHARE_BLOCKED_REMEDY_FOR(remedy) {
|
|
|
5654
5683
|
return /administrator|policy/i.test(remedy) ? remedy : SCREEN_SHARE_BLOCKED_REMEDY;
|
|
5655
5684
|
}
|
|
5656
5685
|
var PARTICIPANT_ROLE_ATTRIBUTE = "matrx.participant.role";
|
|
5686
|
+
var PARTICIPANT_HAND_ATTRIBUTE = "matrx.hand";
|
|
5687
|
+
function handFrom(value) {
|
|
5688
|
+
const at = value === void 0 || value.length === 0 ? Number.NaN : Number(value);
|
|
5689
|
+
return Number.isFinite(at) ? { handRaised: true, handRaisedAt: at } : { handRaised: false, handRaisedAt: null };
|
|
5690
|
+
}
|
|
5657
5691
|
var PARTICIPANT_CAN_RECORD_ATTRIBUTE = "matrx.participant.can_record";
|
|
5658
5692
|
var CONNECTION_KIND_ATTRIBUTE = "matrx.participant.connection_kind";
|
|
5659
5693
|
var PARTICIPANT_STANDING_ATTRIBUTE = "matrx.participant.standing";
|
|
@@ -5707,7 +5741,10 @@ function projectParticipant(driver, joinedAt) {
|
|
|
5707
5741
|
cameraEnabled: driver.cameraEnabled,
|
|
5708
5742
|
screenSharing: driver.screenSharing,
|
|
5709
5743
|
speaking: false,
|
|
5710
|
-
|
|
5744
|
+
// S8: a hand is the SERVER's (`hand_raised_at` on the person row, `/moderate raise_hand`),
|
|
5745
|
+
// replicated as the `matrx.hand` attribute: epoch ms of the raise, "" when down. It is
|
|
5746
|
+
// therefore delivered to late joiners and survives refresh and reconnect.
|
|
5747
|
+
...handFrom(driver.attributes[PARTICIPANT_HAND_ATTRIBUTE]),
|
|
5711
5748
|
quality: "unknown",
|
|
5712
5749
|
joinedAt
|
|
5713
5750
|
};
|
|
@@ -5837,40 +5874,38 @@ function createRoomEngine(options) {
|
|
|
5837
5874
|
},
|
|
5838
5875
|
participants(list) {
|
|
5839
5876
|
const joinedAt = new Date(now()).toISOString();
|
|
5840
|
-
const before = new Set(store.snapshot().participants.map((p) => p.identity));
|
|
5841
5877
|
projectRoster(list, joinedAt);
|
|
5842
|
-
const snapshot = store.snapshot();
|
|
5843
|
-
const newcomer = snapshot.participants.some(
|
|
5844
|
-
(participant) => !participant.isLocal && !before.has(participant.identity)
|
|
5845
|
-
);
|
|
5846
|
-
const me = snapshot.participants.find((p) => p.identity === snapshot.localIdentity);
|
|
5847
|
-
if (newcomer && before.size > 0 && me?.handRaised === true) {
|
|
5848
|
-
void publish(DATA_TOPICS.hand, { raised: true, raisedAt: me.handRaisedAt ?? now() }).catch(
|
|
5849
|
-
() => void 0
|
|
5850
|
-
);
|
|
5851
|
-
}
|
|
5852
5878
|
},
|
|
5853
5879
|
participantLeft(who, reason) {
|
|
5854
5880
|
departures.set(who, reason);
|
|
5855
5881
|
},
|
|
5856
5882
|
roomMetadata(metadata) {
|
|
5857
|
-
let
|
|
5883
|
+
let parsed = {};
|
|
5858
5884
|
if (metadata.length > 0) {
|
|
5859
5885
|
try {
|
|
5860
|
-
const
|
|
5861
|
-
if (typeof
|
|
5862
|
-
const value = parsed.spotlight;
|
|
5863
|
-
if (typeof value === "string" && value.length > 0) spotlight = asParticipantIdentity(value);
|
|
5864
|
-
}
|
|
5886
|
+
const value = JSON.parse(metadata);
|
|
5887
|
+
if (typeof value === "object" && value !== null) parsed = value;
|
|
5865
5888
|
} catch {
|
|
5866
5889
|
report(
|
|
5867
5890
|
"warn",
|
|
5868
|
-
"meet: the room's metadata is not JSON, so
|
|
5869
|
-
"Only aidream writes room metadata; check services/meet/
|
|
5891
|
+
"meet: the room's metadata is not JSON, so the room state was not read from it",
|
|
5892
|
+
"Only aidream writes room metadata; check services/meet/room_state.py."
|
|
5870
5893
|
);
|
|
5871
5894
|
}
|
|
5872
5895
|
}
|
|
5873
|
-
|
|
5896
|
+
const key = (name) => {
|
|
5897
|
+
const value = parsed[name];
|
|
5898
|
+
return typeof value === "string" && value.length > 0 ? asParticipantIdentity(personOf(value)) : null;
|
|
5899
|
+
};
|
|
5900
|
+
const waiting = typeof parsed["waiting_count"] === "number" ? parsed["waiting_count"] : 0;
|
|
5901
|
+
const before = store.snapshot();
|
|
5902
|
+
const next = { spotlight: key("spotlight"), actingHost: key("acting_host"), presenter: key("presenter"), waitingCount: waiting };
|
|
5903
|
+
if (before.spotlight !== next.spotlight || before.actingHost !== next.actingHost || before.presenter !== next.presenter || before.waitingCount !== next.waitingCount) {
|
|
5904
|
+
store.patch(next);
|
|
5905
|
+
}
|
|
5906
|
+
if (parsed["recording"] !== void 0 && parsed["recording"] !== null) {
|
|
5907
|
+
applyRecordingState(store, { meeting_id: parsed["meeting_id"], recording: parsed["recording"] });
|
|
5908
|
+
}
|
|
5874
5909
|
},
|
|
5875
5910
|
activeSpeakers(identities) {
|
|
5876
5911
|
store.setSpeaking([...new Set(identities.map((who) => personOf(who)))]);
|
|
@@ -5972,13 +6007,6 @@ function createRoomEngine(options) {
|
|
|
5972
6007
|
}
|
|
5973
6008
|
const sender = personOf(from ?? "");
|
|
5974
6009
|
switch (topic) {
|
|
5975
|
-
case DATA_TOPICS.hand:
|
|
5976
|
-
store.setHand(
|
|
5977
|
-
sender,
|
|
5978
|
-
envelope.raised === true,
|
|
5979
|
-
typeof envelope.raisedAt === "number" ? envelope.raisedAt : now()
|
|
5980
|
-
);
|
|
5981
|
-
return;
|
|
5982
6010
|
case DATA_TOPICS.reaction:
|
|
5983
6011
|
if (typeof envelope.emoji === "string") {
|
|
5984
6012
|
store.addReaction({
|
|
@@ -6077,6 +6105,7 @@ function createRoomEngine(options) {
|
|
|
6077
6105
|
current = token;
|
|
6078
6106
|
lastServerUrl = token.serverUrl;
|
|
6079
6107
|
connectionPolicy = token.connectionPolicy ?? DEFAULT_CONNECTION_POLICY;
|
|
6108
|
+
if (token.leavePolicy !== void 0) store.patch({ leavePolicy: token.leavePolicy });
|
|
6080
6109
|
store.patch({
|
|
6081
6110
|
localIdentity: token.identity,
|
|
6082
6111
|
breakoutRoomId: token.breakoutRoomId ?? null,
|
|
@@ -6443,11 +6472,6 @@ function createRoomEngine(options) {
|
|
|
6443
6472
|
dismissShareNotice() {
|
|
6444
6473
|
if (store.snapshot().share.state === "refused") store.patch({ share: NO_SHARE });
|
|
6445
6474
|
},
|
|
6446
|
-
async raiseHand(raised) {
|
|
6447
|
-
const local = store.snapshot().localIdentity;
|
|
6448
|
-
if (local !== null) store.setHand(local, raised);
|
|
6449
|
-
await publish(DATA_TOPICS.hand, { raised, raisedAt: now() });
|
|
6450
|
-
},
|
|
6451
6475
|
async react(emoji) {
|
|
6452
6476
|
const local = store.snapshot().localIdentity;
|
|
6453
6477
|
const id = `${local ?? "local"}-${now()}`;
|
|
@@ -6576,6 +6600,8 @@ var MODERATION_ACTIONS = [
|
|
|
6576
6600
|
"ask_unmute",
|
|
6577
6601
|
"ask_all_unmute",
|
|
6578
6602
|
"spotlight",
|
|
6603
|
+
"present",
|
|
6604
|
+
"raise_hand",
|
|
6579
6605
|
"lower_hand",
|
|
6580
6606
|
"lower_all_hands",
|
|
6581
6607
|
"make_cohost",
|
|
@@ -6606,7 +6632,22 @@ function createInRoomClient(options) {
|
|
|
6606
6632
|
function refusal2(response, operation, remedy) {
|
|
6607
6633
|
return refusalFromResponse(response, operation, remedy);
|
|
6608
6634
|
}
|
|
6609
|
-
|
|
6635
|
+
let toldPresenting = false;
|
|
6636
|
+
let tellChain = Promise.resolve();
|
|
6637
|
+
store.subscribe(() => {
|
|
6638
|
+
const local = store.snapshot().participants.find((p) => p.isLocal);
|
|
6639
|
+
const sharing = local?.screenSharing === true;
|
|
6640
|
+
if (sharing === toldPresenting || local === void 0) return;
|
|
6641
|
+
toldPresenting = sharing;
|
|
6642
|
+
const on = sharing;
|
|
6643
|
+
tellChain = tellChain.then(() => client.moderate({ action: "present", on })).then(
|
|
6644
|
+
() => void 0,
|
|
6645
|
+
(error) => {
|
|
6646
|
+
console.warn(`[meet] the server did not take presenter=${String(on)}: ${String(error)}`);
|
|
6647
|
+
}
|
|
6648
|
+
);
|
|
6649
|
+
});
|
|
6650
|
+
const client = {
|
|
6610
6651
|
async moderate(request) {
|
|
6611
6652
|
const operation = `meet.moderate(${request.action})`;
|
|
6612
6653
|
const hdrs = headers();
|
|
@@ -6619,7 +6660,8 @@ function createInRoomClient(options) {
|
|
|
6619
6660
|
action: request.action,
|
|
6620
6661
|
...request.identity != null ? { identity: request.identity } : {},
|
|
6621
6662
|
...request.name !== void 0 ? { name: request.name } : {},
|
|
6622
|
-
...request.reason !== void 0 ? { reason: request.reason } : {}
|
|
6663
|
+
...request.reason !== void 0 ? { reason: request.reason } : {},
|
|
6664
|
+
...request.on !== void 0 ? { on: request.on } : {}
|
|
6623
6665
|
},
|
|
6624
6666
|
...hdrs !== void 0 ? { headers: hdrs } : {}
|
|
6625
6667
|
});
|
|
@@ -6810,6 +6852,7 @@ function createInRoomClient(options) {
|
|
|
6810
6852
|
});
|
|
6811
6853
|
}
|
|
6812
6854
|
};
|
|
6855
|
+
return client;
|
|
6813
6856
|
}
|
|
6814
6857
|
|
|
6815
6858
|
// src/core/livekit-driver.ts
|
|
@@ -8473,16 +8516,16 @@ function useRoom() {
|
|
|
8473
8516
|
async () => requireEngine(engine, "toggleCamera").setCameraEnabled(!(local?.cameraEnabled ?? false)),
|
|
8474
8517
|
[engine, local]
|
|
8475
8518
|
),
|
|
8476
|
-
toggleScreenShare: (0, import_react4.useCallback)(
|
|
8477
|
-
|
|
8478
|
-
|
|
8479
|
-
|
|
8480
|
-
|
|
8481
|
-
|
|
8482
|
-
toggleHand: (0, import_react4.useCallback)(
|
|
8483
|
-
|
|
8484
|
-
|
|
8485
|
-
),
|
|
8519
|
+
toggleScreenShare: (0, import_react4.useCallback)(async () => {
|
|
8520
|
+
const on = !(local?.screenSharing ?? false);
|
|
8521
|
+
await requireEngine(engine, "toggleScreenShare").setScreenShareEnabled(on);
|
|
8522
|
+
}, [engine, local]),
|
|
8523
|
+
// S8: a hand is SERVER state (`/moderate raise_hand · lower_hand`, server time), replicated
|
|
8524
|
+
// back as the `matrx.hand` attribute — no client sets its own hand on the wire.
|
|
8525
|
+
toggleHand: (0, import_react4.useCallback)(async () => {
|
|
8526
|
+
if (host === null) throw noRuntime("toggleHand");
|
|
8527
|
+
await host.inRoom.moderate({ action: local?.handRaised === true ? "lower_hand" : "raise_hand" });
|
|
8528
|
+
}, [host, local]),
|
|
8486
8529
|
react: (0, import_react4.useCallback)(
|
|
8487
8530
|
async (emoji) => requireEngine(engine, "react").react(emoji),
|
|
8488
8531
|
[engine]
|
|
@@ -8664,6 +8707,31 @@ function useWaiting() {
|
|
|
8664
8707
|
askAgain: (0, import_react4.useCallback)(async () => requireSession(session, "askAgain").askAgain(), [session])
|
|
8665
8708
|
};
|
|
8666
8709
|
}
|
|
8710
|
+
function useLeave() {
|
|
8711
|
+
const host = useMeetHost();
|
|
8712
|
+
const snapshot = useMeetSnapshot();
|
|
8713
|
+
const session = host?.meetings ?? null;
|
|
8714
|
+
const engine = host?.engine ?? null;
|
|
8715
|
+
const policy = snapshot?.leavePolicy ?? DEFAULT_LEAVE_POLICY;
|
|
8716
|
+
const participants = snapshot?.participants ?? [];
|
|
8717
|
+
const me = participants.find((p) => p.isLocal);
|
|
8718
|
+
const candidates = participants.filter((p) => !p.isLocal && !p.isAgent && p.role !== "viewer");
|
|
8719
|
+
const choices = leaveChoices({ policy, role: me?.role ?? null, othersPresent: candidates.length > 0 });
|
|
8720
|
+
return {
|
|
8721
|
+
choices,
|
|
8722
|
+
policy,
|
|
8723
|
+
candidates,
|
|
8724
|
+
leave: (0, import_react4.useCallback)(async () => requireEngine(engine, "leave").leave(), [engine]),
|
|
8725
|
+
endForAll: (0, import_react4.useCallback)(async () => requireSession(session, "endForAll").hostLeave("end_for_all"), [session]),
|
|
8726
|
+
assignAndLeave: (0, import_react4.useCallback)(
|
|
8727
|
+
async (person) => {
|
|
8728
|
+
await requireSession(session, "assignAndLeave").hostLeave({ assignTo: String(person.identity) });
|
|
8729
|
+
await requireEngine(engine, "leave").leave();
|
|
8730
|
+
},
|
|
8731
|
+
[session, engine]
|
|
8732
|
+
)
|
|
8733
|
+
};
|
|
8734
|
+
}
|
|
8667
8735
|
function useRecording() {
|
|
8668
8736
|
const host = useMeetHost();
|
|
8669
8737
|
const snapshot = useMeetSnapshot();
|
|
@@ -11587,19 +11655,75 @@ function ControlBar({ onLeave }) {
|
|
|
11587
11655
|
] }) : null
|
|
11588
11656
|
] }) }) : null
|
|
11589
11657
|
] }),
|
|
11590
|
-
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
11591
|
-
|
|
11658
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(LeaveControl, { onLeave })
|
|
11659
|
+
] })
|
|
11660
|
+
] });
|
|
11661
|
+
}
|
|
11662
|
+
var LEAVE_WORDS = {
|
|
11663
|
+
leave_or_end: { leave: "Leave call", assign_and_leave: "Assign a new host", end_for_all: "End call for everyone" },
|
|
11664
|
+
must_assign_or_end: { leave: "Leave meeting", assign_and_leave: "Assign a new host", end_for_all: "End meeting for all" },
|
|
11665
|
+
leave_only: { leave: "Leave", assign_and_leave: "Assign a new host", end_for_all: "End meeting" }
|
|
11666
|
+
};
|
|
11667
|
+
function LeaveControl({ onLeave }) {
|
|
11668
|
+
const leave = useLeave();
|
|
11669
|
+
const [open, setOpen] = (0, import_react10.useState)("none");
|
|
11670
|
+
const [failure, setFailure] = (0, import_react10.useState)(null);
|
|
11671
|
+
const words = LEAVE_WORDS[leave.policy.hostLeavePrompt];
|
|
11672
|
+
const run = (work, leaving) => {
|
|
11673
|
+
setFailure(null);
|
|
11674
|
+
void work().then(() => {
|
|
11675
|
+
setOpen("none");
|
|
11676
|
+
if (leaving) onLeave?.();
|
|
11677
|
+
}).catch((error) => setFailure(error instanceof Error ? error.message : String(error)));
|
|
11678
|
+
};
|
|
11679
|
+
const leaveNow = () => run(leave.leave, true);
|
|
11680
|
+
const endNow = () => run(leave.endForAll, false);
|
|
11681
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { className: "mx-meet__anchor", children: [
|
|
11682
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
11683
|
+
ControlButton,
|
|
11684
|
+
{
|
|
11685
|
+
label: "Leave",
|
|
11686
|
+
danger: true,
|
|
11687
|
+
active: open !== "none",
|
|
11688
|
+
onClick: () => {
|
|
11689
|
+
if (leave.choices.length === 1 && leave.choices[0] === "leave") leaveNow();
|
|
11690
|
+
else setOpen(open === "none" ? "menu" : "none");
|
|
11691
|
+
},
|
|
11692
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(PhoneOffIcon, {})
|
|
11693
|
+
}
|
|
11694
|
+
),
|
|
11695
|
+
open !== "none" ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Popover, { label: open === "assign" ? words.assign_and_leave : "Leave", onClose: () => setOpen("none"), children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "mx-meet__menu", "data-meet-leave-menu": leave.policy.hostLeavePrompt, children: [
|
|
11696
|
+
open === "menu" ? leave.choices.map((choice) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
11697
|
+
"button",
|
|
11592
11698
|
{
|
|
11593
|
-
|
|
11594
|
-
danger:
|
|
11699
|
+
type: "button",
|
|
11700
|
+
className: choice === "end_for_all" ? "mx-meet__menu-item mx-meet__menu-item--danger" : "mx-meet__menu-item",
|
|
11595
11701
|
onClick: () => {
|
|
11596
|
-
|
|
11597
|
-
|
|
11702
|
+
if (choice === "leave") leaveNow();
|
|
11703
|
+
else if (choice === "assign_and_leave") setOpen("assign");
|
|
11704
|
+
else if (leave.policy.endConfirm) setOpen("confirm");
|
|
11705
|
+
else endNow();
|
|
11598
11706
|
},
|
|
11599
|
-
children:
|
|
11600
|
-
}
|
|
11601
|
-
|
|
11602
|
-
|
|
11707
|
+
children: words[choice]
|
|
11708
|
+
},
|
|
11709
|
+
choice
|
|
11710
|
+
)) : null,
|
|
11711
|
+
open === "assign" ? leave.candidates.map((person) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
11712
|
+
"button",
|
|
11713
|
+
{
|
|
11714
|
+
type: "button",
|
|
11715
|
+
className: "mx-meet__menu-item",
|
|
11716
|
+
onClick: () => run(() => leave.assignAndLeave(person), true),
|
|
11717
|
+
children: person.displayName
|
|
11718
|
+
},
|
|
11719
|
+
String(person.identity)
|
|
11720
|
+
)) : null,
|
|
11721
|
+
open === "confirm" ? /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
|
|
11722
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { type: "button", className: "mx-meet__menu-item mx-meet__menu-item--danger", onClick: endNow, children: words.end_for_all }),
|
|
11723
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { type: "button", className: "mx-meet__menu-item", onClick: () => setOpen("menu"), children: "Cancel" })
|
|
11724
|
+
] }) : null,
|
|
11725
|
+
failure !== null ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { className: "mx-meet__menu-note", role: "alert", children: failure }) : null
|
|
11726
|
+
] }) }) : null
|
|
11603
11727
|
] });
|
|
11604
11728
|
}
|
|
11605
11729
|
function useUnreadChat(open) {
|