@realtimexsco/live-chat 1.4.8 → 1.4.10

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.cjs CHANGED
@@ -16,7 +16,7 @@ var lucideReact = require('lucide-react');
16
16
  var classVarianceAuthority = require('class-variance-authority');
17
17
  var EmojiPicker = require('emoji-picker-react');
18
18
  var reactDayPicker = require('react-day-picker');
19
- var toast2 = require('react-hot-toast');
19
+ var toast = require('react-hot-toast');
20
20
 
21
21
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
22
22
 
@@ -41,7 +41,7 @@ function _interopNamespace(e) {
41
41
  var axios__default = /*#__PURE__*/_interopDefault(axios);
42
42
  var React2__namespace = /*#__PURE__*/_interopNamespace(React2);
43
43
  var EmojiPicker__default = /*#__PURE__*/_interopDefault(EmojiPicker);
44
- var toast2__default = /*#__PURE__*/_interopDefault(toast2);
44
+ var toast__default = /*#__PURE__*/_interopDefault(toast);
45
45
 
46
46
  var __defProp = Object.defineProperty;
47
47
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
@@ -4659,46 +4659,114 @@ init_store_helpers();
4659
4659
  init_store_helpers();
4660
4660
  var OPEN_CONVERSATION_EVENT = "realtimex:open-conversation";
4661
4661
  var NOTIFICATION_CLICK_SW_TYPE = "realtimex:notification-click";
4662
+ var PUSH_BROADCAST_CHANNEL = "realtimex-push";
4663
+ var PUSH_DATA_CACHE_NAME = "realtimex-push-data-v1";
4662
4664
  var PENDING_STORAGE_KEY = "realtimex:pending-open-conversation";
4665
+ var PENDING_OPEN_CACHE_PATH = "pending-open";
4663
4666
  var resolveConversationIdFromPushData = (data) => {
4664
4667
  if (!data || typeof data !== "object") return "";
4665
- const nested = data.FCM_MSG && typeof data.FCM_MSG === "object" ? data.FCM_MSG.data : void 0;
4668
+ let fcmMsg = data.FCM_MSG;
4669
+ if (typeof fcmMsg === "string") {
4670
+ try {
4671
+ fcmMsg = JSON.parse(fcmMsg);
4672
+ } catch {
4673
+ fcmMsg = void 0;
4674
+ }
4675
+ }
4676
+ const nested = fcmMsg && typeof fcmMsg === "object" ? fcmMsg.data : void 0;
4666
4677
  const source = { ...nested || {}, ...data };
4667
4678
  return String(
4668
4679
  source.conversationId || source.conversation_id || source.conversationID || ""
4669
- );
4680
+ ).trim();
4681
+ };
4682
+ var normalizePushMeta = (meta) => {
4683
+ if (!meta || typeof meta !== "object") return {};
4684
+ return {
4685
+ senderId: String(
4686
+ meta.senderId || meta.sender_id || ""
4687
+ ).trim() || void 0,
4688
+ kind: String(meta.kind || "").trim() || void 0,
4689
+ messageId: String(
4690
+ meta.messageId || meta.message_id || ""
4691
+ ).trim() || void 0,
4692
+ title: String(meta.title || "").trim() || void 0
4693
+ };
4694
+ };
4695
+ var parsePendingRaw = (raw) => {
4696
+ if (!raw) return null;
4697
+ try {
4698
+ const parsed = JSON.parse(raw);
4699
+ if (parsed?.conversationId) {
4700
+ return {
4701
+ conversationId: String(parsed.conversationId).trim(),
4702
+ ...normalizePushMeta(parsed)
4703
+ };
4704
+ }
4705
+ } catch {
4706
+ }
4707
+ const id = String(raw).trim();
4708
+ return id ? { conversationId: id } : null;
4670
4709
  };
