@cossistant/react 0.0.3 → 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 (320) hide show
  1. package/README.md +74 -0
  2. package/_virtual/rolldown_runtime.js +19 -0
  3. package/conversation.d.ts +26 -3
  4. package/conversation.d.ts.map +1 -1
  5. package/hooks/index.d.ts +5 -3
  6. package/hooks/index.js +7 -5
  7. package/hooks/private/store/use-conversations-store.d.ts +8 -0
  8. package/hooks/private/store/use-conversations-store.d.ts.map +1 -1
  9. package/hooks/private/store/use-conversations-store.js +8 -0
  10. package/hooks/private/store/use-conversations-store.js.map +1 -1
  11. package/hooks/private/store/use-store-selector.d.ts +4 -0
  12. package/hooks/private/store/use-store-selector.d.ts.map +1 -1
  13. package/hooks/private/store/use-store-selector.js +5 -2
  14. package/hooks/private/store/use-store-selector.js.map +1 -1
  15. package/hooks/private/store/use-website-store.d.ts +4 -0
  16. package/hooks/private/store/use-website-store.d.ts.map +1 -1
  17. package/hooks/private/store/use-website-store.js +6 -3
  18. package/hooks/private/store/use-website-store.js.map +1 -1
  19. package/hooks/private/typing.d.ts +35 -0
  20. package/hooks/private/typing.d.ts.map +1 -0
  21. package/hooks/private/typing.js +49 -0
  22. package/hooks/private/typing.js.map +1 -0
  23. package/hooks/private/use-client-query.d.ts +5 -0
  24. package/hooks/private/use-client-query.d.ts.map +1 -1
  25. package/hooks/private/use-client-query.js +5 -0
  26. package/hooks/private/use-client-query.js.map +1 -1
  27. package/hooks/private/use-grouped-messages.d.ts +10 -4
  28. package/hooks/private/use-grouped-messages.d.ts.map +1 -1
  29. package/hooks/private/use-grouped-messages.js +24 -4
  30. package/hooks/private/use-grouped-messages.js.map +1 -1
  31. package/hooks/private/use-multimodal-input.d.ts.map +1 -1
  32. package/hooks/private/use-rest-client.d.ts.map +1 -1
  33. package/hooks/private/use-visitor-typing-reporter.d.ts +6 -0
  34. package/hooks/private/use-visitor-typing-reporter.d.ts.map +1 -1
  35. package/hooks/private/use-visitor-typing-reporter.js +6 -0
  36. package/hooks/private/use-visitor-typing-reporter.js.map +1 -1
  37. package/hooks/use-composer-refocus.d.ts.map +1 -1
  38. package/hooks/use-conversation-auto-seen.d.ts +9 -0
  39. package/hooks/use-conversation-auto-seen.d.ts.map +1 -1
  40. package/hooks/use-conversation-auto-seen.js +44 -3
  41. package/hooks/use-conversation-auto-seen.js.map +1 -1
  42. package/hooks/use-conversation-history-page.d.ts.map +1 -1
  43. package/hooks/use-conversation-history-page.js +16 -18
  44. package/hooks/use-conversation-history-page.js.map +1 -1
  45. package/hooks/use-conversation-lifecycle.d.ts.map +1 -1
  46. package/hooks/use-conversation-lifecycle.js +2 -4
  47. package/hooks/use-conversation-lifecycle.js.map +1 -1
  48. package/hooks/use-conversation-page.d.ts +6 -0
  49. package/hooks/use-conversation-page.d.ts.map +1 -1
  50. package/hooks/use-conversation-page.js +41 -3
  51. package/hooks/use-conversation-page.js.map +1 -1
  52. package/hooks/use-conversation-preview.d.ts +61 -0
  53. package/hooks/use-conversation-preview.d.ts.map +1 -0
  54. package/hooks/use-conversation-preview.js +173 -0
  55. package/hooks/use-conversation-preview.js.map +1 -0
  56. package/hooks/use-conversation-seen.d.ts +4 -0
  57. package/hooks/use-conversation-seen.d.ts.map +1 -1
  58. package/hooks/use-conversation-seen.js +4 -0
  59. package/hooks/use-conversation-seen.js.map +1 -1
  60. package/hooks/use-conversation-timeline-items.d.ts +4 -0
  61. package/hooks/use-conversation-timeline-items.d.ts.map +1 -1
  62. package/hooks/use-conversation-timeline-items.js +4 -0
  63. package/hooks/use-conversation-timeline-items.js.map +1 -1
  64. package/hooks/use-conversation-timeline.d.ts +32 -0
  65. package/hooks/use-conversation-timeline.d.ts.map +1 -0
  66. package/hooks/use-conversation-timeline.js +41 -0
  67. package/hooks/use-conversation-timeline.js.map +1 -0
  68. package/hooks/use-conversation-typing.d.ts +4 -0
  69. package/hooks/use-conversation-typing.d.ts.map +1 -1
  70. package/hooks/use-conversation-typing.js +4 -0
  71. package/hooks/use-conversation-typing.js.map +1 -1
  72. package/hooks/use-conversation.d.ts +11 -0
  73. package/hooks/use-conversation.d.ts.map +1 -1
  74. package/hooks/use-conversation.js +11 -0
  75. package/hooks/use-conversation.js.map +1 -1
  76. package/hooks/use-conversations.d.ts +12 -0
  77. package/hooks/use-conversations.d.ts.map +1 -1
  78. package/hooks/use-conversations.js +12 -0
  79. package/hooks/use-conversations.js.map +1 -1
  80. package/hooks/use-create-conversation.d.ts +5 -0
  81. package/hooks/use-create-conversation.d.ts.map +1 -1
  82. package/hooks/use-create-conversation.js +12 -9
  83. package/hooks/use-create-conversation.js.map +1 -1
  84. package/hooks/use-home-page.d.ts.map +1 -1
  85. package/hooks/use-home-page.js +6 -4
  86. package/hooks/use-home-page.js.map +1 -1
  87. package/hooks/use-message-composer.d.ts.map +1 -1
  88. package/hooks/use-realtime-support.d.ts.map +1 -1
  89. package/hooks/use-send-message.d.ts +9 -0
  90. package/hooks/use-send-message.d.ts.map +1 -1
  91. package/hooks/use-send-message.js +15 -13
  92. package/hooks/use-send-message.js.map +1 -1
  93. package/hooks/use-visitor.d.ts.map +1 -1
  94. package/hooks/use-visitor.js +28 -30
  95. package/hooks/use-visitor.js.map +1 -1
  96. package/hooks/use-window-visibility-focus.d.ts +4 -0
  97. package/hooks/use-window-visibility-focus.d.ts.map +1 -1
  98. package/hooks/use-window-visibility-focus.js +5 -2
  99. package/hooks/use-window-visibility-focus.js.map +1 -1
  100. package/identify-visitor.d.ts +12 -3
  101. package/identify-visitor.d.ts.map +1 -1
  102. package/identify-visitor.js +58 -9
  103. package/identify-visitor.js.map +1 -1
  104. package/index.d.ts +10 -7
  105. package/index.js +10 -9
  106. package/package.json +14 -17
  107. package/primitives/avatar/avatar.d.ts.map +1 -1
  108. package/primitives/avatar/fallback.d.ts.map +1 -1
  109. package/primitives/avatar/fallback.js +1 -3
  110. package/primitives/avatar/fallback.js.map +1 -1
  111. package/primitives/avatar/image.d.ts.map +1 -1
  112. package/primitives/avatar/index.d.ts +1 -0
  113. package/primitives/bubble.d.ts +2 -0
  114. package/primitives/bubble.d.ts.map +1 -1
  115. package/primitives/bubble.js +8 -2
  116. package/primitives/bubble.js.map +1 -1
  117. package/primitives/button.d.ts.map +1 -1
  118. package/primitives/conversation-timeline.d.ts.map +1 -1
  119. package/primitives/conversation-timeline.js +58 -5
  120. package/primitives/conversation-timeline.js.map +1 -1
  121. package/primitives/index.d.ts +1 -0
  122. package/primitives/index.parts.d.ts +1 -0
  123. package/primitives/multimodal-input.d.ts.map +1 -1
  124. package/primitives/timeline-item-group.d.ts +7 -7
  125. package/primitives/timeline-item-group.d.ts.map +1 -1
  126. package/primitives/timeline-item-group.js.map +1 -1
  127. package/primitives/timeline-item.d.ts +1 -1
  128. package/primitives/timeline-item.d.ts.map +1 -1
  129. package/primitives/timeline-item.js +7 -1
  130. package/primitives/timeline-item.js.map +1 -1
  131. package/primitives/window.d.ts +1 -1
  132. package/primitives/window.d.ts.map +1 -1
  133. package/primitives/window.js +4 -4
  134. package/primitives/window.js.map +1 -1
  135. package/provider.d.ts +23 -43
  136. package/provider.d.ts.map +1 -1
  137. package/provider.js +152 -49
  138. package/provider.js.map +1 -1
  139. package/realtime/event-filter.d.ts +4 -0
  140. package/realtime/event-filter.d.ts.map +1 -1
  141. package/realtime/event-filter.js +4 -0
  142. package/realtime/event-filter.js.map +1 -1
  143. package/realtime/index.js +1 -1
  144. package/realtime/provider.d.ts +7 -2
  145. package/realtime/provider.d.ts.map +1 -1
  146. package/realtime/provider.js +23 -1
  147. package/realtime/provider.js.map +1 -1
  148. package/realtime/seen-store.d.ts +13 -0
  149. package/realtime/seen-store.d.ts.map +1 -1
  150. package/realtime/seen-store.js +14 -2
  151. package/realtime/seen-store.js.map +1 -1
  152. package/realtime/support-provider.d.ts +1 -2
  153. package/realtime/support-provider.d.ts.map +1 -1
  154. package/realtime/support-provider.js +19 -20
  155. package/realtime/support-provider.js.map +1 -1
  156. package/realtime/typing-store.d.ts +18 -0
  157. package/realtime/typing-store.d.ts.map +1 -1
  158. package/realtime/typing-store.js +19 -2
  159. package/realtime/typing-store.js.map +1 -1
  160. package/realtime/use-realtime.d.ts +8 -4
  161. package/realtime/use-realtime.d.ts.map +1 -1
  162. package/realtime/use-realtime.js +4 -0
  163. package/realtime/use-realtime.js.map +1 -1
  164. package/realtime-events.d.ts +17 -3
  165. package/realtime-events.d.ts.map +1 -1
  166. package/schemas.d.ts +7 -1
  167. package/schemas.d.ts.map +1 -1
  168. package/support/components/avatar-stack.d.ts +8 -4
  169. package/support/components/avatar-stack.d.ts.map +1 -1
  170. package/support/components/avatar-stack.js +4 -0
  171. package/support/components/avatar-stack.js.map +1 -1
  172. package/support/components/avatar.d.ts +11 -6
  173. package/support/components/avatar.d.ts.map +1 -1
  174. package/support/components/avatar.js +4 -0
  175. package/support/components/avatar.js.map +1 -1
  176. package/support/components/bubble.d.ts.map +1 -1
  177. package/support/components/bubble.js +29 -6
  178. package/support/components/bubble.js.map +1 -1
  179. package/support/components/button.d.ts +8 -5
  180. package/support/components/button.d.ts.map +1 -1
  181. package/support/components/button.js +5 -1
  182. package/support/components/button.js.map +1 -1
  183. package/support/components/container.d.ts +0 -1
  184. package/support/components/container.d.ts.map +1 -1
  185. package/support/components/container.js +2 -8
  186. package/support/components/container.js.map +1 -1
  187. package/support/components/conversation-button-link.d.ts +8 -21
  188. package/support/components/conversation-button-link.d.ts.map +1 -1
  189. package/support/components/conversation-button-link.js +62 -178
  190. package/support/components/conversation-button-link.js.map +1 -1
  191. package/support/components/conversation-event.d.ts.map +1 -1
  192. package/support/components/conversation-event.js +4 -0
  193. package/support/components/conversation-event.js.map +1 -1
  194. package/support/components/conversation-timeline.d.ts +10 -1
  195. package/support/components/conversation-timeline.d.ts.map +1 -1
  196. package/support/components/conversation-timeline.js +63 -57
  197. package/support/components/conversation-timeline.js.map +1 -1
  198. package/support/components/cossistant-branding.d.ts +5 -2
  199. package/support/components/cossistant-branding.d.ts.map +1 -1
  200. package/support/components/cossistant-branding.js +3 -0
  201. package/support/components/cossistant-branding.js.map +1 -1
  202. package/support/components/header.d.ts.map +1 -1
  203. package/support/components/header.js +2 -2
  204. package/support/components/header.js.map +1 -1
  205. package/support/components/icons.d.ts.map +1 -1
  206. package/support/components/multimodal-input.d.ts.map +1 -1
  207. package/support/components/multimodal-input.js +5 -24
  208. package/support/components/multimodal-input.js.map +1 -1
  209. package/support/components/navigation-tab.d.ts +7 -2
  210. package/support/components/navigation-tab.d.ts.map +1 -1
  211. package/support/components/navigation-tab.js +4 -0
  212. package/support/components/navigation-tab.js.map +1 -1
  213. package/support/components/support-content.d.ts +1 -1
  214. package/support/components/support-content.d.ts.map +1 -1
  215. package/support/components/support-content.js +7 -10
  216. package/support/components/support-content.js.map +1 -1
  217. package/support/components/text-effect.d.ts +5 -2
  218. package/support/components/text-effect.d.ts.map +1 -1
  219. package/support/components/text-effect.js +4 -0
  220. package/support/components/text-effect.js.map +1 -1
  221. package/support/components/timeline-identification-tool.d.ts +7 -0
  222. package/support/components/timeline-identification-tool.d.ts.map +1 -0
  223. package/support/components/timeline-identification-tool.js +139 -0
  224. package/support/components/timeline-identification-tool.js.map +1 -0
  225. package/support/components/timeline-message-group.d.ts +2 -1
  226. package/support/components/timeline-message-group.d.ts.map +1 -1
  227. package/support/components/timeline-message-group.js +4 -19
  228. package/support/components/timeline-message-group.js.map +1 -1
  229. package/support/components/timeline-message-item.d.ts +6 -2
  230. package/support/components/timeline-message-item.d.ts.map +1 -1
  231. package/support/components/timeline-message-item.js +8 -4
  232. package/support/components/timeline-message-item.js.map +1 -1
  233. package/support/components/typing-indicator.d.ts +5 -2
  234. package/support/components/typing-indicator.d.ts.map +1 -1
  235. package/support/components/typing-indicator.js +4 -4
  236. package/support/components/typing-indicator.js.map +1 -1
  237. package/support/components/watermark.d.ts.map +1 -1
  238. package/support/context/websocket.d.ts +8 -0
  239. package/support/context/websocket.d.ts.map +1 -1
  240. package/support/context/websocket.js +12 -6
  241. package/support/context/websocket.js.map +1 -1
  242. package/support/index.d.ts +8 -8
  243. package/support/index.d.ts.map +1 -1
  244. package/support/index.js +18 -18
  245. package/support/index.js.map +1 -1
  246. package/support/pages/conversation-history.js +46 -54
  247. package/support/pages/conversation-history.js.map +1 -1
  248. package/support/pages/conversation.d.ts +3 -6
  249. package/support/pages/conversation.d.ts.map +1 -1
  250. package/support/pages/conversation.js +19 -9
  251. package/support/pages/conversation.js.map +1 -1
  252. package/support/pages/home.d.ts +2 -2
  253. package/support/pages/home.d.ts.map +1 -1
  254. package/support/pages/home.js +64 -77
  255. package/support/pages/home.js.map +1 -1
  256. package/support/store/support-store.d.ts +18 -2
  257. package/support/store/support-store.d.ts.map +1 -1
  258. package/support/store/support-store.js +20 -5
  259. package/support/store/support-store.js.map +1 -1
  260. package/support/{support-CMoDLQoC.css → support-Ck4jy29i.css} +1 -2
  261. package/support/support-Ck4jy29i.css.map +1 -0
  262. package/support/text/index.d.ts +15 -2
  263. package/support/text/index.d.ts.map +1 -1
  264. package/support/text/index.js +15 -2
  265. package/support/text/index.js.map +1 -1
  266. package/support/text/locales/en.js +22 -4
  267. package/support/text/locales/en.js.map +1 -1
  268. package/support/text/locales/es.js +18 -0
  269. package/support/text/locales/es.js.map +1 -1
  270. package/support/text/locales/fr.js +18 -0
  271. package/support/text/locales/fr.js.map +1 -1
  272. package/support/text/locales/keys.d.ts +69 -9
  273. package/support/text/locales/keys.d.ts.map +1 -1
  274. package/support/text/locales/keys.js +18 -0
  275. package/support/text/locales/keys.js.map +1 -1
  276. package/support/text/runtime.d.ts +21 -0
  277. package/support/text/runtime.d.ts.map +1 -1
  278. package/support/text/runtime.js +21 -0
  279. package/support/text/runtime.js.map +1 -1
  280. package/support/utils/index.d.ts +4 -0
  281. package/support/utils/index.d.ts.map +1 -1
  282. package/support/utils/index.js +4 -1
  283. package/support/utils/index.js.map +1 -1
  284. package/support/utils/time.d.ts +3 -0
  285. package/support/utils/time.d.ts.map +1 -1
  286. package/support/utils/time.js +3 -0
  287. package/support/utils/time.js.map +1 -1
  288. package/support-config.d.ts +2 -1
  289. package/support-config.d.ts.map +1 -1
  290. package/support-config.js.map +1 -1
  291. package/support.css +2 -2
  292. package/timeline-item.d.ts +10 -0
  293. package/timeline-item.d.ts.map +1 -1
  294. package/utils/conversation.d.ts +7 -0
  295. package/utils/conversation.d.ts.map +1 -0
  296. package/utils/conversation.js +18 -0
  297. package/utils/conversation.js.map +1 -0
  298. package/utils/id.d.ts +3 -0
  299. package/utils/id.d.ts.map +1 -1
  300. package/utils/id.js +3 -0
  301. package/utils/id.js.map +1 -1
  302. package/utils/index.d.ts +2 -1
  303. package/utils/index.js +2 -1
  304. package/utils/metadata-hash.d.ts +12 -0
  305. package/utils/metadata-hash.d.ts.map +1 -0
  306. package/utils/metadata-hash.js +26 -0
  307. package/utils/metadata-hash.js.map +1 -0
  308. package/utils/text.d.ts +3 -0
  309. package/utils/text.d.ts.map +1 -1
  310. package/utils/text.js +3 -0
  311. package/utils/text.js.map +1 -1
  312. package/utils/use-render-element.d.ts +3 -0
  313. package/utils/use-render-element.d.ts.map +1 -1
  314. package/utils/use-render-element.js +3 -0
  315. package/utils/use-render-element.js.map +1 -1
  316. package/support/context/config.d.ts +0 -32
  317. package/support/context/config.d.ts.map +0 -1
  318. package/support/context/config.js +0 -27
  319. package/support/context/config.js.map +0 -1
  320. package/support/support-CMoDLQoC.css.map +0 -1
