@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.
@@ -164,11 +164,18 @@ var MeetError = class extends Error {
164
164
  /** What to DO. Rendered to the user for the device codes. */
165
165
  remedy;
166
166
  cause;
167
- constructor(code, message, remedy, cause) {
167
+ /**
168
+ * The join gate's reason code (CORE-DESIGN §2.5) when the server named one: `not_found`,
169
+ * `cancelled`, `ended`, `locked`, `removed`, `lobby_rejects`, `not_invited`… The phase
170
+ * renders `refused:<reason>` (or `ended`) from it — never from the HTTP status.
171
+ */
172
+ reason;
173
+ constructor(code, message, remedy, cause, reason) {
168
174
  super(message);
169
175
  this.name = "MeetError";
170
176
  this.code = code;
171
177
  this.remedy = remedy;
178
+ this.reason = reason ?? null;
172
179
  if (cause !== void 0) this.cause = cause;
173
180
  }
174
181
  /** True when retrying after the host re-establishes a session is the fix. */
@@ -241,7 +248,7 @@ var MEET_ROUTES = {
241
248
  /**
242
249
  * THE POST-MEETING RECORD FOR A READER WITH NO DATABASE (MRF-1). A `GET`,
243
250
  * authorized by the room token this device already holds, carried in the
244
- * `x-meet-room-token` header — never a query parameter, because a bearer
251
+ * `x-meet-pass` header — never a query parameter, because a bearer
245
252
  * credential in a URL lands in access logs, referrers and history.
246
253
  */
247
254
  record: "/api/v1/meet/record",
@@ -249,7 +256,7 @@ var MEET_ROUTES = {
249
256
  recordAccess: "/api/v1/meet/record/access",
250
257
  /**
251
258
  * KEEP YOUR NOTES (2026-10-01): a guest who just created an account presents
252
- * their kept pass in `x-meet-room-token` with their new session, and the
259
+ * their kept pass in `x-meet-pass` with their new session, and the
253
260
  * meeting they attended becomes theirs to read. Idempotent; never a grant.
254
261
  */
255
262
  claim: "/api/v1/meet/claim",
@@ -271,7 +278,7 @@ var MEET_ROUTES = {
271
278
  * Meet wave 2: EVERY in-meeting host power (mute, stop video, mute all, ask to
272
279
  * unmute, spotlight, lower hands, co-host, hand over, rename, remove, report).
273
280
  * Decided by the server from the meeting itself; a guest's pass rides
274
- * `x-meet-room-token`.
281
+ * `x-meet-pass`.
275
282
  */
276
283
  moderate: "/api/v1/meet/moderate",
277
284
  /** Meet wave 2: the SAVED chat — `POST` sends to everyone, `GET ?meeting_id=` reads history. */
@@ -282,7 +289,17 @@ var MEET_ROUTES = {
282
289
  */
283
290
  collab: "/api/v1/meet/collab",
284
291
  /** Meet wave 5: translate finished caption lines into this viewer's language. */
285
- captionsTranslate: "/api/v1/meet/captions/translate"
292
+ captionsTranslate: "/api/v1/meet/captions/translate",
293
+ /**
294
+ * S4 — THE STATE READ (CORE-DESIGN §2.6): `GET ?meeting_id=` with `x-meet-pass`. This
295
+ * person's admission (lazy expiry applied), whether anyone who can admit is here, the run's
296
+ * phase. Read every `waiting_poll_seconds` while waiting and on every `mx.state.changed`.
297
+ */
298
+ state: "/api/v1/meet/state",
299
+ /** S4: `withdraw` (stop waiting) or `leave` (close this tab); the pass may ride in the body. */
300
+ leave: "/api/v1/meet/leave",
301
+ /** S4: a pass holder (guests included) saw the recording notice, for this run. */
302
+ consent: "/api/v1/meet/consent"
286
303
  };
287
304
 
288
305
  // src/core/recording.ts
@@ -331,11 +348,15 @@ function listenOnlyNotice(media) {
331
348
 
332
349
  // src/core/types.ts
333
350
  var ROLE_RANK = {
334
- host: 3,
335
- cohost: 2,
351
+ host: 4,
352
+ cohost: 3,
353
+ presenter: 2,
336
354
  participant: 1,
337
- guest: 0
355
+ viewer: 0
338
356
  };
357
+ function isLinkFollower(standing) {
358
+ return standing === "guest" || standing === "outsider";
359
+ }
339
360
  function isHostRole(role) {
340
361
  return role === "host" || role === "cohost";
341
362
  }
@@ -413,9 +434,18 @@ function effectiveRole(snapshot) {
413
434
  const fromToken = snapshot.grants.role;
414
435
  return ROLE_RANK[fromRoster] >= ROLE_RANK[fromToken] ? fromRoster : fromToken;
415
436
  }
437
+ function effectiveStanding(snapshot) {
438
+ if (snapshot.grants.standing != null) return snapshot.grants.standing;
439
+ const me = snapshot.participants.find((p) => p.identity === snapshot.localIdentity);
440
+ return me?.standing ?? null;
441
+ }
416
442
  var REACTION_TTL_MS = 5e3;
417
443
 
418
444
  // src/core/observation.ts
445
+ function observedRole(role, standing) {
446
+ if (role === "host" || role === "cohost") return role;
447
+ return isLinkFollower(standing) ? "guest" : "participant";
448
+ }
419
449
  function observedDevice(track) {
420
450
  if (track.enabled) return "on";
421
451
  const refused = track.unavailable;
@@ -443,13 +473,15 @@ function observedPhase(input) {
443
473
  const raw = snapshot.phase;
444
474
  const live = raw === "connected" || raw === "reconnecting";
445
475
  if (endedRecord(snapshot, input.meeting ?? null) && !live) return "ended";
476
+ const reason = raw === "failed" ? snapshot.failure?.reason ?? null : null;
477
+ if (reason !== null) return reason === "ended" ? "ended" : `refused:${reason}`;
446
478
  switch (raw) {
447
479
  case "idle":
448
480
  case "failed":
449
481
  case "left":
450
482
  return "prejoin";
451
483
  case "lobby":
452
- return "waiting:knocking";
484
+ return waitingPhase(snapshot);
453
485
  case "requesting-token":
454
486
  case "connecting":
455
487
  return "joining";
@@ -458,20 +490,39 @@ function observedPhase(input) {
458
490
  return "in_call";
459
491
  }
460
492
  }
493
+ function waitingPhase(snapshot) {
494
+ switch (snapshot.waiting?.admission) {
495
+ case "waiting_host":
496
+ return "waiting:host_not_started";
497
+ case "denied":
498
+ return "denied";
499
+ case "expired":
500
+ return "knock_expired";
501
+ case "withdrawn":
502
+ return "left";
503
+ case "ended":
504
+ return "ended";
505
+ case "removed":
506
+ return "refused:removed";
507
+ default:
508
+ return "waiting:knocking";
509
+ }
510
+ }
461
511
  var ADMITTER_ROLES = /* @__PURE__ */ new Set(["host", "cohost"]);
462
512
  function observe(input) {
463
513
  const { snapshot } = input;
464
514
  const phase = observedPhase(input);
465
515
  const inCall = phase === "in_call";
466
- const role = inCall && snapshot !== null ? effectiveRole(snapshot) : "none";
467
- const canAdmit = role !== "none" && ADMITTER_ROLES.has(role);
516
+ const actual = inCall && snapshot !== null ? effectiveRole(snapshot) : "none";
517
+ const canAdmit = actual !== "none" && ADMITTER_ROLES.has(actual);
518
+ const role = actual === "none" || snapshot === null ? "none" : observedRole(actual, effectiveStanding(snapshot));
468
519
  const deviceInPlay = phase === "joining" || inCall;
469
520
  return {
470
521
  phase,
471
- reason: null,
522
+ reason: phase.startsWith("refused:") ? phase.slice("refused:".length) : null,
472
523
  role,
473
524
  connection: snapshot?.phase === "reconnecting" ? "reconnecting" : "stable",
474
- hostPresent: inCall && snapshot !== null ? snapshot.participants.some((p) => ADMITTER_ROLES.has(p.role)) : null,
525
+ hostPresent: inCall && snapshot !== null ? snapshot.participants.some((p) => ADMITTER_ROLES.has(p.role)) : snapshot?.phase === "lobby" ? snapshot.waiting?.admittersPresent ?? null : null,
475
526
  lobbyCount: canAdmit && snapshot !== null ? snapshot.lobby.filter((entry) => entry.state === "knocking").length : 0,
476
527
  recording: snapshot?.recording === "recording" || snapshot?.recording === "stopping",
477
528
  audioBlocked: null,
@@ -511,7 +562,7 @@ function participantAttributes(p) {
511
562
  "data-meet-participant": "",
512
563
  "data-meet-participant-identity": p.identity,
513
564
  "data-meet-participant-name": p.displayName,
514
- "data-meet-participant-role": p.role,
565
+ "data-meet-participant-role": observedRole(p.role, p.standing),
515
566
  "data-meet-participant-self": String(p.isLocal)
516
567
  };
517
568
  const quality = observedQuality(p.quality);
@@ -1004,12 +1055,22 @@ function useLobby() {
1004
1055
  async () => requireSession(session, "admitAll").admitAll(),
1005
1056
  [session]
1006
1057
  ),
1007
- knock: (0, import_react4.useCallback)(
1008
- (roomName) => requireSession(session, "knock").knock(roomName),
1058
+ undeny: (0, import_react4.useCallback)(
1059
+ async (entry) => requireSession(session, "undeny").undeny(entry),
1009
1060
  [session]
1010
1061
  )
1011
1062
  };
1012
1063
  }
1064
+ function useWaiting() {
1065
+ const host = useMeetHost();
1066
+ const snapshot = useMeetSnapshot();
1067
+ const session = host?.meetings ?? null;
1068
+ return {
1069
+ waiting: snapshot?.waiting ?? null,
1070
+ withdraw: (0, import_react4.useCallback)(async () => requireSession(session, "withdraw").withdraw(), [session]),
1071
+ askAgain: (0, import_react4.useCallback)(async () => requireSession(session, "askAgain").askAgain(), [session])
1072
+ };
1073
+ }
1013
1074
  function useRecording() {
1014
1075
  const host = useMeetHost();
1015
1076
  const snapshot = useMeetSnapshot();
@@ -2779,10 +2840,13 @@ function ConnectionDot({ quality }) {
2779
2840
  if (quality === "excellent" || quality === "good" || quality === "unknown") return null;
2780
2841
  return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: `mx-meet__quality mx-meet__quality--${quality}`, title: `Connection ${quality}`, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(WifiLowIcon, { width: 14, height: 14 }) });
2781
2842
  }
2782
- function RoleBadge({ role }) {
2843
+ function RoleBadge({
2844
+ role,
2845
+ standing
2846
+ }) {
2783
2847
  if (role === "host") return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "mx-meet__badge", children: "Host" });
2784
2848
  if (role === "cohost") return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "mx-meet__badge", children: "Co-host" });
2785
- if (role === "guest") return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "mx-meet__badge", children: "Guest" });
2849
+ if (isLinkFollower(standing)) return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "mx-meet__badge", children: "Guest" });
2786
2850
  return null;
2787
2851
  }
2788
2852
  function ParticipantTile({
@@ -2832,7 +2896,7 @@ function ParticipantTile({
2832
2896
  participant.displayName,
2833
2897
  participant.isLocal ? " (you)" : "",
2834
2898
  participant.isAgent ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "mx-meet__badge", children: "AI" }) : null,
2835
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(RoleBadge, { role: participant.role })
2899
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(RoleBadge, { role: participant.role, standing: participant.standing })
2836
2900
  ] }),