4671
- var peekPendingOpenConversation = () => {
4710
+ var peekPendingOpenConversation = () => peekPendingOpenRequest()?.conversationId ?? null;
4711
+ var peekPendingOpenRequest = () => {
4672
4712
  try {
4673
- return sessionStorage.getItem(PENDING_STORAGE_KEY);
4713
+ return parsePendingRaw(sessionStorage.getItem(PENDING_STORAGE_KEY));
4674
4714
  } catch {
4675
4715
  return null;
4676
4716
  }
4677
4717
  };
4678
- var consumePendingOpenConversation = () => {
4679
- const id = peekPendingOpenConversation();
4718
+ var consumePendingOpenConversation = () => consumePendingOpenRequest()?.conversationId ?? null;
4719
+ var consumePendingOpenRequest = () => {
4720
+ const request = peekPendingOpenRequest();
4680
4721
  try {
4681
4722
  sessionStorage.removeItem(PENDING_STORAGE_KEY);
4682
4723
  } catch {
4683
4724
  }
4684
- return id;
4725
+ return request;
4685
4726
  };
4686
- var requestOpenConversation = (conversationId) => {
4727
+ var requestOpenConversation = (conversationId, meta) => {
4687
4728
  const id = String(conversationId || "").trim();
4688
4729
  if (!id) return;
4730
+ const request = {
4731
+ conversationId: id,
4732
+ ...normalizePushMeta(meta)
4733
+ };
4734
+ console.log("[realtimex-push] requestOpenConversation:", request);
4689
4735
  try {
4690
- sessionStorage.setItem(PENDING_STORAGE_KEY, id);
4736
+ sessionStorage.setItem(PENDING_STORAGE_KEY, JSON.stringify(request));
4691
4737
  } catch {
4692
4738
  }
4693
4739
  try {
4694
4740
  window.dispatchEvent(
4695
- new CustomEvent(OPEN_CONVERSATION_EVENT, {
4696
- detail: { conversationId: id }
4697
- })
4741
+ new CustomEvent(OPEN_CONVERSATION_EVENT, { detail: request })
4698
4742
  );
4699
4743
  } catch {
4700
4744
  }
4701
4745
  };
4746
+ var buildChannelFromPushRequest = (request, loggedUserId, allUsers) => {
4747
+ const conversationId = request.conversationId;
4748
+ const isGroup = request.kind === "group";
4749
+ const senderId = String(request.senderId || "").trim();
4750
+ if (isGroup) {
4751
+ const rawName2 = request.title || "Group Chat";
4752
+ return {
4753
+ id: conversationId,
4754
+ conversationId,
4755
+ name: rawName2.charAt(0).toUpperCase() + rawName2.slice(1),
4756
+ isGroup: true
4757
+ };
4758
+ }
4759
+ const senderUser = senderId ? (allUsers || []).find((user) => String(user._id) === senderId) : null;
4760
+ const rawName = senderUser?.name || request.title || "New message";
4761
+ return {
4762
+ id: senderId || conversationId,
4763
+ conversationId,
4764
+ name: rawName.charAt(0).toUpperCase() + rawName.slice(1),
4765
+ image: senderUser?.image || void 0,
4766
+ email: senderUser?.email,
4767
+ isGroup: false
4768
+ };
4769
+ };
4702
4770
  var mapConversationToChannel = (conversation, loggedUserId, allUsers) => {
4703
4771
  const conversationId = String(conversation?._id || conversation?.id || "");
4704
4772
  const isGroup = isGroupConversation(conversation);
@@ -4735,6 +4803,37 @@ var mapConversationToChannel = (conversation, loggedUserId, allUsers) => {
4735
4803
  starredCount: conversation.starredCount
4736
4804
  };
4737
4805
  };
4806
+ var stripConversationIdFromUrl = () => {
4807
+ if (typeof window === "undefined") return;
4808
+ try {
4809
+ const url = new URL(window.location.href);
4810
+ if (!url.searchParams.has("conversationId")) return;
4811
+ url.searchParams.delete("conversationId");
4812
+ const next = `${url.pathname}${url.search}${url.hash}`;
4813
+ window.history.replaceState(window.history.state, "", next);
4814
+ } catch {
4815
+ }
4816
+ };
4817
+ var cacheRequestForPath = (path) => new Request(new URL(path, window.location.origin).toString());
4818
+ var consumePendingOpenFromPushCache = async () => {
4819
+ if (typeof caches === "undefined") return null;
4820
+ try {
4821
+ const cache = await caches.open(PUSH_DATA_CACHE_NAME);
4822
+ const req = cacheRequestForPath(PENDING_OPEN_CACHE_PATH);
4823
+ const res = await cache.match(req);
4824
+ if (!res) return null;
4825
+ await cache.delete(req);
4826
+ const data = await res.json();
4827
+ const conversationId = resolveConversationIdFromPushData(data);
4828
+ if (!conversationId) return null;
4829
+ return {
4830
+ conversationId,
4831
+ ...normalizePushMeta(data)
4832
+ };
4833
+ } catch {
4834
+ return null;
4835
+ }
4836
+ };
4738
4837
  var readConversationIdFromLocation = () => {
4739
4838
  try {
4740
4839
  return new URLSearchParams(window.location.search).get("conversationId") || "";
@@ -4743,34 +4842,73 @@ var readConversationIdFromLocation = () => {
4743
4842
  }
4744
4843
  };
4745
4844
  var consumedUrlConversationIds = /* @__PURE__ */ new Set();
4845
+ var lastHandledOpenId = "";
4846
+ var lastHandledOpenAt = 0;
4847
+ var shouldHandleOpen = (conversationId) => {
4848
+ const id = String(conversationId || "").trim();
4849
+ if (!id) return false;
4850
+ const now = Date.now();
4851
+ if (lastHandledOpenId === id && now - lastHandledOpenAt < 2500) {
4852
+ return false;
4853
+ }
4854
+ lastHandledOpenId = id;
4855
+ lastHandledOpenAt = now;
4856
+ return true;
4857
+ };
4858
+ var handleNotificationOpen = (conversationId, pushData, options) => {
4859
+ const id = String(conversationId || "").trim();
4860
+ if (!id) {
4861
+ console.warn("[realtimex-push] notification open ignored \u2014 no conversationId", pushData);
4862
+ return;
4863
+ }
4864
+ if (!shouldHandleOpen(id)) {
4865
+ stripConversationIdFromUrl();
4866
+ return;
4867
+ }
4868
+ requestOpenConversation(id, pushData);
4869
+ const chatPath = options.chatPath || "/chat";
4870
+ stripConversationIdFromUrl();
4871
+ options.onNavigate?.(id, chatPath);
4872
+ };
4746
4873
  var installNotificationClickBridge = (options = {}) => {
4747
4874
  if (typeof window === "undefined") return () => void 0;
4748
- const chatPath = options.chatPath || "/chat";
4749
- const handleOpen = (conversationId) => {
4750
- const id = String(conversationId || "").trim();
4751
- if (!id) return;
4752
- requestOpenConversation(id);
4753
- const pathWithQuery = `${chatPath}?conversationId=${encodeURIComponent(id)}`;
4754
- options.onNavigate?.(id, pathWithQuery);
4755
- };
4756
4875
  const onSwMessage = (event) => {
4757
4876
  const data = event.data;
4758
4877
  console.log("[realtimex-push] SW \u2192 page message:", data);
4759
4878
  if (!data || data.type !== NOTIFICATION_CLICK_SW_TYPE) return;
4760
- console.log(
4761
- "[realtimex-push] notification click conversationId:",
4762
- data.conversationId
4763
- );
4764
- handleOpen(String(data.conversationId || ""));
4879
+ const pushData = data.data && typeof data.data === "object" ? data.data : void 0;
4880
+ const conversationId = String(data.conversationId || "") || resolveConversationIdFromPushData(pushData);
4881
+ console.log("[realtimex-push] notification click \u2192 open:", {
4882
+ conversationId,
4883
+ pushData
4884
+ });
4885
+ handleNotificationOpen(conversationId, pushData, options);
4765
4886
  };
4766
4887
  navigator.serviceWorker?.addEventListener("message", onSwMessage);
4888
+ let broadcast = null;
4889
+ try {
4890
+ broadcast = new BroadcastChannel(PUSH_BROADCAST_CHANNEL);
4891
+ broadcast.onmessage = onSwMessage;
4892
+ } catch {
4893
+ }
4767
4894
  const fromUrl = readConversationIdFromLocation();
4768
4895
  if (fromUrl && !consumedUrlConversationIds.has(fromUrl)) {
4769
4896
  consumedUrlConversationIds.add(fromUrl);
4770
- handleOpen(fromUrl);
4897
+ handleNotificationOpen(fromUrl, void 0, options);
4898
+ } else {
4899
+ stripConversationIdFromUrl();
4771
4900
  }
4901
+ void consumePendingOpenFromPushCache().then((request) => {
4902
+ if (!request?.conversationId) return;
4903
+ handleNotificationOpen(
4904
+ request.conversationId,
4905
+ request,
4906
+ options
4907
+ );
4908
+ });
4772
4909
  return () => {
4773
4910
  navigator.serviceWorker?.removeEventListener("message", onSwMessage);
4911
+ broadcast?.close();
4774
4912
  };
4775
4913
  };
4776
4914
 
@@ -4850,7 +4988,7 @@ var useChatController = () => {
4850
4988
  const allUsers = exports.useChatStore((s) => s.allUsers);
4851
4989
  const isFetchingConversations = exports.useChatStore((s) => s.isFetchingConversations);
4852
4990
  const conversations = exports.useChatStore((s) => s.conversations);
4853
- const [pendingOpenConversationId, setPendingOpenConversationId] = React2.useState(() => peekPendingOpenConversation());
4991
+ const [pendingOpenRequest, setPendingOpenRequest] = React2.useState(() => peekPendingOpenRequest());
4854
4992
  const conversationsLength = exports.useChatStore(
4855
4993
  (s) => state.activeChannel?.conversationId ? 0 : s.conversations.length
4856
4994
  );
@@ -4881,32 +5019,42 @@ var useChatController = () => {
4881
5019
  });
4882
5020
  React2.useEffect(() => {
4883
5021
  const onOpen = (event) => {
4884
- const id = String(
4885
- event.detail?.conversationId || ""
4886
- ).trim();
4887
- if (id) setPendingOpenConversationId(id);
5022
+ const detail = event.detail;
5023
+ if (detail?.conversationId) {
5024
+ console.log("[realtimex-push] OPEN_CONVERSATION_EVENT:", detail);
5025
+ setPendingOpenRequest({
5026
+ conversationId: String(detail.conversationId),
5027
+ senderId: detail.senderId,
5028
+ kind: detail.kind,
5029
+ messageId: detail.messageId,
5030
+ title: detail.title
5031
+ });
5032
+ }
4888
5033
  };
4889
5034
  window.addEventListener(OPEN_CONVERSATION_EVENT, onOpen);
4890
5035
  return () => window.removeEventListener(OPEN_CONVERSATION_EVENT, onOpen);
4891
5036
  }, []);
4892
5037
  React2.useEffect(() => {
4893
- if (!pendingOpenConversationId || !loggedUserDetails) return;
5038
+ if (!pendingOpenRequest?.conversationId || !loggedUserDetails) return;
4894
5039
  let cancelled = false;
4895
5040
  const openPending = async () => {
4896
- const targetId = String(pendingOpenConversationId);
5041
+ const targetId = String(pendingOpenRequest.conversationId);
5042
+ console.log("[realtimex-push] opening conversation:", pendingOpenRequest);
4897
5043
  let conversation = conversations.find(
4898
5044
  (c) => String(c._id || c.id) === targetId
4899
5045
  ) || null;
4900
5046
  if (!conversation) {
4901
- const fromInfo = conversationInfos[targetId];
4902
- if (fromInfo) conversation = fromInfo;
5047
+ conversation = conversationInfos[targetId] || null;
4903
5048
  }
4904
5049
  if (!conversation) {
4905
- if (isFetchingConversations) return;
5050
+ if (isFetchingConversations) {
5051
+ console.log("[realtimex-push] waiting for conversations list\u2026");
5052
+ return;
5053
+ }
4906
5054
  try {
4907
5055
  await fetchConversationInfo2(targetId);
4908
- } catch {
4909
- return;
5056
+ } catch (error) {
5057
+ console.warn("[realtimex-push] fetchConversationInfo failed:", error);
4910
5058
  }
4911
5059
  if (cancelled) return;
4912
5060
  const stateNow = exports.useChatStore.getState();
@@ -4914,12 +5062,17 @@ var useChatController = () => {
4914
5062
  (c) => String(c._id || c.id) === targetId
4915
5063
  ) || stateNow.conversationInfos[targetId] || null;
4916
5064
  }
4917
- if (!conversation || cancelled) return;
4918
- const channel = mapConversationToChannel(
5065
+ const channel = conversation ? mapConversationToChannel(
4919
5066
  conversation,
4920
5067
  loggedUserDetails._id,
4921
5068
  allUsers
5069
+ ) : buildChannelFromPushRequest(
5070
+ pendingOpenRequest,
5071
+ loggedUserDetails._id,
5072
+ allUsers
4922
5073
  );
5074
+ console.log("[realtimex-push] selected channel:", channel);
5075
+ if (cancelled) return;
4923
5076
  setState((prev) => {
4924
5077
  if (prev.activeChannel?.conversationId && String(prev.activeChannel.conversationId) === targetId) {
4925
5078
  return prev;
@@ -4930,15 +5083,16 @@ var useChatController = () => {
4930
5083
  activeConversationId: targetId
4931
5084
  };
4932
5085
  });
4933
- consumePendingOpenConversation();
4934
- setPendingOpenConversationId(null);
5086
+ exports.useChatStore.getState().setActiveConversationId(targetId);
5087
+ consumePendingOpenRequest();
5088
+ setPendingOpenRequest(null);
4935
5089
  };
4936
5090
  void openPending();
4937
5091
  return () => {
4938
5092
  cancelled = true;
4939
5093
  };
4940
5094
  }, [
4941
- pendingOpenConversationId,
5095
+ pendingOpenRequest,
4942
5096
  loggedUserDetails,
4943
5097
  conversations,
4944
5098
  conversationInfos,
@@ -6389,184 +6543,562 @@ var CreateChatDialog = (props) => {
6389
6543
  );
6390
6544
  };
6391
6545
  var CreateChatDialog_default = CreateChatDialog;
6392
- var LoggedUserDetails = ({
6393
- loggedUserDetails,
6394
- onUserSelect,
6395
- isCreateModalOpen = false,
6396
- setIsCreateModalOpen,
6397
- searchQuery = "",
6398
- setSearchQuery
6399
- }) => {
6400
- const { showColorModeToggle } = useChatTheme();
6401
- const user = {
6402
- _id: loggedUserDetails?._id || "1",
6403
- name: formatLoggedUserName(loggedUserDetails?.name),
6404
- email: loggedUserDetails?.email || "user@example.com",
6405
- role: loggedUserDetails?.role || "user",
6406
- isActive: loggedUserDetails?.isActive ?? true,
6407
- createdAt: loggedUserDetails?.createdAt ? new Date(loggedUserDetails.createdAt) : /* @__PURE__ */ new Date(),
6408
- updatedAt: loggedUserDetails?.updatedAt ? new Date(loggedUserDetails.updatedAt) : /* @__PURE__ */ new Date(),
6409
- image: loggedUserDetails?.image || void 0
6410
- };
6411
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "shrink-0 border-b border-(--chat-border) bg-(--chat-surface) px-3 pb-3 pt-3", children: [
6412
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex min-w-0 items-center gap-2.5", children: [
6413
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative shrink-0", children: [
6414
- /* @__PURE__ */ jsxRuntime.jsxs(Avatar, { className: "size-8 ring-1 ring-(--chat-surface)", children: [
6415
- user.image ? /* @__PURE__ */ jsxRuntime.jsx(AvatarImage, { src: user.image, alt: user.name }) : null,
6416
- /* @__PURE__ */ jsxRuntime.jsx(AvatarFallback, { className: "bg-(--chat-theme-10) text-[10px] font-medium text-(--chat-theme)", children: (user.name || "?").charAt(0).toUpperCase() })
6417
- ] }),
6418
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "absolute -bottom-0.5 -right-0.5 size-2 rounded-full border-2 border-(--chat-surface) bg-(--chat-online)" })
6419
- ] }),
6420
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-w-0 flex-1", children: [
6421
- /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "truncate text-xs font-medium leading-tight text-(--chat-text)", children: user.name }),
6422
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "truncate text-[10px] text-(--chat-muted)", children: user.email })
6423
- ] }),
6424
- showColorModeToggle ? /* @__PURE__ */ jsxRuntime.jsx(ChatThemeToggle_default, {}) : null
6425
- ] }),
6426
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-3 flex items-center gap-1.5", children: [
6427
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: chatSidebarSearchShellClass, children: [
6428
- /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Search, { className: "pointer-events-none size-3.5 shrink-0 text-(--chat-muted)" }),
6429
- /* @__PURE__ */ jsxRuntime.jsx(
6430
- "input",
6431
- {
6432
- type: "text",
6433
- inputMode: "search",
6434
- enterKeyHint: "search",
6435
- placeholder: "Search conversations...",
6436
- value: searchQuery,
6437
- onChange: (e) => setSearchQuery?.(e.target.value),
6438
- className: chatSidebarSearchInputClass
6439
- }
6440
- ),
6441
- searchQuery ? /* @__PURE__ */ jsxRuntime.jsx(
6442
- "button",
6443
- {
6444
- type: "button",
6445
- onClick: () => setSearchQuery?.(""),
6446
- className: "flex size-6 shrink-0 items-center justify-center rounded-md text-(--chat-muted) transition-colors hover:bg-(--chat-hover) hover:text-(--chat-text)",
6447
- "aria-label": "Clear search",
6448
- children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { size: 12 })
6449
- }
6450
- ) : null
6451
- ] }),
6452
- /* @__PURE__ */ jsxRuntime.jsx(
6453
- CreateChatDialog_default,
6454
- {
6455
- loggedUserDetails,
6456
- onUserSelect,
6457
- isCreateModalOpen,
6458
- setIsCreateModalOpen
6459
- }
6460
- )
6461
- ] })
6462
- ] });
6463
- };
6464
- var LoggedUserDetails_default = LoggedUserDetails;
6465
- function ScrollArea({
6466
- className,
6467
- children,
6468
- ...props
6469
- }) {
6470
- return /* @__PURE__ */ jsxRuntime.jsxs(
6471
- radixUi.ScrollArea.Root,
6472
- {
6473
- "data-slot": "scroll-area",
6474
- className: cn("relative", className),
6475
- ...props,
6476
- children: [
6477
- /* @__PURE__ */ jsxRuntime.jsx(
6478
- radixUi.ScrollArea.Viewport,
6479
- {
6480
- "data-slot": "scroll-area-viewport",
6481
- className: "focus-visible:ring-ring/50 size-full rounded-[inherit] transition-[color,box-shadow] outline-none focus-visible:ring-[3px] focus-visible:outline-1",
6482
- children
6483
- }
6484
- ),
6485
- /* @__PURE__ */ jsxRuntime.jsx(ScrollBar, {}),
6486
- /* @__PURE__ */ jsxRuntime.jsx(radixUi.ScrollArea.Corner, {})
6487
- ]
6488
- }
6489
- );
6490
- }
6491
- function ScrollBar({
6546
+ function Switch({
6492
6547
  className,
6493
- orientation = "vertical",
6548
+ size = "default",
6494
6549
  ...props
6495
6550
  }) {
6496
6551
  return /* @__PURE__ */ jsxRuntime.jsx(
6497
- radixUi.ScrollArea.ScrollAreaScrollbar,
6552
+ radixUi.Switch.Root,
6498
6553
  {
6499
- "data-slot": "scroll-area-scrollbar",
6500
- orientation,
6554
+ "data-slot": "switch",
6555
+ "data-size": size,
6501
6556
  className: cn(
6502
- "flex touch-none p-px transition-colors select-none",
6503
- orientation === "vertical" && "h-full w-2.5 border-l border-l-transparent",
6504
- orientation === "horizontal" && "h-2.5 flex-col border-t border-t-transparent",
6557
+ "peer group/switch inline-flex shrink-0 items-center rounded-full border border-transparent shadow-xs transition-all outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 data-[size=default]:h-[1.15rem] data-[size=default]:w-8 data-[size=sm]:h-3.5 data-[size=sm]:w-6 data-[state=checked]:bg-primary data-[state=unchecked]:bg-input dark:data-[state=unchecked]:bg-input/80",
6505
6558
  className
6506
6559
  ),
6507
6560
  ...props,
6508
6561
  children: /* @__PURE__ */ jsxRuntime.jsx(
6509
- radixUi.ScrollArea.ScrollAreaThumb,
6562
+ radixUi.Switch.Thumb,
6510
6563
  {
6511
- "data-slot": "scroll-area-thumb",
6512
- className: "bg-border relative flex-1 rounded-full"
6564
+ "data-slot": "switch-thumb",
6565
+ className: cn(
6566
+ "pointer-events-none block rounded-full bg-background ring-0 transition-transform group-data-[size=default]/switch:size-4 group-data-[size=sm]/switch:size-3 data-[state=checked]:translate-x-[calc(100%-2px)] data-[state=unchecked]:translate-x-0 dark:data-[state=checked]:bg-primary-foreground dark:data-[state=unchecked]:bg-foreground"
6567
+ )
6513
6568
  }
6514
6569
  )
6515
6570
  }
6516
6571
  );
6517
6572
  }
6518
6573
 
6519
- // src/chat/channel-list/useChannelList.ts
6520
- init_useStore();
6521
- init_store_helpers();
6522
- var conversationDrafts = /* @__PURE__ */ new Map();
6523
- var DRAFT_PREVIEW_MAX_LENGTH = 50;
6524
- var emptyDraft = () => ({
6525
- messageInput: "",
6526
- attachments: []
6527
- });
6528
- var draftRevision = 0;
6529
- var draftListeners = /* @__PURE__ */ new Set();
6530
- function notifyDraftChange() {
6531
- draftRevision += 1;
6532
- draftListeners.forEach((listener) => listener());
6533
- }
6534
- function subscribeToDrafts(listener) {
6535
- draftListeners.add(listener);
6536
- return () => {
6537
- draftListeners.delete(listener);
6538
- };
6539
- }
6540
- function getDraftRevision() {
6541
- return draftRevision;
6542
- }
6543
- function useDraftRevision() {
6544
- return React2.useSyncExternalStore(subscribeToDrafts, getDraftRevision, getDraftRevision);
6545
- }
6546
- function getConversationDraft(conversationId) {
6547
- if (!conversationId) return emptyDraft();
6548
- return conversationDrafts.get(conversationId) ?? emptyDraft();
6549
- }
6550
- function updateConversationDraft(conversationId, draft) {
6551
- if (!conversationId) return;
6552
- if (draft.messageInput.trim() || draft.attachments.length > 0) {
6553
- conversationDrafts.set(conversationId, {
6554
- messageInput: draft.messageInput,
6555
- attachments: draft.attachments
6556
- });
6557
- } else {
6558
- conversationDrafts.delete(conversationId);
6574
+ // src/notifications/push.service.ts
6575
+ init_client();
6576
+ var PUSH_TOKEN_STORAGE_KEY = "realtimex-push-token";
6577
+ var SERVICE_WORKER_PATH = "/firebase-messaging-sw.js";
6578
+ var PUSH_CHANGED_EVENT = "realtimex:push-changed";
6579
+ var emitPushChanged = (enabled) => {
6580
+ try {
6581
+ window.dispatchEvent(
6582
+ new CustomEvent(PUSH_CHANGED_EVENT, { detail: { enabled } })
6583
+ );
6584
+ } catch {
6559
6585
  }
6560
- notifyDraftChange();
6561
- }
6562
- function clearConversationDraft(conversationId) {
6563
- if (!conversationId) return;
6564
- if (!conversationDrafts.has(conversationId)) return;
6565
- conversationDrafts.delete(conversationId);
6566
- notifyDraftChange();
6567
- }
6568
- function getConversationDraftPreview(conversationId) {
6569
- if (!conversationId) return null;
6586
+ };
6587
+ var v2Url = "https://realtimex.softwareco.com/api/v2";
6588
+ var apiGetPushConfig = async () => {
6589
+ const { data } = await apiClient.get(`${v2Url}/notifications/push/config`);
6590
+ return data?.data;
6591
+ };
6592
+ var apiRegisterPushToken = async (token, deviceLabel) => {
6593
+ const { data } = await apiClient.post(`${v2Url}/notifications/push/token`, {
6594
+ token,
6595
+ deviceLabel
6596
+ });
6597
+ return data?.data;
6598
+ };
6599
+ var apiRemovePushToken = async (token) => {
6600
+ const { data } = await apiClient.delete(`${v2Url}/notifications/push/token`, {
6601
+ data: { token }
6602
+ });
6603
+ return data;
6604
+ };
6605
+ var apiGetPushPreference = async () => {
6606
+ const { data } = await apiClient.get(
6607
+ `${v2Url}/notifications/push/preference`
6608
+ );
6609
+ return data?.data;
6610
+ };
6611
+ var apiUpdatePushPreference = async (enabled) => {
6612
+ const { data } = await apiClient.patch(
6613
+ `${v2Url}/notifications/push/preference`,
6614
+ { enabled }
6615
+ );
6616
+ return data?.data;
6617
+ };
6618
+ var isPushSupported = () => typeof window !== "undefined" && "Notification" in window && "serviceWorker" in navigator && "PushManager" in window;
6619
+ var getStoredPushToken = () => {
6620
+ try {
6621
+ return localStorage.getItem(PUSH_TOKEN_STORAGE_KEY);
6622
+ } catch {
6623
+ return null;
6624
+ }
6625
+ };
6626
+ var storePushToken = (token) => {
6627
+ try {
6628
+ if (token) localStorage.setItem(PUSH_TOKEN_STORAGE_KEY, token);
6629
+ else localStorage.removeItem(PUSH_TOKEN_STORAGE_KEY);
6630
+ } catch {
6631
+ }
6632
+ };
6633
+ var defaultDeviceLabel = () => {
6634
+ const ua = navigator.userAgent;
6635
+ const browser = /edg\//i.test(ua) ? "Edge" : /chrome/i.test(ua) ? "Chrome" : /safari/i.test(ua) ? "Safari" : /firefox/i.test(ua) ? "Firefox" : "Browser";
6636
+ const os = /mac/i.test(ua) ? "macOS" : /windows/i.test(ua) ? "Windows" : /android/i.test(ua) ? "Android" : /iphone|ipad/i.test(ua) ? "iOS" : /linux/i.test(ua) ? "Linux" : "Unknown OS";
6637
+ return `${browser} on ${os}`;
6638
+ };
6639
+ var loadFirebaseMessaging = async () => {
6640
+ try {
6641
+ const [{ initializeApp, getApps }, messagingModule] = await Promise.all([
6642
+ import('firebase/app'),
6643
+ import('firebase/messaging')
6644
+ ]);
6645
+ return { initializeApp, getApps, ...messagingModule };
6646
+ } catch {
6647
+ throw new Error(
6648
+ 'Push notifications need the "firebase" package \u2014 run: npm install firebase'
6649
+ );
6650
+ }
6651
+ };
6652
+ var getFirebaseMessaging = async (firebaseConfig) => {
6653
+ const fb = await loadFirebaseMessaging();
6654
+ const app = fb.getApps().length ? fb.getApps()[0] : fb.initializeApp(firebaseConfig);
6655
+ return { fb, messaging: fb.getMessaging(app) };
6656
+ };
6657
+ var enablePushOnThisDevice = async () => {
6658
+ if (!isPushSupported()) {
6659
+ throw new Error("Push notifications are not supported in this browser");
6660
+ }
6661
+ const config = await apiGetPushConfig();
6662
+ if (!config?.enabled) {
6663
+ throw new Error("Push notifications are disabled for this workspace");
6664
+ }
6665
+ if (!config.configured || !config.firebaseConfig || !config.vapidKey) {
6666
+ throw new Error(
6667
+ "Push notifications are not configured on the server yet"
6668
+ );
6669
+ }
6670
+ const permission = await Notification.requestPermission();
6671
+ if (permission !== "granted") {
6672
+ throw new Error("Notification permission was not granted");
6673
+ }
6674
+ let registration;
6675
+ try {
6676
+ registration = await navigator.serviceWorker.register(
6677
+ `${SERVICE_WORKER_PATH}?config=${encodeURIComponent(
6678
+ JSON.stringify(config.firebaseConfig)
6679
+ )}`
6680
+ );
6681
+ } catch {
6682
+ throw new Error(
6683
+ `Could not register ${SERVICE_WORKER_PATH} \u2014 copy it from node_modules/@realtimexsco/live-chat/dist/firebase-messaging-sw.js into your app's public folder`
6684
+ );
6685
+ }
6686
+ const { fb, messaging } = await getFirebaseMessaging(config.firebaseConfig);
6687
+ const token = await fb.getToken(messaging, {
6688
+ vapidKey: config.vapidKey,
6689
+ serviceWorkerRegistration: registration
6690
+ });
6691
+ if (!token) {
6692
+ throw new Error("Could not obtain a push token from Firebase");
6693
+ }
6694
+ await apiRegisterPushToken(token, defaultDeviceLabel());
6695
+ storePushToken(token);
6696
+ emitPushChanged(true);
6697
+ return token;
6698
+ };
6699
+ var disablePushOnThisDevice = async () => {
6700
+ const token = getStoredPushToken();
6701
+ if (!token) return;
6702
+ try {
6703
+ const config = await apiGetPushConfig();
6704
+ if (config?.firebaseConfig) {
6705
+ const { fb, messaging } = await getFirebaseMessaging(
6706
+ config.firebaseConfig
6707
+ );
6708
+ await fb.deleteToken(messaging).catch(() => void 0);
6709
+ }
6710
+ } catch {
6711
+ }
6712
+ await apiRemovePushToken(token).catch(() => void 0);
6713
+ storePushToken(null);
6714
+ emitPushChanged(false);
6715
+ };
6716
+ var onForegroundPush = async (callback) => {
6717
+ const config = await apiGetPushConfig();
6718
+ if (!config?.configured || !config.firebaseConfig) {
6719
+ throw new Error("Push config unavailable (not configured or API not ready)");
6720
+ }
6721
+ const { fb, messaging } = await getFirebaseMessaging(config.firebaseConfig);
6722
+ return fb.onMessage(messaging, (payload) => {
6723
+ console.log("[realtimex-push] FCM foreground payload (raw):", payload);
6724
+ console.log("[realtimex-push] FCM foreground data:", payload?.data);
6725
+ console.log("[realtimex-push] FCM foreground notification:", payload?.notification);
6726
+ callback({
6727
+ title: payload?.notification?.title,
6728
+ body: payload?.notification?.body,
6729
+ data: payload?.data
6730
+ });
6731
+ });
6732
+ };
6733
+
6734
+ // src/notifications/usePushNotifications.ts
6735
+ var usePushNotifications = () => {
6736
+ const [state, setState] = React2.useState({
6737
+ supported: false,
6738
+ allowedByWorkspace: false,
6739
+ configured: false,
6740
+ permission: "unsupported",
6741
+ userEnabled: true,
6742
+ deviceEnabled: false,
6743
+ loading: true,
6744
+ error: null
6745
+ });
6746
+ const refresh = React2.useCallback(async () => {
6747
+ if (!isPushSupported()) {
6748
+ setState((prev) => ({
6749
+ ...prev,
6750
+ supported: false,
6751
+ loading: false
6752
+ }));
6753
+ return;
6754
+ }
6755
+ try {
6756
+ const [config, preference] = await Promise.all([
6757
+ apiGetPushConfig(),
6758
+ apiGetPushPreference().catch(() => null)
6759
+ ]);
6760
+ setState({
6761
+ supported: true,
6762
+ allowedByWorkspace: Boolean(config?.enabled),
6763
+ configured: Boolean(config?.configured),
6764
+ permission: Notification.permission,
6765
+ userEnabled: preference?.enabled ?? true,
6766
+ deviceEnabled: Notification.permission === "granted" && Boolean(getStoredPushToken()),
6767
+ loading: false,
6768
+ error: null
6769
+ });
6770
+ } catch (error) {
6771
+ setState((prev) => ({
6772
+ ...prev,
6773
+ supported: true,
6774
+ loading: false,
6775
+ error: error instanceof Error ? error.message : "Could not load push settings"
6776
+ }));
6777
+ }
6778
+ }, []);
6779
+ React2.useEffect(() => {
6780
+ void refresh();
6781
+ }, [refresh]);
6782
+ const enable = React2.useCallback(async () => {
6783
+ setState((prev) => ({ ...prev, loading: true, error: null }));
6784
+ try {
6785
+ await enablePushOnThisDevice();
6786
+ await refresh();
6787
+ return true;
6788
+ } catch (error) {
6789
+ setState((prev) => ({
6790
+ ...prev,
6791
+ loading: false,
6792
+ permission: isPushSupported() ? Notification.permission : "unsupported",
6793
+ error: error instanceof Error ? error.message : "Could not enable push notifications"
6794
+ }));
6795
+ return false;
6796
+ }
6797
+ }, [refresh]);
6798
+ const disable = React2.useCallback(async () => {
6799
+ setState((prev) => ({ ...prev, loading: true, error: null }));
6800
+ try {
6801
+ await disablePushOnThisDevice();
6802
+ } finally {
6803
+ await refresh();
6804
+ }
6805
+ }, [refresh]);
6806
+ const setUserEnabled = React2.useCallback(
6807
+ async (enabled) => {
6808
+ setState((prev) => ({ ...prev, userEnabled: enabled }));
6809
+ try {
6810
+ await apiUpdatePushPreference(enabled);
6811
+ } catch (error) {
6812
+ setState((prev) => ({
6813
+ ...prev,
6814
+ userEnabled: !enabled,
6815
+ error: error instanceof Error ? error.message : "Could not update push preference"
6816
+ }));
6817
+ }
6818
+ },
6819
+ []
6820
+ );
6821
+ return { ...state, enable, disable, setUserEnabled, refresh };
6822
+ };
6823
+ var PushNotificationToggle = ({
6824
+ className,
6825
+ label = "Push notifications",
6826
+ description = "Get notified about new messages on this device"
6827
+ }) => {
6828
+ const push = usePushNotifications();
6829
+ if (!push.supported || !push.allowedByWorkspace || !push.configured) {
6830
+ return null;
6831
+ }
6832
+ const isOn = push.deviceEnabled && push.userEnabled;
6833
+ const permissionBlocked = push.permission === "denied";
6834
+ const handleChange = async (checked) => {
6835
+ if (checked) {
6836
+ if (!push.userEnabled) await push.setUserEnabled(true);
6837
+ if (!push.deviceEnabled) await push.enable();
6838
+ } else {
6839
+ await push.disable();
6840
+ }
6841
+ };
6842
+ return /* @__PURE__ */ jsxRuntime.jsxs(
6843
+ "div",
6844
+ {
6845
+ className: className ?? "flex items-center justify-between gap-4 rounded-xl border border-(--chat-border) bg-(--chat-surface) px-4 py-3",
6846
+ children: [
6847
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-0.5", children: [
6848
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm font-medium text-(--chat-text)", children: label }),
6849
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-(--chat-muted)", children: permissionBlocked ? "Notifications are blocked in your browser settings" : push.error ?? description })
6850
+ ] }),
6851
+ /* @__PURE__ */ jsxRuntime.jsx(
6852
+ Switch,
6853
+ {
6854
+ checked: isOn,
6855
+ disabled: push.loading || permissionBlocked,
6856
+ onCheckedChange: (checked) => void handleChange(checked),
6857
+ "aria-label": `Toggle ${label}`
6858
+ }
6859
+ )
6860
+ ]
6861
+ }
6862
+ );
6863
+ };
6864
+ var LoggedUserSettingsDialog = ({
6865
+ loggedUserDetails
6866
+ }) => {
6867
+ const [open, setOpen] = React2.useState(false);
6868
+ const user = {
6869
+ _id: loggedUserDetails?._id || "1",
6870
+ name: formatLoggedUserName(loggedUserDetails?.name),
6871
+ email: loggedUserDetails?.email || "user@example.com",
6872
+ role: loggedUserDetails?.role || "user",
6873
+ isActive: loggedUserDetails?.isActive ?? true,
6874
+ createdAt: loggedUserDetails?.createdAt ? new Date(loggedUserDetails.createdAt) : /* @__PURE__ */ new Date(),
6875
+ updatedAt: loggedUserDetails?.updatedAt ? new Date(loggedUserDetails.updatedAt) : /* @__PURE__ */ new Date(),
6876
+ image: loggedUserDetails?.image || void 0
6877
+ };
6878
+ return /* @__PURE__ */ jsxRuntime.jsxs(Dialog, { open, onOpenChange: setOpen, children: [
6879
+ /* @__PURE__ */ jsxRuntime.jsxs(Tooltip, { children: [
6880
+ /* @__PURE__ */ jsxRuntime.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(
6881
+ Button,
6882
+ {
6883
+ type: "button",
6884
+ variant: "ghost",
6885
+ size: "icon",
6886
+ className: sidebarActionBtnClass,
6887
+ "aria-label": "Account settings",
6888
+ onClick: () => setOpen(true),
6889
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Settings, { className: "size-3.5", strokeWidth: 2 })
6890
+ }
6891
+ ) }),
6892
+ /* @__PURE__ */ jsxRuntime.jsx(TooltipContent, { side: "bottom", children: "Settings" })
6893
+ ] }),
6894
+ /* @__PURE__ */ jsxRuntime.jsxs(ChatDialogContent, { className: "gap-0 overflow-hidden p-0 sm:max-w-[380px]", children: [
6895
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "border-b border-(--chat-border) px-5 py-4", children: /* @__PURE__ */ jsxRuntime.jsx(DialogTitle, { className: "text-left text-base font-semibold text-(--chat-text)", children: "Account settings" }) }),
6896
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-5 px-5 py-5", children: [
6897
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col items-center text-center", children: [
6898
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative", children: [
6899
+ /* @__PURE__ */ jsxRuntime.jsxs(Avatar, { className: "size-16 ring-2 ring-(--chat-theme-20)", children: [
6900
+ user.image ? /* @__PURE__ */ jsxRuntime.jsx(AvatarImage, { src: user.image, alt: user.name }) : null,
6901
+ /* @__PURE__ */ jsxRuntime.jsx(AvatarFallback, { className: "bg-(--chat-theme-10) text-lg font-medium text-(--chat-theme)", children: (user.name || "?").charAt(0).toUpperCase() })
6902
+ ] }),
6903
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "absolute bottom-0.5 right-0.5 size-3 rounded-full border-2 border-(--chat-surface) bg-(--chat-online)" })
6904
+ ] }),
6905
+ /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "mt-3 text-base font-semibold text-(--chat-text)", children: user.name }),
6906
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-0.5 text-sm text-(--chat-muted)", children: user.email }),
6907
+ /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "mt-2 inline-flex items-center gap-1.5 rounded-full bg-(--chat-theme-10) px-2.5 py-0.5 text-xs font-medium text-(--chat-theme)", children: [
6908
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "size-1.5 rounded-full bg-(--chat-online)" }),
6909
+ "Online"
6910
+ ] })
6911
+ ] }),
6912
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "border-t border-(--chat-border) pt-4", children: /* @__PURE__ */ jsxRuntime.jsx(PushNotificationToggle, { className: "flex items-center justify-between gap-4 rounded-xl border border-(--chat-border) bg-(--chat-theme-10) px-4 py-3" }) })
6913
+ ] })
6914
+ ] })
6915
+ ] });
6916
+ };
6917
+ var LoggedUserSettingsDialog_default = LoggedUserSettingsDialog;
6918
+ var LoggedUserDetails = ({
6919
+ loggedUserDetails,
6920
+ onUserSelect,
6921
+ isCreateModalOpen = false,
6922
+ setIsCreateModalOpen,
6923
+ searchQuery = "",
6924
+ setSearchQuery
6925
+ }) => {
6926
+ const { showColorModeToggle } = useChatTheme();
6927
+ const user = {
6928
+ _id: loggedUserDetails?._id || "1",
6929
+ name: formatLoggedUserName(loggedUserDetails?.name),
6930
+ email: loggedUserDetails?.email || "user@example.com",
6931
+ role: loggedUserDetails?.role || "user",
6932
+ isActive: loggedUserDetails?.isActive ?? true,
6933
+ createdAt: loggedUserDetails?.createdAt ? new Date(loggedUserDetails.createdAt) : /* @__PURE__ */ new Date(),
6934
+ updatedAt: loggedUserDetails?.updatedAt ? new Date(loggedUserDetails.updatedAt) : /* @__PURE__ */ new Date(),
6935
+ image: loggedUserDetails?.image || void 0
6936
+ };
6937
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "shrink-0 border-b border-(--chat-border) bg-(--chat-surface) px-3 pb-3 pt-3", children: [
6938
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex min-w-0 items-center gap-2.5", children: [
6939
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative shrink-0", children: [
6940
+ /* @__PURE__ */ jsxRuntime.jsxs(Avatar, { className: "size-8 ring-1 ring-(--chat-surface)", children: [
6941
+ user.image ? /* @__PURE__ */ jsxRuntime.jsx(AvatarImage, { src: user.image, alt: user.name }) : null,
6942
+ /* @__PURE__ */ jsxRuntime.jsx(AvatarFallback, { className: "bg-(--chat-theme-10) text-[10px] font-medium text-(--chat-theme)", children: (user.name || "?").charAt(0).toUpperCase() })
6943
+ ] }),
6944
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "absolute -bottom-0.5 -right-0.5 size-2 rounded-full border-2 border-(--chat-surface) bg-(--chat-online)" })
6945
+ ] }),
6946
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-w-0 flex-1", children: [
6947
+ /* @__PURE__ */ jsxRuntime.jsxs("h2", { className: "truncate text-xs font-medium leading-tight text-(--chat-text)", children: [
6948
+ user.name,
6949
+ "123"
6950
+ ] }),
6951
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "truncate text-[10px] text-(--chat-muted)", children: user.email })
6952
+ ] }),
6953
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex shrink-0 items-center gap-0.5", children: [
6954
+ /* @__PURE__ */ jsxRuntime.jsx(LoggedUserSettingsDialog_default, { loggedUserDetails }),
6955
+ showColorModeToggle ? /* @__PURE__ */ jsxRuntime.jsx(ChatThemeToggle_default, {}) : null
6956
+ ] })
6957
+ ] }),
6958
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-3 flex items-center gap-1.5", children: [
6959
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: chatSidebarSearchShellClass, children: [
6960
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Search, { className: "pointer-events-none size-3.5 shrink-0 text-(--chat-muted)" }),
6961
+ /* @__PURE__ */ jsxRuntime.jsx(
6962
+ "input",
6963
+ {
6964
+ type: "text",
6965
+ inputMode: "search",
6966
+ enterKeyHint: "search",
6967
+ placeholder: "Search conversations...",
6968
+ value: searchQuery,
6969
+ onChange: (e) => setSearchQuery?.(e.target.value),
6970
+ className: chatSidebarSearchInputClass
6971
+ }
6972
+ ),
6973
+ searchQuery ? /* @__PURE__ */ jsxRuntime.jsx(
6974
+ "button",
6975
+ {
6976
+ type: "button",
6977
+ onClick: () => setSearchQuery?.(""),
6978
+ className: "flex size-6 shrink-0 items-center justify-center rounded-md text-(--chat-muted) transition-colors hover:bg-(--chat-hover) hover:text-(--chat-text)",
6979
+ "aria-label": "Clear search",
6980
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { size: 12 })
6981
+ }
6982
+ ) : null
6983
+ ] }),
6984
+ /* @__PURE__ */ jsxRuntime.jsx(
6985
+ CreateChatDialog_default,
6986
+ {
6987
+ loggedUserDetails,
6988
+ onUserSelect,
6989
+ isCreateModalOpen,
6990
+ setIsCreateModalOpen
6991
+ }
6992
+ )
6993
+ ] })
6994
+ ] });
6995
+ };
6996
+ var LoggedUserDetails_default = LoggedUserDetails;
6997
+ function ScrollArea({
6998
+ className,
6999
+ children,
7000
+ ...props
7001
+ }) {
7002
+ return /* @__PURE__ */ jsxRuntime.jsxs(
7003
+ radixUi.ScrollArea.Root,
7004
+ {
7005
+ "data-slot": "scroll-area",
7006
+ className: cn("relative", className),
7007
+ ...props,
7008
+ children: [
7009
+ /* @__PURE__ */ jsxRuntime.jsx(
7010
+ radixUi.ScrollArea.Viewport,
7011
+ {
7012
+ "data-slot": "scroll-area-viewport",
7013
+ className: "focus-visible:ring-ring/50 size-full rounded-[inherit] transition-[color,box-shadow] outline-none focus-visible:ring-[3px] focus-visible:outline-1",
7014
+ children
7015
+ }
7016
+ ),
7017
+ /* @__PURE__ */ jsxRuntime.jsx(ScrollBar, {}),
7018
+ /* @__PURE__ */ jsxRuntime.jsx(radixUi.ScrollArea.Corner, {})
7019
+ ]
7020
+ }
7021
+ );
7022
+ }
7023
+ function ScrollBar({
7024
+ className,
7025
+ orientation = "vertical",
7026
+ ...props
7027
+ }) {
7028
+ return /* @__PURE__ */ jsxRuntime.jsx(
7029
+ radixUi.ScrollArea.ScrollAreaScrollbar,
7030
+ {
7031
+ "data-slot": "scroll-area-scrollbar",
7032
+ orientation,
7033
+ className: cn(
7034
+ "flex touch-none p-px transition-colors select-none",
7035
+ orientation === "vertical" && "h-full w-2.5 border-l border-l-transparent",
7036
+ orientation === "horizontal" && "h-2.5 flex-col border-t border-t-transparent",
7037
+ className
7038
+ ),
7039
+ ...props,
7040
+ children: /* @__PURE__ */ jsxRuntime.jsx(
7041
+ radixUi.ScrollArea.ScrollAreaThumb,
7042
+ {
7043
+ "data-slot": "scroll-area-thumb",
7044
+ className: "bg-border relative flex-1 rounded-full"
7045
+ }
7046
+ )
7047
+ }
7048
+ );
7049
+ }
7050
+
7051
+ // src/chat/channel-list/useChannelList.ts
7052
+ init_useStore();
7053
+ init_store_helpers();
7054
+ var conversationDrafts = /* @__PURE__ */ new Map();
7055
+ var DRAFT_PREVIEW_MAX_LENGTH = 50;
7056
+ var emptyDraft = () => ({
7057
+ messageInput: "",
7058
+ attachments: []
7059
+ });
7060
+ var draftRevision = 0;
7061
+ var draftListeners = /* @__PURE__ */ new Set();
7062
+ function notifyDraftChange() {
7063
+ draftRevision += 1;
7064
+ draftListeners.forEach((listener) => listener());
7065
+ }
7066
+ function subscribeToDrafts(listener) {
7067
+ draftListeners.add(listener);
7068
+ return () => {
7069
+ draftListeners.delete(listener);
7070
+ };
7071
+ }
7072
+ function getDraftRevision() {
7073
+ return draftRevision;
7074
+ }
7075
+ function useDraftRevision() {
7076
+ return React2.useSyncExternalStore(subscribeToDrafts, getDraftRevision, getDraftRevision);
7077
+ }
7078
+ function getConversationDraft(conversationId) {
7079
+ if (!conversationId) return emptyDraft();
7080
+ return conversationDrafts.get(conversationId) ?? emptyDraft();
7081
+ }
7082
+ function updateConversationDraft(conversationId, draft) {
7083
+ if (!conversationId) return;
7084
+ if (draft.messageInput.trim() || draft.attachments.length > 0) {
7085
+ conversationDrafts.set(conversationId, {
7086
+ messageInput: draft.messageInput,
7087
+ attachments: draft.attachments
7088
+ });
7089
+ } else {
7090
+ conversationDrafts.delete(conversationId);
7091
+ }
7092
+ notifyDraftChange();
7093
+ }
7094
+ function clearConversationDraft(conversationId) {
7095
+ if (!conversationId) return;
7096
+ if (!conversationDrafts.has(conversationId)) return;
7097
+ conversationDrafts.delete(conversationId);
7098
+ notifyDraftChange();
7099
+ }
7100
+ function getConversationDraftPreview(conversationId) {
7101
+ if (!conversationId) return null;
6570
7102
  const draft = conversationDrafts.get(conversationId);
6571
7103
  if (!draft) return null;
6572
7104
  const text = draft.messageInput.trim();
@@ -12131,52 +12663,23 @@ var ChannelDetailsDrawer = (props) => {
12131
12663
  participantCount > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-wrap gap-1.5 pb-1", children: participants.map((p, index) => {
12132
12664
  const pId = typeof p === "string" ? p : p._id;
12133
12665
  const pName = typeof p === "string" ? "Unknown" : p.name;
12134
- if (!pId) return null;
12135
- return /* @__PURE__ */ jsxRuntime.jsx(
12136
- "span",
12137
- {
12138
- className: "rounded-md border border-(--chat-border) bg-(--chat-input-bg)/50 px-2 py-0.5 text-[10px] font-medium text-(--chat-text-secondary)",
12139
- children: pName
12140
- },
12141
- `${pId}-${index}`
12142
- );
12143
- }) })
12144
- ] })
12145
- ] })
12146
- ] })
12147
- ] })
12148
- ] }) });
12149
- };
12150
- var ChannelDetailsDrawer_default = ChannelDetailsDrawer;
12151
- function Switch({
12152
- className,
12153
- size = "default",
12154
- ...props
12155
- }) {
12156
- return /* @__PURE__ */ jsxRuntime.jsx(
12157
- radixUi.Switch.Root,
12158
- {
12159
- "data-slot": "switch",
12160
- "data-size": size,
12161
- className: cn(
12162
- "peer group/switch inline-flex shrink-0 items-center rounded-full border border-transparent shadow-xs transition-all outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 data-[size=default]:h-[1.15rem] data-[size=default]:w-8 data-[size=sm]:h-3.5 data-[size=sm]:w-6 data-[state=checked]:bg-primary data-[state=unchecked]:bg-input dark:data-[state=unchecked]:bg-input/80",
12163
- className
12164
- ),
12165
- ...props,
12166
- children: /* @__PURE__ */ jsxRuntime.jsx(
12167
- radixUi.Switch.Thumb,
12168
- {
12169
- "data-slot": "switch-thumb",
12170
- className: cn(
12171
- "pointer-events-none block rounded-full bg-background ring-0 transition-transform group-data-[size=default]/switch:size-4 group-data-[size=sm]/switch:size-3 data-[state=checked]:translate-x-[calc(100%-2px)] data-[state=unchecked]:translate-x-0 dark:data-[state=checked]:bg-primary-foreground dark:data-[state=unchecked]:bg-foreground"
12172
- )
12173
- }
12174
- )
12175
- }
12176
- );
12177
- }
12178
-
12179
- // src/chat/header/PermissionDrawer.tsx
12666
+ if (!pId) return null;
12667
+ return /* @__PURE__ */ jsxRuntime.jsx(
12668
+ "span",
12669
+ {
12670
+ className: "rounded-md border border-(--chat-border) bg-(--chat-input-bg)/50 px-2 py-0.5 text-[10px] font-medium text-(--chat-text-secondary)",
12671
+ children: pName
12672
+ },
12673
+ `${pId}-${index}`
12674
+ );
12675
+ }) })
12676
+ ] })
12677
+ ] })
12678
+ ] })
12679
+ ] })
12680
+ ] }) });
12681
+ };
12682
+ var ChannelDetailsDrawer_default = ChannelDetailsDrawer;
12180
12683
  init_useStore();
