@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.
- package/README.md +31 -0
- package/dist/components/ChatBot.d.ts +3 -4
- package/dist/components/ContactCapture.d.ts +7 -0
- package/dist/components/MessageActions.d.ts +8 -0
- package/dist/components/MessageAlertSettings.d.ts +5 -0
- package/dist/components/MessageSelectionActions.d.ts +4 -0
- package/dist/components/ReplyQuote.d.ts +8 -0
- package/dist/components/UnreadMessagePreview.d.ts +15 -0
- package/dist/components/embedchat/EmbedChat.d.ts +6 -2
- package/dist/config/endpoints.d.ts +9 -5
- package/dist/hooks/useMessageAlerts.d.ts +11 -0
- package/dist/hooks/useMessageGesture.d.ts +14 -0
- package/dist/hooks/useTeamAvailability.d.ts +2 -0
- package/dist/hooks/useVisitorChat.d.ts +48 -0
- package/dist/types.d.ts +110 -14
- package/dist/utils/cart-model.d.ts +23 -0
- package/dist/utils/contact-link.d.ts +2 -0
- package/dist/utils/convex.d.ts +2 -3
- package/dist/utils/markdown.d.ts +2 -0
- package/dist/utils/message-alerts.d.ts +15 -0
- package/dist/utils/message-colors.d.ts +3 -0
- package/dist/utils/message-id.d.ts +2 -0
- package/dist/utils/phone.d.ts +16 -0
- package/dist/utils/storefront-cart.d.ts +6 -0
- package/dist/utils/storefront-identity.d.ts +23 -0
- package/dist/utils/transcript.d.ts +23 -0
- package/dist/utils/visitor-presence.d.ts +30 -0
- package/dist/utils/visitor-session.d.ts +31 -0
- package/dist/webchat-bundle.min.js +8435 -5050
- package/dist/webchat-bundle.min.umd.cjs +81 -55
- package/package.json +8 -4
- package/dist/components/ConversationRating.d.ts +0 -7
- package/dist/utils/notchatbot-cookie.d.ts +0 -32
- 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:
|
|
4
|
-
exposeControl?: (api:
|
|
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,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:
|
|
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,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
|
-
|
|
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;
|
package/dist/utils/convex.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ConvexReactClient } from 'convex/react';
|
|
2
|
-
import {
|
|
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
|
|
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,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,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
|
+
};
|