2837
2901
  !participant.micEnabled ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(MicOffIcon, { width: 14, height: 14 }) : null,
2838
2902
  /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(QualityBars, { quality: participant.quality })
@@ -3571,7 +3635,13 @@ function PeoplePanel({
3571
3635
  /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("span", { className: "mx-meet__person-name", children: [
3572
3636
  participant.displayName,
3573
3637
  participant.identity === local ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "mx-meet__you", children: " (you)" }) : null,
3574
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(RoleBadge, { role: participant.identity === local ? myRole : participant.role }),
3638
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
3639
+ RoleBadge,
3640
+ {
3641
+ role: participant.identity === local ? myRole : participant.role,
3642
+ standing: participant.standing
3643
+ }
3644
+ ),
3575
3645
  isNoteTaker(participant) ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "mx-meet__badge", children: "Meeting assistant" }) : null,
3576
3646
  snapshot?.spotlight === participant.identity ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "mx-meet__badge", children: "Spotlight" }) : null
3577
3647
  ] }),
@@ -4487,7 +4557,7 @@ function MeetSkinRoom({
4487
4557
  ) });
4488
4558
  }
4489
4559
  if (phase === "lobby") {
4490
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "mx-meet mx-meet--lobby", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(WaitingRoom, { roomName, host }) });
4560
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "mx-meet mx-meet--lobby", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(WaitingRoom, { host, ...onLeave !== void 0 ? { onLeave } : {} }) });
4491
4561
  }