12181
12684
  var PermissionDrawer = ({ isOpen, onOpenChange, conversationId, conversationInfo }) => {
12182
12685
  const updateGroupPermissions = exports.useChatStore((s) => s.updateGroupPermissions);
@@ -17568,251 +18071,15 @@ function ChatViewport({
17568
18071
  "chat-package-viewport flex min-h-0 w-full flex-col overflow-hidden",
17569
18072
  resolved.className,
17570
18073
  className
17571
- ),
17572
- style: { ...resolved.style, ...style },
17573
- children
17574
- }
17575
- );
17576
- }
17577
- var ChatViewport_default = ChatViewport;
17578
- var showNotification = (message, type = "info", icon, duration) => {
17579
- const options = {
17580
- icon,
17581
- duration
17582
- };
17583
- switch (type) {
17584
- case "success":
17585
- toast2.toast.success(message, options);
17586
- break;
17587
- case "error":
17588
- toast2.toast.error(message, options);
17589
- break;
17590
- case "info":
17591
- toast2.toast(message, options);
17592
- break;
17593
- case "warning":
17594
- toast2.toast(message, { ...options, icon: "\u26A0\uFE0F" });
17595
- break;
17596
- default:
17597
- toast2.toast(message, options);
17598
- }
17599
- };
17600
- var downloadFile = async (src, name) => {
17601
- try {
17602
- const link = document.createElement("a");
17603
- link.href = src;
17604
- link.setAttribute("download", name);
17605
- link.setAttribute("rel", "noopener noreferrer");
17606
- link.click();
17607
- showNotification("File Downloaded Successfully", "success");
17608
- } catch (err) {
17609
- console.error("File download failed:", err);
17610
- showNotification("Failed to download file", "error");
17611
- }
17612
- };
17613
- var getResponsiveWidth = (width) => {
17614
- const screenWidth = window.innerWidth;
17615
- if (width) {
17616
- return width;
17617
- } else if (screenWidth < 640) {
17618
- return 350;
17619
- } else if (screenWidth >= 640 && screenWidth < 1024) {
17620
- return 600;
17621
- } else if (screenWidth >= 1024 && screenWidth < 1280) {
17622
- return 800;
17623
- } else {
17624
- return 1e3;
17625
- }
17626
- };
17627
- var isEmpty = (value) => {
17628
- if (value === null || value === void 0) return true;
17629
- if (typeof value === "string") return value.trim().length === 0;
17630
- if (Array.isArray(value)) {
17631
- return value.length === 0 || value.every((item) => isEmpty(item));
17632
- }
17633
- if (typeof value === "object") {
17634
- return Object.keys(value).length === 0;
17635
- }
17636
- return false;
17637
- };
17638
- function uniqueList(list, key) {
17639
- const seen = /* @__PURE__ */ new Set();
17640
- return list.filter((item) => {
17641
- const value = item[key];
17642
- if (seen.has(value)) return false;
17643
- seen.add(value);
17644
- return true;
17645
- });
17646
- }
17647
- var capitalizeFirst = (str) => str.charAt(0).toUpperCase() + str.slice(1);
17648
- var getFirstAndLastNameOneCharacter = (name) => {
17649
- const names = name.split(" ");
17650
- return names.map((name2) => name2.charAt(0).toUpperCase()).join("");
17651
- };
17652
-
17653
- // src/notifications/ForegroundPushBridge.tsx
17654
- init_normalize_helpers();
17655
- init_useStore();
17656
-
17657
- // src/notifications/push.service.ts
17658
- init_client();
17659
- var PUSH_TOKEN_STORAGE_KEY = "realtimex-push-token";
17660
- var SERVICE_WORKER_PATH = "/firebase-messaging-sw.js";
17661
- var PUSH_CHANGED_EVENT = "realtimex:push-changed";
17662
- var emitPushChanged = (enabled) => {
17663
- try {
17664
- window.dispatchEvent(
17665
- new CustomEvent(PUSH_CHANGED_EVENT, { detail: { enabled } })
17666
- );
17667
- } catch {
17668
- }
17669
- };
17670
- var v2Url = "https://realtimex.softwareco.com/api/v2";
17671
- var apiGetPushConfig = async () => {
17672
- const { data } = await apiClient.get(`${v2Url}/notifications/push/config`);
17673
- return data?.data;
17674
- };
17675
- var apiRegisterPushToken = async (token, deviceLabel) => {
17676
- const { data } = await apiClient.post(`${v2Url}/notifications/push/token`, {
17677
- token,
17678
- deviceLabel
17679
- });
17680
- return data?.data;
17681
- };
17682
- var apiRemovePushToken = async (token) => {
17683
- const { data } = await apiClient.delete(`${v2Url}/notifications/push/token`, {
17684
- data: { token }
17685
- });
17686
- return data;
17687
- };
17688
- var apiGetPushPreference = async () => {
17689
- const { data } = await apiClient.get(
17690
- `${v2Url}/notifications/push/preference`
17691
- );
17692
- return data?.data;
17693
- };
17694
- var apiUpdatePushPreference = async (enabled) => {
17695
- const { data } = await apiClient.patch(
17696
- `${v2Url}/notifications/push/preference`,
17697
- { enabled }
17698
- );
17699
- return data?.data;
17700
- };
17701
- var isPushSupported = () => typeof window !== "undefined" && "Notification" in window && "serviceWorker" in navigator && "PushManager" in window;
17702
- var getStoredPushToken = () => {
17703
- try {
17704
- return localStorage.getItem(PUSH_TOKEN_STORAGE_KEY);
17705
- } catch {
17706
- return null;
17707
- }
17708
- };
17709
- var storePushToken = (token) => {
17710
- try {
17711
- if (token) localStorage.setItem(PUSH_TOKEN_STORAGE_KEY, token);
17712
- else localStorage.removeItem(PUSH_TOKEN_STORAGE_KEY);
17713
- } catch {
17714
- }
17715
- };
17716
- var defaultDeviceLabel = () => {
17717
- const ua = navigator.userAgent;
17718
- const browser = /edg\//i.test(ua) ? "Edge" : /chrome/i.test(ua) ? "Chrome" : /safari/i.test(ua) ? "Safari" : /firefox/i.test(ua) ? "Firefox" : "Browser";
17719
- const os = /mac/i.test(ua) ? "macOS" : /windows/i.test(ua) ? "Windows" : /android/i.test(ua) ? "Android" : /iphone|ipad/i.test(ua) ? "iOS" : /linux/i.test(ua) ? "Linux" : "Unknown OS";
17720
- return `${browser} on ${os}`;
17721
- };
17722
- var loadFirebaseMessaging = async () => {
17723
- try {
17724
- const [{ initializeApp, getApps }, messagingModule] = await Promise.all([
17725
- import('firebase/app'),
17726
- import('firebase/messaging')
17727
- ]);
17728
- return { initializeApp, getApps, ...messagingModule };
17729
- } catch {
17730
- throw new Error(
17731
- 'Push notifications need the "firebase" package \u2014 run: npm install firebase'
17732
- );
17733
- }
17734
- };
17735
- var getFirebaseMessaging = async (firebaseConfig) => {
17736
- const fb = await loadFirebaseMessaging();
17737
- const app = fb.getApps().length ? fb.getApps()[0] : fb.initializeApp(firebaseConfig);
17738
- return { fb, messaging: fb.getMessaging(app) };
17739
- };
17740
- var enablePushOnThisDevice = async () => {
17741
- if (!isPushSupported()) {
17742
- throw new Error("Push notifications are not supported in this browser");
17743
- }
17744
- const config = await apiGetPushConfig();
17745
- if (!config?.enabled) {
17746
- throw new Error("Push notifications are disabled for this workspace");
17747
- }
17748
- if (!config.configured || !config.firebaseConfig || !config.vapidKey) {
17749
- throw new Error(
17750
- "Push notifications are not configured on the server yet"
17751
- );
17752
- }
17753
- const permission = await Notification.requestPermission();
17754
- if (permission !== "granted") {
17755
- throw new Error("Notification permission was not granted");
17756
- }
17757
- let registration;
17758
- try {
17759
- registration = await navigator.serviceWorker.register(
17760
- `${SERVICE_WORKER_PATH}?config=${encodeURIComponent(
17761
- JSON.stringify(config.firebaseConfig)
17762
- )}`
17763
- );
17764
- } catch {
17765
- throw new Error(
17766
- `Could not register ${SERVICE_WORKER_PATH} \u2014 copy it from node_modules/@realtimexsco/live-chat/dist/firebase-messaging-sw.js into your app's public folder`
17767
- );
17768
- }
17769
- const { fb, messaging } = await getFirebaseMessaging(config.firebaseConfig);
17770
- const token = await fb.getToken(messaging, {
17771
- vapidKey: config.vapidKey,
17772
- serviceWorkerRegistration: registration
17773
- });
17774
- if (!token) {
17775
- throw new Error("Could not obtain a push token from Firebase");
17776
- }
17777
- await apiRegisterPushToken(token, defaultDeviceLabel());
17778
- storePushToken(token);
17779
- emitPushChanged(true);
17780
- return token;
17781
- };
17782
- var disablePushOnThisDevice = async () => {
17783
- const token = getStoredPushToken();
17784
- if (!token) return;
17785
- try {
17786
- const config = await apiGetPushConfig();
17787
- if (config?.firebaseConfig) {
17788
- const { fb, messaging } = await getFirebaseMessaging(
17789
- config.firebaseConfig
17790
- );
17791
- await fb.deleteToken(messaging).catch(() => void 0);
17792
- }
17793
- } catch {
17794
- }
17795
- await apiRemovePushToken(token).catch(() => void 0);
17796
- storePushToken(null);
17797
- emitPushChanged(false);
17798
- };
17799
- var onForegroundPush = async (callback) => {
17800
- const config = await apiGetPushConfig();
17801
- if (!config?.configured || !config.firebaseConfig) {
17802
- throw new Error("Push config unavailable (not configured or API not ready)");
17803
- }
17804
- const { fb, messaging } = await getFirebaseMessaging(config.firebaseConfig);
17805
- return fb.onMessage(messaging, (payload) => {
17806
- console.log("[realtimex-push] FCM foreground payload (raw):", payload);
17807
- console.log("[realtimex-push] FCM foreground data:", payload?.data);
17808
- console.log("[realtimex-push] FCM foreground notification:", payload?.notification);
17809
- callback({
17810
- title: payload?.notification?.title,
17811
- body: payload?.notification?.body,
17812
- data: payload?.data
17813
- });
17814
- });
17815
- };
18074
+ ),
18075
+ style: { ...resolved.style, ...style },
18076
+ children
18077
+ }
18078
+ );
18079
+ }
18080
+ var ChatViewport_default = ChatViewport;
18081
+ init_normalize_helpers();
18082
+ init_useStore();
17816
18083
  var RETRY_DELAYS_MS = [1e3, 3e3, 8e3, 2e4];
