@ai-matrx/meet 0.11.46 → 0.11.48

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.
@@ -394,17 +394,34 @@ function recordingLabel(state) {
394
394
 
395
395
  // src/core/live-policy.ts
396
396
  var DEFAULT_LIVE_POLICY = {
397
+ headlessPowers: "members_admit",
397
398
  chatMode: "everyone",
398
399
  avLock: "off",
399
400
  timeLimitMinutes: 0,
400
401
  timeLimitWarningMinutes: 5,
401
- recordingConsent: "notice"
402
+ recordingConsent: "notice",
403
+ shareWhoCan: "everyone",
404
+ shareConcurrency: "one_takeover"
402
405
  };
403
406
  var MODERATORS = /* @__PURE__ */ new Set(["host", "cohost"]);
404
407
  function isLockedFor(policy, role, kind) {
405
408
  if (MODERATORS.has(role)) return false;
406
409
  return policy.avLock === "both" || (kind === "microphone" ? policy.avLock === "mic" : policy.avLock === "camera");
407
410
  }
411
+ function canShareUnder(policy, role, standing) {
412
+ if (role === "viewer") return false;
413
+ if (MODERATORS.has(role)) return true;
414
+ switch (policy.shareWhoCan) {
415
+ case "host_only":
416
+ return false;
417
+ case "presenters":
418
+ return role === "presenter";
419
+ case "org":
420
+ return role === "presenter" || standing === "member";
421
+ default:
422
+ return true;
423
+ }
424
+ }
408
425
  function consentPending(policy, role, recordingOn, given) {
409
426
  return recordingOn && !given && policy.recordingConsent !== "notice" && !MODERATORS.has(role);
410
427
  }
@@ -416,6 +433,12 @@ function minutesLeft(policy, startedAtMs, nowMs) {
416
433
  if (policy.timeLimitMinutes <= 0 || startedAtMs === null) return null;
417
434
  return Math.ceil((startedAtMs + policy.timeLimitMinutes * 6e4 - nowMs) / 6e4);
418
435
  }
436
+ function mayAdmitNow(role, participants, policy) {
437
+ if (role === "none") return false;
438
+ if (role === "host" || role === "cohost") return true;
439
+ const hostIn = participants.some((p) => p.role === "host" || p.role === "cohost");
440
+ return !hostIn && policy.headlessPowers === "members_admit";
441
+ }
419
442
 
420
443
  // src/core/attendance.ts
421
444
  var IDLE_PREVIEW = { state: "idle", unavailable: null };
@@ -753,7 +776,7 @@ function observe(input) {
753
776
  const phase = observedPhase(input);
754
777
  const inCall = phase === "in_call";
755
778
  const actual = inCall && snapshot !== null ? effectiveRole(snapshot) : "none";
756
- const canAdmit = actual !== "none" && ADMITTER_ROLES.has(actual);
779
+ const canAdmit = snapshot !== null && mayAdmitNow(actual, snapshot.participants, snapshot.livePolicy);
757
780
  const role = actual === "none" || snapshot === null ? "none" : observedRole(actual, effectiveStanding(snapshot));
758
781
  const deviceInPlay = phase === "joining" || inCall;
759
782
  const region = snapshot?.mediaRegion;
@@ -1114,6 +1137,7 @@ async function moveToRoom(runtime, roomId) {
1114
1137
  if (runtime === null) return { ok: false, message: "This meeting is not connected." };
1115
1138
  try {
1116
1139
  await runtime.engine.moveToBreakout(roomId);
1140
+ await runtime.inRoom.loadCollab?.().catch(() => void 0);
1117
1141
  return { ok: true, message: roomId === null ? "Back in the main room." : "Joined the breakout room." };
1118
1142
  } catch (error) {
1119
1143
  return { ok: false, message: error.message ?? "That room could not be joined." };
@@ -1283,6 +1307,13 @@ function requireSession(session, action) {
1283
1307
  if (session === null) throw noRuntime(action);
1284
1308
  return session;
1285
1309
  }
1310
+ function otherPresenterOf(snapshot) {
1311
+ if (snapshot === null || snapshot.presenter === null || snapshot.presenter === snapshot.localIdentity) return null;
1312
+ const rule = snapshot.livePolicy.shareConcurrency;
1313
+ if (rule === "multiple") return null;
1314
+ const who = snapshot.participants.find((p) => p.identity === snapshot.presenter);
1315
+ return who === void 0 ? null : { name: who.displayName, rule };
1316
+ }
1286
1317
  function useRoom() {
1287
1318
  const host = useMeetHost();
1288
1319
  const snapshot = useMeetSnapshot();
@@ -1297,7 +1328,9 @@ function useRoom() {
1297
1328
  secondDevice: snapshot?.secondDevice ?? null,
1298
1329
  local,
1299
1330
  // An observer (role `viewer`) publishes nothing: no Share screen to press and be refused (MD-16).
1300
- canShareScreen: (engine?.supportsScreenShare() ?? true) && !isObserverRole(snapshot?.grants.role ?? null),
1331
+ sharingAllowed: snapshot === null || canShareUnder(snapshot.livePolicy, effectiveRole(snapshot), effectiveStanding(snapshot)),
1332
+ canShareScreen: (engine?.supportsScreenShare() ?? true) && !isObserverRole(snapshot?.grants.role ?? null) && (snapshot === null || canShareUnder(snapshot.livePolicy, effectiveRole(snapshot), effectiveStanding(snapshot))),
1333
+ otherPresenter: otherPresenterOf(snapshot),
1301
1334
  join: (0, import_react5.useCallback)(
1302
1335
  async (request) => requireEngine(engine, "join").join(request),
1303
1336
  [engine]
@@ -1395,7 +1428,7 @@ function useLobby() {
1395
1428
  declined: (snapshot?.lobby ?? []).filter((entry) => entry.state === "denied"),
1396
1429
  // No runtime means no host powers — `<LobbyPanel>` renders nothing rather
1397
1430
  // than an admit button that would throw.
1398
- canAdmit: session !== null && isHost,
1431
+ canAdmit: session !== null && (isHost || snapshot !== null && snapshot.phase === "in_call" && mayAdmitNow(snapshot.participants.find((p) => p.identity === snapshot.localIdentity)?.role ?? "none", snapshot.participants, snapshot.livePolicy)),
1399
1432
  admit: (0, import_react5.useCallback)(
1400
1433
  async (entry) => requireSession(session, "admit").admit(entry, true),
1401
1434
  [session]
@@ -3509,7 +3542,8 @@ function ParticipantTile({
3509
3542
  participant.isLocal ? " (you)" : "",
3510
3543
  participant.isAgent ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "mx-meet__badge", children: "AI" }) : null,
3511
3544
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(RoleBadge, { role: participant.role, standing: participant.standing }),
3512
- participant.reconnecting === true ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "mx-meet__badge", children: "Reconnecting\u2026" }) : null
3545
+ participant.reconnecting === true ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "mx-meet__badge", children: "Reconnecting\u2026" }) : null,
3546
+ participant.reconnecting !== true && participant.videoPaused === true && !participant.isLocal ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "mx-meet__badge", "data-meet-video-paused": "", children: "Video paused to save audio" }) : null
3513
3547
  ] }),
