@ai-matrx/meet 0.8.1 → 0.10.0

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.
@@ -1,6 +1,6 @@
1
- export { A as AiPanel, a as AttendanceNotice, b as Avatar, C as Captions, c as ChatPanel, d as ConnectionDot, e as ConsentNotice, f as ControlBar, g as ControlButton, H as HostMenu, L as LobbyPanel, M as MeetSkinRoom, h as MeetingOverlays, i as MenuItem, P as PanelShell, j as ParticipantTile, k as PeoplePanel, l as PersonMenu, m as Popover, n as PreJoin, Q as QualityBars, R as ReactionOverlay, o as RecordingIndicator, S as SettingsPanel, p as ShortcutsSheet, q as Stage, T as Toast } from '../components-BOUmOiPj.cjs';
1
+ export { A as AiPanel, a as AttendanceNotice, b as Avatar, C as Captions, c as ChatPanel, d as ConnectionDot, e as ConsentNotice, f as ControlBar, g as ControlButton, H as HostMenu, L as LobbyPanel, M as MeetSkinRoom, h as MeetingOverlays, i as MenuItem, P as PanelShell, j as ParticipantTile, k as PeoplePanel, l as PersonMenu, m as Popover, n as PreJoin, Q as QualityBars, R as ReactionOverlay, o as RecordingIndicator, S as SettingsPanel, p as ShortcutsSheet, q as Stage, T as Toast } from '../components-DKHfkEVB.cjs';
2
2
  import { ReactNode, SVGProps } from 'react';
3
- import { C as CollabPanel } from '../collab-Bss-_Jfp.cjs';
3
+ import { C as CollabPanel } from '../collab-C2R-kQnV.cjs';
4
4
 
5
5
  /**
6
6
  * POLLS, Q&A, THE SHARED WHITEBOARD AND BREAKOUT ROOMS — the in-room surfaces (Meet wave 5).
@@ -1,6 +1,6 @@
1
- export { A as AiPanel, a as AttendanceNotice, b as Avatar, C as Captions, c as ChatPanel, d as ConnectionDot, e as ConsentNotice, f as ControlBar, g as ControlButton, H as HostMenu, L as LobbyPanel, M as MeetSkinRoom, h as MeetingOverlays, i as MenuItem, P as PanelShell, j as ParticipantTile, k as PeoplePanel, l as PersonMenu, m as Popover, n as PreJoin, Q as QualityBars, R as ReactionOverlay, o as RecordingIndicator, S as SettingsPanel, p as ShortcutsSheet, q as Stage, T as Toast } from '../components-BmgstFx_.js';
1
+ export { A as AiPanel, a as AttendanceNotice, b as Avatar, C as Captions, c as ChatPanel, d as ConnectionDot, e as ConsentNotice, f as ControlBar, g as ControlButton, H as HostMenu, L as LobbyPanel, M as MeetSkinRoom, h as MeetingOverlays, i as MenuItem, P as PanelShell, j as ParticipantTile, k as PeoplePanel, l as PersonMenu, m as Popover, n as PreJoin, Q as QualityBars, R as ReactionOverlay, o as RecordingIndicator, S as SettingsPanel, p as ShortcutsSheet, q as Stage, T as Toast } from '../components-CMtSekZS.js';
2
2
  import { ReactNode, SVGProps } from 'react';
3
- import { C as CollabPanel } from '../collab-Bss-_Jfp.js';
3
+ import { C as CollabPanel } from '../collab-C2R-kQnV.js';
4
4
 
5
5
  /**
6
6
  * POLLS, Q&A, THE SHARED WHITEBOARD AND BREAKOUT ROOMS — the in-room surfaces (Meet wave 5).
@@ -59,11 +59,18 @@ var MeetError = class extends Error {
59
59
  /** What to DO. Rendered to the user for the device codes. */
60
60
  remedy;
61
61
  cause;