17817
18084
  var recentToastIds = /* @__PURE__ */ new Set();
17818
18085
  var rememberToastId = (id) => {
@@ -17826,48 +18093,95 @@ var rememberToastId = (id) => {
17826
18093
  }
17827
18094
  return true;
17828
18095
  };
17829
- var resolveToastText = (title, body) => {
17830
- if (title && body) return `${title}: ${body}`;
17831
- return title || body || "";
18096
+ var truncate = (value, max) => value.length > max ? `${value.slice(0, max - 1)}\u2026` : value;
18097
+ var PushMessageToast = ({
18098
+ t,
18099
+ title,
18100
+ body,
18101
+ onOpen
18102
+ }) => {
18103
+ const dismiss = () => toast__default.default.dismiss(t.id);
18104
+ const handleDismiss = (event) => {
18105
+ event.stopPropagation();
18106
+ dismiss();
18107
+ };
18108
+ const handleOpen = () => {
18109
+ dismiss();
18110
+ onOpen?.();
18111
+ };
18112
+ return /* @__PURE__ */ jsxRuntime.jsxs(
18113
+ "div",
18114
+ {
18115
+ role: "alert",
18116
+ "aria-live": "polite",
18117
+ onClick: onOpen ? handleOpen : void 0,
18118
+ onKeyDown: onOpen ? (event) => {
18119
+ if (event.key === "Enter" || event.key === " ") {
18120
+ event.preventDefault();
18121
+ handleOpen();
18122
+ }
18123
+ } : void 0,
18124
+ tabIndex: onOpen ? 0 : void 0,
18125
+ className: cn(
18126
+ "pointer-events-auto flex w-[min(320px,calc(100vw-24px))] items-start gap-3 rounded-xl border border-(--chat-border) bg-(--chat-surface) p-3 shadow-[0_8px_24px_rgba(15,23,42,0.12)] transition-[opacity,transform] duration-200 ease-out",
18127
+ onOpen && "cursor-pointer hover:border-(--chat-theme-20) hover:shadow-[0_10px_28px_rgba(15,23,42,0.16)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-(--chat-theme-30)",
18128
+ t.visible ? "translate-y-0 opacity-100" : "-translate-y-1 opacity-0"
18129
+ ),
18130
+ children: [
18131
+ /* @__PURE__ */ jsxRuntime.jsx(
18132
+ "div",
18133
+ {
18134
+ "aria-hidden": true,
18135
+ className: "flex size-9 shrink-0 items-center justify-center rounded-lg bg-(--chat-theme-10) text-(--chat-theme)",
18136
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.MessageSquare, { className: "size-4", strokeWidth: 2.25 })
18137
+ }
18138
+ ),
18139
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-w-0 flex-1 pt-0.5", children: [
18140
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-[11px] font-semibold uppercase tracking-wide text-(--chat-muted)", children: "New message" }),
18141
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-0.5 truncate text-sm font-semibold leading-snug text-(--chat-text)", children: truncate(title, 40) }),
18142
+ body ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-0.5 line-clamp-2 text-xs leading-relaxed text-(--chat-muted)", children: truncate(body, 72) }) : null
18143
+ ] }),
18144
+ /* @__PURE__ */ jsxRuntime.jsx(
18145
+ "button",
18146
+ {
18147
+ type: "button",
18148
+ "aria-label": "Dismiss notification",
18149
+ onClick: handleDismiss,
18150
+ className: "shrink-0 rounded-md p-1 text-(--chat-muted) transition-colors hover:bg-(--chat-hover) hover:text-(--chat-text) focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-(--chat-theme-30)",
18151
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { className: "size-4", strokeWidth: 2 })
18152
+ }
18153
+ )
18154
+ ]
18155
+ }
18156
+ );
17832
18157
  };
