@realtimexsco/live-chat 1.4.14 → 1.4.15

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/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  [![npm version](https://img.shields.io/npm/v/@realtimexsco/live-chat.svg)](https://www.npmjs.com/package/@realtimexsco/live-chat)
6
6
  [![npm downloads](https://img.shields.io/npm/dm/@realtimexsco/live-chat.svg)](https://www.npmjs.com/package/@realtimexsco/live-chat)
7
- [![license](https://img.shields.io/npm/l/@realtimexsco/live-chat.svg)](https://www.npmjs.com/package/@realtimexsco/live-chat#license)
7
+ [![license](https://img.shields.io/npm/l/@realtimexsco/live-chat.svg)](#license)
8
8
 
9
9
  | | |
10
10
  |---|---|
@@ -12,7 +12,7 @@
12
12
  | **Styles** | `@realtimexsco/live-chat/styles.css` |
13
13
  | **Requires** | React 17+, Tailwind CSS **v4**, Zustand 4+ |
14
14
  | **Docs** | [Package documentation](https://realtimex.softwareco.com/documentations/package) |
15
- | **License** | [MIT](https://www.npmjs.com/package/@realtimexsco/live-chat#license) |
15
+ | **License** | [MIT](#license) |
16
16
 
17
17
  Socket.IO, REST client, Zustand store, Radix UI, emoji picker, and chat UI are **bundled**. Your app only installs peer dependencies.
18
18
 
@@ -61,10 +61,12 @@ DMs · groups · Socket.IO · reactions · replies · forward · edit/delete ·
61
61
  |-------|------|
62
62
  | Full setup & docs | [https://realtimex.softwareco.com/documentations/package](https://realtimex.softwareco.com/documentations/package) |
63
63
  | npm package | [npmjs.com/package/@realtimexsco/live-chat](https://www.npmjs.com/package/@realtimexsco/live-chat) |
64
- | License | [MIT](https://www.npmjs.com/package/@realtimexsco/live-chat#license) |
64
+ | License | [MIT](#license) |
65
65
 
66
66
  ---
67
67
 
68
+ <a id="license"></a>
69
+
68
70
  ## License
69
71
 
70
72
  The MIT License (MIT)
package/dist/index.cjs CHANGED
@@ -12922,9 +12922,91 @@ init_useStore();
12922
12922
 
12923
12923
  // src/hooks/useGroupInfo.ts
12924
12924
  init_useStore();
12925
+
12926
+ // src/lib/effective-settings.helpers.ts
12927
+ function minutesToMs(minutes) {
12928
+ return Math.max(0, minutes) * 60 * 1e3;
12929
+ }
12930
+ function getEditWindowMs(timers, isGroup) {
12931
+ return minutesToMs(isGroup ? timers.groupEditMinutes : timers.dmEditMinutes);
12932
+ }
12933
+ function getDeleteWindowMs(timers, isGroup) {
12934
+ return minutesToMs(
12935
+ isGroup ? timers.groupDeleteMinutes : timers.dmDeleteMinutes
12936
+ );
12937
+ }
12938
+ function isWithinWindow(createdAt, windowMs) {
12939
+ if (!createdAt) return false;
12940
+ const date = new Date(createdAt);
12941
+ if (isNaN(date.getTime())) return false;
12942
+ const diff = Date.now() - date.getTime();
12943
+ return diff >= 0 && diff < windowMs;
12944
+ }
12945
+ function isWithinMessageEditWindow(createdAt, timers, isGroup) {
12946
+ return isWithinWindow(createdAt, getEditWindowMs(timers, isGroup));
12947
+ }
12948
+ function isWithinMessageDeleteWindow(createdAt, timers, isGroup) {
12949
+ return isWithinWindow(createdAt, getDeleteWindowMs(timers, isGroup));
12950
+ }
12951
+ function allowsAllMimeTypes(allowedMimeTypes) {
12952
+ return allowedMimeTypes.some((pattern) => {
12953
+ const normalized = pattern.trim().toLowerCase();
12954
+ return normalized === "*" || normalized === "*/*";
12955
+ });
12956
+ }
12957
+ function matchesAllowedMimeType(fileType, allowedMimeTypes) {
12958
+ if (!allowedMimeTypes.length) return false;
12959
+ if (allowsAllMimeTypes(allowedMimeTypes)) return true;
12960
+ const mime = (fileType || "application/octet-stream").toLowerCase();
12961
+ return allowedMimeTypes.some((pattern) => {
12962
+ const normalized = pattern.trim().toLowerCase();
12963
+ if (!normalized) return false;
12964
+ if (normalized.endsWith("/*")) {
12965
+ const prefix = normalized.slice(0, -1);
12966
+ return mime.startsWith(prefix);
12967
+ }
12968
+ return mime === normalized;
12969
+ });
12970
+ }
12971
+ function buildAcceptFromMimeTypes(allowedMimeTypes) {
12972
+ if (allowsAllMimeTypes(allowedMimeTypes)) return "";
12973
+ return allowedMimeTypes.map((type) => type.trim()).filter(Boolean).join(",");
12974
+ }
12975
+ function formatFileSizeMB(sizeBytes) {
12976
+ return `${(sizeBytes / (1024 * 1024)).toFixed(1)} MB`;
12977
+ }
12978
+ var GROUP_PERMISSION_KEYS = [
12979
+ "onlyAdminCanSendMessage",
12980
+ "onlyAdminCanEditInfo",
12981
+ "senderCanEditMessage",
12982
+ "allowMemberAdd",
12983
+ "allowMemberRemove",
12984
+ "moderationEnabled"
12985
+ ];
12986
+ function resolveGroupPermissions(conversationPermissions, defaults, locked = []) {
12987
+ const next = { ...defaults };
12988
+ if (conversationPermissions) {
12989
+ for (const key of GROUP_PERMISSION_KEYS) {
12990
+ const value = conversationPermissions[key];
12991
+ if (typeof value === "boolean") {
12992
+ next[key] = value;
12993
+ }
12994
+ }
12995
+ }
12996
+ for (const key of locked) {
12997
+ if (GROUP_PERMISSION_KEYS.includes(String(key))) {
12998
+ const typedKey = key;
12999
+ next[typedKey] = defaults[typedKey];
13000
+ }
13001
+ }
13002
+ return next;
13003
+ }
13004
+
13005
+ // src/hooks/useGroupInfo.ts
12925
13006
  var useGroupInfo = (conversationId) => {
12926
13007
  const conversationInfos = exports.useChatStore((s) => s.conversationInfos);
12927
13008
  const loggedUserDetails = exports.useChatStore((s) => s.loggedUserDetails);
13009
+ const { settings } = useEffectiveSettingsOptional();
12928
13010
  const info = conversationId ? conversationInfos[conversationId] : null;
12929
13011
  const isAdmin = React3.useMemo(() => {
12930
13012
  if (!info || !loggedUserDetails) return false;
@@ -12940,7 +13022,14 @@ var useGroupInfo = (conversationId) => {
12940
13022
  }
12941
13023
  return String(ownerId) === String(loggedUserDetails._id);
12942
13024
  }, [info, loggedUserDetails]);
12943
- const permissions = info?.groupPermissions || null;
13025
+ const permissions = React3.useMemo(() => {
13026
+ if (!info) return null;
13027
+ return resolveGroupPermissions(
13028
+ info.groupPermissions,
13029
+ settings.groupPolicy.defaults,
13030
+ settings.groupPolicy.locked
13031
+ );
13032
+ }, [info, settings.groupPolicy.defaults, settings.groupPolicy.locked]);
12944
13033
  const userPermissions = React3.useMemo(() => {
12945
13034
  return {
12946
13035
  canSendMessage: isOwner || isAdmin || !permissions?.onlyAdminCanSendMessage,
@@ -12948,7 +13037,6 @@ var useGroupInfo = (conversationId) => {
12948
13037
  canAddMember: isOwner || !!permissions?.allowMemberAdd,
12949
13038
  canRemoveMember: isOwner || !!permissions?.allowMemberRemove,
12950
13039
  canEditMessage: isOwner || isAdmin || !!permissions?.senderCanEditMessage
12951
- // You can add more derived permissions here as needed
12952
13040
  };
12953
13041
  }, [isOwner, isAdmin, permissions]);
12954
13042
  return {
@@ -13375,14 +13463,20 @@ var PermissionDrawer = ({ isOpen, onOpenChange, conversationId, conversationInfo
13375
13463
  const [permissions, setPermissions] = React3.useState(null);
13376
13464
  const [isSaving, setIsSaving] = React3.useState(false);
13377
13465
  React3.useEffect(() => {
13378
- if (conversationInfo?.groupPermissions) {
13379
- setPermissions(conversationInfo.groupPermissions);
13380
- return;
13381
- }
13382
- if (isOpen) {
13383
- setPermissions({ ...settings.groupPolicy.defaults });
13384
- }
13385
- }, [conversationInfo, isOpen, settings.groupPolicy.defaults]);
13466
+ if (!isOpen) return;
13467
+ setPermissions(
13468
+ resolveGroupPermissions(
13469
+ conversationInfo?.groupPermissions,
13470
+ settings.groupPolicy.defaults,
13471
+ settings.groupPolicy.locked
13472
+ )
13473
+ );
13474
+ }, [
13475
+ isOpen,
13476
+ conversationInfo?.groupPermissions,
13477
+ settings.groupPolicy.defaults,
13478
+ settings.groupPolicy.locked
13479
+ ]);
13386
13480
  if (!conversationId || !permissions) return null;
13387
13481
  const handleToggle = (key) => {
13388
13482
  if (isGroupPermissionLocked(key)) return;
@@ -13392,7 +13486,12 @@ var PermissionDrawer = ({ isOpen, onOpenChange, conversationId, conversationInfo
13392
13486
  if (!conversationId || !permissions) return;
13393
13487
  setIsSaving(true);
13394
13488
  try {
13395
- await updateGroupPermissions(conversationId, permissions);
13489
+ const payload = resolveGroupPermissions(
13490
+ permissions,
13491
+ settings.groupPolicy.defaults,
13492
+ settings.groupPolicy.locked
13493
+ );
13494
+ await updateGroupPermissions(conversationId, payload);
13396
13495
  onOpenChange(false);
13397
13496
  } finally {
13398
13497
  setIsSaving(false);
@@ -13419,6 +13518,7 @@ var PermissionDrawer = ({ isOpen, onOpenChange, conversationId, conversationInfo
13419
13518
  /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "mb-1 px-0.5 text-[10px] font-semibold uppercase tracking-wide text-(--chat-muted)", children: "Permissions" }),
13420
13519
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "rounded-lg border border-(--chat-border) px-3", children: permissionItems.map((item, index) => {
13421
13520
  const locked = isGroupPermissionLocked(item.key);
13521
+ const checked = item.inverted ? !permissions[item.key] : permissions[item.key];
13422
13522
  return /* @__PURE__ */ jsxRuntime.jsxs(
13423
13523
  "div",
13424
13524
  {
@@ -13443,7 +13543,7 @@ var PermissionDrawer = ({ isOpen, onOpenChange, conversationId, conversationInfo
13443
13543
  size: "sm",
13444
13544
  className: "mt-0.5 shrink-0 data-[state=checked]:bg-(--chat-theme) data-[state=unchecked]:bg-(--chat-border)",
13445
13545
  disabled: locked,
13446
- checked: item.inverted ? !permissions[item.key] : permissions[item.key],
13546
+ checked,
13447
13547
  onCheckedChange: () => handleToggle(item.key)
13448
13548
  }
13449
13549
  )
@@ -14987,61 +15087,6 @@ function resolveForwardItems(selectedKeys, messages) {
14987
15087
 
14988
15088
  // src/chat/lib/chat-edit-window.ts
14989
15089
  init_settings_types();
14990
-
14991
- // src/lib/effective-settings.helpers.ts
14992
- function minutesToMs(minutes) {
14993
- return Math.max(0, minutes) * 60 * 1e3;
14994
- }
14995
- function getEditWindowMs(timers, isGroup) {
14996
- return minutesToMs(isGroup ? timers.groupEditMinutes : timers.dmEditMinutes);
14997
- }
14998
- function getDeleteWindowMs(timers, isGroup) {
14999
- return minutesToMs(
15000
- isGroup ? timers.groupDeleteMinutes : timers.dmDeleteMinutes
15001
- );
15002
- }
15003
- function isWithinWindow(createdAt, windowMs) {
15004
- if (!createdAt) return false;
15005
- const date = new Date(createdAt);
15006
- if (isNaN(date.getTime())) return false;
15007
- const diff = Date.now() - date.getTime();
15008
- return diff >= 0 && diff < windowMs;
15009
- }
15010
- function isWithinMessageEditWindow(createdAt, timers, isGroup) {
15011
- return isWithinWindow(createdAt, getEditWindowMs(timers, isGroup));
15012
- }
15013
- function isWithinMessageDeleteWindow(createdAt, timers, isGroup) {
15014
- return isWithinWindow(createdAt, getDeleteWindowMs(timers, isGroup));
15015
- }
15016
- function allowsAllMimeTypes(allowedMimeTypes) {
15017
- return allowedMimeTypes.some((pattern) => {
15018
- const normalized = pattern.trim().toLowerCase();
15019
- return normalized === "*" || normalized === "*/*";
15020
- });
15021
- }
15022
- function matchesAllowedMimeType(fileType, allowedMimeTypes) {
15023
- if (!allowedMimeTypes.length) return false;
15024
- if (allowsAllMimeTypes(allowedMimeTypes)) return true;
15025
- const mime = (fileType || "application/octet-stream").toLowerCase();
15026
- return allowedMimeTypes.some((pattern) => {
15027
- const normalized = pattern.trim().toLowerCase();
15028
- if (!normalized) return false;
15029
- if (normalized.endsWith("/*")) {
15030
- const prefix = normalized.slice(0, -1);
15031
- return mime.startsWith(prefix);
15032
- }
15033
- return mime === normalized;
15034
- });
15035
- }
15036
- function buildAcceptFromMimeTypes(allowedMimeTypes) {
15037
- if (allowsAllMimeTypes(allowedMimeTypes)) return "";
15038
- return allowedMimeTypes.map((type) => type.trim()).filter(Boolean).join(",");
15039
- }
15040
- function formatFileSizeMB(sizeBytes) {
15041
- return `${(sizeBytes / (1024 * 1024)).toFixed(1)} MB`;
15042
- }
15043
-
15044
- // src/chat/lib/chat-edit-window.ts
15045
15090
  var FALLBACK_TIMERS = exports.DEFAULT_EFFECTIVE_SETTINGS.messageTimers;
15046
15091
  function isWithinMessageEditWindow2(createdAt, isGroup, timers = FALLBACK_TIMERS) {
15047
15092
  return isWithinMessageEditWindow(createdAt, timers, isGroup);