@developer.notchatbot/webchat 1.6.0 → 2.0.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.
Files changed (34) hide show
  1. package/README.md +31 -0
  2. package/dist/components/ChatBot.d.ts +3 -4
  3. package/dist/components/ContactCapture.d.ts +7 -0
  4. package/dist/components/MessageActions.d.ts +8 -0
  5. package/dist/components/MessageAlertSettings.d.ts +5 -0
  6. package/dist/components/MessageSelectionActions.d.ts +4 -0
  7. package/dist/components/ReplyQuote.d.ts +8 -0
  8. package/dist/components/UnreadMessagePreview.d.ts +15 -0
  9. package/dist/components/embedchat/EmbedChat.d.ts +6 -2
  10. package/dist/config/endpoints.d.ts +9 -5
  11. package/dist/hooks/useMessageAlerts.d.ts +11 -0
  12. package/dist/hooks/useMessageGesture.d.ts +14 -0
  13. package/dist/hooks/useTeamAvailability.d.ts +2 -0
  14. package/dist/hooks/useVisitorChat.d.ts +48 -0
  15. package/dist/types.d.ts +110 -14
  16. package/dist/utils/cart-model.d.ts +23 -0
  17. package/dist/utils/contact-link.d.ts +2 -0
  18. package/dist/utils/convex.d.ts +2 -3
  19. package/dist/utils/markdown.d.ts +2 -0
  20. package/dist/utils/message-alerts.d.ts +15 -0
  21. package/dist/utils/message-colors.d.ts +3 -0
  22. package/dist/utils/message-id.d.ts +2 -0
  23. package/dist/utils/phone.d.ts +16 -0
  24. package/dist/utils/storefront-cart.d.ts +6 -0
  25. package/dist/utils/storefront-identity.d.ts +23 -0
  26. package/dist/utils/transcript.d.ts +23 -0
  27. package/dist/utils/visitor-presence.d.ts +30 -0
  28. package/dist/utils/visitor-session.d.ts +31 -0
  29. package/dist/webchat-bundle.min.js +8435 -5050
  30. package/dist/webchat-bundle.min.umd.cjs +81 -55
  31. package/package.json +8 -4
  32. package/dist/components/ConversationRating.d.ts +0 -7
  33. package/dist/utils/notchatbot-cookie.d.ts +0 -32
  34. package/dist/utils/survey.d.ts +0 -30
package/README.md CHANGED
@@ -1,5 +1,7 @@
1
1
  # 🔷 WebChat - Embedded Chat
2
2
 
3
+ Webchat 2.0.0 uses origin-bound visitor credentials and requires the matching NotChatbot backend and Cloudflare presence service. See [the rollout guide](../next/apps/next/docs/webchat-v2-rollout.md) before upgrading an existing install.
4
+
3
5
  ## 📦 Quick Start
4
6
 
5
7
  ### 1. Add the widget to your website
@@ -44,6 +46,8 @@ interface WebChatConfig {
44
46
  active?: boolean; // Enable or disable the button (default: true)
45
47
  color?: string; // Customize the internal WhatsApp button color
46
48
  };
49
+ apiBaseUrl?: string; // Backend override (see "Targeting a non-production backend")
50
+ realtimeUrl?: string; // Convex deployment override (same section)
47
51
  }
48
52
 