4492
4562
  return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "mx-meet", children: [
4493
4563
  /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(ConsentNotice, {}),
@@ -4512,21 +4582,58 @@ function MeetSkinRoom({
4512
4582
  /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(ControlBar, { ...onLeave !== void 0 ? { onLeave } : {} })
4513
4583
  ] });
4514
4584
  }
4515
- function WaitingRoom({ roomName, host }) {
4516
- const knocked = (0, import_react9.useRef)(false);
4517
- (0, import_react9.useEffect)(() => {
4518
- if (knocked.current) return;
4519
- knocked.current = true;
4520
- host.meetings.knock(roomName);
4521
- }, [host, roomName]);
4585
+ function WaitingRoom({ host, onLeave }) {
4586
+ const { waiting, withdraw, askAgain } = useWaiting();
4587
+ const [busy, setBusy] = (0, import_react9.useState)(false);
4588
+ const admission = waiting?.admission ?? "knocking";
4589
+ const run = (action) => () => {
4590
+ setBusy(true);
4591
+ void action().catch(() => void 0).finally(() => setBusy(false));
4592
+ };
4593
+ const home = /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_design_system.Button, { type: "button", variant: "outline", onClick: () => onLeave !== void 0 ? onLeave() : window.location.assign("/"), children: "Back to home" });
4594
+ let title;
4595
+ let line = null;
4596
+ let actions;
4597
+ switch (admission) {
4598
+ case "waiting_host":
4599
+ title = "Waiting for the host to start";
4600
+ actions = /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_design_system.Button, { type: "button", variant: "outline", disabled: busy, onClick: run(withdraw), children: "Leave" });
4601
+ break;
4602
+ case "denied":
4603
+ title = "The host declined your request";
4604
+ actions = /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [
4605
+ waiting?.reknockAllowed === true ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_design_system.Button, { type: "button", disabled: busy, onClick: run(askAgain), children: "Ask to join again" }) : null,
4606
+ home
4607
+ ] });
4608
+ break;
4609
+ case "expired":
4610
+ title = "No one responded";
4611
+ actions = /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [
4612
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_design_system.Button, { type: "button", disabled: busy, onClick: run(askAgain), children: "Ask again" }),
4613
+ home
4614
+ ] });
4615
+ break;
4616
+ case "withdrawn":
4617
+ title = "You left the waiting room";
4618
+ actions = /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [
4619
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_design_system.Button, { type: "button", disabled: busy, onClick: run(askAgain), children: "Rejoin" }),
4620
+ home
4621
+ ] });
4622
+ break;
4623
+ case "ended":
4624
+ title = "The meeting has ended";
4625
+ actions = home;
4626
+ break;
4627
+ default:
4628
+ title = "Waiting for the host to let you in";
4629
+ line = waiting?.admittersPresent === false ? "The host isn't here yet" : null;
4630
+ actions = /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_design_system.Button, { type: "button", variant: "outline", disabled: busy, onClick: run(withdraw), children: "Cancel request" });
4631
+ }
4522
4632
  return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "mx-meet__waiting", children: [
4523
4633
  /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Avatar, { name: host.identity.displayName, url: host.identity.avatarUrl, size: 72 }),
4524
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("h1", { className: "mx-meet__prejoin-title", children: "Waiting for the host to let you in" }),
4525
- /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("p", { className: "mx-meet__empty-body", children: [
4526
- "You are in the waiting room as ",
4527
- host.identity.displayName,
4528
- ". You will join automatically once you are admitted."
4529
- ] })
4634
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("h1", { className: "mx-meet__prejoin-title", children: title }),
4635
+ line !== null ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { className: "mx-meet__empty-body", role: "status", "data-meet-notice": "host-absent", children: line }) : null,
4636
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "mx-meet__waiting-actions", children: actions })
4530
4637
  ] });
4531
4638
  }
4532
4639
  function HostMenu() {