@cossistant/react 0.0.28 → 0.0.30
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/_virtual/rolldown_runtime.js +9 -23
- package/hooks/index.d.ts +3 -3
- package/hooks/private/store/use-conversations-store.d.ts +2 -0
- package/hooks/private/store/use-conversations-store.d.ts.map +1 -1
- package/hooks/private/store/use-conversations-store.js +15 -8
- package/hooks/private/store/use-conversations-store.js.map +1 -1
- package/hooks/private/store/use-store-selector.d.ts +3 -0
- package/hooks/private/store/use-store-selector.d.ts.map +1 -1
- package/hooks/private/store/use-store-selector.js +4 -8
- package/hooks/private/store/use-store-selector.js.map +1 -1
- package/hooks/private/store/use-website-store.d.ts +3 -1
- package/hooks/private/store/use-website-store.d.ts.map +1 -1
- package/hooks/private/store/use-website-store.js +14 -6
- package/hooks/private/store/use-website-store.js.map +1 -1
- package/hooks/private/use-client-query.d.ts +1 -1
- package/hooks/private/use-client-query.d.ts.map +1 -1
- package/hooks/private/use-client-query.js +1 -0
- package/hooks/private/use-client-query.js.map +1 -1
- package/hooks/private/use-default-messages.d.ts +1 -1
- package/hooks/private/use-grouped-messages.d.ts +10 -7
- package/hooks/private/use-grouped-messages.d.ts.map +1 -1
- package/hooks/private/use-grouped-messages.js +44 -11
- package/hooks/private/use-grouped-messages.js.map +1 -1
- package/hooks/private/use-rest-client.d.ts +13 -3
- package/hooks/private/use-rest-client.d.ts.map +1 -1
- package/hooks/private/use-rest-client.js +49 -22
- package/hooks/private/use-rest-client.js.map +1 -1
- package/hooks/private/use-visitor-typing-reporter.d.ts +1 -1
- package/hooks/use-conversation-auto-seen.d.ts +1 -1
- package/hooks/use-conversation-page.d.ts +1 -1
- package/hooks/use-conversation-page.d.ts.map +1 -1
- package/hooks/use-conversation-page.js +10 -3
- package/hooks/use-conversation-page.js.map +1 -1
- package/hooks/use-conversation-preview.d.ts +3 -1
- package/hooks/use-conversation-preview.d.ts.map +1 -1
- package/hooks/use-conversation-preview.js +6 -3
- package/hooks/use-conversation-preview.js.map +1 -1
- package/hooks/use-conversation-seen.d.ts +1 -1
- package/hooks/use-conversation-seen.js +1 -1
- package/hooks/use-conversation-seen.js.map +1 -1
- package/hooks/use-conversation-timeline-items.d.ts +1 -1
- package/hooks/use-conversation-timeline-items.js +2 -3
- package/hooks/use-conversation-timeline-items.js.map +1 -1
- package/hooks/use-conversation-timeline.d.ts +1 -1
- package/hooks/use-conversation-timeline.d.ts.map +1 -1
- package/hooks/use-conversation-timeline.js +1 -3
- package/hooks/use-conversation-timeline.js.map +1 -1
- package/hooks/use-conversation.d.ts +1 -1
- package/hooks/use-conversation.js +2 -3
- package/hooks/use-conversation.js.map +1 -1
- package/hooks/use-conversations.d.ts +1 -1
- package/hooks/use-conversations.js +5 -3
- package/hooks/use-conversations.js.map +1 -1
- package/hooks/use-create-conversation.d.ts +3 -3
- package/hooks/use-create-conversation.js +1 -0
- package/hooks/use-create-conversation.js.map +1 -1
- package/hooks/use-file-upload.d.ts +1 -1
- package/hooks/use-file-upload.js +3 -3
- package/hooks/use-file-upload.js.map +1 -1
- package/hooks/use-home-page.js +3 -3
- package/hooks/use-home-page.js.map +1 -1
- package/hooks/use-message-composer.d.ts +10 -3
- package/hooks/use-message-composer.d.ts.map +1 -1
- package/hooks/use-message-composer.js +5 -2
- package/hooks/use-message-composer.js.map +1 -1
- package/hooks/use-realtime-support.d.ts +1 -1
- package/hooks/use-send-message.d.ts +8 -2
- package/hooks/use-send-message.d.ts.map +1 -1
- package/hooks/use-send-message.js +5 -3
- package/hooks/use-send-message.js.map +1 -1
- package/hooks/use-visitor.js +2 -2
- package/hooks/use-visitor.js.map +1 -1
- package/identify-visitor.d.ts.map +1 -1
- package/identify-visitor.js +15 -1
- package/identify-visitor.js.map +1 -1
- package/index.d.ts +3 -3
- package/index.js +1 -1
- package/package.json +5 -3
- package/{conversation.d.ts → packages/types/src/api/conversation.d.ts} +368 -64
- package/packages/types/src/api/conversation.d.ts.map +1 -0
- package/packages/types/src/api/timeline-item.d.ts +460 -0
- package/packages/types/src/api/timeline-item.d.ts.map +1 -0
- package/packages/types/src/realtime-events.d.ts +1004 -0
- package/packages/types/src/realtime-events.d.ts.map +1 -0
- package/{schemas3.d.ts → packages/types/src/schemas.d.ts} +95 -19
- package/packages/types/src/schemas.d.ts.map +1 -0
- package/primitives/avatar/avatar.js +1 -1
- package/primitives/avatar/avatar.js.map +1 -1
- package/primitives/avatar/fallback.d.ts.map +1 -1
- package/primitives/avatar/fallback.js +2 -2
- package/primitives/avatar/fallback.js.map +1 -1
- package/primitives/avatar/image.d.ts +1 -1
- package/primitives/avatar/image.js +1 -1
- package/primitives/avatar/image.js.map +1 -1
- package/primitives/button.js +1 -1
- package/primitives/button.js.map +1 -1
- package/primitives/conversation-timeline.d.ts +1 -1
- package/primitives/conversation-timeline.js +4 -4
- package/primitives/conversation-timeline.js.map +1 -1
- package/primitives/day-separator.d.ts +76 -0
- package/primitives/day-separator.d.ts.map +1 -0
- package/primitives/day-separator.js +111 -0
- package/primitives/day-separator.js.map +1 -0
- package/primitives/index.d.ts +3 -2
- package/primitives/index.js +6 -1
- package/primitives/index.parts.d.ts +2 -1
- package/primitives/index.parts.js +2 -1
- package/primitives/multimodal-input.d.ts +2 -2
- package/primitives/multimodal-input.d.ts.map +1 -1
- package/primitives/multimodal-input.js +2 -2
- package/primitives/multimodal-input.js.map +1 -1
- package/primitives/timeline-item-attachments.d.ts +1 -1
- package/primitives/timeline-item-attachments.js +6 -7
- package/primitives/timeline-item-attachments.js.map +1 -1
- package/primitives/timeline-item-group.d.ts +1 -1
- package/primitives/timeline-item-group.d.ts.map +1 -1
- package/primitives/timeline-item-group.js +8 -8
- package/primitives/timeline-item-group.js.map +1 -1
- package/primitives/timeline-item.d.ts +1 -1
- package/primitives/timeline-item.d.ts.map +1 -1
- package/primitives/timeline-item.js +33 -8
- package/primitives/timeline-item.js.map +1 -1
- package/primitives/trigger.js +1 -1
- package/primitives/trigger.js.map +1 -1
- package/primitives/window.js +1 -1
- package/primitives/window.js.map +1 -1
- package/provider.d.ts +4 -2
- package/provider.d.ts.map +1 -1
- package/provider.js +56 -8
- package/provider.js.map +1 -1
- package/realtime/event-filter.d.ts +4 -1
- package/realtime/event-filter.d.ts.map +1 -1
- package/realtime/event-filter.js +14 -0
- package/realtime/event-filter.js.map +1 -1
- package/realtime/provider.d.ts +1 -1
- package/realtime/provider.d.ts.map +1 -1
- package/realtime/provider.js +1 -2
- package/realtime/provider.js.map +1 -1
- package/realtime/seen-store.d.ts +2 -2
- package/realtime/support-provider.js +6 -1
- package/realtime/support-provider.js.map +1 -1
- package/realtime/typing-store.d.ts +1 -1
- package/realtime/use-realtime.d.ts +1 -1
- package/support/components/avatar-stack.d.ts.map +1 -1
- package/support/components/avatar-stack.js +32 -12
- package/support/components/avatar-stack.js.map +1 -1
- package/support/components/avatar.d.ts +34 -3
- package/support/components/avatar.d.ts.map +1 -1
- package/support/components/avatar.js +61 -8
- package/support/components/avatar.js.map +1 -1
- package/support/components/button.d.ts +4 -2
- package/support/components/button.d.ts.map +1 -1
- package/support/components/button.js +3 -3
- package/support/components/button.js.map +1 -1
- package/support/components/configuration-error.d.ts +16 -0
- package/support/components/configuration-error.d.ts.map +1 -0
- package/support/components/configuration-error.js +162 -0
- package/support/components/configuration-error.js.map +1 -0
- package/support/components/content.js +1 -2
- package/support/components/content.js.map +1 -1
- package/support/components/conversation-button-link.js +18 -23
- package/support/components/conversation-button-link.js.map +1 -1
- package/support/components/conversation-event.d.ts.map +1 -1
- package/support/components/conversation-event.js +7 -5
- package/support/components/conversation-event.js.map +1 -1
- package/support/components/conversation-timeline.d.ts +6 -1
- package/support/components/conversation-timeline.d.ts.map +1 -1
- package/support/components/conversation-timeline.js +22 -2
- package/support/components/conversation-timeline.js.map +1 -1
- package/support/components/header.js +1 -1
- package/support/components/image-lightbox.d.ts +1 -1
- package/support/components/image-lightbox.js +1 -2
- package/support/components/image-lightbox.js.map +1 -1
- package/support/components/index.js +1 -1
- package/support/components/multimodal-input.js +0 -1
- package/support/components/multimodal-input.js.map +1 -1
- package/support/components/navigation-tab.js +1 -1
- package/support/components/online-indicator.d.ts +50 -0
- package/support/components/online-indicator.d.ts.map +1 -0
- package/support/components/online-indicator.js +65 -0
- package/support/components/online-indicator.js.map +1 -0
- package/support/components/root.js +0 -1
- package/support/components/root.js.map +1 -1
- package/support/components/timeline-identification-tool.js +4 -4
- package/support/components/timeline-identification-tool.js.map +1 -1
- package/support/components/timeline-message-group.d.ts +1 -1
- package/support/components/timeline-message-group.d.ts.map +1 -1
- package/support/components/timeline-message-group.js +6 -4
- package/support/components/timeline-message-group.js.map +1 -1
- package/support/components/timeline-message-item.d.ts +1 -1
- package/support/components/timeline-message-item.js +4 -4
- package/support/components/timeline-message-item.js.map +1 -1
- package/support/components/trigger.js +1 -2
- package/support/components/trigger.js.map +1 -1
- package/support/components/typing-indicator.js +1 -1
- package/support/components/typing-indicator.js.map +1 -1
- package/support/context/controlled-state.js +0 -1
- package/support/context/controlled-state.js.map +1 -1
- package/support/context/events.d.ts +1 -1
- package/support/context/events.js +0 -1
- package/support/context/events.js.map +1 -1
- package/support/context/handle.js +0 -1
- package/support/context/handle.js.map +1 -1
- package/support/context/identification.d.ts +33 -0
- package/support/context/identification.d.ts.map +1 -0
- package/support/context/identification.js +34 -0
- package/support/context/identification.js.map +1 -0
- package/support/context/positioning.js +0 -1
- package/support/context/positioning.js.map +1 -1
- package/support/context/slots.js +0 -1
- package/support/context/slots.js.map +1 -1
- package/support/context/websocket.d.ts +1 -1
- package/support/context/websocket.js +0 -1
- package/support/context/websocket.js.map +1 -1
- package/support/index.d.ts.map +1 -1
- package/support/index.js +51 -18
- package/support/index.js.map +1 -1
- package/support/pages/conversation-history.js +2 -1
- package/support/pages/conversation-history.js.map +1 -1
- package/support/pages/conversation.d.ts +1 -1
- package/support/pages/conversation.js +1 -1
- package/support/pages/conversation.js.map +1 -1
- package/support/pages/home.js +5 -3
- package/support/pages/home.js.map +1 -1
- package/support/router.d.ts.map +1 -1
- package/support/router.js +4 -0
- package/support/router.js.map +1 -1
- package/support/store/support-store.js +0 -1
- package/support/store/support-store.js.map +1 -1
- package/support/{support-C7Xaw-N6.css → support-DmViRaga.css} +2 -2
- package/support/{support-C7Xaw-N6.css.map → support-DmViRaga.css.map} +1 -1
- package/support/text/index.d.ts +1 -1
- package/support/text/index.d.ts.map +1 -1
- package/support/text/index.js +1 -1
- package/support/text/index.js.map +1 -1
- package/support/text/locales/en.js +1 -1
- package/support/text/locales/en.js.map +1 -1
- package/support/text/locales/es.js +1 -1
- package/support/text/locales/es.js.map +1 -1
- package/support/text/locales/fr.js +1 -1
- package/support/text/locales/fr.js.map +1 -1
- package/support/utils/index.d.ts +1 -1
- package/support-config.js +0 -1
- package/support-config.js.map +1 -1
- package/support.css +1 -1
- package/tailwind.css +1 -1
- package/utils/conversation.d.ts.map +1 -1
- package/utils/conversation.js +1 -3
- package/utils/conversation.js.map +1 -1
- package/utils/use-render-element.d.ts.map +1 -1
- package/utils/use-render-element.js +20 -5
- package/utils/use-render-element.js.map +1 -1
- package/api.d.ts +0 -71
- package/api.d.ts.map +0 -1
- package/checks.d.ts +0 -189
- package/checks.d.ts.map +0 -1
- package/clsx.d.ts +0 -7
- package/clsx.d.ts.map +0 -1
- package/coerce.d.ts +0 -9
- package/coerce.d.ts.map +0 -1
- package/conversation.d.ts.map +0 -1
- package/core.d.ts +0 -35
- package/core.d.ts.map +0 -1
- package/errors.d.ts +0 -121
- package/errors.d.ts.map +0 -1
- package/errors2.d.ts +0 -24
- package/errors2.d.ts.map +0 -1
- package/index2.d.ts +0 -4
- package/index3.d.ts +0 -1
- package/metadata.d.ts +0 -1
- package/openapi-generator.d.ts +0 -1
- package/openapi-generator2.d.ts +0 -1
- package/openapi-generator3.d.ts +0 -1
- package/openapi30.d.ts +0 -125
- package/openapi30.d.ts.map +0 -1
- package/openapi31.d.ts +0 -131
- package/openapi31.d.ts.map +0 -1
- package/parse.d.ts +0 -17
- package/parse.d.ts.map +0 -1
- package/realtime-events.d.ts +0 -482
- package/realtime-events.d.ts.map +0 -1
- package/registries.d.ts +0 -32
- package/registries.d.ts.map +0 -1
- package/schemas.d.ts +0 -673
- package/schemas.d.ts.map +0 -1
- package/schemas2.d.ts +0 -320
- package/schemas2.d.ts.map +0 -1
- package/schemas3.d.ts.map +0 -1
- package/specification-extension.d.ts +0 -9
- package/specification-extension.d.ts.map +0 -1
- package/standard-schema.d.ts +0 -59
- package/standard-schema.d.ts.map +0 -1
- package/timeline-item.d.ts +0 -227
- package/timeline-item.d.ts.map +0 -1
- package/util.d.ts +0 -41
- package/util.d.ts.map +0 -1
- package/versions.d.ts +0 -9
- package/versions.d.ts.map +0 -1
- package/zod-extensions.d.ts +0 -39
- package/zod-extensions.d.ts.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"timeline-item.js","names":["React","renderProps: TimelineItemRenderProps"],"sources":["../../src/primitives/timeline-item.tsx"],"sourcesContent":["import type { TimelineItem as TimelineItemType } from \"@cossistant/types/api/timeline-item\";\nimport * as React from \"react\";\nimport ReactMarkdown from \"react-markdown\";\nimport { useRenderElement } from \"../utils/use-render-element\";\n\n/**\n * Metadata describing the origin of a timeline item and pre-parsed content that can\n * be consumed by render-prop children.\n */\nexport type TimelineItemRenderProps = {\n\tisVisitor: boolean;\n\tisAI: boolean;\n\tisHuman: boolean;\n\ttimestamp: Date;\n\ttext: string | null;\n\tsenderType: \"visitor\" | \"ai\" | \"human\";\n\titemType: \"message\" | \"event\" | \"identification\";\n};\n\nexport type TimelineItemProps = Omit<\n\tReact.HTMLAttributes<HTMLDivElement>,\n\t\"children\"\n> & {\n\tchildren?:\n\t\t| React.ReactNode\n\t\t| ((props: TimelineItemRenderProps) => React.ReactNode);\n\tasChild?: boolean;\n\tclassName?: string;\n\titem: TimelineItemType;\n};\n\n/**\n * Generic timeline item wrapper that adds accessibility attributes and resolves the\n * sender type into convenient render props for custom layouts. Works with\n * both MESSAGE and EVENT timeline item types.\n */\nexport const TimelineItem = (() => {\n\tconst Component = React.forwardRef<HTMLDivElement, TimelineItemProps>(\n\t\t({ children, className, asChild = false, item, ...props }, ref) => {\n\t\t\t// Determine sender type from timeline item properties\n\t\t\tconst isVisitor = item.visitorId !== null;\n\t\t\tconst isAI = item.aiAgentId !== null;\n\t\t\tconst isHuman = item.userId !== null && !isVisitor;\n\n\t\t\tconst senderType = isVisitor ? \"visitor\" : isAI ? \"ai\" : \"human\";\n\n\t\t\tconst renderProps: TimelineItemRenderProps = {\n\t\t\t\tisVisitor,\n\t\t\t\tisAI,\n\t\t\t\tisHuman,\n\t\t\t\ttimestamp: new Date(item.createdAt),\n\t\t\t\ttext: item.text,\n\t\t\t\tsenderType,\n\t\t\t\titemType: item.type,\n\t\t\t};\n\n\t\t\tconst content =\n\t\t\t\ttypeof children === \"function\" ? children(renderProps) : children;\n\n\t\t\tconst itemTypeLabel = (() => {\n\t\t\t\tif (item.type === \"event\") {\n\t\t\t\t\treturn \"Event\";\n\t\t\t\t}\n\t\t\t\tif (item.type === \"identification\") {\n\t\t\t\t\treturn \"Identification\";\n\t\t\t\t}\n\t\t\t\tif (isVisitor) {\n\t\t\t\t\treturn \"visitor\";\n\t\t\t\t}\n\t\t\t\tif (isAI) {\n\t\t\t\t\treturn \"AI assistant\";\n\t\t\t\t}\n\t\t\t\treturn \"human agent\";\n\t\t\t})();\n\n\t\t\treturn useRenderElement(\n\t\t\t\t\"div\",\n\t\t\t\t{\n\t\t\t\t\tclassName,\n\t\t\t\t\tasChild,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tref,\n\t\t\t\t\tstate: renderProps,\n\t\t\t\t\tprops: {\n\t\t\t\t\t\trole: \"article\",\n\t\t\t\t\t\t\"aria-label\": `${item.type === \"message\" ? \"Message\" : \"Event\"} from ${itemTypeLabel}`,\n\t\t\t\t\t\t...props,\n\t\t\t\t\t\tchildren: content,\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t);\n\t\t}\n\t);\n\n\tComponent.displayName = \"TimelineItem\";\n\treturn Component;\n})();\n\nconst MemoizedMarkdownBlock = React.memo(\n\t({ content }: { content: string }) => {\n\t\treturn (\n\t\t\t<ReactMarkdown\n\t\t\t\tcomponents={{\n\t\t\t\t\t// Customize paragraph rendering to prevent excessive spacing\n\t\t\t\t\tp: ({ children }) => <span className=\"inline\">{children}</span>,\n\t\t\t\t\t// Ensure proper line break handling\n\t\t\t\t\tbr: () => <br />,\n\t\t\t\t\t// Handle code blocks properly\n\t\t\t\t\tcode: ({ children, ...props }) => {\n\t\t\t\t\t\t// Check if it's inline code by looking at the parent element\n\t\t\t\t\t\tconst isInline = !(\n\t\t\t\t\t\t\t\"className\" in props &&\n\t\t\t\t\t\t\ttypeof props.className === \"string\" &&\n\t\t\t\t\t\t\tprops.className.includes(\"language-\")\n\t\t\t\t\t\t);\n\t\t\t\t\t\treturn isInline ? (\n\t\t\t\t\t\t\t<code className=\"rounded bg-co-background-300 px-1 py-0.5 text-xs\">\n\t\t\t\t\t\t\t\t{children}\n\t\t\t\t\t\t\t</code>\n\t\t\t\t\t\t) : (\n\t\t\t\t\t\t\t<pre className=\"overflow-x-auto rounded bg-co-background-300 p-2\">\n\t\t\t\t\t\t\t\t<code className=\"text-xs\">{children}</code>\n\t\t\t\t\t\t\t</pre>\n\t\t\t\t\t\t);\n\t\t\t\t\t},\n\t\t\t\t\t// Handle strong/bold text\n\t\t\t\t\tstrong: ({ children }) => (\n\t\t\t\t\t\t<strong className=\"font-semibold\">{children}</strong>\n\t\t\t\t\t),\n\t\t\t\t\t// Handle links\n\t\t\t\t\ta: ({ href, children }) => (\n\t\t\t\t\t\t<a\n\t\t\t\t\t\t\tclassName=\"underline hover:opacity-80\"\n\t\t\t\t\t\t\thref={href}\n\t\t\t\t\t\t\trel=\"noopener noreferrer\"\n\t\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t{children}\n\t\t\t\t\t\t</a>\n\t\t\t\t\t),\n\t\t\t\t}}\n\t\t\t>\n\t\t\t\t{content}\n\t\t\t</ReactMarkdown>\n\t\t);\n\t},\n\t(prevProps, nextProps) => {\n\t\tif (prevProps.content !== nextProps.content) {\n\t\t\treturn false;\n\t\t}\n\t\treturn true;\n\t}\n);\n\nMemoizedMarkdownBlock.displayName = \"MemoizedMarkdownBlock\";\n\nexport type TimelineItemContentProps = Omit<\n\tReact.HTMLAttributes<HTMLDivElement>,\n\t\"children\"\n> & {\n\tchildren?: React.ReactNode | ((content: string) => React.ReactNode);\n\tasChild?: boolean;\n\tclassName?: string;\n\ttext?: string | null;\n\trenderMarkdown?: boolean;\n};\n\n/**\n * Renders the content of a timeline item, optionally piping Markdown content through a\n * memoised renderer or handing the raw text to a render prop for custom\n * formatting.\n */\nexport const TimelineItemContent = (() => {\n\tconst Component = React.forwardRef<HTMLDivElement, TimelineItemContentProps>(\n\t\t(\n\t\t\t{\n\t\t\t\tchildren,\n\t\t\t\tclassName,\n\t\t\t\tasChild = false,\n\t\t\t\ttext = \"\",\n\t\t\t\trenderMarkdown = true,\n\t\t\t\t...props\n\t\t\t},\n\t\t\tref\n\t\t) => {\n\t\t\tconst content = React.useMemo(() => {\n\t\t\t\tconst textContent = text ?? \"\";\n\t\t\t\tif (typeof children === \"function\") {\n\t\t\t\t\treturn children(textContent);\n\t\t\t\t}\n\t\t\t\tif (children) {\n\t\t\t\t\treturn children;\n\t\t\t\t}\n\t\t\t\tif (renderMarkdown && textContent) {\n\t\t\t\t\treturn <MemoizedMarkdownBlock content={textContent} />;\n\t\t\t\t}\n\t\t\t\treturn textContent;\n\t\t\t}, [children, text, renderMarkdown]);\n\n\t\t\treturn useRenderElement(\n\t\t\t\t\"div\",\n\t\t\t\t{\n\t\t\t\t\tclassName,\n\t\t\t\t\tasChild,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tref,\n\t\t\t\t\tprops: {\n\t\t\t\t\t\t...props,\n\t\t\t\t\t\tchildren: content,\n\t\t\t\t\t\tstyle: {\n\t\t\t\t\t\t\t...props.style,\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}\n\t);\n\n\tComponent.displayName = \"TimelineItemContent\";\n\treturn Component;\n})();\n\nexport type TimelineItemTimestampProps = Omit<\n\tReact.HTMLAttributes<HTMLSpanElement>,\n\t\"children\"\n> & {\n\tchildren?: React.ReactNode | ((timestamp: Date) => React.ReactNode);\n\tasChild?: boolean;\n\tclassName?: string;\n\ttimestamp: Date;\n\tformat?: (date: Date) => string;\n};\n\n/**\n * Timestamp helper that renders a formatted date or allows callers to supply a\n * render prop for custom time displays while preserving semantic markup.\n */\nexport const TimelineItemTimestamp = (() => {\n\tconst Component = React.forwardRef<\n\t\tHTMLSpanElement,\n\t\tTimelineItemTimestampProps\n\t>(\n\t\t(\n\t\t\t{\n\t\t\t\tchildren,\n\t\t\t\tclassName,\n\t\t\t\tasChild = false,\n\t\t\t\ttimestamp,\n\t\t\t\tformat = (date) =>\n\t\t\t\t\tdate.toLocaleTimeString([], {\n\t\t\t\t\t\thour: \"2-digit\",\n\t\t\t\t\t\tminute: \"2-digit\",\n\t\t\t\t\t}),\n\t\t\t\t...props\n\t\t\t},\n\t\t\tref\n\t\t) => {\n\t\t\tconst content =\n\t\t\t\ttypeof children === \"function\"\n\t\t\t\t\t? children(timestamp)\n\t\t\t\t\t: children || format(timestamp);\n\n\t\t\treturn useRenderElement(\n\t\t\t\t\"span\",\n\t\t\t\t{\n\t\t\t\t\tclassName,\n\t\t\t\t\tasChild,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tref,\n\t\t\t\t\tprops: {\n\t\t\t\t\t\t...props,\n\t\t\t\t\t\tchildren: content,\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t);\n\t\t}\n\t);\n\n\tComponent.displayName = \"TimelineItemTimestamp\";\n\treturn Component;\n})();\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"timeline-item.js","names":["React","renderProps: TimelineItemRenderProps"],"sources":["../../src/primitives/timeline-item.tsx"],"sourcesContent":["import type { TimelineItem as TimelineItemType } from \"@cossistant/types/api/timeline-item\";\nimport * as React from \"react\";\nimport ReactMarkdown from \"react-markdown\";\nimport remarkBreaks from \"remark-breaks\";\nimport { useRenderElement } from \"../utils/use-render-element\";\n\n/**\n * Metadata describing the origin of a timeline item and pre-parsed content that can\n * be consumed by render-prop children.\n */\nexport type TimelineItemRenderProps = {\n\tisVisitor: boolean;\n\tisAI: boolean;\n\tisHuman: boolean;\n\ttimestamp: Date;\n\ttext: string | null;\n\tsenderType: \"visitor\" | \"ai\" | \"human\";\n\titemType: \"message\" | \"event\" | \"identification\";\n};\n\nexport type TimelineItemProps = Omit<\n\tReact.HTMLAttributes<HTMLDivElement>,\n\t\"children\"\n> & {\n\tchildren?:\n\t\t| React.ReactNode\n\t\t| ((props: TimelineItemRenderProps) => React.ReactNode);\n\tasChild?: boolean;\n\tclassName?: string;\n\titem: TimelineItemType;\n};\n\n/**\n * Generic timeline item wrapper that adds accessibility attributes and resolves the\n * sender type into convenient render props for custom layouts. Works with\n * both MESSAGE and EVENT timeline item types.\n */\nexport const TimelineItem = (() => {\n\tconst Component = React.forwardRef<HTMLDivElement, TimelineItemProps>(\n\t\t({ children, className, asChild = false, item, ...props }, ref) => {\n\t\t\t// Determine sender type from timeline item properties\n\t\t\tconst isVisitor = item.visitorId !== null;\n\t\t\tconst isAI = item.aiAgentId !== null;\n\t\t\tconst isHuman = item.userId !== null && !isVisitor;\n\n\t\t\tconst senderType = isVisitor ? \"visitor\" : isAI ? \"ai\" : \"human\";\n\n\t\t\tconst renderProps: TimelineItemRenderProps = {\n\t\t\t\tisVisitor,\n\t\t\t\tisAI,\n\t\t\t\tisHuman,\n\t\t\t\ttimestamp: new Date(item.createdAt),\n\t\t\t\ttext: item.text,\n\t\t\t\tsenderType,\n\t\t\t\titemType: item.type,\n\t\t\t};\n\n\t\t\tconst content =\n\t\t\t\ttypeof children === \"function\" ? children(renderProps) : children;\n\n\t\t\tconst itemTypeLabel = (() => {\n\t\t\t\tif (item.type === \"event\") {\n\t\t\t\t\treturn \"Event\";\n\t\t\t\t}\n\t\t\t\tif (item.type === \"identification\") {\n\t\t\t\t\treturn \"Identification\";\n\t\t\t\t}\n\t\t\t\tif (isVisitor) {\n\t\t\t\t\treturn \"visitor\";\n\t\t\t\t}\n\t\t\t\tif (isAI) {\n\t\t\t\t\treturn \"AI assistant\";\n\t\t\t\t}\n\t\t\t\treturn \"human agent\";\n\t\t\t})();\n\n\t\t\treturn useRenderElement(\n\t\t\t\t\"div\",\n\t\t\t\t{\n\t\t\t\t\tclassName,\n\t\t\t\t\tasChild,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tref,\n\t\t\t\t\tstate: renderProps,\n\t\t\t\t\tprops: {\n\t\t\t\t\t\trole: \"article\",\n\t\t\t\t\t\t\"aria-label\": `${item.type === \"message\" ? \"Message\" : \"Event\"} from ${itemTypeLabel}`,\n\t\t\t\t\t\t...props,\n\t\t\t\t\t\tchildren: content,\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t);\n\t\t}\n\t);\n\n\tComponent.displayName = \"TimelineItem\";\n\treturn Component;\n})();\n\nconst MemoizedMarkdownBlock = React.memo(\n\t({ content }: { content: string }) => {\n\t\treturn (\n\t\t\t<ReactMarkdown\n\t\t\t\tcomponents={{\n\t\t\t\t\t// Render paragraphs as block elements to preserve multiline spacing\n\t\t\t\t\tp: ({ children }) => {\n\t\t\t\t\t\t// Skip empty paragraphs (caused by consecutive blank lines in markdown)\n\t\t\t\t\t\tconst isEmpty =\n\t\t\t\t\t\t\tchildren === undefined ||\n\t\t\t\t\t\t\tchildren === null ||\n\t\t\t\t\t\t\tchildren === \"\" ||\n\t\t\t\t\t\t\t(Array.isArray(children) &&\n\t\t\t\t\t\t\t\tchildren.every((c) => c === \"\\n\" || c === \"\" || c == null));\n\t\t\t\t\t\tif (isEmpty) {\n\t\t\t\t\t\t\treturn null;\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn <span className=\"mt-1 block first:mt-0\">{children}</span>;\n\t\t\t\t\t},\n\t\t\t\t\t// Ensure proper line break handling\n\t\t\t\t\tbr: () => <br />,\n\t\t\t\t\t// Handle code blocks properly\n\t\t\t\t\tcode: ({ children, ...props }) => {\n\t\t\t\t\t\t// Check if it's inline code by looking at the parent element\n\t\t\t\t\t\tconst isInline = !(\n\t\t\t\t\t\t\t\"className\" in props &&\n\t\t\t\t\t\t\ttypeof props.className === \"string\" &&\n\t\t\t\t\t\t\tprops.className.includes(\"language-\")\n\t\t\t\t\t\t);\n\t\t\t\t\t\treturn isInline ? (\n\t\t\t\t\t\t\t<code className=\"rounded bg-co-background-300 px-1 py-0.5 text-xs\">\n\t\t\t\t\t\t\t\t{children}\n\t\t\t\t\t\t\t</code>\n\t\t\t\t\t\t) : (\n\t\t\t\t\t\t\t<pre className=\"overflow-x-auto rounded bg-co-background-300 p-2\">\n\t\t\t\t\t\t\t\t<code className=\"text-xs\">{children}</code>\n\t\t\t\t\t\t\t</pre>\n\t\t\t\t\t\t);\n\t\t\t\t\t},\n\t\t\t\t\t// Handle strong/bold text\n\t\t\t\t\tstrong: ({ children }) => (\n\t\t\t\t\t\t<strong className=\"font-semibold\">{children}</strong>\n\t\t\t\t\t),\n\t\t\t\t\t// Handle ordered lists\n\t\t\t\t\tol: ({ children }) => (\n\t\t\t\t\t\t<ol className=\"my-0 list-decimal pl-6\">{children}</ol>\n\t\t\t\t\t),\n\t\t\t\t\t// Handle unordered lists\n\t\t\t\t\tul: ({ children }) => (\n\t\t\t\t\t\t<ul className=\"my-0 list-disc pl-6\">{children}</ul>\n\t\t\t\t\t),\n\t\t\t\t\t// Handle list items\n\t\t\t\t\tli: ({ children }) => (\n\t\t\t\t\t\t<li className=\"[&>span.block]:mt-0 [&>span.block]:inline\">\n\t\t\t\t\t\t\t{children}\n\t\t\t\t\t\t</li>\n\t\t\t\t\t),\n\t\t\t\t\t// Handle blockquotes\n\t\t\t\t\tblockquote: ({ children }) => (\n\t\t\t\t\t\t<blockquote className=\"my-1 border-co-border border-l-2 pl-3 italic opacity-80\">\n\t\t\t\t\t\t\t{children}\n\t\t\t\t\t\t</blockquote>\n\t\t\t\t\t),\n\t\t\t\t\t// Handle emphasis\n\t\t\t\t\tem: ({ children }) => <em className=\"italic\">{children}</em>,\n\t\t\t\t\t// Handle links\n\t\t\t\t\ta: ({ href, children }) => (\n\t\t\t\t\t\t<a\n\t\t\t\t\t\t\tclassName=\"underline hover:opacity-80\"\n\t\t\t\t\t\t\thref={href}\n\t\t\t\t\t\t\trel=\"noopener noreferrer\"\n\t\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t{children}\n\t\t\t\t\t\t</a>\n\t\t\t\t\t),\n\t\t\t\t}}\n\t\t\t\tremarkPlugins={[remarkBreaks]}\n\t\t\t>\n\t\t\t\t{content}\n\t\t\t</ReactMarkdown>\n\t\t);\n\t},\n\t(prevProps, nextProps) => {\n\t\tif (prevProps.content !== nextProps.content) {\n\t\t\treturn false;\n\t\t}\n\t\treturn true;\n\t}\n);\n\nMemoizedMarkdownBlock.displayName = \"MemoizedMarkdownBlock\";\n\nexport type TimelineItemContentProps = Omit<\n\tReact.HTMLAttributes<HTMLDivElement>,\n\t\"children\"\n> & {\n\tchildren?: React.ReactNode | ((content: string) => React.ReactNode);\n\tasChild?: boolean;\n\tclassName?: string;\n\ttext?: string | null;\n\trenderMarkdown?: boolean;\n};\n\n/**\n * Renders the content of a timeline item, optionally piping Markdown content through a\n * memoised renderer or handing the raw text to a render prop for custom\n * formatting.\n */\nexport const TimelineItemContent = (() => {\n\tconst Component = React.forwardRef<HTMLDivElement, TimelineItemContentProps>(\n\t\t(\n\t\t\t{\n\t\t\t\tchildren,\n\t\t\t\tclassName,\n\t\t\t\tasChild = false,\n\t\t\t\ttext = \"\",\n\t\t\t\trenderMarkdown = true,\n\t\t\t\t...props\n\t\t\t},\n\t\t\tref\n\t\t) => {\n\t\t\tconst content = React.useMemo(() => {\n\t\t\t\tconst textContent = text ?? \"\";\n\t\t\t\tif (typeof children === \"function\") {\n\t\t\t\t\treturn children(textContent);\n\t\t\t\t}\n\t\t\t\tif (children) {\n\t\t\t\t\treturn children;\n\t\t\t\t}\n\t\t\t\tif (renderMarkdown && textContent) {\n\t\t\t\t\treturn <MemoizedMarkdownBlock content={textContent} />;\n\t\t\t\t}\n\t\t\t\treturn textContent;\n\t\t\t}, [children, text, renderMarkdown]);\n\n\t\t\treturn useRenderElement(\n\t\t\t\t\"div\",\n\t\t\t\t{\n\t\t\t\t\tclassName,\n\t\t\t\t\tasChild,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tref,\n\t\t\t\t\tprops: {\n\t\t\t\t\t\t...props,\n\t\t\t\t\t\tchildren: content,\n\t\t\t\t\t\tstyle: {\n\t\t\t\t\t\t\t...props.style,\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}\n\t);\n\n\tComponent.displayName = \"TimelineItemContent\";\n\treturn Component;\n})();\n\nexport type TimelineItemTimestampProps = Omit<\n\tReact.HTMLAttributes<HTMLSpanElement>,\n\t\"children\"\n> & {\n\tchildren?: React.ReactNode | ((timestamp: Date) => React.ReactNode);\n\tasChild?: boolean;\n\tclassName?: string;\n\ttimestamp: Date;\n\tformat?: (date: Date) => string;\n};\n\n/**\n * Timestamp helper that renders a formatted date or allows callers to supply a\n * render prop for custom time displays while preserving semantic markup.\n */\nexport const TimelineItemTimestamp = (() => {\n\tconst Component = React.forwardRef<\n\t\tHTMLSpanElement,\n\t\tTimelineItemTimestampProps\n\t>(\n\t\t(\n\t\t\t{\n\t\t\t\tchildren,\n\t\t\t\tclassName,\n\t\t\t\tasChild = false,\n\t\t\t\ttimestamp,\n\t\t\t\tformat = (date) =>\n\t\t\t\t\tdate.toLocaleTimeString([], {\n\t\t\t\t\t\thour: \"2-digit\",\n\t\t\t\t\t\tminute: \"2-digit\",\n\t\t\t\t\t}),\n\t\t\t\t...props\n\t\t\t},\n\t\t\tref\n\t\t) => {\n\t\t\tconst content =\n\t\t\t\ttypeof children === \"function\"\n\t\t\t\t\t? children(timestamp)\n\t\t\t\t\t: children || format(timestamp);\n\n\t\t\treturn useRenderElement(\n\t\t\t\t\"span\",\n\t\t\t\t{\n\t\t\t\t\tclassName,\n\t\t\t\t\tasChild,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tref,\n\t\t\t\t\tprops: {\n\t\t\t\t\t\t...props,\n\t\t\t\t\t\tchildren: content,\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t);\n\t\t}\n\t);\n\n\tComponent.displayName = \"TimelineItemTimestamp\";\n\treturn Component;\n})();\n"],"mappings":"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"}
|
package/primitives/trigger.js
CHANGED
|
@@ -31,7 +31,7 @@ import * as React$1 from "react";
|
|
|
31
31
|
* <MyCustomButton>Help</MyCustomButton>
|
|
32
32
|
* </Trigger>
|
|
33
33
|
*/
|
|
34
|
-
const SupportTrigger = React$1.forwardRef(({ children, className, asChild = false
|
|
34
|
+
const SupportTrigger = React$1.forwardRef(({ children, className, asChild = false, ...props }, ref) => {
|
|
35
35
|
const { isOpen, toggle } = useSupportConfig();
|
|
36
36
|
const { unreadCount, visitor } = useSupport();
|
|
37
37
|
const visitorId = visitor?.id ?? null;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"trigger.js","names":["React","renderProps: TriggerRenderProps"],"sources":["../../src/primitives/trigger.tsx"],"sourcesContent":["import * as React from \"react\";\nimport { useSupport } from \"../provider\";\nimport { useTypingStore } from \"../realtime/typing-store\";\nimport { useSupportConfig } from \"../support\";\nimport { useTriggerRef } from \"../support/context/positioning\";\nimport { useRenderElement } from \"../utils/use-render-element\";\n\n/**\n * Render props provided to the Trigger's children function.\n */\nexport type TriggerRenderProps = {\n\tisOpen: boolean;\n\tunreadCount: number;\n\tisTyping: boolean;\n\ttoggle: () => void;\n};\n\nexport type TriggerProps = Omit<\n\tReact.ButtonHTMLAttributes<HTMLButtonElement>,\n\t\"children\"\n> & {\n\t/**\n\t * Content to render inside the trigger.\n\t * Can be a ReactNode or a function that receives render props.\n\t *\n\t * @example\n\t * // Static content\n\t * <Trigger>Help</Trigger>\n\t *\n\t * @example\n\t * // Dynamic content with render props\n\t * <Trigger>\n\t * {({ isOpen, unreadCount }) => (\n\t * <span>{isOpen ? \"Close\" : `Help (${unreadCount})`}</span>\n\t * )}\n\t * </Trigger>\n\t */\n\tchildren?: React.ReactNode | ((props: TriggerRenderProps) => React.ReactNode);\n\t/**\n\t * When true, the Trigger will render its children directly,\n\t * passing all props to the child element.\n\t */\n\tasChild?: boolean;\n\tclassName?: string;\n};\n\n/**\n * Trigger button that toggles the support window.\n * Can be placed anywhere in the DOM - the window will position itself relative to this element.\n *\n * @example\n * // Simple usage\n * <Trigger className=\"my-button\">Need help?</Trigger>\n *\n * @example\n * // With render props\n * <Trigger>\n * {({ isOpen, unreadCount, isTyping }) => (\n * <button className=\"flex items-center gap-2\">\n * {isOpen ? \"×\" : \"💬\"}\n * {unreadCount > 0 && <span className=\"badge\">{unreadCount}</span>}\n * </button>\n * )}\n * </Trigger>\n *\n * @example\n * // With asChild pattern\n * <Trigger asChild>\n * <MyCustomButton>Help</MyCustomButton>\n * </Trigger>\n */\nexport const SupportTrigger = React.forwardRef<HTMLButtonElement, TriggerProps>(\n\t({ children, className, asChild = false, ...props }, ref) => {\n\t\tconst { isOpen, toggle } = useSupportConfig();\n\t\tconst { unreadCount, visitor } = useSupport();\n\t\tconst visitorId = visitor?.id ?? null;\n\t\tconst triggerRefContext = useTriggerRef();\n\n\t\t// Extract setTriggerElement for stable dependency (state setter has stable identity)\n\t\tconst setTriggerElement = triggerRefContext?.setTriggerElement;\n\n\t\t// Merge the external ref with the positioning context ref\n\t\t// Using setTriggerElement directly ensures stable ref callback identity\n\t\tconst mergedRef = React.useCallback(\n\t\t\t(element: HTMLButtonElement | null) => {\n\t\t\t\t// Set the positioning context ref\n\t\t\t\tsetTriggerElement?.(element);\n\n\t\t\t\t// Handle the forwarded ref\n\t\t\t\tif (typeof ref === \"function\") {\n\t\t\t\t\tref(element);\n\t\t\t\t} else if (ref) {\n\t\t\t\t\tref.current = element;\n\t\t\t\t}\n\t\t\t},\n\t\t\t[ref, setTriggerElement]\n\t\t);\n\n\t\tconst hasTyping = useTypingStore(\n\t\t\tReact.useCallback(\n\t\t\t\t(state) =>\n\t\t\t\t\tObject.values(state.conversations).some((entries) =>\n\t\t\t\t\t\tObject.values(entries).some((entry) => {\n\t\t\t\t\t\t\tif (\n\t\t\t\t\t\t\t\tvisitorId &&\n\t\t\t\t\t\t\t\tentry.actorType === \"visitor\" &&\n\t\t\t\t\t\t\t\tentry.actorId === visitorId\n\t\t\t\t\t\t\t) {\n\t\t\t\t\t\t\t\treturn false;\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\treturn true;\n\t\t\t\t\t\t})\n\t\t\t\t\t),\n\t\t\t\t[visitorId]\n\t\t\t)\n\t\t);\n\n\t\tconst renderProps: TriggerRenderProps = {\n\t\t\tisOpen,\n\t\t\tunreadCount,\n\t\t\tisTyping: hasTyping,\n\t\t\ttoggle,\n\t\t};\n\n\t\tconst content =\n\t\t\ttypeof children === \"function\" ? children(renderProps) : children;\n\n\t\treturn useRenderElement(\n\t\t\t\"button\",\n\t\t\t{\n\t\t\t\tasChild,\n\t\t\t\tclassName,\n\t\t\t},\n\t\t\t{\n\t\t\t\tref: mergedRef,\n\t\t\t\tstate: renderProps,\n\t\t\t\tprops: {\n\t\t\t\t\ttype: \"button\",\n\t\t\t\t\t\"aria-haspopup\": \"dialog\",\n\t\t\t\t\t\"aria-expanded\": isOpen,\n\t\t\t\t\tonClick: toggle,\n\t\t\t\t\t...props,\n\t\t\t\t\tchildren: content,\n\t\t\t\t},\n\t\t\t}\n\t\t);\n\t}\n);\n\nSupportTrigger.displayName = \"SupportTrigger\";\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAuEA,MAAa,iBAAiBA,QAAM,YAClC,EAAE,UAAU,WAAW,UAAU,
|
|
1
|
+
{"version":3,"file":"trigger.js","names":["React","renderProps: TriggerRenderProps"],"sources":["../../src/primitives/trigger.tsx"],"sourcesContent":["import * as React from \"react\";\nimport { useSupport } from \"../provider\";\nimport { useTypingStore } from \"../realtime/typing-store\";\nimport { useSupportConfig } from \"../support\";\nimport { useTriggerRef } from \"../support/context/positioning\";\nimport { useRenderElement } from \"../utils/use-render-element\";\n\n/**\n * Render props provided to the Trigger's children function.\n */\nexport type TriggerRenderProps = {\n\tisOpen: boolean;\n\tunreadCount: number;\n\tisTyping: boolean;\n\ttoggle: () => void;\n};\n\nexport type TriggerProps = Omit<\n\tReact.ButtonHTMLAttributes<HTMLButtonElement>,\n\t\"children\"\n> & {\n\t/**\n\t * Content to render inside the trigger.\n\t * Can be a ReactNode or a function that receives render props.\n\t *\n\t * @example\n\t * // Static content\n\t * <Trigger>Help</Trigger>\n\t *\n\t * @example\n\t * // Dynamic content with render props\n\t * <Trigger>\n\t * {({ isOpen, unreadCount }) => (\n\t * <span>{isOpen ? \"Close\" : `Help (${unreadCount})`}</span>\n\t * )}\n\t * </Trigger>\n\t */\n\tchildren?: React.ReactNode | ((props: TriggerRenderProps) => React.ReactNode);\n\t/**\n\t * When true, the Trigger will render its children directly,\n\t * passing all props to the child element.\n\t */\n\tasChild?: boolean;\n\tclassName?: string;\n};\n\n/**\n * Trigger button that toggles the support window.\n * Can be placed anywhere in the DOM - the window will position itself relative to this element.\n *\n * @example\n * // Simple usage\n * <Trigger className=\"my-button\">Need help?</Trigger>\n *\n * @example\n * // With render props\n * <Trigger>\n * {({ isOpen, unreadCount, isTyping }) => (\n * <button className=\"flex items-center gap-2\">\n * {isOpen ? \"×\" : \"💬\"}\n * {unreadCount > 0 && <span className=\"badge\">{unreadCount}</span>}\n * </button>\n * )}\n * </Trigger>\n *\n * @example\n * // With asChild pattern\n * <Trigger asChild>\n * <MyCustomButton>Help</MyCustomButton>\n * </Trigger>\n */\nexport const SupportTrigger = React.forwardRef<HTMLButtonElement, TriggerProps>(\n\t({ children, className, asChild = false, ...props }, ref) => {\n\t\tconst { isOpen, toggle } = useSupportConfig();\n\t\tconst { unreadCount, visitor } = useSupport();\n\t\tconst visitorId = visitor?.id ?? null;\n\t\tconst triggerRefContext = useTriggerRef();\n\n\t\t// Extract setTriggerElement for stable dependency (state setter has stable identity)\n\t\tconst setTriggerElement = triggerRefContext?.setTriggerElement;\n\n\t\t// Merge the external ref with the positioning context ref\n\t\t// Using setTriggerElement directly ensures stable ref callback identity\n\t\tconst mergedRef = React.useCallback(\n\t\t\t(element: HTMLButtonElement | null) => {\n\t\t\t\t// Set the positioning context ref\n\t\t\t\tsetTriggerElement?.(element);\n\n\t\t\t\t// Handle the forwarded ref\n\t\t\t\tif (typeof ref === \"function\") {\n\t\t\t\t\tref(element);\n\t\t\t\t} else if (ref) {\n\t\t\t\t\tref.current = element;\n\t\t\t\t}\n\t\t\t},\n\t\t\t[ref, setTriggerElement]\n\t\t);\n\n\t\tconst hasTyping = useTypingStore(\n\t\t\tReact.useCallback(\n\t\t\t\t(state) =>\n\t\t\t\t\tObject.values(state.conversations).some((entries) =>\n\t\t\t\t\t\tObject.values(entries).some((entry) => {\n\t\t\t\t\t\t\tif (\n\t\t\t\t\t\t\t\tvisitorId &&\n\t\t\t\t\t\t\t\tentry.actorType === \"visitor\" &&\n\t\t\t\t\t\t\t\tentry.actorId === visitorId\n\t\t\t\t\t\t\t) {\n\t\t\t\t\t\t\t\treturn false;\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\treturn true;\n\t\t\t\t\t\t})\n\t\t\t\t\t),\n\t\t\t\t[visitorId]\n\t\t\t)\n\t\t);\n\n\t\tconst renderProps: TriggerRenderProps = {\n\t\t\tisOpen,\n\t\t\tunreadCount,\n\t\t\tisTyping: hasTyping,\n\t\t\ttoggle,\n\t\t};\n\n\t\tconst content =\n\t\t\ttypeof children === \"function\" ? children(renderProps) : children;\n\n\t\treturn useRenderElement(\n\t\t\t\"button\",\n\t\t\t{\n\t\t\t\tasChild,\n\t\t\t\tclassName,\n\t\t\t},\n\t\t\t{\n\t\t\t\tref: mergedRef,\n\t\t\t\tstate: renderProps,\n\t\t\t\tprops: {\n\t\t\t\t\ttype: \"button\",\n\t\t\t\t\t\"aria-haspopup\": \"dialog\",\n\t\t\t\t\t\"aria-expanded\": isOpen,\n\t\t\t\t\tonClick: toggle,\n\t\t\t\t\t...props,\n\t\t\t\t\tchildren: content,\n\t\t\t\t},\n\t\t\t}\n\t\t);\n\t}\n);\n\nSupportTrigger.displayName = \"SupportTrigger\";\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAuEA,MAAa,iBAAiBA,QAAM,YAClC,EAAE,UAAU,WAAW,UAAU,OAAO,GAAG,SAAS,QAAQ;CAC5D,MAAM,EAAE,QAAQ,WAAW,kBAAkB;CAC7C,MAAM,EAAE,aAAa,YAAY,YAAY;CAC7C,MAAM,YAAY,SAAS,MAAM;CAIjC,MAAM,oBAHoB,eAAe,EAGI;CAI7C,MAAM,YAAYA,QAAM,aACtB,YAAsC;AAEtC,sBAAoB,QAAQ;AAG5B,MAAI,OAAO,QAAQ,WAClB,KAAI,QAAQ;WACF,IACV,KAAI,UAAU;IAGhB,CAAC,KAAK,kBAAkB,CACxB;CAsBD,MAAMC,cAAkC;EACvC;EACA;EACA,UAvBiB,eACjBD,QAAM,aACJ,UACA,OAAO,OAAO,MAAM,cAAc,CAAC,MAAM,YACxC,OAAO,OAAO,QAAQ,CAAC,MAAM,UAAU;AACtC,OACC,aACA,MAAM,cAAc,aACpB,MAAM,YAAY,UAElB,QAAO;AAGR,UAAO;IACN,CACF,EACF,CAAC,UAAU,CACX,CACD;EAMA;EACA;CAED,MAAM,UACL,OAAO,aAAa,aAAa,SAAS,YAAY,GAAG;AAE1D,QAAO,iBACN,UACA;EACC;EACA;EACA,EACD;EACC,KAAK;EACL,OAAO;EACP,OAAO;GACN,MAAM;GACN,iBAAiB;GACjB,iBAAiB;GACjB,SAAS;GACT,GAAG;GACH,UAAU;GACV;EACD,CACD;EAEF;AAED,eAAe,cAAc"}
|
package/primitives/window.js
CHANGED
|
@@ -42,7 +42,7 @@ function getFocusableElements(container) {
|
|
|
42
42
|
* </Window>
|
|
43
43
|
*/
|
|
44
44
|
const SupportWindow = (() => {
|
|
45
|
-
const Component = React$1.forwardRef(({ isOpen: isOpenProp, onOpenChange, children, className, asChild = false, closeOnEscape = true, trapFocus = true, restoreFocus = true, id = "cossistant-window"
|
|
45
|
+
const Component = React$1.forwardRef(({ isOpen: isOpenProp, onOpenChange, children, className, asChild = false, closeOnEscape = true, trapFocus = true, restoreFocus = true, id = "cossistant-window", ...props }, ref) => {
|
|
46
46
|
const { isOpen, close } = useSupportConfig();
|
|
47
47
|
const containerRef = React$1.useRef(null);
|
|
48
48
|
const previouslyFocusedRef = React$1.useRef(null);
|
package/primitives/window.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"window.js","names":["React","renderProps: WindowRenderProps"],"sources":["../../src/primitives/window.tsx"],"sourcesContent":["import * as React from \"react\";\nimport { useSupportConfig } from \"../support/store/support-store\";\nimport { useRenderElement } from \"../utils/use-render-element\";\n\nexport type WindowRenderProps = {\n\tisOpen: boolean;\n\tclose: () => void;\n};\n\nexport type WindowProps = Omit<\n\tReact.HTMLAttributes<HTMLDivElement>,\n\t\"children\"\n> & {\n\tisOpen?: boolean;\n\tonOpenChange?: (open: boolean) => void;\n\tchildren?: React.ReactNode | ((props: WindowRenderProps) => React.ReactNode);\n\tasChild?: boolean;\n\tcloseOnEscape?: boolean;\n\t/**\n\t * Whether to trap focus within the dialog when open.\n\t * @default true\n\t */\n\ttrapFocus?: boolean;\n\t/**\n\t * Whether to restore focus to the previously focused element when closing.\n\t * @default true\n\t */\n\trestoreFocus?: boolean;\n\tid?: string;\n};\n\n/**\n * Selector for focusable elements within a container\n */\nconst FOCUSABLE_SELECTOR = [\n\t\"a[href]\",\n\t\"area[href]\",\n\t\"input:not([disabled])\",\n\t\"select:not([disabled])\",\n\t\"textarea:not([disabled])\",\n\t\"button:not([disabled])\",\n\t\"iframe\",\n\t\"object\",\n\t\"embed\",\n\t\"[tabindex]:not([tabindex='-1'])\",\n\t\"[contenteditable]\",\n\t\"audio[controls]\",\n\t\"video[controls]\",\n].join(\",\");\n\n/**\n * Get all focusable elements within a container\n */\nfunction getFocusableElements(container: HTMLElement): HTMLElement[] {\n\treturn Array.from(\n\t\tcontainer.querySelectorAll<HTMLElement>(FOCUSABLE_SELECTOR)\n\t).filter((el) => {\n\t\t// Check visibility\n\t\tconst style = window.getComputedStyle(el);\n\t\treturn style.display !== \"none\" && style.visibility !== \"hidden\";\n\t});\n}\n\n/**\n * Dialog container with open/close state, escape key handling,\n * focus trap, and focus restoration.\n *\n * @example\n * <Window isOpen={isOpen} onOpenChange={setOpen}>\n * {({ isOpen, close }) => (\n * <div>Content here</div>\n * )}\n * </Window>\n */\nexport const SupportWindow = (() => {\n\tconst Component = React.forwardRef<HTMLDivElement, WindowProps>(\n\t\t(\n\t\t\t{\n\t\t\t\tisOpen: isOpenProp,\n\t\t\t\tonOpenChange,\n\t\t\t\tchildren,\n\t\t\t\tclassName,\n\t\t\t\tasChild = false,\n\t\t\t\tcloseOnEscape = true,\n\t\t\t\ttrapFocus = true,\n\t\t\t\trestoreFocus = true,\n\t\t\t\tid = \"cossistant-window\",\n\t\t\t\t...props\n\t\t\t},\n\t\t\tref\n\t\t) => {\n\t\t\tconst { isOpen, close } = useSupportConfig();\n\t\t\tconst containerRef = React.useRef<HTMLDivElement>(null);\n\t\t\tconst previouslyFocusedRef = React.useRef<HTMLElement | null>(null);\n\n\t\t\tconst open = isOpenProp ?? isOpen ?? false;\n\n\t\t\tconst closeFn = React.useCallback(() => {\n\t\t\t\tif (onOpenChange) {\n\t\t\t\t\tonOpenChange(false);\n\t\t\t\t} else if (close) {\n\t\t\t\t\tclose();\n\t\t\t\t}\n\t\t\t}, [onOpenChange, close]);\n\n\t\t\t// Store previously focused element and focus first element when opening\n\t\t\tReact.useEffect(() => {\n\t\t\t\tif (open) {\n\t\t\t\t\t// Store the currently focused element\n\t\t\t\t\tpreviouslyFocusedRef.current = document.activeElement as HTMLElement;\n\n\t\t\t\t\t// Focus the first focusable element after a short delay\n\t\t\t\t\t// to allow the DOM to render\n\t\t\t\t\tconst timer = setTimeout(() => {\n\t\t\t\t\t\tconst container = containerRef.current;\n\t\t\t\t\t\tif (container) {\n\t\t\t\t\t\t\tconst focusable = getFocusableElements(container);\n\t\t\t\t\t\t\tconst firstElement = focusable[0];\n\t\t\t\t\t\t\tif (firstElement) {\n\t\t\t\t\t\t\t\tfirstElement.focus();\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t// If no focusable elements, focus the container itself\n\t\t\t\t\t\t\t\tcontainer.focus();\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}, 50);\n\n\t\t\t\t\treturn () => clearTimeout(timer);\n\t\t\t\t}\n\t\t\t\t// Restore focus when closing\n\t\t\t\tif (!open && restoreFocus && previouslyFocusedRef.current) {\n\t\t\t\t\tpreviouslyFocusedRef.current.focus();\n\t\t\t\t\tpreviouslyFocusedRef.current = null;\n\t\t\t\t}\n\t\t\t}, [open, restoreFocus]);\n\n\t\t\t// Close on Escape\n\t\t\tReact.useEffect(() => {\n\t\t\t\tif (!(open && closeOnEscape)) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst onKey = (e: KeyboardEvent) => {\n\t\t\t\t\tif (e.key === \"Escape\") {\n\t\t\t\t\t\tcloseFn();\n\t\t\t\t\t}\n\t\t\t\t};\n\t\t\t\twindow.addEventListener(\"keydown\", onKey);\n\t\t\t\treturn () => window.removeEventListener(\"keydown\", onKey);\n\t\t\t}, [open, closeFn, closeOnEscape]);\n\n\t\t\t// Focus trap - trap Tab and Shift+Tab within the dialog\n\t\t\tReact.useEffect(() => {\n\t\t\t\tif (!(open && trapFocus)) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tconst container = containerRef.current;\n\t\t\t\tif (!container) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tconst handleKeyDown = (e: KeyboardEvent) => {\n\t\t\t\t\tif (e.key !== \"Tab\") {\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\tconst focusable = getFocusableElements(container);\n\t\t\t\t\tif (focusable.length === 0) {\n\t\t\t\t\t\te.preventDefault();\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\tconst first = focusable[0];\n\t\t\t\t\tconst last = focusable.at(-1);\n\t\t\t\t\tconst active = document.activeElement;\n\n\t\t\t\t\t// Shift+Tab from first element wraps to last\n\t\t\t\t\tif (e.shiftKey && active === first) {\n\t\t\t\t\t\te.preventDefault();\n\t\t\t\t\t\tlast?.focus();\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\t// Tab from last element wraps to first\n\t\t\t\t\tif (!e.shiftKey && active === last) {\n\t\t\t\t\t\te.preventDefault();\n\t\t\t\t\t\tfirst?.focus();\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\t// If focus is outside the container, bring it back\n\t\t\t\t\tif (!container.contains(active)) {\n\t\t\t\t\t\te.preventDefault();\n\t\t\t\t\t\tfirst?.focus();\n\t\t\t\t\t}\n\t\t\t\t};\n\n\t\t\t\tdocument.addEventListener(\"keydown\", handleKeyDown);\n\t\t\t\treturn () => document.removeEventListener(\"keydown\", handleKeyDown);\n\t\t\t}, [open, trapFocus]);\n\n\t\t\t// Merge refs\n\t\t\tconst mergedRef = React.useCallback(\n\t\t\t\t(node: HTMLDivElement | null) => {\n\t\t\t\t\tcontainerRef.current = node;\n\t\t\t\t\tif (typeof ref === \"function\") {\n\t\t\t\t\t\tref(node);\n\t\t\t\t\t} else if (ref) {\n\t\t\t\t\t\tref.current = node;\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t[ref]\n\t\t\t);\n\n\t\t\tconst renderProps: WindowRenderProps = { isOpen: open, close: closeFn };\n\n\t\t\tconst content =\n\t\t\t\ttypeof children === \"function\" ? children(renderProps) : children;\n\n\t\t\treturn useRenderElement(\n\t\t\t\t\"div\",\n\t\t\t\t{\n\t\t\t\t\tclassName,\n\t\t\t\t\tasChild,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tref: mergedRef,\n\t\t\t\t\tstate: renderProps,\n\t\t\t\t\tprops: {\n\t\t\t\t\t\trole: \"dialog\",\n\t\t\t\t\t\t\"aria-modal\": \"true\",\n\t\t\t\t\t\tid,\n\t\t\t\t\t\ttabIndex: -1, // Allow container to receive focus\n\t\t\t\t\t\t...props,\n\t\t\t\t\t\tchildren: content,\n\t\t\t\t\t},\n\t\t\t\t\tenabled: open,\n\t\t\t\t}\n\t\t\t);\n\t\t}\n\t);\n\n\tComponent.displayName = \"SupportWindow\";\n\treturn Component;\n})();\n"],"mappings":";;;;;;;;AAkCA,MAAM,qBAAqB;CAC1B;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA,CAAC,KAAK,IAAI;;;;AAKX,SAAS,qBAAqB,WAAuC;AACpE,QAAO,MAAM,KACZ,UAAU,iBAA8B,mBAAmB,CAC3D,CAAC,QAAQ,OAAO;EAEhB,MAAM,QAAQ,OAAO,iBAAiB,GAAG;AACzC,SAAO,MAAM,YAAY,UAAU,MAAM,eAAe;GACvD;;;;;;;;;;;;;AAcH,MAAa,uBAAuB;CACnC,MAAM,YAAYA,QAAM,YAEtB,EACC,QAAQ,YACR,cACA,UACA,WACA,UAAU,OACV,gBAAgB,MAChB,YAAY,MACZ,eAAe,MACf,KAAK,
|
|
1
|
+
{"version":3,"file":"window.js","names":["React","renderProps: WindowRenderProps"],"sources":["../../src/primitives/window.tsx"],"sourcesContent":["import * as React from \"react\";\nimport { useSupportConfig } from \"../support/store/support-store\";\nimport { useRenderElement } from \"../utils/use-render-element\";\n\nexport type WindowRenderProps = {\n\tisOpen: boolean;\n\tclose: () => void;\n};\n\nexport type WindowProps = Omit<\n\tReact.HTMLAttributes<HTMLDivElement>,\n\t\"children\"\n> & {\n\tisOpen?: boolean;\n\tonOpenChange?: (open: boolean) => void;\n\tchildren?: React.ReactNode | ((props: WindowRenderProps) => React.ReactNode);\n\tasChild?: boolean;\n\tcloseOnEscape?: boolean;\n\t/**\n\t * Whether to trap focus within the dialog when open.\n\t * @default true\n\t */\n\ttrapFocus?: boolean;\n\t/**\n\t * Whether to restore focus to the previously focused element when closing.\n\t * @default true\n\t */\n\trestoreFocus?: boolean;\n\tid?: string;\n};\n\n/**\n * Selector for focusable elements within a container\n */\nconst FOCUSABLE_SELECTOR = [\n\t\"a[href]\",\n\t\"area[href]\",\n\t\"input:not([disabled])\",\n\t\"select:not([disabled])\",\n\t\"textarea:not([disabled])\",\n\t\"button:not([disabled])\",\n\t\"iframe\",\n\t\"object\",\n\t\"embed\",\n\t\"[tabindex]:not([tabindex='-1'])\",\n\t\"[contenteditable]\",\n\t\"audio[controls]\",\n\t\"video[controls]\",\n].join(\",\");\n\n/**\n * Get all focusable elements within a container\n */\nfunction getFocusableElements(container: HTMLElement): HTMLElement[] {\n\treturn Array.from(\n\t\tcontainer.querySelectorAll<HTMLElement>(FOCUSABLE_SELECTOR)\n\t).filter((el) => {\n\t\t// Check visibility\n\t\tconst style = window.getComputedStyle(el);\n\t\treturn style.display !== \"none\" && style.visibility !== \"hidden\";\n\t});\n}\n\n/**\n * Dialog container with open/close state, escape key handling,\n * focus trap, and focus restoration.\n *\n * @example\n * <Window isOpen={isOpen} onOpenChange={setOpen}>\n * {({ isOpen, close }) => (\n * <div>Content here</div>\n * )}\n * </Window>\n */\nexport const SupportWindow = (() => {\n\tconst Component = React.forwardRef<HTMLDivElement, WindowProps>(\n\t\t(\n\t\t\t{\n\t\t\t\tisOpen: isOpenProp,\n\t\t\t\tonOpenChange,\n\t\t\t\tchildren,\n\t\t\t\tclassName,\n\t\t\t\tasChild = false,\n\t\t\t\tcloseOnEscape = true,\n\t\t\t\ttrapFocus = true,\n\t\t\t\trestoreFocus = true,\n\t\t\t\tid = \"cossistant-window\",\n\t\t\t\t...props\n\t\t\t},\n\t\t\tref\n\t\t) => {\n\t\t\tconst { isOpen, close } = useSupportConfig();\n\t\t\tconst containerRef = React.useRef<HTMLDivElement>(null);\n\t\t\tconst previouslyFocusedRef = React.useRef<HTMLElement | null>(null);\n\n\t\t\tconst open = isOpenProp ?? isOpen ?? false;\n\n\t\t\tconst closeFn = React.useCallback(() => {\n\t\t\t\tif (onOpenChange) {\n\t\t\t\t\tonOpenChange(false);\n\t\t\t\t} else if (close) {\n\t\t\t\t\tclose();\n\t\t\t\t}\n\t\t\t}, [onOpenChange, close]);\n\n\t\t\t// Store previously focused element and focus first element when opening\n\t\t\tReact.useEffect(() => {\n\t\t\t\tif (open) {\n\t\t\t\t\t// Store the currently focused element\n\t\t\t\t\tpreviouslyFocusedRef.current = document.activeElement as HTMLElement;\n\n\t\t\t\t\t// Focus the first focusable element after a short delay\n\t\t\t\t\t// to allow the DOM to render\n\t\t\t\t\tconst timer = setTimeout(() => {\n\t\t\t\t\t\tconst container = containerRef.current;\n\t\t\t\t\t\tif (container) {\n\t\t\t\t\t\t\tconst focusable = getFocusableElements(container);\n\t\t\t\t\t\t\tconst firstElement = focusable[0];\n\t\t\t\t\t\t\tif (firstElement) {\n\t\t\t\t\t\t\t\tfirstElement.focus();\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t// If no focusable elements, focus the container itself\n\t\t\t\t\t\t\t\tcontainer.focus();\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}, 50);\n\n\t\t\t\t\treturn () => clearTimeout(timer);\n\t\t\t\t}\n\t\t\t\t// Restore focus when closing\n\t\t\t\tif (!open && restoreFocus && previouslyFocusedRef.current) {\n\t\t\t\t\tpreviouslyFocusedRef.current.focus();\n\t\t\t\t\tpreviouslyFocusedRef.current = null;\n\t\t\t\t}\n\t\t\t}, [open, restoreFocus]);\n\n\t\t\t// Close on Escape\n\t\t\tReact.useEffect(() => {\n\t\t\t\tif (!(open && closeOnEscape)) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst onKey = (e: KeyboardEvent) => {\n\t\t\t\t\tif (e.key === \"Escape\") {\n\t\t\t\t\t\tcloseFn();\n\t\t\t\t\t}\n\t\t\t\t};\n\t\t\t\twindow.addEventListener(\"keydown\", onKey);\n\t\t\t\treturn () => window.removeEventListener(\"keydown\", onKey);\n\t\t\t}, [open, closeFn, closeOnEscape]);\n\n\t\t\t// Focus trap - trap Tab and Shift+Tab within the dialog\n\t\t\tReact.useEffect(() => {\n\t\t\t\tif (!(open && trapFocus)) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tconst container = containerRef.current;\n\t\t\t\tif (!container) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tconst handleKeyDown = (e: KeyboardEvent) => {\n\t\t\t\t\tif (e.key !== \"Tab\") {\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\tconst focusable = getFocusableElements(container);\n\t\t\t\t\tif (focusable.length === 0) {\n\t\t\t\t\t\te.preventDefault();\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\tconst first = focusable[0];\n\t\t\t\t\tconst last = focusable.at(-1);\n\t\t\t\t\tconst active = document.activeElement;\n\n\t\t\t\t\t// Shift+Tab from first element wraps to last\n\t\t\t\t\tif (e.shiftKey && active === first) {\n\t\t\t\t\t\te.preventDefault();\n\t\t\t\t\t\tlast?.focus();\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\t// Tab from last element wraps to first\n\t\t\t\t\tif (!e.shiftKey && active === last) {\n\t\t\t\t\t\te.preventDefault();\n\t\t\t\t\t\tfirst?.focus();\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\t// If focus is outside the container, bring it back\n\t\t\t\t\tif (!container.contains(active)) {\n\t\t\t\t\t\te.preventDefault();\n\t\t\t\t\t\tfirst?.focus();\n\t\t\t\t\t}\n\t\t\t\t};\n\n\t\t\t\tdocument.addEventListener(\"keydown\", handleKeyDown);\n\t\t\t\treturn () => document.removeEventListener(\"keydown\", handleKeyDown);\n\t\t\t}, [open, trapFocus]);\n\n\t\t\t// Merge refs\n\t\t\tconst mergedRef = React.useCallback(\n\t\t\t\t(node: HTMLDivElement | null) => {\n\t\t\t\t\tcontainerRef.current = node;\n\t\t\t\t\tif (typeof ref === \"function\") {\n\t\t\t\t\t\tref(node);\n\t\t\t\t\t} else if (ref) {\n\t\t\t\t\t\tref.current = node;\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t[ref]\n\t\t\t);\n\n\t\t\tconst renderProps: WindowRenderProps = { isOpen: open, close: closeFn };\n\n\t\t\tconst content =\n\t\t\t\ttypeof children === \"function\" ? children(renderProps) : children;\n\n\t\t\treturn useRenderElement(\n\t\t\t\t\"div\",\n\t\t\t\t{\n\t\t\t\t\tclassName,\n\t\t\t\t\tasChild,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tref: mergedRef,\n\t\t\t\t\tstate: renderProps,\n\t\t\t\t\tprops: {\n\t\t\t\t\t\trole: \"dialog\",\n\t\t\t\t\t\t\"aria-modal\": \"true\",\n\t\t\t\t\t\tid,\n\t\t\t\t\t\ttabIndex: -1, // Allow container to receive focus\n\t\t\t\t\t\t...props,\n\t\t\t\t\t\tchildren: content,\n\t\t\t\t\t},\n\t\t\t\t\tenabled: open,\n\t\t\t\t}\n\t\t\t);\n\t\t}\n\t);\n\n\tComponent.displayName = \"SupportWindow\";\n\treturn Component;\n})();\n"],"mappings":"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"}
|
package/provider.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ConfigurationError } from "./hooks/private/use-rest-client.js";
|
|
1
2
|
import React from "react";
|
|
2
3
|
import { CossistantClient } from "@cossistant/core";
|
|
3
4
|
import { DefaultMessage, PublicWebsiteResponse } from "@cossistant/types";
|
|
@@ -28,7 +29,8 @@ type CossistantContextValue = {
|
|
|
28
29
|
setUnreadCount: (count: number) => void;
|
|
29
30
|
isLoading: boolean;
|
|
30
31
|
error: Error | null;
|
|
31
|
-
|
|
32
|
+
configurationError: ConfigurationError | null;
|
|
33
|
+
client: CossistantClient | null;
|
|
32
34
|
isOpen: boolean;
|
|
33
35
|
open: () => void;
|
|
34
36
|
close: () => void;
|
|
@@ -71,5 +73,5 @@ declare function SupportProvider({
|
|
|
71
73
|
*/
|
|
72
74
|
declare function useSupport(): UseSupportValue;
|
|
73
75
|
//#endregion
|
|
74
|
-
export { CossistantContextValue, CossistantProviderProps, SupportContext, SupportProvider, SupportProviderProps, UseSupportValue, useSupport };
|
|
76
|
+
export { type ConfigurationError, CossistantContextValue, CossistantProviderProps, SupportContext, SupportProvider, SupportProviderProps, UseSupportValue, useSupport };
|
|
75
77
|
//# sourceMappingURL=provider.d.ts.map
|
package/provider.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"provider.d.ts","names":[],"sources":["../src/provider.tsx"],"sourcesContent":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"provider.d.ts","names":[],"sources":["../src/provider.tsx"],"sourcesContent":[],"mappings":";;;;;;KAsEY,oBAAA;YACD,KAAA,CAAM;EADL,WAAA,CAAA,EAAA,OAAA;EACD,MAAM,CAAA,EAAA,MAAA;EAKE,KAAA,CAAA,EAAA,MAAA;EAKE,SAAA,CAAA,EAAA,MAAA;EAAK,eAAA,CAAA,EALP,cAKO,EAAA;EAId,YAAA,CAAA,EAAA,MAAA,EAAA;EAEA,WAAA,CAAA,EAAA,OAAA;EACF,WAAA,CAAA,EAAA,GAAA,GAAA,IAAA;EACQ,cAAA,CAAA,EAAA,GAAA,GAAA,IAAA;EAEc,SAAA,CAAA,EAAA,CAAA,KAAA,EAVX,KAUW,EAAA,GAAA,IAAA;EAKxB,IAAA,CAAA,EAAA,QAAA,GAAA,QAAA;CACa;AACZ,KAbG,uBAAA,GAA0B,oBAa7B;AAAgB,KAXb,sBAAA,GAWa;EAOpB,OAAA,EAjBK,qBAiBqB,GAAA,IAAA;EAE1B,eAAA,EAlBa,cAkBI,EAAA;EAAG,YAAA,EAAA,MAAA,EAAA;EAEV,kBAAA,EAAA,CAAA,QAAA,EAlBiB,cAkBjB,EAAA,EAAA,GAAA,IAAA;EAAZ,eAAA,EAAA,CAAA,OAAA,EAAA,MAAA,EAAA,EAAA,GAAA,IAAA;EAAW,WAAA,EAAA,MAAA;EA6CF,cAAA,EAAA,CAAA,KAAe,EAAA,MAAA,EAAA,GAAA,IAAA;EAAG,SAAA,EAAA,OAAA;EACK,KAAA,EA3D3B,KA2D2B,GAAA,IAAA;EAAZ,kBAAA,EA1DF,kBA0DE,GAAA,IAAA;EACS,MAAA,EA1DvB,gBA0DuB,GAAA,IAAA;EAAZ,MAAA,EAAA,OAAA;EACT,IAAA,EAAA,GAAA,GAAA,IAAA;EAAiB,KAAA,EAAA,GAAA,GAAA,IAAA;EAIf,MAAA,EAAA,GAAA,GAAA,IAED;AAqVZ,CAAA;KA/YK,WAAA,GAAc,WAgZlB,CAhZ8B,sBAgZ9B,CAAA,SAAA,CAAA,CAAA;KA9YI,iBAAA,GAAoB,WA+YxB,CAAA,SAAA,CAAA,SAAA,IAAA,GAAA,SAAA,GAAA,SAAA,GA7YE,WA6YF,CA7Yc,WA6Yd,CAAA,SAAA,CAAA,CAAA,GAAA;EACA,MAAA,EAAA,MAAA,GAAA,IAAA;CACA;AACA,KAnWW,eAAA,GAAkB,sBAmW7B,GAAA;EACA,oBAAA,EAnWsB,WAmWtB,CAnWkC,WAmWlC,CAAA,sBAAA,CAAA,CAAA,GAAA,EAAA;EACA,iBAAA,EAnWmB,WAmWnB,CAnW+B,WAmW/B,CAAA,mBAAA,CAAA,CAAA,GAAA,EAAA;EACA,OAAA,CAAA,EAnWU,iBAmWV;EACA,IAAA,EAAA,QAAA,GAAA,QAAA;CACA;AACA,cAlWY,cAkWZ,EAlW0B,KAAA,CAAA,OAkW1B,CAlW0B,sBAkW1B,GAAA,SAAA,CAAA;;;;;AA0BD;;iBArCgB,eAAA;;;;;;;;;;;;;GAab,uBAAuB,KAAA,CAAM;;;;;iBAwBhB,UAAA,CAAA,GAAc"}
|
package/provider.js
CHANGED
|
@@ -3,13 +3,45 @@ import { useWebsiteStore } from "./hooks/private/store/use-website-store.js";
|
|
|
3
3
|
import { useClient } from "./hooks/private/use-rest-client.js";
|
|
4
4
|
import { useSeenStore } from "./realtime/seen-store.js";
|
|
5
5
|
import { initializeSupportStore, useSupportStore } from "./support/store/support-store.js";
|
|
6
|
+
import { IdentificationProvider } from "./support/context/identification.js";
|
|
6
7
|
import { WebSocketProvider } from "./support/context/websocket.js";
|
|
7
8
|
import React from "react";
|
|
8
|
-
import { normalizeLocale } from "@cossistant/core";
|
|
9
|
+
import { CossistantAPIError, normalizeLocale } from "@cossistant/core";
|
|
9
10
|
import { ConversationTimelineType } from "@cossistant/types/enums";
|
|
10
11
|
import { jsx } from "react/jsx-runtime";
|
|
11
12
|
|
|
12
13
|
//#region src/provider.tsx
|
|
14
|
+
/**
|
|
15
|
+
* Auth-related error codes that indicate API key issues.
|
|
16
|
+
*/
|
|
17
|
+
const AUTH_ERROR_CODES = new Set([
|
|
18
|
+
"UNAUTHORIZED",
|
|
19
|
+
"FORBIDDEN",
|
|
20
|
+
"INVALID_API_KEY",
|
|
21
|
+
"API_KEY_EXPIRED",
|
|
22
|
+
"API_KEY_MISSING",
|
|
23
|
+
"HTTP_401",
|
|
24
|
+
"HTTP_403"
|
|
25
|
+
]);
|
|
26
|
+
/**
|
|
27
|
+
* Check if an error is an authentication/authorization error.
|
|
28
|
+
*/
|
|
29
|
+
function isAuthError(error) {
|
|
30
|
+
if (!error) return false;
|
|
31
|
+
if (error instanceof CossistantAPIError) {
|
|
32
|
+
const code = error.code?.toUpperCase() ?? "";
|
|
33
|
+
return AUTH_ERROR_CODES.has(code) || code.includes("AUTH") || code.includes("API_KEY");
|
|
34
|
+
}
|
|
35
|
+
const message = error.message?.toLowerCase() ?? "";
|
|
36
|
+
return message.includes("api key") || message.includes("unauthorized") || message.includes("forbidden") || message.includes("not authorized");
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Detect if running in a Next.js environment.
|
|
40
|
+
*/
|
|
41
|
+
function isNextJSEnvironment() {
|
|
42
|
+
if (typeof window !== "undefined") return "__NEXT_DATA__" in window;
|
|
43
|
+
return typeof process !== "undefined" && "__NEXT_RUNTIME" in process.env;
|
|
44
|
+
}
|
|
13
45
|
function areConversationSnapshotsEqual(a, b) {
|
|
14
46
|
if (a === b) return true;
|
|
15
47
|
if (a.length !== b.length) return false;
|
|
@@ -47,12 +79,24 @@ function SupportProviderInner({ children, apiUrl, wsUrl, publicKey, defaultMessa
|
|
|
47
79
|
React.useEffect(() => {
|
|
48
80
|
if (quickOptions?.length) _setQuickOptions(quickOptions);
|
|
49
81
|
}, [quickOptions]);
|
|
50
|
-
const { client } = useClient(publicKey, apiUrl, wsUrl);
|
|
82
|
+
const { client, configurationError: clientConfigError } = useClient(publicKey, apiUrl, wsUrl);
|
|
51
83
|
const { website, isLoading, error: websiteError } = useWebsiteStore(client);
|
|
52
84
|
const isVisitorBlocked = website?.visitor?.isBlocked ?? false;
|
|
53
85
|
const visitorId = website?.visitor?.id ?? null;
|
|
86
|
+
const configurationError = React.useMemo(() => {
|
|
87
|
+
if (clientConfigError) return clientConfigError;
|
|
88
|
+
if (websiteError && isAuthError(websiteError)) {
|
|
89
|
+
const envVarName = isNextJSEnvironment() ? "NEXT_PUBLIC_COSSISTANT_API_KEY" : "COSSISTANT_API_KEY";
|
|
90
|
+
return {
|
|
91
|
+
type: "invalid_api_key",
|
|
92
|
+
message: websiteError.message,
|
|
93
|
+
envVarName
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
return null;
|
|
97
|
+
}, [clientConfigError, websiteError]);
|
|
54
98
|
const seenEntriesByConversation = useSeenStore(React.useCallback((state) => state.conversations, []));
|
|
55
|
-
const conversationSnapshots = useStoreSelector(client
|
|
99
|
+
const conversationSnapshots = useStoreSelector(client?.conversationsStore ?? null, React.useCallback((state) => state ? state.ids.map((id) => {
|
|
56
100
|
const conversation = state.byId[id];
|
|
57
101
|
if (!conversation) return null;
|
|
58
102
|
return {
|
|
@@ -60,7 +104,7 @@ function SupportProviderInner({ children, apiUrl, wsUrl, publicKey, defaultMessa
|
|
|
60
104
|
lastTimelineItem: conversation.lastTimelineItem ?? null,
|
|
61
105
|
visitorLastSeenAt: conversation.visitorLastSeenAt ?? null
|
|
62
106
|
};
|
|
63
|
-
}).filter((snapshot) => snapshot !== null), []), areConversationSnapshotsEqual);
|
|
107
|
+
}).filter((snapshot) => snapshot !== null) : [], []), areConversationSnapshotsEqual);
|
|
64
108
|
const derivedUnreadCount = React.useMemo(() => {
|
|
65
109
|
if (!visitorId) return 0;
|
|
66
110
|
let count = 0;
|
|
@@ -94,10 +138,11 @@ function SupportProviderInner({ children, apiUrl, wsUrl, publicKey, defaultMessa
|
|
|
94
138
|
setUnreadCount(derivedUnreadCount);
|
|
95
139
|
}, [derivedUnreadCount, setUnreadCount]);
|
|
96
140
|
React.useEffect(() => {
|
|
97
|
-
if (!website) return;
|
|
141
|
+
if (!(website && client)) return;
|
|
98
142
|
client.setWebsiteContext(website.id, website.visitor?.id ?? void 0);
|
|
99
143
|
}, [client, website]);
|
|
100
144
|
React.useEffect(() => {
|
|
145
|
+
if (!client) return;
|
|
101
146
|
if (isVisitorBlocked) {
|
|
102
147
|
prefetchedVisitorRef.current = null;
|
|
103
148
|
return;
|
|
@@ -122,6 +167,7 @@ function SupportProviderInner({ children, apiUrl, wsUrl, publicKey, defaultMessa
|
|
|
122
167
|
]);
|
|
123
168
|
const error = websiteError;
|
|
124
169
|
React.useEffect(() => {
|
|
170
|
+
if (!client) return;
|
|
125
171
|
client.setVisitorBlocked(isVisitorBlocked);
|
|
126
172
|
}, [client, isVisitorBlocked]);
|
|
127
173
|
const setDefaultMessages = React.useCallback((messages) => {
|
|
@@ -136,6 +182,7 @@ function SupportProviderInner({ children, apiUrl, wsUrl, publicKey, defaultMessa
|
|
|
136
182
|
setUnreadCount,
|
|
137
183
|
isLoading,
|
|
138
184
|
error,
|
|
185
|
+
configurationError,
|
|
139
186
|
client,
|
|
140
187
|
defaultMessages: _defaultMessages,
|
|
141
188
|
setDefaultMessages,
|
|
@@ -150,6 +197,7 @@ function SupportProviderInner({ children, apiUrl, wsUrl, publicKey, defaultMessa
|
|
|
150
197
|
unreadCount,
|
|
151
198
|
isLoading,
|
|
152
199
|
error,
|
|
200
|
+
configurationError,
|
|
153
201
|
client,
|
|
154
202
|
_defaultMessages,
|
|
155
203
|
_quickOptions,
|
|
@@ -168,8 +216,8 @@ function SupportProviderInner({ children, apiUrl, wsUrl, publicKey, defaultMessa
|
|
|
168
216
|
}, [isVisitorBlocked, website]);
|
|
169
217
|
return /* @__PURE__ */ jsx(SupportContext.Provider, {
|
|
170
218
|
value,
|
|
171
|
-
children: /* @__PURE__ */ jsx(WebSocketProvider, {
|
|
172
|
-
autoConnect: autoConnect && !isVisitorBlocked,
|
|
219
|
+
children: /* @__PURE__ */ jsx(IdentificationProvider, { children: /* @__PURE__ */ jsx(WebSocketProvider, {
|
|
220
|
+
autoConnect: autoConnect && !isVisitorBlocked && !configurationError,
|
|
173
221
|
onConnect: onWsConnect,
|
|
174
222
|
onDisconnect: onWsDisconnect,
|
|
175
223
|
onError: onWsError,
|
|
@@ -178,7 +226,7 @@ function SupportProviderInner({ children, apiUrl, wsUrl, publicKey, defaultMessa
|
|
|
178
226
|
websiteId: website?.id,
|
|
179
227
|
wsUrl,
|
|
180
228
|
children
|
|
181
|
-
}, webSocketKey)
|
|
229
|
+
}, webSocketKey) })
|
|
182
230
|
});
|
|
183
231
|
}
|
|
184
232
|
/**
|
package/provider.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"provider.js","names":[],"sources":["../src/provider.tsx"],"sourcesContent":["import type { CossistantClient } from \"@cossistant/core\";\nimport { normalizeLocale } from \"@cossistant/core\";\nimport type { DefaultMessage, PublicWebsiteResponse } from \"@cossistant/types\";\nimport type { TimelineItem } from \"@cossistant/types/api/timeline-item\";\nimport { ConversationTimelineType } from \"@cossistant/types/enums\";\nimport React from \"react\";\nimport { useStoreSelector } from \"./hooks/private/store/use-store-selector\";\nimport { useWebsiteStore } from \"./hooks/private/store/use-website-store\";\nimport { useClient } from \"./hooks/private/use-rest-client\";\nimport { useSeenStore } from \"./realtime/seen-store\";\nimport { WebSocketProvider } from \"./support\";\nimport {\n\tinitializeSupportStore,\n\tuseSupportStore,\n} from \"./support/store/support-store\";\n\nexport type SupportProviderProps = {\n\tchildren: React.ReactNode;\n\tdefaultOpen?: boolean;\n\tapiUrl?: string;\n\twsUrl?: string;\n\tpublicKey?: string;\n\tdefaultMessages?: DefaultMessage[];\n\tquickOptions?: string[];\n\tautoConnect?: boolean;\n\tonWsConnect?: () => void;\n\tonWsDisconnect?: () => void;\n\tonWsError?: (error: Error) => void;\n\tsize?: \"normal\" | \"larger\";\n};\n\nexport type CossistantProviderProps = SupportProviderProps;\n\nexport type CossistantContextValue = {\n\twebsite: PublicWebsiteResponse | null;\n\tdefaultMessages: DefaultMessage[];\n\tquickOptions: string[];\n\tsetDefaultMessages: (messages: DefaultMessage[]) => void;\n\tsetQuickOptions: (options: string[]) => void;\n\tunreadCount: number;\n\tsetUnreadCount: (count: number) => void;\n\tisLoading: boolean;\n\terror: Error | null;\n\tclient: CossistantClient;\n\tisOpen: boolean;\n\topen: () => void;\n\tclose: () => void;\n\ttoggle: () => void;\n};\n\ntype WebsiteData = NonNullable<CossistantContextValue[\"website\"]>;\n\ntype VisitorWithLocale = WebsiteData[\"visitor\"] extends null | undefined\n\t? undefined\n\t: NonNullable<WebsiteData[\"visitor\"]> & { locale: string | null };\n\ntype ConversationSnapshot = {\n\tid: string;\n\tlastTimelineItem: TimelineItem | null;\n\tvisitorLastSeenAt: string | null;\n};\n\nfunction areConversationSnapshotsEqual(\n\ta: ConversationSnapshot[],\n\tb: ConversationSnapshot[]\n): boolean {\n\tif (a === b) {\n\t\treturn true;\n\t}\n\n\tif (a.length !== b.length) {\n\t\treturn false;\n\t}\n\n\tfor (let index = 0; index < a.length; index += 1) {\n\t\tconst snapshotA = a[index];\n\t\tconst snapshotB = b[index];\n\n\t\tif (!snapshotA) {\n\t\t\treturn false;\n\t\t}\n\t\tif (!snapshotB) {\n\t\t\treturn false;\n\t\t}\n\n\t\tconst aLastCreatedAt = snapshotA.lastTimelineItem?.createdAt ?? null;\n\t\tconst bLastCreatedAt = snapshotB.lastTimelineItem?.createdAt ?? null;\n\t\tif (\n\t\t\tsnapshotA.id !== snapshotB.id ||\n\t\t\taLastCreatedAt !== bLastCreatedAt ||\n\t\t\tsnapshotA.visitorLastSeenAt !== snapshotB.visitorLastSeenAt\n\t\t) {\n\t\t\treturn false;\n\t\t}\n\t}\n\n\treturn true;\n}\n\nexport type UseSupportValue = CossistantContextValue & {\n\tavailableHumanAgents: NonNullable<WebsiteData[\"availableHumanAgents\"]> | [];\n\tavailableAIAgents: NonNullable<WebsiteData[\"availableAIAgents\"]> | [];\n\tvisitor?: VisitorWithLocale;\n\tsize: \"normal\" | \"larger\";\n};\n\nexport const SupportContext = React.createContext<\n\tCossistantContextValue | undefined\n>(undefined);\n\n/**\n * Internal implementation that wires the REST client and websocket provider\n * together before exposing the combined context.\n */\nfunction SupportProviderInner({\n\tchildren,\n\tapiUrl,\n\twsUrl,\n\tpublicKey,\n\tdefaultMessages,\n\tquickOptions,\n\tautoConnect,\n\tonWsConnect,\n\tonWsDisconnect,\n\tonWsError,\n\tsize = \"normal\",\n\tdefaultOpen = false,\n}: SupportProviderProps) {\n\tconst [unreadCount, setUnreadCount] = React.useState(0);\n\tconst prefetchedVisitorRef = React.useRef<string | null>(null);\n\tconst [_defaultMessages, _setDefaultMessages] = React.useState<\n\t\tDefaultMessage[]\n\t>(defaultMessages ?? []);\n\tconst [_quickOptions, _setQuickOptions] = React.useState<string[]>(\n\t\tquickOptions ?? []\n\t);\n\n\t// Initialize support store with configuration\n\tReact.useEffect(() => {\n\t\tinitializeSupportStore({ size, defaultOpen });\n\t}, [size, defaultOpen]);\n\n\t// Get support store state and actions\n\tconst { config, open, close, toggle } = useSupportStore();\n\n\t// Update state when props change (for initial values from provider)\n\tReact.useEffect(() => {\n\t\tif (defaultMessages?.length) {\n\t\t\t_setDefaultMessages(defaultMessages);\n\t\t}\n\t}, [defaultMessages]);\n\n\tReact.useEffect(() => {\n\t\tif (quickOptions?.length) {\n\t\t\t_setQuickOptions(quickOptions);\n\t\t}\n\t}, [quickOptions]);\n\n\tconst { client } = useClient(publicKey, apiUrl, wsUrl);\n\tconst { website, isLoading, error: websiteError } = useWebsiteStore(client);\n\tconst isVisitorBlocked = website?.visitor?.isBlocked ?? false;\n\tconst visitorId = website?.visitor?.id ?? null;\n\n\tconst seenEntriesByConversation = useSeenStore(\n\t\tReact.useCallback((state) => state.conversations, [])\n\t);\n\n\tconst conversationSnapshots = useStoreSelector(\n\t\tclient.conversationsStore,\n\t\tReact.useCallback(\n\t\t\t(state) =>\n\t\t\t\tstate.ids\n\t\t\t\t\t.map((id) => {\n\t\t\t\t\t\tconst conversation = state.byId[id];\n\n\t\t\t\t\t\tif (!conversation) {\n\t\t\t\t\t\t\treturn null;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\tid: conversation.id,\n\t\t\t\t\t\t\tlastTimelineItem: conversation.lastTimelineItem ?? null,\n\t\t\t\t\t\t\tvisitorLastSeenAt: conversation.visitorLastSeenAt ?? null,\n\t\t\t\t\t\t} satisfies ConversationSnapshot;\n\t\t\t\t\t})\n\t\t\t\t\t.filter(\n\t\t\t\t\t\t(snapshot): snapshot is ConversationSnapshot => snapshot !== null\n\t\t\t\t\t),\n\t\t\t[]\n\t\t),\n\t\tareConversationSnapshotsEqual\n\t);\n\n\tconst derivedUnreadCount = React.useMemo(() => {\n\t\tif (!visitorId) {\n\t\t\treturn 0;\n\t\t}\n\n\t\tlet count = 0;\n\n\t\tfor (const {\n\t\t\tid: conversationId,\n\t\t\tlastTimelineItem,\n\t\t\tvisitorLastSeenAt,\n\t\t} of conversationSnapshots) {\n\t\t\tif (!lastTimelineItem) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (lastTimelineItem.type !== ConversationTimelineType.MESSAGE) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (\n\t\t\t\tlastTimelineItem.visitorId &&\n\t\t\t\tlastTimelineItem.visitorId === visitorId\n\t\t\t) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tconst createdAtTime = Date.parse(lastTimelineItem.createdAt);\n\n\t\t\tif (Number.isNaN(createdAtTime)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// First check visitorLastSeenAt from the API response (available immediately)\n\t\t\tif (visitorLastSeenAt) {\n\t\t\t\tconst lastSeenTime = Date.parse(visitorLastSeenAt);\n\t\t\t\tif (!Number.isNaN(lastSeenTime) && createdAtTime <= lastSeenTime) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Fall back to seen store (updated via realtime events)\n\t\t\tconst seenEntries = seenEntriesByConversation[conversationId];\n\n\t\t\tif (seenEntries) {\n\t\t\t\tconst visitorSeenEntry = Object.values(seenEntries).find(\n\t\t\t\t\t(entry) =>\n\t\t\t\t\t\tentry.actorType === \"visitor\" && entry.actorId === visitorId\n\t\t\t\t);\n\n\t\t\t\tif (visitorSeenEntry) {\n\t\t\t\t\tconst lastSeenTime = Date.parse(visitorSeenEntry.lastSeenAt);\n\n\t\t\t\t\tif (!Number.isNaN(lastSeenTime) && createdAtTime <= lastSeenTime) {\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tcount += 1;\n\t\t}\n\n\t\treturn count;\n\t}, [conversationSnapshots, seenEntriesByConversation, visitorId]);\n\n\tReact.useEffect(() => {\n\t\tsetUnreadCount(derivedUnreadCount);\n\t}, [derivedUnreadCount, setUnreadCount]);\n\n\t// Prime REST client with website/visitor context so headers are sent reliably\n\tReact.useEffect(() => {\n\t\tif (!website) {\n\t\t\treturn;\n\t\t}\n\n\t\tclient.setWebsiteContext(website.id, website.visitor?.id ?? undefined);\n\t}, [client, website]);\n\n\tReact.useEffect(() => {\n\t\tif (isVisitorBlocked) {\n\t\t\tprefetchedVisitorRef.current = null;\n\t\t\treturn;\n\t\t}\n\n\t\tif (!autoConnect) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (!website) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (!visitorId) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (prefetchedVisitorRef.current === visitorId) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst hasExistingConversations =\n\t\t\tclient.conversationsStore.getState().ids.length > 0;\n\n\t\tprefetchedVisitorRef.current = visitorId;\n\n\t\tif (hasExistingConversations) {\n\t\t\treturn;\n\t\t}\n\n\t\tvoid client.listConversations().catch((err) => {\n\t\t\tconsole.error(\"[SupportProvider] Failed to prefetch conversations\", err);\n\t\t\tprefetchedVisitorRef.current = null;\n\t\t});\n\t}, [autoConnect, client, isVisitorBlocked, visitorId, website]);\n\n\tconst error = websiteError;\n\n\tReact.useEffect(() => {\n\t\tclient.setVisitorBlocked(isVisitorBlocked);\n\t}, [client, isVisitorBlocked]);\n\n\tconst setDefaultMessages = React.useCallback((messages: DefaultMessage[]) => {\n\t\t_setDefaultMessages(messages);\n\t}, []);\n\n\tconst setQuickOptions = React.useCallback((options: string[]) => {\n\t\t_setQuickOptions(options);\n\t}, []);\n\n\tconst value = React.useMemo<CossistantContextValue>(\n\t\t() => ({\n\t\t\twebsite,\n\t\t\tunreadCount,\n\t\t\tsetUnreadCount,\n\t\t\tisLoading,\n\t\t\terror,\n\t\t\tclient,\n\t\t\tdefaultMessages: _defaultMessages,\n\t\t\tsetDefaultMessages,\n\t\t\tquickOptions: _quickOptions,\n\t\t\tsetQuickOptions,\n\t\t\tisOpen: config.isOpen,\n\t\t\topen,\n\t\t\tclose,\n\t\t\ttoggle,\n\t\t}),\n\t\t[\n\t\t\twebsite,\n\t\t\tunreadCount,\n\t\t\tisLoading,\n\t\t\terror,\n\t\t\tclient,\n\t\t\t_defaultMessages,\n\t\t\t_quickOptions,\n\t\t\tsetDefaultMessages,\n\t\t\tsetQuickOptions,\n\t\t\tconfig.isOpen,\n\t\t\topen,\n\t\t\tclose,\n\t\t\ttoggle,\n\t\t]\n\t);\n\n\tconst webSocketKey = React.useMemo(() => {\n\t\tif (!website) {\n\t\t\treturn \"no-website\";\n\t\t}\n\n\t\tconst visitorKey = website.visitor?.id ?? \"anonymous\";\n\t\tconst blockedState = isVisitorBlocked ? \"blocked\" : \"active\";\n\n\t\treturn `${website.id}:${visitorKey}:${blockedState}`;\n\t}, [isVisitorBlocked, website]);\n\n\treturn (\n\t\t<SupportContext.Provider value={value}>\n\t\t\t<WebSocketProvider\n\t\t\t\tautoConnect={autoConnect && !isVisitorBlocked}\n\t\t\t\tkey={webSocketKey}\n\t\t\t\tonConnect={onWsConnect}\n\t\t\t\tonDisconnect={onWsDisconnect}\n\t\t\t\tonError={onWsError}\n\t\t\t\tpublicKey={publicKey}\n\t\t\t\tvisitorId={isVisitorBlocked ? undefined : website?.visitor?.id}\n\t\t\t\twebsiteId={website?.id}\n\t\t\t\twsUrl={wsUrl}\n\t\t\t>\n\t\t\t\t{children}\n\t\t\t</WebSocketProvider>\n\t\t</SupportContext.Provider>\n\t);\n}\n\n/**\n * Hosts the entire customer support widget ecosystem by handing out context\n * about the current website, visitor, unread counts, realtime subscriptions\n * and the REST client. Provide your Cossistant public key plus optional\n * defaults to configure the widget behaviour.\n */\nexport function SupportProvider({\n\tchildren,\n\tapiUrl = \"https://api.cossistant.com/v1\",\n\twsUrl = \"wss://api.cossistant.com/ws\",\n\tpublicKey,\n\tdefaultMessages,\n\tquickOptions,\n\tautoConnect = true,\n\tonWsConnect,\n\tonWsDisconnect,\n\tonWsError,\n\tsize = \"normal\",\n\tdefaultOpen = false,\n}: SupportProviderProps): React.ReactElement {\n\treturn (\n\t\t<SupportProviderInner\n\t\t\tapiUrl={apiUrl}\n\t\t\tautoConnect={autoConnect}\n\t\t\tdefaultMessages={defaultMessages}\n\t\t\tdefaultOpen={defaultOpen}\n\t\t\tonWsConnect={onWsConnect}\n\t\t\tonWsDisconnect={onWsDisconnect}\n\t\t\tonWsError={onWsError}\n\t\t\tpublicKey={publicKey}\n\t\t\tquickOptions={quickOptions}\n\t\t\tsize={size}\n\t\t\twsUrl={wsUrl}\n\t\t>\n\t\t\t{children}\n\t\t</SupportProviderInner>\n\t);\n}\n\n/**\n * Convenience hook that exposes the aggregated support context. Throws when it\n * is consumed outside of `SupportProvider` to catch integration mistakes.\n */\nexport function useSupport(): UseSupportValue {\n\tconst context = React.useContext(SupportContext);\n\tif (!context) {\n\t\tthrow new Error(\n\t\t\t\"useSupport must be used within a cossistant SupportProvider\"\n\t\t);\n\t}\n\n\tconst availableHumanAgents = context.website?.availableHumanAgents || [];\n\tconst availableAIAgents = context.website?.availableAIAgents || [];\n\tconst visitorLanguage = context.website?.visitor?.language || null;\n\n\t// Get additional config from support store\n\tconst { config } = useSupportStore();\n\n\t// Create visitor object with normalized locale\n\tconst visitor = context.website?.visitor\n\t\t? {\n\t\t\t\t...context.website.visitor,\n\t\t\t\tlocale: normalizeLocale(visitorLanguage),\n\t\t\t}\n\t\t: undefined;\n\n\treturn {\n\t\t...context,\n\t\tavailableHumanAgents,\n\t\tavailableAIAgents,\n\t\tvisitor,\n\t\tsize: config.size,\n\t};\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"provider.js","names":[],"sources":["../src/provider.tsx"],"sourcesContent":["import type { CossistantClient } from \"@cossistant/core\";\nimport { CossistantAPIError, normalizeLocale } from \"@cossistant/core\";\nimport type { DefaultMessage, PublicWebsiteResponse } from \"@cossistant/types\";\nimport type { TimelineItem } from \"@cossistant/types/api/timeline-item\";\nimport { ConversationTimelineType } from \"@cossistant/types/enums\";\nimport React from \"react\";\nimport { useStoreSelector } from \"./hooks/private/store/use-store-selector\";\nimport { useWebsiteStore } from \"./hooks/private/store/use-website-store\";\nimport {\n\ttype ConfigurationError,\n\tuseClient,\n} from \"./hooks/private/use-rest-client\";\nimport { useSeenStore } from \"./realtime/seen-store\";\nimport { WebSocketProvider } from \"./support\";\nimport { IdentificationProvider } from \"./support/context/identification\";\nimport {\n\tinitializeSupportStore,\n\tuseSupportStore,\n} from \"./support/store/support-store\";\n\n/**\n * Auth-related error codes that indicate API key issues.\n */\nconst AUTH_ERROR_CODES = new Set([\n\t\"UNAUTHORIZED\",\n\t\"FORBIDDEN\",\n\t\"INVALID_API_KEY\",\n\t\"API_KEY_EXPIRED\",\n\t\"API_KEY_MISSING\",\n\t\"HTTP_401\",\n\t\"HTTP_403\",\n]);\n\n/**\n * Check if an error is an authentication/authorization error.\n */\nfunction isAuthError(error: Error | null): boolean {\n\tif (!error) {\n\t\treturn false;\n\t}\n\n\tif (error instanceof CossistantAPIError) {\n\t\tconst code = error.code?.toUpperCase() ?? \"\";\n\t\treturn (\n\t\t\tAUTH_ERROR_CODES.has(code) ||\n\t\t\tcode.includes(\"AUTH\") ||\n\t\t\tcode.includes(\"API_KEY\")\n\t\t);\n\t}\n\n\t// Check error message as fallback\n\tconst message = error.message?.toLowerCase() ?? \"\";\n\treturn (\n\t\tmessage.includes(\"api key\") ||\n\t\tmessage.includes(\"unauthorized\") ||\n\t\tmessage.includes(\"forbidden\") ||\n\t\tmessage.includes(\"not authorized\")\n\t);\n}\n\n/**\n * Detect if running in a Next.js environment.\n */\nfunction isNextJSEnvironment(): boolean {\n\tif (typeof window !== \"undefined\") {\n\t\treturn \"__NEXT_DATA__\" in window;\n\t}\n\treturn typeof process !== \"undefined\" && \"__NEXT_RUNTIME\" in process.env;\n}\n\nexport type SupportProviderProps = {\n\tchildren: React.ReactNode;\n\tdefaultOpen?: boolean;\n\tapiUrl?: string;\n\twsUrl?: string;\n\tpublicKey?: string;\n\tdefaultMessages?: DefaultMessage[];\n\tquickOptions?: string[];\n\tautoConnect?: boolean;\n\tonWsConnect?: () => void;\n\tonWsDisconnect?: () => void;\n\tonWsError?: (error: Error) => void;\n\tsize?: \"normal\" | \"larger\";\n};\n\nexport type CossistantProviderProps = SupportProviderProps;\n\nexport type CossistantContextValue = {\n\twebsite: PublicWebsiteResponse | null;\n\tdefaultMessages: DefaultMessage[];\n\tquickOptions: string[];\n\tsetDefaultMessages: (messages: DefaultMessage[]) => void;\n\tsetQuickOptions: (options: string[]) => void;\n\tunreadCount: number;\n\tsetUnreadCount: (count: number) => void;\n\tisLoading: boolean;\n\terror: Error | null;\n\tconfigurationError: ConfigurationError | null;\n\tclient: CossistantClient | null;\n\tisOpen: boolean;\n\topen: () => void;\n\tclose: () => void;\n\ttoggle: () => void;\n};\n\ntype WebsiteData = NonNullable<CossistantContextValue[\"website\"]>;\n\ntype VisitorWithLocale = WebsiteData[\"visitor\"] extends null | undefined\n\t? undefined\n\t: NonNullable<WebsiteData[\"visitor\"]> & { locale: string | null };\n\ntype ConversationSnapshot = {\n\tid: string;\n\tlastTimelineItem: TimelineItem | null;\n\tvisitorLastSeenAt: string | null;\n};\n\nfunction areConversationSnapshotsEqual(\n\ta: ConversationSnapshot[],\n\tb: ConversationSnapshot[]\n): boolean {\n\tif (a === b) {\n\t\treturn true;\n\t}\n\n\tif (a.length !== b.length) {\n\t\treturn false;\n\t}\n\n\tfor (let index = 0; index < a.length; index += 1) {\n\t\tconst snapshotA = a[index];\n\t\tconst snapshotB = b[index];\n\n\t\tif (!snapshotA) {\n\t\t\treturn false;\n\t\t}\n\t\tif (!snapshotB) {\n\t\t\treturn false;\n\t\t}\n\n\t\tconst aLastCreatedAt = snapshotA.lastTimelineItem?.createdAt ?? null;\n\t\tconst bLastCreatedAt = snapshotB.lastTimelineItem?.createdAt ?? null;\n\t\tif (\n\t\t\tsnapshotA.id !== snapshotB.id ||\n\t\t\taLastCreatedAt !== bLastCreatedAt ||\n\t\t\tsnapshotA.visitorLastSeenAt !== snapshotB.visitorLastSeenAt\n\t\t) {\n\t\t\treturn false;\n\t\t}\n\t}\n\n\treturn true;\n}\n\nexport type UseSupportValue = CossistantContextValue & {\n\tavailableHumanAgents: NonNullable<WebsiteData[\"availableHumanAgents\"]> | [];\n\tavailableAIAgents: NonNullable<WebsiteData[\"availableAIAgents\"]> | [];\n\tvisitor?: VisitorWithLocale;\n\tsize: \"normal\" | \"larger\";\n};\n\nexport const SupportContext = React.createContext<\n\tCossistantContextValue | undefined\n>(undefined);\n\n/**\n * Internal implementation that wires the REST client and websocket provider\n * together before exposing the combined context.\n */\nfunction SupportProviderInner({\n\tchildren,\n\tapiUrl,\n\twsUrl,\n\tpublicKey,\n\tdefaultMessages,\n\tquickOptions,\n\tautoConnect,\n\tonWsConnect,\n\tonWsDisconnect,\n\tonWsError,\n\tsize = \"normal\",\n\tdefaultOpen = false,\n}: SupportProviderProps) {\n\tconst [unreadCount, setUnreadCount] = React.useState(0);\n\tconst prefetchedVisitorRef = React.useRef<string | null>(null);\n\tconst [_defaultMessages, _setDefaultMessages] = React.useState<\n\t\tDefaultMessage[]\n\t>(defaultMessages ?? []);\n\tconst [_quickOptions, _setQuickOptions] = React.useState<string[]>(\n\t\tquickOptions ?? []\n\t);\n\n\t// Initialize support store with configuration\n\tReact.useEffect(() => {\n\t\tinitializeSupportStore({ size, defaultOpen });\n\t}, [size, defaultOpen]);\n\n\t// Get support store state and actions\n\tconst { config, open, close, toggle } = useSupportStore();\n\n\t// Update state when props change (for initial values from provider)\n\tReact.useEffect(() => {\n\t\tif (defaultMessages?.length) {\n\t\t\t_setDefaultMessages(defaultMessages);\n\t\t}\n\t}, [defaultMessages]);\n\n\tReact.useEffect(() => {\n\t\tif (quickOptions?.length) {\n\t\t\t_setQuickOptions(quickOptions);\n\t\t}\n\t}, [quickOptions]);\n\n\tconst { client, configurationError: clientConfigError } = useClient(\n\t\tpublicKey,\n\t\tapiUrl,\n\t\twsUrl\n\t);\n\n\t// Only use website store if we have a valid client\n\tconst { website, isLoading, error: websiteError } = useWebsiteStore(client);\n\tconst isVisitorBlocked = website?.visitor?.isBlocked ?? false;\n\tconst visitorId = website?.visitor?.id ?? null;\n\n\t// Derive final configuration error from both client error and API auth errors\n\tconst configurationError = React.useMemo<ConfigurationError | null>(() => {\n\t\t// Client-level config error takes precedence (missing API key)\n\t\tif (clientConfigError) {\n\t\t\treturn clientConfigError;\n\t\t}\n\n\t\t// Check if website error is an auth error (invalid/expired API key)\n\t\tif (websiteError && isAuthError(websiteError)) {\n\t\t\tconst isNextJS = isNextJSEnvironment();\n\t\t\tconst envVarName = isNextJS\n\t\t\t\t? \"NEXT_PUBLIC_COSSISTANT_API_KEY\"\n\t\t\t\t: \"COSSISTANT_API_KEY\";\n\n\t\t\treturn {\n\t\t\t\ttype: \"invalid_api_key\",\n\t\t\t\tmessage: websiteError.message,\n\t\t\t\tenvVarName,\n\t\t\t};\n\t\t}\n\n\t\treturn null;\n\t}, [clientConfigError, websiteError]);\n\n\tconst seenEntriesByConversation = useSeenStore(\n\t\tReact.useCallback((state) => state.conversations, [])\n\t);\n\n\tconst conversationSnapshots = useStoreSelector(\n\t\tclient?.conversationsStore ?? null,\n\t\tReact.useCallback(\n\t\t\t(\n\t\t\t\tstate: {\n\t\t\t\t\tids: string[];\n\t\t\t\t\tbyId: Record<\n\t\t\t\t\t\tstring,\n\t\t\t\t\t\t| {\n\t\t\t\t\t\t\t\tid: string;\n\t\t\t\t\t\t\t\tlastTimelineItem?: TimelineItem | null;\n\t\t\t\t\t\t\t\tvisitorLastSeenAt?: string | null;\n\t\t\t\t\t\t }\n\t\t\t\t\t\t| undefined\n\t\t\t\t\t>;\n\t\t\t\t} | null\n\t\t\t): ConversationSnapshot[] =>\n\t\t\t\tstate\n\t\t\t\t\t? state.ids\n\t\t\t\t\t\t\t.map((id) => {\n\t\t\t\t\t\t\t\tconst conversation = state.byId[id];\n\n\t\t\t\t\t\t\t\tif (!conversation) {\n\t\t\t\t\t\t\t\t\treturn null;\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\t\t\tid: conversation.id,\n\t\t\t\t\t\t\t\t\tlastTimelineItem: conversation.lastTimelineItem ?? null,\n\t\t\t\t\t\t\t\t\tvisitorLastSeenAt: conversation.visitorLastSeenAt ?? null,\n\t\t\t\t\t\t\t\t} satisfies ConversationSnapshot;\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t.filter(\n\t\t\t\t\t\t\t\t(snapshot): snapshot is ConversationSnapshot =>\n\t\t\t\t\t\t\t\t\tsnapshot !== null\n\t\t\t\t\t\t\t)\n\t\t\t\t\t: [],\n\t\t\t[]\n\t\t),\n\t\tareConversationSnapshotsEqual\n\t);\n\n\tconst derivedUnreadCount = React.useMemo(() => {\n\t\tif (!visitorId) {\n\t\t\treturn 0;\n\t\t}\n\n\t\tlet count = 0;\n\n\t\tfor (const {\n\t\t\tid: conversationId,\n\t\t\tlastTimelineItem,\n\t\t\tvisitorLastSeenAt,\n\t\t} of conversationSnapshots) {\n\t\t\tif (!lastTimelineItem) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (lastTimelineItem.type !== ConversationTimelineType.MESSAGE) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (\n\t\t\t\tlastTimelineItem.visitorId &&\n\t\t\t\tlastTimelineItem.visitorId === visitorId\n\t\t\t) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tconst createdAtTime = Date.parse(lastTimelineItem.createdAt);\n\n\t\t\tif (Number.isNaN(createdAtTime)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// First check visitorLastSeenAt from the API response (available immediately)\n\t\t\tif (visitorLastSeenAt) {\n\t\t\t\tconst lastSeenTime = Date.parse(visitorLastSeenAt);\n\t\t\t\tif (!Number.isNaN(lastSeenTime) && createdAtTime <= lastSeenTime) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Fall back to seen store (updated via realtime events)\n\t\t\tconst seenEntries = seenEntriesByConversation[conversationId];\n\n\t\t\tif (seenEntries) {\n\t\t\t\tconst visitorSeenEntry = Object.values(seenEntries).find(\n\t\t\t\t\t(entry) =>\n\t\t\t\t\t\tentry.actorType === \"visitor\" && entry.actorId === visitorId\n\t\t\t\t);\n\n\t\t\t\tif (visitorSeenEntry) {\n\t\t\t\t\tconst lastSeenTime = Date.parse(visitorSeenEntry.lastSeenAt);\n\n\t\t\t\t\tif (!Number.isNaN(lastSeenTime) && createdAtTime <= lastSeenTime) {\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tcount += 1;\n\t\t}\n\n\t\treturn count;\n\t}, [conversationSnapshots, seenEntriesByConversation, visitorId]);\n\n\tReact.useEffect(() => {\n\t\tsetUnreadCount(derivedUnreadCount);\n\t}, [derivedUnreadCount, setUnreadCount]);\n\n\t// Prime REST client with website/visitor context so headers are sent reliably\n\tReact.useEffect(() => {\n\t\tif (!(website && client)) {\n\t\t\treturn;\n\t\t}\n\n\t\tclient.setWebsiteContext(website.id, website.visitor?.id ?? undefined);\n\t}, [client, website]);\n\n\tReact.useEffect(() => {\n\t\tif (!client) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (isVisitorBlocked) {\n\t\t\tprefetchedVisitorRef.current = null;\n\t\t\treturn;\n\t\t}\n\n\t\tif (!autoConnect) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (!website) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (!visitorId) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (prefetchedVisitorRef.current === visitorId) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst hasExistingConversations =\n\t\t\tclient.conversationsStore.getState().ids.length > 0;\n\n\t\tprefetchedVisitorRef.current = visitorId;\n\n\t\tif (hasExistingConversations) {\n\t\t\treturn;\n\t\t}\n\n\t\tvoid client.listConversations().catch((err) => {\n\t\t\tconsole.error(\"[SupportProvider] Failed to prefetch conversations\", err);\n\t\t\tprefetchedVisitorRef.current = null;\n\t\t});\n\t}, [autoConnect, client, isVisitorBlocked, visitorId, website]);\n\n\tconst error = websiteError;\n\n\tReact.useEffect(() => {\n\t\tif (!client) {\n\t\t\treturn;\n\t\t}\n\t\tclient.setVisitorBlocked(isVisitorBlocked);\n\t}, [client, isVisitorBlocked]);\n\n\tconst setDefaultMessages = React.useCallback((messages: DefaultMessage[]) => {\n\t\t_setDefaultMessages(messages);\n\t}, []);\n\n\tconst setQuickOptions = React.useCallback((options: string[]) => {\n\t\t_setQuickOptions(options);\n\t}, []);\n\n\tconst value = React.useMemo<CossistantContextValue>(\n\t\t() => ({\n\t\t\twebsite,\n\t\t\tunreadCount,\n\t\t\tsetUnreadCount,\n\t\t\tisLoading,\n\t\t\terror,\n\t\t\tconfigurationError,\n\t\t\tclient,\n\t\t\tdefaultMessages: _defaultMessages,\n\t\t\tsetDefaultMessages,\n\t\t\tquickOptions: _quickOptions,\n\t\t\tsetQuickOptions,\n\t\t\tisOpen: config.isOpen,\n\t\t\topen,\n\t\t\tclose,\n\t\t\ttoggle,\n\t\t}),\n\t\t[\n\t\t\twebsite,\n\t\t\tunreadCount,\n\t\t\tisLoading,\n\t\t\terror,\n\t\t\tconfigurationError,\n\t\t\tclient,\n\t\t\t_defaultMessages,\n\t\t\t_quickOptions,\n\t\t\tsetDefaultMessages,\n\t\t\tsetQuickOptions,\n\t\t\tconfig.isOpen,\n\t\t\topen,\n\t\t\tclose,\n\t\t\ttoggle,\n\t\t]\n\t);\n\n\tconst webSocketKey = React.useMemo(() => {\n\t\tif (!website) {\n\t\t\treturn \"no-website\";\n\t\t}\n\n\t\tconst visitorKey = website.visitor?.id ?? \"anonymous\";\n\t\tconst blockedState = isVisitorBlocked ? \"blocked\" : \"active\";\n\n\t\treturn `${website.id}:${visitorKey}:${blockedState}`;\n\t}, [isVisitorBlocked, website]);\n\n\treturn (\n\t\t<SupportContext.Provider value={value}>\n\t\t\t<IdentificationProvider>\n\t\t\t\t<WebSocketProvider\n\t\t\t\t\tautoConnect={autoConnect && !isVisitorBlocked && !configurationError}\n\t\t\t\t\tkey={webSocketKey}\n\t\t\t\t\tonConnect={onWsConnect}\n\t\t\t\t\tonDisconnect={onWsDisconnect}\n\t\t\t\t\tonError={onWsError}\n\t\t\t\t\tpublicKey={publicKey}\n\t\t\t\t\tvisitorId={isVisitorBlocked ? undefined : website?.visitor?.id}\n\t\t\t\t\twebsiteId={website?.id}\n\t\t\t\t\twsUrl={wsUrl}\n\t\t\t\t>\n\t\t\t\t\t{children}\n\t\t\t\t</WebSocketProvider>\n\t\t\t</IdentificationProvider>\n\t\t</SupportContext.Provider>\n\t);\n}\n\n/**\n * Hosts the entire customer support widget ecosystem by handing out context\n * about the current website, visitor, unread counts, realtime subscriptions\n * and the REST client. Provide your Cossistant public key plus optional\n * defaults to configure the widget behaviour.\n */\nexport function SupportProvider({\n\tchildren,\n\tapiUrl = \"https://api.cossistant.com/v1\",\n\twsUrl = \"wss://api.cossistant.com/ws\",\n\tpublicKey,\n\tdefaultMessages,\n\tquickOptions,\n\tautoConnect = true,\n\tonWsConnect,\n\tonWsDisconnect,\n\tonWsError,\n\tsize = \"normal\",\n\tdefaultOpen = false,\n}: SupportProviderProps): React.ReactElement {\n\treturn (\n\t\t<SupportProviderInner\n\t\t\tapiUrl={apiUrl}\n\t\t\tautoConnect={autoConnect}\n\t\t\tdefaultMessages={defaultMessages}\n\t\t\tdefaultOpen={defaultOpen}\n\t\t\tonWsConnect={onWsConnect}\n\t\t\tonWsDisconnect={onWsDisconnect}\n\t\t\tonWsError={onWsError}\n\t\t\tpublicKey={publicKey}\n\t\t\tquickOptions={quickOptions}\n\t\t\tsize={size}\n\t\t\twsUrl={wsUrl}\n\t\t>\n\t\t\t{children}\n\t\t</SupportProviderInner>\n\t);\n}\n\n/**\n * Convenience hook that exposes the aggregated support context. Throws when it\n * is consumed outside of `SupportProvider` to catch integration mistakes.\n */\nexport function useSupport(): UseSupportValue {\n\tconst context = React.useContext(SupportContext);\n\tif (!context) {\n\t\tthrow new Error(\n\t\t\t\"useSupport must be used within a cossistant SupportProvider\"\n\t\t);\n\t}\n\n\tconst availableHumanAgents = context.website?.availableHumanAgents || [];\n\tconst availableAIAgents = context.website?.availableAIAgents || [];\n\tconst visitorLanguage = context.website?.visitor?.language || null;\n\n\t// Get additional config from support store\n\tconst { config } = useSupportStore();\n\n\t// Create visitor object with normalized locale\n\tconst visitor = context.website?.visitor\n\t\t? {\n\t\t\t\t...context.website.visitor,\n\t\t\t\tlocale: normalizeLocale(visitorLanguage),\n\t\t\t}\n\t\t: undefined;\n\n\treturn {\n\t\t...context,\n\t\tavailableHumanAgents,\n\t\tavailableAIAgents,\n\t\tvisitor,\n\t\tsize: config.size,\n\t};\n}\n\n// Re-export ConfigurationError type for consumers\nexport type { ConfigurationError } from \"./hooks/private/use-rest-client\";\n"],"mappings":"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"}
|
|
@@ -1,10 +1,13 @@
|
|
|
1
|
-
import { AnyRealtimeEvent } from "../realtime-events.js";
|
|
1
|
+
import { AnyRealtimeEvent } from "../packages/types/src/realtime-events.js";
|
|
2
2
|
|
|
3
3
|
//#region src/realtime/event-filter.d.ts
|
|
4
4
|
declare function getTargetVisitorId(event: AnyRealtimeEvent): string | null;
|
|
5
5
|
/**
|
|
6
6
|
* Determines whether a realtime event should be processed based on website and
|
|
7
7
|
* visitor identifiers.
|
|
8
|
+
*
|
|
9
|
+
* When a visitorId is provided (i.e. the consumer is a visitor/widget), private
|
|
10
|
+
* timeline items are filtered out to prevent leaking internal data.
|
|
8
11
|
*/
|
|
9
12
|
declare function shouldDeliverEvent(event: AnyRealtimeEvent, websiteId: string | null, visitorId: string | null): boolean;
|
|
10
13
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"event-filter.d.ts","names":[],"sources":["../../src/realtime/event-filter.ts"],"sourcesContent":[],"mappings":";;;iBAES,kBAAA,QAA0B;;AAFuC;
|
|
1
|
+
{"version":3,"file":"event-filter.d.ts","names":[],"sources":["../../src/realtime/event-filter.ts"],"sourcesContent":[],"mappings":";;;iBAES,kBAAA,QAA0B;;AAFuC;AA2B1E;;;;;iBAAgB,kBAAA,QACR"}
|
package/realtime/event-filter.js
CHANGED
|
@@ -11,14 +11,28 @@ function getTargetVisitorId(event) {
|
|
|
11
11
|
/**
|
|
12
12
|
* Determines whether a realtime event should be processed based on website and
|
|
13
13
|
* visitor identifiers.
|
|
14
|
+
*
|
|
15
|
+
* When a visitorId is provided (i.e. the consumer is a visitor/widget), private
|
|
16
|
+
* timeline items are filtered out to prevent leaking internal data.
|
|
14
17
|
*/
|
|
15
18
|
function shouldDeliverEvent(event, websiteId, visitorId) {
|
|
16
19
|
if (websiteId && event.payload.websiteId !== websiteId) return false;
|
|
20
|
+
if (visitorId && isPrivateTimelineEvent(event)) return false;
|
|
17
21
|
if (!visitorId) return true;
|
|
18
22
|
const targetVisitorId = getTargetVisitorId(event);
|
|
19
23
|
if (targetVisitorId && targetVisitorId !== visitorId) return false;
|
|
20
24
|
return true;
|
|
21
25
|
}
|
|
26
|
+
/**
|
|
27
|
+
* Returns true if the event carries a timeline item with private visibility.
|
|
28
|
+
*/
|
|
29
|
+
function isPrivateTimelineEvent(event) {
|
|
30
|
+
if (event.type === "timelineItemCreated" || event.type === "timelineItemUpdated") {
|
|
31
|
+
const item = event.payload.item;
|
|
32
|
+
if (item && item.visibility === "private") return true;
|
|
33
|
+
}
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
22
36
|
|
|
23
37
|
//#endregion
|
|
24
38
|
export { getTargetVisitorId, shouldDeliverEvent };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"event-filter.js","names":[],"sources":["../../src/realtime/event-filter.ts"],"sourcesContent":["import type { AnyRealtimeEvent } from \"@cossistant/types/realtime-events\";\n\nfunction getTargetVisitorId(event: AnyRealtimeEvent): string | null {\n\tconst payloadVisitorId = event.payload.visitorId;\n\n\tif (typeof payloadVisitorId === \"string\" && payloadVisitorId.length > 0) {\n\t\treturn payloadVisitorId;\n\t}\n\n\tif (event.type === \"timelineItemCreated\") {\n\t\tconst itemVisitorId = event.payload.item.visitorId;\n\n\t\tif (typeof itemVisitorId === \"string\" && itemVisitorId.length > 0) {\n\t\t\treturn itemVisitorId;\n\t\t}\n\t}\n\n\treturn null;\n}\n\n/**\n * Determines whether a realtime event should be processed based on website and\n * visitor identifiers.\n */\nexport function shouldDeliverEvent(\n\tevent: AnyRealtimeEvent,\n\twebsiteId: string | null,\n\tvisitorId: string | null\n): boolean {\n\tif (websiteId && event.payload.websiteId !== websiteId) {\n\t\treturn false;\n\t}\n\n\tif (!visitorId) {\n\t\treturn true;\n\t}\n\n\tconst targetVisitorId = getTargetVisitorId(event);\n\n\tif (targetVisitorId && targetVisitorId !== visitorId) {\n\t\treturn false;\n\t}\n\n\treturn true;\n}\n\nexport { getTargetVisitorId };\n"],"mappings":";AAEA,SAAS,mBAAmB,OAAwC;CACnE,MAAM,mBAAmB,MAAM,QAAQ;AAEvC,KAAI,OAAO,qBAAqB,YAAY,iBAAiB,SAAS,EACrE,QAAO;AAGR,KAAI,MAAM,SAAS,uBAAuB;EACzC,MAAM,gBAAgB,MAAM,QAAQ,KAAK;AAEzC,MAAI,OAAO,kBAAkB,YAAY,cAAc,SAAS,EAC/D,QAAO;;AAIT,QAAO
|
|
1
|
+
{"version":3,"file":"event-filter.js","names":[],"sources":["../../src/realtime/event-filter.ts"],"sourcesContent":["import type { AnyRealtimeEvent } from \"@cossistant/types/realtime-events\";\n\nfunction getTargetVisitorId(event: AnyRealtimeEvent): string | null {\n\tconst payloadVisitorId = event.payload.visitorId;\n\n\tif (typeof payloadVisitorId === \"string\" && payloadVisitorId.length > 0) {\n\t\treturn payloadVisitorId;\n\t}\n\n\tif (event.type === \"timelineItemCreated\") {\n\t\tconst itemVisitorId = event.payload.item.visitorId;\n\n\t\tif (typeof itemVisitorId === \"string\" && itemVisitorId.length > 0) {\n\t\t\treturn itemVisitorId;\n\t\t}\n\t}\n\n\treturn null;\n}\n\n/**\n * Determines whether a realtime event should be processed based on website and\n * visitor identifiers.\n *\n * When a visitorId is provided (i.e. the consumer is a visitor/widget), private\n * timeline items are filtered out to prevent leaking internal data.\n */\nexport function shouldDeliverEvent(\n\tevent: AnyRealtimeEvent,\n\twebsiteId: string | null,\n\tvisitorId: string | null\n): boolean {\n\tif (websiteId && event.payload.websiteId !== websiteId) {\n\t\treturn false;\n\t}\n\n\t// When consuming as a visitor, never deliver private timeline items.\n\t// This is a defense-in-depth measure; the server should also filter these.\n\tif (visitorId && isPrivateTimelineEvent(event)) {\n\t\treturn false;\n\t}\n\n\tif (!visitorId) {\n\t\treturn true;\n\t}\n\n\tconst targetVisitorId = getTargetVisitorId(event);\n\n\tif (targetVisitorId && targetVisitorId !== visitorId) {\n\t\treturn false;\n\t}\n\n\treturn true;\n}\n\n/**\n * Returns true if the event carries a timeline item with private visibility.\n */\nfunction isPrivateTimelineEvent(event: AnyRealtimeEvent): boolean {\n\tif (\n\t\tevent.type === \"timelineItemCreated\" ||\n\t\tevent.type === \"timelineItemUpdated\"\n\t) {\n\t\tconst payload = event.payload as Record<string, unknown>;\n\t\tconst item = payload.item as Record<string, unknown> | undefined;\n\t\tif (item && item.visibility === \"private\") {\n\t\t\treturn true;\n\t\t}\n\t}\n\treturn false;\n}\n\nexport { getTargetVisitorId };\n"],"mappings":";AAEA,SAAS,mBAAmB,OAAwC;CACnE,MAAM,mBAAmB,MAAM,QAAQ;AAEvC,KAAI,OAAO,qBAAqB,YAAY,iBAAiB,SAAS,EACrE,QAAO;AAGR,KAAI,MAAM,SAAS,uBAAuB;EACzC,MAAM,gBAAgB,MAAM,QAAQ,KAAK;AAEzC,MAAI,OAAO,kBAAkB,YAAY,cAAc,SAAS,EAC/D,QAAO;;AAIT,QAAO;;;;;;;;;AAUR,SAAgB,mBACf,OACA,WACA,WACU;AACV,KAAI,aAAa,MAAM,QAAQ,cAAc,UAC5C,QAAO;AAKR,KAAI,aAAa,uBAAuB,MAAM,CAC7C,QAAO;AAGR,KAAI,CAAC,UACJ,QAAO;CAGR,MAAM,kBAAkB,mBAAmB,MAAM;AAEjD,KAAI,mBAAmB,oBAAoB,UAC1C,QAAO;AAGR,QAAO;;;;;AAMR,SAAS,uBAAuB,OAAkC;AACjE,KACC,MAAM,SAAS,yBACf,MAAM,SAAS,uBACd;EAED,MAAM,OADU,MAAM,QACD;AACrB,MAAI,QAAQ,KAAK,eAAe,UAC/B,QAAO;;AAGT,QAAO"}
|
package/realtime/provider.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"provider.d.ts","names":[],"sources":["../../src/realtime/provider.tsx"],"sourcesContent":[],"mappings":";;;;KAuBK,gBAAA,WAA2B;KA+B3B,iBAAA;EA/BA,IAAA,EAAA,SAAA;EA+BA,SAAA,EAAA,MAAA,GAAiB,IAAA;EAOjB,SAAA,CAAA,EAAA,MAAA,GAAiB,IAAA;EAOjB,SAAA,CAAA,EAAA,MAAA,GAAkB,IAAA;AAAwC,CAAA;KAP1D,iBAAA,GAmBY;EAEV,IAAA,EAAA,SAAA;EAIY,YAAA,EAAA,MAAA,GAAA,IAAA;EAAK,SAAA,CAAA,EAAA,MAAA,GAAA,IAAA;EAGnB,MAAA,CAAA,EAAA,MAAA,GAAA,IAAA;CAGG;KAxBH,kBAAA,GAAqB,iBAyBX,GAzB+B,iBAyB/B;KAdV,qBAAA,GAgBiB;EACV,QAAA,EAhBD,KAAA,CAAM,SAgBL;EAAgB,KAAA,CAAA,EAAA,MAAA;EAKvB,IAAA,EAnBE,kBAmBkB,GAAA,IAAA;
|
|
1
|
+
{"version":3,"file":"provider.d.ts","names":[],"sources":["../../src/realtime/provider.tsx"],"sourcesContent":[],"mappings":";;;;KAuBK,gBAAA,WAA2B;KA+B3B,iBAAA;EA/BA,IAAA,EAAA,SAAA;EA+BA,SAAA,EAAA,MAAA,GAAiB,IAAA;EAOjB,SAAA,CAAA,EAAA,MAAA,GAAiB,IAAA;EAOjB,SAAA,CAAA,EAAA,MAAA,GAAkB,IAAA;AAAwC,CAAA;KAP1D,iBAAA,GAmBY;EAEV,IAAA,EAAA,SAAA;EAIY,YAAA,EAAA,MAAA,GAAA,IAAA;EAAK,SAAA,CAAA,EAAA,MAAA,GAAA,IAAA;EAGnB,MAAA,CAAA,EAAA,MAAA,GAAA,IAAA;CAGG;KAxBH,kBAAA,GAAqB,iBAyBX,GAzB+B,iBAyB/B;KAdV,qBAAA,GAgBiB;EACV,QAAA,EAhBD,KAAA,CAAM,SAgBL;EAAgB,KAAA,CAAA,EAAA,MAAA;EAKvB,IAAA,EAnBE,kBAmBkB,GAAA,IAAA;EAklBT,WAAA,CAAA,EAAA,OAAgB;EAC/B,SAAA,CAAA,EAAA,GAAA,GAAA,IAAA;EACA,YAAA,CAAA,EAAA,GAAA,GAAA,IAAA;EACA,OAAA,CAAA,EAAA,CAAA,KAAA,EApmBkB,KAomBlB,EAAA,GAAA,IAAA;CACA;KAlmBI,uBAAA,GAmmBJ;EACA,WAAA,EAAA,OAAA;EACA,YAAA,EAAA,OAAA;EACE,KAAA,EAnmBK,KAmmBL,GAAA,IAAA;EAAwB,IAAM,EAAA,CAAA,KAAA,EAlmBlB,gBAkmBkB,EAAA,GAAA,IAAA;EAAY,OAAA,EAAA,CAAA,IAAA,EAAA,MAAA,EAAA,GAAA,IAAA;EA+D7B,SAAA,EAAA,CAAA,OAAA,EA/pBM,gBA+pBmB,EAAA,GAAA,GAAA,GAAA,IAAA;aA9pB7B;;;;KAKP,oBAAA,GAAuB;;;;;;;;;iBAklBZ,gBAAA;;;;;;;;GAQb,wBAAwB,KAAA,CAAM;;;;iBA+DjB,qBAAA,CAAA,GAAyB"}
|
package/realtime/provider.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
|
|
4
3
|
import { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
|
|
5
4
|
import { jsx } from "react/jsx-runtime";
|
|
6
5
|
import { isValidEventType, validateRealtimeEvent } from "@cossistant/types/realtime-events";
|
|
@@ -136,7 +135,7 @@ function isHeartbeatTimedOut(lastHeartbeat, timeoutMs) {
|
|
|
136
135
|
function resolvePublicKey(explicit) {
|
|
137
136
|
const trimmed = explicit?.trim();
|
|
138
137
|
if (trimmed) return trimmed;
|
|
139
|
-
const normalized = (process.env.NEXT_PUBLIC_COSSISTANT_API_KEY || process.env.
|
|
138
|
+
const normalized = (process.env.NEXT_PUBLIC_COSSISTANT_API_KEY || process.env.COSSISTANT_API_KEY || null)?.trim();
|
|
140
139
|
return normalized && normalized.length > 0 ? normalized : null;
|
|
141
140
|
}
|
|
142
141
|
function normalizeAuth(auth) {
|