17833
18158
  var emitToast = (payload, onPush) => {
17834
18159
  if (onPush && onPush(payload) !== false) return;
17835
- const title = payload.title || payload.data?.title;
17836
- const body = payload.body || payload.data?.body || payload.data?.message;
17837
- const text = resolveToastText(title, body);
18160
+ const title = payload.title || payload.data?.title || "New message";
18161
+ const body = payload.body || payload.data?.body || payload.data?.message || "";
17838
18162
  const conversationId = resolveConversationIdFromPushData(payload.data);
17839
- if (!text) return;
17840
- if (conversationId) {
17841
- toast2__default.default.custom(
17842
- (t) => /* @__PURE__ */ jsxRuntime.jsx(
17843
- "button",
17844
- {
17845
- type: "button",
17846
- onClick: () => {
17847
- toast2__default.default.dismiss(t.id);
17848
- requestOpenConversation(conversationId);
17849
- },
17850
- style: {
17851
- display: "block",
17852
- width: "100%",
17853
- textAlign: "left",
17854
- cursor: "pointer",
17855
- background: "var(--background, #fff)",
17856
- color: "var(--foreground, #111)",
17857
- border: "1px solid rgba(0,0,0,0.08)",
17858
- borderRadius: 8,
17859
- padding: "10px 14px",
17860
- boxShadow: "0 4px 12px rgba(0,0,0,0.12)",
17861
- font: "inherit"
17862
- },
17863
- children: text
17864
- }
17865
- ),
17866
- { duration: 5e3 }
17867
- );
17868
- return;
17869
- }
17870
- showNotification(text, "info");
18163
+ const messageId2 = String(
18164
+ payload.data?.messageId || payload.data?.message_id || payload.data?.messageID || ""
18165
+ );
18166
+ if (!title && !body) return;
18167
+ toast__default.default.custom(
18168
+ (t) => /* @__PURE__ */ jsxRuntime.jsx(
18169
+ PushMessageToast,
18170
+ {
18171
+ t,
18172
+ title,
18173
+ body: body || void 0,
18174
+ onOpen: conversationId ? () => requestOpenConversation(
18175
+ conversationId,
18176
+ payload.data
18177
+ ) : void 0
18178
+ }
18179
+ ),
18180
+ {
18181
+ duration: 5e3,
18182
+ id: messageId2 || void 0
18183
+ }
18184
+ );
17871
18185
  };