3514
3548
  !participant.micEnabled ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(MicOffIcon, { width: 14, height: 14 }) : null,
3515
3549
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(QualityBars, { quality: participant.quality })
@@ -3599,6 +3633,8 @@ function ControlBar({ onLeave }) {
3599
3633
  const queue = useHandQueue();
3600
3634
  const micUnavailable = useLocalMedia().microphone.unavailable;
3601
3635
  const live = useLivePolicy();
3636
+ const core = useMeetCore();
3637
+ const [askPresentInstead, setAskPresentInstead] = (0, import_react10.useState)(false);
3602
3638
  const unread = useUnreadChat(panel === "chat");
3603
3639
  const [whiteboard, setWhiteboard] = (0, import_react10.useState)(false);
3604
3640
  const app = useMeetAppPanel();
@@ -3687,15 +3723,39 @@ function ControlBar({ onLeave }) {
3687
3723
  }
3688
3724
  ),
3689
3725
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(CaptionsControl, {}),
3690
- room.canShareScreen || room.local?.screenSharing === true ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
3691
- ControlButton,
3692
- {
3693
- label: room.local?.screenSharing === true ? "Stop sharing" : "Share screen",
3694
- active: room.local?.screenSharing === true,
3695
- onClick: () => void room.toggleScreenShare(),
3696
- children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(ScreenShareIcon, {})
3697
- }
3698
- ) : null,
3726
+ room.canShareScreen || room.local?.screenSharing === true && core.share.state !== "ended" ? /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { className: "mx-meet__anchor", children: [
3727
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
3728
+ ControlButton,
3729
+ {
3730
+ label: room.local?.screenSharing === true ? "Stop sharing" : "Share screen",
3731
+ active: room.local?.screenSharing === true,
3732
+ onClick: () => {
3733
+ if (room.local?.screenSharing !== true && room.otherPresenter !== null) setAskPresentInstead(true);
3734
+ else void room.toggleScreenShare();
3735
+ },
3736
+ children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(ScreenShareIcon, {})
3737
+ }
3738
+ ),
3739
+ askPresentInstead && room.otherPresenter !== null ? /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "mx-meet__confirm", role: "group", "aria-label": "Someone else is presenting", "data-meet-notice": "share-takeover", children: [
3740
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { className: "mx-meet__confirm-text", children: [
3741
+ room.otherPresenter.name,
3742
+ " is presenting"
3743
+ ] }),
3744
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
3745
+ "button",
3746
+ {
3747
+ type: "button",
3748
+ className: "mx-meet__primary mx-meet__primary--small",
3749
+ onClick: () => {
3750
+ setAskPresentInstead(false);
3751
+ void room.toggleScreenShare();
3752
+ },
3753
+ children: room.otherPresenter.rule === "one_takeover" ? "Present instead" : "Share anyway"
3754
+ }
3755
+ ),
3756
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { type: "button", className: "mx-meet__link", onClick: () => setAskPresentInstead(false), children: "Cancel" })
3757
+ ] }) : null
3758
+ ] }) : null,
3699
3759
  /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { className: "mx-meet__anchor", children: [
3700
3760
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
3701
3761
  ControlButton,
@@ -3792,7 +3852,7 @@ function ControlBar({ onLeave }) {
3792
3852
  ]
3793
3853
  }
3794
3854
  ),