@@ -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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"}
@@ -5,14 +5,27 @@ import { SeenActorType, SeenState } from "@cossistant/core";
5
5
  //#region src/realtime/seen-store.d.ts
6
6
  type Selector<T> = (state: SeenState) => T;
7
7
  type EqualityChecker<T> = (previous: T, next: T) => boolean;
8
+ /**
9
+ * Public hook for subscribing to slices of the shared "seen" store.
10
+ */
8
11
  declare function useSeenStore<TSelected>(selector: Selector<TSelected>, isEqual?: EqualityChecker<TSelected>): TSelected;
12
+ /**
13
+ * Seeds the seen store with initial data, typically from the REST API or SSR.
14
+ */
9
15
  declare function hydrateConversationSeen(conversationId: string, entries: ConversationSeen[]): void;
16
+ /**
17
+ * Inserts or updates a seen entry for the provided actor.
18
+ */
10
19
  declare function upsertConversationSeen(options: {
11
20
  conversationId: string;
12
21
  actorType: SeenActorType;
13
22
  actorId: string;
14
23
  lastSeenAt: Date;
15
24
  }): void;
25
+ /**
26
+ * Applies realtime `conversationSeen` events to the store while optionally
27
+ * ignoring the local visitor/user.
28
+ */
16
29
  declare function applyConversationSeenEvent(event: RealtimeEvent<"conversationSeen">, options?: {
17
30
  ignoreVisitorId?: string | null;
18
31
  ignoreUserId?: string | null;
@@ -1 +1 @@
1
- {"version":3,"file":"seen-store.d.ts","names":[],"sources":["../../src/realtime/seen-store.ts"],"sourcesContent":[],"mappings":";;;;;KAeK,sBAAsB,cAAc;KAEpC,gCAAgC,SAAS;AAFzC,iBAiCW,YAjCH,CAAA,SAAA,CAAA,CAAA,QAAA,EAkCF,QAlCE,CAkCO,SAlCP,CAAA,EAAA,OAAA,CAAA,EAmCF,eAnCE,CAmCc,SAnCd,CAAA,CAAA,EAoCV,SApCU;AAAA,iBAwCG,uBAAA,CAxCH,cAAA,EAAA,MAAA,EAAA,OAAA,EA0CH,gBA1CG,EAAA,CAAA,EAAA,IAAA;AAAc,iBA+CX,sBAAA,CA/CW,OAAA,EAAA;gBAAc,EAAA,MAAA;EAAC,SAAA,EAiD9B,aAjD8B;EAErC,OAAA,EAAA,MAAA;EAAe,UAAA,EAiDP,IAjDO;QAAiB;AAAS,iBAyD9B,0BAAA,CAzD8B,KAAA,EA0DtC,aA1DsC,CAAA,kBAAA,CAAA,EAAA,QAAA,EAAA;EAAC,eAAA,CAAA,EAAA,MAAA,GAAA,IAAA;EA+B/B,YAAA,CAAA,EAAY,MAAA,GAAA,IAAA;EAAA,eAAA,CAAA,EAAA,MAAA,GAAA,IAAA;QACR"}
1
+ {"version":3,"file":"seen-store.d.ts","names":[],"sources":["../../src/realtime/seen-store.ts"],"sourcesContent":[],"mappings":";;;;;KAeK,sBAAsB,cAAc;KAEpC,gCAAgC,SAAS;AAPoB;AAKxB;AAoC1C;AACoB,iBADJ,YACI,CAAA,SAAA,CAAA,CAAA,QAAA,EAAT,QAAS,CAAA,SAAA,CAAA,EAAA,OAAA,CAAA,EACT,eADS,CACO,SADP,CAAA,CAAA,EAEjB,SAFiB;;;;AAEjB,iBAOa,uBAAA,CAPb,cAAA,EAAA,MAAA,EAAA,OAAA,EASO,gBATP,EAAA,CAAA,EAAA,IAAA;;AAOH;AAUA;AAgBgB,iBAhBA,sBAAA,CAiBR,OAAA,EAAA;;aAfI;;cAEC;;;;;;iBAYG,0BAAA,QACR"}
@@ -8,23 +8,35 @@ function useSelector(selector, isEqual = Object.is) {
8
8
  const subscribe = (onStoreChange) => store.subscribe(() => {
9
9
  onStoreChange();
10
10
  });
11
- const snapshot = useSyncExternalStore(subscribe, store.getState, store.getState);
12
- const selected = selector(snapshot);
11
+ const selected = selector(useSyncExternalStore(subscribe, store.getState, store.getState));
13
12
  if (selectionRef.current === void 0 || !isEqual(selectionRef.current, selected)) selectionRef.current = selected;
14
13
  return selectionRef.current;
15
14
  }
15
+ /**
16
+ * Public hook for subscribing to slices of the shared "seen" store.
17
+ */
16
18
  function useSeenStore(selector, isEqual) {
17
19
  return useSelector(selector, isEqual);
18
20
  }
21
+ /**
22
+ * Seeds the seen store with initial data, typically from the REST API or SSR.
23
+ */
19
24
  function hydrateConversationSeen(conversationId, entries) {
20
25
  hydrateConversationSeen$1(store, conversationId, entries);
21
26
  }
27
+ /**
28
+ * Inserts or updates a seen entry for the provided actor.
29
+ */
22
30
  function upsertConversationSeen(options) {
23
31
  upsertConversationSeen$1(store, {
24
32
  ...options,
25
33
  lastSeenAt: options.lastSeenAt.toISOString()
26
34
  });
27
35
  }
36
+ /**
37
+ * Applies realtime `conversationSeen` events to the store while optionally
38
+ * ignoring the local visitor/user.
39
+ */
28
40
  function applyConversationSeenEvent(event, options) {
29
41
  applyConversationSeenEvent$1(store, event, options);
30
42
  }
@@ -1 +1 @@
1
- {"version":3,"file":"seen-store.js","names":[],"sources":["../../src/realtime/seen-store.ts"],"sourcesContent":["import {\n\tapplyConversationSeenEvent as applyEvent,\n\tcreateSeenStore,\n\thydrateConversationSeen as hydrateStore,\n\ttype SeenActorType,\n\ttype SeenEntry,\n\ttype SeenState,\n\tupsertConversationSeen as upsertStore,\n} from \"@cossistant/core\";\nimport type { RealtimeEvent } from \"@cossistant/types/realtime-events\";\nimport type { ConversationSeen } from \"@cossistant/types/schemas\";\nimport { useRef, useSyncExternalStore } from \"react\";\n\nconst store = createSeenStore();\n\ntype Selector<T> = (state: SeenState) => T;\n\ntype EqualityChecker<T> = (previous: T, next: T) => boolean;\n\nfunction useSelector<TSelected>(\n\tselector: Selector<TSelected>,\n\tisEqual: EqualityChecker<TSelected> = Object.is\n): TSelected {\n\tconst selectionRef = useRef<TSelected>(undefined);\n\n\tconst subscribe = (onStoreChange: () => void) =>\n\t\tstore.subscribe(() => {\n\t\t\tonStoreChange();\n\t\t});\n\n\tconst snapshot = useSyncExternalStore(\n\t\tsubscribe,\n\t\tstore.getState,\n\t\tstore.getState\n\t);\n\n\tconst selected = selector(snapshot);\n\n\tif (\n\t\tselectionRef.current === undefined ||\n\t\t!isEqual(selectionRef.current, selected)\n\t) {\n\t\tselectionRef.current = selected;\n\t}\n\n\treturn selectionRef.current as TSelected;\n}\n\nexport function useSeenStore<TSelected>(\n\tselector: Selector<TSelected>,\n\tisEqual?: EqualityChecker<TSelected>\n): TSelected {\n\treturn useSelector(selector, isEqual);\n}\n\nexport function hydrateConversationSeen(\n\tconversationId: string,\n\tentries: ConversationSeen[]\n) {\n\thydrateStore(store, conversationId, entries);\n}\n\nexport function upsertConversationSeen(options: {\n\tconversationId: string;\n\tactorType: SeenActorType;\n\tactorId: string;\n\tlastSeenAt: Date;\n}) {\n\tupsertStore(store, {\n\t\t...options,\n\t\tlastSeenAt: options.lastSeenAt.toISOString(),\n\t});\n}\n\nexport function applyConversationSeenEvent(\n\tevent: RealtimeEvent<\"conversationSeen\">,\n\toptions?: {\n\t\tignoreVisitorId?: string | null;\n\t\tignoreUserId?: string | null;\n\t\tignoreAiAgentId?: string | null;\n\t}\n) {\n\tapplyEvent(store, event, options);\n}\n"],"mappings":";;;;AAaA,MAAM,QAAQ,iBAAiB;AAM/B,SAAS,YACR,UACA,UAAsC,OAAO,IACjC;CACZ,MAAM,eAAe,OAAkB,OAAU;CAEjD,MAAM,aAAa,kBAClB,MAAM,gBAAgB;AACrB,iBAAe;GACd;CAEH,MAAM,WAAW,qBAChB,WACA,MAAM,UACN,MAAM,SACN;CAED,MAAM,WAAW,SAAS,SAAS;AAEnC,KACC,aAAa,YAAY,UACzB,CAAC,QAAQ,aAAa,SAAS,SAAS,CAExC,cAAa,UAAU;AAGxB,QAAO,aAAa;;AAGrB,SAAgB,aACf,UACA,SACY;AACZ,QAAO,YAAY,UAAU,QAAQ;;AAGtC,SAAgB,wBACf,gBACA,SACC;AACD,2BAAa,OAAO,gBAAgB,QAAQ;;AAG7C,SAAgB,uBAAuB,SAKpC;AACF,0BAAY,OAAO;EAClB,GAAG;EACH,YAAY,QAAQ,WAAW,aAAa;EAC5C,CAAC;;AAGH,SAAgB,2BACf,OACA,SAKC;AACD,8BAAW,OAAO,OAAO,QAAQ"}
1
+ {"version":3,"file":"seen-store.js","names":[],"sources":["../../src/realtime/seen-store.ts"],"sourcesContent":["import {\n\tapplyConversationSeenEvent as applyEvent,\n\tcreateSeenStore,\n\thydrateConversationSeen as hydrateStore,\n\ttype SeenActorType,\n\ttype SeenEntry,\n\ttype SeenState,\n\tupsertConversationSeen as upsertStore,\n} from \"@cossistant/core\";\nimport type { RealtimeEvent } from \"@cossistant/types/realtime-events\";\nimport type { ConversationSeen } from \"@cossistant/types/schemas\";\nimport { useRef, useSyncExternalStore } from \"react\";\n\nconst store = createSeenStore();\n\ntype Selector<T> = (state: SeenState) => T;\n\ntype EqualityChecker<T> = (previous: T, next: T) => boolean;\n\nfunction useSelector<TSelected>(\n\tselector: Selector<TSelected>,\n\tisEqual: EqualityChecker<TSelected> = Object.is\n): TSelected {\n\tconst selectionRef = useRef<TSelected>(undefined);\n\n\tconst subscribe = (onStoreChange: () => void) =>\n\t\tstore.subscribe(() => {\n\t\t\tonStoreChange();\n\t\t});\n\n\tconst snapshot = useSyncExternalStore(\n\t\tsubscribe,\n\t\tstore.getState,\n\t\tstore.getState\n\t);\n\n\tconst selected = selector(snapshot);\n\n\tif (\n\t\tselectionRef.current === undefined ||\n\t\t!isEqual(selectionRef.current, selected)\n\t) {\n\t\tselectionRef.current = selected;\n\t}\n\n\treturn selectionRef.current as TSelected;\n}\n\n/**\n * Public hook for subscribing to slices of the shared \"seen\" store.\n */\nexport function useSeenStore<TSelected>(\n\tselector: Selector<TSelected>,\n\tisEqual?: EqualityChecker<TSelected>\n): TSelected {\n\treturn useSelector(selector, isEqual);\n}\n\n/**\n * Seeds the seen store with initial data, typically from the REST API or SSR.\n */\nexport function hydrateConversationSeen(\n\tconversationId: string,\n\tentries: ConversationSeen[]\n) {\n\thydrateStore(store, conversationId, entries);\n}\n\n/**\n * Inserts or updates a seen entry for the provided actor.\n */\nexport function upsertConversationSeen(options: {\n\tconversationId: string;\n\tactorType: SeenActorType;\n\tactorId: string;\n\tlastSeenAt: Date;\n}) {\n\tupsertStore(store, {\n\t\t...options,\n\t\tlastSeenAt: options.lastSeenAt.toISOString(),\n\t});\n}\n\n/**\n * Applies realtime `conversationSeen` events to the store while optionally\n * ignoring the local visitor/user.\n */\nexport function applyConversationSeenEvent(\n\tevent: RealtimeEvent<\"conversationSeen\">,\n\toptions?: {\n\t\tignoreVisitorId?: string | null;\n\t\tignoreUserId?: string | null;\n\t\tignoreAiAgentId?: string | null;\n\t}\n) {\n\tapplyEvent(store, event, options);\n}\n"],"mappings":";;;;AAaA,MAAM,QAAQ,iBAAiB;AAM/B,SAAS,YACR,UACA,UAAsC,OAAO,IACjC;CACZ,MAAM,eAAe,OAAkB,OAAU;CAEjD,MAAM,aAAa,kBAClB,MAAM,gBAAgB;AACrB,iBAAe;GACd;CAQH,MAAM,WAAW,SANA,qBAChB,WACA,MAAM,UACN,MAAM,SACN,CAEkC;AAEnC,KACC,aAAa,YAAY,UACzB,CAAC,QAAQ,aAAa,SAAS,SAAS,CAExC,cAAa,UAAU;AAGxB,QAAO,aAAa;;;;;AAMrB,SAAgB,aACf,UACA,SACY;AACZ,QAAO,YAAY,UAAU,QAAQ;;;;;AAMtC,SAAgB,wBACf,gBACA,SACC;AACD,2BAAa,OAAO,gBAAgB,QAAQ;;;;;AAM7C,SAAgB,uBAAuB,SAKpC;AACF,0BAAY,OAAO;EAClB,GAAG;EACH,YAAY,QAAQ,WAAW,aAAa;EAC5C,CAAC;;;;;;AAOH,SAAgB,2BACf,OACA,SAKC;AACD,8BAAW,OAAO,OAAO,QAAQ"}
@@ -1,5 +1,4 @@
1
1
  import React from "react";
2
- import * as react_jsx_runtime2 from "react/jsx-runtime";
3
2
 
4
3
  //#region src/realtime/support-provider.d.ts
5
4
  type SupportRealtimeProviderProps = {
@@ -11,7 +10,7 @@ type SupportRealtimeProviderProps = {
11
10
  */
12
11
  declare function SupportRealtimeProvider({
13
12
  children
14
- }: SupportRealtimeProviderProps): react_jsx_runtime2.JSX.Element;
13
+ }: SupportRealtimeProviderProps): React.ReactElement;
15
14
  //#endregion
16
15
  export { SupportRealtimeProvider };
17
16
  //# sourceMappingURL=support-provider.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"support-provider.d.ts","names":[],"sources":["../../src/realtime/support-provider.tsx"],"sourcesContent":[],"mappings":";;;;KAkBK,4BAAA;YACM,KAAA,CAAM;;AAjBc;AAwB/B;;;AAEG,iBAFa,uBAAA,CAEb;EAAA;AAAA,CAAA,EAAA,4BAAA,CAAA,EAA4B,kBAAA,CAAA,GAAA,CAAA,OAA5B"}
1
+ {"version":3,"file":"support-provider.d.ts","names":[],"sources":["../../src/realtime/support-provider.tsx"],"sourcesContent":[],"mappings":";;;KAkBK,4BAAA;YACM,KAAA,CAAM;AAjBc,CAAA;AAwB/B;;;;AAEoD,iBAFpC,uBAAA,CAEoC;EAAA;AAAA,CAAA,EAAjD,4BAAiD,CAAA,EAAlB,KAAA,CAAM,YAAY"}
@@ -21,28 +21,27 @@ function SupportRealtimeProvider({ children }) {
21
21
  visitor?.id,
22
22
  client
23
23
  ]);
24
- const events = useMemo(() => ({
25
- timelineItemCreated: (_data, { event, context }) => {
26
- if (context.websiteId && event.payload.websiteId !== context.websiteId) return;
27
- clearTypingFromTimelineItem(event);
28
- context.client.handleRealtimeEvent(event);
29
- },
30
- conversationSeen: (_data, { event, context }) => {
31
- if (context.websiteId && event.payload.websiteId !== context.websiteId) return;
32
- applyConversationSeenEvent(event);
33
- },
34
- conversationTyping: (_data, { event, context }) => {
35
- if (context.websiteId && event.payload.websiteId !== context.websiteId) return;
36
- applyConversationTypingEvent(event, { ignoreVisitorId: context.visitorId });
37
- },
38
- conversationEventCreated: (_data, { event, context }) => {
39
- if (context.websiteId && event.payload.websiteId !== context.websiteId) return;
40
- context.client.handleRealtimeEvent(event);
41
- }
42
- }), []);
43
24
  useRealtime({
44
25
  context: realtimeContext,
45
- events,
26
+ events: useMemo(() => ({
27
+ timelineItemCreated: (_data, { event, context }) => {
28
+ if (context.websiteId && event.payload.websiteId !== context.websiteId) return;
29
+ clearTypingFromTimelineItem(event);
30
+ context.client.handleRealtimeEvent(event);
31
+ },
32
+ conversationSeen: (_data, { event, context }) => {
33
+ if (context.websiteId && event.payload.websiteId !== context.websiteId) return;
34
+ applyConversationSeenEvent(event);
35
+ },
36
+ conversationTyping: (_data, { event, context }) => {
37
+ if (context.websiteId && event.payload.websiteId !== context.websiteId) return;
38
+ applyConversationTypingEvent(event, { ignoreVisitorId: context.visitorId });
39
+ },
40
+ conversationEventCreated: (_data, { event, context }) => {
41
+ if (context.websiteId && event.payload.websiteId !== context.websiteId) return;
42
+ context.client.handleRealtimeEvent(event);
43
+ }
44
+ }), []),
46
45
  websiteId: realtimeContext.websiteId,
47
46
  visitorId: realtimeContext.visitorId
48
47
  });
@@ -1 +1 @@
1
- {"version":3,"file":"support-provider.js","names":[],"sources":["../../src/realtime/support-provider.tsx"],"sourcesContent":["import type { CossistantClient } from \"@cossistant/core\";\nimport type { RealtimeEvent } from \"@cossistant/types/realtime-events\";\nimport type React from \"react\";\nimport { useMemo } from \"react\";\nimport { useSupport } from \"../provider\";\nimport { applyConversationSeenEvent } from \"./seen-store\";\nimport {\n\tapplyConversationTypingEvent,\n\tclearTypingFromTimelineItem,\n} from \"./typing-store\";\nimport { useRealtime } from \"./use-realtime\";\n\ntype SupportRealtimeContext = {\n\twebsiteId: string | null;\n\tvisitorId: string | null;\n\tclient: CossistantClient;\n};\n\ntype SupportRealtimeProviderProps = {\n\tchildren: React.ReactNode;\n};\n\n/**\n * Bridges websocket events into the core client stores so support hooks stay\n * in sync without forcing refetches.\n */\nexport function SupportRealtimeProvider({\n\tchildren,\n}: SupportRealtimeProviderProps) {\n\tconst { website, client, visitor } = useSupport();\n\n\tconst realtimeContext = useMemo<SupportRealtimeContext>(\n\t\t() => ({\n\t\t\twebsiteId: website?.id ?? null,\n\t\t\tvisitorId: visitor?.id ?? null,\n\t\t\tclient,\n\t\t}),\n\t\t[website?.id, visitor?.id, client]\n\t);\n\n\tconst events = useMemo(\n\t\t() => ({\n\t\t\ttimelineItemCreated: (\n\t\t\t\t_data: unknown,\n\t\t\t\t{\n\t\t\t\t\tevent,\n\t\t\t\t\tcontext,\n\t\t\t\t}: {\n\t\t\t\t\tevent: RealtimeEvent<\"timelineItemCreated\">;\n\t\t\t\t\tcontext: SupportRealtimeContext;\n\t\t\t\t}\n\t\t\t) => {\n\t\t\t\tif (\n\t\t\t\t\tcontext.websiteId &&\n\t\t\t\t\tevent.payload.websiteId !== context.websiteId\n\t\t\t\t) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\t// Clear typing state when a timeline item is created\n\t\t\t\tclearTypingFromTimelineItem(event);\n\n\t\t\t\tcontext.client.handleRealtimeEvent(event);\n\t\t\t},\n\t\t\tconversationSeen: (\n\t\t\t\t_data: unknown,\n\t\t\t\t{\n\t\t\t\t\tevent,\n\t\t\t\t\tcontext,\n\t\t\t\t}: {\n\t\t\t\t\tevent: RealtimeEvent<\"conversationSeen\">;\n\t\t\t\t\tcontext: SupportRealtimeContext;\n\t\t\t\t}\n\t\t\t) => {\n\t\t\t\tif (\n\t\t\t\t\tcontext.websiteId &&\n\t\t\t\t\tevent.payload.websiteId !== context.websiteId\n\t\t\t\t) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\t// Update the seen store so the UI reflects who has seen messages\n\t\t\t\tapplyConversationSeenEvent(event);\n\t\t\t},\n\t\t\tconversationTyping: (\n\t\t\t\t_data: unknown,\n\t\t\t\t{\n\t\t\t\t\tevent,\n\t\t\t\t\tcontext,\n\t\t\t\t}: {\n\t\t\t\t\tevent: RealtimeEvent<\"conversationTyping\">;\n\t\t\t\t\tcontext: SupportRealtimeContext;\n\t\t\t\t}\n\t\t\t) => {\n\t\t\t\tif (\n\t\t\t\t\tcontext.websiteId &&\n\t\t\t\t\tevent.payload.websiteId !== context.websiteId\n\t\t\t\t) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\t// Update typing store, but ignore events from the current visitor (their own typing)\n\t\t\t\t// Note: We use context.visitorId which is fresh from the context object\n\t\t\t\tapplyConversationTypingEvent(event, {\n\t\t\t\t\tignoreVisitorId: context.visitorId,\n\t\t\t\t});\n\t\t\t},\n\t\t\tconversationEventCreated: (\n\t\t\t\t_data: unknown,\n\t\t\t\t{\n\t\t\t\t\tevent,\n\t\t\t\t\tcontext,\n\t\t\t\t}: {\n\t\t\t\t\tevent: RealtimeEvent<\"conversationEventCreated\">;\n\t\t\t\t\tcontext: SupportRealtimeContext;\n\t\t\t\t}\n\t\t\t) => {\n\t\t\t\tif (\n\t\t\t\t\tcontext.websiteId &&\n\t\t\t\t\tevent.payload.websiteId !== context.websiteId\n\t\t\t\t) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tcontext.client.handleRealtimeEvent(event);\n\t\t\t},\n\t\t}),\n\t\t// Empty dependencies is fine here since we use the context parameter\n\t\t// which always has fresh data from the memoized realtimeContext\n\t\t[]\n\t);\n\n\tuseRealtime<SupportRealtimeContext>({\n\t\tcontext: realtimeContext,\n\t\tevents,\n\t\twebsiteId: realtimeContext.websiteId,\n\t\tvisitorId: realtimeContext.visitorId,\n\t});\n\n\treturn <>{children}</>;\n}\n"],"mappings":";;;;;;;;;;;;AA0BA,SAAgB,wBAAwB,EACvC,YACgC;CAChC,MAAM,EAAE,SAAS,QAAQ,YAAY,YAAY;CAEjD,MAAM,kBAAkB,eAChB;EACN,WAAW,SAAS,MAAM;EAC1B,WAAW,SAAS,MAAM;EAC1B;EACA,GACD;EAAC,SAAS;EAAI,SAAS;EAAI;EAAO,CAClC;CAED,MAAM,SAAS,eACP;EACN,sBACC,OACA,EACC,OACA,cAKG;AACJ,OACC,QAAQ,aACR,MAAM,QAAQ,cAAc,QAAQ,UAEpC;AAID,+BAA4B,MAAM;AAElC,WAAQ,OAAO,oBAAoB,MAAM;;EAE1C,mBACC,OACA,EACC,OACA,cAKG;AACJ,OACC,QAAQ,aACR,MAAM,QAAQ,cAAc,QAAQ,UAEpC;AAID,8BAA2B,MAAM;;EAElC,qBACC,OACA,EACC,OACA,cAKG;AACJ,OACC,QAAQ,aACR,MAAM,QAAQ,cAAc,QAAQ,UAEpC;AAKD,gCAA6B,OAAO,EACnC,iBAAiB,QAAQ,WACzB,CAAC;;EAEH,2BACC,OACA,EACC,OACA,cAKG;AACJ,OACC,QAAQ,aACR,MAAM,QAAQ,cAAc,QAAQ,UAEpC;AAGD,WAAQ,OAAO,oBAAoB,MAAM;;EAE1C,GAGD,EAAE,CACF;AAED,aAAoC;EACnC,SAAS;EACT;EACA,WAAW,gBAAgB;EAC3B,WAAW,gBAAgB;EAC3B,CAAC;AAEF,QAAO,gCAAG,WAAY"}
1
+ {"version":3,"file":"support-provider.js","names":[],"sources":["../../src/realtime/support-provider.tsx"],"sourcesContent":["import type { CossistantClient } from \"@cossistant/core\";\nimport type { RealtimeEvent } from \"@cossistant/types/realtime-events\";\nimport type React from \"react\";\nimport { useMemo } from \"react\";\nimport { useSupport } from \"../provider\";\nimport { applyConversationSeenEvent } from \"./seen-store\";\nimport {\n\tapplyConversationTypingEvent,\n\tclearTypingFromTimelineItem,\n} from \"./typing-store\";\nimport { useRealtime } from \"./use-realtime\";\n\ntype SupportRealtimeContext = {\n\twebsiteId: string | null;\n\tvisitorId: string | null;\n\tclient: CossistantClient;\n};\n\ntype SupportRealtimeProviderProps = {\n\tchildren: React.ReactNode;\n};\n\n/**\n * Bridges websocket events into the core client stores so support hooks stay\n * in sync without forcing refetches.\n */\nexport function SupportRealtimeProvider({\n\tchildren,\n}: SupportRealtimeProviderProps): React.ReactElement {\n\tconst { website, client, visitor } = useSupport();\n\n\tconst realtimeContext = useMemo<SupportRealtimeContext>(\n\t\t() => ({\n\t\t\twebsiteId: website?.id ?? null,\n\t\t\tvisitorId: visitor?.id ?? null,\n\t\t\tclient,\n\t\t}),\n\t\t[website?.id, visitor?.id, client]\n\t);\n\n\tconst events = useMemo(\n\t\t() => ({\n\t\t\ttimelineItemCreated: (\n\t\t\t\t_data: unknown,\n\t\t\t\t{\n\t\t\t\t\tevent,\n\t\t\t\t\tcontext,\n\t\t\t\t}: {\n\t\t\t\t\tevent: RealtimeEvent<\"timelineItemCreated\">;\n\t\t\t\t\tcontext: SupportRealtimeContext;\n\t\t\t\t}\n\t\t\t) => {\n\t\t\t\tif (\n\t\t\t\t\tcontext.websiteId &&\n\t\t\t\t\tevent.payload.websiteId !== context.websiteId\n\t\t\t\t) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\t// Clear typing state when a timeline item is created\n\t\t\t\tclearTypingFromTimelineItem(event);\n\n\t\t\t\tcontext.client.handleRealtimeEvent(event);\n\t\t\t},\n\t\t\tconversationSeen: (\n\t\t\t\t_data: unknown,\n\t\t\t\t{\n\t\t\t\t\tevent,\n\t\t\t\t\tcontext,\n\t\t\t\t}: {\n\t\t\t\t\tevent: RealtimeEvent<\"conversationSeen\">;\n\t\t\t\t\tcontext: SupportRealtimeContext;\n\t\t\t\t}\n\t\t\t) => {\n\t\t\t\tif (\n\t\t\t\t\tcontext.websiteId &&\n\t\t\t\t\tevent.payload.websiteId !== context.websiteId\n\t\t\t\t) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\t// Update the seen store so the UI reflects who has seen messages\n\t\t\t\tapplyConversationSeenEvent(event);\n\t\t\t},\n\t\t\tconversationTyping: (\n\t\t\t\t_data: unknown,\n\t\t\t\t{\n\t\t\t\t\tevent,\n\t\t\t\t\tcontext,\n\t\t\t\t}: {\n\t\t\t\t\tevent: RealtimeEvent<\"conversationTyping\">;\n\t\t\t\t\tcontext: SupportRealtimeContext;\n\t\t\t\t}\n\t\t\t) => {\n\t\t\t\tif (\n\t\t\t\t\tcontext.websiteId &&\n\t\t\t\t\tevent.payload.websiteId !== context.websiteId\n\t\t\t\t) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\t// Update typing store, but ignore events from the current visitor (their own typing)\n\t\t\t\t// Note: We use context.visitorId which is fresh from the context object\n\t\t\t\tapplyConversationTypingEvent(event, {\n\t\t\t\t\tignoreVisitorId: context.visitorId,\n\t\t\t\t});\n\t\t\t},\n\t\t\tconversationEventCreated: (\n\t\t\t\t_data: unknown,\n\t\t\t\t{\n\t\t\t\t\tevent,\n\t\t\t\t\tcontext,\n\t\t\t\t}: {\n\t\t\t\t\tevent: RealtimeEvent<\"conversationEventCreated\">;\n\t\t\t\t\tcontext: SupportRealtimeContext;\n\t\t\t\t}\n\t\t\t) => {\n\t\t\t\tif (\n\t\t\t\t\tcontext.websiteId &&\n\t\t\t\t\tevent.payload.websiteId !== context.websiteId\n\t\t\t\t) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tcontext.client.handleRealtimeEvent(event);\n\t\t\t},\n\t\t}),\n\t\t// Empty dependencies is fine here since we use the context parameter\n\t\t// which always has fresh data from the memoized realtimeContext\n\t\t[]\n\t);\n\n\tuseRealtime<SupportRealtimeContext>({\n\t\tcontext: realtimeContext,\n\t\tevents,\n\t\twebsiteId: realtimeContext.websiteId,\n\t\tvisitorId: realtimeContext.visitorId,\n\t});\n\n\treturn <>{children}</>;\n}\n"],"mappings":";;;;;;;;;;;;AA0BA,SAAgB,wBAAwB,EACvC,YACoD;CACpD,MAAM,EAAE,SAAS,QAAQ,YAAY,YAAY;CAEjD,MAAM,kBAAkB,eAChB;EACN,WAAW,SAAS,MAAM;EAC1B,WAAW,SAAS,MAAM;EAC1B;EACA,GACD;EAAC,SAAS;EAAI,SAAS;EAAI;EAAO,CAClC;AA8FD,aAAoC;EACnC,SAAS;EACT,QA9Fc,eACP;GACN,sBACC,OACA,EACC,OACA,cAKG;AACJ,QACC,QAAQ,aACR,MAAM,QAAQ,cAAc,QAAQ,UAEpC;AAID,gCAA4B,MAAM;AAElC,YAAQ,OAAO,oBAAoB,MAAM;;GAE1C,mBACC,OACA,EACC,OACA,cAKG;AACJ,QACC,QAAQ,aACR,MAAM,QAAQ,cAAc,QAAQ,UAEpC;AAID,+BAA2B,MAAM;;GAElC,qBACC,OACA,EACC,OACA,cAKG;AACJ,QACC,QAAQ,aACR,MAAM,QAAQ,cAAc,QAAQ,UAEpC;AAKD,iCAA6B,OAAO,EACnC,iBAAiB,QAAQ,WACzB,CAAC;;GAEH,2BACC,OACA,EACC,OACA,cAKG;AACJ,QACC,QAAQ,aACR,MAAM,QAAQ,cAAc,QAAQ,UAEpC;AAGD,YAAQ,OAAO,oBAAoB,MAAM;;GAE1C,GAGD,EAAE,CACF;EAKA,WAAW,gBAAgB;EAC3B,WAAW,gBAAgB;EAC3B,CAAC;AAEF,QAAO,gCAAG,WAAY"}
@@ -4,7 +4,14 @@ import { TypingActorType, TypingState } from "@cossistant/core";
4
4
  //#region src/realtime/typing-store.d.ts
5
5
  type Selector<T> = (state: TypingState) => T;
6
6
  type EqualityChecker<T> = (previous: T, next: T) => boolean;
7
+ /**
8
+ * Hook wrapper for the typing Zustand store used by realtime helpers.
9
+ */
7
10
  declare function useTypingStore<TSelected>(selector: Selector<TSelected>, isEqual?: EqualityChecker<TSelected>): TSelected;
11
+ /**
12
+ * Manually sets the typing state for a participant, typically in response to
13
+ * local input changes.
14
+ */
8
15
  declare function setTypingState(options: {
9
16
  conversationId: string;
10
17
  actorType: TypingActorType;
@@ -13,17 +20,28 @@ declare function setTypingState(options: {
13
20
  preview?: string | null;
14
21
  ttlMs?: number;
15
22
  }): void;
23
+ /**
24
+ * Removes typing state entries for a participant.
25
+ */
16
26
  declare function clearTypingState(options: {
17
27
  conversationId: string;
18
28
  actorType: TypingActorType;
19
29
  actorId: string;
20
30
  }): void;
31
+ /**
32
+ * Applies realtime typing events to the store while supporting exclusions for
33
+ * the current visitor or agents.
34
+ */
21
35
  declare function applyConversationTypingEvent(event: RealtimeEvent<"conversationTyping">, options?: {
22
36
  ignoreVisitorId?: string | null;
23
37
  ignoreUserId?: string | null;
24
38
  ignoreAiAgentId?: string | null;
25
39
  ttlMs?: number;
26
40
  }): void;
41
+ /**
42
+ * Utility invoked when a timeline item is created to clear stale typing
43
+ * indicators for the sender.
44
+ */
27
45
  declare function clearTypingFromTimelineItem(event: RealtimeEvent<"timelineItemCreated">): void;
28
46
  //#endregion
29
47
  export { applyConversationTypingEvent, clearTypingFromTimelineItem, clearTypingState, setTypingState, useTypingStore };
@@ -1 +1 @@
1
- {"version":3,"file":"typing-store.d.ts","names":[],"sources":["../../src/realtime/typing-store.ts"],"sourcesContent":[],"mappings":";;;;KAgBK,sBAAsB,gBAAgB;KAEtC,gCAAgC,SAAS;AAFzC,iBAiCW,cAjCH,CAAA,SAAA,CAAA,CAAA,QAAA,EAkCF,QAlCE,CAkCO,SAlCP,CAAA,EAAA,OAAA,CAAA,EAmCF,eAnCE,CAmCc,SAnCd,CAAA,CAAA,EAoCV,SApCU;AAAA,iBAwCG,cAAA,CAxCH,OAAA,EAAA;gBAAc,EAAA,MAAA;WAAgB,EA0C/B,eA1C+B;EAAC,OAAA,EAAA,MAAA;EAEvC,QAAA,EAAA,OAAA;EAAe,OAAA,CAAA,EAAA,MAAA,GAAA,IAAA;OAAiB,CAAA,EAAA,MAAA;QAAS;AAAC,iBAiD/B,gBAAA,CAjD+B,OAAA,EAAA;EA+B/B,cAAA,EAAA,MAAc;EAAA,SAAA,EAoBlB,eApBkB;SACV,EAAA,MAAA;QAAT;AACgB,iBAwBX,4BAAA,CAxBW,KAAA,EAyBnB,aAzBmB,CAAA,oBAAA,CAAA,EAAA,OAgB3B,CAhB2B,EAAA;iBAAhB,CAAA,EAAA,MAAA,GAAA,IAAA;cACR,CAAA,EAAA,MAAA,GAAA,IAAA;EAAS,eAAA,CAAA,EAAA,MAAA,GAAA,IAAA;EAII,KAAA,CAAA,EAAA,MAAA;AAWhB,CAAA,CAAA,EAAgB,IAAA;AAQA,iBAYA,2BAAA,CAXR,KAAA,EAYA,aAZa,CAAA,qBAAA,CAAA,CAAA,EAAA,IAAA"}
1
+ {"version":3,"file":"typing-store.d.ts","names":[],"sources":["../../src/realtime/typing-store.ts"],"sourcesContent":[],"mappings":";;;;KAgBK,sBAAsB,gBAAgB;KAEtC,gCAAgC,SAAS;AAPyB;AAK3B;AAoC5C;AACoB,iBADJ,cACI,CAAA,SAAA,CAAA,CAAA,QAAA,EAAT,QAAS,CAAA,SAAA,CAAA,EAAA,OAAA,CAAA,EACT,eADS,CACO,SADP,CAAA,CAAA,EAEjB,SAFiB;;;;;AAER,iBAQI,cAAA,CARJ,OAAA,EAAA;EAQI,cAAA,EAAA,MAAc;EAcd,SAAA,EAZJ,eAYoB;EAYhB,OAAA,EAAA,MAAA;EAgBA,QAAA,EAAA,OAAA;;;;;;;iBA5BA,gBAAA;;aAEJ;;;;;;;iBAUI,4BAAA,QACR;;;;;;;;;;iBAeQ,2BAAA,QACR"}
@@ -8,23 +8,40 @@ function useSelector(selector, isEqual = Object.is) {
8
8
  const subscribe = (onStoreChange) => store.subscribe(() => {
9
9
  onStoreChange();
10
10
  });
11
- const snapshot = useSyncExternalStore(subscribe, store.getState, store.getState);
12
- const selected = selector(snapshot);
11
+ const selected = selector(useSyncExternalStore(subscribe, store.getState, store.getState));
13
12
  if (selectionRef.current === void 0 || !isEqual(selectionRef.current, selected)) selectionRef.current = selected;
14
13
  return selectionRef.current;
15
14
  }
15
+ /**
16
+ * Hook wrapper for the typing Zustand store used by realtime helpers.
17
+ */
16
18
  function useTypingStore(selector, isEqual) {
17
19
  return useSelector(selector, isEqual);
18
20
  }
21
+ /**
22
+ * Manually sets the typing state for a participant, typically in response to
23
+ * local input changes.
24
+ */
19
25
  function setTypingState(options) {
20
26
  setTypingState$1(store, options);
21
27
  }
28
+ /**
29
+ * Removes typing state entries for a participant.
30
+ */
22
31
  function clearTypingState(options) {
23
32
  clearTypingState$1(store, options);
24
33
  }
34
+ /**
35
+ * Applies realtime typing events to the store while supporting exclusions for
36
+ * the current visitor or agents.
37
+ */
25
38
  function applyConversationTypingEvent(event, options) {
26
39
  applyConversationTypingEvent$1(store, event, options);
27
40
  }
41
+ /**
42
+ * Utility invoked when a timeline item is created to clear stale typing
43
+ * indicators for the sender.
44
+ */
28
45
  function clearTypingFromTimelineItem(event) {
29
46
  clearTypingFromTimelineItem$1(store, event);
30
47
  }
@@ -1 +1 @@
1
- {"version":3,"file":"typing-store.js","names":[],"sources":["../../src/realtime/typing-store.ts"],"sourcesContent":["import {\n\tapplyConversationTypingEvent as applyEvent,\n\ttype ConversationTypingState,\n\tclearTypingFromTimelineItem as clearFromTimelineItem,\n\tclearTypingState as clearState,\n\tcreateTypingStore,\n\tsetTypingState as setState,\n\ttype TypingActorType,\n\ttype TypingEntry,\n\ttype TypingState,\n} from \"@cossistant/core\";\nimport type { RealtimeEvent } from \"@cossistant/types/realtime-events\";\nimport { useRef, useSyncExternalStore } from \"react\";\n\nconst store = createTypingStore();\n\ntype Selector<T> = (state: TypingState) => T;\n\ntype EqualityChecker<T> = (previous: T, next: T) => boolean;\n\nfunction useSelector<TSelected>(\n\tselector: Selector<TSelected>,\n\tisEqual: EqualityChecker<TSelected> = Object.is\n): TSelected {\n\tconst selectionRef = useRef<TSelected>(undefined);\n\n\tconst subscribe = (onStoreChange: () => void) =>\n\t\tstore.subscribe(() => {\n\t\t\tonStoreChange();\n\t\t});\n\n\tconst snapshot = useSyncExternalStore(\n\t\tsubscribe,\n\t\tstore.getState,\n\t\tstore.getState\n\t);\n\n\tconst selected = selector(snapshot);\n\n\tif (\n\t\tselectionRef.current === undefined ||\n\t\t!isEqual(selectionRef.current, selected)\n\t) {\n\t\tselectionRef.current = selected;\n\t}\n\n\treturn selectionRef.current as TSelected;\n}\n\nexport function useTypingStore<TSelected>(\n\tselector: Selector<TSelected>,\n\tisEqual?: EqualityChecker<TSelected>\n): TSelected {\n\treturn useSelector(selector, isEqual);\n}\n\nexport function setTypingState(options: {\n\tconversationId: string;\n\tactorType: TypingActorType;\n\tactorId: string;\n\tisTyping: boolean;\n\tpreview?: string | null;\n\tttlMs?: number;\n}) {\n\tsetState(store, options);\n}\n\nexport function clearTypingState(options: {\n\tconversationId: string;\n\tactorType: TypingActorType;\n\tactorId: string;\n}) {\n\tclearState(store, options);\n}\n\nexport function applyConversationTypingEvent(\n\tevent: RealtimeEvent<\"conversationTyping\">,\n\toptions?: {\n\t\tignoreVisitorId?: string | null;\n\t\tignoreUserId?: string | null;\n\t\tignoreAiAgentId?: string | null;\n\t\tttlMs?: number;\n\t}\n) {\n\tapplyEvent(store, event, options);\n}\n\nexport function clearTypingFromTimelineItem(\n\tevent: RealtimeEvent<\"timelineItemCreated\">\n) {\n\tclearFromTimelineItem(store, event);\n}\n"],"mappings":";;;;AAcA,MAAM,QAAQ,mBAAmB;AAMjC,SAAS,YACR,UACA,UAAsC,OAAO,IACjC;CACZ,MAAM,eAAe,OAAkB,OAAU;CAEjD,MAAM,aAAa,kBAClB,MAAM,gBAAgB;AACrB,iBAAe;GACd;CAEH,MAAM,WAAW,qBAChB,WACA,MAAM,UACN,MAAM,SACN;CAED,MAAM,WAAW,SAAS,SAAS;AAEnC,KACC,aAAa,YAAY,UACzB,CAAC,QAAQ,aAAa,SAAS,SAAS,CAExC,cAAa,UAAU;AAGxB,QAAO,aAAa;;AAGrB,SAAgB,eACf,UACA,SACY;AACZ,QAAO,YAAY,UAAU,QAAQ;;AAGtC,SAAgB,eAAe,SAO5B;AACF,kBAAS,OAAO,QAAQ;;AAGzB,SAAgB,iBAAiB,SAI9B;AACF,oBAAW,OAAO,QAAQ;;AAG3B,SAAgB,6BACf,OACA,SAMC;AACD,gCAAW,OAAO,OAAO,QAAQ;;AAGlC,SAAgB,4BACf,OACC;AACD,+BAAsB,OAAO,MAAM"}
1
+ {"version":3,"file":"typing-store.js","names":[],"sources":["../../src/realtime/typing-store.ts"],"sourcesContent":["import {\n\tapplyConversationTypingEvent as applyEvent,\n\ttype ConversationTypingState,\n\tclearTypingFromTimelineItem as clearFromTimelineItem,\n\tclearTypingState as clearState,\n\tcreateTypingStore,\n\tsetTypingState as setState,\n\ttype TypingActorType,\n\ttype TypingEntry,\n\ttype TypingState,\n} from \"@cossistant/core\";\nimport type { RealtimeEvent } from \"@cossistant/types/realtime-events\";\nimport { useRef, useSyncExternalStore } from \"react\";\n\nconst store = createTypingStore();\n\ntype Selector<T> = (state: TypingState) => T;\n\ntype EqualityChecker<T> = (previous: T, next: T) => boolean;\n\nfunction useSelector<TSelected>(\n\tselector: Selector<TSelected>,\n\tisEqual: EqualityChecker<TSelected> = Object.is\n): TSelected {\n\tconst selectionRef = useRef<TSelected>(undefined);\n\n\tconst subscribe = (onStoreChange: () => void) =>\n\t\tstore.subscribe(() => {\n\t\t\tonStoreChange();\n\t\t});\n\n\tconst snapshot = useSyncExternalStore(\n\t\tsubscribe,\n\t\tstore.getState,\n\t\tstore.getState\n\t);\n\n\tconst selected = selector(snapshot);\n\n\tif (\n\t\tselectionRef.current === undefined ||\n\t\t!isEqual(selectionRef.current, selected)\n\t) {\n\t\tselectionRef.current = selected;\n\t}\n\n\treturn selectionRef.current as TSelected;\n}\n\n/**\n * Hook wrapper for the typing Zustand store used by realtime helpers.\n */\nexport function useTypingStore<TSelected>(\n\tselector: Selector<TSelected>,\n\tisEqual?: EqualityChecker<TSelected>\n): TSelected {\n\treturn useSelector(selector, isEqual);\n}\n\n/**\n * Manually sets the typing state for a participant, typically in response to\n * local input changes.\n */\nexport function setTypingState(options: {\n\tconversationId: string;\n\tactorType: TypingActorType;\n\tactorId: string;\n\tisTyping: boolean;\n\tpreview?: string | null;\n\tttlMs?: number;\n}) {\n\tsetState(store, options);\n}\n\n/**\n * Removes typing state entries for a participant.\n */\nexport function clearTypingState(options: {\n\tconversationId: string;\n\tactorType: TypingActorType;\n\tactorId: string;\n}) {\n\tclearState(store, options);\n}\n\n/**\n * Applies realtime typing events to the store while supporting exclusions for\n * the current visitor or agents.\n */\nexport function applyConversationTypingEvent(\n\tevent: RealtimeEvent<\"conversationTyping\">,\n\toptions?: {\n\t\tignoreVisitorId?: string | null;\n\t\tignoreUserId?: string | null;\n\t\tignoreAiAgentId?: string | null;\n\t\tttlMs?: number;\n\t}\n) {\n\tapplyEvent(store, event, options);\n}\n\n/**\n * Utility invoked when a timeline item is created to clear stale typing\n * indicators for the sender.\n */\nexport function clearTypingFromTimelineItem(\n\tevent: RealtimeEvent<\"timelineItemCreated\">\n) {\n\tclearFromTimelineItem(store, event);\n}\n"],"mappings":";;;;AAcA,MAAM,QAAQ,mBAAmB;AAMjC,SAAS,YACR,UACA,UAAsC,OAAO,IACjC;CACZ,MAAM,eAAe,OAAkB,OAAU;CAEjD,MAAM,aAAa,kBAClB,MAAM,gBAAgB;AACrB,iBAAe;GACd;CAQH,MAAM,WAAW,SANA,qBAChB,WACA,MAAM,UACN,MAAM,SACN,CAEkC;AAEnC,KACC,aAAa,YAAY,UACzB,CAAC,QAAQ,aAAa,SAAS,SAAS,CAExC,cAAa,UAAU;AAGxB,QAAO,aAAa;;;;;AAMrB,SAAgB,eACf,UACA,SACY;AACZ,QAAO,YAAY,UAAU,QAAQ;;;;;;AAOtC,SAAgB,eAAe,SAO5B;AACF,kBAAS,OAAO,QAAQ;;;;;AAMzB,SAAgB,iBAAiB,SAI9B;AACF,oBAAW,OAAO,QAAQ;;;;;;AAO3B,SAAgB,6BACf,OACA,SAMC;AACD,gCAAW,OAAO,OAAO,QAAQ;;;;;;AAOlC,SAAgB,4BACf,OACC;AACD,+BAAsB,OAAO,MAAM"}
@@ -3,12 +3,12 @@ import { RealtimeContextValue } from "./provider.js";
3
3
 
4
4
  //#region src/realtime/use-realtime.d.ts
5
5
  type RealtimeHandlerContext<TContext> = TContext;
6
- type RealtimeEventMeta<TType extends RealtimeEventType, TContext> = {
7
- event: RealtimeEvent<TType>;
6
+ type RealtimeEventMeta<TType$1 extends RealtimeEventType, TContext> = {
7
+ event: RealtimeEvent<TType$1>;
8
8
  context: RealtimeHandlerContext<TContext>;
9
9
  };
10
- type RealtimeEventHandler<TType extends RealtimeEventType, TContext> = (data: RealtimeEventData<TType>, meta: RealtimeEventMeta<TType, TContext>) => void | Promise<void>;
11
- type RealtimeEventHandlerEntry<TType extends RealtimeEventType, TContext> = RealtimeEventHandler<TType, TContext> | RealtimeEventHandler<TType, TContext>[];
10
+ type RealtimeEventHandler<TType$1 extends RealtimeEventType, TContext> = (data: RealtimeEventData<TType$1>, meta: RealtimeEventMeta<TType$1, TContext>) => void | Promise<void>;
11
+ type RealtimeEventHandlerEntry<TType$1 extends RealtimeEventType, TContext> = RealtimeEventHandler<TType$1, TContext> | RealtimeEventHandler<TType$1, TContext>[];
12
12
  type RealtimeEventHandlersMap<TContext> = Partial<{ [TType in RealtimeEventType]: RealtimeEventHandlerEntry<TType, TContext> }>;
13
13
  type UseRealtimeOptions<TContext, THandlers extends RealtimeEventHandlersMap<TContext>> = {
14
14
  events: THandlers;
@@ -17,6 +17,10 @@ type UseRealtimeOptions<TContext, THandlers extends RealtimeEventHandlersMap<TCo
17
17
  context?: TContext;
18
18
  onEventError?: (error: unknown, event: RealtimeEvent<RealtimeEventType>) => void;
19
19
  };
20
+ /**
21
+ * Binds realtime connection events to typed handler maps with automatic
22
+ * filtering by website/visitor ids.
23
+ */
20
24
  declare function useRealtime<TContext = void, THandlers extends RealtimeEventHandlersMap<TContext> = RealtimeEventHandlersMap<TContext>>({
21
25
  events,
22
26
  websiteId,
@@ -1 +1 @@
1
- {"version":3,"file":"use-realtime.d.ts","names":[],"sources":["../../src/realtime/use-realtime.ts"],"sourcesContent":[],"mappings":";;;;KAUY,mCAAmC;KAEnC,gCAAgC;SACpC,cAAc;EAHV,OAAA,EAIF,sBAJwB,CAID,QAJc,CAAA;AAE/C,CAAA;AAA6B,KAKjB,oBALiB,CAAA,cAKkB,iBALlB,EAAA,QAAA,CAAA,GAAA,CAAA,IAAA,EAMtB,iBANsB,CAMJ,KANI,CAAA,EAAA,IAAA,EAOtB,iBAPsB,CAOJ,KAPI,EAOG,QAPH,CAAA,EAAA,GAAA,IAAA,GAQjB,OARiB,CAAA,IAAA,CAAA;AAAe,KAUhC,yBAVgC,CAAA,cAW7B,iBAX6B,EAAA,QAAA,CAAA,GAczC,oBAdyC,CAcpB,KAdoB,EAcb,QAda,CAAA,GAezC,oBAfyC,CAepB,KAfoB,EAeb,QAfa,CAAA,EAAA;AACtB,KAgBV,wBAhBU,CAAA,QAAA,CAAA,GAgB2B,OAhB3B,CAAA,YAiBX,iBAjBH,GAiBuB,yBAjBvB,CAiBiD,KAjBjD,EAiBwD,QAjBxD,CAAA;KAoBH,kBAnBK,CAAA,QAAA,EAAA,kBAqBS,wBArBT,CAqBkC,QArBlC,CAAA,CAAA,GAAA;EAAsB,MAAA,EAuBvB,SAvBuB;EAGpB,SAAA,CAAA,EAAA,MAAA,GAAA,IAAoB;EAAA,SAAA,CAAA,EAAA,MAAA,GAAA,IAAA;SAAe,CAAA,EAuBpC,QAvBoC;cACtB,CAAA,EAAA,CAAA,KAAA,EAAA,OAAA,EAAA,KAAA,EAyBhB,aAzBgB,CAyBF,iBAzBE,CAAA,EAAA,GAAA,IAAA;;AACA,iBA4BT,WA5BS,CAAA,WAAA,IAAA,EAAA,kBA+BvB,wBA/BuB,CA+BE,QA/BF,CAAA,GA+Bc,wBA/Bd,CA+BuC,QA/BvC,CAAA,CAAA,CAAA;EAAA,MAAA;EAAA,SAAA;EAAA,SAAA;EAAA,OAAA;EAAA;AAAA,CAAA,EAsCtB,kBAtCsB,CAsCH,QAtCG,EAsCO,SAtCP,CAAA,CAAA,EAsCJ,oBAtCI"}
1
+ {"version":3,"file":"use-realtime.d.ts","names":[],"sources":["../../src/realtime/use-realtime.ts"],"sourcesContent":[],"mappings":";;;;KAUY,mCAAmC;KAEnC,kCAAgC;SACpC,cAAc;EAHV,OAAA,EAIF,sBAJwB,CAID,QAJC,CAAA;AAElC,CAAA;AAA4C,KAKhC,oBALgC,CAAA,gBAKG,iBALH,EAAA,QAAA,CAAA,GAAA,CAAA,IAAA,EAMrC,iBANqC,CAMnB,OANmB,CAAA,EAAA,IAAA,EAOrC,iBAPqC,CAOnB,OAPmB,EAOZ,QAPY,CAAA,EAAA,GAAA,IAAA,GAQhC,OARgC,CAAA,IAAA,CAAA;AACtB,KASV,yBATU,CAAA,gBAUP,iBAVO,EAAA,QAAA,CAAA,GAanB,oBAbmB,CAaE,OAbF,EAaS,QAbT,CAAA,GAcnB,oBAdmB,CAcE,OAdF,EAcS,QAdT,CAAA,EAAA;AAAd,KAgBI,wBAhBJ,CAAA,QAAA,CAAA,GAgByC,OAhBzC,CAAA,YAiBG,iBAhBsB,GAgBF,yBAhBE,CAgBwB,KAhBxB,EAgB+B,QAhB/B,CAAA,EAAvB,CAAA;KAmBL,kBAnB2B,CAAA,QAAA,EAAA,kBAqBb,wBArBa,CAqBY,QArBZ,CAAA,CAAA,GAAA;EAGpB,MAAA,EAoBH,SApBG;EAAmC,SAAA,CAAA,EAAA,MAAA,GAAA,IAAA;EACtB,SAAA,CAAA,EAAA,MAAA,GAAA,IAAA;EAAlB,OAAA,CAAA,EAsBI,QAtBJ;EACkB,YAAA,CAAA,EAAA,CAAA,KAAA,EAAA,OAAA,EAAA,KAAA,EAwBhB,aAxBgB,CAwBF,iBAxBE,CAAA,EAAA,GAAA,IAAA;CAAO;;;;AAGhC;AACe,iBA4BC,WA5BD,CAAA,WAAA,IAAA,EAAA,kBA+Bb,wBA/Ba,CA+BY,QA/BZ,CAAA,GA+BwB,wBA/BxB,CA+BiD,QA/BjD,CAAA,CAAA,CAAA;EAAA,MAAA;EAAA,SAAA;EAAA,SAAA;EAAA,OAAA;EAAA;AAAA,CAAA,EAsCZ,kBAtCY,CAsCO,QAtCP,EAsCiB,SAtCjB,CAAA,CAAA,EAsCM,oBAtCN"}
@@ -3,6 +3,10 @@ import { shouldDeliverEvent } from "./event-filter.js";
3
3
  import { useEffect, useRef } from "react";
4
4
 
5
5
  //#region src/realtime/use-realtime.ts
6
+ /**
7
+ * Binds realtime connection events to typed handler maps with automatic
8
+ * filtering by website/visitor ids.
9
+ */
6
10
  function useRealtime({ events, websiteId, visitorId, context, onEventError }) {
7
11
  const connection = useRealtimeConnection();
8
12
  const handlersRef = useRef(events);
@@ -1 +1 @@
1
- {"version":3,"file":"use-realtime.js","names":[],"sources":["../../src/realtime/use-realtime.ts"],"sourcesContent":["import type {\n\tAnyRealtimeEvent,\n\tRealtimeEvent,\n\tRealtimeEventData,\n\tRealtimeEventType,\n} from \"@cossistant/types/realtime-events\";\nimport { useEffect, useRef } from \"react\";\nimport { shouldDeliverEvent } from \"./event-filter\";\nimport { useRealtimeConnection } from \"./provider\";\n\nexport type RealtimeHandlerContext<TContext> = TContext;\n\nexport type RealtimeEventMeta<TType extends RealtimeEventType, TContext> = {\n\tevent: RealtimeEvent<TType>;\n\tcontext: RealtimeHandlerContext<TContext>;\n};\n\nexport type RealtimeEventHandler<TType extends RealtimeEventType, TContext> = (\n\tdata: RealtimeEventData<TType>,\n\tmeta: RealtimeEventMeta<TType, TContext>\n) => void | Promise<void>;\n\nexport type RealtimeEventHandlerEntry<\n\tTType extends RealtimeEventType,\n\tTContext,\n> =\n\t| RealtimeEventHandler<TType, TContext>\n\t| RealtimeEventHandler<TType, TContext>[];\n\nexport type RealtimeEventHandlersMap<TContext> = Partial<{\n\t[TType in RealtimeEventType]: RealtimeEventHandlerEntry<TType, TContext>;\n}>;\n\ntype UseRealtimeOptions<\n\tTContext,\n\tTHandlers extends RealtimeEventHandlersMap<TContext>,\n> = {\n\tevents: THandlers;\n\twebsiteId?: string | null;\n\tvisitorId?: string | null;\n\tcontext?: TContext;\n\tonEventError?: (\n\t\terror: unknown,\n\t\tevent: RealtimeEvent<RealtimeEventType>\n\t) => void;\n};\n\nexport function useRealtime<\n\tTContext = void,\n\tTHandlers extends\n\t\tRealtimeEventHandlersMap<TContext> = RealtimeEventHandlersMap<TContext>,\n>({\n\tevents,\n\twebsiteId,\n\tvisitorId,\n\tcontext,\n\tonEventError,\n}: UseRealtimeOptions<TContext, THandlers>) {\n\tconst connection = useRealtimeConnection();\n\tconst handlersRef = useRef<RealtimeEventHandlersMap<TContext>>(events);\n\tconst contextRef = useRef<TContext | undefined>(context);\n\tconst websiteIdRef = useRef<string | null>(websiteId ?? null);\n\tconst visitorIdRef = useRef<string | null>(visitorId ?? null);\n\tconst errorHandlerRef = useRef<\n\t\t| ((error: unknown, event: RealtimeEvent<RealtimeEventType>) => void)\n\t\t| undefined\n\t>(onEventError);\n\n\tuseEffect(() => {\n\t\thandlersRef.current = events;\n\t}, [events]);\n\n\tuseEffect(() => {\n\t\tcontextRef.current = context;\n\t}, [context]);\n\n\tuseEffect(() => {\n\t\twebsiteIdRef.current = websiteId ?? null;\n\t}, [websiteId]);\n\n\tuseEffect(() => {\n\t\tvisitorIdRef.current = visitorId ?? null;\n\t}, [visitorId]);\n\n\tuseEffect(() => {\n\t\terrorHandlerRef.current = onEventError;\n\t}, [onEventError]);\n\n\tuseEffect(\n\t\t() =>\n\t\t\tconnection.subscribe((event) => {\n\t\t\t\tconst handlers = handlersRef.current[event.type];\n\n\t\t\t\tif (!handlers) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (\n\t\t\t\t\t!shouldDeliverEvent(event, websiteIdRef.current, visitorIdRef.current)\n\t\t\t\t) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tconst payload = Array.isArray(handlers) ? handlers : [handlers];\n\n\t\t\t\tfor (const handler of payload) {\n\t\t\t\t\tPromise.resolve(\n\t\t\t\t\t\thandler(event.payload as never, {\n\t\t\t\t\t\t\tevent: event as never,\n\t\t\t\t\t\t\tcontext: contextRef.current as TContext,\n\t\t\t\t\t\t})\n\t\t\t\t\t).catch((error) => {\n\t\t\t\t\t\tconst errorHandler = errorHandlerRef.current;\n\t\t\t\t\t\tif (errorHandler) {\n\t\t\t\t\t\t\terrorHandler(error, event);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tconsole.error(\"[Realtime] Event handler threw an error\", error);\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}),\n\t\t[connection]\n\t);\n\n\treturn connection;\n}\n"],"mappings":";;;;;AA+CA,SAAgB,YAId,EACD,QACA,WACA,WACA,SACA,gBAC2C;CAC3C,MAAM,aAAa,uBAAuB;CAC1C,MAAM,cAAc,OAA2C,OAAO;CACtE,MAAM,aAAa,OAA6B,QAAQ;CACxD,MAAM,eAAe,OAAsB,aAAa,KAAK;CAC7D,MAAM,eAAe,OAAsB,aAAa,KAAK;CAC7D,MAAM,kBAAkB,OAGtB,aAAa;AAEf,iBAAgB;AACf,cAAY,UAAU;IACpB,CAAC,OAAO,CAAC;AAEZ,iBAAgB;AACf,aAAW,UAAU;IACnB,CAAC,QAAQ,CAAC;AAEb,iBAAgB;AACf,eAAa,UAAU,aAAa;IAClC,CAAC,UAAU,CAAC;AAEf,iBAAgB;AACf,eAAa,UAAU,aAAa;IAClC,CAAC,UAAU,CAAC;AAEf,iBAAgB;AACf,kBAAgB,UAAU;IACxB,CAAC,aAAa,CAAC;AAElB,iBAEE,WAAW,WAAW,UAAU;EAC/B,MAAM,WAAW,YAAY,QAAQ,MAAM;AAE3C,MAAI,CAAC,SACJ;AAGD,MACC,CAAC,mBAAmB,OAAO,aAAa,SAAS,aAAa,QAAQ,CAEtE;EAGD,MAAM,UAAU,MAAM,QAAQ,SAAS,GAAG,WAAW,CAAC,SAAS;AAE/D,OAAK,MAAM,WAAW,QACrB,SAAQ,QACP,QAAQ,MAAM,SAAkB;GACxB;GACP,SAAS,WAAW;GACpB,CAAC,CACF,CAAC,OAAO,UAAU;GAClB,MAAM,eAAe,gBAAgB;AACrC,OAAI,aACH,cAAa,OAAO,MAAM;OAE1B,SAAQ,MAAM,2CAA2C,MAAM;IAE/D;GAEF,EACH,CAAC,WAAW,CACZ;AAED,QAAO"}
1
+ {"version":3,"file":"use-realtime.js","names":[],"sources":["../../src/realtime/use-realtime.ts"],"sourcesContent":["import type {\n\tAnyRealtimeEvent,\n\tRealtimeEvent,\n\tRealtimeEventData,\n\tRealtimeEventType,\n} from \"@cossistant/types/realtime-events\";\nimport { useEffect, useRef } from \"react\";\nimport { shouldDeliverEvent } from \"./event-filter\";\nimport { useRealtimeConnection } from \"./provider\";\n\nexport type RealtimeHandlerContext<TContext> = TContext;\n\nexport type RealtimeEventMeta<TType extends RealtimeEventType, TContext> = {\n\tevent: RealtimeEvent<TType>;\n\tcontext: RealtimeHandlerContext<TContext>;\n};\n\nexport type RealtimeEventHandler<TType extends RealtimeEventType, TContext> = (\n\tdata: RealtimeEventData<TType>,\n\tmeta: RealtimeEventMeta<TType, TContext>\n) => void | Promise<void>;\n\nexport type RealtimeEventHandlerEntry<\n\tTType extends RealtimeEventType,\n\tTContext,\n> =\n\t| RealtimeEventHandler<TType, TContext>\n\t| RealtimeEventHandler<TType, TContext>[];\n\nexport type RealtimeEventHandlersMap<TContext> = Partial<{\n\t[TType in RealtimeEventType]: RealtimeEventHandlerEntry<TType, TContext>;\n}>;\n\ntype UseRealtimeOptions<\n\tTContext,\n\tTHandlers extends RealtimeEventHandlersMap<TContext>,\n> = {\n\tevents: THandlers;\n\twebsiteId?: string | null;\n\tvisitorId?: string | null;\n\tcontext?: TContext;\n\tonEventError?: (\n\t\terror: unknown,\n\t\tevent: RealtimeEvent<RealtimeEventType>\n\t) => void;\n};\n\n/**\n * Binds realtime connection events to typed handler maps with automatic\n * filtering by website/visitor ids.\n */\nexport function useRealtime<\n\tTContext = void,\n\tTHandlers extends\n\t\tRealtimeEventHandlersMap<TContext> = RealtimeEventHandlersMap<TContext>,\n>({\n\tevents,\n\twebsiteId,\n\tvisitorId,\n\tcontext,\n\tonEventError,\n}: UseRealtimeOptions<TContext, THandlers>) {\n\tconst connection = useRealtimeConnection();\n\tconst handlersRef = useRef<RealtimeEventHandlersMap<TContext>>(events);\n\tconst contextRef = useRef<TContext | undefined>(context);\n\tconst websiteIdRef = useRef<string | null>(websiteId ?? null);\n\tconst visitorIdRef = useRef<string | null>(visitorId ?? null);\n\tconst errorHandlerRef = useRef<\n\t\t| ((error: unknown, event: RealtimeEvent<RealtimeEventType>) => void)\n\t\t| undefined\n\t>(onEventError);\n\n\tuseEffect(() => {\n\t\thandlersRef.current = events;\n\t}, [events]);\n\n\tuseEffect(() => {\n\t\tcontextRef.current = context;\n\t}, [context]);\n\n\tuseEffect(() => {\n\t\twebsiteIdRef.current = websiteId ?? null;\n\t}, [websiteId]);\n\n\tuseEffect(() => {\n\t\tvisitorIdRef.current = visitorId ?? null;\n\t}, [visitorId]);\n\n\tuseEffect(() => {\n\t\terrorHandlerRef.current = onEventError;\n\t}, [onEventError]);\n\n\tuseEffect(\n\t\t() =>\n\t\t\tconnection.subscribe((event) => {\n\t\t\t\tconst handlers = handlersRef.current[event.type];\n\n\t\t\t\tif (!handlers) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (\n\t\t\t\t\t!shouldDeliverEvent(event, websiteIdRef.current, visitorIdRef.current)\n\t\t\t\t) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tconst payload = Array.isArray(handlers) ? handlers : [handlers];\n\n\t\t\t\tfor (const handler of payload) {\n\t\t\t\t\tPromise.resolve(\n\t\t\t\t\t\thandler(event.payload as never, {\n\t\t\t\t\t\t\tevent: event as never,\n\t\t\t\t\t\t\tcontext: contextRef.current as TContext,\n\t\t\t\t\t\t})\n\t\t\t\t\t).catch((error) => {\n\t\t\t\t\t\tconst errorHandler = errorHandlerRef.current;\n\t\t\t\t\t\tif (errorHandler) {\n\t\t\t\t\t\t\terrorHandler(error, event);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tconsole.error(\"[Realtime] Event handler threw an error\", error);\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}),\n\t\t[connection]\n\t);\n\n\treturn connection;\n}\n"],"mappings":";;;;;;;;;AAmDA,SAAgB,YAId,EACD,QACA,WACA,WACA,SACA,gBAC2C;CAC3C,MAAM,aAAa,uBAAuB;CAC1C,MAAM,cAAc,OAA2C,OAAO;CACtE,MAAM,aAAa,OAA6B,QAAQ;CACxD,MAAM,eAAe,OAAsB,aAAa,KAAK;CAC7D,MAAM,eAAe,OAAsB,aAAa,KAAK;CAC7D,MAAM,kBAAkB,OAGtB,aAAa;AAEf,iBAAgB;AACf,cAAY,UAAU;IACpB,CAAC,OAAO,CAAC;AAEZ,iBAAgB;AACf,aAAW,UAAU;IACnB,CAAC,QAAQ,CAAC;AAEb,iBAAgB;AACf,eAAa,UAAU,aAAa;IAClC,CAAC,UAAU,CAAC;AAEf,iBAAgB;AACf,eAAa,UAAU,aAAa;IAClC,CAAC,UAAU,CAAC;AAEf,iBAAgB;AACf,kBAAgB,UAAU;IACxB,CAAC,aAAa,CAAC;AAElB,iBAEE,WAAW,WAAW,UAAU;EAC/B,MAAM,WAAW,YAAY,QAAQ,MAAM;AAE3C,MAAI,CAAC,SACJ;AAGD,MACC,CAAC,mBAAmB,OAAO,aAAa,SAAS,aAAa,QAAQ,CAEtE;EAGD,MAAM,UAAU,MAAM,QAAQ,SAAS,GAAG,WAAW,CAAC,SAAS;AAE/D,OAAK,MAAM,WAAW,QACrB,SAAQ,QACP,QAAQ,MAAM,SAAkB;GACxB;GACP,SAAS,WAAW;GACpB,CAAC,CACF,CAAC,OAAO,UAAU;GAClB,MAAM,eAAe,gBAAgB;AACrC,OAAI,aACH,cAAa,OAAO,MAAM;OAE1B,SAAQ,MAAM,2CAA2C,MAAM;IAE/D;GAEF,EACH,CAAC,WAAW,CACZ;AAED,QAAO"}
@@ -83,6 +83,7 @@ declare const realtimeSchema: {
83
83
  type: z.ZodEnum<{
84
84
  message: "message";
85
85
  event: "event";
86
+ identification: "identification";
86
87
  }>;
87
88
  text: z.ZodNullable<z.ZodString>;
88
89
  parts: z.ZodArray<z.ZodUnknown>;
@@ -91,6 +92,7 @@ declare const realtimeSchema: {
91
92
  aiAgentId: z.ZodNullable<z.ZodString>;
92
93
  createdAt: z.ZodString;
93
94
  deletedAt: z.ZodNullable<z.ZodString>;
95
+ tool: z.ZodOptional<z.ZodNullable<z.ZodString>>;
94
96
  }, z.core.$strip>;
95
97
  }, z.core.$strip>;
96
98
  readonly conversationCreated: z.ZodObject<{
@@ -107,10 +109,11 @@ declare const realtimeSchema: {
107
109
  visitorId: z.ZodString;
108
110
  websiteId: z.ZodString;
109
111
  status: z.ZodDefault<z.ZodEnum<{
110
- resolved: "resolved";
111
112
  open: "open";
113
+ resolved: "resolved";
112
114
  spam: "spam";
113
115
  }>>;
116
+ deletedAt: z.ZodDefault<z.ZodNullable<z.ZodString>>;
114
117
  lastTimelineItem: z.ZodOptional<z.ZodObject<{
115
118
  id: z.ZodOptional<z.ZodString>;
116
119
  conversationId: z.ZodString;
@@ -122,8 +125,10 @@ declare const realtimeSchema: {
122
125
  type: z.ZodEnum<{
123
126
  message: "message";
124
127
  event: "event";
128
+ identification: "identification";
125
129
  }>;
126
130
  text: z.ZodNullable<z.ZodString>;
131
+ tool: z.ZodOptional<z.ZodNullable<z.ZodString>>;
127
132
  parts: z.ZodArray<z.ZodUnion<readonly [z.ZodObject<{
128
133
  type: z.ZodLiteral<"text">;
129
134
  text: z.ZodString;
@@ -141,6 +146,9 @@ declare const realtimeSchema: {
141
146
  tag_removed: "tag_removed";
142
147
  resolved: "resolved";
143
148
  reopened: "reopened";
149
+ visitor_blocked: "visitor_blocked";
150
+ visitor_unblocked: "visitor_unblocked";
151
+ visitor_identified: "visitor_identified";
144
152
  }>;
145
153
  actorUserId: z.ZodNullable<z.ZodString>;
146
154
  actorAiAgentId: z.ZodNullable<z.ZodString>;
@@ -172,14 +180,14 @@ declare const realtimeSchema: {
172
180
  header: z.ZodObject<{
173
181
  id: z.ZodString;
174
182
  status: z.ZodEnum<{
175
- resolved: "resolved";
176
183
  open: "open";
184
+ resolved: "resolved";
177
185
  spam: "spam";
178
186
  }>;
179
187
  priority: z.ZodEnum<{
180
- normal: "normal";
181
188
  high: "high";
182
189
  low: "low";
190
+ normal: "normal";
183
191
  urgent: "urgent";
184
192
  }>;
185
193
  organizationId: z.ZodString;
@@ -195,6 +203,7 @@ declare const realtimeSchema: {
195
203
  name: z.ZodNullable<z.ZodString>;
196
204
  email: z.ZodNullable<z.ZodString>;
197
205
  image: z.ZodNullable<z.ZodString>;
206
+ metadataHash: z.ZodOptional<z.ZodString>;
198
207
  }, z.core.$strip>>;
199
208
  }, z.core.$strip>;
200
209
  websiteId: z.ZodString;
@@ -222,8 +231,10 @@ declare const realtimeSchema: {
222
231
  type: z.ZodEnum<{
223
232
  message: "message";
224
233
  event: "event";
234
+ identification: "identification";
225
235
  }>;
226
236
  text: z.ZodNullable<z.ZodString>;
237
+ tool: z.ZodOptional<z.ZodNullable<z.ZodString>>;
227
238
  parts: z.ZodArray<z.ZodUnion<readonly [z.ZodObject<{
228
239
  type: z.ZodLiteral<"text">;
229
240
  text: z.ZodString;
@@ -241,6 +252,9 @@ declare const realtimeSchema: {
241
252
  tag_removed: "tag_removed";
242
253
  resolved: "resolved";
243
254
  reopened: "reopened";
255
+ visitor_blocked: "visitor_blocked";
256
+ visitor_unblocked: "visitor_unblocked";
257
+ visitor_identified: "visitor_identified";
244
258
  }>;
245
259
  actorUserId: z.ZodNullable<z.ZodString>;
246
260
  actorAiAgentId: z.ZodNullable<z.ZodString>;
@@ -1 +1 @@
1
- {"version":3,"file":"realtime-events.d.ts","names":[],"sources":["../../types/src/realtime-events.ts"],"sourcesContent":[],"mappings":";;;;;;;;cAiBa;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAiED,iBAAA,gBAAiC;KAEjC,+BAA+B,qBAAqB,CAAA,CAAE,cACzD,gBAAgB;KAGb,wBAAwB;QAC7B;WACG,qBAAqB;;KAGnB,gBAAA,WACL,oBAAoB,cAAc,KACvC;KAEU,4BAA4B,qBACvC,qBAAqB"}
1
+ {"version":3,"file":"realtime-events.d.ts","names":[],"sources":["../../types/src/realtime-events.ts"],"sourcesContent":[],"mappings":";;;;;;;;cAiBa;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAmED,iBAAA,gBAAiC;KAEjC,+BAA+B,qBAAqB,CAAA,CAAE,cACzD,gBAAgB;KAGb,wBAAwB;QAC7B;WACG,qBAAqB;;KAGnB,gBAAA,WACL,oBAAoB,cAAc,KACvC;KAEU,4BAA4B,qBACvC,qBAAqB"}