17872
18186
  var ForegroundPushBridge = ({ onPush }) => {
17873
18187
  const lastDM = exports.useChatStore((s) => s.lastDM);
@@ -18002,11 +18316,31 @@ var ForegroundPushBridge = ({ onPush }) => {
18002
18316
  return () => window.removeEventListener(OPEN_CONVERSATION_EVENT, onOpen);
18003
18317
  }, []);
18004
18318
  return /* @__PURE__ */ jsxRuntime.jsx(
18005
- toast2.Toaster,
18319
+ toast.Toaster,
18006
18320
  {
18007
18321
  position: "top-right",
18008
- toastOptions: { duration: 4e3 },
18009
- containerStyle: { zIndex: 99999 }
18322
+ gutter: 12,
18323
+ containerClassName: "realtimex-push-toaster",
18324
+ containerStyle: {
18325
+ zIndex: 99999,
18326
+ top: 16,
18327
+ right: 16,
18328
+ width: "auto",
18329
+ maxWidth: "none"
18330
+ },
18331
+ toastOptions: {
18332
+ duration: 5e3,
18333
+ className: "!w-auto !max-w-none",
18334
+ style: {
18335
+ padding: 0,
18336
+ margin: 0,
18337
+ background: "transparent",
18338
+ boxShadow: "none",
18339
+ border: "none",
18340
+ width: "auto",
18341
+ maxWidth: "none"
18342
+ }
18343
+ }
18010
18344
  }
18011
18345
  );
