@cossistant/react 0.0.4 → 0.0.5

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.
Files changed (319) hide show
  1. package/_virtual/rolldown_runtime.js +19 -0
  2. package/conversation.d.ts +26 -3
  3. package/conversation.d.ts.map +1 -1
  4. package/hooks/index.d.ts +5 -3
  5. package/hooks/index.js +7 -5
  6. package/hooks/private/store/use-conversations-store.d.ts +8 -0
  7. package/hooks/private/store/use-conversations-store.d.ts.map +1 -1
  8. package/hooks/private/store/use-conversations-store.js +8 -0
  9. package/hooks/private/store/use-conversations-store.js.map +1 -1
  10. package/hooks/private/store/use-store-selector.d.ts +4 -0
  11. package/hooks/private/store/use-store-selector.d.ts.map +1 -1
  12. package/hooks/private/store/use-store-selector.js +5 -2
  13. package/hooks/private/store/use-store-selector.js.map +1 -1
  14. package/hooks/private/store/use-website-store.d.ts +4 -0
  15. package/hooks/private/store/use-website-store.d.ts.map +1 -1
  16. package/hooks/private/store/use-website-store.js +6 -3
  17. package/hooks/private/store/use-website-store.js.map +1 -1
  18. package/hooks/private/typing.d.ts +35 -0
  19. package/hooks/private/typing.d.ts.map +1 -0
  20. package/hooks/private/typing.js +49 -0
  21. package/hooks/private/typing.js.map +1 -0
  22. package/hooks/private/use-client-query.d.ts +5 -0
  23. package/hooks/private/use-client-query.d.ts.map +1 -1
  24. package/hooks/private/use-client-query.js +5 -0
  25. package/hooks/private/use-client-query.js.map +1 -1
  26. package/hooks/private/use-grouped-messages.d.ts +10 -4
  27. package/hooks/private/use-grouped-messages.d.ts.map +1 -1
  28. package/hooks/private/use-grouped-messages.js +24 -4
  29. package/hooks/private/use-grouped-messages.js.map +1 -1
  30. package/hooks/private/use-multimodal-input.d.ts.map +1 -1
  31. package/hooks/private/use-rest-client.d.ts.map +1 -1
  32. package/hooks/private/use-visitor-typing-reporter.d.ts +6 -0
  33. package/hooks/private/use-visitor-typing-reporter.d.ts.map +1 -1
  34. package/hooks/private/use-visitor-typing-reporter.js +6 -0
  35. package/hooks/private/use-visitor-typing-reporter.js.map +1 -1
  36. package/hooks/use-composer-refocus.d.ts.map +1 -1
  37. package/hooks/use-conversation-auto-seen.d.ts +9 -0
  38. package/hooks/use-conversation-auto-seen.d.ts.map +1 -1
  39. package/hooks/use-conversation-auto-seen.js +44 -3
  40. package/hooks/use-conversation-auto-seen.js.map +1 -1
  41. package/hooks/use-conversation-history-page.d.ts.map +1 -1
  42. package/hooks/use-conversation-history-page.js +16 -18
  43. package/hooks/use-conversation-history-page.js.map +1 -1
  44. package/hooks/use-conversation-lifecycle.d.ts.map +1 -1
  45. package/hooks/use-conversation-lifecycle.js +2 -4
  46. package/hooks/use-conversation-lifecycle.js.map +1 -1
  47. package/hooks/use-conversation-page.d.ts +6 -0
  48. package/hooks/use-conversation-page.d.ts.map +1 -1
  49. package/hooks/use-conversation-page.js +41 -3
  50. package/hooks/use-conversation-page.js.map +1 -1
  51. package/hooks/use-conversation-preview.d.ts +61 -0
  52. package/hooks/use-conversation-preview.d.ts.map +1 -0
  53. package/hooks/use-conversation-preview.js +173 -0
  54. package/hooks/use-conversation-preview.js.map +1 -0
  55. package/hooks/use-conversation-seen.d.ts +4 -0
  56. package/hooks/use-conversation-seen.d.ts.map +1 -1
  57. package/hooks/use-conversation-seen.js +4 -0
  58. package/hooks/use-conversation-seen.js.map +1 -1
  59. package/hooks/use-conversation-timeline-items.d.ts +4 -0
  60. package/hooks/use-conversation-timeline-items.d.ts.map +1 -1
  61. package/hooks/use-conversation-timeline-items.js +4 -0
  62. package/hooks/use-conversation-timeline-items.js.map +1 -1
  63. package/hooks/use-conversation-timeline.d.ts +32 -0
  64. package/hooks/use-conversation-timeline.d.ts.map +1 -0
  65. package/hooks/use-conversation-timeline.js +41 -0
  66. package/hooks/use-conversation-timeline.js.map +1 -0
  67. package/hooks/use-conversation-typing.d.ts +4 -0
  68. package/hooks/use-conversation-typing.d.ts.map +1 -1
  69. package/hooks/use-conversation-typing.js +4 -0
  70. package/hooks/use-conversation-typing.js.map +1 -1
  71. package/hooks/use-conversation.d.ts +11 -0
  72. package/hooks/use-conversation.d.ts.map +1 -1
  73. package/hooks/use-conversation.js +11 -0
  74. package/hooks/use-conversation.js.map +1 -1
  75. package/hooks/use-conversations.d.ts +12 -0
  76. package/hooks/use-conversations.d.ts.map +1 -1
  77. package/hooks/use-conversations.js +12 -0
  78. package/hooks/use-conversations.js.map +1 -1
  79. package/hooks/use-create-conversation.d.ts +5 -0
  80. package/hooks/use-create-conversation.d.ts.map +1 -1
  81. package/hooks/use-create-conversation.js +12 -9
  82. package/hooks/use-create-conversation.js.map +1 -1
  83. package/hooks/use-home-page.d.ts.map +1 -1
  84. package/hooks/use-home-page.js +6 -4
  85. package/hooks/use-home-page.js.map +1 -1
  86. package/hooks/use-message-composer.d.ts.map +1 -1
  87. package/hooks/use-realtime-support.d.ts.map +1 -1
  88. package/hooks/use-send-message.d.ts +9 -0
  89. package/hooks/use-send-message.d.ts.map +1 -1
  90. package/hooks/use-send-message.js +15 -13
  91. package/hooks/use-send-message.js.map +1 -1
  92. package/hooks/use-visitor.d.ts.map +1 -1
  93. package/hooks/use-visitor.js +28 -30
  94. package/hooks/use-visitor.js.map +1 -1
  95. package/hooks/use-window-visibility-focus.d.ts +4 -0
  96. package/hooks/use-window-visibility-focus.d.ts.map +1 -1
  97. package/hooks/use-window-visibility-focus.js +5 -2
  98. package/hooks/use-window-visibility-focus.js.map +1 -1
  99. package/identify-visitor.d.ts +12 -3
  100. package/identify-visitor.d.ts.map +1 -1
  101. package/identify-visitor.js +58 -9
  102. package/identify-visitor.js.map +1 -1
  103. package/index.d.ts +10 -7
  104. package/index.js +10 -9
  105. package/package.json +11 -16
  106. package/primitives/avatar/avatar.d.ts.map +1 -1
  107. package/primitives/avatar/fallback.d.ts.map +1 -1
  108. package/primitives/avatar/fallback.js +1 -3
  109. package/primitives/avatar/fallback.js.map +1 -1
  110. package/primitives/avatar/image.d.ts.map +1 -1
  111. package/primitives/avatar/index.d.ts +1 -0
  112. package/primitives/bubble.d.ts +2 -0
  113. package/primitives/bubble.d.ts.map +1 -1
  114. package/primitives/bubble.js +8 -2
  115. package/primitives/bubble.js.map +1 -1
  116. package/primitives/button.d.ts.map +1 -1
  117. package/primitives/conversation-timeline.d.ts.map +1 -1
  118. package/primitives/conversation-timeline.js +58 -5
  119. package/primitives/conversation-timeline.js.map +1 -1
  120. package/primitives/index.d.ts +1 -0
  121. package/primitives/index.parts.d.ts +1 -0
  122. package/primitives/multimodal-input.d.ts.map +1 -1
  123. package/primitives/timeline-item-group.d.ts +7 -7
  124. package/primitives/timeline-item-group.d.ts.map +1 -1
  125. package/primitives/timeline-item-group.js.map +1 -1
  126. package/primitives/timeline-item.d.ts +1 -1
  127. package/primitives/timeline-item.d.ts.map +1 -1
  128. package/primitives/timeline-item.js +7 -1
  129. package/primitives/timeline-item.js.map +1 -1
  130. package/primitives/window.d.ts +1 -1
  131. package/primitives/window.d.ts.map +1 -1
  132. package/primitives/window.js +4 -4
  133. package/primitives/window.js.map +1 -1
  134. package/provider.d.ts +23 -43
  135. package/provider.d.ts.map +1 -1
  136. package/provider.js +152 -49
  137. package/provider.js.map +1 -1
  138. package/realtime/event-filter.d.ts +4 -0
  139. package/realtime/event-filter.d.ts.map +1 -1
  140. package/realtime/event-filter.js +4 -0
  141. package/realtime/event-filter.js.map +1 -1
  142. package/realtime/index.js +1 -1
  143. package/realtime/provider.d.ts +7 -2
  144. package/realtime/provider.d.ts.map +1 -1
  145. package/realtime/provider.js +23 -1
  146. package/realtime/provider.js.map +1 -1
  147. package/realtime/seen-store.d.ts +13 -0
  148. package/realtime/seen-store.d.ts.map +1 -1
  149. package/realtime/seen-store.js +14 -2
  150. package/realtime/seen-store.js.map +1 -1
  151. package/realtime/support-provider.d.ts +1 -2
  152. package/realtime/support-provider.d.ts.map +1 -1
  153. package/realtime/support-provider.js +19 -20
  154. package/realtime/support-provider.js.map +1 -1
  155. package/realtime/typing-store.d.ts +18 -0
  156. package/realtime/typing-store.d.ts.map +1 -1
  157. package/realtime/typing-store.js +19 -2
  158. package/realtime/typing-store.js.map +1 -1
  159. package/realtime/use-realtime.d.ts +8 -4
  160. package/realtime/use-realtime.d.ts.map +1 -1
  161. package/realtime/use-realtime.js +4 -0
  162. package/realtime/use-realtime.js.map +1 -1
  163. package/realtime-events.d.ts +17 -3
  164. package/realtime-events.d.ts.map +1 -1
  165. package/schemas.d.ts +7 -1
  166. package/schemas.d.ts.map +1 -1
  167. package/support/components/avatar-stack.d.ts +8 -4
  168. package/support/components/avatar-stack.d.ts.map +1 -1
  169. package/support/components/avatar-stack.js +4 -0
  170. package/support/components/avatar-stack.js.map +1 -1
  171. package/support/components/avatar.d.ts +11 -6
  172. package/support/components/avatar.d.ts.map +1 -1
  173. package/support/components/avatar.js +4 -0
  174. package/support/components/avatar.js.map +1 -1
  175. package/support/components/bubble.d.ts.map +1 -1
  176. package/support/components/bubble.js +29 -6
  177. package/support/components/bubble.js.map +1 -1
  178. package/support/components/button.d.ts +8 -5
  179. package/support/components/button.d.ts.map +1 -1
  180. package/support/components/button.js +5 -1
  181. package/support/components/button.js.map +1 -1
  182. package/support/components/container.d.ts +0 -1
  183. package/support/components/container.d.ts.map +1 -1
  184. package/support/components/container.js +2 -8
  185. package/support/components/container.js.map +1 -1
  186. package/support/components/conversation-button-link.d.ts +8 -21
  187. package/support/components/conversation-button-link.d.ts.map +1 -1
  188. package/support/components/conversation-button-link.js +62 -178
  189. package/support/components/conversation-button-link.js.map +1 -1
  190. package/support/components/conversation-event.d.ts.map +1 -1
  191. package/support/components/conversation-event.js +4 -0
  192. package/support/components/conversation-event.js.map +1 -1
  193. package/support/components/conversation-timeline.d.ts +10 -1
  194. package/support/components/conversation-timeline.d.ts.map +1 -1
  195. package/support/components/conversation-timeline.js +63 -57
  196. package/support/components/conversation-timeline.js.map +1 -1
  197. package/support/components/cossistant-branding.d.ts +5 -2
  198. package/support/components/cossistant-branding.d.ts.map +1 -1
  199. package/support/components/cossistant-branding.js +3 -0
  200. package/support/components/cossistant-branding.js.map +1 -1
  201. package/support/components/header.d.ts.map +1 -1
  202. package/support/components/header.js +2 -2
  203. package/support/components/header.js.map +1 -1
  204. package/support/components/icons.d.ts.map +1 -1
  205. package/support/components/multimodal-input.d.ts.map +1 -1
  206. package/support/components/multimodal-input.js +5 -24
  207. package/support/components/multimodal-input.js.map +1 -1
  208. package/support/components/navigation-tab.d.ts +7 -2
  209. package/support/components/navigation-tab.d.ts.map +1 -1
  210. package/support/components/navigation-tab.js +4 -0
  211. package/support/components/navigation-tab.js.map +1 -1
  212. package/support/components/support-content.d.ts +1 -1
  213. package/support/components/support-content.d.ts.map +1 -1
  214. package/support/components/support-content.js +7 -10
  215. package/support/components/support-content.js.map +1 -1
  216. package/support/components/text-effect.d.ts +5 -2
  217. package/support/components/text-effect.d.ts.map +1 -1
  218. package/support/components/text-effect.js +4 -0
  219. package/support/components/text-effect.js.map +1 -1
  220. package/support/components/timeline-identification-tool.d.ts +7 -0
  221. package/support/components/timeline-identification-tool.d.ts.map +1 -0
  222. package/support/components/timeline-identification-tool.js +139 -0
  223. package/support/components/timeline-identification-tool.js.map +1 -0
  224. package/support/components/timeline-message-group.d.ts +2 -1
  225. package/support/components/timeline-message-group.d.ts.map +1 -1
  226. package/support/components/timeline-message-group.js +4 -19
  227. package/support/components/timeline-message-group.js.map +1 -1
  228. package/support/components/timeline-message-item.d.ts +6 -2
  229. package/support/components/timeline-message-item.d.ts.map +1 -1
  230. package/support/components/timeline-message-item.js +8 -4
  231. package/support/components/timeline-message-item.js.map +1 -1
  232. package/support/components/typing-indicator.d.ts +5 -2
  233. package/support/components/typing-indicator.d.ts.map +1 -1
  234. package/support/components/typing-indicator.js +4 -4
  235. package/support/components/typing-indicator.js.map +1 -1
  236. package/support/components/watermark.d.ts.map +1 -1
  237. package/support/context/websocket.d.ts +8 -0
  238. package/support/context/websocket.d.ts.map +1 -1
  239. package/support/context/websocket.js +12 -6
  240. package/support/context/websocket.js.map +1 -1
  241. package/support/index.d.ts +8 -8
  242. package/support/index.d.ts.map +1 -1
  243. package/support/index.js +18 -18
  244. package/support/index.js.map +1 -1
  245. package/support/pages/conversation-history.js +46 -54
  246. package/support/pages/conversation-history.js.map +1 -1
  247. package/support/pages/conversation.d.ts +3 -6
  248. package/support/pages/conversation.d.ts.map +1 -1
  249. package/support/pages/conversation.js +19 -9
  250. package/support/pages/conversation.js.map +1 -1
  251. package/support/pages/home.d.ts +2 -2
  252. package/support/pages/home.d.ts.map +1 -1
  253. package/support/pages/home.js +64 -77
  254. package/support/pages/home.js.map +1 -1
  255. package/support/store/support-store.d.ts +18 -2
  256. package/support/store/support-store.d.ts.map +1 -1
  257. package/support/store/support-store.js +20 -5
  258. package/support/store/support-store.js.map +1 -1
  259. package/support/{support-CMoDLQoC.css → support-Ck4jy29i.css} +1 -2
  260. package/support/support-Ck4jy29i.css.map +1 -0
  261. package/support/text/index.d.ts +15 -2
  262. package/support/text/index.d.ts.map +1 -1
  263. package/support/text/index.js +15 -2
  264. package/support/text/index.js.map +1 -1
  265. package/support/text/locales/en.js +22 -4
  266. package/support/text/locales/en.js.map +1 -1
  267. package/support/text/locales/es.js +18 -0
  268. package/support/text/locales/es.js.map +1 -1
  269. package/support/text/locales/fr.js +18 -0
  270. package/support/text/locales/fr.js.map +1 -1
  271. package/support/text/locales/keys.d.ts +69 -9
  272. package/support/text/locales/keys.d.ts.map +1 -1
  273. package/support/text/locales/keys.js +18 -0
  274. package/support/text/locales/keys.js.map +1 -1
  275. package/support/text/runtime.d.ts +21 -0
  276. package/support/text/runtime.d.ts.map +1 -1
  277. package/support/text/runtime.js +21 -0
  278. package/support/text/runtime.js.map +1 -1
  279. package/support/utils/index.d.ts +4 -0
  280. package/support/utils/index.d.ts.map +1 -1
  281. package/support/utils/index.js +4 -1
  282. package/support/utils/index.js.map +1 -1
  283. package/support/utils/time.d.ts +3 -0
  284. package/support/utils/time.d.ts.map +1 -1
  285. package/support/utils/time.js +3 -0
  286. package/support/utils/time.js.map +1 -1
  287. package/support-config.d.ts +2 -1
  288. package/support-config.d.ts.map +1 -1
  289. package/support-config.js.map +1 -1
  290. package/support.css +2 -2
  291. package/timeline-item.d.ts +10 -0
  292. package/timeline-item.d.ts.map +1 -1
  293. package/utils/conversation.d.ts +7 -0
  294. package/utils/conversation.d.ts.map +1 -0
  295. package/utils/conversation.js +18 -0
  296. package/utils/conversation.js.map +1 -0
  297. package/utils/id.d.ts +3 -0
  298. package/utils/id.d.ts.map +1 -1
  299. package/utils/id.js +3 -0
  300. package/utils/id.js.map +1 -1
  301. package/utils/index.d.ts +2 -1
  302. package/utils/index.js +2 -1
  303. package/utils/metadata-hash.d.ts +12 -0
  304. package/utils/metadata-hash.d.ts.map +1 -0
  305. package/utils/metadata-hash.js +26 -0
  306. package/utils/metadata-hash.js.map +1 -0
  307. package/utils/text.d.ts +3 -0
  308. package/utils/text.d.ts.map +1 -1
  309. package/utils/text.js +3 -0
  310. package/utils/text.js.map +1 -1
  311. package/utils/use-render-element.d.ts +3 -0
  312. package/utils/use-render-element.d.ts.map +1 -1
  313. package/utils/use-render-element.js +3 -0
  314. package/utils/use-render-element.js.map +1 -1
  315. package/support/context/config.d.ts +0 -32
  316. package/support/context/config.d.ts.map +0 -1
  317. package/support/context/config.js +0 -27
  318. package/support/context/config.js.map +0 -1
  319. package/support/support-CMoDLQoC.css.map +0 -1