62
- constructor(code, message, remedy, cause) {
62
+ /**
63
+ * The join gate's reason code (CORE-DESIGN §2.5) when the server named one: `not_found`,
64
+ * `cancelled`, `ended`, `locked`, `removed`, `lobby_rejects`, `not_invited`… The phase
65
+ * renders `refused:<reason>` (or `ended`) from it — never from the HTTP status.
66
+ */
67
+ reason;
68
+ constructor(code, message, remedy, cause, reason) {
63
69
  super(message);
64
70
  this.name = "MeetError";
65
71
  this.code = code;
66
72
  this.remedy = remedy;
73
+ this.reason = reason ?? null;
67
74
  if (cause !== void 0) this.cause = cause;
68
75
  }
69
76
  /** True when retrying after the host re-establishes a session is the fix. */
@@ -136,7 +143,7 @@ var MEET_ROUTES = {
136
143
  /**
137
144
  * THE POST-MEETING RECORD FOR A READER WITH NO DATABASE (MRF-1). A `GET`,
138
145
  * authorized by the room token this device already holds, carried in the
139
- * `x-meet-room-token` header — never a query parameter, because a bearer
146
+ * `x-meet-pass` header — never a query parameter, because a bearer
140
147
  * credential in a URL lands in access logs, referrers and history.
141
148
  */
142
149
  record: "/api/v1/meet/record",
@@ -144,7 +151,7 @@ var MEET_ROUTES = {
144
151
  recordAccess: "/api/v1/meet/record/access",
145
152
  /**
146
153
  * KEEP YOUR NOTES (2026-10-01): a guest who just created an account presents
147
- * their kept pass in `x-meet-room-token` with their new session, and the
154
+ * their kept pass in `x-meet-pass` with their new session, and the
148
155
  * meeting they attended becomes theirs to read. Idempotent; never a grant.
149
156
  */
150
157
  claim: "/api/v1/meet/claim",
@@ -166,7 +173,7 @@ var MEET_ROUTES = {
166
173
  * Meet wave 2: EVERY in-meeting host power (mute, stop video, mute all, ask to
167
174
  * unmute, spotlight, lower hands, co-host, hand over, rename, remove, report).
168
175
  * Decided by the server from the meeting itself; a guest's pass rides
169
- * `x-meet-room-token`.
176
+ * `x-meet-pass`.
170
177
  */
171
178
  moderate: "/api/v1/meet/moderate",
172
179
  /** Meet wave 2: the SAVED chat — `POST` sends to everyone, `GET ?meeting_id=` reads history. */
@@ -177,7 +184,17 @@ var MEET_ROUTES = {
177
184
  */
178
185
  collab: "/api/v1/meet/collab",
179
186
  /** Meet wave 5: translate finished caption lines into this viewer's language. */
180
- captionsTranslate: "/api/v1/meet/captions/translate"
187
+ captionsTranslate: "/api/v1/meet/captions/translate",
188
+ /**
189
+ * S4 — THE STATE READ (CORE-DESIGN §2.6): `GET ?meeting_id=` with `x-meet-pass`. This
190
+ * person's admission (lazy expiry applied), whether anyone who can admit is here, the run's
191
+ * phase. Read every `waiting_poll_seconds` while waiting and on every `mx.state.changed`.
192
+ */
193
+ state: "/api/v1/meet/state",
194
+ /** S4: `withdraw` (stop waiting) or `leave` (close this tab); the pass may ride in the body. */
195
+ leave: "/api/v1/meet/leave",
196
+ /** S4: a pass holder (guests included) saw the recording notice, for this run. */
197
+ consent: "/api/v1/meet/consent"
181
198
  };
182
199
 
183
200
  // src/core/recording.ts
@@ -226,11 +243,15 @@ function listenOnlyNotice(media) {
226
243
 
227
244
  // src/core/types.ts
228
245
  var ROLE_RANK = {
229
- host: 3,
230
- cohost: 2,
246
+ host: 4,
247
+ cohost: 3,
248
+ presenter: 2,
231
249
  participant: 1,
232
- guest: 0
250
+ viewer: 0
233
251
  };
252
+ function isLinkFollower(standing) {
253
+ return standing === "guest" || standing === "outsider";
254
+ }
234
255
  function isHostRole(role) {
235
256
  return role === "host" || role === "cohost";
236
257
  }
@@ -308,9 +329,18 @@ function effectiveRole(snapshot) {
308
329
  const fromToken = snapshot.grants.role;
309
330
  return ROLE_RANK[fromRoster] >= ROLE_RANK[fromToken] ? fromRoster : fromToken;
310
331
  }
332
+ function effectiveStanding(snapshot) {
333
+ if (snapshot.grants.standing != null) return snapshot.grants.standing;
334
+ const me = snapshot.participants.find((p) => p.identity === snapshot.localIdentity);
335
+ return me?.standing ?? null;
336
+ }
311
337
  var REACTION_TTL_MS = 5e3;
312
338
 
313
339
  // src/core/observation.ts
340
+ function observedRole(role, standing) {
341
+ if (role === "host" || role === "cohost") return role;
342
+ return isLinkFollower(standing) ? "guest" : "participant";
343
+ }
314
344
  function observedDevice(track) {
315
345
  if (track.enabled) return "on";
316
346
  const refused = track.unavailable;
@@ -338,13 +368,15 @@ function observedPhase(input) {
338
368
  const raw = snapshot.phase;
339
369
  const live = raw === "connected" || raw === "reconnecting";
340
370
  if (endedRecord(snapshot, input.meeting ?? null) && !live) return "ended";
371
+ const reason = raw === "failed" ? snapshot.failure?.reason ?? null : null;
372
+ if (reason !== null) return reason === "ended" ? "ended" : `refused:${reason}`;
341
373
  switch (raw) {
342
374
  case "idle":
343
375
  case "failed":
344
376
  case "left":
345
377
  return "prejoin";
346
378
  case "lobby":
347
- return "waiting:knocking";
379
+ return waitingPhase(snapshot);
348
380
  case "requesting-token":
349
381
  case "connecting":
350
382
  return "joining";
@@ -353,20 +385,39 @@ function observedPhase(input) {
353
385
  return "in_call";
354
386
  }
355
387
  }
388
+ function waitingPhase(snapshot) {
389
+ switch (snapshot.waiting?.admission) {
390
+ case "waiting_host":
391
+ return "waiting:host_not_started";
392
+ case "denied":
393
+ return "denied";
394
+ case "expired":
395
+ return "knock_expired";
396
+ case "withdrawn":
397
+ return "left";
398
+ case "ended":
399
+ return "ended";
400
+ case "removed":
401
+ return "refused:removed";
402
+ default:
403
+ return "waiting:knocking";
404
+ }
405
+ }
356
406
  var ADMITTER_ROLES = /* @__PURE__ */ new Set(["host", "cohost"]);
357
407
  function observe(input) {
358
408
  const { snapshot } = input;
359
409
  const phase = observedPhase(input);
360
410
  const inCall = phase === "in_call";
361
- const role = inCall && snapshot !== null ? effectiveRole(snapshot) : "none";
362
- const canAdmit = role !== "none" && ADMITTER_ROLES.has(role);
411
+ const actual = inCall && snapshot !== null ? effectiveRole(snapshot) : "none";
412
+ const canAdmit = actual !== "none" && ADMITTER_ROLES.has(actual);
413
+ const role = actual === "none" || snapshot === null ? "none" : observedRole(actual, effectiveStanding(snapshot));
363
414
  const deviceInPlay = phase === "joining" || inCall;
364
415
  return {
365
416
  phase,
366
- reason: null,
417
+ reason: phase.startsWith("refused:") ? phase.slice("refused:".length) : null,
367
418
  role,
368
419
  connection: snapshot?.phase === "reconnecting" ? "reconnecting" : "stable",
369
- hostPresent: inCall && snapshot !== null ? snapshot.participants.some((p) => ADMITTER_ROLES.has(p.role)) : null,
420
+ hostPresent: inCall && snapshot !== null ? snapshot.participants.some((p) => ADMITTER_ROLES.has(p.role)) : snapshot?.phase === "lobby" ? snapshot.waiting?.admittersPresent ?? null : null,
370
421
  lobbyCount: canAdmit && snapshot !== null ? snapshot.lobby.filter((entry) => entry.state === "knocking").length : 0,
371
422
  recording: snapshot?.recording === "recording" || snapshot?.recording === "stopping",
372
423
  audioBlocked: null,
@@ -406,7 +457,7 @@ function participantAttributes(p) {
406
457
  "data-meet-participant": "",
407
458
  "data-meet-participant-identity": p.identity,
408
459
  "data-meet-participant-name": p.displayName,
409
- "data-meet-participant-role": p.role,
460
+ "data-meet-participant-role": observedRole(p.role, p.standing),
410
461
  "data-meet-participant-self": String(p.isLocal)
411
462
  };
412
463
  const quality = observedQuality(p.quality);
@@ -912,12 +963,22 @@ function useLobby() {
912
963
  async () => requireSession(session, "admitAll").admitAll(),
913
964
  [session]
914
965
  ),
915
- knock: useCallback2(
916
- (roomName) => requireSession(session, "knock").knock(roomName),
966
+ undeny: useCallback2(
967
+ async (entry) => requireSession(session, "undeny").undeny(entry),
917
968
  [session]
918
969
  )
919
970
  };
920
971
  }
972
+ function useWaiting() {
973
+ const host = useMeetHost();
974
+ const snapshot = useMeetSnapshot();
975
+ const session = host?.meetings ?? null;
976
+ return {
977
+ waiting: snapshot?.waiting ?? null,
978
+ withdraw: useCallback2(async () => requireSession(session, "withdraw").withdraw(), [session]),
979
+ askAgain: useCallback2(async () => requireSession(session, "askAgain").askAgain(), [session])
980
+ };
981
+ }
921
982
  function useRecording() {
922
983
  const host = useMeetHost();
923
984
  const snapshot = useMeetSnapshot();
@@ -2687,10 +2748,13 @@ function ConnectionDot({ quality }) {
2687
2748
  if (quality === "excellent" || quality === "good" || quality === "unknown") return null;
2688
2749
  return /* @__PURE__ */ jsx5("span", { className: `mx-meet__quality mx-meet__quality--${quality}`, title: `Connection ${quality}`, children: /* @__PURE__ */ jsx5(WifiLowIcon, { width: 14, height: 14 }) });
2689
2750
  }
2690
- function RoleBadge({ role }) {
2751
+ function RoleBadge({
2752
+ role,
2753
+ standing
2754
+ }) {
2691
2755
  if (role === "host") return /* @__PURE__ */ jsx5("span", { className: "mx-meet__badge", children: "Host" });
2692
2756
  if (role === "cohost") return /* @__PURE__ */ jsx5("span", { className: "mx-meet__badge", children: "Co-host" });
2693
- if (role === "guest") return /* @__PURE__ */ jsx5("span", { className: "mx-meet__badge", children: "Guest" });
2757
+ if (isLinkFollower(standing)) return /* @__PURE__ */ jsx5("span", { className: "mx-meet__badge", children: "Guest" });
2694
2758
  return null;
2695
2759
  }
2696
2760
  function ParticipantTile({
@@ -2740,7 +2804,7 @@ function ParticipantTile({
2740
2804
  participant.displayName,
2741
2805
  participant.isLocal ? " (you)" : "",
2742
2806
  participant.isAgent ? /* @__PURE__ */ jsx5("span", { className: "mx-meet__badge", children: "AI" }) : null,
2743
- /* @__PURE__ */ jsx5(RoleBadge, { role: participant.role })
2807
+ /* @__PURE__ */ jsx5(RoleBadge, { role: participant.role, standing: participant.standing })
2744
2808
  ] }),
2745
2809
  !participant.micEnabled ? /* @__PURE__ */ jsx5(MicOffIcon, { width: 14, height: 14 }) : null,
2746
2810
  /* @__PURE__ */ jsx5(QualityBars, { quality: participant.quality })
@@ -3479,7 +3543,13 @@ function PeoplePanel({
3479
3543
  /* @__PURE__ */ jsxs3("span", { className: "mx-meet__person-name", children: [
3480
3544
  participant.displayName,
3481
3545
  participant.identity === local ? /* @__PURE__ */ jsx5("span", { className: "mx-meet__you", children: " (you)" }) : null,
3482
- /* @__PURE__ */ jsx5(RoleBadge, { role: participant.identity === local ? myRole : participant.role }),
3546
+ /* @__PURE__ */ jsx5(
3547
+ RoleBadge,
3548
+ {
3549
+ role: participant.identity === local ? myRole : participant.role,
3550
+ standing: participant.standing
3551
+ }
3552
+ ),
3483
3553
  isNoteTaker(participant) ? /* @__PURE__ */ jsx5("span", { className: "mx-meet__badge", children: "Meeting assistant" }) : null,
3484
3554
  snapshot?.spotlight === participant.identity ? /* @__PURE__ */ jsx5("span", { className: "mx-meet__badge", children: "Spotlight" }) : null
3485
3555
  ] }),
@@ -4395,7 +4465,7 @@ function MeetSkinRoom({
4395
4465
  ) });
4396
4466
  }
4397
4467
  if (phase === "lobby") {
4398
- return /* @__PURE__ */ jsx5("div", { className: "mx-meet mx-meet--lobby", children: /* @__PURE__ */ jsx5(WaitingRoom, { roomName, host }) });
4468
+ return /* @__PURE__ */ jsx5("div", { className: "mx-meet mx-meet--lobby", children: /* @__PURE__ */ jsx5(WaitingRoom, { host, ...onLeave !== void 0 ? { onLeave } : {} }) });
4399
4469
  }
4400
4470
  return /* @__PURE__ */ jsxs3("div", { className: "mx-meet", children: [
4401
4471
  /* @__PURE__ */ jsx5(ConsentNotice, {}),
@@ -4420,21 +4490,58 @@ function MeetSkinRoom({
4420
4490
  /* @__PURE__ */ jsx5(ControlBar, { ...onLeave !== void 0 ? { onLeave } : {} })
4421
4491
  ] });
4422
4492
  }
4423
- function WaitingRoom({ roomName, host }) {
4424
- const knocked = useRef6(false);
4425
- useEffect6(() => {
4426
- if (knocked.current) return;
4427
- knocked.current = true;
4428
- host.meetings.knock(roomName);
4429
- }, [host, roomName]);
4493
+ function WaitingRoom({ host, onLeave }) {
4494
+ const { waiting, withdraw, askAgain } = useWaiting();
4495
+ const [busy, setBusy] = useState4(false);
4496
+ const admission = waiting?.admission ?? "knocking";
4497
+ const run = (action) => () => {
4498
+ setBusy(true);
4499
+ void action().catch(() => void 0).finally(() => setBusy(false));
4500
+ };
4501
+ const home = /* @__PURE__ */ jsx5(Button, { type: "button", variant: "outline", onClick: () => onLeave !== void 0 ? onLeave() : window.location.assign("/"), children: "Back to home" });
4502
+ let title;
4503
+ let line = null;
4504
+ let actions;
4505
+ switch (admission) {
4506
+ case "waiting_host":
4507
+ title = "Waiting for the host to start";
4508
+ actions = /* @__PURE__ */ jsx5(Button, { type: "button", variant: "outline", disabled: busy, onClick: run(withdraw), children: "Leave" });
4509
+ break;
4510
+ case "denied":
4511
+ title = "The host declined your request";
4512
+ actions = /* @__PURE__ */ jsxs3(Fragment, { children: [
4513
+ waiting?.reknockAllowed === true ? /* @__PURE__ */ jsx5(Button, { type: "button", disabled: busy, onClick: run(askAgain), children: "Ask to join again" }) : null,
4514
+ home
4515
+ ] });
4516
+ break;
4517
+ case "expired":
4518
+ title = "No one responded";
4519
+ actions = /* @__PURE__ */ jsxs3(Fragment, { children: [
4520
+ /* @__PURE__ */ jsx5(Button, { type: "button", disabled: busy, onClick: run(askAgain), children: "Ask again" }),
4521
+ home
4522
+ ] });
4523
+ break;
4524
+ case "withdrawn":
4525
+ title = "You left the waiting room";
4526
+ actions = /* @__PURE__ */ jsxs3(Fragment, { children: [
4527
+ /* @__PURE__ */ jsx5(Button, { type: "button", disabled: busy, onClick: run(askAgain), children: "Rejoin" }),
4528
+ home
4529
+ ] });
4530
+ break;
4531
+ case "ended":
4532
+ title = "The meeting has ended";
4533
+ actions = home;
4534
+ break;
4535
+ default:
4536
+ title = "Waiting for the host to let you in";
4537
+ line = waiting?.admittersPresent === false ? "The host isn't here yet" : null;
4538
+ actions = /* @__PURE__ */ jsx5(Button, { type: "button", variant: "outline", disabled: busy, onClick: run(withdraw), children: "Cancel request" });
4539
+ }
4430
4540
  return /* @__PURE__ */ jsxs3("div", { className: "mx-meet__waiting", children: [
4431
4541
  /* @__PURE__ */ jsx5(Avatar, { name: host.identity.displayName, url: host.identity.avatarUrl, size: 72 }),
4432
- /* @__PURE__ */ jsx5("h1", { className: "mx-meet__prejoin-title", children: "Waiting for the host to let you in" }),
4433
- /* @__PURE__ */ jsxs3("p", { className: "mx-meet__empty-body", children: [
4434
- "You are in the waiting room as ",
4435
- host.identity.displayName,
4436
- ". You will join automatically once you are admitted."
4437
- ] })
4542
+ /* @__PURE__ */ jsx5("h1", { className: "mx-meet__prejoin-title", children: title }),
4543
+ line !== null ? /* @__PURE__ */ jsx5("p", { className: "mx-meet__empty-body", role: "status", "data-meet-notice": "host-absent", children: line }) : null,
4544
+ /* @__PURE__ */ jsx5("div", { className: "mx-meet__waiting-actions", children: actions })
4438
4545
  ] });
4439
4546
  }
4440
4547
  function HostMenu() {