18012
18346
  };
@@ -18163,6 +18497,80 @@ var getSocketConfig = (accessToken, tenantId, options) => {
18163
18497
  }
18164
18498
  };
18165
18499
  };
18500
+ var showNotification = (message, type = "info", icon, duration) => {
18501
+ const options = {
18502
+ icon,
18503
+ duration
18504
+ };
18505
+ switch (type) {
18506
+ case "success":
18507
+ toast.toast.success(message, options);
18508
+ break;
18509
+ case "error":
18510
+ toast.toast.error(message, options);
18511
+ break;
18512
+ case "info":
18513
+ toast.toast(message, options);
18514
+ break;
18515
+ case "warning":
18516
+ toast.toast(message, { ...options, icon: "\u26A0\uFE0F" });
18517
+ break;
18518
+ default:
18519
+ toast.toast(message, options);
18520
+ }
18521
+ };
18522
+ var downloadFile = async (src, name) => {
18523
+ try {
18524
+ const link = document.createElement("a");
18525
+ link.href = src;
18526
+ link.setAttribute("download", name);
18527
+ link.setAttribute("rel", "noopener noreferrer");
18528
+ link.click();
18529
+ showNotification("File Downloaded Successfully", "success");
18530
+ } catch (err) {
18531
+ console.error("File download failed:", err);
18532
+ showNotification("Failed to download file", "error");
18533
+ }
18534
+ };
18535
+ var getResponsiveWidth = (width) => {
18536
+ const screenWidth = window.innerWidth;
18537
+ if (width) {
18538
+ return width;
18539
+ } else if (screenWidth < 640) {
18540
+ return 350;
18541
+ } else if (screenWidth >= 640 && screenWidth < 1024) {
18542
+ return 600;
18543
+ } else if (screenWidth >= 1024 && screenWidth < 1280) {
18544
+ return 800;
18545
+ } else {
18546
+ return 1e3;
18547
+ }
18548
+ };
18549
+ var isEmpty = (value) => {
18550
+ if (value === null || value === void 0) return true;
18551
+ if (typeof value === "string") return value.trim().length === 0;
18552
+ if (Array.isArray(value)) {
18553
+ return value.length === 0 || value.every((item) => isEmpty(item));
18554
+ }
18555
+ if (typeof value === "object") {
18556
+ return Object.keys(value).length === 0;
18557
+ }
18558
+ return false;
18559
+ };
18560
+ function uniqueList(list, key) {
18561
+ const seen = /* @__PURE__ */ new Set();
18562
+ return list.filter((item) => {
18563
+ const value = item[key];
18564
+ if (seen.has(value)) return false;
18565
+ seen.add(value);
18566
+ return true;
18567
+ });
18568
+ }
18569
+ var capitalizeFirst = (str) => str.charAt(0).toUpperCase() + str.slice(1);
18570
+ var getFirstAndLastNameOneCharacter = (name) => {
18571
+ const names = name.split(" ");
18572
+ return names.map((name2) => name2.charAt(0).toUpperCase()).join("");
18573
+ };
18166
18574
  var SIZE_MAP = {
18167
18575
  xs: "size-6",
18168
18576
  sm: "size-8",
@@ -18336,135 +18744,6 @@ var packageDefaultComponents = {
18336
18744
  ChatDateTimeSettings: ChatDateTimeSettings_default,
18337
18745
  ChatSocketStatus: ChatSocketStatus_default
18338
18746
  };
18339
- var usePushNotifications = () => {
18340
- const [state, setState] = React2.useState({
18341
- supported: false,
18342
- allowedByWorkspace: false,
18343
- configured: false,
18344
- permission: "unsupported",
18345
- userEnabled: true,
18346
- deviceEnabled: false,
18347
- loading: true,
18348
- error: null
18349
- });
18350
- const refresh = React2.useCallback(async () => {
18351
- if (!isPushSupported()) {
18352
- setState((prev) => ({
18353
- ...prev,
18354
- supported: false,
18355
- loading: false
18356
- }));
18357
- return;
18358
- }
18359
- try {
18360
- const [config, preference] = await Promise.all([
18361
- apiGetPushConfig(),
18362
- apiGetPushPreference().catch(() => null)
18363
- ]);
18364
- setState({
18365
- supported: true,
18366
- allowedByWorkspace: Boolean(config?.enabled),
18367
- configured: Boolean(config?.configured),
18368
- permission: Notification.permission,
18369
- userEnabled: preference?.enabled ?? true,
18370
- deviceEnabled: Notification.permission === "granted" && Boolean(getStoredPushToken()),
18371
- loading: false,
18372
- error: null
18373
- });
18374
- } catch (error) {
18375
- setState((prev) => ({
18376
- ...prev,
18377
- supported: true,
18378
- loading: false,
18379
- error: error instanceof Error ? error.message : "Could not load push settings"
18380
- }));
18381
- }
18382
- }, []);
18383
- React2.useEffect(() => {
18384
- void refresh();
18385
- }, [refresh]);
18386
- const enable = React2.useCallback(async () => {
18387
- setState((prev) => ({ ...prev, loading: true, error: null }));
18388
- try {
18389
- await enablePushOnThisDevice();
18390
- await refresh();
18391
- return true;
18392
- } catch (error) {
18393
- setState((prev) => ({
18394
- ...prev,
18395
- loading: false,
18396
- permission: isPushSupported() ? Notification.permission : "unsupported",
18397
- error: error instanceof Error ? error.message : "Could not enable push notifications"
18398
- }));
18399
- return false;
18400
- }
18401
- }, [refresh]);
18402
- const disable = React2.useCallback(async () => {
18403
- setState((prev) => ({ ...prev, loading: true, error: null }));
18404
- try {
18405
- await disablePushOnThisDevice();
18406
- } finally {
18407
- await refresh();
18408
- }
18409
- }, [refresh]);
18410
- const setUserEnabled = React2.useCallback(
18411
- async (enabled) => {
18412
- setState((prev) => ({ ...prev, userEnabled: enabled }));
18413
- try {
18414
- await apiUpdatePushPreference(enabled);
18415
- } catch (error) {
18416
- setState((prev) => ({
18417
- ...prev,
18418
- userEnabled: !enabled,
18419
- error: error instanceof Error ? error.message : "Could not update push preference"
18420
- }));
18421
- }
18422
- },
18423
- []
18424
- );
18425
- return { ...state, enable, disable, setUserEnabled, refresh };
18426
- };
18427
- var PushNotificationToggle = ({
18428
- className,
18429
- label = "Push notifications",
18430
- description = "Get notified about new messages on this device"
18431
- }) => {
18432
- const push = usePushNotifications();
18433
- if (!push.supported || !push.allowedByWorkspace || !push.configured) {
18434
- return null;
18435
- }
18436
- const isOn = push.deviceEnabled && push.userEnabled;
18437
- const permissionBlocked = push.permission === "denied";
18438
- const handleChange = async (checked) => {
18439
- if (checked) {
18440
- if (!push.userEnabled) await push.setUserEnabled(true);
18441
- if (!push.deviceEnabled) await push.enable();
18442
- } else {
18443
- await push.disable();
18444
- }
18445
- };
18446
- return /* @__PURE__ */ jsxRuntime.jsxs(
18447
- "div",
18448
- {
18449
- className: className ?? "flex items-center justify-between gap-4 rounded-xl border border-border bg-background px-4 py-3",
18450
- children: [
18451
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-0.5", children: [
18452
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm font-medium text-foreground", children: label }),
18453
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-muted-foreground", children: permissionBlocked ? "Notifications are blocked in your browser settings" : push.error ?? description })
18454
- ] }),
18455
- /* @__PURE__ */ jsxRuntime.jsx(
18456
- Switch,
18457
- {
18458
- checked: isOn,
18459
- disabled: push.loading || permissionBlocked,
18460
- onCheckedChange: (checked) => void handleChange(checked),
18461
- "aria-label": `Toggle ${label}`
18462
- }
18463
- )
18464
- ]
18465
- }
18466
- );
18467
- };
18468
18747
 