package/provider.js CHANGED
@@ -1,48 +1,145 @@
1
- import { QueryClient } from "./node_modules/@tanstack/query-core/build/modern/queryClient.js";
2
- import { QueryClientProvider } from "./node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js";
1
+ import { useStoreSelector } from "./hooks/private/store/use-store-selector.js";
3
2
  import { useWebsiteStore } from "./hooks/private/store/use-website-store.js";
4
3
  import { useClient } from "./hooks/private/use-rest-client.js";
4
+ import { useSeenStore } from "./realtime/seen-store.js";
5
+ import { initializeSupportStore, useSupportStore } from "./support/store/support-store.js";
5
6
  import { WebSocketProvider } from "./support/context/websocket.js";
6
- import * as React$1 from "react";
7
+ import React from "react";
7
8
  import { normalizeLocale } from "@cossistant/core";
9
+ import { ConversationTimelineType } from "@cossistant/types/enums";
8
10
  import { jsx } from "react/jsx-runtime";
9
11
 
10
12
  //#region src/provider.tsx
11
- const SupportContext = React$1.createContext(void 0);
13
+ function areConversationSnapshotsEqual(a, b) {
14
+ if (a === b) return true;
15
+ if (a.length !== b.length) return false;
16
+ for (let index = 0; index < a.length; index += 1) {
17
+ const snapshotA = a[index];
18
+ const snapshotB = b[index];
19
+ if (!snapshotA) return false;
20
+ if (!snapshotB) return false;
21
+ const aLastCreatedAt = snapshotA.lastTimelineItem?.createdAt ?? null;
22
+ const bLastCreatedAt = snapshotB.lastTimelineItem?.createdAt ?? null;
23
+ if (snapshotA.id !== snapshotB.id || aLastCreatedAt !== bLastCreatedAt) return false;
24
+ }
25
+ return true;
26
+ }
27
+ const SupportContext = React.createContext(void 0);
12
28
  /**
13
- * Internal implementation that wires the React Query cache, REST client and
14
- * websocket provider together before exposing the combined context.
29
+ * Internal implementation that wires the REST client and websocket provider
30
+ * together before exposing the combined context.
15
31
  */