49
53
  interface WebChatPositionConfig {
@@ -85,6 +89,33 @@ WebChat.initialize({
85
89
  });
86
90
  ```
87
91
 
92
+ ## 🎯 Targeting a non-production backend
93
+
94
+ By default the widget picks its backend from the hostname of the page it runs
95
+ on: `localhost` → `http://localhost:3000`, anything else → production
96
+ (`https://next.notchatbot.com`). That default is right for a merchant's
97
+ storefront, but wrong wherever the page itself is a non-production deployment —
98
+ a Vercel preview, `dev.notchatbot.com`, or a local dev server on a port other
99
+ than 3000. Without an override, a message typed on a preview lands in the
100
+ **production** inbox.
101
+
102
+ `apiBaseUrl` and `realtimeUrl` override that detection:
103
+
104
+ ```js
105
+ WebChat.initialize({
106
+ apiKey: "your-chatbot-uid",
107
+ apiBaseUrl: window.location.origin, // HTTP: /api/webchat, /upload, /rate
108
+ realtimeUrl: "https://<deployment>.convex.cloud" // Convex realtime subscriptions
109
+ });
110
+ ```
111
+
112
+ - Both must be `https://` (or `http://localhost` / `http://127.0.0.1`); anything
113
+ else is ignored with a console warning and the defaults are kept.
114
+ - **Set them as a pair.** `apiBaseUrl` alone writes to one deployment while the
115
+ widget keeps listening on the default Convex, so agent replies never arrive.
116
+ - Omitting them keeps the previous behaviour exactly — existing embeds are
117
+ unaffected, and each `initialize()` clears any override from a prior call.
118
+
88
119
  # 💬 WhatsApp Button
89
120
 
90
121
  If you simply want a floating WhatsApp action button with no WebChat, you can use `WhatsAppButtonAPI`, which isolates itself in the Shadow DOM to avoid CSS conflicts.
@@ -1,6 +1,5 @@
1
- import { default as React } from 'react';
2
1
  import { ChatBotProps } from '../types';
3
- declare const ChatBot: React.FC<ChatBotProps & {
4
- exposeControl?: (api: any) => void;
5
- }>;
2
+ declare const ChatBot: ({ config, exposeControl }: ChatBotProps & {
3
+ exposeControl?: (api: Record<string, unknown>) => void;
4
+ }) => import("react/jsx-runtime").JSX.Element | null;
6
5
  export default ChatBot;
@@ -0,0 +1,7 @@
1
+ interface Props {
2
+ uid: string;
3
+ conversationId?: string;
4
+ }
5
+ /** Save intentional contact edits after a pause; passive identity hints never authorize follow-up. */
6
+ export declare function ContactCapture({ uid, conversationId }: Props): import("react/jsx-runtime").JSX.Element | null;
7
+ export {};
@@ -0,0 +1,8 @@
1
+ export default function MessageActions({ anchor, selectionMode, reaction, onReact, onReply, onClose }: {
2
+ selectionMode?: boolean;
3
+ anchor: HTMLElement;
4
+ reaction?: string | null;
5
+ onReact?: (emoji: string | null) => void;
6
+ onReply?: () => void;
7
+ onClose: () => void;
8
+ }): import('react').ReactPortal;
@@ -0,0 +1,5 @@
1
+ import { MessageAlertControls } from '../hooks/useMessageAlerts';
2
+ /** Customer-controlled alert preferences. Opening this menu never requests browser permission. */
3
+ export default function MessageAlertSettings({ controls }: {
4
+ controls: MessageAlertControls;
5
+ }): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,4 @@
1
+ export default function MessageSelectionActions({ onReply, onCancel }: {
2
+ onReply: () => void;
3
+ onCancel: () => void;
4
+ }): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,8 @@
1
+ import { Message } from '../types';
2
+ export declare const quoteName: (message: Message) => string;
3
+ export declare const quoteText: (message: Pick<Message, "type" | "text">) => string;
4
+ export default function ReplyQuote({ message, onClick, textColor }: {
5
+ message: Message;
6
+ onClick?: () => void;
7
+ textColor?: string;
8
+ }): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,15 @@
1
+ import { Message } from '../types';
2
+ interface Props {
3
+ messages: Message[];
4
+ isStaffTyping?: boolean;
5
+ staffTypingAuthor?: Message["author"];
6
+ title: string;
7
+ avatar?: string;
8
+ position: 'bottom-left' | 'bottom-right';
9
+ marginBottom: number;
10
+ marginSide: number;
11
+ onOpen: () => void;
12
+ }
13
+ /** Real unread replies in transcript order. Previewing or dismissing never sends viewed receipts. */
14
+ export default function UnreadMessagePreview({ messages, isStaffTyping, staffTypingAuthor, title, avatar, position, marginBottom, marginSide, onOpen }: Props): import("react/jsx-runtime").JSX.Element | null;
15
+ export {};
@@ -1,4 +1,8 @@
1
- import { default as React } from 'react';
2
1
  import { EmbedChatProps } from '../../types';
3
- declare const EmbedChat: React.FC<EmbedChatProps>;
2
+ declare const EmbedChat: ({ config, exposeControl }: EmbedChatProps & {
3
+ exposeControl?: (api: {
4
+ restart: () => void;
5
+ identify: (hint: import('../../utils/storefront-identity').CustomerHint) => void;
6
+ }) => void;
7
+ }) => import("react/jsx-runtime").JSX.Element | null;
4
8
  export default EmbedChat;
@@ -2,9 +2,17 @@ export interface EndpointConfig {
2
2
  baseUrl: string;
3
3
  chatEndpoint: string;
4
4
  uploadEndpoint: string;
5
- rateEndpoint: string;
6
5
  realtimeUrl: string;
7
6
  }
7
+ /**
8
+ * Replaces the endpoint overrides. Called on every `initialize()`, so a config
9
+ * without these keys clears any previous override. Invalid URLs are ignored
10
+ * with a warning and the environment defaults are kept.
11
+ */
12
+ export declare function setEndpointOverrides(overrides: {
13
+ apiBaseUrl?: string;
14
+ realtimeUrl?: string;
15
+ }): void;
8
16
  /**
9
17
  * Detecta si estamos en ambiente de desarrollo
10
18
  */
@@ -21,10 +29,6 @@ export declare function getChatEndpoint(): string;
21
29
  * Obtiene el endpoint de upload
22
30
  */
23
31
  export declare function getUploadEndpoint(): string;
24
- /**
25
- * Obtiene el endpoint de rating
26
- */
27
- export declare function getRateEndpoint(): string;
28
32
  /**
29
33
  * Obtiene la URL base según el ambiente
30
34
  */
@@ -0,0 +1,11 @@
1
+ import { Message } from '../types';
2
+ export interface MessageAlertControls {
3
+ soundEnabled: boolean;
4
+ toggleSound: () => void;
5
+ notificationsEnabled: boolean;
6
+ notificationsSupported: boolean;
7
+ permission: NotificationPermission | 'unsupported';
8
+ toggleNotifications: () => Promise<void>;
9
+ }
10
+ /** Alerts use the existing unread feed; old history, typing and duplicate snapshots remain silent. */
11
+ export declare function useMessageAlerts(uid: string, conversationId: string | undefined, messages: Message[], unreadCount: number, isOpen: boolean, title: string, onOpen: () => void): MessageAlertControls;
@@ -0,0 +1,14 @@
1
+ import { default as React } from 'react';
2
+ export declare function useMessageGesture(enabled: boolean, onReply: () => void, onHold: (touch: boolean) => void): {
3
+ offset: number;
4
+ gestures: {
5
+ onPointerDown: (event: React.PointerEvent<HTMLDivElement>) => void;
6
+ onPointerMove: (event: React.PointerEvent) => void;
7
+ onPointerUp: (event: React.PointerEvent) => void;
8
+ onPointerCancel: () => void;
9
+ onLostPointerCapture: () => void;
10
+ onClickCapture: (event: React.MouseEvent) => void;
11
+ onKeyDown: (event: React.KeyboardEvent) => void;
12
+ onContextMenu: (event: React.MouseEvent) => void;
13
+ };
14
+ };
@@ -0,0 +1,2 @@
1
+ /** Before engagement, an open widget reads only a cached anonymous edge boolean. */
2
+ export declare function useTeamAvailability(uid: string, open: boolean, engaged: boolean, liveOnline: boolean): boolean;
@@ -0,0 +1,48 @@
1
+ import { VisitorSession } from '../utils/visitor-session';
2
+ import { Transcript } from '../utils/transcript';
3
+ import { FileWithBlobUrl, Message, WebChatConfig } from '../types';
4
+ import { CustomerHint } from '../utils/storefront-identity';
5
+ interface PendingSend {
6
+ createdAt?: string;
7
+ requestId: string;
8
+ conversationId: string;
9
+ chatbotUid: string;
10
+ newMessages: Array<{
11
+ id: string;
12
+ role: 'user';
13
+ content: string;
14
+ type: string;
15
+ metaContent?: string;
16
+ replyToId?: string;
17
+ }>;
18
+ currentUrl: string;
19
+ }
20
+ /** Owns the visitor lifecycle shared by floating and embedded chat surfaces. */
21
+ export declare function useVisitorChat(config: WebChatConfig, isOpen: boolean): {
22
+ react: (messageId: string, emoji: string | null) => Promise<boolean>;
23
+ teamOnline: boolean;
24
+ staffTypingAuthor: {
25
+ name: string;
26
+ avatarUrl?: string;
27
+ } | undefined;
28
+ staffTyping: boolean;
29
+ unreadMessages: Message[];
30
+ unreadMessage: Message;
31
+ session: VisitorSession | null;
32
+ draftRevision: number;
33
+ messages: Message[];
34
+ sending: boolean;
35
+ error: string;
36
+ unread: boolean;
37
+ unreadCount: number;
38
+ state: Partial<Transcript>;
39
+ connected: boolean;
40
+ pending: PendingSend;
41
+ send: (text: string, files?: FileWithBlobUrl[], replyTo?: Message) => false | Promise<boolean | undefined>;
42
+ restart: (forgetContact?: boolean) => void;
43
+ viewed: (ids: string[]) => void;
44
+ typing: (active: boolean) => void | undefined;
45
+ identify: (hint: CustomerHint) => void;
46
+ retry: () => void;
47
+ };
48
+ export {};
package/dist/types.d.ts CHANGED
@@ -5,6 +5,10 @@ export interface WebChatPositionConfig {
5
5
  showPopup?: boolean;
6
6
  }
7
7
  export interface WebChatConfig {
8
+ active?: boolean;
9
+ quickReplies?: string[];
10
+ contactCapture?: boolean;
11
+ identityEndpoint?: string;
8
12
  elementId: string;
9
13
  title?: string;
10
14
  placeholder?: string;
@@ -25,6 +29,22 @@ export interface WebChatConfig {
25
29
  whatsapp?: Omit<WhatsAppConfig, 'position' | 'marginBottom' | 'marginSide' | 'mobile' | 'desktop'> & {
26
30
  active?: boolean;
27
31
  };
32
+ /**
33
+ * Optional backend base URL override (e.g. a Vercel preview deployment or
34
+ * dev.notchatbot.com). `/api/webchat`, `/api/webchat/upload` and
35
+ * `/api/webchat/conversation/rate` are derived from it. Must be https://
36
+ * (or http://localhost). Omitted or invalid → the environment defaults.
37
+ */
38
+ apiBaseUrl?: string;
39
+ /**
40
+ * Optional Convex deployment URL override for the realtime subscriptions
41
+ * (agent replies, history, activation). Must be https:// (or
42
+ * http://localhost). Omitted or invalid → the environment default.
43
+ *
44
+ * Pair it with `apiBaseUrl`: pointing HTTP at one deployment and realtime at
45
+ * another means the widget writes to one database and listens on another.
46
+ */
47
+ realtimeUrl?: string;
28
48
  /**
29
49
  * Optional GTM/GA event name overrides for dataLayer pushes.
30
50
  * If omitted, defaults are used.
@@ -42,6 +62,11 @@ export interface EmbedChatInputConfig {
42
62
  iconColor?: string;
43
63
  }
44
64
  export interface EmbedChatConfig {
65
+ apiBaseUrl?: string;
66
+ realtimeUrl?: string;
67
+ contactCapture?: boolean;
68
+ quickReplies?: string[];
69
+ identityEndpoint?: string;
45
70
  elementId: string;
46
71
  title?: string;
47
72
  placeholder?: string;
@@ -79,14 +104,28 @@ export interface EmbedChatProps {
79
104
  config: EmbedChatConfig;
80
105
  }
81
106
  export interface EmbedChatWindowProps {
107
+ onReact?: (messageId: string, emoji: string | null) => Promise<boolean>;
108
+ alertSettings?: React.ReactNode;
109
+ onTyping?: (active: boolean) => void;
110
+ contactMessage?: React.ReactNode;
111
+ children?: React.ReactNode;
112
+ onViewed?: (ids: string[]) => void;
113
+ sending?: boolean;
114
+ draftRevision?: number;
82
115
  config: EmbedChatConfig;
83
116
  messages: Message[];
84
- onSendMessage: (message: string, files?: FileWithBlobUrl[]) => void;
117
+ onSendMessage: (message: string, files?: FileWithBlobUrl[], replyTo?: Message) => void | boolean | Promise<void | boolean>;
85
118
  isLoading?: boolean;
86
119
  isAiTyping?: boolean;
120
+ teamOnline?: boolean;
121
+ isStaffTyping?: boolean;
122
+ staffTypingAuthor?: Message["author"];
87
123
  chatbotActivated?: boolean;
88
124
  }
89
125
  export interface EmbedChatHeaderProps {
126
+ selectionActions?: React.ReactNode;
127
+ teamOnline?: boolean;
128
+ alertSettings?: React.ReactNode;
90
129
  title: string;
91
130
  primaryColor?: string;
92
131
  avatar?: string;
@@ -97,12 +136,29 @@ export interface EmbedChatFooterProps {
97
136
  footerColor?: string;
98
137
  }
99
138
  export interface EmbedChatInstance {
139
+ logout?: () => void;
140
+ setCart: (cart: Omit<import('./utils/cart-model').WebchatCart, 'itemCount' | 'truncated'> | null) => void;
141
+ identify?: (hint: import('./utils/storefront-identity').CustomerHint) => void;
100
142
  destroy: () => void;
101
143
  updateConfig?: (newConfig: Partial<EmbedChatConfig>) => void;
102
144
  injectCSS?: (css: string) => void;
103
145
  removeCustomCSS?: () => void;
104
146
  }
105
147
  export interface Message {
148
+ reaction?: string;
149
+ replyToId?: string;
150
+ replyTo?: {
151
+ id: string;
152
+ content: string;
153
+ role: string;
154
+ type: string;
155
+ };
156
+ author?: {
157
+ name: string;
158
+ avatarUrl?: string;
159
+ };
160
+ contactPromptAfter?: boolean;
161
+ delivery?: 'sending' | 'accepted' | 'failed';
106
162
  id: string;
107
163
  text: string;
108
164
  sender: 'user' | 'bot';
@@ -122,37 +178,44 @@ export interface ChatButtonProps {
122
178
  marginBottom?: number;
123
179
  marginSide?: number;
124
180
  hasNewMessages?: boolean;
181
+ unreadCount?: number;
125
182
  isMobile?: boolean;
126
183
  isMenuOpen?: boolean;
127
184
  hasWhatsAppMenu?: boolean;
128
185
  }
129
186
  export interface ChatWindowProps {
187
+ onReact?: (messageId: string, emoji: string | null) => Promise<boolean>;
188
+ alertSettings?: React.ReactNode;
189
+ onTyping?: (active: boolean) => void;
190
+ draftRevision?: number;
191
+ onViewed?: (ids: string[]) => void;
192
+ sendError?: string;
193
+ onRetry?: () => void;
194
+ sending?: boolean;
195
+ contactCaptured?: boolean;
196
+ handoffRequested?: boolean;
197
+ engaged?: boolean;
130
198
  isOpen: boolean;
131
199
  onClose: () => void;
132
200
  config: WebChatConfig;
133
201
  messages: Message[];
134
- onSendMessage: (message: string, files?: FileWithBlobUrl[]) => void;
202
+ onSendMessage: (message: string, files?: FileWithBlobUrl[], replyTo?: Message) => void | boolean | Promise<void | boolean>;
135
203
  isLoading?: boolean;
136
204
  isAiTyping?: boolean;
205
+ teamOnline?: boolean;
206
+ isStaffTyping?: boolean;
207
+ staffTypingAuthor?: Message["author"];
137
208
  position?: 'bottom-right' | 'bottom-left';
138
209
  marginBottom?: number;
139
210
  marginSide?: number;
140
211
  isMobile?: boolean;
141
212
  chatbotActivated?: boolean;
142
- isRated?: boolean;
143
- onRatingChange?: (isRated: boolean) => void;
144
213
  conversationId?: string;
145
- /** Configurable auto-survey threshold from the backend; undefined = legacy default (2). */
146
- surveyAfterUserMessages?: number;
147
- /** Timestamp of the last manual survey request from Livechat; undefined = never requested. */
148
- webchatSurveyRequestedAt?: number;
149
- }
150
- /** Survey-related conversation state forwarded from the Convex realtime subscription. */
151
- export interface ConversationSurveyState {
152
- surveyAfterUserMessages?: number;
153
- webchatSurveyRequestedAt?: number;
154
214
  }
155
215
  export interface ChatHeaderProps {
216
+ selectionActions?: React.ReactNode;
217
+ teamOnline?: boolean;
218
+ alertSettings?: React.ReactNode;
156
219
  title: string;
157
220
  onClose: () => void;
158
221
  primaryColor?: string;
@@ -162,21 +225,40 @@ export interface ChatHeaderProps {
162
225
  closeButtonCustomIcon?: string;
163
226
  }
164
227
  export interface MessageListProps {
228
+ selectedMessageId?: string;
229
+ onSelectMessage?: (message: Message | undefined) => void;
230
+ onReact?: (messageId: string, emoji: string | null) => Promise<boolean>;
231
+ onReply?: (message: Message) => void;
232
+ contactMessage?: React.ReactNode;
233
+ onViewed?: (ids: string[]) => void;
165
234
  messages: Message[];
166
235
  isTyping: boolean;
236
+ teamOnline?: boolean;
237
+ isStaffTyping?: boolean;
238
+ staffTypingAuthor?: Message["author"];
167
239
  avatar?: string;
168
240
  textColor?: string;
169
241
  bubbleUserColor?: string;
170
242
  chatbotActivated?: boolean;
171
243
  }
172
244
  export interface MessageProps {
245
+ selectedMessageId?: string;
246
+ onSelectMessage?: (message: Message | undefined) => void;
247
+ onReact?: (messageId: string, emoji: string | null) => Promise<boolean>;
248
+ onReply?: (message: Message) => void;
249
+ referencedMessage?: Message;
250
+ onJump?: (id: string) => void;
173
251
  message: Message;
174
252
  avatar?: string;
175
253
  textColor?: string;
176
254
  bubbleUserColor?: string;
177
255
  }
178
256
  export interface MessageInputProps {
179
- onSendMessage: (message: string, files?: FileWithBlobUrl[]) => void;
257
+ replyTo?: Message;
258
+ onCancelReply?: () => void;
259
+ onTyping?: (active: boolean) => void;
260
+ draftScope?: string;
261
+ onSendMessage: (message: string, files?: FileWithBlobUrl[], replyTo?: Message) => void | boolean | Promise<void | boolean>;
180
262
  placeholder?: string;
181
263
  primaryColor?: string;
182
264
  disabled?: boolean;
@@ -185,6 +267,9 @@ export interface MessageInputProps {
185
267
  }
186
268
  export interface TypingIndicatorProps {
187
269
  avatar?: string;
270
+ author?: Message["author"];
271
+ human?: boolean;
272
+ compact?: boolean;
188
273
  }
189
274
  export interface ChatBubblePopoverProps {
190
275
  initialMessage?: string;
@@ -198,14 +283,25 @@ export interface ChatBubblePopoverProps {
198
283
  onPopoverClick: () => void;
199
284
  }
200
285
  export interface WebChatInstance {
286
+ logout?: () => void;
287
+ setCart: (cart: Omit<import('./utils/cart-model').WebchatCart, 'itemCount' | 'truncated'> | null) => void;
288
+ identify?: (hint: import('./utils/storefront-identity').CustomerHint) => void;
201
289
  root: any;
202
290
  destroy: () => void;
203
291
  getConversationId: () => string | null;
204
292
  setIsOpen?: (open: boolean) => void;
205
293
  updateConfig?: (newConfig: Partial<WebChatConfig>) => void;
206
294
  setPosition?: (pos: Partial<WebChatConfig>) => void;
295
+ getIsOpen?: () => boolean;
207
296
  hide?: () => void;
208
297
  show?: () => void;
298
+ /**
299
+ * Starts a new conversation in place: new conversation id, empty transcript,
300
+ * fresh visitor session, realtime subscriptions reopened. Unlike calling
301
+ * `initialize()` again it does not destroy and rebuild the widget, so the
302
+ * panel does not blink out while the new thread is prepared.
303
+ */
304
+ restartConversation?: () => void;
209
305
  injectCSS?: (css: string) => void;
210
306
  removeCustomCSS?: () => void;
211
307
  }
@@ -0,0 +1,23 @@
1
+ /** Cart amounts are integer minor currency units. Storefront hints never authorize orders or prices. */
2
+ export interface WebchatCartItem {
3
+ id: string;
4
+ title: string;
5
+ quantity: number;
6
+ unitPrice: number;
7
+ variant?: string;
8
+ imageUrl?: string;
9
+ url?: string;
10
+ }
11
+ export interface WebchatCart {
12
+ currency: string;
13
+ total: number;
14
+ itemCount: number;
15
+ items: WebchatCartItem[];
16
+ truncated: boolean;
17
+ }
18
+ export interface WebchatCartSnapshot {
19
+ cart: WebchatCart | null;
20
+ updatedAt: number;
21
+ }
22
+ /** Allowlist and bound storefront context before sending it to staff or edge storage. */
23
+ export declare function sanitizeWebchatCart(value: unknown, origin: string): WebchatCart | null;
@@ -0,0 +1,2 @@
1
+ /** Remove the bearer credential before widget analytics or presence can use the URL. */
2
+ export declare function takeContactLink(chatbotUid: string): string | null;
@@ -1,5 +1,5 @@
1
1
  import { ConvexReactClient } from 'convex/react';
2
- import { ConversationSurveyState, Message } from '../types';
2
+ import { Message } from '../types';
3
3
  export interface ConvexConnectionData {
4
4
  client: ConvexReactClient;
5
5
  unsubscribe: (() => void) | null;
@@ -11,10 +11,9 @@ export interface ConvexConnectionData {
11
11
  * @param conversationId Conversation ID
12
12
  * @param onMessage Callback for new messages
13
13
  * @param onChatbotToggle Callback for chatbot activation changes
14
- * @param onSurveyStateChange Callback for survey state changes (threshold + manual trigger)
15
14
  * @returns Promise that resolves to Convex connection data
16
15
  */
17
- export declare const initializeConvexConnection: (realtimeEndpoint: string, _chatbotUid: string, conversationId: string, onMessage: (message: Message) => void, onChatbotToggle?: (chatbotActivated: boolean) => void, onSurveyStateChange?: (surveyState: ConversationSurveyState) => void) => Promise<ConvexConnectionData>;
16
+ export declare const initializeConvexConnection: (realtimeEndpoint: string, _chatbotUid: string, conversationId: string, onMessage: (message: Message) => void, onChatbotToggle?: (chatbotActivated: boolean) => void) => Promise<ConvexConnectionData>;
18
17
  /**
19
18
  * Fetches initial conversation data with messages from Convex
20
19
  * @param realtimeEndpoint Realtime service deployment URL
@@ -0,0 +1,2 @@
1
+ /** Both visitor and assistant text are untrusted, including raw HTML inside Markdown. */
2
+ export declare function renderMessageMarkdown(text: string): string;
@@ -0,0 +1,15 @@
1
+ /** Keep notification counters out of the page metadata sent to Livechat. */
2
+ export declare const getStorefrontTitle: () => string;
3
+ /** Preserve storefront title changes, including client-side navigation, while showing unread replies. */
4
+ export declare function createUnreadTitle(): {
5
+ update(next: number): void;
6
+ close(): void;
7
+ };
8
+ /** Share a bounded receipt ledger between storefront tabs; Web Locks prevent simultaneous alerts. */
9
+ export declare function claimMessageAlert(uid: string, conversationId: string, ids: string[]): Promise<any>;
10
+ /** Unlock audio only on customer interaction; unsupported/autoplay-blocked browsers remain silent. */
11
+ export declare function createMessageSound(): {
12
+ unlock: () => void;
13
+ play(): void;
14
+ close(): void;
15
+ };
@@ -0,0 +1,3 @@
1
+ /** Store theme colors are opaque hex colors; keep a custom text color only when legible. */
2
+ export declare function messageTextColor(background: string, preferred?: string): string;
3
+ export declare function messageMetaColor(background: string, foreground: string): string;
@@ -0,0 +1,2 @@
1
+ /** Mirrors the API's conversation/request/index ID, so optimistic bubbles reconcile before HTTP completes. */
2
+ export declare function webchatMessageId(conversationId: string, requestId: string, index: number): Promise<string>;
@@ -0,0 +1,16 @@
1
+ import { getCountryCallingCode, CountryCode } from 'libphonenumber-js/min';
2
+ export { getCountryCallingCode, type CountryCode };
3
+ export declare const phoneCountries: CountryCode[];
4
+ export declare const isPhoneCountry: (value: unknown) => value is CountryCode;
5
+ export declare const countryFlag: (country: CountryCode) => string;
6
+ /** Browser hints are a convenience, not geolocation. No request or permission prompt is needed. */
7
+ export declare function suggestPhoneCountry(languages: readonly string[], timeZone: string): CountryCode;
8
+ /** Split pasted/saved international numbers without duplicating their country calling code. */
9
+ export declare function splitPhone(value: string, fallback: CountryCode): {
10
+ value: string;
11
+ country: CountryCode;
12
+ };
13
+ /** Normalize national input (including trunk prefixes) for the existing E.164 contact endpoint. */
14
+ export declare function normalizePhone(value: string, country: CountryCode): string | null;
15
+ export declare const countryName: (country: CountryCode) => string;
16
+ export declare const sortedPhoneCountries: CountryCode[];
@@ -0,0 +1,6 @@
1
+ import { WebchatCart } from './cart-model';
2
+ export type { WebchatCart } from './cart-model';
3
+ /** Commerce adapters call this on initial load and cart changes. No network until chat engagement. */
4
+ export declare function setStorefrontCart(uid: string, input: unknown): void;
5
+ export declare const getStorefrontCart: (uid: string) => WebchatCart | null | undefined;
6
+ export declare function subscribeStorefrontCart(uid: string, listener: () => void): () => void;
@@ -0,0 +1,23 @@
1
+ import { VisitorSession } from './visitor-session';
2
+ export interface CustomerHint {
3
+ platform?: 'tiendanube' | 'shopify' | 'vtex' | 'custom';
4
+ subject?: string;
5
+ email?: string;
6
+ name?: string;
7
+ phone?: string;
8
+ }
9
+ interface VerifiedIdentity extends CustomerHint {
10
+ verified: boolean;
11
+ expiresAt: number;
12
+ }
13
+ /** Hints only prefill the form; they do not authenticate or link customer records. */
14
+ export declare function saveCustomerHint(uid: string, hint: CustomerHint): void;
15
+ /** The configured same-origin merchant route must validate its own login before signing. */
16
+ export declare function refreshStorefrontIdentity(session: VisitorSession, endpoint: string): Promise<{
17
+ changed: boolean;
18
+ identity: null;
19
+ } | {
20
+ changed: boolean;
21
+ identity: VerifiedIdentity;
22
+ }>;
23
+ export {};
@@ -0,0 +1,23 @@
1
+ import { VisitorSession } from './visitor-session';
2
+ import { Message } from '../types';
3
+ export interface Transcript {
4
+ messages: Array<{
5
+ reaction?: string;
6
+ replyToId?: string;
7
+ replyTo?: Message["replyTo"];
8
+ id: string;
9
+ role: string;
10
+ content: string;
11
+ createdAt: string;
12
+ type?: string;
13
+ metaContent?: string;
14
+ contactPromptAfter?: boolean;
15
+ author?: Message['author'];
16
+ }>;
17
+ chatbotActivated: boolean;
18
+ contactCaptured?: boolean;
19
+ handoffRequested?: boolean;
20
+ }
21
+ export declare const transcriptMessages: (data: Transcript) => Message[];
22
+ /** One bounded subscription supplies both initial history and live replies, avoiding snapshot races. */
23
+ export declare function subscribeTranscript(session: VisitorSession, onData: (data: Transcript) => void, onError: () => void): () => void;
@@ -0,0 +1,30 @@
1
+ import { VisitorSession } from './visitor-session';
2
+ export interface EdgeReply {
3
+ id: string;
4
+ at: number;
5
+ receipt: 'available' | 'delivered' | 'viewed';
6
+ }
7
+ export interface EdgeState {
8
+ staffTyping?: {
9
+ name: string;
10
+ avatarUrl?: string;
11
+ } | null;
12
+ staffTypingUntil?: number;
13
+ typingUntil?: number;
14
+ page?: {
15
+ url: string;
16
+ title: string;
17
+ } | null;
18
+ type: 'state';
19
+ presence: string;
20
+ replies: EdgeReply[];
21
+ observedAt: number;
22
+ }
23
+ /** Reconnectable metadata channel. Transcript content is only loaded while the chat is open. */
24
+ export declare function connectVisitorPresence(session: VisitorSession, onState: (state: EdgeState) => void, onConnection: (connected: boolean) => void, onTeamAvailability?: (online: boolean) => void): {
25
+ setTyping: (active: boolean) => void;
26
+ setOpen(value: boolean): void;
27
+ delivered(ids: string[]): void;
28
+ viewed(ids: string[]): void;
29
+ close(): void;
30
+ };