18469
18748
  // src/index.ts
18470
18749
  var index_default = ChatMain_default;
@@ -18497,16 +18776,21 @@ exports.MessageItem = MessageItemView;
18497
18776
  exports.MessageList = ActiveChannelMessagesView_default;
18498
18777
  exports.NOTIFICATION_CLICK_SW_TYPE = NOTIFICATION_CLICK_SW_TYPE;
18499
18778
  exports.OPEN_CONVERSATION_EVENT = OPEN_CONVERSATION_EVENT;
18779
+ exports.PUSH_BROADCAST_CHANNEL = PUSH_BROADCAST_CHANNEL;
18500
18780
  exports.PUSH_CHANGED_EVENT = PUSH_CHANGED_EVENT;
18781
+ exports.PUSH_DATA_CACHE_NAME = PUSH_DATA_CACHE_NAME;
18501
18782
  exports.PushNotificationToggle = PushNotificationToggle;
18502
18783
  exports.apiGetPushConfig = apiGetPushConfig;
18503
18784
  exports.apiGetPushPreference = apiGetPushPreference;
18504
18785
  exports.apiRegisterPushToken = apiRegisterPushToken;
18505
18786
  exports.apiRemovePushToken = apiRemovePushToken;
18506
18787
  exports.apiUpdatePushPreference = apiUpdatePushPreference;
18788
+ exports.buildChannelFromPushRequest = buildChannelFromPushRequest;
18507
18789
  exports.capitalizeFirst = capitalizeFirst;
18508
18790
  exports.clampJumpDate = clampJumpDate;
18509
18791
  exports.consumePendingOpenConversation = consumePendingOpenConversation;
18792
+ exports.consumePendingOpenFromPushCache = consumePendingOpenFromPushCache;
18793
+ exports.consumePendingOpenRequest = consumePendingOpenRequest;
18510
18794
  exports.default = index_default;
18511
18795
  exports.defaultChatClassNames = defaultChatClassNames;
18512
18796
  exports.defaultChatComponents = defaultChatComponents;
@@ -18527,6 +18811,7 @@ exports.mergeChatLayoutConfig = mergeChatLayoutConfig;
18527
18811
  exports.onForegroundPush = onForegroundPush;
18528
18812
  exports.packageDefaultComponents = packageDefaultComponents;
18529
18813
  exports.peekPendingOpenConversation = peekPendingOpenConversation;
18814
+ exports.peekPendingOpenRequest = peekPendingOpenRequest;
18530
18815
  exports.requestOpenConversation = requestOpenConversation;
18531
18816
  exports.resolveApiUrl = resolveApiUrl;
18532
18817
  exports.resolveChatApiKey = resolveChatApiKey;