16
- function SupportProviderInner({ children, apiUrl, wsUrl, publicKey, defaultMessages, quickOptions, autoConnect, onWsConnect, onWsDisconnect, onWsError }) {
17
- const [unreadCount, setUnreadCount] = React$1.useState(0);
18
- const [_defaultMessages, _setDefaultMessages] = React$1.useState(defaultMessages || []);
19
- const [_quickOptions, _setQuickOptions] = React$1.useState(quickOptions || []);
20
- React$1.useEffect(() => {
21
- if (defaultMessages && defaultMessages.length > 0) _setDefaultMessages(defaultMessages);
32
+ function SupportProviderInner({ children, apiUrl, wsUrl, publicKey, defaultMessages, quickOptions, autoConnect, onWsConnect, onWsDisconnect, onWsError, size = "normal", defaultOpen = false }) {
33
+ const [unreadCount, setUnreadCount] = React.useState(0);
34
+ const prefetchedVisitorRef = React.useRef(null);
35
+ const [_defaultMessages, _setDefaultMessages] = React.useState(defaultMessages ?? []);
36
+ const [_quickOptions, _setQuickOptions] = React.useState(quickOptions ?? []);
37
+ React.useEffect(() => {
38
+ initializeSupportStore({
39
+ size,
40
+ defaultOpen
41
+ });
42
+ }, [size, defaultOpen]);
43
+ const { config, open, close, toggle } = useSupportStore();
44
+ React.useEffect(() => {
45
+ if (defaultMessages?.length) _setDefaultMessages(defaultMessages);
22
46
  }, [defaultMessages]);
23
- React$1.useEffect(() => {
24
- if (quickOptions && quickOptions.length > 0) _setQuickOptions(quickOptions);
47
+ React.useEffect(() => {
48
+ if (quickOptions?.length) _setQuickOptions(quickOptions);
25
49
  }, [quickOptions]);
26
50
  const { client } = useClient(publicKey, apiUrl, wsUrl);
27
51
  const { website, isLoading, error: websiteError } = useWebsiteStore(client);
28
- const error = websiteError;
29
- React$1.useEffect(() => {
30
- if (website) client.restClient?.setWebsiteContext?.(website.id, website.visitor?.id);
52
+ const isVisitorBlocked = website?.visitor?.isBlocked ?? false;
53
+ const visitorId = website?.visitor?.id ?? null;
54
+ const seenEntriesByConversation = useSeenStore(React.useCallback((state) => state.conversations, []));
55
+ const conversationSnapshots = useStoreSelector(client.conversationsStore, React.useCallback((state) => state.ids.map((id) => {
56
+ const conversation = state.byId[id];
57
+ if (!conversation) return null;
58
+ return {
59
+ id: conversation.id,
60
+ lastTimelineItem: conversation.lastTimelineItem ?? null
61
+ };
62
+ }).filter((snapshot) => snapshot !== null), []), areConversationSnapshotsEqual);
63
+ const derivedUnreadCount = React.useMemo(() => {
64
+ if (!visitorId) return 0;
65
+ let count = 0;
66
+ for (const { id: conversationId, lastTimelineItem } of conversationSnapshots) {
67
+ if (!lastTimelineItem) continue;
68
+ if (lastTimelineItem.type !== ConversationTimelineType.MESSAGE) continue;
69
+ if (lastTimelineItem.visitorId && lastTimelineItem.visitorId === visitorId) continue;
70
+ const createdAtTime = Date.parse(lastTimelineItem.createdAt);
71
+ if (Number.isNaN(createdAtTime)) continue;
72
+ const seenEntries = seenEntriesByConversation[conversationId];
73
+ if (seenEntries) {
74
+ const visitorSeenEntry = Object.values(seenEntries).find((entry) => entry.actorType === "visitor" && entry.actorId === visitorId);
75
+ if (visitorSeenEntry) {
76
+ const lastSeenTime = Date.parse(visitorSeenEntry.lastSeenAt);
77
+ if (!Number.isNaN(lastSeenTime) && createdAtTime <= lastSeenTime) continue;
78
+ }
79
+ }
80
+ count += 1;
81
+ }
82
+ return count;
83
+ }, [
84
+ conversationSnapshots,
85
+ seenEntriesByConversation,
86
+ visitorId
87
+ ]);
88
+ React.useEffect(() => {
89
+ setUnreadCount(derivedUnreadCount);
90
+ }, [derivedUnreadCount, setUnreadCount]);
91
+ React.useEffect(() => {
92
+ if (!website) return;
93
+ client.setWebsiteContext(website.id, website.visitor?.id ?? void 0);
31
94
  }, [client, website]);
32
- const setDefaultMessages = React$1.useCallback((messages) => _setDefaultMessages(messages), []);
33
- const setQuickOptions = React$1.useCallback((options) => _setQuickOptions(options), []);
34
- const setUnreadCountStable = React$1.useCallback((count) => setUnreadCount(count), []);
35
- const value = React$1.useMemo(() => ({
95
+ React.useEffect(() => {
96
+ if (isVisitorBlocked) {
97
+ prefetchedVisitorRef.current = null;
98
+ return;
99
+ }
100
+ if (!autoConnect) return;
101
+ if (!website) return;
102
+ if (!visitorId) return;
103
+ if (prefetchedVisitorRef.current === visitorId) return;
104
+ const hasExistingConversations = client.conversationsStore.getState().ids.length > 0;
105
+ prefetchedVisitorRef.current = visitorId;
106
+ if (hasExistingConversations) return;
107
+ client.listConversations().catch((err) => {
108
+ console.error("[SupportProvider] Failed to prefetch conversations", err);
109
+ prefetchedVisitorRef.current = null;
110
+ });
111
+ }, [
112
+ autoConnect,
113
+ client,
114
+ isVisitorBlocked,
115
+ visitorId,
116
+ website
117
+ ]);
118
+ const error = websiteError;
119
+ React.useEffect(() => {
120
+ client.setVisitorBlocked(isVisitorBlocked);
121
+ }, [client, isVisitorBlocked]);
122
+ const setDefaultMessages = React.useCallback((messages) => {
123
+ _setDefaultMessages(messages);
124
+ }, []);
125
+ const setQuickOptions = React.useCallback((options) => {
126
+ _setQuickOptions(options);
127
+ }, []);
128
+ const value = React.useMemo(() => ({
36
129
  website,
37
130
  unreadCount,
38
- setUnreadCount: setUnreadCountStable,
131
+ setUnreadCount,
39
132
  isLoading,
40
133
  error,
41
134
  client,
42
135
  defaultMessages: _defaultMessages,
43
136
  setDefaultMessages,
44
137
  quickOptions: _quickOptions,
45
- setQuickOptions
138
+ setQuickOptions,
139
+ isOpen: config.isOpen,
140
+ open,
141
+ close,
142
+ toggle
46
143
  }), [
47
144
  website,
48
145
  unreadCount,
@@ -53,21 +150,30 @@ function SupportProviderInner({ children, apiUrl, wsUrl, publicKey, defaultMessa
53
150
  _quickOptions,
54
151
  setDefaultMessages,
55
152
  setQuickOptions,
56
- setUnreadCountStable
153
+ config.isOpen,
154
+ open,
155
+ close,
156
+ toggle
57
157
  ]);
158
+ const webSocketKey = React.useMemo(() => {
159
+ if (!website) return "no-website";
160
+ const visitorKey = website.visitor?.id ?? "anonymous";
161
+ const blockedState = isVisitorBlocked ? "blocked" : "active";
162
+ return `${website.id}:${visitorKey}:${blockedState}`;
163
+ }, [isVisitorBlocked, website]);
58
164
  return /* @__PURE__ */ jsx(SupportContext.Provider, {
59
165
  value,
60
166
  children: /* @__PURE__ */ jsx(WebSocketProvider, {
61
- autoConnect,
167
+ autoConnect: autoConnect && !isVisitorBlocked,
62
168
  onConnect: onWsConnect,
63
169
  onDisconnect: onWsDisconnect,
64
170
  onError: onWsError,
65
171
  publicKey,
66
- visitorId: website?.visitor?.id,
172
+ visitorId: isVisitorBlocked ? void 0 : website?.visitor?.id,
67
173
  websiteId: website?.id,
68
174
  wsUrl,
69
175
  children
70
- })
176
+ }, webSocketKey)
71
177
  });
72
178
  }
73
179
  /**
@@ -76,25 +182,20 @@ function SupportProviderInner({ children, apiUrl, wsUrl, publicKey, defaultMessa
76
182
  * and the REST client. Provide your Cossistant public key plus optional
77
183
  * defaults to configure the widget behaviour.
78
184
  */
79
- function SupportProvider({ children, apiUrl = "https://api.cossistant.com/v1", wsUrl = "wss://api.cossistant.com/ws", publicKey, defaultMessages, quickOptions, autoConnect = true, onWsConnect, onWsDisconnect, onWsError, queryClient }) {
80
- const [defaultQueryClient] = React$1.useState(() => new QueryClient({ defaultOptions: { queries: {
81
- staleTime: 300 * 1e3,
82
- gcTime: 600 * 1e3
83
- } } }));
84
- return /* @__PURE__ */ jsx(QueryClientProvider, {
85
- client: queryClient || defaultQueryClient,
86
- children: /* @__PURE__ */ jsx(SupportProviderInner, {
87
- apiUrl,
88
- autoConnect,
89
- defaultMessages,
90
- onWsConnect,
91
- onWsDisconnect,
92
- onWsError,
93
- publicKey,
94
- quickOptions,
95
- wsUrl,
96
- children
97
- })
185
+ function SupportProvider({ children, apiUrl = "https://api.cossistant.com/v1", wsUrl = "wss://api.cossistant.com/ws", publicKey, defaultMessages, quickOptions, autoConnect = true, onWsConnect, onWsDisconnect, onWsError, size = "normal", defaultOpen = false }) {
186
+ return /* @__PURE__ */ jsx(SupportProviderInner, {
187
+ apiUrl,
188
+ autoConnect,
189
+ defaultMessages,
190
+ defaultOpen,
191
+ onWsConnect,
192
+ onWsDisconnect,
193
+ onWsError,
194
+ publicKey,
195
+ quickOptions,
196
+ size,
197
+ wsUrl,
198
+ children
98
199
  });
99
200
  }
100
201
  /**
@@ -102,11 +203,12 @@ function SupportProvider({ children, apiUrl = "https://api.cossistant.com/v1", w
102
203
  * is consumed outside of `SupportProvider` to catch integration mistakes.
103
204
  */
104
205
  function useSupport() {
105
- const context = React$1.useContext(SupportContext);
206
+ const context = React.useContext(SupportContext);
106
207
  if (!context) throw new Error("useSupport must be used within a cossistant SupportProvider");
107
208
  const availableHumanAgents = context.website?.availableHumanAgents || [];
108
209
  const availableAIAgents = context.website?.availableAIAgents || [];
109
210
  const visitorLanguage = context.website?.visitor?.language || null;
211
+ const { config } = useSupportStore();
110
212
  const visitor = context.website?.visitor ? {
111
213
  ...context.website.visitor,
112
214
  locale: normalizeLocale(visitorLanguage)
@@ -115,10 +217,11 @@ function useSupport() {
115
217
  ...context,
116
218
  availableHumanAgents,
117
219
  availableAIAgents,
118
- visitor
220
+ visitor,
221
+ size: config.size
119
222
  };
120
223
  }
121
224
 
122
225
  //#endregion
123
- export { SupportProvider, useSupport };
226
+ export { SupportContext, SupportProvider, useSupport };
124
227
  //# sourceMappingURL=provider.js.map
package/provider.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"provider.js","names":["React"],"sources":["../src/provider.tsx"],"sourcesContent":["import type { CossistantClient } from \"@cossistant/core\";\nimport { normalizeLocale } from \"@cossistant/core\";\nimport type { DefaultMessage, PublicWebsiteResponse } from \"@cossistant/types\";\nimport { QueryClient, QueryClientProvider } from \"@tanstack/react-query\";\nimport * as React from \"react\";\nimport { useWebsiteStore } from \"./hooks/private/store/use-website-store\";\nimport { useClient } from \"./hooks/private/use-rest-client\";\nimport { WebSocketProvider } from \"./support\";\n\nexport type SupportProviderProps = {\n\tchildren: React.ReactNode;\n\tdefaultOpen?: boolean;\n\tapiUrl?: string;\n\twsUrl?: string;\n\tpublicKey?: string;\n\tdefaultMessages?: DefaultMessage[];\n\tquickOptions?: string[];\n\tautoConnect?: boolean;\n\tonWsConnect?: () => void;\n\tonWsDisconnect?: () => void;\n\tonWsError?: (error: Error) => void;\n\tqueryClient?: QueryClient;\n};\n\nexport type CossistantProviderProps = SupportProviderProps;\n\nexport type CossistantContextValue = {\n\twebsite: PublicWebsiteResponse | null;\n\tdefaultMessages: DefaultMessage[];\n\tquickOptions: string[];\n\tsetDefaultMessages: (messages: DefaultMessage[]) => void;\n\tsetQuickOptions: (options: string[]) => void;\n\tunreadCount: number;\n\tsetUnreadCount: (count: number) => void;\n\tisLoading: boolean;\n\terror: Error | null;\n\tclient: CossistantClient;\n};\n\nconst SupportContext = React.createContext<CossistantContextValue | undefined>(\n\tundefined\n);\n\n/**\n * Internal implementation that wires the React Query cache, REST client and\n * websocket provider together before exposing the combined context.\n */\nfunction SupportProviderInner({\n\tchildren,\n\tapiUrl,\n\twsUrl,\n\tpublicKey,\n\tdefaultMessages,\n\tquickOptions,\n\tautoConnect,\n\tonWsConnect,\n\tonWsDisconnect,\n\tonWsError,\n}: SupportProviderProps) {\n\tconst [unreadCount, setUnreadCount] = React.useState(0);\n\tconst [_defaultMessages, _setDefaultMessages] = React.useState<\n\t\tDefaultMessage[]\n\t>(defaultMessages || []);\n\tconst [_quickOptions, _setQuickOptions] = React.useState<string[]>(\n\t\tquickOptions || []\n\t);\n\t// Update state when props change (for initial values from provider)\n\tReact.useEffect(() => {\n\t\tif (defaultMessages && defaultMessages.length > 0) {\n\t\t\t_setDefaultMessages(defaultMessages);\n\t\t}\n\t}, [defaultMessages]);\n\n\tReact.useEffect(() => {\n\t\tif (quickOptions && quickOptions.length > 0) {\n\t\t\t_setQuickOptions(quickOptions);\n\t\t}\n\t}, [quickOptions]);\n\n\tconst { client } = useClient(publicKey, apiUrl, wsUrl);\n\tconst { website, isLoading, error: websiteError } = useWebsiteStore(client);\n\n\t// Prefetch conversations\n\t// useConversations(client, {\n\t// enabled: !!website && !!website.visitor && isClientPrimed,\n\t// });\n\n\tconst error = websiteError;\n\n\t// Prime REST client with website/visitor context so headers are sent reliably\n\tReact.useEffect(() => {\n\t\tif (website) {\n\t\t\t// @ts-expect-error internal priming: safe in our library context\n\t\t\tclient.restClient?.setWebsiteContext?.(website.id, website.visitor?.id);\n\t\t}\n\t}, [client, website]);\n\n\tconst setDefaultMessages = React.useCallback(\n\t\t(messages: DefaultMessage[]) => _setDefaultMessages(messages),\n\t\t[]\n\t);\n\n\tconst setQuickOptions = React.useCallback(\n\t\t(options: string[]) => _setQuickOptions(options),\n\t\t[]\n\t);\n\n\tconst setUnreadCountStable = React.useCallback(\n\t\t(count: number) => setUnreadCount(count),\n\t\t[]\n\t);\n\n\tconst value = React.useMemo<CossistantContextValue>(\n\t\t() => ({\n\t\t\twebsite,\n\t\t\tunreadCount,\n\t\t\tsetUnreadCount: setUnreadCountStable,\n\t\t\tisLoading,\n\t\t\terror,\n\t\t\tclient,\n\t\t\tdefaultMessages: _defaultMessages,\n\t\t\tsetDefaultMessages,\n\t\t\tquickOptions: _quickOptions,\n\t\t\tsetQuickOptions,\n\t\t}),\n\t\t[\n\t\t\twebsite,\n\t\t\tunreadCount,\n\t\t\tisLoading,\n\t\t\terror,\n\t\t\tclient,\n\t\t\t_defaultMessages,\n\t\t\t_quickOptions,\n\t\t\tsetDefaultMessages,\n\t\t\tsetQuickOptions,\n\t\t\tsetUnreadCountStable,\n\t\t]\n\t);\n\n\treturn (\n\t\t<SupportContext.Provider value={value}>\n\t\t\t<WebSocketProvider\n\t\t\t\tautoConnect={autoConnect}\n\t\t\t\tonConnect={onWsConnect}\n\t\t\t\tonDisconnect={onWsDisconnect}\n\t\t\t\tonError={onWsError}\n\t\t\t\tpublicKey={publicKey}\n\t\t\t\tvisitorId={website?.visitor?.id}\n\t\t\t\twebsiteId={website?.id}\n\t\t\t\twsUrl={wsUrl}\n\t\t\t>\n\t\t\t\t{children}\n\t\t\t</WebSocketProvider>\n\t\t</SupportContext.Provider>\n\t);\n}\n\n/**\n * Hosts the entire customer support widget ecosystem by handing out context\n * about the current website, visitor, unread counts, realtime subscriptions\n * and the REST client. Provide your Cossistant public key plus optional\n * defaults to configure the widget behaviour.\n */\nexport function SupportProvider({\n\tchildren,\n\tapiUrl = \"https://api.cossistant.com/v1\",\n\twsUrl = \"wss://api.cossistant.com/ws\",\n\tpublicKey,\n\tdefaultMessages,\n\tquickOptions,\n\tautoConnect = true,\n\tonWsConnect,\n\tonWsDisconnect,\n\tonWsError,\n\tqueryClient,\n}: SupportProviderProps) {\n\t// Create a default QueryClient if none provided\n\tconst [defaultQueryClient] = React.useState(\n\t\t() =>\n\t\t\tnew QueryClient({\n\t\t\t\tdefaultOptions: {\n\t\t\t\t\tqueries: {\n\t\t\t\t\t\tstaleTime: 5 * 60 * 1000, // 5 minutes\n\t\t\t\t\t\tgcTime: 10 * 60 * 1000, // 10 minutes\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t})\n\t);\n\n\tconst activeQueryClient = queryClient || defaultQueryClient;\n\n\treturn (\n\t\t<QueryClientProvider client={activeQueryClient}>\n\t\t\t<SupportProviderInner\n\t\t\t\tapiUrl={apiUrl}\n\t\t\t\tautoConnect={autoConnect}\n\t\t\t\tdefaultMessages={defaultMessages}\n\t\t\t\tonWsConnect={onWsConnect}\n\t\t\t\tonWsDisconnect={onWsDisconnect}\n\t\t\t\tonWsError={onWsError}\n\t\t\t\tpublicKey={publicKey}\n\t\t\t\tquickOptions={quickOptions}\n\t\t\t\twsUrl={wsUrl}\n\t\t\t>\n\t\t\t\t{children}\n\t\t\t</SupportProviderInner>\n\t\t</QueryClientProvider>\n\t);\n}\n\n/**\n * Convenience hook that exposes the aggregated support context. Throws when it\n * is consumed outside of `SupportProvider` to catch integration mistakes.\n */\nexport function useSupport() {\n\tconst context = React.useContext(SupportContext);\n\tif (!context) {\n\t\tthrow new Error(\n\t\t\t\"useSupport must be used within a cossistant SupportProvider\"\n\t\t);\n\t}\n\n\tconst availableHumanAgents = context.website?.availableHumanAgents || [];\n\tconst availableAIAgents = context.website?.availableAIAgents || [];\n\tconst visitorLanguage = context.website?.visitor?.language || null;\n\n\t// Create visitor object with normalized locale\n\tconst visitor = context.website?.visitor\n\t\t? {\n\t\t\t\t...context.website.visitor,\n\t\t\t\tlocale: normalizeLocale(visitorLanguage),\n\t\t\t}\n\t\t: undefined;\n\n\treturn {\n\t\t...context,\n\t\tavailableHumanAgents,\n\t\tavailableAIAgents,\n\t\tvisitor,\n\t};\n}\n"],"mappings":";;;;;;;;;;AAuCA,MAAM,iBAAiBA,QAAM,cAC5B,OACA;;;;;AAMD,SAAS,qBAAqB,EAC7B,UACA,QACA,OACA,WACA,iBACA,cACA,aACA,aACA,gBACA,aACwB;CACxB,MAAM,CAAC,aAAa,kBAAkBA,QAAM,SAAS,EAAE;CACvD,MAAM,CAAC,kBAAkB,uBAAuBA,QAAM,SAEpD,mBAAmB,EAAE,CAAC;CACxB,MAAM,CAAC,eAAe,oBAAoBA,QAAM,SAC/C,gBAAgB,EAAE,CAClB;AAED,SAAM,gBAAgB;AACrB,MAAI,mBAAmB,gBAAgB,SAAS,EAC/C,qBAAoB,gBAAgB;IAEnC,CAAC,gBAAgB,CAAC;AAErB,SAAM,gBAAgB;AACrB,MAAI,gBAAgB,aAAa,SAAS,EACzC,kBAAiB,aAAa;IAE7B,CAAC,aAAa,CAAC;CAElB,MAAM,EAAE,WAAW,UAAU,WAAW,QAAQ,MAAM;CACtD,MAAM,EAAE,SAAS,WAAW,OAAO,iBAAiB,gBAAgB,OAAO;CAO3E,MAAM,QAAQ;AAGd,SAAM,gBAAgB;AACrB,MAAI,QAEH,QAAO,YAAY,oBAAoB,QAAQ,IAAI,QAAQ,SAAS,GAAG;IAEtE,CAAC,QAAQ,QAAQ,CAAC;CAErB,MAAM,qBAAqBA,QAAM,aAC/B,aAA+B,oBAAoB,SAAS,EAC7D,EAAE,CACF;CAED,MAAM,kBAAkBA,QAAM,aAC5B,YAAsB,iBAAiB,QAAQ,EAChD,EAAE,CACF;CAED,MAAM,uBAAuBA,QAAM,aACjC,UAAkB,eAAe,MAAM,EACxC,EAAE,CACF;CAED,MAAM,QAAQA,QAAM,eACZ;EACN;EACA;EACA,gBAAgB;EAChB;EACA;EACA;EACA,iBAAiB;EACjB;EACA,cAAc;EACd;EACA,GACD;EACC;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,CACD;AAED,QACC,oBAAC,eAAe;EAAgB;YAC/B,oBAAC;GACa;GACb,WAAW;GACX,cAAc;GACd,SAAS;GACE;GACX,WAAW,SAAS,SAAS;GAC7B,WAAW,SAAS;GACb;GAEN;IACkB;GACK;;;;;;;;AAU5B,SAAgB,gBAAgB,EAC/B,UACA,SAAS,iCACT,QAAQ,+BACR,WACA,iBACA,cACA,cAAc,MACd,aACA,gBACA,WACA,eACwB;CAExB,MAAM,CAAC,sBAAsBA,QAAM,eAEjC,IAAI,YAAY,EACf,gBAAgB,EACf,SAAS;EACR,WAAW,MAAS;EACpB,QAAQ,MAAU;EAClB,EACD,EACD,CAAC,CACH;AAID,QACC,oBAAC;EAAoB,QAHI,eAAe;YAIvC,oBAAC;GACQ;GACK;GACI;GACJ;GACG;GACL;GACA;GACG;GACP;GAEN;IACqB;GACF;;;;;;AAQxB,SAAgB,aAAa;CAC5B,MAAM,UAAUA,QAAM,WAAW,eAAe;AAChD,KAAI,CAAC,QACJ,OAAM,IAAI,MACT,8DACA;CAGF,MAAM,uBAAuB,QAAQ,SAAS,wBAAwB,EAAE;CACxE,MAAM,oBAAoB,QAAQ,SAAS,qBAAqB,EAAE;CAClE,MAAM,kBAAkB,QAAQ,SAAS,SAAS,YAAY;CAG9D,MAAM,UAAU,QAAQ,SAAS,UAC9B;EACA,GAAG,QAAQ,QAAQ;EACnB,QAAQ,gBAAgB,gBAAgB;EACxC,GACA;AAEH,QAAO;EACN,GAAG;EACH;EACA;EACA;EACA"}
1
+ {"version":3,"file":"provider.js","names":[],"sources":["../src/provider.tsx"],"sourcesContent":["import type { CossistantClient } from \"@cossistant/core\";\nimport { normalizeLocale } from \"@cossistant/core\";\nimport type { DefaultMessage, PublicWebsiteResponse } from \"@cossistant/types\";\nimport type { TimelineItem } from \"@cossistant/types/api/timeline-item\";\nimport { ConversationTimelineType } from \"@cossistant/types/enums\";\nimport React from \"react\";\nimport { useStoreSelector } from \"./hooks/private/store/use-store-selector\";\nimport { useWebsiteStore } from \"./hooks/private/store/use-website-store\";\nimport { useClient } from \"./hooks/private/use-rest-client\";\nimport { useSeenStore } from \"./realtime/seen-store\";\nimport { WebSocketProvider } from \"./support\";\nimport {\n\tinitializeSupportStore,\n\tuseSupportStore,\n} from \"./support/store/support-store\";\n\nexport type SupportProviderProps = {\n\tchildren: React.ReactNode;\n\tdefaultOpen?: boolean;\n\tapiUrl?: string;\n\twsUrl?: string;\n\tpublicKey?: string;\n\tdefaultMessages?: DefaultMessage[];\n\tquickOptions?: string[];\n\tautoConnect?: boolean;\n\tonWsConnect?: () => void;\n\tonWsDisconnect?: () => void;\n\tonWsError?: (error: Error) => void;\n\tsize?: \"normal\" | \"larger\";\n};\n\nexport type CossistantProviderProps = SupportProviderProps;\n\nexport type CossistantContextValue = {\n\twebsite: PublicWebsiteResponse | null;\n\tdefaultMessages: DefaultMessage[];\n\tquickOptions: string[];\n\tsetDefaultMessages: (messages: DefaultMessage[]) => void;\n\tsetQuickOptions: (options: string[]) => void;\n\tunreadCount: number;\n\tsetUnreadCount: (count: number) => void;\n\tisLoading: boolean;\n\terror: Error | null;\n\tclient: CossistantClient;\n\tisOpen: boolean;\n\topen: () => void;\n\tclose: () => void;\n\ttoggle: () => void;\n};\n\ntype WebsiteData = NonNullable<CossistantContextValue[\"website\"]>;\n\ntype VisitorWithLocale = WebsiteData[\"visitor\"] extends null | undefined\n\t? undefined\n\t: NonNullable<WebsiteData[\"visitor\"]> & { locale: string | null };\n\ntype ConversationSnapshot = {\n\tid: string;\n\tlastTimelineItem: TimelineItem | null;\n};\n\nfunction areConversationSnapshotsEqual(\n\ta: ConversationSnapshot[],\n\tb: ConversationSnapshot[]\n): boolean {\n\tif (a === b) {\n\t\treturn true;\n\t}\n\n\tif (a.length !== b.length) {\n\t\treturn false;\n\t}\n\n\tfor (let index = 0; index < a.length; index += 1) {\n\t\tconst snapshotA = a[index];\n\t\tconst snapshotB = b[index];\n\n\t\tif (!snapshotA) {\n\t\t\treturn false;\n\t\t}\n\t\tif (!snapshotB) {\n\t\t\treturn false;\n\t\t}\n\n\t\tconst aLastCreatedAt = snapshotA.lastTimelineItem?.createdAt ?? null;\n\t\tconst bLastCreatedAt = snapshotB.lastTimelineItem?.createdAt ?? null;\n\t\tif (snapshotA.id !== snapshotB.id || aLastCreatedAt !== bLastCreatedAt) {\n\t\t\treturn false;\n\t\t}\n\t}\n\n\treturn true;\n}\n\nexport type UseSupportValue = CossistantContextValue & {\n\tavailableHumanAgents: NonNullable<WebsiteData[\"availableHumanAgents\"]> | [];\n\tavailableAIAgents: NonNullable<WebsiteData[\"availableAIAgents\"]> | [];\n\tvisitor?: VisitorWithLocale;\n\tsize: \"normal\" | \"larger\";\n};\n\nexport const SupportContext = React.createContext<\n\tCossistantContextValue | undefined\n>(undefined);\n\n/**\n * Internal implementation that wires the REST client and websocket provider\n * together before exposing the combined context.\n */\nfunction SupportProviderInner({\n\tchildren,\n\tapiUrl,\n\twsUrl,\n\tpublicKey,\n\tdefaultMessages,\n\tquickOptions,\n\tautoConnect,\n\tonWsConnect,\n\tonWsDisconnect,\n\tonWsError,\n\tsize = \"normal\",\n\tdefaultOpen = false,\n}: SupportProviderProps) {\n\tconst [unreadCount, setUnreadCount] = React.useState(0);\n\tconst prefetchedVisitorRef = React.useRef<string | null>(null);\n\tconst [_defaultMessages, _setDefaultMessages] = React.useState<\n\t\tDefaultMessage[]\n\t>(defaultMessages ?? []);\n\tconst [_quickOptions, _setQuickOptions] = React.useState<string[]>(\n\t\tquickOptions ?? []\n\t);\n\n\t// Initialize support store with configuration\n\tReact.useEffect(() => {\n\t\tinitializeSupportStore({ size, defaultOpen });\n\t}, [size, defaultOpen]);\n\n\t// Get support store state and actions\n\tconst { config, open, close, toggle } = useSupportStore();\n\n\t// Update state when props change (for initial values from provider)\n\tReact.useEffect(() => {\n\t\tif (defaultMessages?.length) {\n\t\t\t_setDefaultMessages(defaultMessages);\n\t\t}\n\t}, [defaultMessages]);\n\n\tReact.useEffect(() => {\n\t\tif (quickOptions?.length) {\n\t\t\t_setQuickOptions(quickOptions);\n\t\t}\n\t}, [quickOptions]);\n\n\tconst { client } = useClient(publicKey, apiUrl, wsUrl);\n\tconst { website, isLoading, error: websiteError } = useWebsiteStore(client);\n\tconst isVisitorBlocked = website?.visitor?.isBlocked ?? false;\n\tconst visitorId = website?.visitor?.id ?? null;\n\n\tconst seenEntriesByConversation = useSeenStore(\n\t\tReact.useCallback((state) => state.conversations, [])\n\t);\n\n\tconst conversationSnapshots = useStoreSelector(\n\t\tclient.conversationsStore,\n\t\tReact.useCallback(\n\t\t\t(state) =>\n\t\t\t\tstate.ids\n\t\t\t\t\t.map((id) => {\n\t\t\t\t\t\tconst conversation = state.byId[id];\n\n\t\t\t\t\t\tif (!conversation) {\n\t\t\t\t\t\t\treturn null;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\tid: conversation.id,\n\t\t\t\t\t\t\tlastTimelineItem: conversation.lastTimelineItem ?? null,\n\t\t\t\t\t\t} satisfies ConversationSnapshot;\n\t\t\t\t\t})\n\t\t\t\t\t.filter(\n\t\t\t\t\t\t(snapshot): snapshot is ConversationSnapshot => snapshot !== null\n\t\t\t\t\t),\n\t\t\t[]\n\t\t),\n\t\tareConversationSnapshotsEqual\n\t);\n\n\tconst derivedUnreadCount = React.useMemo(() => {\n\t\tif (!visitorId) {\n\t\t\treturn 0;\n\t\t}\n\n\t\tlet count = 0;\n\n\t\tfor (const {\n\t\t\tid: conversationId,\n\t\t\tlastTimelineItem,\n\t\t} of conversationSnapshots) {\n\t\t\tif (!lastTimelineItem) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (lastTimelineItem.type !== ConversationTimelineType.MESSAGE) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (\n\t\t\t\tlastTimelineItem.visitorId &&\n\t\t\t\tlastTimelineItem.visitorId === visitorId\n\t\t\t) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tconst createdAtTime = Date.parse(lastTimelineItem.createdAt);\n\n\t\t\tif (Number.isNaN(createdAtTime)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tconst seenEntries = seenEntriesByConversation[conversationId];\n\n\t\t\tif (seenEntries) {\n\t\t\t\tconst visitorSeenEntry = Object.values(seenEntries).find(\n\t\t\t\t\t(entry) =>\n\t\t\t\t\t\tentry.actorType === \"visitor\" && entry.actorId === visitorId\n\t\t\t\t);\n\n\t\t\t\tif (visitorSeenEntry) {\n\t\t\t\t\tconst lastSeenTime = Date.parse(visitorSeenEntry.lastSeenAt);\n\n\t\t\t\t\tif (!Number.isNaN(lastSeenTime) && createdAtTime <= lastSeenTime) {\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tcount += 1;\n\t\t}\n\n\t\treturn count;\n\t}, [conversationSnapshots, seenEntriesByConversation, visitorId]);\n\n\tReact.useEffect(() => {\n\t\tsetUnreadCount(derivedUnreadCount);\n\t}, [derivedUnreadCount, setUnreadCount]);\n\n\t// Prime REST client with website/visitor context so headers are sent reliably\n\tReact.useEffect(() => {\n\t\tif (!website) {\n\t\t\treturn;\n\t\t}\n\n\t\tclient.setWebsiteContext(website.id, website.visitor?.id ?? undefined);\n\t}, [client, website]);\n\n\tReact.useEffect(() => {\n\t\tif (isVisitorBlocked) {\n\t\t\tprefetchedVisitorRef.current = null;\n\t\t\treturn;\n\t\t}\n\n\t\tif (!autoConnect) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (!website) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (!visitorId) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (prefetchedVisitorRef.current === visitorId) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst hasExistingConversations =\n\t\t\tclient.conversationsStore.getState().ids.length > 0;\n\n\t\tprefetchedVisitorRef.current = visitorId;\n\n\t\tif (hasExistingConversations) {\n\t\t\treturn;\n\t\t}\n\n\t\tvoid client.listConversations().catch((err) => {\n\t\t\tconsole.error(\"[SupportProvider] Failed to prefetch conversations\", err);\n\t\t\tprefetchedVisitorRef.current = null;\n\t\t});\n\t}, [autoConnect, client, isVisitorBlocked, visitorId, website]);\n\n\tconst error = websiteError;\n\n\tReact.useEffect(() => {\n\t\tclient.setVisitorBlocked(isVisitorBlocked);\n\t}, [client, isVisitorBlocked]);\n\n\tconst setDefaultMessages = React.useCallback((messages: DefaultMessage[]) => {\n\t\t_setDefaultMessages(messages);\n\t}, []);\n\n\tconst setQuickOptions = React.useCallback((options: string[]) => {\n\t\t_setQuickOptions(options);\n\t}, []);\n\n\tconst value = React.useMemo<CossistantContextValue>(\n\t\t() => ({\n\t\t\twebsite,\n\t\t\tunreadCount,\n\t\t\tsetUnreadCount,\n\t\t\tisLoading,\n\t\t\terror,\n\t\t\tclient,\n\t\t\tdefaultMessages: _defaultMessages,\n\t\t\tsetDefaultMessages,\n\t\t\tquickOptions: _quickOptions,\n\t\t\tsetQuickOptions,\n\t\t\tisOpen: config.isOpen,\n\t\t\topen,\n\t\t\tclose,\n\t\t\ttoggle,\n\t\t}),\n\t\t[\n\t\t\twebsite,\n\t\t\tunreadCount,\n\t\t\tisLoading,\n\t\t\terror,\n\t\t\tclient,\n\t\t\t_defaultMessages,\n\t\t\t_quickOptions,\n\t\t\tsetDefaultMessages,\n\t\t\tsetQuickOptions,\n\t\t\tconfig.isOpen,\n\t\t\topen,\n\t\t\tclose,\n\t\t\ttoggle,\n\t\t]\n\t);\n\n\tconst webSocketKey = React.useMemo(() => {\n\t\tif (!website) {\n\t\t\treturn \"no-website\";\n\t\t}\n\n\t\tconst visitorKey = website.visitor?.id ?? \"anonymous\";\n\t\tconst blockedState = isVisitorBlocked ? \"blocked\" : \"active\";\n\n\t\treturn `${website.id}:${visitorKey}:${blockedState}`;\n\t}, [isVisitorBlocked, website]);\n\n\treturn (\n\t\t<SupportContext.Provider value={value}>\n\t\t\t<WebSocketProvider\n\t\t\t\tautoConnect={autoConnect && !isVisitorBlocked}\n\t\t\t\tkey={webSocketKey}\n\t\t\t\tonConnect={onWsConnect}\n\t\t\t\tonDisconnect={onWsDisconnect}\n\t\t\t\tonError={onWsError}\n\t\t\t\tpublicKey={publicKey}\n\t\t\t\tvisitorId={isVisitorBlocked ? undefined : website?.visitor?.id}\n\t\t\t\twebsiteId={website?.id}\n\t\t\t\twsUrl={wsUrl}\n\t\t\t>\n\t\t\t\t{children}\n\t\t\t</WebSocketProvider>\n\t\t</SupportContext.Provider>\n\t);\n}\n\n/**\n * Hosts the entire customer support widget ecosystem by handing out context\n * about the current website, visitor, unread counts, realtime subscriptions\n * and the REST client. Provide your Cossistant public key plus optional\n * defaults to configure the widget behaviour.\n */\nexport function SupportProvider({\n\tchildren,\n\tapiUrl = \"https://api.cossistant.com/v1\",\n\twsUrl = \"wss://api.cossistant.com/ws\",\n\tpublicKey,\n\tdefaultMessages,\n\tquickOptions,\n\tautoConnect = true,\n\tonWsConnect,\n\tonWsDisconnect,\n\tonWsError,\n\tsize = \"normal\",\n\tdefaultOpen = false,\n}: SupportProviderProps): React.ReactElement {\n\treturn (\n\t\t<SupportProviderInner\n\t\t\tapiUrl={apiUrl}\n\t\t\tautoConnect={autoConnect}\n\t\t\tdefaultMessages={defaultMessages}\n\t\t\tdefaultOpen={defaultOpen}\n\t\t\tonWsConnect={onWsConnect}\n\t\t\tonWsDisconnect={onWsDisconnect}\n\t\t\tonWsError={onWsError}\n\t\t\tpublicKey={publicKey}\n\t\t\tquickOptions={quickOptions}\n\t\t\tsize={size}\n\t\t\twsUrl={wsUrl}\n\t\t>\n\t\t\t{children}\n\t\t</SupportProviderInner>\n\t);\n}\n\n/**\n * Convenience hook that exposes the aggregated support context. Throws when it\n * is consumed outside of `SupportProvider` to catch integration mistakes.\n */\nexport function useSupport(): UseSupportValue {\n\tconst context = React.useContext(SupportContext);\n\tif (!context) {\n\t\tthrow new Error(\n\t\t\t\"useSupport must be used within a cossistant SupportProvider\"\n\t\t);\n\t}\n\n\tconst availableHumanAgents = context.website?.availableHumanAgents || [];\n\tconst availableAIAgents = context.website?.availableAIAgents || [];\n\tconst visitorLanguage = context.website?.visitor?.language || null;\n\n\t// Get additional config from support store\n\tconst { config } = useSupportStore();\n\n\t// Create visitor object with normalized locale\n\tconst visitor = context.website?.visitor\n\t\t? {\n\t\t\t\t...context.website.visitor,\n\t\t\t\tlocale: normalizeLocale(visitorLanguage),\n\t\t\t}\n\t\t: undefined;\n\n\treturn {\n\t\t...context,\n\t\tavailableHumanAgents,\n\t\tavailableAIAgents,\n\t\tvisitor,\n\t\tsize: config.size,\n\t};\n}\n"],"mappings":"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"}
@@ -2,6 +2,10 @@ import { AnyRealtimeEvent } from "../realtime-events.js";
2
2
 
3
3
  //#region src/realtime/event-filter.d.ts
4
4
  declare function getTargetVisitorId(event: AnyRealtimeEvent): string | null;
5
+ /**
6
+ * Determines whether a realtime event should be processed based on website and
7
+ * visitor identifiers.
8
+ */
5
9
  declare function shouldDeliverEvent(event: AnyRealtimeEvent, websiteId: string | null, visitorId: string | null): boolean;
6
10
  //#endregion
7
11
  export { getTargetVisitorId, shouldDeliverEvent };
@@ -1 +1 @@
1
- {"version":3,"file":"event-filter.d.ts","names":[],"sources":["../../src/realtime/event-filter.ts"],"sourcesContent":[],"mappings":";;;iBAES,kBAAA,QAA0B;iBAkBnB,kBAAA,QACR"}
1
+ {"version":3,"file":"event-filter.d.ts","names":[],"sources":["../../src/realtime/event-filter.ts"],"sourcesContent":[],"mappings":";;;iBAES,kBAAA,QAA0B;;AAFuC;AAwB1E;;iBAAgB,kBAAA,QACR"}
@@ -8,6 +8,10 @@ function getTargetVisitorId(event) {
8
8
  }
9
9
  return null;
10
10
  }
11
+ /**
12
+ * Determines whether a realtime event should be processed based on website and
13
+ * visitor identifiers.
14
+ */
11
15
  function shouldDeliverEvent(event, websiteId, visitorId) {
12
16
  if (websiteId && event.payload.websiteId !== websiteId) return false;
13
17
  if (!visitorId) return true;
@@ -1 +1 @@
1
- {"version":3,"file":"event-filter.js","names":[],"sources":["../../src/realtime/event-filter.ts"],"sourcesContent":["import type { AnyRealtimeEvent } from \"@cossistant/types/realtime-events\";\n\nfunction getTargetVisitorId(event: AnyRealtimeEvent): string | null {\n\tconst payloadVisitorId = event.payload.visitorId;\n\n\tif (typeof payloadVisitorId === \"string\" && payloadVisitorId.length > 0) {\n\t\treturn payloadVisitorId;\n\t}\n\n\tif (event.type === \"timelineItemCreated\") {\n\t\tconst itemVisitorId = event.payload.item.visitorId;\n\n\t\tif (typeof itemVisitorId === \"string\" && itemVisitorId.length > 0) {\n\t\t\treturn itemVisitorId;\n\t\t}\n\t}\n\n\treturn null;\n}\n\nexport function shouldDeliverEvent(\n\tevent: AnyRealtimeEvent,\n\twebsiteId: string | null,\n\tvisitorId: string | null\n): boolean {\n\tif (websiteId && event.payload.websiteId !== websiteId) {\n\t\treturn false;\n\t}\n\n\tif (!visitorId) {\n\t\treturn true;\n\t}\n\n\tconst targetVisitorId = getTargetVisitorId(event);\n\n\tif (targetVisitorId && targetVisitorId !== visitorId) {\n\t\treturn false;\n\t}\n\n\treturn true;\n}\n\nexport { getTargetVisitorId };\n"],"mappings":";AAEA,SAAS,mBAAmB,OAAwC;CACnE,MAAM,mBAAmB,MAAM,QAAQ;AAEvC,KAAI,OAAO,qBAAqB,YAAY,iBAAiB,SAAS,EACrE,QAAO;AAGR,KAAI,MAAM,SAAS,uBAAuB;EACzC,MAAM,gBAAgB,MAAM,QAAQ,KAAK;AAEzC,MAAI,OAAO,kBAAkB,YAAY,cAAc,SAAS,EAC/D,QAAO;;AAIT,QAAO;;AAGR,SAAgB,mBACf,OACA,WACA,WACU;AACV,KAAI,aAAa,MAAM,QAAQ,cAAc,UAC5C,QAAO;AAGR,KAAI,CAAC,UACJ,QAAO;CAGR,MAAM,kBAAkB,mBAAmB,MAAM;AAEjD,KAAI,mBAAmB,oBAAoB,UAC1C,QAAO;AAGR,QAAO"}
1
+ {"version":3,"file":"event-filter.js","names":[],"sources":["../../src/realtime/event-filter.ts"],"sourcesContent":["import type { AnyRealtimeEvent } from \"@cossistant/types/realtime-events\";\n\nfunction getTargetVisitorId(event: AnyRealtimeEvent): string | null {\n\tconst payloadVisitorId = event.payload.visitorId;\n\n\tif (typeof payloadVisitorId === \"string\" && payloadVisitorId.length > 0) {\n\t\treturn payloadVisitorId;\n\t}\n\n\tif (event.type === \"timelineItemCreated\") {\n\t\tconst itemVisitorId = event.payload.item.visitorId;\n\n\t\tif (typeof itemVisitorId === \"string\" && itemVisitorId.length > 0) {\n\t\t\treturn itemVisitorId;\n\t\t}\n\t}\n\n\treturn null;\n}\n\n/**\n * Determines whether a realtime event should be processed based on website and\n * visitor identifiers.\n */\nexport function shouldDeliverEvent(\n\tevent: AnyRealtimeEvent,\n\twebsiteId: string | null,\n\tvisitorId: string | null\n): boolean {\n\tif (websiteId && event.payload.websiteId !== websiteId) {\n\t\treturn false;\n\t}\n\n\tif (!visitorId) {\n\t\treturn true;\n\t}\n\n\tconst targetVisitorId = getTargetVisitorId(event);\n\n\tif (targetVisitorId && targetVisitorId !== visitorId) {\n\t\treturn false;\n\t}\n\n\treturn true;\n}\n\nexport { getTargetVisitorId };\n"],"mappings":";AAEA,SAAS,mBAAmB,OAAwC;CACnE,MAAM,mBAAmB,MAAM,QAAQ;AAEvC,KAAI,OAAO,qBAAqB,YAAY,iBAAiB,SAAS,EACrE,QAAO;AAGR,KAAI,MAAM,SAAS,uBAAuB;EACzC,MAAM,gBAAgB,MAAM,QAAQ,KAAK;AAEzC,MAAI,OAAO,kBAAkB,YAAY,cAAc,SAAS,EAC/D,QAAO;;AAIT,QAAO;;;;;;AAOR,SAAgB,mBACf,OACA,WACA,WACU;AACV,KAAI,aAAa,MAAM,QAAQ,cAAc,UAC5C,QAAO;AAGR,KAAI,CAAC,UACJ,QAAO;CAGR,MAAM,kBAAkB,mBAAmB,MAAM;AAEjD,KAAI,mBAAmB,oBAAoB,UAC1C,QAAO;AAGR,QAAO"}
package/realtime/index.js CHANGED
@@ -1,5 +1,5 @@
1
- import { RealtimeProvider, useRealtimeConnection } from "./provider.js";
2
1
  import { applyConversationSeenEvent, hydrateConversationSeen, upsertConversationSeen } from "./seen-store.js";
2
+ import { RealtimeProvider, useRealtimeConnection } from "./provider.js";
3
3
  import { applyConversationTypingEvent, clearTypingFromTimelineItem, clearTypingState, setTypingState } from "./typing-store.js";
4
4
  import { useRealtime } from "./use-realtime.js";
5
5
  import { SupportRealtimeProvider } from "./support-provider.js";
@@ -1,6 +1,5 @@
1
1
  import { AnyRealtimeEvent, RealtimeEvent } from "../realtime-events.js";
2
2
  import React from "react";
3
- import * as react_jsx_runtime0 from "react/jsx-runtime";
4
3
 
5
4
  //#region src/realtime/provider.d.ts
6
5
  type SubscribeHandler = (event: AnyRealtimeEvent) => void;
@@ -42,6 +41,9 @@ type RealtimeContextValue = RealtimeConnectionState & {
42
41
  websiteId: string | null;
43
42
  userId: string | null;
44
43
  };
44
+ /**
45
+ * Provides websocket connectivity and heartbeating logic for realtime events.
46
+ */
45
47
  declare function RealtimeProvider({
46
48
  children,
47
49
  wsUrl,
@@ -50,7 +52,10 @@ declare function RealtimeProvider({
50
52
  onConnect,
51
53
  onDisconnect,
52
54
  onError
53
- }: RealtimeProviderProps): react_jsx_runtime0.JSX.Element;
55
+ }: RealtimeProviderProps): React.ReactElement;
56
+ /**
57
+ * Returns the realtime connection context.
58
+ */
54
59
  declare function useRealtimeConnection(): RealtimeContextValue;
55
60
  //#endregion
56
61
  export { type RealtimeAuthConfig, type RealtimeContextValue, type RealtimeEvent, RealtimeProvider, type RealtimeProviderProps, useRealtimeConnection };
@@ -1 +1 @@
1
- {"version":3,"file":"provider.d.ts","names":[],"sources":["../../src/realtime/provider.tsx"],"sourcesContent":[],"mappings":";;;;;KAuBK,gBAAA,WAA2B;KA+B3B,iBAAA;;EA/BA,SAAA,EAAA,MAAA,GAAgB,IAAA;EA+BhB,SAAA,CAAA,EAAA,MAAA,GAAiB,IAAA;EAOjB,SAAA,CAAA,EAAA,MAAA,GAAiB,IAAA;AAAA,CAAA;KAAjB,iBAAA,GAOkB;MAAG,EAAA,SAAA;cAAoB,EAAA,MAAA,GAAA,IAAA;EAAiB,SAAA,CAAA,EAAA,MAAA,GAAA,IAAA;EAW1D,MAAA,CAAA,EAAA,MAAA,GAAA,IAAA;CAAqB;KAXrB,kBAAA,GAAqB,iBAYT,GAZ6B,iBAY7B;KADZ,qBAAA,GAGE;UAIY,EANR,KAAA,CAAM,SAME;EAAK,KAAA,CAAA,EAAA,MAAA;EAGnB,IAAA,EAPE,kBAOF,GAAuB,IAAA;EAAA,WAAA,CAAA,EAAA,OAAA;WAGpB,CAAA,EAAA,GAAA,GAAA,IAAA;cACO,CAAA,EAAA,GAAA,GAAA,IAAA;SAEO,CAAA,EAAA,CAAA,KAAA,EATH,KASG,EAAA,GAAA,IAAA;;KANjB,uBAAA,GAOuB;EAKvB,WAAA,EAAA,OAAA;EA2QW,YAAA,EAAA,OAAgB;EAAA,KAAA,EApRxB,KAoRwB,GAAA,IAAA;MAC/B,EAAA,CAAA,KAAA,EApRc,gBAoRd,EAAA,GAAA,IAAA;SACA,EAAA,CAAA,IAAA,EAAA,MAAA,EAAA,GAAA,IAAA;WACA,EAAA,CAAA,OAAA,EApRqB,gBAoRrB,EAAA,GAAA,GAAA,GAAA,IAAA;WACA,EApRW,gBAoRX,GAAA,IAAA;cACA,EAAA,MAAA,GAAA,IAAA;WACA,EAAA,GAAA,GAAA,IAAA;;KAjRI,oBAAA,GAAuB,uBAmRzB,GAAA;WAAqB,EAAA,MAAA,GAAA,IAAA;EAAA,SAAA,EAAA,MAAA,GAAA,IAAA;EAmPR,MAAA,EAAA,MAAA,GAAA,IAAA;;iBA3PA,gBAAA;;;;;;;;GAQb,wBAAqB,kBAAA,CAAA,GAAA,CAAA;iBAmPR,qBAAA,CAAA,GAAyB"}
1
+ {"version":3,"file":"provider.d.ts","names":[],"sources":["../../src/realtime/provider.tsx"],"sourcesContent":[],"mappings":";;;;KAuBK,gBAAA,WAA2B;KA+B3B,iBAAA;EA/BA,IAAA,EAAA,SAAA;EA+BA,SAAA,EAAA,MAAA,GAAiB,IAAA;EAOjB,SAAA,CAAA,EAAA,MAAA,GAAiB,IAAA;EAOjB,SAAA,CAAA,EAAA,MAAA,GAAkB,IAAA;AAAwC,CAAA;KAP1D,iBAAA,GAmBY;EAEV,IAAA,EAAA,SAAA;EAIY,YAAA,EAAA,MAAA,GAAA,IAAA;EAAK,SAAA,CAAA,EAAA,MAAA,GAAA,IAAA;EAGnB,MAAA,CAAA,EAAA,MAAA,GAAA,IAAA;CAGG;KAxBH,kBAAA,GAAqB,iBAyBX,GAzB+B,iBAyB/B;KAdV,qBAAA,GAgBiB;EACV,QAAA,EAhBD,KAAA,CAAM,SAgBL;EAAgB,KAAA,CAAA,EAAA,MAAA;EAKvB,IAAA,EAnBE,kBAmBkB,GAAA,IAAA;EA8QT,WAAA,CAAA,EAAA,OAAgB;EAC/B,SAAA,CAAA,EAAA,GAAA,GAAA,IAAA;EACA,YAAA,CAAA,EAAA,GAAA,GAAA,IAAA;EACA,OAAA,CAAA,EAAA,CAAA,KAAA,EAhSkB,KAgSlB,EAAA,GAAA,IAAA;CACA;KA9RI,uBAAA,GA+RJ;EACA,WAAA,EAAA,OAAA;EACA,YAAA,EAAA,OAAA;EACE,KAAA,EA/RK,KA+RL,GAAA,IAAA;EAAwB,IAAM,EAAA,CAAA,KAAA,EA9RlB,gBA8RkB,EAAA,GAAA,IAAA;EAAY,OAAA,EAAA,CAAA,IAAA,EAAA,MAAA,EAAA,GAAA,IAAA;EA6S7B,SAAA,EAAA,CAAA,OAAA,EAzkBM,gBAykBmB,EAAA,GAAA,GAAA,GAAA,IAAA;aAxkB7B;;;;KAKP,oBAAA,GAAuB;;;;;;;;iBA8QZ,gBAAA;;;;;;;;GAQb,wBAAwB,KAAA,CAAM;;;;iBA6SjB,qBAAA,CAAA,GAAyB"}
@@ -2,9 +2,9 @@
2
2
 
3
3
 
4
4
  import { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
5
- import { jsx } from "react/jsx-runtime";
6
5
  import { isValidEventType, validateRealtimeEvent } from "@cossistant/types/realtime-events";
7
6
  import useWebSocket, { ReadyState } from "react-use-websocket";
7
+ import { jsx } from "react/jsx-runtime";
8
8
 
9
9
  //#region src/realtime/provider.tsx
10
10
  const DEFAULT_HEARTBEAT_INTERVAL_MS = 15e3;
@@ -180,11 +180,16 @@ function buildSocketUrl(baseUrl, auth) {
180
180
  return null;
181
181
  }
182
182
  }
183
+ /**
184
+ * Provides websocket connectivity and heartbeating logic for realtime events.
185
+ */
183
186
  function RealtimeProvider({ children, wsUrl = DEFAULT_WS_URL, auth, autoConnect = true, onConnect, onDisconnect, onError }) {
184
187
  const normalizedAuth = normalizeAuth(auth);
185
188
  const socketUrl = buildSocketUrl(wsUrl, normalizedAuth);
186
189
  const eventHandlersRef = useRef(/* @__PURE__ */ new Set());
187
190
  const lastHeartbeatRef = useRef(Date.now());
191
+ const hasOpenedRef = useRef(false);
192
+ const previousUrlRef = useRef(null);
188
193
  const [connectionError, setConnectionError] = useState(null);
189
194
  const [lastEvent, setLastEvent] = useState(null);
190
195
  const [connectionId, setConnectionId] = useState(null);
@@ -192,6 +197,12 @@ function RealtimeProvider({ children, wsUrl = DEFAULT_WS_URL, auth, autoConnect
192
197
  const heartbeatTimeoutMs = DEFAULT_HEARTBEAT_TIMEOUT_MS;
193
198
  const canConnect = Boolean(autoConnect && socketUrl);
194
199
  const connectionUrl = canConnect ? socketUrl : null;
200
+ useEffect(() => {
201
+ if (connectionUrl !== previousUrlRef.current) {
202
+ previousUrlRef.current = connectionUrl;
203
+ hasOpenedRef.current = false;
204
+ }
205
+ }, [connectionUrl]);
195
206
  const { sendMessage, sendJsonMessage, lastMessage, readyState, getWebSocket } = useWebSocket(connectionUrl, {
196
207
  shouldReconnect: (closeEvent) => {
197
208
  if (!canConnect) return false;
@@ -210,6 +221,7 @@ function RealtimeProvider({ children, wsUrl = DEFAULT_WS_URL, auth, autoConnect
210
221
  },
211
222
  retryOnError: false,
212
223
  onOpen: () => {
224
+ hasOpenedRef.current = true;
213
225
  setConnectionError(null);
214
226
  lastHeartbeatRef.current = Date.now();
215
227
  onConnect?.();
@@ -219,6 +231,13 @@ function RealtimeProvider({ children, wsUrl = DEFAULT_WS_URL, auth, autoConnect
219
231
  onDisconnect?.();
220
232
  },
221
233
  onError: (event) => {
234
+ if (!canConnect) return;
235
+ const socketLike = event.target;
236
+ const currentSocket = getWebSocket();
237
+ const socketState = typeof WebSocket !== "undefined" && socketLike instanceof WebSocket ? socketLike.readyState : void 0;
238
+ const isThisProvidersSocket = currentSocket === socketLike;
239
+ if (!isThisProvidersSocket && currentSocket || !connectionUrl || isThisProvidersSocket && !hasOpenedRef.current && (socketState === WebSocket.CLOSING || socketState === WebSocket.CLOSED)) return;
240
+ if (isThisProvidersSocket && !hasOpenedRef.current && socketState === WebSocket.CONNECTING && connectionUrl !== previousUrlRef.current) return;
222
241
  const err = /* @__PURE__ */ new Error(`WebSocket error: ${event.type}`);
223
242
  setConnectionError(err);
224
243
  onError?.(err);
@@ -340,6 +359,9 @@ function RealtimeProvider({ children, wsUrl = DEFAULT_WS_URL, auth, autoConnect
340
359
  children
341
360
  });
342
361
  }
362
+ /**
363
+ * Returns the realtime connection context.
364
+ */
343
365
  function useRealtimeConnection() {
344
366
  const context = useContext(RealtimeContext);
345
367
  if (!context) throw new Error("useRealtimeConnection must be used within RealtimeProvider");
@@ -1 +1 @@
1
- {"version":3,"file":"provider.js","names":["payload: unknown"],"sources":["../../src/realtime/provider.tsx"],"sourcesContent":["\"use client\";\n\nimport {\n\ttype AnyRealtimeEvent,\n\tisValidEventType,\n\ttype RealtimeEvent,\n\tvalidateRealtimeEvent,\n} from \"@cossistant/types/realtime-events\";\nimport type React from \"react\";\nimport {\n\tcreateContext,\n\tuseCallback,\n\tuseContext,\n\tuseEffect,\n\tuseMemo,\n\tuseRef,\n\tuseState,\n} from \"react\";\nimport useWebSocket, { ReadyState } from \"react-use-websocket\";\n\nconst DEFAULT_HEARTBEAT_INTERVAL_MS = 15_000;\nconst DEFAULT_HEARTBEAT_TIMEOUT_MS = 45_000;\n\ntype SubscribeHandler = (event: AnyRealtimeEvent) => void;\n\ntype MessageDecodeResult =\n\t| {\n\t\t\ttype: \"raw-text\";\n\t\t\tdata: string;\n\t }\n\t| {\n\t\t\ttype: \"unsupported\";\n\t };\n\ntype ParsedMessage =\n\t| {\n\t\t\ttype: \"pong\";\n\t }\n\t| {\n\t\t\ttype: \"connection-established\";\n\t\t\tconnectionId: string | null;\n\t }\n\t| {\n\t\t\ttype: \"error\";\n\t\t\tmessage: string;\n\t }\n\t| {\n\t\t\ttype: \"event\";\n\t\t\tevent: AnyRealtimeEvent;\n\t }\n\t| {\n\t\t\ttype: \"invalid\";\n\t };\n\ntype VisitorAuthConfig = {\n\tkind: \"visitor\";\n\tvisitorId: string | null;\n\twebsiteId?: string | null;\n\tpublicKey?: string | null;\n};\n\ntype SessionAuthConfig = {\n\tkind: \"session\";\n\tsessionToken: string | null;\n\twebsiteId?: string | null;\n\tuserId?: string | null;\n};\n\ntype RealtimeAuthConfig = VisitorAuthConfig | SessionAuthConfig;\n\ntype ResolvedAuthConfig = {\n\ttype: \"visitor\" | \"session\";\n\tvisitorId: string | null;\n\twebsiteId: string | null;\n\tuserId: string | null;\n\tsessionToken: string | null;\n\tpublicKey: string | null;\n};\n\ntype RealtimeProviderProps = {\n\tchildren: React.ReactNode;\n\twsUrl?: string;\n\tauth: RealtimeAuthConfig | null;\n\tautoConnect?: boolean;\n\tonConnect?: () => void;\n\tonDisconnect?: () => void;\n\tonError?: (error: Error) => void;\n};\n\ntype RealtimeConnectionState = {\n\tisConnected: boolean;\n\tisConnecting: boolean;\n\terror: Error | null;\n\tsend: (event: AnyRealtimeEvent) => void;\n\tsendRaw: (data: string) => void;\n\tsubscribe: (handler: SubscribeHandler) => () => void;\n\tlastEvent: AnyRealtimeEvent | null;\n\tconnectionId: string | null;\n\treconnect: () => void;\n};\n\ntype RealtimeContextValue = RealtimeConnectionState & {\n\tvisitorId: string | null;\n\twebsiteId: string | null;\n\tuserId: string | null;\n};\n\nconst DEFAULT_WS_URL = \"wss://api.cossistant.com/ws\";\n\nconst RealtimeContext = createContext<RealtimeContextValue | null>(null);\n\n/**\n * Decodes WebSocket message data into a string.\n * Handles string, ArrayBuffer, and ArrayBufferView formats.\n */\nfunction decodeMessageData(data: unknown): MessageDecodeResult {\n\tif (typeof data === \"string\") {\n\t\treturn { type: \"raw-text\", data };\n\t}\n\n\tif (data instanceof ArrayBuffer) {\n\t\ttry {\n\t\t\treturn { type: \"raw-text\", data: new TextDecoder().decode(data) };\n\t\t} catch {\n\t\t\treturn { type: \"unsupported\" };\n\t\t}\n\t}\n\n\tif (ArrayBuffer.isView(data)) {\n\t\ttry {\n\t\t\treturn { type: \"raw-text\", data: new TextDecoder().decode(data.buffer) };\n\t\t} catch {\n\t\t\treturn { type: \"unsupported\" };\n\t\t}\n\t}\n\n\treturn { type: \"unsupported\" };\n}\n\n/**\n * Safely parses JSON string, returning null if invalid.\n */\nfunction parseJson(raw: string): unknown {\n\ttry {\n\t\treturn JSON.parse(raw);\n\t} catch {\n\t\treturn null;\n\t}\n}\n\n/**\n * Extracts a string field from an unknown object, with optional validation.\n */\nfunction extractStringField(\n\tobj: unknown,\n\tfield: string,\n\trequired = false\n): string | null {\n\tif (!obj || typeof obj !== \"object\" || !(field in obj)) {\n\t\treturn required ? null : null;\n\t}\n\tconst value = (obj as Record<string, unknown>)[field];\n\tif (typeof value === \"string\" && value.length > 0) {\n\t\treturn value;\n\t}\n\treturn required ? null : null;\n}\n\n/**\n * Parses a WebSocket message and determines its type and content.\n */\nfunction parseWebSocketMessage(rawText: string): ParsedMessage {\n\t// Handle pong heartbeat\n\tif (rawText === \"pong\") {\n\t\treturn { type: \"pong\" };\n\t}\n\n\t// Try to parse as JSON\n\tconst parsed = parseJson(rawText);\n\tif (!parsed || typeof parsed !== \"object\") {\n\t\treturn { type: \"invalid\" };\n\t}\n\n\tconst messageType = extractStringField(parsed, \"type\");\n\n\t// Handle CONNECTION_ESTABLISHED\n\tif (messageType === \"CONNECTION_ESTABLISHED\") {\n\t\tconst payload = (parsed as { payload?: unknown }).payload;\n\t\tconst connectionId = extractStringField(payload, \"connectionId\");\n\t\treturn { type: \"connection-established\", connectionId };\n\t}\n\n\t// Handle error messages\n\tif (\"error\" in parsed && \"message\" in parsed) {\n\t\tconst message =\n\t\t\textractStringField(parsed, \"message\") || \"Realtime connection error\";\n\t\treturn { type: \"error\", message };\n\t}\n\n\t// Handle realtime events\n\tif (messageType && isValidEventType(messageType)) {\n\t\ttry {\n\t\t\tconst event = constructRealtimeEvent(parsed);\n\t\t\tif (!event) {\n\t\t\t\treturn { type: \"invalid\" };\n\t\t\t}\n\t\t\treturn { type: \"event\", event };\n\t\t} catch (error) {\n\t\t\tconsole.error(\"[Realtime] Failed to construct event\", error);\n\t\t\treturn { type: \"invalid\" };\n\t\t}\n\t}\n\n\treturn { type: \"invalid\" };\n}\n\n/**\n * Constructs a RealtimeEvent from parsed JSON data.\n * Returns null if required fields are missing or validation fails.\n */\nfunction constructRealtimeEvent(parsed: unknown): AnyRealtimeEvent | null {\n\tif (!parsed || typeof parsed !== \"object\" || !(\"type\" in parsed)) {\n\t\treturn null;\n\t}\n\n\tconst type = (parsed as { type: unknown }).type;\n\tif (!isValidEventType(type)) {\n\t\treturn null;\n\t}\n\n\tconst eventType = type;\n\n\t// Extract payload directly\n\tconst payloadSource = (parsed as { payload?: unknown }).payload;\n\n\tlet payload: unknown;\n\ttry {\n\t\tpayload = validateRealtimeEvent(eventType, payloadSource);\n\t} catch (error) {\n\t\tconsole.error(\"[Realtime] Received invalid event payload\", error);\n\t\treturn null;\n\t}\n\n\tconst organizationId = extractStringField(\n\t\tpayloadSource,\n\t\t\"organizationId\",\n\t\ttrue\n\t);\n\tconst websiteId = extractStringField(payloadSource, \"websiteId\", true);\n\n\tif (!organizationId) {\n\t\tconsole.error(\"[Realtime] Received event without organizationId\", parsed);\n\t\treturn null;\n\t}\n\n\tif (!websiteId) {\n\t\tconsole.error(\"[Realtime] Received event without websiteId\", parsed);\n\t\treturn null;\n\t}\n\n\tconst visitorId = extractStringField(parsed, \"visitorId\");\n\n\treturn {\n\t\ttype: eventType,\n\t\tpayload,\n\t\torganizationId,\n\t\twebsiteId,\n\t\tvisitorId,\n\t} as AnyRealtimeEvent;\n}\n\n/**\n * Checks if heartbeat has timed out.\n */\nfunction isHeartbeatTimedOut(\n\tlastHeartbeat: number,\n\ttimeoutMs: number\n): boolean {\n\tconst elapsed = Date.now() - lastHeartbeat;\n\treturn elapsed > timeoutMs;\n}\n\nfunction resolvePublicKey(explicit?: string | null): string | null {\n\tconst trimmed = explicit?.trim();\n\tif (trimmed) {\n\t\treturn trimmed;\n\t}\n\n\tconst fromEnv =\n\t\tprocess.env.NEXT_PUBLIC_COSSISTANT_KEY ||\n\t\tprocess.env.COSSISTANT_PUBLIC_KEY ||\n\t\tnull;\n\n\tconst normalized = fromEnv?.trim();\n\treturn normalized && normalized.length > 0 ? normalized : null;\n}\n\nfunction normalizeAuth(\n\tauth: RealtimeAuthConfig | null\n): ResolvedAuthConfig | null {\n\tif (!auth) {\n\t\treturn null;\n\t}\n\n\tif (auth.kind === \"visitor\") {\n\t\tconst visitorId = auth.visitorId?.trim() || null;\n\n\t\tif (!visitorId) {\n\t\t\treturn null;\n\t\t}\n\n\t\treturn {\n\t\t\ttype: \"visitor\",\n\t\t\tvisitorId,\n\t\t\twebsiteId: auth.websiteId?.trim() || null,\n\t\t\tuserId: null,\n\t\t\tsessionToken: null,\n\t\t\tpublicKey: resolvePublicKey(auth.publicKey ?? null),\n\t\t} satisfies ResolvedAuthConfig;\n\t}\n\n\tconst sessionToken = auth.sessionToken?.trim() || null;\n\n\tif (!sessionToken) {\n\t\treturn null;\n\t}\n\n\treturn {\n\t\ttype: \"session\",\n\t\tvisitorId: null,\n\t\twebsiteId: auth.websiteId?.trim() || null,\n\t\tuserId: auth.userId?.trim() || null,\n\t\tsessionToken,\n\t\tpublicKey: null,\n\t} satisfies ResolvedAuthConfig;\n}\n\nfunction buildSocketUrl(\n\tbaseUrl: string,\n\tauth: ResolvedAuthConfig | null\n): string | null {\n\tif (!auth) {\n\t\treturn null;\n\t}\n\n\ttry {\n\t\tconst url = new URL(baseUrl);\n\n\t\tif (auth.type === \"visitor\") {\n\t\t\turl.searchParams.set(\"visitorId\", auth.visitorId ?? \"\");\n\t\t\tconst publicKey = auth.publicKey;\n\t\t\tif (publicKey) {\n\t\t\t\turl.searchParams.set(\"publicKey\", publicKey);\n\t\t\t}\n\t\t} else {\n\t\t\turl.searchParams.set(\"sessionToken\", auth.sessionToken ?? \"\");\n\t\t\tif (auth.websiteId) {\n\t\t\t\turl.searchParams.set(\"websiteId\", auth.websiteId);\n\t\t\t}\n\t\t}\n\n\t\treturn url.toString();\n\t} catch (error) {\n\t\tconsole.error(\"[Realtime] Failed to build WebSocket URL\", error);\n\t\treturn null;\n\t}\n}\n\nexport function RealtimeProvider({\n\tchildren,\n\twsUrl = DEFAULT_WS_URL,\n\tauth,\n\tautoConnect = true,\n\tonConnect,\n\tonDisconnect,\n\tonError,\n}: RealtimeProviderProps) {\n\tconst normalizedAuth = normalizeAuth(auth);\n\n\tconst socketUrl = buildSocketUrl(wsUrl, normalizedAuth);\n\tconst eventHandlersRef = useRef<Set<SubscribeHandler>>(new Set());\n\tconst lastHeartbeatRef = useRef<number>(Date.now());\n\tconst [connectionError, setConnectionError] = useState<Error | null>(null);\n\tconst [lastEvent, setLastEvent] = useState<AnyRealtimeEvent | null>(null);\n\tconst [connectionId, setConnectionId] = useState<string | null>(null);\n\n\tconst heartbeatIntervalMs = DEFAULT_HEARTBEAT_INTERVAL_MS;\n\tconst heartbeatTimeoutMs = DEFAULT_HEARTBEAT_TIMEOUT_MS;\n\n\tconst canConnect = Boolean(autoConnect && socketUrl);\n\tconst connectionUrl = canConnect ? socketUrl : null;\n\n\tconst {\n\t\tsendMessage,\n\t\tsendJsonMessage,\n\t\tlastMessage,\n\t\treadyState,\n\t\tgetWebSocket,\n\t} = useWebSocket(\n\t\tconnectionUrl,\n\t\t{\n\t\t\tshouldReconnect: (closeEvent) => {\n\t\t\t\tif (!canConnect) {\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\n\t\t\t\tif (closeEvent.code === 1008 || closeEvent.code === 1011) {\n\t\t\t\t\tconst err = new Error(\n\t\t\t\t\t\tcloseEvent.reason ||\n\t\t\t\t\t\t\t\"Realtime connection closed by server. Please check your credentials.\"\n\t\t\t\t\t);\n\t\t\t\t\tsetConnectionError(err);\n\t\t\t\t\tonError?.(err);\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\n\t\t\t\treturn true;\n\t\t\t},\n\t\t\treconnectAttempts: autoConnect ? undefined : 0,\n\t\t\treconnectInterval: (attempt) => {\n\t\t\t\tconst base = 500 * 2 ** attempt;\n\t\t\t\treturn Math.min(base, 30_000);\n\t\t\t},\n\t\t\tretryOnError: false,\n\t\t\tonOpen: () => {\n\t\t\t\tsetConnectionError(null);\n\t\t\t\tlastHeartbeatRef.current = Date.now();\n\t\t\t\tonConnect?.();\n\t\t\t},\n\t\t\tonClose: () => {\n\t\t\t\tsetConnectionId(null);\n\t\t\t\tonDisconnect?.();\n\t\t\t},\n\t\t\tonError: (event) => {\n\t\t\t\tconst err = new Error(`WebSocket error: ${event.type}`);\n\t\t\t\tsetConnectionError(err);\n\t\t\t\tonError?.(err);\n\t\t\t},\n\t\t},\n\t\tcanConnect\n\t);\n\n\tuseEffect(() => {\n\t\tif (!lastMessage) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Decode message data from various formats\n\t\tconst decoded = decodeMessageData(lastMessage.data);\n\t\tif (decoded.type === \"unsupported\") {\n\t\t\treturn;\n\t\t}\n\n\t\t// Parse the message and determine its type\n\t\tconst message = parseWebSocketMessage(decoded.data);\n\n\t\t// Handle different message types\n\t\tswitch (message.type) {\n\t\t\tcase \"pong\":\n\t\t\t\tlastHeartbeatRef.current = Date.now();\n\t\t\t\tbreak;\n\n\t\t\tcase \"connection-established\":\n\t\t\t\tsetConnectionId(message.connectionId);\n\t\t\t\tlastHeartbeatRef.current = Date.now();\n\t\t\t\tbreak;\n\n\t\t\tcase \"error\": {\n\t\t\t\tconst err = new Error(message.message);\n\t\t\t\tsetConnectionError(err);\n\t\t\t\tonError?.(err);\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase \"event\":\n\t\t\t\tlastHeartbeatRef.current = Date.now();\n\t\t\t\tsetLastEvent(message.event);\n\t\t\t\tfor (const handler of eventHandlersRef.current) {\n\t\t\t\t\tPromise.resolve(handler(message.event)).catch((error) => {\n\t\t\t\t\t\tconst err =\n\t\t\t\t\t\t\terror instanceof Error\n\t\t\t\t\t\t\t\t? error\n\t\t\t\t\t\t\t\t: new Error(`Subscriber threw an exception: ${String(error)}`);\n\t\t\t\t\t\tonError?.(err);\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\tbreak;\n\n\t\t\tdefault:\n\t\t\t\t// Silently ignore invalid or unknown messages\n\t\t\t\tbreak;\n\t\t}\n\t}, [lastMessage, onError]);\n\n\tuseEffect(() => {\n\t\tif (!canConnect) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst interval = window.setInterval(() => {\n\t\t\tif (readyState !== ReadyState.OPEN) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Check if heartbeat has timed out\n\t\t\tif (isHeartbeatTimedOut(lastHeartbeatRef.current, heartbeatTimeoutMs)) {\n\t\t\t\tconst socket = getWebSocket();\n\t\t\t\tsocket?.close(4000, \"Heartbeat timeout\");\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Send ping to keep connection alive\n\t\t\ttry {\n\t\t\t\tsendMessage(\"ping\");\n\t\t\t} catch {\n\t\t\t\t// Ignore send failures; reconnect logic will handle it\n\t\t\t}\n\t\t}, heartbeatIntervalMs);\n\n\t\treturn () => {\n\t\t\twindow.clearInterval(interval);\n\t\t};\n\t}, [\n\t\tcanConnect,\n\t\theartbeatIntervalMs,\n\t\theartbeatTimeoutMs,\n\t\treadyState,\n\t\tsendMessage,\n\t\tgetWebSocket,\n\t]);\n\n\tconst send = useCallback(\n\t\t(event: AnyRealtimeEvent) => {\n\t\t\tif (!connectionUrl) {\n\t\t\t\tthrow new Error(\"Realtime connection is disabled\");\n\t\t\t}\n\n\t\t\tif (readyState !== ReadyState.OPEN) {\n\t\t\t\tthrow new Error(\"Realtime connection is not established\");\n\t\t\t}\n\n\t\t\tsendJsonMessage(event);\n\t\t},\n\t\t[connectionUrl, readyState, sendJsonMessage]\n\t);\n\n\tconst sendRaw = useCallback(\n\t\t(data: string) => {\n\t\t\tif (!connectionUrl) {\n\t\t\t\tthrow new Error(\"Realtime connection is disabled\");\n\t\t\t}\n\n\t\t\tif (readyState !== ReadyState.OPEN) {\n\t\t\t\tthrow new Error(\"Realtime connection is not established\");\n\t\t\t}\n\n\t\t\tsendMessage(data);\n\t\t},\n\t\t[connectionUrl, readyState, sendMessage]\n\t);\n\n\tconst subscribe = useCallback((handler: SubscribeHandler) => {\n\t\teventHandlersRef.current.add(handler);\n\t\treturn () => {\n\t\t\teventHandlersRef.current.delete(handler);\n\t\t};\n\t}, []);\n\n\tconst reconnect = useCallback(() => {\n\t\tconst socket = getWebSocket();\n\t\tsocket?.close();\n\t}, [getWebSocket]);\n\n\tconst connection = useMemo<RealtimeConnectionState>(\n\t\t() => ({\n\t\t\tisConnected: readyState === ReadyState.OPEN,\n\t\t\tisConnecting: readyState === ReadyState.CONNECTING,\n\t\t\terror: connectionError,\n\t\t\tsend,\n\t\t\tsendRaw,\n\t\t\tsubscribe,\n\t\t\tlastEvent,\n\t\t\tconnectionId,\n\t\t\treconnect,\n\t\t}),\n\t\t[\n\t\t\treadyState,\n\t\t\tconnectionError,\n\t\t\tsend,\n\t\t\tsendRaw,\n\t\t\tsubscribe,\n\t\t\tlastEvent,\n\t\t\tconnectionId,\n\t\t\treconnect,\n\t\t]\n\t);\n\n\tconst value = useMemo<RealtimeContextValue>(\n\t\t() => ({\n\t\t\t...connection,\n\t\t\tvisitorId: normalizedAuth?.visitorId ?? null,\n\t\t\twebsiteId: normalizedAuth?.websiteId ?? null,\n\t\t\tuserId: normalizedAuth?.userId ?? null,\n\t\t}),\n\t\t[\n\t\t\tconnection,\n\t\t\tnormalizedAuth?.visitorId,\n\t\t\tnormalizedAuth?.websiteId,\n\t\t\tnormalizedAuth?.userId,\n\t\t]\n\t);\n\n\treturn (\n\t\t<RealtimeContext.Provider value={value}>\n\t\t\t{children}\n\t\t</RealtimeContext.Provider>\n\t);\n}\n\nexport function useRealtimeConnection(): RealtimeContextValue {\n\tconst context = useContext(RealtimeContext);\n\tif (!context) {\n\t\tthrow new Error(\n\t\t\t\"useRealtimeConnection must be used within RealtimeProvider\"\n\t\t);\n\t}\n\n\treturn context;\n}\n\nexport type { RealtimeContextValue };\nexport type { RealtimeAuthConfig };\nexport type { RealtimeProviderProps };\nexport type { RealtimeEvent } from \"@cossistant/types/realtime-events\";\n"],"mappings":"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"}
1
+ {"version":3,"file":"provider.js","names":["payload: unknown"],"sources":["../../src/realtime/provider.tsx"],"sourcesContent":["\"use client\";\n\nimport {\n\ttype AnyRealtimeEvent,\n\tisValidEventType,\n\ttype RealtimeEvent,\n\tvalidateRealtimeEvent,\n} from \"@cossistant/types/realtime-events\";\nimport type React from \"react\";\nimport {\n\tcreateContext,\n\tuseCallback,\n\tuseContext,\n\tuseEffect,\n\tuseMemo,\n\tuseRef,\n\tuseState,\n} from \"react\";\nimport useWebSocket, { ReadyState } from \"react-use-websocket\";\n\nconst DEFAULT_HEARTBEAT_INTERVAL_MS = 15_000;\nconst DEFAULT_HEARTBEAT_TIMEOUT_MS = 45_000;\n\ntype SubscribeHandler = (event: AnyRealtimeEvent) => void;\n\ntype MessageDecodeResult =\n\t| {\n\t\t\ttype: \"raw-text\";\n\t\t\tdata: string;\n\t }\n\t| {\n\t\t\ttype: \"unsupported\";\n\t };\n\ntype ParsedMessage =\n\t| {\n\t\t\ttype: \"pong\";\n\t }\n\t| {\n\t\t\ttype: \"connection-established\";\n\t\t\tconnectionId: string | null;\n\t }\n\t| {\n\t\t\ttype: \"error\";\n\t\t\tmessage: string;\n\t }\n\t| {\n\t\t\ttype: \"event\";\n\t\t\tevent: AnyRealtimeEvent;\n\t }\n\t| {\n\t\t\ttype: \"invalid\";\n\t };\n\ntype VisitorAuthConfig = {\n\tkind: \"visitor\";\n\tvisitorId: string | null;\n\twebsiteId?: string | null;\n\tpublicKey?: string | null;\n};\n\ntype SessionAuthConfig = {\n\tkind: \"session\";\n\tsessionToken: string | null;\n\twebsiteId?: string | null;\n\tuserId?: string | null;\n};\n\ntype RealtimeAuthConfig = VisitorAuthConfig | SessionAuthConfig;\n\ntype ResolvedAuthConfig = {\n\ttype: \"visitor\" | \"session\";\n\tvisitorId: string | null;\n\twebsiteId: string | null;\n\tuserId: string | null;\n\tsessionToken: string | null;\n\tpublicKey: string | null;\n};\n\ntype RealtimeProviderProps = {\n\tchildren: React.ReactNode;\n\twsUrl?: string;\n\tauth: RealtimeAuthConfig | null;\n\tautoConnect?: boolean;\n\tonConnect?: () => void;\n\tonDisconnect?: () => void;\n\tonError?: (error: Error) => void;\n};\n\ntype RealtimeConnectionState = {\n\tisConnected: boolean;\n\tisConnecting: boolean;\n\terror: Error | null;\n\tsend: (event: AnyRealtimeEvent) => void;\n\tsendRaw: (data: string) => void;\n\tsubscribe: (handler: SubscribeHandler) => () => void;\n\tlastEvent: AnyRealtimeEvent | null;\n\tconnectionId: string | null;\n\treconnect: () => void;\n};\n\ntype RealtimeContextValue = RealtimeConnectionState & {\n\tvisitorId: string | null;\n\twebsiteId: string | null;\n\tuserId: string | null;\n};\n\nconst DEFAULT_WS_URL = \"wss://api.cossistant.com/ws\";\n\nconst RealtimeContext = createContext<RealtimeContextValue | null>(null);\n\n/**\n * Decodes WebSocket message data into a string.\n * Handles string, ArrayBuffer, and ArrayBufferView formats.\n */\nfunction decodeMessageData(data: unknown): MessageDecodeResult {\n\tif (typeof data === \"string\") {\n\t\treturn { type: \"raw-text\", data };\n\t}\n\n\tif (data instanceof ArrayBuffer) {\n\t\ttry {\n\t\t\treturn { type: \"raw-text\", data: new TextDecoder().decode(data) };\n\t\t} catch {\n\t\t\treturn { type: \"unsupported\" };\n\t\t}\n\t}\n\n\tif (ArrayBuffer.isView(data)) {\n\t\ttry {\n\t\t\treturn { type: \"raw-text\", data: new TextDecoder().decode(data.buffer) };\n\t\t} catch {\n\t\t\treturn { type: \"unsupported\" };\n\t\t}\n\t}\n\n\treturn { type: \"unsupported\" };\n}\n\n/**\n * Safely parses JSON string, returning null if invalid.\n */\nfunction parseJson(raw: string): unknown {\n\ttry {\n\t\treturn JSON.parse(raw);\n\t} catch {\n\t\treturn null;\n\t}\n}\n\n/**\n * Extracts a string field from an unknown object, with optional validation.\n */\nfunction extractStringField(\n\tobj: unknown,\n\tfield: string,\n\trequired = false\n): string | null {\n\tif (!obj || typeof obj !== \"object\" || !(field in obj)) {\n\t\treturn required ? null : null;\n\t}\n\tconst value = (obj as Record<string, unknown>)[field];\n\tif (typeof value === \"string\" && value.length > 0) {\n\t\treturn value;\n\t}\n\treturn required ? null : null;\n}\n\n/**\n * Parses a WebSocket message and determines its type and content.\n */\nfunction parseWebSocketMessage(rawText: string): ParsedMessage {\n\t// Handle pong heartbeat\n\tif (rawText === \"pong\") {\n\t\treturn { type: \"pong\" };\n\t}\n\n\t// Try to parse as JSON\n\tconst parsed = parseJson(rawText);\n\tif (!parsed || typeof parsed !== \"object\") {\n\t\treturn { type: \"invalid\" };\n\t}\n\n\tconst messageType = extractStringField(parsed, \"type\");\n\n\t// Handle CONNECTION_ESTABLISHED\n\tif (messageType === \"CONNECTION_ESTABLISHED\") {\n\t\tconst payload = (parsed as { payload?: unknown }).payload;\n\t\tconst connectionId = extractStringField(payload, \"connectionId\");\n\t\treturn { type: \"connection-established\", connectionId };\n\t}\n\n\t// Handle error messages\n\tif (\"error\" in parsed && \"message\" in parsed) {\n\t\tconst message =\n\t\t\textractStringField(parsed, \"message\") || \"Realtime connection error\";\n\t\treturn { type: \"error\", message };\n\t}\n\n\t// Handle realtime events\n\tif (messageType && isValidEventType(messageType)) {\n\t\ttry {\n\t\t\tconst event = constructRealtimeEvent(parsed);\n\t\t\tif (!event) {\n\t\t\t\treturn { type: \"invalid\" };\n\t\t\t}\n\t\t\treturn { type: \"event\", event };\n\t\t} catch (error) {\n\t\t\tconsole.error(\"[Realtime] Failed to construct event\", error);\n\t\t\treturn { type: \"invalid\" };\n\t\t}\n\t}\n\n\treturn { type: \"invalid\" };\n}\n\n/**\n * Constructs a RealtimeEvent from parsed JSON data.\n * Returns null if required fields are missing or validation fails.\n */\nfunction constructRealtimeEvent(parsed: unknown): AnyRealtimeEvent | null {\n\tif (!parsed || typeof parsed !== \"object\" || !(\"type\" in parsed)) {\n\t\treturn null;\n\t}\n\n\tconst type = (parsed as { type: unknown }).type;\n\tif (!isValidEventType(type)) {\n\t\treturn null;\n\t}\n\n\tconst eventType = type;\n\n\t// Extract payload directly\n\tconst payloadSource = (parsed as { payload?: unknown }).payload;\n\n\tlet payload: unknown;\n\ttry {\n\t\tpayload = validateRealtimeEvent(eventType, payloadSource);\n\t} catch (error) {\n\t\tconsole.error(\"[Realtime] Received invalid event payload\", error);\n\t\treturn null;\n\t}\n\n\tconst organizationId = extractStringField(\n\t\tpayloadSource,\n\t\t\"organizationId\",\n\t\ttrue\n\t);\n\tconst websiteId = extractStringField(payloadSource, \"websiteId\", true);\n\n\tif (!organizationId) {\n\t\tconsole.error(\"[Realtime] Received event without organizationId\", parsed);\n\t\treturn null;\n\t}\n\n\tif (!websiteId) {\n\t\tconsole.error(\"[Realtime] Received event without websiteId\", parsed);\n\t\treturn null;\n\t}\n\n\tconst visitorId = extractStringField(parsed, \"visitorId\");\n\n\treturn {\n\t\ttype: eventType,\n\t\tpayload,\n\t\torganizationId,\n\t\twebsiteId,\n\t\tvisitorId,\n\t} as AnyRealtimeEvent;\n}\n\n/**\n * Checks if heartbeat has timed out.\n */\nfunction isHeartbeatTimedOut(\n\tlastHeartbeat: number,\n\ttimeoutMs: number\n): boolean {\n\tconst elapsed = Date.now() - lastHeartbeat;\n\treturn elapsed > timeoutMs;\n}\n\nfunction resolvePublicKey(explicit?: string | null): string | null {\n\tconst trimmed = explicit?.trim();\n\tif (trimmed) {\n\t\treturn trimmed;\n\t}\n\n\tconst fromEnv =\n\t\tprocess.env.NEXT_PUBLIC_COSSISTANT_KEY ||\n\t\tprocess.env.COSSISTANT_PUBLIC_KEY ||\n\t\tnull;\n\n\tconst normalized = fromEnv?.trim();\n\treturn normalized && normalized.length > 0 ? normalized : null;\n}\n\nfunction normalizeAuth(\n\tauth: RealtimeAuthConfig | null\n): ResolvedAuthConfig | null {\n\tif (!auth) {\n\t\treturn null;\n\t}\n\n\tif (auth.kind === \"visitor\") {\n\t\tconst visitorId = auth.visitorId?.trim() || null;\n\n\t\tif (!visitorId) {\n\t\t\treturn null;\n\t\t}\n\n\t\treturn {\n\t\t\ttype: \"visitor\",\n\t\t\tvisitorId,\n\t\t\twebsiteId: auth.websiteId?.trim() || null,\n\t\t\tuserId: null,\n\t\t\tsessionToken: null,\n\t\t\tpublicKey: resolvePublicKey(auth.publicKey ?? null),\n\t\t} satisfies ResolvedAuthConfig;\n\t}\n\n\tconst sessionToken = auth.sessionToken?.trim() || null;\n\n\tif (!sessionToken) {\n\t\treturn null;\n\t}\n\n\treturn {\n\t\ttype: \"session\",\n\t\tvisitorId: null,\n\t\twebsiteId: auth.websiteId?.trim() || null,\n\t\tuserId: auth.userId?.trim() || null,\n\t\tsessionToken,\n\t\tpublicKey: null,\n\t} satisfies ResolvedAuthConfig;\n}\n\nfunction buildSocketUrl(\n\tbaseUrl: string,\n\tauth: ResolvedAuthConfig | null\n): string | null {\n\tif (!auth) {\n\t\treturn null;\n\t}\n\n\ttry {\n\t\tconst url = new URL(baseUrl);\n\n\t\tif (auth.type === \"visitor\") {\n\t\t\turl.searchParams.set(\"visitorId\", auth.visitorId ?? \"\");\n\t\t\tconst publicKey = auth.publicKey;\n\t\t\tif (publicKey) {\n\t\t\t\turl.searchParams.set(\"publicKey\", publicKey);\n\t\t\t}\n\t\t} else {\n\t\t\turl.searchParams.set(\"sessionToken\", auth.sessionToken ?? \"\");\n\t\t\tif (auth.websiteId) {\n\t\t\t\turl.searchParams.set(\"websiteId\", auth.websiteId);\n\t\t\t}\n\t\t}\n\n\t\treturn url.toString();\n\t} catch (error) {\n\t\tconsole.error(\"[Realtime] Failed to build WebSocket URL\", error);\n\t\treturn null;\n\t}\n}\n\n/**\n * Provides websocket connectivity and heartbeating logic for realtime events.\n */\nexport function RealtimeProvider({\n\tchildren,\n\twsUrl = DEFAULT_WS_URL,\n\tauth,\n\tautoConnect = true,\n\tonConnect,\n\tonDisconnect,\n\tonError,\n}: RealtimeProviderProps): React.ReactElement {\n\tconst normalizedAuth = normalizeAuth(auth);\n\n\tconst socketUrl = buildSocketUrl(wsUrl, normalizedAuth);\n\tconst eventHandlersRef = useRef<Set<SubscribeHandler>>(new Set());\n\tconst lastHeartbeatRef = useRef<number>(Date.now());\n\tconst hasOpenedRef = useRef(false);\n\tconst previousUrlRef = useRef<string | null>(null);\n\tconst [connectionError, setConnectionError] = useState<Error | null>(null);\n\tconst [lastEvent, setLastEvent] = useState<AnyRealtimeEvent | null>(null);\n\tconst [connectionId, setConnectionId] = useState<string | null>(null);\n\n\tconst heartbeatIntervalMs = DEFAULT_HEARTBEAT_INTERVAL_MS;\n\tconst heartbeatTimeoutMs = DEFAULT_HEARTBEAT_TIMEOUT_MS;\n\n\tconst canConnect = Boolean(autoConnect && socketUrl);\n\tconst connectionUrl = canConnect ? socketUrl : null;\n\n\t// Track URL changes to detect when connection is being replaced\n\tuseEffect(() => {\n\t\tif (connectionUrl !== previousUrlRef.current) {\n\t\t\tpreviousUrlRef.current = connectionUrl;\n\t\t\t// Reset hasOpenedRef when URL changes so we know a new connection is starting\n\t\t\thasOpenedRef.current = false;\n\t\t}\n\t}, [connectionUrl]);\n\n\tconst {\n\t\tsendMessage,\n\t\tsendJsonMessage,\n\t\tlastMessage,\n\t\treadyState,\n\t\tgetWebSocket,\n\t} = useWebSocket(\n\t\tconnectionUrl,\n\t\t{\n\t\t\tshouldReconnect: (closeEvent) => {\n\t\t\t\tif (!canConnect) {\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\n\t\t\t\tif (closeEvent.code === 1008 || closeEvent.code === 1011) {\n\t\t\t\t\tconst err = new Error(\n\t\t\t\t\t\tcloseEvent.reason ||\n\t\t\t\t\t\t\t\"Realtime connection closed by server. Please check your credentials.\"\n\t\t\t\t\t);\n\t\t\t\t\tsetConnectionError(err);\n\t\t\t\t\tonError?.(err);\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\n\t\t\t\treturn true;\n\t\t\t},\n\t\t\treconnectAttempts: autoConnect ? undefined : 0,\n\t\t\treconnectInterval: (attempt) => {\n\t\t\t\tconst base = 500 * 2 ** attempt;\n\t\t\t\treturn Math.min(base, 30_000);\n\t\t\t},\n\t\t\tretryOnError: false,\n\t\t\tonOpen: () => {\n\t\t\t\thasOpenedRef.current = true;\n\t\t\t\tsetConnectionError(null);\n\t\t\t\tlastHeartbeatRef.current = Date.now();\n\t\t\t\tonConnect?.();\n\t\t\t},\n\t\t\tonClose: () => {\n\t\t\t\tsetConnectionId(null);\n\t\t\t\tonDisconnect?.();\n\t\t\t},\n\t\t\tonError: (event) => {\n\t\t\t\tif (!canConnect) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tconst socketLike = event.target;\n\t\t\t\tconst currentSocket = getWebSocket();\n\t\t\t\tconst isBrowserSocket =\n\t\t\t\t\ttypeof WebSocket !== \"undefined\" && socketLike instanceof WebSocket;\n\t\t\t\tconst socketState = isBrowserSocket ? socketLike.readyState : undefined;\n\n\t\t\t\t// Only suppress errors for THIS provider's socket, not other nested providers\n\t\t\t\t// Check if the errored socket belongs to this provider instance\n\t\t\t\tconst isThisProvidersSocket = currentSocket === socketLike;\n\n\t\t\t\t// Suppress errors if:\n\t\t\t\t// 1. This socket was replaced (URL changed while connecting) - only for this provider\n\t\t\t\t// 2. Connection URL is null (component unmounting or disabled)\n\t\t\t\t// 3. Socket is in CLOSING/CLOSED state and hasn't opened (cleanup/unmount) - only for this provider\n\t\t\t\tif (\n\t\t\t\t\t(!isThisProvidersSocket && currentSocket) ||\n\t\t\t\t\t!connectionUrl ||\n\t\t\t\t\t(isThisProvidersSocket &&\n\t\t\t\t\t\t!hasOpenedRef.current &&\n\t\t\t\t\t\t(socketState === WebSocket.CLOSING ||\n\t\t\t\t\t\t\tsocketState === WebSocket.CLOSED))\n\t\t\t\t) {\n\t\t\t\t\t// Suppress these expected errors during connection replacement or cleanup\n\t\t\t\t\t// But only if it's THIS provider's socket being replaced\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\t// For errors that occur during CONNECTING state, check if URL changed\n\t\t\t\t// Only suppress if it's this provider's socket\n\t\t\t\tif (\n\t\t\t\t\tisThisProvidersSocket &&\n\t\t\t\t\t!hasOpenedRef.current &&\n\t\t\t\t\tsocketState === WebSocket.CONNECTING &&\n\t\t\t\t\tconnectionUrl !== previousUrlRef.current\n\t\t\t\t) {\n\t\t\t\t\t// URL changed while connecting, suppress error\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tconst err = new Error(`WebSocket error: ${event.type}`);\n\t\t\t\tsetConnectionError(err);\n\t\t\t\tonError?.(err);\n\t\t\t},\n\t\t},\n\t\tcanConnect\n\t);\n\n\tuseEffect(() => {\n\t\tif (!lastMessage) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Decode message data from various formats\n\t\tconst decoded = decodeMessageData(lastMessage.data);\n\t\tif (decoded.type === \"unsupported\") {\n\t\t\treturn;\n\t\t}\n\n\t\t// Parse the message and determine its type\n\t\tconst message = parseWebSocketMessage(decoded.data);\n\n\t\t// Handle different message types\n\t\tswitch (message.type) {\n\t\t\tcase \"pong\":\n\t\t\t\tlastHeartbeatRef.current = Date.now();\n\t\t\t\tbreak;\n\n\t\t\tcase \"connection-established\":\n\t\t\t\tsetConnectionId(message.connectionId);\n\t\t\t\tlastHeartbeatRef.current = Date.now();\n\t\t\t\tbreak;\n\n\t\t\tcase \"error\": {\n\t\t\t\tconst err = new Error(message.message);\n\t\t\t\tsetConnectionError(err);\n\t\t\t\tonError?.(err);\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase \"event\":\n\t\t\t\tlastHeartbeatRef.current = Date.now();\n\t\t\t\tsetLastEvent(message.event);\n\t\t\t\tfor (const handler of eventHandlersRef.current) {\n\t\t\t\t\tPromise.resolve(handler(message.event)).catch((error) => {\n\t\t\t\t\t\tconst err =\n\t\t\t\t\t\t\terror instanceof Error\n\t\t\t\t\t\t\t\t? error\n\t\t\t\t\t\t\t\t: new Error(`Subscriber threw an exception: ${String(error)}`);\n\t\t\t\t\t\tonError?.(err);\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\tbreak;\n\n\t\t\tdefault:\n\t\t\t\t// Silently ignore invalid or unknown messages\n\t\t\t\tbreak;\n\t\t}\n\t}, [lastMessage, onError]);\n\n\tuseEffect(() => {\n\t\tif (!canConnect) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst interval = window.setInterval(() => {\n\t\t\tif (readyState !== ReadyState.OPEN) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Check if heartbeat has timed out\n\t\t\tif (isHeartbeatTimedOut(lastHeartbeatRef.current, heartbeatTimeoutMs)) {\n\t\t\t\tconst socket = getWebSocket();\n\t\t\t\tsocket?.close(4000, \"Heartbeat timeout\");\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Send ping to keep connection alive\n\t\t\ttry {\n\t\t\t\tsendMessage(\"ping\");\n\t\t\t} catch {\n\t\t\t\t// Ignore send failures; reconnect logic will handle it\n\t\t\t}\n\t\t}, heartbeatIntervalMs);\n\n\t\treturn () => {\n\t\t\twindow.clearInterval(interval);\n\t\t};\n\t}, [\n\t\tcanConnect,\n\t\theartbeatIntervalMs,\n\t\theartbeatTimeoutMs,\n\t\treadyState,\n\t\tsendMessage,\n\t\tgetWebSocket,\n\t]);\n\n\tconst send = useCallback(\n\t\t(event: AnyRealtimeEvent) => {\n\t\t\tif (!connectionUrl) {\n\t\t\t\tthrow new Error(\"Realtime connection is disabled\");\n\t\t\t}\n\n\t\t\tif (readyState !== ReadyState.OPEN) {\n\t\t\t\tthrow new Error(\"Realtime connection is not established\");\n\t\t\t}\n\n\t\t\tsendJsonMessage(event);\n\t\t},\n\t\t[connectionUrl, readyState, sendJsonMessage]\n\t);\n\n\tconst sendRaw = useCallback(\n\t\t(data: string) => {\n\t\t\tif (!connectionUrl) {\n\t\t\t\tthrow new Error(\"Realtime connection is disabled\");\n\t\t\t}\n\n\t\t\tif (readyState !== ReadyState.OPEN) {\n\t\t\t\tthrow new Error(\"Realtime connection is not established\");\n\t\t\t}\n\n\t\t\tsendMessage(data);\n\t\t},\n\t\t[connectionUrl, readyState, sendMessage]\n\t);\n\n\tconst subscribe = useCallback((handler: SubscribeHandler) => {\n\t\teventHandlersRef.current.add(handler);\n\t\treturn () => {\n\t\t\teventHandlersRef.current.delete(handler);\n\t\t};\n\t}, []);\n\n\tconst reconnect = useCallback(() => {\n\t\tconst socket = getWebSocket();\n\t\tsocket?.close();\n\t}, [getWebSocket]);\n\n\tconst connection = useMemo<RealtimeConnectionState>(\n\t\t() => ({\n\t\t\tisConnected: readyState === ReadyState.OPEN,\n\t\t\tisConnecting: readyState === ReadyState.CONNECTING,\n\t\t\terror: connectionError,\n\t\t\tsend,\n\t\t\tsendRaw,\n\t\t\tsubscribe,\n\t\t\tlastEvent,\n\t\t\tconnectionId,\n\t\t\treconnect,\n\t\t}),\n\t\t[\n\t\t\treadyState,\n\t\t\tconnectionError,\n\t\t\tsend,\n\t\t\tsendRaw,\n\t\t\tsubscribe,\n\t\t\tlastEvent,\n\t\t\tconnectionId,\n\t\t\treconnect,\n\t\t]\n\t);\n\n\tconst value = useMemo<RealtimeContextValue>(\n\t\t() => ({\n\t\t\t...connection,\n\t\t\tvisitorId: normalizedAuth?.visitorId ?? null,\n\t\t\twebsiteId: normalizedAuth?.websiteId ?? null,\n\t\t\tuserId: normalizedAuth?.userId ?? null,\n\t\t}),\n\t\t[\n\t\t\tconnection,\n\t\t\tnormalizedAuth?.visitorId,\n\t\t\tnormalizedAuth?.websiteId,\n\t\t\tnormalizedAuth?.userId,\n\t\t]\n\t);\n\n\treturn (\n\t\t<RealtimeContext.Provider value={value}>\n\t\t\t{children}\n\t\t</RealtimeContext.Provider>\n\t);\n}\n\n/**\n * Returns the realtime connection context.\n */\nexport function useRealtimeConnection(): RealtimeContextValue {\n\tconst context = useContext(RealtimeContext);\n\tif (!context) {\n\t\tthrow new Error(\n\t\t\t\"useRealtimeConnection must be used within RealtimeProvider\"\n\t\t);\n\t}\n\n\treturn context;\n}\n\nexport type { RealtimeContextValue };\nexport type { RealtimeAuthConfig };\nexport type { RealtimeProviderProps };\nexport type { RealtimeEvent } from \"@cossistant/types/realtime-events\";\n"],"mappings":"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"}