@ai-matrx/meet 0.11.44 → 0.11.45

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/dist/react.cjs CHANGED
@@ -358,7 +358,14 @@ function createTabClaim(platform = webTabPlatform()) {
358
358
  if (message.kind === "tab-alive?" && mine !== null && message.tabId === mine && message.from !== self) {
359
359
  platform.bus?.post({ kind: "tab-alive", tabId: mine });
360
360
  } else if (message.kind === "who-is-in?" && current !== null && mine !== null && message.from !== self && message.room === current.room && message.person === current.person) {
361
- platform.bus?.post({ kind: "in-room", room: current.room, person: current.person, tabId: mine, to: message.from });
361
+ platform.bus?.post({
362
+ kind: "in-room",
363
+ room: current.room,
364
+ person: current.person,
365
+ tabId: mine,
366
+ to: message.from,
367
+ rule: current.rule
368
+ });
362
369
  }
363
370
  }) ?? (() => {
364
371
  });
@@ -388,28 +395,32 @@ function createTabClaim(platform = webTabPlatform()) {
388
395
  return candidate;
389
396
  }
390
397
  const tabId = claim();
398
+ async function presence(room, person) {
399
+ const bus = platform.bus;
400
+ if (bus === null) return null;
401
+ let seen = null;
402
+ const stop = bus.subscribe((message) => {
403
+ if (message.kind === "in-room" && message.to === self && message.room === room && message.person === person) {
404
+ seen = { rule: seen?.rule ?? message.rule ?? null };
405
+ }
406
+ });
407
+ bus.post({ kind: "who-is-in?", room, person, from: self });
408
+ await wait(TAB_ANSWER_MS);
409
+ stop();
410
+ return seen;
411
+ }
391
412
  return {
392
413
  tabId,
393
- inRoom(room, person) {
394
- current = { room, person };
414
+ inRoom(room, person, rule) {
415
+ current = { room, person, rule: rule ?? null };
395
416
  },
396
417
  left() {
397
418
  current = null;
398
419
  },
399
420
  async presentElsewhere(room, person) {
400
- const bus = platform.bus;
401
- if (bus === null) return false;
402
- let seen = false;
403
- const stop = bus.subscribe((message) => {
404
- if (message.kind === "in-room" && message.to === self && message.room === room && message.person === person) {
405
- seen = true;
406
- }
407
- });
408
- bus.post({ kind: "who-is-in?", room, person, from: self });
409
- await wait(TAB_ANSWER_MS);
410
- stop();
411
- return seen;
421
+ return await presence(room, person) !== null;
412
422
  },
423
+ presenceElsewhere: (room, person) => presence(room, person),
413
424
  dispose() {
414
425
  unsubscribe();
415
426
  }
@@ -587,7 +598,8 @@ function createWebResumeStorage() {
587
598
  }
588
599
  };
589
600
  }