3795
- !room.canShareScreen && room.local?.screenSharing !== true ? (
3855
+ !room.canShareScreen && room.sharingAllowed && room.local?.screenSharing !== true ? (
3796
3856
  // `share-unsupported-mobile`: the control is absent; the menu says where it lives.
3797
3857
  /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("p", { className: "mx-meet__menu-note", role: "note", "data-meet-notice": "share-unsupported", children: [
3798
3858
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(ScreenShareIcon, { width: 16, height: 16 }),
@@ -5797,13 +5857,53 @@ function MeetSkinRoom({
5797
5857
  ] }),
5798
5858
  core.connectionBanner && core.connection !== "stable" ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { className: "mx-meet__banner", role: "status", "data-meet-notice": `connection-${core.connection}`, children: CONNECTION_BANNER[core.connection] }) : null,
5799
5859
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(ShareNotice, {}),
5860
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(NetworkNotice, {}),
5800
5861
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Stage, {}),
5801
5862
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Captions, {}),
5802
5863
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(ControlBar, { ...onLeave !== void 0 ? { onLeave } : {} })
5803
5864
  ] });
5804
5865
  }
5866
+ var RESTRICTED_NOTICE_MS = 3e4;
5867
+ function NetworkNotice() {
5868
+ const { snapshot, connectionBanner } = useMeetCore();
5869
+ const room = useRoom();
5870
+ const network = snapshot?.localNetwork;
5871
+ const [closed, setClosed] = (0, import_react10.useState)(() => /* @__PURE__ */ new Set());
5872
+ const [restrictedSince, setRestrictedSince] = (0, import_react10.useState)(null);
5873
+ const [, setTick] = (0, import_react10.useState)(0);
5874
+ const restricted = network?.path === "restricted";
5875
+ (0, import_react10.useEffect)(() => {
5876
+ if (!restricted || restrictedSince !== null) return;
5877
+ setRestrictedSince(Date.now());
5878
+ const timer = setTimeout(() => setTick((n) => n + 1), RESTRICTED_NOTICE_MS + 50);
5879
+ return () => clearTimeout(timer);
5880
+ }, [restricted, restrictedSince]);
5881
+ if (network === void 0 || snapshot?.phase !== "in_call" || connectionBanner) return null;
5882
+ const close = (code) => () => setClosed((prev) => /* @__PURE__ */ new Set([...prev, code]));
5883
+ const cameraOn = room.local?.cameraEnabled === true;
5884
+ if (network.videoPaused && !cameraOn) {
5885
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("p", { className: "mx-meet__banner", role: "status", "data-meet-notice": "video-paused", children: [
5886
+ "Your video is paused to save your audio",
5887
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { type: "button", className: "mx-meet__link", onClick: () => void room.toggleCamera(), children: "Try video again" })
5888
+ ] });
5889
+ }
5890
+ if (network.quality === "poor" && !closed.has("connection-poor")) {
5891
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("p", { className: "mx-meet__banner", role: "status", "data-meet-notice": "connection-poor", children: [
5892
+ "Your connection is unstable",
5893
+ cameraOn ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { type: "button", className: "mx-meet__link", onClick: () => void room.toggleCamera(), children: "Turn off camera" }) : null,
5894
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { type: "button", className: "mx-meet__link", onClick: close("connection-poor"), children: "Dismiss" })
5895
+ ] });
5896
+ }
5897
+ if (restricted && !closed.has("restricted-network") && restrictedSince !== null && Date.now() - restrictedSince < RESTRICTED_NOTICE_MS) {
5898
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("p", { className: "mx-meet__banner", role: "status", "data-meet-notice": "restricted-network", children: [
5899
+ "Restricted network \xB7 quality may be reduced",
5900
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { type: "button", className: "mx-meet__link", onClick: close("restricted-network"), children: "Dismiss" })
5901
+ ] });
5902
+ }
5903
+ return null;
5904
+ }
5805
5905
  function ShareNotice() {
5806
- const { share, actions } = useMeetCore();
5906
+ const { share, actions, snapshot } = useMeetCore();
5807
5907
  const [soundHintClosed, setSoundHintClosed] = (0, import_react10.useState)(null);
5808
5908
  if (share.state === "presenting" && share.audio === false && soundHintClosed !== share.at) {
5809
5909
  return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("p", { className: "mx-meet__banner", role: "status", "data-meet-notice": "share-audio-missing", children: [
@@ -5812,6 +5912,16 @@ function ShareNotice() {
5812
5912
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { type: "button", className: "mx-meet__link", onClick: () => setSoundHintClosed(share.at), children: "Dismiss" })
5813
5913
  ] });
5814
5914
  }
5915
+ if (share.state === "ended") {
5916
+ const replaced = share.reason === "replaced";
5917
+ const rule = snapshot?.livePolicy.shareWhoCan ?? "host_only";
5918
+ const why = replaced ? `${share.by ?? "Someone"} is now presenting` : rule === "presenters" ? "Sharing is turned off. Only presenters can share their screen" : rule === "org" ? "Sharing is turned off. Only people in the organization can share their screen" : "Sharing is turned off. Only the host can share their screen";
5919
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("p", { className: "mx-meet__banner", role: "status", "data-meet-notice": replaced ? "share-ended" : "share-unavailable", children: [
5920
+ replaced ? "Your presentation ended" : "Your presentation was stopped",
5921
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "mx-meet__remedy", children: why }),
5922
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { type: "button", className: "mx-meet__link", onClick: () => actions.dismissShareNotice(), children: "Dismiss" })
5923
+ ] });
5924
+ }
5815
5925
  if (share.state !== "refused") return null;
