@cossistant/react 0.0.32 → 0.1.0
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 +3 -3
- package/hooks/index.d.ts +2 -2
- package/hooks/index.js +2 -2
- package/hooks/private/store/use-store-selector.d.ts.map +1 -1
- package/hooks/private/store/use-store-selector.js +8 -4
- package/hooks/private/store/use-store-selector.js.map +1 -1
- package/hooks/private/use-client-query.js +5 -2
- package/hooks/private/use-client-query.js.map +1 -1
- package/hooks/private/use-grouped-messages.d.ts +27 -2
- package/hooks/private/use-grouped-messages.d.ts.map +1 -1
- package/hooks/private/use-grouped-messages.js +154 -106
- package/hooks/private/use-grouped-messages.js.map +1 -1
- package/hooks/private/use-rest-client.js +2 -1
- package/hooks/private/use-rest-client.js.map +1 -1
- package/hooks/use-conversation-auto-seen.d.ts.map +1 -1
- package/hooks/use-conversation-auto-seen.js +9 -3
- package/hooks/use-conversation-auto-seen.js.map +1 -1
- package/hooks/use-conversation-page.d.ts.map +1 -1
- package/hooks/use-conversation-page.js +13 -3
- package/hooks/use-conversation-page.js.map +1 -1
- package/hooks/use-new-message-sound.d.ts.map +1 -1
- package/hooks/use-new-message-sound.js +2 -2
- package/hooks/use-new-message-sound.js.map +1 -1
- package/hooks/use-typing-sound.d.ts.map +1 -1
- package/hooks/use-typing-sound.js +2 -2
- package/hooks/use-typing-sound.js.map +1 -1
- package/index.d.ts +3 -3
- package/index.js +3 -3
- package/package.json +6 -12
- package/packages/tiny-markdown/src/context/index.d.ts +1 -0
- package/packages/tiny-markdown/src/context/tiny-markdown-context.d.ts +3 -0
- package/packages/tiny-markdown/src/hooks/index.d.ts +4 -0
- package/packages/tiny-markdown/src/hooks/use-caret-position.d.ts +1 -0
- package/packages/tiny-markdown/src/hooks/use-tiny-markdown.d.ts +1 -0
- package/packages/tiny-markdown/src/hooks/use-tiny-mention.d.ts +1 -0
- package/packages/tiny-markdown/src/hooks/use-tiny-shortcuts.d.ts +1 -0
- package/packages/tiny-markdown/src/index.d.ts +4 -0
- package/packages/tiny-markdown/src/types.d.ts +75 -0
- package/packages/tiny-markdown/src/types.d.ts.map +1 -0
- package/packages/tiny-markdown/src/utils/index.d.ts +3 -0
- package/packages/tiny-markdown/src/utils/markdown-parser.d.ts +1 -0
- package/packages/tiny-markdown/src/utils/mention-parser.d.ts +1 -0
- package/packages/tiny-markdown/src/utils/merge-refs.d.ts +1 -0
- package/packages/types/src/api/conversation.d.ts +300 -0
- package/packages/types/src/api/conversation.d.ts.map +1 -1
- package/packages/types/src/api/timeline-item.d.ts +225 -0
- package/packages/types/src/api/timeline-item.d.ts.map +1 -1
- package/packages/types/src/realtime-events.d.ts +228 -3
- package/packages/types/src/realtime-events.d.ts.map +1 -1
- package/packages/types/src/schemas.d.ts +75 -0
- package/packages/types/src/schemas.d.ts.map +1 -1
- package/primitives/avatar/image.d.ts +1 -1
- package/primitives/command-block-utils.d.ts +26 -0
- package/primitives/command-block-utils.d.ts.map +1 -0
- package/primitives/command-block-utils.js +310 -0
- package/primitives/command-block-utils.js.map +1 -0
- package/primitives/index.d.ts +7 -3
- package/primitives/index.js +11 -2
- package/primitives/index.parts.d.ts +6 -2
- package/primitives/index.parts.js +5 -1
- package/primitives/timeline-code-block.d.ts +32 -0
- package/primitives/timeline-code-block.d.ts.map +1 -0
- package/primitives/timeline-code-block.js +66 -0
- package/primitives/timeline-code-block.js.map +1 -0
- package/primitives/timeline-command-block.d.ts +29 -0
- package/primitives/timeline-command-block.d.ts.map +1 -0
- package/primitives/timeline-command-block.js +97 -0
- package/primitives/timeline-command-block.js.map +1 -0
- package/primitives/timeline-item-group.d.ts.map +1 -1
- package/primitives/timeline-item-group.js +5 -15
- package/primitives/timeline-item-group.js.map +1 -1
- package/primitives/timeline-item.d.ts +21 -1
- package/primitives/timeline-item.d.ts.map +1 -1
- package/primitives/timeline-item.js +148 -83
- package/primitives/timeline-item.js.map +1 -1
- package/primitives/timeline-message-layout.d.ts +9 -0
- package/primitives/timeline-message-layout.d.ts.map +1 -0
- package/primitives/timeline-message-layout.js +20 -0
- package/primitives/timeline-message-layout.js.map +1 -0
- package/provider.d.ts.map +1 -1
- package/provider.js +1 -7
- package/provider.js.map +1 -1
- package/realtime/event-filter.js +4 -3
- package/realtime/event-filter.js.map +1 -1
- package/realtime/provider.d.ts +0 -1
- package/realtime/provider.d.ts.map +1 -1
- package/realtime/provider.js +29 -34
- package/realtime/provider.js.map +1 -1
- package/sounds/sound-data.d.ts +6 -0
- package/sounds/sound-data.d.ts.map +1 -0
- package/sounds/sound-data.js +7 -0
- package/sounds/sound-data.js.map +1 -0
- package/styles.css +2 -0
- package/support/components/avatar.js +3 -3
- package/support/components/avatar.js.map +1 -1
- package/support/components/button.d.ts +2 -2
- package/support/components/button.d.ts.map +1 -1
- package/support/components/button.js +1 -0
- package/support/components/button.js.map +1 -1
- package/support/components/conversation-event.d.ts +3 -0
- package/support/components/conversation-event.d.ts.map +1 -1
- package/support/components/conversation-event.js +47 -16
- package/support/components/conversation-event.js.map +1 -1
- package/support/components/conversation-timeline.d.ts.map +1 -1
- package/support/components/conversation-timeline.js +12 -0
- package/support/components/conversation-timeline.js.map +1 -1
- package/support/components/index.d.ts +2 -1
- package/support/components/index.js +2 -1
- package/support/components/timeline-activity-group.d.ts +25 -0
- package/support/components/timeline-activity-group.d.ts.map +1 -0
- package/support/components/timeline-activity-group.js +104 -0
- package/support/components/timeline-activity-group.js.map +1 -0
- package/support/components/timeline-code-block.d.ts +14 -0
- package/support/components/timeline-code-block.d.ts.map +1 -0
- package/support/components/timeline-code-block.js +44 -0
- package/support/components/timeline-code-block.js.map +1 -0
- package/support/components/timeline-command-block.d.ts +12 -0
- package/support/components/timeline-command-block.d.ts.map +1 -0
- package/support/components/timeline-command-block.js +42 -0
- package/support/components/timeline-command-block.js.map +1 -0
- package/support/components/timeline-message-item.d.ts +2 -1
- package/support/components/timeline-message-item.d.ts.map +1 -1
- package/support/components/timeline-message-item.js +23 -3
- package/support/components/timeline-message-item.js.map +1 -1
- package/support/pages/home.js +1 -1
- package/support/pages/home.js.map +1 -1
- package/support/store/support-store.d.ts.map +1 -1
- package/support/store/support-store.js +4 -4
- package/support/store/support-store.js.map +1 -1
- package/support/{support-DmViRaga.css → support-Dc5L__HC.css} +15 -15
- package/support/{support-DmViRaga.css.map → support-Dc5L__HC.css.map} +1 -1
- package/{tailwind.css → support/support.css} +14 -14
- package/support-config.d.ts +31 -4
- package/support-config.d.ts.map +1 -1
- package/support-config.js +52 -4
- package/support-config.js.map +1 -1
- package/support.css +1 -2
- package/utils/metadata-hash.d.ts +1 -1
- package/utils/metadata-hash.js +9 -4
- package/utils/metadata-hash.js.map +1 -1
- package/utils/timeline-item-sender.d.ts +17 -0
- package/utils/timeline-item-sender.d.ts.map +1 -0
- package/utils/timeline-item-sender.js +43 -0
- package/utils/timeline-item-sender.js.map +1 -0
- package/utils/use-render-element.d.ts.map +1 -1
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { useRenderElement } from "../utils/use-render-element.js";
|
|
2
|
+
import { mapCommandVariants, mapInlineCommandFromParagraphChildren } from "./command-block-utils.js";
|
|
3
|
+
import { TimelineCodeBlock } from "./timeline-code-block.js";
|
|
4
|
+
import { TimelineCommandBlock } from "./timeline-command-block.js";
|
|
2
5
|
import * as React$1 from "react";
|
|
3
|
-
import { jsx } from "react/jsx-runtime";
|
|
4
|
-
import { hasMarkdownFormatting } from "@cossistant/tiny-markdown/utils";
|
|
5
|
-
import ReactMarkdown from "react-markdown";
|
|
6
|
-
import remarkBreaks from "remark-breaks";
|
|
6
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
7
|
+
import { hasMarkdownFormatting, parseMarkdown } from "@cossistant/tiny-markdown/utils";
|
|
7
8
|
|
|
8
9
|
//#region src/primitives/timeline-item.tsx
|
|
9
10
|
/**
|
|
@@ -52,87 +53,147 @@ const TimelineItem = (() => {
|
|
|
52
53
|
Component.displayName = "TimelineItem";
|
|
53
54
|
return Component;
|
|
54
55
|
})();
|
|
55
|
-
|
|
56
|
-
if (!
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
56
|
+
function parseMentionHref(href) {
|
|
57
|
+
if (!href.startsWith("mention:")) return null;
|
|
58
|
+
const parts = href.split(":");
|
|
59
|
+
const mentionType = parts[1];
|
|
60
|
+
const mentionId = parts.slice(2).join(":");
|
|
61
|
+
if (!(mentionType && mentionId)) return null;
|
|
62
|
+
return {
|
|
63
|
+
mentionType,
|
|
64
|
+
mentionId
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
function renderInlineCode(code, key, renderers) {
|
|
68
|
+
if (renderers?.inlineCode) return /* @__PURE__ */ jsx(React$1.Fragment, { children: renderers.inlineCode({ code }) }, key);
|
|
69
|
+
return /* @__PURE__ */ jsx("code", { children: code }, key);
|
|
70
|
+
}
|
|
71
|
+
function renderCodeBlock(props, key, renderers) {
|
|
72
|
+
if (renderers?.codeBlock) return /* @__PURE__ */ jsx(React$1.Fragment, { children: renderers.codeBlock(props) }, key);
|
|
73
|
+
return /* @__PURE__ */ jsx(TimelineCodeBlock, {
|
|
74
|
+
code: props.code,
|
|
75
|
+
fileName: props.fileName,
|
|
76
|
+
language: props.language
|
|
77
|
+
}, key);
|
|
78
|
+
}
|
|
79
|
+
function renderCommandBlock(props, key, renderers) {
|
|
80
|
+
if (renderers?.commandBlock) return /* @__PURE__ */ jsx(React$1.Fragment, { children: renderers.commandBlock(props) }, key);
|
|
81
|
+
return /* @__PURE__ */ jsx(TimelineCommandBlock, { commands: props.commands }, key);
|
|
82
|
+
}
|
|
83
|
+
function hasNonWhitespaceParagraphContent(children) {
|
|
84
|
+
return children.some((child) => child.type !== "text" || child.content.trim().length > 0);
|
|
85
|
+
}
|
|
86
|
+
function renderMarkdownToken(token, key, renderers) {
|
|
87
|
+
switch (token.type) {
|
|
88
|
+
case "text": return token.content;
|
|
89
|
+
case "strong": return /* @__PURE__ */ jsx("strong", {
|
|
90
|
+
className: "font-semibold",
|
|
91
|
+
children: token.children.map((child, index) => renderMarkdownToken(child, `${key}-${index}`, renderers))
|
|
92
|
+
}, key);
|
|
93
|
+
case "em": return /* @__PURE__ */ jsx("em", {
|
|
94
|
+
className: "italic",
|
|
95
|
+
children: token.children.map((child, index) => renderMarkdownToken(child, `${key}-${index}`, renderers))
|
|
96
|
+
}, key);
|
|
97
|
+
case "code": {
|
|
98
|
+
if (token.inline) return renderInlineCode(token.content, key, renderers);
|
|
99
|
+
const commandVariants = mapCommandVariants(token.content);
|
|
100
|
+
if (commandVariants) return renderCommandBlock({
|
|
101
|
+
command: token.content,
|
|
102
|
+
commands: commandVariants
|
|
103
|
+
}, key, renderers);
|
|
104
|
+
return renderCodeBlock({
|
|
105
|
+
code: token.content,
|
|
106
|
+
fileName: token.fileName,
|
|
107
|
+
language: token.language
|
|
108
|
+
}, key, renderers);
|
|
109
|
+
}
|
|
110
|
+
case "p": {
|
|
111
|
+
const inlineCommand = mapInlineCommandFromParagraphChildren(token.children);
|
|
112
|
+
if (inlineCommand) {
|
|
113
|
+
const beforeChildren = token.children.slice(0, inlineCommand.index);
|
|
114
|
+
const afterChildren = token.children.slice(inlineCommand.index + 1);
|
|
115
|
+
const hasBefore = hasNonWhitespaceParagraphContent(beforeChildren);
|
|
116
|
+
const hasAfter = hasNonWhitespaceParagraphContent(afterChildren);
|
|
117
|
+
if (!(hasBefore || hasAfter)) return renderCommandBlock({
|
|
118
|
+
command: inlineCommand.command,
|
|
119
|
+
commands: inlineCommand.variants
|
|
120
|
+
}, key, renderers);
|
|
121
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
65
122
|
className: "mt-1 block first:mt-0",
|
|
66
|
-
children
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
});
|
|
81
|
-
},
|
|
82
|
-
strong: ({ children }) => /* @__PURE__ */ jsx("strong", {
|
|
83
|
-
className: "font-semibold",
|
|
84
|
-
children
|
|
85
|
-
}),
|
|
86
|
-
ol: ({ children }) => /* @__PURE__ */ jsx("ol", {
|
|
87
|
-
className: "my-0 list-decimal pl-6",
|
|
88
|
-
children
|
|
89
|
-
}),
|
|
90
|
-
ul: ({ children }) => /* @__PURE__ */ jsx("ul", {
|
|
91
|
-
className: "my-0 list-disc pl-6",
|
|
92
|
-
children
|
|
93
|
-
}),
|
|
94
|
-
li: ({ children }) => /* @__PURE__ */ jsx("li", {
|
|
95
|
-
className: "[&>span.block]:mt-0 [&>span.block]:inline",
|
|
96
|
-
children
|
|
97
|
-
}),
|
|
98
|
-
blockquote: ({ children }) => /* @__PURE__ */ jsx("blockquote", {
|
|
99
|
-
className: "my-1 border-co-border border-l-2 pl-3 italic opacity-80",
|
|
100
|
-
children
|
|
101
|
-
}),
|
|
102
|
-
em: ({ children }) => /* @__PURE__ */ jsx("em", {
|
|
103
|
-
className: "italic",
|
|
104
|
-
children
|
|
105
|
-
}),
|
|
106
|
-
a: ({ href, children, node }) => {
|
|
107
|
-
const rawHref = href || node?.properties?.href || "";
|
|
108
|
-
if (rawHref.startsWith("mention:")) {
|
|
109
|
-
const parts = rawHref.split(":");
|
|
110
|
-
const mentionType = parts[1];
|
|
111
|
-
const mentionId = parts.slice(2).join(":");
|
|
112
|
-
return /* @__PURE__ */ jsx("span", {
|
|
113
|
-
className: "rounded bg-co-orange/15 font-medium text-co-orange",
|
|
114
|
-
"data-mention-id": mentionId,
|
|
115
|
-
"data-mention-type": mentionType,
|
|
116
|
-
children
|
|
117
|
-
});
|
|
118
|
-
}
|
|
119
|
-
return /* @__PURE__ */ jsx("a", {
|
|
120
|
-
className: "underline hover:opacity-80",
|
|
121
|
-
href,
|
|
122
|
-
rel: "noopener noreferrer",
|
|
123
|
-
target: "_blank",
|
|
124
|
-
children
|
|
125
|
-
});
|
|
123
|
+
children: [
|
|
124
|
+
hasBefore ? /* @__PURE__ */ jsx("span", {
|
|
125
|
+
className: "block",
|
|
126
|
+
children: beforeChildren.map((child, index) => renderMarkdownToken(child, `${key}-before-${index}`, renderers))
|
|
127
|
+
}) : null,
|
|
128
|
+
renderCommandBlock({
|
|
129
|
+
command: inlineCommand.command,
|
|
130
|
+
commands: inlineCommand.variants
|
|
131
|
+
}, `${key}-command`, renderers),
|
|
132
|
+
hasAfter ? /* @__PURE__ */ jsx("span", {
|
|
133
|
+
className: "mt-1 block",
|
|
134
|
+
children: afterChildren.map((child, index) => renderMarkdownToken(child, `${key}-after-${index}`, renderers))
|
|
135
|
+
}) : null
|
|
136
|
+
]
|
|
137
|
+
}, key);
|
|
126
138
|
}
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
139
|
+
return /* @__PURE__ */ jsx("span", {
|
|
140
|
+
className: "mt-1 block first:mt-0",
|
|
141
|
+
children: token.children.map((child, index) => renderMarkdownToken(child, `${key}-${index}`, renderers))
|
|
142
|
+
}, key);
|
|
143
|
+
}
|
|
144
|
+
case "blockquote": return /* @__PURE__ */ jsx("blockquote", {
|
|
145
|
+
className: "my-1 border-co-border border-l-2 pl-3 italic opacity-80",
|
|
146
|
+
children: token.children.map((child, index) => renderMarkdownToken(child, `${key}-${index}`, renderers))
|
|
147
|
+
}, key);
|
|
148
|
+
case "ul": return /* @__PURE__ */ jsx("ul", {
|
|
149
|
+
className: "my-0 list-disc pl-6",
|
|
150
|
+
children: token.children.map((child, index) => renderMarkdownToken(child, `${key}-${index}`, renderers))
|
|
151
|
+
}, key);
|
|
152
|
+
case "ol": return /* @__PURE__ */ jsx("ol", {
|
|
153
|
+
className: "my-0 list-decimal pl-6",
|
|
154
|
+
children: token.children.map((child, index) => renderMarkdownToken(child, `${key}-${index}`, renderers))
|
|
155
|
+
}, key);
|
|
156
|
+
case "li": return /* @__PURE__ */ jsx("li", {
|
|
157
|
+
className: "[&>span.block]:mt-0 [&>span.block]:inline",
|
|
158
|
+
children: token.children.map((child, index) => renderMarkdownToken(child, `${key}-${index}`, renderers))
|
|
159
|
+
}, key);
|
|
160
|
+
case "a": {
|
|
161
|
+
const mention = parseMentionHref(token.href);
|
|
162
|
+
if (mention) return /* @__PURE__ */ jsx("span", {
|
|
163
|
+
className: "rounded bg-co-orange/15 font-medium text-co-orange",
|
|
164
|
+
"data-mention-id": mention.mentionId,
|
|
165
|
+
"data-mention-type": mention.mentionType,
|
|
166
|
+
children: token.children.map((child, index) => renderMarkdownToken(child, `${key}-${index}`, renderers))
|
|
167
|
+
}, key);
|
|
168
|
+
return /* @__PURE__ */ jsx("a", {
|
|
169
|
+
className: "underline hover:opacity-80",
|
|
170
|
+
href: token.href,
|
|
171
|
+
rel: "noopener noreferrer",
|
|
172
|
+
target: "_blank",
|
|
173
|
+
children: token.children.map((child, index) => renderMarkdownToken(child, `${key}-${index}`, renderers))
|
|
174
|
+
}, key);
|
|
175
|
+
}
|
|
176
|
+
case "mention": return /* @__PURE__ */ jsxs("span", {
|
|
177
|
+
className: "rounded bg-co-orange/15 font-medium text-co-orange",
|
|
178
|
+
"data-mention-id": token.mention.id,
|
|
179
|
+
"data-mention-type": token.mention.type,
|
|
180
|
+
children: ["@", token.mention.name]
|
|
181
|
+
}, key);
|
|
182
|
+
case "header": return /* @__PURE__ */ jsx("span", {
|
|
183
|
+
className: token.level === 1 ? "mt-1 block text-base font-semibold first:mt-0" : token.level === 2 ? "mt-1 block text-sm font-semibold first:mt-0" : "mt-1 block text-sm font-medium first:mt-0",
|
|
184
|
+
children: token.children.map((child, index) => renderMarkdownToken(child, `${key}-${index}`, renderers))
|
|
185
|
+
}, key);
|
|
186
|
+
case "br": return /* @__PURE__ */ jsx("br", {}, key);
|
|
187
|
+
default: return null;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
const MemoizedMarkdownBlock = React$1.memo(({ content, markdownRenderers }) => {
|
|
191
|
+
if (!hasMarkdownFormatting(content)) return /* @__PURE__ */ jsx("span", {
|
|
192
|
+
className: "whitespace-pre-wrap break-words",
|
|
130
193
|
children: content
|
|
131
194
|
});
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
return true;
|
|
135
|
-
});
|
|
195
|
+
return /* @__PURE__ */ jsx(Fragment, { children: parseMarkdown(content).map((token, index) => renderMarkdownToken(token, `markdown-${index}`, markdownRenderers)) });
|
|
196
|
+
}, (prevProps, nextProps) => prevProps.content === nextProps.content && prevProps.markdownRenderers === nextProps.markdownRenderers);
|
|
136
197
|
MemoizedMarkdownBlock.displayName = "MemoizedMarkdownBlock";
|
|
137
198
|
/**
|
|
138
199
|
* Renders the content of a timeline item, optionally piping Markdown content through a
|
|
@@ -140,15 +201,19 @@ MemoizedMarkdownBlock.displayName = "MemoizedMarkdownBlock";
|
|
|
140
201
|
* formatting.
|
|
141
202
|
*/
|
|
142
203
|
const TimelineItemContent = (() => {
|
|
143
|
-
const Component = React$1.forwardRef(({ children, className, asChild = false, text = "", renderMarkdown = true, ...props }, ref) => {
|
|
204
|
+
const Component = React$1.forwardRef(({ children, className, asChild = false, text = "", renderMarkdown = true, markdownRenderers, ...props }, ref) => {
|
|
144
205
|
const content = React$1.useMemo(() => {
|
|
145
206
|
const textContent = text ?? "";
|
|
146
207
|
if (typeof children === "function") return children(textContent);
|
|
147
208
|
if (children) return children;
|
|
148
|
-
if (renderMarkdown && textContent) return /* @__PURE__ */ jsx(MemoizedMarkdownBlock, {
|
|
209
|
+
if (renderMarkdown && textContent) return /* @__PURE__ */ jsx(MemoizedMarkdownBlock, {
|
|
210
|
+
content: textContent,
|
|
211
|
+
markdownRenderers
|
|
212
|
+
});
|
|
149
213
|
return textContent;
|
|
150
214
|
}, [
|
|
151
215
|
children,
|
|
216
|
+
markdownRenderers,
|
|
152
217
|
text,
|
|
153
218
|
renderMarkdown
|
|
154
219
|
]);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"timeline-item.js","names":["React","renderProps: TimelineItemRenderProps"],"sources":["../../src/primitives/timeline-item.tsx"],"sourcesContent":["import { hasMarkdownFormatting } from \"@cossistant/tiny-markdown/utils\";\nimport 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\" | \"tool\";\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 * message, event, identification, and tool 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 (item.type === \"tool\") {\n\t\t\t\t\treturn \"Tool call\";\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\" : item.type === \"tool\" ? \"Tool call\" : \"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\tconst shouldRenderMarkdown = hasMarkdownFormatting(content);\n\n\t\tif (!shouldRenderMarkdown) {\n\t\t\treturn <span className=\"whitespace-pre-wrap break-words\">{content}</span>;\n\t\t}\n\n\t\treturn (\n\t\t\t<ReactMarkdown\n\t\t\t\t// Allow mention: protocol URLs (not sanitized by default)\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 - with special handling for mentions\n\t\t\t\t\t// Mention format: [@Name](mention:type:id) - the @ is inside the link\n\t\t\t\t\ta: ({ href, children, node }) => {\n\t\t\t\t\t\t// Get the raw href from the AST node if available (react-markdown may sanitize href)\n\t\t\t\t\t\tconst rawHref =\n\t\t\t\t\t\t\thref || (node?.properties?.href as string | undefined) || \"\";\n\n\t\t\t\t\t\t// Check if this is a mention link: mention:type:id\n\t\t\t\t\t\tif (rawHref.startsWith(\"mention:\")) {\n\t\t\t\t\t\t\t// Parse mention:type:id format\n\t\t\t\t\t\t\tconst parts = rawHref.split(\":\");\n\t\t\t\t\t\t\tconst mentionType = parts[1]; // visitor, ai-agent, human-agent\n\t\t\t\t\t\t\tconst mentionId = parts.slice(2).join(\":\"); // id (may contain colons)\n\n\t\t\t\t\t\t\t// Render as styled orange pill (same design as input)\n\t\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t\t<span\n\t\t\t\t\t\t\t\t\tclassName=\"rounded bg-co-orange/15 font-medium text-co-orange\"\n\t\t\t\t\t\t\t\t\tdata-mention-id={mentionId}\n\t\t\t\t\t\t\t\t\tdata-mention-type={mentionType}\n\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t{children}\n\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Regular link\n\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t<a\n\t\t\t\t\t\t\t\tclassName=\"underline hover:opacity-80\"\n\t\t\t\t\t\t\t\thref={href}\n\t\t\t\t\t\t\t\trel=\"noopener noreferrer\"\n\t\t\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t{children}\n\t\t\t\t\t\t\t</a>\n\t\t\t\t\t\t);\n\t\t\t\t\t},\n\t\t\t\t}}\n\t\t\t\tremarkPlugins={[remarkBreaks]}\n\t\t\t\turlTransform={(url) => url}\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 { MarkdownToken } from \"@cossistant/tiny-markdown\";\nimport {\n\thasMarkdownFormatting,\n\tparseMarkdown,\n} from \"@cossistant/tiny-markdown/utils\";\nimport type { TimelineItem as TimelineItemType } from \"@cossistant/types/api/timeline-item\";\nimport * as React from \"react\";\nimport { useRenderElement } from \"../utils/use-render-element\";\nimport {\n\ttype CommandVariants,\n\tmapCommandVariants,\n\tmapInlineCommandFromParagraphChildren,\n} from \"./command-block-utils\";\nimport { TimelineCodeBlock } from \"./timeline-code-block\";\nimport { TimelineCommandBlock } from \"./timeline-command-block\";\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\" | \"tool\";\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 * message, event, identification, and tool 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 (item.type === \"tool\") {\n\t\t\t\t\treturn \"Tool call\";\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\" : item.type === \"tool\" ? \"Tool call\" : \"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\nfunction parseMentionHref(\n\thref: string\n): { mentionType: string; mentionId: string } | null {\n\tif (!href.startsWith(\"mention:\")) {\n\t\treturn null;\n\t}\n\n\tconst parts = href.split(\":\");\n\tconst mentionType = parts[1];\n\tconst mentionId = parts.slice(2).join(\":\");\n\n\tif (!(mentionType && mentionId)) {\n\t\treturn null;\n\t}\n\n\treturn { mentionType, mentionId };\n}\n\nexport type TimelineInlineCodeRendererProps = {\n\tcode: string;\n};\n\nexport type TimelineCodeBlockRendererProps = {\n\tcode: string;\n\tlanguage?: string;\n\tfileName?: string;\n};\n\nexport type TimelineCommandBlockRendererProps = {\n\tcommand: string;\n\tcommands: CommandVariants;\n};\n\nexport type TimelineItemContentMarkdownRenderers = {\n\tinlineCode?: (props: TimelineInlineCodeRendererProps) => React.ReactNode;\n\tcodeBlock?: (props: TimelineCodeBlockRendererProps) => React.ReactNode;\n\tcommandBlock?: (props: TimelineCommandBlockRendererProps) => React.ReactNode;\n};\n\nfunction renderInlineCode(\n\tcode: string,\n\tkey: string,\n\trenderers?: TimelineItemContentMarkdownRenderers\n): React.ReactNode {\n\tif (renderers?.inlineCode) {\n\t\treturn (\n\t\t\t<React.Fragment key={key}>\n\t\t\t\t{renderers.inlineCode({ code })}\n\t\t\t</React.Fragment>\n\t\t);\n\t}\n\n\treturn <code key={key}>{code}</code>;\n}\n\nfunction renderCodeBlock(\n\tprops: TimelineCodeBlockRendererProps,\n\tkey: string,\n\trenderers?: TimelineItemContentMarkdownRenderers\n): React.ReactNode {\n\tif (renderers?.codeBlock) {\n\t\treturn (\n\t\t\t<React.Fragment key={key}>{renderers.codeBlock(props)}</React.Fragment>\n\t\t);\n\t}\n\n\treturn (\n\t\t<TimelineCodeBlock\n\t\t\tcode={props.code}\n\t\t\tfileName={props.fileName}\n\t\t\tkey={key}\n\t\t\tlanguage={props.language}\n\t\t/>\n\t);\n}\n\nfunction renderCommandBlock(\n\tprops: TimelineCommandBlockRendererProps,\n\tkey: string,\n\trenderers?: TimelineItemContentMarkdownRenderers\n): React.ReactNode {\n\tif (renderers?.commandBlock) {\n\t\treturn (\n\t\t\t<React.Fragment key={key}>{renderers.commandBlock(props)}</React.Fragment>\n\t\t);\n\t}\n\n\treturn <TimelineCommandBlock commands={props.commands} key={key} />;\n}\n\nfunction hasNonWhitespaceParagraphContent(children: MarkdownToken[]): boolean {\n\treturn children.some(\n\t\t(child) => child.type !== \"text\" || child.content.trim().length > 0\n\t);\n}\n\nfunction renderMarkdownToken(\n\ttoken: MarkdownToken,\n\tkey: string,\n\trenderers?: TimelineItemContentMarkdownRenderers\n): React.ReactNode {\n\tswitch (token.type) {\n\t\tcase \"text\":\n\t\t\treturn token.content;\n\t\tcase \"strong\":\n\t\t\treturn (\n\t\t\t\t<strong className=\"font-semibold\" key={key}>\n\t\t\t\t\t{token.children.map((child, index) =>\n\t\t\t\t\t\trenderMarkdownToken(child, `${key}-${index}`, renderers)\n\t\t\t\t\t)}\n\t\t\t\t</strong>\n\t\t\t);\n\t\tcase \"em\":\n\t\t\treturn (\n\t\t\t\t<em className=\"italic\" key={key}>\n\t\t\t\t\t{token.children.map((child, index) =>\n\t\t\t\t\t\trenderMarkdownToken(child, `${key}-${index}`, renderers)\n\t\t\t\t\t)}\n\t\t\t\t</em>\n\t\t\t);\n\t\tcase \"code\": {\n\t\t\tif (token.inline) {\n\t\t\t\treturn renderInlineCode(token.content, key, renderers);\n\t\t\t}\n\n\t\t\tconst commandVariants = mapCommandVariants(token.content);\n\t\t\tif (commandVariants) {\n\t\t\t\treturn renderCommandBlock(\n\t\t\t\t\t{\n\t\t\t\t\t\tcommand: token.content,\n\t\t\t\t\t\tcommands: commandVariants,\n\t\t\t\t\t},\n\t\t\t\t\tkey,\n\t\t\t\t\trenderers\n\t\t\t\t);\n\t\t\t}\n\n\t\t\treturn renderCodeBlock(\n\t\t\t\t{\n\t\t\t\t\tcode: token.content,\n\t\t\t\t\tfileName: token.fileName,\n\t\t\t\t\tlanguage: token.language,\n\t\t\t\t},\n\t\t\t\tkey,\n\t\t\t\trenderers\n\t\t\t);\n\t\t}\n\t\tcase \"p\": {\n\t\t\tconst inlineCommand = mapInlineCommandFromParagraphChildren(\n\t\t\t\ttoken.children\n\t\t\t);\n\t\t\tif (inlineCommand) {\n\t\t\t\tconst beforeChildren = token.children.slice(0, inlineCommand.index);\n\t\t\t\tconst afterChildren = token.children.slice(inlineCommand.index + 1);\n\t\t\t\tconst hasBefore = hasNonWhitespaceParagraphContent(beforeChildren);\n\t\t\t\tconst hasAfter = hasNonWhitespaceParagraphContent(afterChildren);\n\n\t\t\t\tif (!(hasBefore || hasAfter)) {\n\t\t\t\t\treturn renderCommandBlock(\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tcommand: inlineCommand.command,\n\t\t\t\t\t\t\tcommands: inlineCommand.variants,\n\t\t\t\t\t\t},\n\t\t\t\t\t\tkey,\n\t\t\t\t\t\trenderers\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t\treturn (\n\t\t\t\t\t<div className=\"mt-1 block first:mt-0\" key={key}>\n\t\t\t\t\t\t{hasBefore ? (\n\t\t\t\t\t\t\t<span className=\"block\">\n\t\t\t\t\t\t\t\t{beforeChildren.map((child, index) =>\n\t\t\t\t\t\t\t\t\trenderMarkdownToken(\n\t\t\t\t\t\t\t\t\t\tchild,\n\t\t\t\t\t\t\t\t\t\t`${key}-before-${index}`,\n\t\t\t\t\t\t\t\t\t\trenderers\n\t\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t) : null}\n\n\t\t\t\t\t\t{renderCommandBlock(\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tcommand: inlineCommand.command,\n\t\t\t\t\t\t\t\tcommands: inlineCommand.variants,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t`${key}-command`,\n\t\t\t\t\t\t\trenderers\n\t\t\t\t\t\t)}\n\n\t\t\t\t\t\t{hasAfter ? (\n\t\t\t\t\t\t\t<span className=\"mt-1 block\">\n\t\t\t\t\t\t\t\t{afterChildren.map((child, index) =>\n\t\t\t\t\t\t\t\t\trenderMarkdownToken(child, `${key}-after-${index}`, renderers)\n\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t) : null}\n\t\t\t\t\t</div>\n\t\t\t\t);\n\t\t\t}\n\n\t\t\treturn (\n\t\t\t\t<span className=\"mt-1 block first:mt-0\" key={key}>\n\t\t\t\t\t{token.children.map((child, index) =>\n\t\t\t\t\t\trenderMarkdownToken(child, `${key}-${index}`, renderers)\n\t\t\t\t\t)}\n\t\t\t\t</span>\n\t\t\t);\n\t\t}\n\t\tcase \"blockquote\":\n\t\t\treturn (\n\t\t\t\t<blockquote\n\t\t\t\t\tclassName=\"my-1 border-co-border border-l-2 pl-3 italic opacity-80\"\n\t\t\t\t\tkey={key}\n\t\t\t\t>\n\t\t\t\t\t{token.children.map((child, index) =>\n\t\t\t\t\t\trenderMarkdownToken(child, `${key}-${index}`, renderers)\n\t\t\t\t\t)}\n\t\t\t\t</blockquote>\n\t\t\t);\n\t\tcase \"ul\":\n\t\t\treturn (\n\t\t\t\t<ul className=\"my-0 list-disc pl-6\" key={key}>\n\t\t\t\t\t{token.children.map((child, index) =>\n\t\t\t\t\t\trenderMarkdownToken(child, `${key}-${index}`, renderers)\n\t\t\t\t\t)}\n\t\t\t\t</ul>\n\t\t\t);\n\t\tcase \"ol\":\n\t\t\treturn (\n\t\t\t\t<ol className=\"my-0 list-decimal pl-6\" key={key}>\n\t\t\t\t\t{token.children.map((child, index) =>\n\t\t\t\t\t\trenderMarkdownToken(child, `${key}-${index}`, renderers)\n\t\t\t\t\t)}\n\t\t\t\t</ol>\n\t\t\t);\n\t\tcase \"li\":\n\t\t\treturn (\n\t\t\t\t<li className=\"[&>span.block]:mt-0 [&>span.block]:inline\" key={key}>\n\t\t\t\t\t{token.children.map((child, index) =>\n\t\t\t\t\t\trenderMarkdownToken(child, `${key}-${index}`, renderers)\n\t\t\t\t\t)}\n\t\t\t\t</li>\n\t\t\t);\n\t\tcase \"a\": {\n\t\t\tconst mention = parseMentionHref(token.href);\n\n\t\t\tif (mention) {\n\t\t\t\treturn (\n\t\t\t\t\t<span\n\t\t\t\t\t\tclassName=\"rounded bg-co-orange/15 font-medium text-co-orange\"\n\t\t\t\t\t\tdata-mention-id={mention.mentionId}\n\t\t\t\t\t\tdata-mention-type={mention.mentionType}\n\t\t\t\t\t\tkey={key}\n\t\t\t\t\t>\n\t\t\t\t\t\t{token.children.map((child, index) =>\n\t\t\t\t\t\t\trenderMarkdownToken(child, `${key}-${index}`, renderers)\n\t\t\t\t\t\t)}\n\t\t\t\t\t</span>\n\t\t\t\t);\n\t\t\t}\n\n\t\t\treturn (\n\t\t\t\t<a\n\t\t\t\t\tclassName=\"underline hover:opacity-80\"\n\t\t\t\t\thref={token.href}\n\t\t\t\t\tkey={key}\n\t\t\t\t\trel=\"noopener noreferrer\"\n\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t>\n\t\t\t\t\t{token.children.map((child, index) =>\n\t\t\t\t\t\trenderMarkdownToken(child, `${key}-${index}`, renderers)\n\t\t\t\t\t)}\n\t\t\t\t</a>\n\t\t\t);\n\t\t}\n\t\tcase \"mention\":\n\t\t\treturn (\n\t\t\t\t<span\n\t\t\t\t\tclassName=\"rounded bg-co-orange/15 font-medium text-co-orange\"\n\t\t\t\t\tdata-mention-id={token.mention.id}\n\t\t\t\t\tdata-mention-type={token.mention.type}\n\t\t\t\t\tkey={key}\n\t\t\t\t>\n\t\t\t\t\t@{token.mention.name}\n\t\t\t\t</span>\n\t\t\t);\n\t\tcase \"header\": {\n\t\t\tconst headerClass =\n\t\t\t\ttoken.level === 1\n\t\t\t\t\t? \"mt-1 block text-base font-semibold first:mt-0\"\n\t\t\t\t\t: token.level === 2\n\t\t\t\t\t\t? \"mt-1 block text-sm font-semibold first:mt-0\"\n\t\t\t\t\t\t: \"mt-1 block text-sm font-medium first:mt-0\";\n\n\t\t\treturn (\n\t\t\t\t<span className={headerClass} key={key}>\n\t\t\t\t\t{token.children.map((child, index) =>\n\t\t\t\t\t\trenderMarkdownToken(child, `${key}-${index}`, renderers)\n\t\t\t\t\t)}\n\t\t\t\t</span>\n\t\t\t);\n\t\t}\n\t\tcase \"br\":\n\t\t\treturn <br key={key} />;\n\t\tdefault:\n\t\t\treturn null;\n\t}\n}\n\nconst MemoizedMarkdownBlock = React.memo(\n\t({\n\t\tcontent,\n\t\tmarkdownRenderers,\n\t}: {\n\t\tcontent: string;\n\t\tmarkdownRenderers?: TimelineItemContentMarkdownRenderers;\n\t}) => {\n\t\tconst shouldRenderMarkdown = hasMarkdownFormatting(content);\n\n\t\tif (!shouldRenderMarkdown) {\n\t\t\treturn <span className=\"whitespace-pre-wrap break-words\">{content}</span>;\n\t\t}\n\n\t\tconst tokens = parseMarkdown(content);\n\n\t\treturn (\n\t\t\t<>\n\t\t\t\t{tokens.map((token, index) =>\n\t\t\t\t\trenderMarkdownToken(token, `markdown-${index}`, markdownRenderers)\n\t\t\t\t)}\n\t\t\t</>\n\t\t);\n\t},\n\t(prevProps, nextProps) =>\n\t\tprevProps.content === nextProps.content &&\n\t\tprevProps.markdownRenderers === nextProps.markdownRenderers\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\tmarkdownRenderers?: TimelineItemContentMarkdownRenderers;\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\tmarkdownRenderers,\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 (\n\t\t\t\t\t\t<MemoizedMarkdownBlock\n\t\t\t\t\t\t\tcontent={textContent}\n\t\t\t\t\t\t\tmarkdownRenderers={markdownRenderers}\n\t\t\t\t\t\t/>\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\treturn textContent;\n\t\t\t}, [children, markdownRenderers, 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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"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
//#region src/primitives/timeline-message-layout.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Detects whether message text includes fenced code content that should use
|
|
4
|
+
* the expanded timeline bubble layout.
|
|
5
|
+
*/
|
|
6
|
+
declare function hasExpandedTimelineContent(text: string | null | undefined): boolean;
|
|
7
|
+
//#endregion
|
|
8
|
+
export { hasExpandedTimelineContent };
|
|
9
|
+
//# sourceMappingURL=timeline-message-layout.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"timeline-message-layout.d.ts","names":[],"sources":["../../src/primitives/timeline-message-layout.ts"],"sourcesContent":[],"mappings":";;AAOA;;;iBAAgB,0BAAA"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { mapInlineCommandFromParagraphChildren } from "./command-block-utils.js";
|
|
2
|
+
import { parseMarkdown } from "@cossistant/tiny-markdown/utils";
|
|
3
|
+
|
|
4
|
+
//#region src/primitives/timeline-message-layout.ts
|
|
5
|
+
/**
|
|
6
|
+
* Detects whether message text includes fenced code content that should use
|
|
7
|
+
* the expanded timeline bubble layout.
|
|
8
|
+
*/
|
|
9
|
+
function hasExpandedTimelineContent(text) {
|
|
10
|
+
if (!text || text.trim().length === 0) return false;
|
|
11
|
+
return parseMarkdown(text).some((token) => {
|
|
12
|
+
if (token.type === "code" && token.inline === false) return true;
|
|
13
|
+
if (token.type === "p") return mapInlineCommandFromParagraphChildren(token.children) !== null;
|
|
14
|
+
return false;
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
//#endregion
|
|
19
|
+
export { hasExpandedTimelineContent };
|
|
20
|
+
//# sourceMappingURL=timeline-message-layout.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"timeline-message-layout.js","names":[],"sources":["../../src/primitives/timeline-message-layout.ts"],"sourcesContent":["import { parseMarkdown } from \"@cossistant/tiny-markdown/utils\";\nimport { mapInlineCommandFromParagraphChildren } from \"./command-block-utils\";\n\n/**\n * Detects whether message text includes fenced code content that should use\n * the expanded timeline bubble layout.\n */\nexport function hasExpandedTimelineContent(\n\ttext: string | null | undefined\n): boolean {\n\tif (!text || text.trim().length === 0) {\n\t\treturn false;\n\t}\n\n\tconst tokens = parseMarkdown(text);\n\treturn tokens.some((token) => {\n\t\tif (token.type === \"code\" && token.inline === false) {\n\t\t\treturn true;\n\t\t}\n\n\t\tif (token.type === \"p\") {\n\t\t\treturn mapInlineCommandFromParagraphChildren(token.children) !== null;\n\t\t}\n\n\t\treturn false;\n\t});\n}\n"],"mappings":";;;;;;;;AAOA,SAAgB,2BACf,MACU;AACV,KAAI,CAAC,QAAQ,KAAK,MAAM,CAAC,WAAW,EACnC,QAAO;AAIR,QADe,cAAc,KAAK,CACpB,MAAM,UAAU;AAC7B,MAAI,MAAM,SAAS,UAAU,MAAM,WAAW,MAC7C,QAAO;AAGR,MAAI,MAAM,SAAS,IAClB,QAAO,sCAAsC,MAAM,SAAS,KAAK;AAGlE,SAAO;GACN"}
|
package/provider.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
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;EAiDF,cAAA,EAAA,CAAA,KAAe,EAAA,MAAA,EAAA,GAAA,IAAA;EAAG,SAAA,EAAA,OAAA;EACK,KAAA,EA/D3B,KA+D2B,GAAA,IAAA;EAAZ,kBAAA,EA9DF,kBA8DE,GAAA,IAAA;EACS,MAAA,EA9DvB,gBA8DuB,GAAA,IAAA;EAAZ,MAAA,EAAA,OAAA;EACT,IAAA,EAAA,GAAA,GAAA,IAAA;EAAiB,KAAA,EAAA,GAAA,GAAA,IAAA;EAIf,MAAA,EAAA,GAAA,GAAA,IAED;
|
|
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;EAiDF,cAAA,EAAA,CAAA,KAAe,EAAA,MAAA,EAAA,GAAA,IAAA;EAAG,SAAA,EAAA,OAAA;EACK,KAAA,EA/D3B,KA+D2B,GAAA,IAAA;EAAZ,kBAAA,EA9DF,kBA8DE,GAAA,IAAA;EACS,MAAA,EA9DvB,gBA8DuB,GAAA,IAAA;EAAZ,MAAA,EAAA,OAAA;EACT,IAAA,EAAA,GAAA,GAAA,IAAA;EAAiB,KAAA,EAAA,GAAA,GAAA,IAAA;EAIf,MAAA,EAAA,GAAA,GAAA,IAED;AAoVZ,CAAA;KAlZK,WAAA,GAAc,WAmZlB,CAnZ8B,sBAmZ9B,CAAA,SAAA,CAAA,CAAA;KAjZI,iBAAA,GAAoB,WAkZxB,CAAA,SAAA,CAAA,SAAA,IAAA,GAAA,SAAA,GAAA,SAAA,GAhZE,WAgZF,CAhZc,WAgZd,CAAA,SAAA,CAAA,CAAA,GAAA;EACA,MAAA,EAAA,MAAA,GAAA,IAAA;CACA;AACA,KAlWW,eAAA,GAAkB,sBAkW7B,GAAA;EACA,oBAAA,EAlWsB,WAkWtB,CAlWkC,WAkWlC,CAAA,sBAAA,CAAA,CAAA,GAAA,EAAA;EACA,iBAAA,EAlWmB,WAkWnB,CAlW+B,WAkW/B,CAAA,mBAAA,CAAA,CAAA,GAAA,EAAA;EACA,OAAA,CAAA,EAlWU,iBAkWV;EACA,IAAA,EAAA,QAAA,GAAA,QAAA;CACA;AACA,cAjWY,cAiWZ,EAjW0B,KAAA,CAAA,OAiW1B,CAjW0B,sBAiW1B,GAAA,SAAA,CAAA;;;;;AA0BD;;iBArCgB,eAAA;;;;;;;;;;;;;GAab,uBAAuB,KAAA,CAAM;;;;;iBAwBhB,UAAA,CAAA,GAAc"}
|
package/provider.js
CHANGED
|
@@ -211,12 +211,6 @@ function SupportProviderInner({ children, apiUrl, wsUrl, publicKey, defaultMessa
|
|
|
211
211
|
close,
|
|
212
212
|
toggle
|
|
213
213
|
]);
|
|
214
|
-
const webSocketKey = React.useMemo(() => {
|
|
215
|
-
if (!website) return "no-website";
|
|
216
|
-
const visitorKey = website.visitor?.id ?? "anonymous";
|
|
217
|
-
const blockedState = isVisitorBlocked ? "blocked" : "active";
|
|
218
|
-
return `${website.id}:${visitorKey}:${blockedState}`;
|
|
219
|
-
}, [isVisitorBlocked, website]);
|
|
220
214
|
return /* @__PURE__ */ jsx(SupportContext.Provider, {
|
|
221
215
|
value,
|
|
222
216
|
children: /* @__PURE__ */ jsx(IdentificationProvider, { children: /* @__PURE__ */ jsx(WebSocketProvider, {
|
|
@@ -229,7 +223,7 @@ function SupportProviderInner({ children, apiUrl, wsUrl, publicKey, defaultMessa
|
|
|
229
223
|
websiteId: website?.id,
|
|
230
224
|
wsUrl,
|
|
231
225
|
children
|
|
232
|
-
}
|
|
226
|
+
}) })
|
|
233
227
|
});
|
|
234
228
|
}
|
|
235
229
|
/**
|
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 { 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\tstatus: string;\n\tdeletedAt: 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\tsnapshotA.status !== snapshotB.status ||\n\t\t\tsnapshotA.deletedAt !== snapshotB.deletedAt\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\t\tstatus?: string;\n\t\t\t\t\t\t\t\tdeletedAt?: 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\tstatus: conversation.status ?? \"open\",\n\t\t\t\t\t\t\t\t\tdeletedAt: conversation.deletedAt ?? 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\tstatus,\n\t\t\tdeletedAt,\n\t\t} of conversationSnapshots) {\n\t\t\t// Skip resolved, spam, or deleted conversations\n\t\t\tif (status !== \"open\" || deletedAt) {\n\t\t\t\tcontinue;\n\t\t\t}\n\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
|
+
{"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\tstatus: string;\n\tdeletedAt: 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\tsnapshotA.status !== snapshotB.status ||\n\t\t\tsnapshotA.deletedAt !== snapshotB.deletedAt\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\t\tstatus?: string;\n\t\t\t\t\t\t\t\tdeletedAt?: 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\tstatus: conversation.status ?? \"open\",\n\t\t\t\t\t\t\t\t\tdeletedAt: conversation.deletedAt ?? 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\tstatus,\n\t\t\tdeletedAt,\n\t\t} of conversationSnapshots) {\n\t\t\t// Skip resolved, spam, or deleted conversations\n\t\t\tif (status !== \"open\" || deletedAt) {\n\t\t\t\tcontinue;\n\t\t\t}\n\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\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\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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"}
|
package/realtime/event-filter.js
CHANGED
|
@@ -17,19 +17,20 @@ function getTargetVisitorId(event) {
|
|
|
17
17
|
*/
|
|
18
18
|
function shouldDeliverEvent(event, websiteId, visitorId) {
|
|
19
19
|
if (websiteId && event.payload.websiteId !== websiteId) return false;
|
|
20
|
-
if (visitorId &&
|
|
20
|
+
if (visitorId && isBlockedTimelineEventForVisitor(event)) return false;
|
|
21
21
|
if (!visitorId) return true;
|
|
22
22
|
const targetVisitorId = getTargetVisitorId(event);
|
|
23
23
|
if (targetVisitorId && targetVisitorId !== visitorId) return false;
|
|
24
24
|
return true;
|
|
25
25
|
}
|
|
26
26
|
/**
|
|
27
|
-
* Returns true if the event carries a timeline item
|
|
27
|
+
* Returns true if the event carries a blocked timeline item for visitor delivery.
|
|
28
28
|
*/
|
|
29
|
-
function
|
|
29
|
+
function isBlockedTimelineEventForVisitor(event) {
|
|
30
30
|
if (event.type === "timelineItemCreated" || event.type === "timelineItemUpdated") {
|
|
31
31
|
const item = event.payload.item;
|
|
32
32
|
if (item && item.visibility === "private") return true;
|
|
33
|
+
if (item && item.type === "tool" && (!("visibility" in item) || item.visibility !== "public")) return true;
|
|
33
34
|
}
|
|
34
35
|
return false;
|
|
35
36
|
}
|
|
@@ -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 *\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 &&
|
|
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/non-public tool timeline items.\n\t// This is a defense-in-depth measure; the server should also filter these.\n\tif (visitorId && isBlockedTimelineEventForVisitor(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 blocked timeline item for visitor delivery.\n */\nfunction isBlockedTimelineEventForVisitor(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\tif (\n\t\t\titem &&\n\t\t\titem.type === \"tool\" &&\n\t\t\t(!(\"visibility\" in item) || item.visibility !== \"public\")\n\t\t) {\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,iCAAiC,MAAM,CACvD,QAAO;AAGR,KAAI,CAAC,UACJ,QAAO;CAGR,MAAM,kBAAkB,mBAAmB,MAAM;AAEjD,KAAI,mBAAmB,oBAAoB,UAC1C,QAAO;AAGR,QAAO;;;;;AAMR,SAAS,iCAAiC,OAAkC;AAC3E,KACC,MAAM,SAAS,yBACf,MAAM,SAAS,uBACd;EAED,MAAM,OADU,MAAM,QACD;AACrB,MAAI,QAAQ,KAAK,eAAe,UAC/B,QAAO;AAER,MACC,QACA,KAAK,SAAS,WACb,EAAE,gBAAgB,SAAS,KAAK,eAAe,UAEhD,QAAO;;AAGT,QAAO"}
|
package/realtime/provider.d.ts
CHANGED
|
@@ -43,7 +43,6 @@ type RealtimeContextValue = RealtimeConnectionState & {
|
|
|
43
43
|
};
|
|
44
44
|
/**
|
|
45
45
|
* Provides websocket connectivity and heartbeating logic for realtime events.
|
|
46
|
-
* Handles SSR by only initializing the WebSocket connection in the browser.
|
|
47
46
|
*/
|
|
48
47
|
declare function RealtimeProvider({
|
|
49
48
|
children,
|
|
@@ -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,
|
|
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,GAwBY;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;KA7BH,kBAAA,GAAqB,iBA8BX,GA9B+B,iBA8B/B;KAdV,qBAAA,GAgBiB;EACV,QAAA,EAhBD,KAAA,CAAM,SAgBL;EAAgB,KAAA,CAAA,EAAA,MAAA;EAKvB,IAAA,EAnBE,kBAmBkB,GAAA,IAAA;EAmoBT,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,EArpBkB,KAqpBlB,EAAA,GAAA,IAAA;CACA;KAnpBI,uBAAA,GAopBJ;EACA,WAAA,EAAA,OAAA;EACA,YAAA,EAAA,OAAA;EACE,KAAA,EAppBK,KAopBL,GAAA,IAAA;EAAwB,IAAM,EAAA,CAAA,KAAA,EAnpBlB,gBAmpBkB,EAAA,GAAA,IAAA;EAAY,OAAA,EAAA,CAAA,IAAA,EAAA,MAAA,EAAA,GAAA,IAAA;EAkB7B,SAAA,EAAA,CAAA,OAAA,EAnqBM,gBAmqBmB,EAAA,GAAA,GAAA,GAAA,IAAA;aAlqB7B;;;;KAKP,oBAAA,GAAuB;;;;;;;;iBAmoBZ,gBAAA;;;;;;;;GAQb,wBAAwB,KAAA,CAAM;;;;iBAkBjB,qBAAA,CAAA,GAAyB"}
|