590
- var CLEARS_RESUME = (phase) => phase === "left" || phase === "removed" || phase === "superseded" || phase === "inactive" || phase === "ended" || phase === "record" || phase === "prejoin" || phase.startsWith("refused:") || phase.startsWith("waiting:");
601
+ var CLEARS_RESUME = (phase) => phase === "left" || phase === "removed" || phase === "superseded" || phase === "inactive" || phase === "ended" || phase === "record" || phase === "prejoin" || phase === "denied" || phase === "knock_expired" || phase.startsWith("refused:");
602
+ var WAITING_RESUMES = (phase) => phase.startsWith("waiting:");
591
603
  function autoRejoinDelayMs(attempt, random = Math.random) {
592
604
  const base = Math.min(15e3, 1e3 * 2 ** Math.max(0, attempt));
593
605
  return Math.round(base * (0.5 + random()));
@@ -719,6 +731,10 @@ function createConnectionSupervisor(options) {
719
731
  rejoinAttempts = 0;
720
732
  stopRejoin();
721
733
  saveMarker(true);
734
+ } else if (WAITING_RESUMES(phase)) {
735
+ stopRejoin();
736
+ driverRegion = null;
737
+ saveMarker(true);
722
738
  } else if (phase === "disconnected") {
723
739
  driverRegion = null;
724
740
  saveMarker(true);
@@ -1239,6 +1255,7 @@ function parseRoomToken(body, roomName) {
1239
1255
  connectionIdentity: readString(payload, "connection_identity") ?? personKey,
1240
1256
  connectionKind: readString(payload, "connection_kind") === "companion" ? "companion" : "main",
1241
1257
  secondDeviceChoice,
1258
+ secondDeviceRule: readString(payload, "second_device_rule"),
1242
1259
  displayName: readString(payload, "display_name") ?? readString(payload, "displayName") ?? identity,
1243
1260
  expiresAt,
1244
1261
  canPublish: readBoolean(payload, "can_publish", true),
@@ -1952,6 +1969,12 @@ var JOINABLE_FROM = /* @__PURE__ */ new Set([
1952
1969
  // allows the press; the server's gate decides whether it knocks or is refused again.
1953
1970
  "refused:lobby_rejects"
1954
1971
  ]);
1972
+ var RETRYABLE_REFUSALS = /* @__PURE__ */ new Set([
1973
+ "refused:locked",
1974
+ "refused:too_early",
1975
+ "refused:meeting_full",
1976
+ "refused:platform_capacity"
1977
+ ]);
1955
1978
  var NOT_ENDABLE = /* @__PURE__ */ new Set([
1956
1979
  "ended",
1957
1980
  "record",
@@ -2017,7 +2040,7 @@ function nextPhase(from, event) {
2017
2040
  case "link_state":
2018
2041
  return from === "resolving" && event.reason !== null && isJoinRefusalReason(event.reason) ? refusalPhase(event.reason) : null;
2019
2042
  case "join":
2020
- return JOINABLE_FROM.has(from) ? "joining" : null;
2043
+ return JOINABLE_FROM.has(from) || RETRYABLE_REFUSALS.has(from) ? "joining" : null;
2021
2044
  case "auto_rejoin":
2022
2045
  return from === "disconnected" ? "joining" : null;
2023
2046
  case "resumed":
@@ -2859,6 +2882,10 @@ var RPCS = {
2859
2882
  admitAll: "meet_admit_all",
2860
2883
  setLock: "meet_set_lock",
2861
2884
  recordConsent: "meet_record_consent",
2885
+ /** One meeting rule as the gate obeys it (the knob ladder, this meeting's rung included). */
2886
+ meetingRule: "meet_policy",
2887
+ /** A host sets one rule for THIS meeting (the `meet_meeting` rung); refusals come back as `{ok:false}`. */
2888
+ setMeetingRule: "meet_policy_set",
2862
2889
  /*
2863
2890
  * ── MEET WAVE 1 — management (0.8). Every write is a SECURITY DEFINER door
2864
2891
  * authorized by the meeting's host or a real iam grant (editor to change,
@@ -2905,7 +2932,7 @@ var INVITEE_COLUMNS = "id,meeting_id,invitee_user_id,email,display_name,role,rsv
2905
2932
  var RECORDING_COLUMNS = "id,meeting_id,state,started_at,ended_at,started_by,file_id,failure_reason";
2906
2933
  var SEGMENT_COLUMNS = "id,meeting_id,identity,speaker,text,started_at,is_final,metadata";
2907
2934
  var NOTE_COLUMNS = "id,meeting_id,text,kind,assignee_user_id,created_at,metadata";
2908
- var ATTENDEE_COLUMNS = "id,meeting_id,identity,participant_user_id,display_name,avatar_url,role,admission_state,knocked_at,knock_expires_at,is_agent,joined_at,left_at,sessions:metadata->sessions";
2935
+ var ATTENDEE_COLUMNS = "id,meeting_id,identity,participant_user_id,display_name,avatar_url,role,admission_state,knocked_at,knock_expires_at,is_agent,joined_at,left_at,sessions:metadata->sessions,knock_flag:metadata->>knock_flag";
2909
2936
  function projectStays(value) {
2910
2937
  if (!Array.isArray(value)) return [];
2911
2938
  const stays = [];
@@ -3305,6 +3332,8 @@ function createMeetRepository(options) {
3305
3332
  admissionState: states.includes(admission) ? admission : "admitted",
3306
3333
  knockedAt: text(row, "knocked_at"),
3307
3334
  knockExpiresAt: text(row, "knock_expires_at"),
3335
+ // The list read selects `metadata->>knock_flag`; a Postgres Changes row carries `metadata`.
3336
+ knockFlag: text(row, "knock_flag") ?? (typeof row["metadata"] === "object" && row["metadata"] !== null ? text(row["metadata"], "knock_flag") : null),
3308
3337
  isAgent: bool(row, "is_agent", false),
3309
3338
  joinedAt: text(row, "joined_at"),
3310
3339
  leftAt: text(row, "left_at"),
@@ -3628,6 +3657,25 @@ function createMeetRepository(options) {
3628
3657
  );
3629
3658
  return projectMeeting(row);
3630
3659
  },
3660
+ async meetingRule(meetingId, key) {
3661
+ const response = await db.rpc(RPCS.meetingRule, { p_meeting_id: meetingId, p_key: key });
3662
+ if (response.error !== null) throw normalizeMeetError(response.error, "meet.meetingRule");
3663
+ return response.data;
3664
+ },
3665
+ async setMeetingRule(meetingId, key, value) {
3666
+ const response = await db.rpc(RPCS.setMeetingRule, { p_meeting_id: meetingId, p_key: key, p_value: value });
3667
+ if (response.error !== null) throw normalizeMeetError(response.error, "meet.setMeetingRule");
3668
+ const answer = response.data ?? {};
3669
+ if (answer["ok"] !== true) {
3670
+ throw new MeetError(
3671
+ "forbidden",
3672
+ typeof answer["detail"] === "string" ? answer["detail"] : "This meeting's rule was not changed.",
3673
+ "Only the meeting's host or a co-host sets its rules; an organization can lock a rule for everyone.",
3674
+ answer
3675
+ );
3676
+ }
3677
+ return answer["effective_value"];
3678
+ },
3631
3679
  async transcript(meetingId, limit = 1e3) {
3632
3680
  const response = await db.from(TABLES.transcripts).select(SEGMENT_COLUMNS).eq("meeting_id", meetingId).order("started_at", { ascending: true }).order("id", { ascending: true }).limit(limit);
3633
3681
  if (response.error !== null) throw normalizeMeetError(response.error, "meet.transcript");
@@ -4522,7 +4570,8 @@ function lobbyEntryFrom(attendee, now = Date.now()) {
4522
4570
  knockedAt: attendee.knockedAt ?? attendee.joinedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
4523
4571
  knockExpiresAt: attendee.knockExpiresAt ?? null,
4524
4572
  decidedAt: null,
4525
- decidedBy: null
4573
+ decidedBy: null,
4574
+ flag: attendee.knockFlag ?? null
4526
4575
  };
4527
4576
  }
4528
4577
  function deniedEntryFrom(attendee) {
@@ -5054,6 +5103,17 @@ function createMeetingSession(options) {
5054
5103
  async askAgain() {
5055
5104
  await options.rejoin?.();
5056
5105
  },
5106
+ async meetingRule(key) {
5107
+ const meeting = requireMeeting("meet.meetingRule");
5108
+ return repository.meetingRule(meeting.id, key);
5109
+ },
5110
+ async setMeetingRule(key, value) {
5111
+ requireHostUser("meet.setMeetingRule");
5112
+ const meeting = requireMeeting("meet.setMeetingRule");
5113
+ const effective = await repository.setMeetingRule(meeting.id, key, value);
5114
+ hint();
5115
+ return effective;
5116
+ },
5057
5117
  async setLock(locked) {
5058
5118
  const by = requireHostUser("meet.setLock");
5059
5119
  const meeting = requireMeeting("meet.setLock");
@@ -6319,7 +6379,7 @@ function createRoomEngine(options) {
6319
6379
  const { secondDevice: _answered, ...rest } = lastRequest;
6320
6380
  lastRequest = rest;
6321
6381
  }
6322
- options.tabClaim?.inRoom(String(request.roomName), String(token.identity));
6382
+ options.tabClaim?.inRoom(String(request.roomName), String(token.identity), token.secondDeviceRule ?? null);
6323
6383
  current = token;
6324
6384
  lastServerUrl = token.serverUrl;
6325
6385
  connectionPolicy = token.connectionPolicy ?? DEFAULT_CONNECTION_POLICY;
@@ -6631,10 +6691,12 @@ function createRoomEngine(options) {
6631
6691
  if (claim === void 0 || userId === null) return false;
6632
6692
  const before = store.snapshot();
6633
6693
  if (before.phase !== "prejoin" || before.secondDevice !== null) return false;
6634
- if (!await claim.presentElsewhere(String(roomName), `user:${String(userId)}`)) return false;
6694
+ const person = `user:${String(userId)}`;
6695
+ const found = claim.presenceElsewhere !== void 0 ? await claim.presenceElsewhere(String(roomName), person) : await claim.presentElsewhere(String(roomName), person) ? { rule: null } : null;
6696
+ if (found === null || found.rule === "allow_both") return false;
6635
6697
  const now2 = store.snapshot();
6636
6698
  if (now2.phase !== "prejoin" || now2.secondDevice !== null) return false;
6637
- store.patch({ secondDevice: { rule: "ask_switch_or_join" } });
6699
+ store.patch({ secondDevice: { rule: found.rule ?? "ask_switch_or_join" } });
6638
6700
  report("info", "meet: this browser already has you in this meeting \u2014 offering the choice before Join");
6639
6701
  return true;
6640
6702
  },
@@ -10439,6 +10501,10 @@ function actionsFor(host) {
10439
10501
  leave: () => engine.leave(),
10440
10502
  backToPrejoin: () => engine.backToPrejoin(),
10441
10503
  retry: () => {
10504
+ if (RETRYABLE_REFUSALS.has(store.snapshot().phase)) {
10505
+ void engine.rejoin().catch(() => void 0);
10506
+ return;
10507
+ }
10442
10508
  store.dispatch({ type: "retry", click: true });
10443
10509
  },
10444
10510
  resolved: () => {
@@ -12158,6 +12224,7 @@ function ControlBar({ onLeave }) {
12158
12224
  ]
12159
12225
  }
12160
12226
  ),
12227
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(WaitingRoomRule, {}),
12161
12228
  queue.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
12162
12229
  MenuItem,
12163
12230
  {
@@ -12377,6 +12444,58 @@ function MenuItem({
12377
12444
  }
12378
12445
  );
12379
12446
  }
12447
+ var LOBBY_BYPASS_CHOICES = [
12448
+ { value: "everyone", label: "Everyone" },
12449
+ { value: "org_and_invited", label: "Your organization and invited people" },
12450
+ { value: "org", label: "Your organization" },
12451
+ { value: "invited", label: "Invited people" },
12452
+ { value: "host_only", label: "Only hosts" }
12453
+ ];
12454
+ function WaitingRoomRule() {
12455
+ const host = useRequiredMeetHost();
12456
+ const [open, setOpen] = (0, import_react10.useState)(false);
12457
+ const [value, setValue] = (0, import_react10.useState)(null);
12458
+ const [notice, setNotice] = (0, import_react10.useState)(null);
12459
+ (0, import_react10.useEffect)(() => {
12460
+ let live = true;
12461
+ void host.meetings.meetingRule("lobby_bypass").then((rule) => {
12462
+ if (live) setValue(typeof rule === "string" ? rule : null);
12463
+ }).catch(() => void 0);
12464
+ return () => {
12465
+ live = false;
12466
+ };
12467
+ }, [host]);
12468
+ const current = LOBBY_BYPASS_CHOICES.find((choice) => choice.value === value)?.label ?? null;
12469
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
12470
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(MenuItem, { onSelect: () => setOpen(!open), children: [
12471
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(LockIcon, { width: 16, height: 16 }),
12472
+ " Who can skip the waiting room",
12473
+ current !== null ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "mx-meet__menu-value", children: current }) : null
12474
+ ] }),
12475
+ open ? /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { role: "group", "aria-label": "Who can skip the waiting room", "data-meet-rule": "lobby_bypass", children: [
12476
+ LOBBY_BYPASS_CHOICES.map((choice) => /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
12477
+ "button",
12478
+ {
12479
+ type: "button",
12480
+ role: "menuitemradio",
12481
+ "aria-checked": choice.value === value,
12482
+ className: "mx-meet__menu-item mx-meet__menu-item--sub",
12483
+ onClick: () => {
12484
+ setNotice(null);
12485
+ void host.meetings.setMeetingRule("lobby_bypass", choice.value).then(() => setValue(choice.value)).catch((error) => setNotice(error instanceof Error ? error.message : String(error)));
12486
+ },
12487
+ children: [
12488
+ choice.value === value ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(CheckIcon, { width: 14, height: 14 }) : null,
12489
+ " ",
12490
+ choice.label
12491
+ ]
12492
+ },
12493
+ choice.value
12494
+ )),
12495
+ notice !== null ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { className: "mx-meet__menu-note", role: "status", children: notice }) : null
12496
+ ] }) : null
12497
+ ] });
12498
+ }
12380
12499
  function Toast({ text: text3, failed, onDone }) {
12381
12500
  (0, import_react10.useEffect)(() => {
12382
12501
  const timer = setTimeout(onDone, failed ? 7e3 : 4e3);
@@ -13465,7 +13584,11 @@ function WaitingList({ lobby }) {
13465
13584
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Avatar, { name: entry.displayName, url: entry.avatarUrl, size: 32 }),
13466
13585
  /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { className: "mx-meet__person-name", children: [
13467
13586
  entry.displayName,
13468
- entry.userId === null ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "mx-meet__badge", children: "Guest" }) : null
13587
+ entry.userId === null ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "mx-meet__badge", children: "Guest" }) : null,
13588
+ entry.flag === "previously_removed" ? (
13589
+ // `mod-removed-bypass`: advice for the host, never a block — Admit stays.
13590
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "mx-meet__badge mx-meet__badge--warn", "data-meet-notice": "knock-flagged", children: "Previously removed" })
13591
+ ) : null
13469
13592
  ] }),
13470
13593
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(ControlButton, { label: `Admit ${entry.displayName}`, onClick: () => void lobby.admit(entry), children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(CheckIcon, { width: 16, height: 16 }) }),
13471
13594
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
@@ -14090,7 +14213,20 @@ function ExitScreen({ onLeave }) {
14090
14213
  const { phase, reason, host, snapshot, actions, call, connection } = useMeetCore();
14091
14214
  const [busy, setBusy] = (0, import_react10.useState)(false);
14092
14215
  const failure = snapshot?.failure ?? null;
14093
- const retryLeft = useCountdown(phase === "refused:lobby_rejects" ? failure?.retryAt ?? null : null);
14216
+ const retryLeft = useCountdown(
14217
+ phase === "refused:lobby_rejects" || phase === "refused:too_early" ? failure?.retryAt ?? null : null
14218
+ );
14219
+ const reaskedAtZero = (0, import_react10.useRef)(false);
14220
+ (0, import_react10.useEffect)(() => {
14221
+ if (phase !== "refused:too_early") {
14222
+ reaskedAtZero.current = false;
14223
+ return;
14224
+ }
14225
+ if (retryLeft === 0 && !reaskedAtZero.current) {
14226
+ reaskedAtZero.current = true;
14227
+ actions.retry();
14228
+ }
14229
+ }, [phase, retryLeft, actions]);
14094
14230
  const rejoin = /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
14095
14231
  import_design_system.Button,
14096
14232
  {
@@ -14128,11 +14264,19 @@ function ExitScreen({ onLeave }) {
14128
14264
  if (phase.startsWith("refused:")) {
14129
14265
  const code = phase.slice("refused:".length);
14130
14266
  title = REFUSED_TITLES[code] ?? "You can't join this meeting";
14131
- line = code === "too_early" || code === "registration_required" || code === "cancelled" ? failure?.remedy ?? null : null;
14267
+ line = code === "too_early" || code === "registration_required" || code === "cancelled" || code === "wrong_account" ? failure?.remedy ?? null : null;
14268
+ if (code === "too_early" && retryLeft !== null && retryLeft > 0) line = `Opens in ${clockText(retryLeft)}`;
14132
14269
  actionsNode = /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
14133
14270
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_design_system.Button, { type: "button", variant: "outline", onClick: actions.retry, children: "Try again" }),
14134
14271
  home
14135
14272
  ] });
14273
+ if (code === "sign_in_required" || code === "wrong_account") {
14274
+ const back = typeof window === "undefined" ? "/" : `${window.location.pathname}${window.location.search}`;
14275
+ actionsNode = /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
14276
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_design_system.Button, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("a", { href: `/login?redirectTo=${encodeURIComponent(back)}`, children: code === "sign_in_required" ? "Sign in" : "Switch account" }) }),
14277
+ home
14278
+ ] });
14279
+ }
14136
14280
  if (code === "lobby_rejects") {
14137
14281
  if (retryLeft !== null && retryLeft > 0) {
14138
14282
  line = `You can ask again in ${clockText(retryLeft)}`;