@realtimexsco/live-chat 1.4.14 → 1.4.16

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/index.d.mts CHANGED
@@ -560,8 +560,8 @@ interface ChatFeatures {
560
560
  /** Show video call button in header (default: `true`) */
561
561
  showVideoCall?: boolean;
562
562
  /**
563
- * When `true`, hide phone/video buttons in group chats.
564
- * Default: `false` (buttons show in all chats).
563
+ * @deprecated Call permission gates from effective settings now apply the same
564
+ * in DM and group chats. Buttons stay visible; use showPhoneCall/showVideoCall to hide.
565
565
  */
566
566
  hideCallActionsInGroupChats?: boolean;
567
567
  /** Called when user taps voice call */
package/dist/index.d.ts CHANGED
@@ -560,8 +560,8 @@ interface ChatFeatures {
560
560
  /** Show video call button in header (default: `true`) */
561
561
  showVideoCall?: boolean;
562
562
  /**
563
- * When `true`, hide phone/video buttons in group chats.
564
- * Default: `false` (buttons show in all chats).
563
+ * @deprecated Call permission gates from effective settings now apply the same
564
+ * in DM and group chats. Buttons stay visible; use showPhoneCall/showVideoCall to hide.
565
565
  */
566
566
  hideCallActionsInGroupChats?: boolean;
567
567
  /** Called when user taps voice call */
package/dist/index.mjs CHANGED
@@ -6878,8 +6878,21 @@ function AdminPermissionTooltip({
6878
6878
  if (!disabled || !message) {
6879
6879
  return /* @__PURE__ */ jsx(Fragment, { children });
6880
6880
  }
6881
+ const gatedChildren = React3__default.Children.map(children, (child) => {
6882
+ if (!React3__default.isValidElement(child)) return child;
6883
+ return React3__default.cloneElement(child, {
6884
+ className: cn(child.props.className, "pointer-events-none")
6885
+ });
6886
+ });
6881
6887
  return /* @__PURE__ */ jsxs(Tooltip, { children: [
6882
- /* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx("span", { className: cn("inline-flex", className), children }) }),
6888
+ /* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
6889
+ "span",
6890
+ {
6891
+ className: cn("inline-flex cursor-not-allowed", className),
6892
+ tabIndex: 0,
6893
+ children: gatedChildren
6894
+ }
6895
+ ) }),
6883
6896
  /* @__PURE__ */ jsx(TooltipContent, { side, className: "max-w-[240px] text-xs", children: message })
6884
6897
  ] });
6885
6898
  }
@@ -7292,9 +7305,34 @@ var PushNotificationToggle = ({
7292
7305
  children
7293
7306
  }) => {
7294
7307
  const push = usePushNotifications();
7295
- const { getPushNotificationGate } = useEffectiveSettingsOptional();
7308
+ const { loading: settingsLoading, getPushNotificationGate } = useEffectiveSettingsOptional();
7296
7309
  const adminGate = getPushNotificationGate();
7297
7310
  const [isToggling, setIsToggling] = useState(false);
7311
+ const isBootstrapping = push.loading || settingsLoading;
7312
+ if (isBootstrapping && !push.supported) {
7313
+ return /* @__PURE__ */ jsxs(
7314
+ "div",
7315
+ {
7316
+ className: cn(
7317
+ "flex items-center justify-between gap-4 rounded-xl border border-(--chat-border) bg-(--chat-surface) px-4 py-3",
7318
+ className
7319
+ ),
7320
+ children: [
7321
+ /* @__PURE__ */ jsxs("div", { className: "space-y-0.5", children: [
7322
+ /* @__PURE__ */ jsx("p", { className: "text-sm font-medium text-(--chat-text)", children: label }),
7323
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-(--chat-muted)", children: "Loading\u2026" })
7324
+ ] }),
7325
+ /* @__PURE__ */ jsx(
7326
+ Loader2,
7327
+ {
7328
+ className: "size-5 shrink-0 animate-spin text-(--chat-theme)",
7329
+ "aria-label": "Loading push notification settings"
7330
+ }
7331
+ )
7332
+ ]
7333
+ }
7334
+ );
7335
+ }
7298
7336
  if (!push.supported) {
7299
7337
  return null;
7300
7338
  }
@@ -7302,8 +7340,8 @@ var PushNotificationToggle = ({
7302
7340
  const permissionBlocked = push.permission === "denied";
7303
7341
  const adminBlocked = !adminGate.enabled;
7304
7342
  const notConfigured = !push.configured;
7305
- const showLoader = isToggling;
7306
- const isDisabled = showLoader || permissionBlocked || push.loading || adminBlocked || notConfigured;
7343
+ const showLoader = isToggling || isBootstrapping;
7344
+ const isDisabled = showLoader || permissionBlocked || push.loading || settingsLoading || adminBlocked || notConfigured;
7307
7345
  const handleChange = async (checked) => {
7308
7346
  if (adminBlocked || notConfigured) return;
7309
7347
  setIsToggling(true);
@@ -8975,14 +9013,12 @@ var DefaultHeaderCallActions = ({
8975
9013
  onVideoCall,
8976
9014
  className
8977
9015
  }) => {
8978
- const { components, slotClassName, features } = useChatCustomizationOptional();
9016
+ const { components, slotClassName } = useChatCustomizationOptional();
8979
9017
  const { getAudioCallGate, getVideoCallGate } = useEffectiveSettingsOptional();
8980
9018
  const PhoneSlot = components.PhoneCallButton;
8981
9019
  const VideoSlot = components.VideoCallButton;
8982
- const hideInGroups = features.hideCallActionsInGroupChats ?? false;
8983
9020
  const audioGate = getAudioCallGate();
8984
9021
  const videoGate = getVideoCallGate();
8985
- if (hideInGroups && activeChannel.isGroup) return null;
8986
9022
  if (!showPhoneCall && !showVideoCall) return null;
8987
9023
  const phoneEnabled = showPhoneCall && audioGate.enabled;
8988
9024
  const videoEnabled = showVideoCall && videoGate.enabled;
@@ -8992,6 +9028,7 @@ var DefaultHeaderCallActions = ({
8992
9028
  {
8993
9029
  disabled: !audioGate.enabled,
8994
9030
  message: audioGate.disabledReason,
9031
+ side: "bottom",
8995
9032
  children: /* @__PURE__ */ jsx("span", { className: "inline-flex", children: /* @__PURE__ */ jsx(
8996
9033
  PhoneSlot,
8997
9034
  {
@@ -9001,7 +9038,7 @@ var DefaultHeaderCallActions = ({
9001
9038
  className: cn(
9002
9039
  className,
9003
9040
  slotClassName("phoneCallButton"),
9004
- !audioGate.enabled && "pointer-events-none opacity-40"
9041
+ !audioGate.enabled && "opacity-40"
9005
9042
  )
9006
9043
  }
9007
9044
  ) })
@@ -9011,6 +9048,7 @@ var DefaultHeaderCallActions = ({
9011
9048
  {
9012
9049
  disabled: !audioGate.enabled,
9013
9050
  message: audioGate.disabledReason,
9051
+ side: "bottom",
9014
9052
  children: /* @__PURE__ */ jsx(
9015
9053
  Button,
9016
9054
  {
@@ -9030,6 +9068,7 @@ var DefaultHeaderCallActions = ({
9030
9068
  {
9031
9069
  disabled: !videoGate.enabled,
9032
9070
  message: videoGate.disabledReason,
9071
+ side: "bottom",
9033
9072
  children: /* @__PURE__ */ jsx("span", { className: "inline-flex", children: /* @__PURE__ */ jsx(
9034
9073
  VideoSlot,
9035
9074
  {
@@ -9039,7 +9078,7 @@ var DefaultHeaderCallActions = ({
9039
9078
  className: cn(
9040
9079
  className,
9041
9080
  slotClassName("videoCallButton"),
9042
- !videoGate.enabled && "pointer-events-none opacity-40"
9081
+ !videoGate.enabled && "opacity-40"
9043
9082
  )
9044
9083
  }
9045
9084
  ) })
@@ -9049,6 +9088,7 @@ var DefaultHeaderCallActions = ({
9049
9088
  {
9050
9089
  disabled: !videoGate.enabled,
9051
9090
  message: videoGate.disabledReason,
9091
+ side: "bottom",
9052
9092
  children: /* @__PURE__ */ jsx(
9053
9093
  Button,
9054
9094
  {
@@ -9066,16 +9106,13 @@ var DefaultHeaderCallActions = ({
9066
9106
  ] });
9067
9107
  };
9068
9108
  var HeaderCallActions = (props) => {
9069
- const { components, slotClassName, features } = useChatCustomizationOptional();
9109
+ const { components, slotClassName } = useChatCustomizationOptional();
9070
9110
  const Custom = components.HeaderCallActions;
9071
- if (!Custom) {
9072
- const hideInGroups = features.hideCallActionsInGroupChats ?? false;
9073
- const showPhoneCall = props.showPhoneCall ?? true;
9074
- const showVideoCall = props.showVideoCall ?? true;
9075
- if (hideInGroups && props.activeChannel.isGroup) return null;
9076
- if (!showPhoneCall && !showVideoCall) return null;
9077
- }
9078
- return /* @__PURE__ */ jsx("div", { className: cn("flex items-center gap-0.5", slotClassName("headerCallActions")), children: Custom ? /* @__PURE__ */ jsx(Custom, { ...props }) : /* @__PURE__ */ jsx(DefaultHeaderCallActions, { ...props }) });
9111
+ const showPhoneCall = props.showPhoneCall ?? true;
9112
+ const showVideoCall = props.showVideoCall ?? true;
9113
+ if (!showPhoneCall && !showVideoCall) return null;
9114
+ const useCustom = Custom != null && Custom !== HeaderCallActions;
9115
+ return /* @__PURE__ */ jsx("div", { className: cn("flex items-center gap-0.5", slotClassName("headerCallActions")), children: useCustom ? /* @__PURE__ */ jsx(Custom, { ...props }) : /* @__PURE__ */ jsx(DefaultHeaderCallActions, { ...props }) });
9079
9116
  };
9080
9117
  var HeaderCallActions_default = HeaderCallActions;
9081
9118
  function OptionsMenuItem({
@@ -12894,9 +12931,91 @@ init_useStore();
12894
12931
 
12895
12932
  // src/hooks/useGroupInfo.ts
12896
12933
  init_useStore();
12934
+
12935
+ // src/lib/effective-settings.helpers.ts
12936
+ function minutesToMs(minutes) {
12937
+ return Math.max(0, minutes) * 60 * 1e3;
12938
+ }
12939
+ function getEditWindowMs(timers, isGroup) {
12940
+ return minutesToMs(isGroup ? timers.groupEditMinutes : timers.dmEditMinutes);
12941
+ }
12942
+ function getDeleteWindowMs(timers, isGroup) {
12943
+ return minutesToMs(
12944
+ isGroup ? timers.groupDeleteMinutes : timers.dmDeleteMinutes
12945
+ );
12946
+ }
12947
+ function isWithinWindow(createdAt, windowMs) {
12948
+ if (!createdAt) return false;
12949
+ const date = new Date(createdAt);
12950
+ if (isNaN(date.getTime())) return false;
12951
+ const diff = Date.now() - date.getTime();
12952
+ return diff >= 0 && diff < windowMs;
12953
+ }
12954
+ function isWithinMessageEditWindow(createdAt, timers, isGroup) {
12955
+ return isWithinWindow(createdAt, getEditWindowMs(timers, isGroup));
12956
+ }
12957
+ function isWithinMessageDeleteWindow(createdAt, timers, isGroup) {
12958
+ return isWithinWindow(createdAt, getDeleteWindowMs(timers, isGroup));
12959
+ }
12960
+ function allowsAllMimeTypes(allowedMimeTypes) {
12961
+ return allowedMimeTypes.some((pattern) => {
12962
+ const normalized = pattern.trim().toLowerCase();
12963
+ return normalized === "*" || normalized === "*/*";
12964
+ });
12965
+ }
12966
+ function matchesAllowedMimeType(fileType, allowedMimeTypes) {
12967
+ if (!allowedMimeTypes.length) return false;
12968
+ if (allowsAllMimeTypes(allowedMimeTypes)) return true;
12969
+ const mime = (fileType || "application/octet-stream").toLowerCase();
12970
+ return allowedMimeTypes.some((pattern) => {
12971
+ const normalized = pattern.trim().toLowerCase();
12972
+ if (!normalized) return false;
12973
+ if (normalized.endsWith("/*")) {
12974
+ const prefix = normalized.slice(0, -1);
12975
+ return mime.startsWith(prefix);
12976
+ }
12977
+ return mime === normalized;
12978
+ });
12979
+ }
12980
+ function buildAcceptFromMimeTypes(allowedMimeTypes) {
12981
+ if (allowsAllMimeTypes(allowedMimeTypes)) return "";
12982
+ return allowedMimeTypes.map((type) => type.trim()).filter(Boolean).join(",");
12983
+ }
12984
+ function formatFileSizeMB(sizeBytes) {
12985
+ return `${(sizeBytes / (1024 * 1024)).toFixed(1)} MB`;
12986
+ }
12987
+ var GROUP_PERMISSION_KEYS = [
12988
+ "onlyAdminCanSendMessage",
12989
+ "onlyAdminCanEditInfo",
12990
+ "senderCanEditMessage",
12991
+ "allowMemberAdd",
12992
+ "allowMemberRemove",
12993
+ "moderationEnabled"
12994
+ ];
12995
+ function resolveGroupPermissions(conversationPermissions, defaults, locked = []) {
12996
+ const next = { ...defaults };
12997
+ if (conversationPermissions) {
12998
+ for (const key of GROUP_PERMISSION_KEYS) {
12999
+ const value = conversationPermissions[key];
13000
+ if (typeof value === "boolean") {
13001
+ next[key] = value;
13002
+ }
13003
+ }
13004
+ }
13005
+ for (const key of locked) {
13006
+ if (GROUP_PERMISSION_KEYS.includes(String(key))) {
13007
+ const typedKey = key;
13008
+ next[typedKey] = defaults[typedKey];
13009
+ }
13010
+ }
13011
+ return next;
13012
+ }
13013
+
13014
+ // src/hooks/useGroupInfo.ts
12897
13015
  var useGroupInfo = (conversationId) => {
12898
13016
  const conversationInfos = useStore((s) => s.conversationInfos);
12899
13017
  const loggedUserDetails = useStore((s) => s.loggedUserDetails);
13018
+ const { settings } = useEffectiveSettingsOptional();
12900
13019
  const info = conversationId ? conversationInfos[conversationId] : null;
12901
13020
  const isAdmin = useMemo(() => {
12902
13021
  if (!info || !loggedUserDetails) return false;
@@ -12912,7 +13031,14 @@ var useGroupInfo = (conversationId) => {
12912
13031
  }
12913
13032
  return String(ownerId) === String(loggedUserDetails._id);
12914
13033
  }, [info, loggedUserDetails]);
12915
- const permissions = info?.groupPermissions || null;
13034
+ const permissions = useMemo(() => {
13035
+ if (!info) return null;
13036
+ return resolveGroupPermissions(
13037
+ info.groupPermissions,
13038
+ settings.groupPolicy.defaults,
13039
+ settings.groupPolicy.locked
13040
+ );
13041
+ }, [info, settings.groupPolicy.defaults, settings.groupPolicy.locked]);
12916
13042
  const userPermissions = useMemo(() => {
12917
13043
  return {
12918
13044
  canSendMessage: isOwner || isAdmin || !permissions?.onlyAdminCanSendMessage,
@@ -12920,7 +13046,6 @@ var useGroupInfo = (conversationId) => {
12920
13046
  canAddMember: isOwner || !!permissions?.allowMemberAdd,
12921
13047
  canRemoveMember: isOwner || !!permissions?.allowMemberRemove,
12922
13048
  canEditMessage: isOwner || isAdmin || !!permissions?.senderCanEditMessage
12923
- // You can add more derived permissions here as needed
12924
13049
  };
12925
13050
  }, [isOwner, isAdmin, permissions]);
12926
13051
  return {
@@ -13347,14 +13472,20 @@ var PermissionDrawer = ({ isOpen, onOpenChange, conversationId, conversationInfo
13347
13472
  const [permissions, setPermissions] = useState(null);
13348
13473
  const [isSaving, setIsSaving] = useState(false);
13349
13474
  useEffect(() => {
13350
- if (conversationInfo?.groupPermissions) {
13351
- setPermissions(conversationInfo.groupPermissions);
13352
- return;
13353
- }
13354
- if (isOpen) {
13355
- setPermissions({ ...settings.groupPolicy.defaults });
13356
- }
13357
- }, [conversationInfo, isOpen, settings.groupPolicy.defaults]);
13475
+ if (!isOpen) return;
13476
+ setPermissions(
13477
+ resolveGroupPermissions(
13478
+ conversationInfo?.groupPermissions,
13479
+ settings.groupPolicy.defaults,
13480
+ settings.groupPolicy.locked
13481
+ )
13482
+ );
13483
+ }, [
13484
+ isOpen,
13485
+ conversationInfo?.groupPermissions,
13486
+ settings.groupPolicy.defaults,
13487
+ settings.groupPolicy.locked
13488
+ ]);
13358
13489
  if (!conversationId || !permissions) return null;
13359
13490
  const handleToggle = (key) => {
13360
13491
  if (isGroupPermissionLocked(key)) return;
@@ -13364,7 +13495,12 @@ var PermissionDrawer = ({ isOpen, onOpenChange, conversationId, conversationInfo
13364
13495
  if (!conversationId || !permissions) return;
13365
13496
  setIsSaving(true);
13366
13497
  try {
13367
- await updateGroupPermissions(conversationId, permissions);
13498
+ const payload = resolveGroupPermissions(
13499
+ permissions,
13500
+ settings.groupPolicy.defaults,
13501
+ settings.groupPolicy.locked
13502
+ );
13503
+ await updateGroupPermissions(conversationId, payload);
13368
13504
  onOpenChange(false);
13369
13505
  } finally {
13370
13506
  setIsSaving(false);
@@ -13374,8 +13510,8 @@ var PermissionDrawer = ({ isOpen, onOpenChange, conversationId, conversationInfo
13374
13510
  { key: "onlyAdminCanSendMessage", label: "Only Admin Can Send Message", desc: "Restricts message sending to group administrators only." },
13375
13511
  { key: "onlyAdminCanEditInfo", label: "Only Admin Can Edit Info", desc: "Only admins can change group name, description, and avatar." },
13376
13512
  { key: "senderCanEditMessage", label: "Sender Can Edit Message", desc: "Allows users to edit their own sent messages." },
13377
- { key: "allowMemberAdd", label: "Only Admin Can Add Members", desc: "Only administrators can add new participants to the group.", inverted: true },
13378
- { key: "allowMemberRemove", label: "Only Admin Can Remove Members", desc: "Only administrators can remove other participants.", inverted: true },
13513
+ { key: "allowMemberAdd", label: "Allow Members to Add", desc: "Allows group members to add new participants." },
13514
+ { key: "allowMemberRemove", label: "Allow Members to Remove", desc: "Allows group members to remove other participants." },
13379
13515
  { key: "moderationEnabled", label: "Moderation Enabled", desc: "Enables advanced message moderation features." }
13380
13516
  ];
13381
13517
  return /* @__PURE__ */ jsx(Sheet, { open: isOpen, onOpenChange, children: /* @__PURE__ */ jsxs(ChatSheetContent, { showCloseButton: true, className: "sm:max-w-[380px]", children: [
@@ -13391,6 +13527,7 @@ var PermissionDrawer = ({ isOpen, onOpenChange, conversationId, conversationInfo
13391
13527
  /* @__PURE__ */ jsx("h3", { className: "mb-1 px-0.5 text-[10px] font-semibold uppercase tracking-wide text-(--chat-muted)", children: "Permissions" }),
13392
13528
  /* @__PURE__ */ jsx("div", { className: "rounded-lg border border-(--chat-border) px-3", children: permissionItems.map((item, index) => {
13393
13529
  const locked = isGroupPermissionLocked(item.key);
13530
+ const checked = permissions[item.key];
13394
13531
  return /* @__PURE__ */ jsxs(
13395
13532
  "div",
13396
13533
  {
@@ -13415,7 +13552,7 @@ var PermissionDrawer = ({ isOpen, onOpenChange, conversationId, conversationInfo
13415
13552
  size: "sm",
13416
13553
  className: "mt-0.5 shrink-0 data-[state=checked]:bg-(--chat-theme) data-[state=unchecked]:bg-(--chat-border)",
13417
13554
  disabled: locked,
13418
- checked: item.inverted ? !permissions[item.key] : permissions[item.key],
13555
+ checked,
13419
13556
  onCheckedChange: () => handleToggle(item.key)
13420
13557
  }
13421
13558
  )
@@ -14959,61 +15096,6 @@ function resolveForwardItems(selectedKeys, messages) {
14959
15096
 
14960
15097
  // src/chat/lib/chat-edit-window.ts
14961
15098
  init_settings_types();
14962
-
14963
- // src/lib/effective-settings.helpers.ts
14964
- function minutesToMs(minutes) {
14965
- return Math.max(0, minutes) * 60 * 1e3;
14966
- }
14967
- function getEditWindowMs(timers, isGroup) {
14968
- return minutesToMs(isGroup ? timers.groupEditMinutes : timers.dmEditMinutes);
14969
- }
14970
- function getDeleteWindowMs(timers, isGroup) {
14971
- return minutesToMs(
14972
- isGroup ? timers.groupDeleteMinutes : timers.dmDeleteMinutes
14973
- );
14974
- }
14975
- function isWithinWindow(createdAt, windowMs) {
14976
- if (!createdAt) return false;
14977
- const date = new Date(createdAt);
14978
- if (isNaN(date.getTime())) return false;
14979
- const diff = Date.now() - date.getTime();
14980
- return diff >= 0 && diff < windowMs;
14981
- }
14982
- function isWithinMessageEditWindow(createdAt, timers, isGroup) {
14983
- return isWithinWindow(createdAt, getEditWindowMs(timers, isGroup));
14984
- }
14985
- function isWithinMessageDeleteWindow(createdAt, timers, isGroup) {
14986
- return isWithinWindow(createdAt, getDeleteWindowMs(timers, isGroup));
14987
- }
14988
- function allowsAllMimeTypes(allowedMimeTypes) {
14989
- return allowedMimeTypes.some((pattern) => {
14990
- const normalized = pattern.trim().toLowerCase();
14991
- return normalized === "*" || normalized === "*/*";
14992
- });
14993
- }
14994
- function matchesAllowedMimeType(fileType, allowedMimeTypes) {
14995
- if (!allowedMimeTypes.length) return false;
14996
- if (allowsAllMimeTypes(allowedMimeTypes)) return true;
14997
- const mime = (fileType || "application/octet-stream").toLowerCase();
14998
- return allowedMimeTypes.some((pattern) => {
14999
- const normalized = pattern.trim().toLowerCase();
15000
- if (!normalized) return false;
15001
- if (normalized.endsWith("/*")) {
15002
- const prefix = normalized.slice(0, -1);
15003
- return mime.startsWith(prefix);
15004
- }
15005
- return mime === normalized;
15006
- });
15007
- }
15008
- function buildAcceptFromMimeTypes(allowedMimeTypes) {
15009
- if (allowsAllMimeTypes(allowedMimeTypes)) return "";
15010
- return allowedMimeTypes.map((type) => type.trim()).filter(Boolean).join(",");
15011
- }
15012
- function formatFileSizeMB(sizeBytes) {
15013
- return `${(sizeBytes / (1024 * 1024)).toFixed(1)} MB`;
15014
- }
15015
-
15016
- // src/chat/lib/chat-edit-window.ts
15017
15099
  var FALLBACK_TIMERS = DEFAULT_EFFECTIVE_SETTINGS.messageTimers;
15018
15100
  function isWithinMessageEditWindow2(createdAt, isGroup, timers = FALLBACK_TIMERS) {
15019
15101
  return isWithinMessageEditWindow(createdAt, timers, isGroup);