5816
5926
  const text = share.reason === "unsupported" ? "You can't share your screen from this browser" : share.reason === "blocked" ? "Screen sharing is blocked" : "Couldn't start sharing your screen";
5817
5927
  return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("p", { className: "mx-meet__banner", role: "status", "data-meet-notice": "share-unavailable", children: [
@@ -5995,8 +6105,8 @@ function ExitScreen({ onLeave }) {
5995
6105
  ] });
5996
6106
  break;
5997
6107
  case "unsupported":
5998
- title = "This browser can't join meetings";
5999
- line = "Open the link in Chrome, Edge, Firefox or Safari.";
6108
+ title = "Meet is not compatible with your browser yet";
6109
+ line = "Open this link in Chrome, Edge, Firefox or Safari.";
6000
6110
  break;
6001
6111
  case "ended":
6002
6112
  case "record":
@@ -6023,6 +6133,9 @@ function ExitScreen({ onLeave }) {
6023
6133
  function WaitingRoom({ host, onLeave }) {
6024
6134
  const { waiting, withdraw, askAgain } = useWaiting();
6025
6135
  const { phaseNotice } = useMeetCore();
6136
+ const localMedia = useLocalMedia();
6137
+ const { region: mediaRegion } = useMediaSupervisor();
6138
+ const noMicrophone = localMedia.microphone.unavailable !== null || mediaRegion.preview.microphone.state === "unavailable";
6026
6139
  const [busy, setBusy] = (0, import_react10.useState)(false);
6027
6140
  const admission = waiting?.admission ?? "knocking";
6028
6141
  const reknockLeft = useCountdown(admission === "denied" ? waiting?.reknockAt ?? null : null);
@@ -6087,6 +6200,7 @@ function WaitingRoom({ host, onLeave }) {
6087
6200
  children: line
6088
6201
  }
6089
6202
  ) : null,
6203
+ noMicrophone && (admission === "knocking" || admission === "waiting_host") ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { className: "mx-meet__empty-body", role: "status", "data-meet-notice": "joining-without-microphone", children: "You're joining without a microphone. You'll be able to listen, but others won't hear you." }) : null,
6090
6204
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "mx-meet__waiting-actions", children: actions })
6091
6205
  ] });
6092
6206
  }