@customerhero/js 0.0.1
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/LICENSE +21 -0
- package/README.md +61 -0
- package/dist/index.cjs +324 -0
- package/dist/index.d.cts +122 -0
- package/dist/index.d.ts +122 -0
- package/dist/index.js +296 -0
- package/package.json +65 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 CustomerHero
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# @customerhero/js
|
|
2
|
+
|
|
3
|
+
Framework-agnostic JavaScript client for the [CustomerHero](https://customerhero.app) chat widget. Use this package directly in vanilla JS / TS apps, or via the React bindings in [`@customerhero/react`](https://www.npmjs.com/package/@customerhero/react).
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @customerhero/js
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quick start
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
import { CustomerHeroChat } from "@customerhero/js";
|
|
15
|
+
|
|
16
|
+
const chat = new CustomerHeroChat({
|
|
17
|
+
chatbotId: "bot_xxxxxxxxxxxxxxxxxxxx",
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
chat.subscribe((state) => {
|
|
21
|
+
// React to state changes — messages, open/closed, loading, etc.
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
chat.open();
|
|
25
|
+
await chat.sendMessage("Hello!");
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Identify a signed-in user
|
|
29
|
+
|
|
30
|
+
Link conversations to a user in your system by calling `identify` as soon as you know who the user is.
|
|
31
|
+
|
|
32
|
+
```ts
|
|
33
|
+
chat.identify({
|
|
34
|
+
userId: "usr_123",
|
|
35
|
+
email: "jane@example.com",
|
|
36
|
+
name: "Jane Doe",
|
|
37
|
+
// Optional HMAC for identity verification (recommended in production)
|
|
38
|
+
userHash: "<hmac-sha256(userId, secret)>",
|
|
39
|
+
});
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Configuration
|
|
43
|
+
|
|
44
|
+
| Option | Type | Description |
|
|
45
|
+
| ------------------- | --------------------------------- | --------------------------------------------------------------------------- |
|
|
46
|
+
| `chatbotId` | `string` (required) | The chatbot to connect to. |
|
|
47
|
+
| `apiBase` | `string` | API base URL. Defaults to `https://customerhero.app`. |
|
|
48
|
+
| `primaryColor` | `string` | Accent color override. |
|
|
49
|
+
| `backgroundColor` | `string` | Chat window background override. |
|
|
50
|
+
| `textColor` | `string` | Text color override. |
|
|
51
|
+
| `position` | `"bottom-right" \| "bottom-left"` | Widget position. |
|
|
52
|
+
| `placeholderText` | `string` | Input placeholder override. |
|
|
53
|
+
| `welcomeMessage` | `string` | Welcome message override. |
|
|
54
|
+
| `title` | `string` | Header title override. |
|
|
55
|
+
| `avatarUrl` | `string` | Bot avatar URL override. |
|
|
56
|
+
| `locale` | `string` | Widget locale (e.g. `"en"`, `"es"`). Auto-detected from browser if omitted. |
|
|
57
|
+
| `suggestedMessages` | `string[]` | Quick-reply options shown before the first message. |
|
|
58
|
+
|
|
59
|
+
## License
|
|
60
|
+
|
|
61
|
+
MIT
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
CustomerHeroChat: () => CustomerHeroChat,
|
|
24
|
+
DEFAULTS: () => DEFAULTS
|
|
25
|
+
});
|
|
26
|
+
module.exports = __toCommonJS(index_exports);
|
|
27
|
+
|
|
28
|
+
// src/defaults.ts
|
|
29
|
+
var DEFAULTS = {
|
|
30
|
+
apiBase: "https://customerhero.app",
|
|
31
|
+
primaryColor: "#6C3CE1",
|
|
32
|
+
backgroundColor: "#FFFFFF",
|
|
33
|
+
textColor: "#1A1A2E",
|
|
34
|
+
position: "bottom-right",
|
|
35
|
+
placeholderText: "Type your message...",
|
|
36
|
+
welcomeMessage: "Hi! How can I help you today?",
|
|
37
|
+
title: "CustomerHero"
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
// src/i18n.ts
|
|
41
|
+
var en = {
|
|
42
|
+
online: "Online",
|
|
43
|
+
typing: "Typing...",
|
|
44
|
+
unable_to_load: "Unable to load chat",
|
|
45
|
+
powered_by: "Powered by",
|
|
46
|
+
new_conversation: "New conversation",
|
|
47
|
+
open_chat: "Open chat",
|
|
48
|
+
close_chat: "Close chat",
|
|
49
|
+
send_message: "Send message",
|
|
50
|
+
helpful: "Helpful",
|
|
51
|
+
not_helpful: "Not helpful",
|
|
52
|
+
menu: "Menu"
|
|
53
|
+
};
|
|
54
|
+
var es = {
|
|
55
|
+
online: "En l\xEDnea",
|
|
56
|
+
typing: "Escribiendo...",
|
|
57
|
+
unable_to_load: "No se pudo cargar el chat",
|
|
58
|
+
powered_by: "Desarrollado por",
|
|
59
|
+
new_conversation: "Nueva conversaci\xF3n",
|
|
60
|
+
open_chat: "Abrir chat",
|
|
61
|
+
close_chat: "Cerrar chat",
|
|
62
|
+
send_message: "Enviar mensaje",
|
|
63
|
+
helpful: "\xDAtil",
|
|
64
|
+
not_helpful: "No \xFAtil",
|
|
65
|
+
menu: "Men\xFA"
|
|
66
|
+
};
|
|
67
|
+
var locales = { en, es };
|
|
68
|
+
function createTranslate(locale) {
|
|
69
|
+
const resolved = resolveLocale(locale);
|
|
70
|
+
const translations = locales[resolved] ?? en;
|
|
71
|
+
return (key) => translations[key] ?? en[key] ?? key;
|
|
72
|
+
}
|
|
73
|
+
function resolveLocale(locale) {
|
|
74
|
+
if (locale) {
|
|
75
|
+
const base = locale.split("-")[0].toLowerCase();
|
|
76
|
+
if (locales[base]) return base;
|
|
77
|
+
}
|
|
78
|
+
if (typeof navigator !== "undefined" && navigator.language) {
|
|
79
|
+
const base = navigator.language.split("-")[0].toLowerCase();
|
|
80
|
+
if (locales[base]) return base;
|
|
81
|
+
}
|
|
82
|
+
return "en";
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// src/client.ts
|
|
86
|
+
function resolveConfig(userConfig, fetched) {
|
|
87
|
+
return {
|
|
88
|
+
chatbotId: userConfig.chatbotId,
|
|
89
|
+
apiBase: userConfig.apiBase ?? DEFAULTS.apiBase,
|
|
90
|
+
primaryColor: userConfig.primaryColor ?? fetched?.primaryColor ?? DEFAULTS.primaryColor,
|
|
91
|
+
backgroundColor: userConfig.backgroundColor ?? fetched?.backgroundColor ?? DEFAULTS.backgroundColor,
|
|
92
|
+
textColor: userConfig.textColor ?? fetched?.textColor ?? DEFAULTS.textColor,
|
|
93
|
+
position: userConfig.position ?? fetched?.position ?? DEFAULTS.position,
|
|
94
|
+
placeholderText: userConfig.placeholderText ?? fetched?.placeholderText ?? DEFAULTS.placeholderText,
|
|
95
|
+
welcomeMessage: userConfig.welcomeMessage ?? fetched?.welcomeMessage ?? DEFAULTS.welcomeMessage,
|
|
96
|
+
title: userConfig.title ?? fetched?.title ?? DEFAULTS.title,
|
|
97
|
+
avatarUrl: userConfig.avatarUrl ?? fetched?.avatarUrl,
|
|
98
|
+
suggestedMessages: userConfig.suggestedMessages ?? fetched?.suggestedMessages ?? []
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
function getStorage() {
|
|
102
|
+
try {
|
|
103
|
+
return typeof window !== "undefined" ? window.localStorage : null;
|
|
104
|
+
} catch {
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
var CustomerHeroChat = class {
|
|
109
|
+
state;
|
|
110
|
+
listeners = /* @__PURE__ */ new Set();
|
|
111
|
+
storage;
|
|
112
|
+
userConfig;
|
|
113
|
+
identityData = null;
|
|
114
|
+
t;
|
|
115
|
+
constructor(config) {
|
|
116
|
+
this.userConfig = config;
|
|
117
|
+
this.storage = getStorage();
|
|
118
|
+
this.t = createTranslate(config.locale);
|
|
119
|
+
const resolved = resolveConfig(config);
|
|
120
|
+
const storedConvId = this.storage?.getItem(`ch_conv_${config.chatbotId}`);
|
|
121
|
+
this.state = {
|
|
122
|
+
messages: [],
|
|
123
|
+
isOpen: false,
|
|
124
|
+
isLoading: false,
|
|
125
|
+
conversationId: storedConvId ?? null,
|
|
126
|
+
config: resolved,
|
|
127
|
+
configLoaded: false,
|
|
128
|
+
configError: null,
|
|
129
|
+
error: null,
|
|
130
|
+
identity: null
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
subscribe(listener) {
|
|
134
|
+
this.listeners.add(listener);
|
|
135
|
+
return () => {
|
|
136
|
+
this.listeners.delete(listener);
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
getState() {
|
|
140
|
+
return this.state;
|
|
141
|
+
}
|
|
142
|
+
setState(partial) {
|
|
143
|
+
this.state = { ...this.state, ...partial };
|
|
144
|
+
this.notifyListeners();
|
|
145
|
+
}
|
|
146
|
+
notifyListeners() {
|
|
147
|
+
for (const listener of this.listeners) {
|
|
148
|
+
listener(this.state);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
async fetchConfig() {
|
|
152
|
+
const { chatbotId } = this.userConfig;
|
|
153
|
+
const apiBase = this.userConfig.apiBase ?? DEFAULTS.apiBase;
|
|
154
|
+
try {
|
|
155
|
+
const response = await fetch(`${apiBase}/api/widget/${chatbotId}/config`);
|
|
156
|
+
if (!response.ok) {
|
|
157
|
+
throw new Error(`Failed to fetch config: ${response.status}`);
|
|
158
|
+
}
|
|
159
|
+
const fetched = await response.json();
|
|
160
|
+
const resolved = resolveConfig(this.userConfig, fetched);
|
|
161
|
+
this.setState({ config: resolved, configLoaded: true });
|
|
162
|
+
} catch (error) {
|
|
163
|
+
const errorMsg = error instanceof Error ? error.message : "Failed to load widget config";
|
|
164
|
+
console.error("CustomerHero: Failed to fetch widget config", error);
|
|
165
|
+
this.setState({
|
|
166
|
+
configLoaded: true,
|
|
167
|
+
configError: errorMsg
|
|
168
|
+
});
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
if (this.state.conversationId) {
|
|
172
|
+
await this.loadHistory();
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
async loadHistory() {
|
|
176
|
+
const { chatbotId, apiBase } = this.state.config;
|
|
177
|
+
const { conversationId } = this.state;
|
|
178
|
+
if (!conversationId) return;
|
|
179
|
+
try {
|
|
180
|
+
const response = await fetch(
|
|
181
|
+
`${apiBase}/api/chat/${chatbotId}/messages/${conversationId}`
|
|
182
|
+
);
|
|
183
|
+
if (!response.ok) {
|
|
184
|
+
this.storage?.removeItem(`ch_conv_${chatbotId}`);
|
|
185
|
+
this.setState({ conversationId: null });
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
const data = await response.json();
|
|
189
|
+
const messages = (data.messages ?? []).map(
|
|
190
|
+
(m) => ({
|
|
191
|
+
id: m.id,
|
|
192
|
+
role: m.role,
|
|
193
|
+
content: m.content
|
|
194
|
+
})
|
|
195
|
+
);
|
|
196
|
+
if (messages.length > 0) {
|
|
197
|
+
this.setState({ messages });
|
|
198
|
+
}
|
|
199
|
+
} catch {
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
async sendMessage(message) {
|
|
203
|
+
const trimmed = message.trim();
|
|
204
|
+
if (!trimmed || this.state.isLoading) return;
|
|
205
|
+
const userMsg = { role: "user", content: trimmed };
|
|
206
|
+
this.setState({
|
|
207
|
+
messages: [...this.state.messages, userMsg],
|
|
208
|
+
isLoading: true,
|
|
209
|
+
error: null
|
|
210
|
+
});
|
|
211
|
+
const { chatbotId } = this.state.config;
|
|
212
|
+
const apiBase = this.state.config.apiBase;
|
|
213
|
+
try {
|
|
214
|
+
const response = await fetch(`${apiBase}/api/chat/${chatbotId}`, {
|
|
215
|
+
method: "POST",
|
|
216
|
+
headers: { "Content-Type": "application/json" },
|
|
217
|
+
body: JSON.stringify({
|
|
218
|
+
message: trimmed,
|
|
219
|
+
...this.state.conversationId ? { conversationId: this.state.conversationId } : {},
|
|
220
|
+
...this.identityData ? { identity: this.identityData } : {}
|
|
221
|
+
})
|
|
222
|
+
});
|
|
223
|
+
if (!response.ok) {
|
|
224
|
+
const data2 = await response.json().catch(() => null);
|
|
225
|
+
const errorMsg = data2?.error ?? `Request failed: ${response.status}`;
|
|
226
|
+
throw new Error(errorMsg);
|
|
227
|
+
}
|
|
228
|
+
const data = await response.json();
|
|
229
|
+
const botMsg = {
|
|
230
|
+
id: data.messageId,
|
|
231
|
+
role: "bot",
|
|
232
|
+
content: data.message
|
|
233
|
+
};
|
|
234
|
+
const conversationId = data.conversationId ?? this.state.conversationId;
|
|
235
|
+
if (conversationId) {
|
|
236
|
+
this.storage?.setItem(`ch_conv_${chatbotId}`, conversationId);
|
|
237
|
+
}
|
|
238
|
+
this.setState({
|
|
239
|
+
messages: [...this.state.messages, botMsg],
|
|
240
|
+
isLoading: false,
|
|
241
|
+
conversationId
|
|
242
|
+
});
|
|
243
|
+
} catch (error) {
|
|
244
|
+
const errorMsg = error instanceof Error ? error.message : "Something went wrong";
|
|
245
|
+
this.setState({
|
|
246
|
+
isLoading: false,
|
|
247
|
+
error: errorMsg
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
async rateMessage(messageId, rating) {
|
|
252
|
+
const { chatbotId, apiBase } = this.state.config;
|
|
253
|
+
const { conversationId } = this.state;
|
|
254
|
+
if (!conversationId) return;
|
|
255
|
+
try {
|
|
256
|
+
await fetch(`${apiBase}/api/chat/${chatbotId}/rate`, {
|
|
257
|
+
method: "POST",
|
|
258
|
+
headers: { "Content-Type": "application/json" },
|
|
259
|
+
body: JSON.stringify({ conversationId, messageId, rating })
|
|
260
|
+
});
|
|
261
|
+
} catch (error) {
|
|
262
|
+
console.error("CustomerHero: Failed to rate message", error);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
toggle() {
|
|
266
|
+
const willOpen = !this.state.isOpen;
|
|
267
|
+
if (willOpen && this.state.messages.length === 0 && !this.state.conversationId && this.state.config.welcomeMessage) {
|
|
268
|
+
this.setState({
|
|
269
|
+
isOpen: true,
|
|
270
|
+
messages: [{ role: "bot", content: this.state.config.welcomeMessage }]
|
|
271
|
+
});
|
|
272
|
+
} else {
|
|
273
|
+
this.setState({ isOpen: willOpen });
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
open() {
|
|
277
|
+
if (!this.state.isOpen) this.toggle();
|
|
278
|
+
}
|
|
279
|
+
close() {
|
|
280
|
+
if (this.state.isOpen) this.setState({ isOpen: false });
|
|
281
|
+
}
|
|
282
|
+
reset() {
|
|
283
|
+
const { chatbotId, welcomeMessage } = this.state.config;
|
|
284
|
+
this.storage?.removeItem(`ch_conv_${chatbotId}`);
|
|
285
|
+
this.setState({
|
|
286
|
+
messages: welcomeMessage ? [{ role: "bot", content: welcomeMessage }] : [],
|
|
287
|
+
conversationId: null,
|
|
288
|
+
isLoading: false,
|
|
289
|
+
error: null
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
identify(payload) {
|
|
293
|
+
const { userId, email, name, phone, company, userHash, ...rest } = payload;
|
|
294
|
+
const customProperties = {};
|
|
295
|
+
for (const [k, v] of Object.entries(rest)) {
|
|
296
|
+
if (typeof v === "string" || typeof v === "number" || typeof v === "boolean") {
|
|
297
|
+
customProperties[k] = v;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
this.identityData = {
|
|
301
|
+
userId,
|
|
302
|
+
email,
|
|
303
|
+
name,
|
|
304
|
+
phone,
|
|
305
|
+
company,
|
|
306
|
+
userHash,
|
|
307
|
+
customProperties: Object.keys(customProperties).length > 0 ? customProperties : void 0
|
|
308
|
+
};
|
|
309
|
+
const { chatbotId, welcomeMessage } = this.state.config;
|
|
310
|
+
this.storage?.removeItem(`ch_conv_${chatbotId}`);
|
|
311
|
+
this.setState({
|
|
312
|
+
messages: welcomeMessage ? [{ role: "bot", content: welcomeMessage }] : [],
|
|
313
|
+
conversationId: null,
|
|
314
|
+
isLoading: false,
|
|
315
|
+
error: null,
|
|
316
|
+
identity: this.identityData
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
};
|
|
320
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
321
|
+
0 && (module.exports = {
|
|
322
|
+
CustomerHeroChat,
|
|
323
|
+
DEFAULTS
|
|
324
|
+
});
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
type TranslationKey = "online" | "typing" | "unable_to_load" | "powered_by" | "new_conversation" | "open_chat" | "close_chat" | "send_message" | "helpful" | "not_helpful" | "menu";
|
|
2
|
+
type TranslateFn = (key: TranslationKey) => string;
|
|
3
|
+
|
|
4
|
+
interface CustomerHeroChatConfig {
|
|
5
|
+
/** The chatbot ID to connect to */
|
|
6
|
+
chatbotId: string;
|
|
7
|
+
/** API base URL (defaults to "https://customerhero.app") */
|
|
8
|
+
apiBase?: string;
|
|
9
|
+
/** Override primary/accent color */
|
|
10
|
+
primaryColor?: string;
|
|
11
|
+
/** Override chat window background color */
|
|
12
|
+
backgroundColor?: string;
|
|
13
|
+
/** Override text color */
|
|
14
|
+
textColor?: string;
|
|
15
|
+
/** Widget position */
|
|
16
|
+
position?: "bottom-right" | "bottom-left";
|
|
17
|
+
/** Override input placeholder text */
|
|
18
|
+
placeholderText?: string;
|
|
19
|
+
/** Override welcome message */
|
|
20
|
+
welcomeMessage?: string;
|
|
21
|
+
/** Override header title */
|
|
22
|
+
title?: string;
|
|
23
|
+
/** Override avatar URL */
|
|
24
|
+
avatarUrl?: string;
|
|
25
|
+
/** Widget locale (e.g. "en", "es"). Auto-detected from browser if omitted. */
|
|
26
|
+
locale?: string;
|
|
27
|
+
/** Predefined quick-reply options shown before the user sends a message */
|
|
28
|
+
suggestedMessages?: string[];
|
|
29
|
+
}
|
|
30
|
+
interface ResolvedConfig {
|
|
31
|
+
chatbotId: string;
|
|
32
|
+
apiBase: string;
|
|
33
|
+
primaryColor: string;
|
|
34
|
+
backgroundColor: string;
|
|
35
|
+
textColor: string;
|
|
36
|
+
position: "bottom-right" | "bottom-left";
|
|
37
|
+
placeholderText: string;
|
|
38
|
+
welcomeMessage: string;
|
|
39
|
+
title: string;
|
|
40
|
+
avatarUrl?: string;
|
|
41
|
+
suggestedMessages: string[];
|
|
42
|
+
}
|
|
43
|
+
interface ChatMessage {
|
|
44
|
+
/** Message ID from the API (only present for bot messages) */
|
|
45
|
+
id?: string;
|
|
46
|
+
role: "user" | "bot";
|
|
47
|
+
content: string;
|
|
48
|
+
}
|
|
49
|
+
type MessageRating = "positive" | "negative";
|
|
50
|
+
interface IdentifyPayload {
|
|
51
|
+
/** Stable unique user ID from your system (required) */
|
|
52
|
+
userId: string;
|
|
53
|
+
/** User's email address */
|
|
54
|
+
email?: string;
|
|
55
|
+
/** User's display name */
|
|
56
|
+
name?: string;
|
|
57
|
+
/** User's phone number */
|
|
58
|
+
phone?: string;
|
|
59
|
+
/** User's company name */
|
|
60
|
+
company?: string;
|
|
61
|
+
/** HMAC-SHA256 hash for identity verification */
|
|
62
|
+
userHash?: string;
|
|
63
|
+
/** Additional custom properties (string, number, or boolean values) */
|
|
64
|
+
[key: string]: unknown;
|
|
65
|
+
}
|
|
66
|
+
interface IdentityData {
|
|
67
|
+
userId: string;
|
|
68
|
+
email?: string;
|
|
69
|
+
name?: string;
|
|
70
|
+
phone?: string;
|
|
71
|
+
company?: string;
|
|
72
|
+
userHash?: string;
|
|
73
|
+
customProperties?: Record<string, string | number | boolean>;
|
|
74
|
+
}
|
|
75
|
+
interface ChatState {
|
|
76
|
+
messages: ChatMessage[];
|
|
77
|
+
isOpen: boolean;
|
|
78
|
+
isLoading: boolean;
|
|
79
|
+
conversationId: string | null;
|
|
80
|
+
config: ResolvedConfig;
|
|
81
|
+
configLoaded: boolean;
|
|
82
|
+
configError: string | null;
|
|
83
|
+
error: string | null;
|
|
84
|
+
identity: IdentityData | null;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
type Listener = (state: ChatState) => void;
|
|
88
|
+
declare class CustomerHeroChat {
|
|
89
|
+
private state;
|
|
90
|
+
private listeners;
|
|
91
|
+
private storage;
|
|
92
|
+
private userConfig;
|
|
93
|
+
private identityData;
|
|
94
|
+
readonly t: TranslateFn;
|
|
95
|
+
constructor(config: CustomerHeroChatConfig);
|
|
96
|
+
subscribe(listener: Listener): () => void;
|
|
97
|
+
getState(): ChatState;
|
|
98
|
+
private setState;
|
|
99
|
+
private notifyListeners;
|
|
100
|
+
fetchConfig(): Promise<void>;
|
|
101
|
+
private loadHistory;
|
|
102
|
+
sendMessage(message: string): Promise<void>;
|
|
103
|
+
rateMessage(messageId: string, rating: MessageRating): Promise<void>;
|
|
104
|
+
toggle(): void;
|
|
105
|
+
open(): void;
|
|
106
|
+
close(): void;
|
|
107
|
+
reset(): void;
|
|
108
|
+
identify(payload: IdentifyPayload): void;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
declare const DEFAULTS: {
|
|
112
|
+
apiBase: string;
|
|
113
|
+
primaryColor: string;
|
|
114
|
+
backgroundColor: string;
|
|
115
|
+
textColor: string;
|
|
116
|
+
position: "bottom-right";
|
|
117
|
+
placeholderText: string;
|
|
118
|
+
welcomeMessage: string;
|
|
119
|
+
title: string;
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
export { type ChatMessage, type ChatState, CustomerHeroChat, type CustomerHeroChatConfig, DEFAULTS, type IdentifyPayload, type IdentityData, type MessageRating, type ResolvedConfig, type TranslateFn, type TranslationKey };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
type TranslationKey = "online" | "typing" | "unable_to_load" | "powered_by" | "new_conversation" | "open_chat" | "close_chat" | "send_message" | "helpful" | "not_helpful" | "menu";
|
|
2
|
+
type TranslateFn = (key: TranslationKey) => string;
|
|
3
|
+
|
|
4
|
+
interface CustomerHeroChatConfig {
|
|
5
|
+
/** The chatbot ID to connect to */
|
|
6
|
+
chatbotId: string;
|
|
7
|
+
/** API base URL (defaults to "https://customerhero.app") */
|
|
8
|
+
apiBase?: string;
|
|
9
|
+
/** Override primary/accent color */
|
|
10
|
+
primaryColor?: string;
|
|
11
|
+
/** Override chat window background color */
|
|
12
|
+
backgroundColor?: string;
|
|
13
|
+
/** Override text color */
|
|
14
|
+
textColor?: string;
|
|
15
|
+
/** Widget position */
|
|
16
|
+
position?: "bottom-right" | "bottom-left";
|
|
17
|
+
/** Override input placeholder text */
|
|
18
|
+
placeholderText?: string;
|
|
19
|
+
/** Override welcome message */
|
|
20
|
+
welcomeMessage?: string;
|
|
21
|
+
/** Override header title */
|
|
22
|
+
title?: string;
|
|
23
|
+
/** Override avatar URL */
|
|
24
|
+
avatarUrl?: string;
|
|
25
|
+
/** Widget locale (e.g. "en", "es"). Auto-detected from browser if omitted. */
|
|
26
|
+
locale?: string;
|
|
27
|
+
/** Predefined quick-reply options shown before the user sends a message */
|
|
28
|
+
suggestedMessages?: string[];
|
|
29
|
+
}
|
|
30
|
+
interface ResolvedConfig {
|
|
31
|
+
chatbotId: string;
|
|
32
|
+
apiBase: string;
|
|
33
|
+
primaryColor: string;
|
|
34
|
+
backgroundColor: string;
|
|
35
|
+
textColor: string;
|
|
36
|
+
position: "bottom-right" | "bottom-left";
|
|
37
|
+
placeholderText: string;
|
|
38
|
+
welcomeMessage: string;
|
|
39
|
+
title: string;
|
|
40
|
+
avatarUrl?: string;
|
|
41
|
+
suggestedMessages: string[];
|
|
42
|
+
}
|
|
43
|
+
interface ChatMessage {
|
|
44
|
+
/** Message ID from the API (only present for bot messages) */
|
|
45
|
+
id?: string;
|
|
46
|
+
role: "user" | "bot";
|
|
47
|
+
content: string;
|
|
48
|
+
}
|
|
49
|
+
type MessageRating = "positive" | "negative";
|
|
50
|
+
interface IdentifyPayload {
|
|
51
|
+
/** Stable unique user ID from your system (required) */
|
|
52
|
+
userId: string;
|
|
53
|
+
/** User's email address */
|
|
54
|
+
email?: string;
|
|
55
|
+
/** User's display name */
|
|
56
|
+
name?: string;
|
|
57
|
+
/** User's phone number */
|
|
58
|
+
phone?: string;
|
|
59
|
+
/** User's company name */
|
|
60
|
+
company?: string;
|
|
61
|
+
/** HMAC-SHA256 hash for identity verification */
|
|
62
|
+
userHash?: string;
|
|
63
|
+
/** Additional custom properties (string, number, or boolean values) */
|
|
64
|
+
[key: string]: unknown;
|
|
65
|
+
}
|
|
66
|
+
interface IdentityData {
|
|
67
|
+
userId: string;
|
|
68
|
+
email?: string;
|
|
69
|
+
name?: string;
|
|
70
|
+
phone?: string;
|
|
71
|
+
company?: string;
|
|
72
|
+
userHash?: string;
|
|
73
|
+
customProperties?: Record<string, string | number | boolean>;
|
|
74
|
+
}
|
|
75
|
+
interface ChatState {
|
|
76
|
+
messages: ChatMessage[];
|
|
77
|
+
isOpen: boolean;
|
|
78
|
+
isLoading: boolean;
|
|
79
|
+
conversationId: string | null;
|
|
80
|
+
config: ResolvedConfig;
|
|
81
|
+
configLoaded: boolean;
|
|
82
|
+
configError: string | null;
|
|
83
|
+
error: string | null;
|
|
84
|
+
identity: IdentityData | null;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
type Listener = (state: ChatState) => void;
|
|
88
|
+
declare class CustomerHeroChat {
|
|
89
|
+
private state;
|
|
90
|
+
private listeners;
|
|
91
|
+
private storage;
|
|
92
|
+
private userConfig;
|
|
93
|
+
private identityData;
|
|
94
|
+
readonly t: TranslateFn;
|
|
95
|
+
constructor(config: CustomerHeroChatConfig);
|
|
96
|
+
subscribe(listener: Listener): () => void;
|
|
97
|
+
getState(): ChatState;
|
|
98
|
+
private setState;
|
|
99
|
+
private notifyListeners;
|
|
100
|
+
fetchConfig(): Promise<void>;
|
|
101
|
+
private loadHistory;
|
|
102
|
+
sendMessage(message: string): Promise<void>;
|
|
103
|
+
rateMessage(messageId: string, rating: MessageRating): Promise<void>;
|
|
104
|
+
toggle(): void;
|
|
105
|
+
open(): void;
|
|
106
|
+
close(): void;
|
|
107
|
+
reset(): void;
|
|
108
|
+
identify(payload: IdentifyPayload): void;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
declare const DEFAULTS: {
|
|
112
|
+
apiBase: string;
|
|
113
|
+
primaryColor: string;
|
|
114
|
+
backgroundColor: string;
|
|
115
|
+
textColor: string;
|
|
116
|
+
position: "bottom-right";
|
|
117
|
+
placeholderText: string;
|
|
118
|
+
welcomeMessage: string;
|
|
119
|
+
title: string;
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
export { type ChatMessage, type ChatState, CustomerHeroChat, type CustomerHeroChatConfig, DEFAULTS, type IdentifyPayload, type IdentityData, type MessageRating, type ResolvedConfig, type TranslateFn, type TranslationKey };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
// src/defaults.ts
|
|
2
|
+
var DEFAULTS = {
|
|
3
|
+
apiBase: "https://customerhero.app",
|
|
4
|
+
primaryColor: "#6C3CE1",
|
|
5
|
+
backgroundColor: "#FFFFFF",
|
|
6
|
+
textColor: "#1A1A2E",
|
|
7
|
+
position: "bottom-right",
|
|
8
|
+
placeholderText: "Type your message...",
|
|
9
|
+
welcomeMessage: "Hi! How can I help you today?",
|
|
10
|
+
title: "CustomerHero"
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
// src/i18n.ts
|
|
14
|
+
var en = {
|
|
15
|
+
online: "Online",
|
|
16
|
+
typing: "Typing...",
|
|
17
|
+
unable_to_load: "Unable to load chat",
|
|
18
|
+
powered_by: "Powered by",
|
|
19
|
+
new_conversation: "New conversation",
|
|
20
|
+
open_chat: "Open chat",
|
|
21
|
+
close_chat: "Close chat",
|
|
22
|
+
send_message: "Send message",
|
|
23
|
+
helpful: "Helpful",
|
|
24
|
+
not_helpful: "Not helpful",
|
|
25
|
+
menu: "Menu"
|
|
26
|
+
};
|
|
27
|
+
var es = {
|
|
28
|
+
online: "En l\xEDnea",
|
|
29
|
+
typing: "Escribiendo...",
|
|
30
|
+
unable_to_load: "No se pudo cargar el chat",
|
|
31
|
+
powered_by: "Desarrollado por",
|
|
32
|
+
new_conversation: "Nueva conversaci\xF3n",
|
|
33
|
+
open_chat: "Abrir chat",
|
|
34
|
+
close_chat: "Cerrar chat",
|
|
35
|
+
send_message: "Enviar mensaje",
|
|
36
|
+
helpful: "\xDAtil",
|
|
37
|
+
not_helpful: "No \xFAtil",
|
|
38
|
+
menu: "Men\xFA"
|
|
39
|
+
};
|
|
40
|
+
var locales = { en, es };
|
|
41
|
+
function createTranslate(locale) {
|
|
42
|
+
const resolved = resolveLocale(locale);
|
|
43
|
+
const translations = locales[resolved] ?? en;
|
|
44
|
+
return (key) => translations[key] ?? en[key] ?? key;
|
|
45
|
+
}
|
|
46
|
+
function resolveLocale(locale) {
|
|
47
|
+
if (locale) {
|
|
48
|
+
const base = locale.split("-")[0].toLowerCase();
|
|
49
|
+
if (locales[base]) return base;
|
|
50
|
+
}
|
|
51
|
+
if (typeof navigator !== "undefined" && navigator.language) {
|
|
52
|
+
const base = navigator.language.split("-")[0].toLowerCase();
|
|
53
|
+
if (locales[base]) return base;
|
|
54
|
+
}
|
|
55
|
+
return "en";
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// src/client.ts
|
|
59
|
+
function resolveConfig(userConfig, fetched) {
|
|
60
|
+
return {
|
|
61
|
+
chatbotId: userConfig.chatbotId,
|
|
62
|
+
apiBase: userConfig.apiBase ?? DEFAULTS.apiBase,
|
|
63
|
+
primaryColor: userConfig.primaryColor ?? fetched?.primaryColor ?? DEFAULTS.primaryColor,
|
|
64
|
+
backgroundColor: userConfig.backgroundColor ?? fetched?.backgroundColor ?? DEFAULTS.backgroundColor,
|
|
65
|
+
textColor: userConfig.textColor ?? fetched?.textColor ?? DEFAULTS.textColor,
|
|
66
|
+
position: userConfig.position ?? fetched?.position ?? DEFAULTS.position,
|
|
67
|
+
placeholderText: userConfig.placeholderText ?? fetched?.placeholderText ?? DEFAULTS.placeholderText,
|
|
68
|
+
welcomeMessage: userConfig.welcomeMessage ?? fetched?.welcomeMessage ?? DEFAULTS.welcomeMessage,
|
|
69
|
+
title: userConfig.title ?? fetched?.title ?? DEFAULTS.title,
|
|
70
|
+
avatarUrl: userConfig.avatarUrl ?? fetched?.avatarUrl,
|
|
71
|
+
suggestedMessages: userConfig.suggestedMessages ?? fetched?.suggestedMessages ?? []
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
function getStorage() {
|
|
75
|
+
try {
|
|
76
|
+
return typeof window !== "undefined" ? window.localStorage : null;
|
|
77
|
+
} catch {
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
var CustomerHeroChat = class {
|
|
82
|
+
state;
|
|
83
|
+
listeners = /* @__PURE__ */ new Set();
|
|
84
|
+
storage;
|
|
85
|
+
userConfig;
|
|
86
|
+
identityData = null;
|
|
87
|
+
t;
|
|
88
|
+
constructor(config) {
|
|
89
|
+
this.userConfig = config;
|
|
90
|
+
this.storage = getStorage();
|
|
91
|
+
this.t = createTranslate(config.locale);
|
|
92
|
+
const resolved = resolveConfig(config);
|
|
93
|
+
const storedConvId = this.storage?.getItem(`ch_conv_${config.chatbotId}`);
|
|
94
|
+
this.state = {
|
|
95
|
+
messages: [],
|
|
96
|
+
isOpen: false,
|
|
97
|
+
isLoading: false,
|
|
98
|
+
conversationId: storedConvId ?? null,
|
|
99
|
+
config: resolved,
|
|
100
|
+
configLoaded: false,
|
|
101
|
+
configError: null,
|
|
102
|
+
error: null,
|
|
103
|
+
identity: null
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
subscribe(listener) {
|
|
107
|
+
this.listeners.add(listener);
|
|
108
|
+
return () => {
|
|
109
|
+
this.listeners.delete(listener);
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
getState() {
|
|
113
|
+
return this.state;
|
|
114
|
+
}
|
|
115
|
+
setState(partial) {
|
|
116
|
+
this.state = { ...this.state, ...partial };
|
|
117
|
+
this.notifyListeners();
|
|
118
|
+
}
|
|
119
|
+
notifyListeners() {
|
|
120
|
+
for (const listener of this.listeners) {
|
|
121
|
+
listener(this.state);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
async fetchConfig() {
|
|
125
|
+
const { chatbotId } = this.userConfig;
|
|
126
|
+
const apiBase = this.userConfig.apiBase ?? DEFAULTS.apiBase;
|
|
127
|
+
try {
|
|
128
|
+
const response = await fetch(`${apiBase}/api/widget/${chatbotId}/config`);
|
|
129
|
+
if (!response.ok) {
|
|
130
|
+
throw new Error(`Failed to fetch config: ${response.status}`);
|
|
131
|
+
}
|
|
132
|
+
const fetched = await response.json();
|
|
133
|
+
const resolved = resolveConfig(this.userConfig, fetched);
|
|
134
|
+
this.setState({ config: resolved, configLoaded: true });
|
|
135
|
+
} catch (error) {
|
|
136
|
+
const errorMsg = error instanceof Error ? error.message : "Failed to load widget config";
|
|
137
|
+
console.error("CustomerHero: Failed to fetch widget config", error);
|
|
138
|
+
this.setState({
|
|
139
|
+
configLoaded: true,
|
|
140
|
+
configError: errorMsg
|
|
141
|
+
});
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
if (this.state.conversationId) {
|
|
145
|
+
await this.loadHistory();
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
async loadHistory() {
|
|
149
|
+
const { chatbotId, apiBase } = this.state.config;
|
|
150
|
+
const { conversationId } = this.state;
|
|
151
|
+
if (!conversationId) return;
|
|
152
|
+
try {
|
|
153
|
+
const response = await fetch(
|
|
154
|
+
`${apiBase}/api/chat/${chatbotId}/messages/${conversationId}`
|
|
155
|
+
);
|
|
156
|
+
if (!response.ok) {
|
|
157
|
+
this.storage?.removeItem(`ch_conv_${chatbotId}`);
|
|
158
|
+
this.setState({ conversationId: null });
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
const data = await response.json();
|
|
162
|
+
const messages = (data.messages ?? []).map(
|
|
163
|
+
(m) => ({
|
|
164
|
+
id: m.id,
|
|
165
|
+
role: m.role,
|
|
166
|
+
content: m.content
|
|
167
|
+
})
|
|
168
|
+
);
|
|
169
|
+
if (messages.length > 0) {
|
|
170
|
+
this.setState({ messages });
|
|
171
|
+
}
|
|
172
|
+
} catch {
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
async sendMessage(message) {
|
|
176
|
+
const trimmed = message.trim();
|
|
177
|
+
if (!trimmed || this.state.isLoading) return;
|
|
178
|
+
const userMsg = { role: "user", content: trimmed };
|
|
179
|
+
this.setState({
|
|
180
|
+
messages: [...this.state.messages, userMsg],
|
|
181
|
+
isLoading: true,
|
|
182
|
+
error: null
|
|
183
|
+
});
|
|
184
|
+
const { chatbotId } = this.state.config;
|
|
185
|
+
const apiBase = this.state.config.apiBase;
|
|
186
|
+
try {
|
|
187
|
+
const response = await fetch(`${apiBase}/api/chat/${chatbotId}`, {
|
|
188
|
+
method: "POST",
|
|
189
|
+
headers: { "Content-Type": "application/json" },
|
|
190
|
+
body: JSON.stringify({
|
|
191
|
+
message: trimmed,
|
|
192
|
+
...this.state.conversationId ? { conversationId: this.state.conversationId } : {},
|
|
193
|
+
...this.identityData ? { identity: this.identityData } : {}
|
|
194
|
+
})
|
|
195
|
+
});
|
|
196
|
+
if (!response.ok) {
|
|
197
|
+
const data2 = await response.json().catch(() => null);
|
|
198
|
+
const errorMsg = data2?.error ?? `Request failed: ${response.status}`;
|
|
199
|
+
throw new Error(errorMsg);
|
|
200
|
+
}
|
|
201
|
+
const data = await response.json();
|
|
202
|
+
const botMsg = {
|
|
203
|
+
id: data.messageId,
|
|
204
|
+
role: "bot",
|
|
205
|
+
content: data.message
|
|
206
|
+
};
|
|
207
|
+
const conversationId = data.conversationId ?? this.state.conversationId;
|
|
208
|
+
if (conversationId) {
|
|
209
|
+
this.storage?.setItem(`ch_conv_${chatbotId}`, conversationId);
|
|
210
|
+
}
|
|
211
|
+
this.setState({
|
|
212
|
+
messages: [...this.state.messages, botMsg],
|
|
213
|
+
isLoading: false,
|
|
214
|
+
conversationId
|
|
215
|
+
});
|
|
216
|
+
} catch (error) {
|
|
217
|
+
const errorMsg = error instanceof Error ? error.message : "Something went wrong";
|
|
218
|
+
this.setState({
|
|
219
|
+
isLoading: false,
|
|
220
|
+
error: errorMsg
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
async rateMessage(messageId, rating) {
|
|
225
|
+
const { chatbotId, apiBase } = this.state.config;
|
|
226
|
+
const { conversationId } = this.state;
|
|
227
|
+
if (!conversationId) return;
|
|
228
|
+
try {
|
|
229
|
+
await fetch(`${apiBase}/api/chat/${chatbotId}/rate`, {
|
|
230
|
+
method: "POST",
|
|
231
|
+
headers: { "Content-Type": "application/json" },
|
|
232
|
+
body: JSON.stringify({ conversationId, messageId, rating })
|
|
233
|
+
});
|
|
234
|
+
} catch (error) {
|
|
235
|
+
console.error("CustomerHero: Failed to rate message", error);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
toggle() {
|
|
239
|
+
const willOpen = !this.state.isOpen;
|
|
240
|
+
if (willOpen && this.state.messages.length === 0 && !this.state.conversationId && this.state.config.welcomeMessage) {
|
|
241
|
+
this.setState({
|
|
242
|
+
isOpen: true,
|
|
243
|
+
messages: [{ role: "bot", content: this.state.config.welcomeMessage }]
|
|
244
|
+
});
|
|
245
|
+
} else {
|
|
246
|
+
this.setState({ isOpen: willOpen });
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
open() {
|
|
250
|
+
if (!this.state.isOpen) this.toggle();
|
|
251
|
+
}
|
|
252
|
+
close() {
|
|
253
|
+
if (this.state.isOpen) this.setState({ isOpen: false });
|
|
254
|
+
}
|
|
255
|
+
reset() {
|
|
256
|
+
const { chatbotId, welcomeMessage } = this.state.config;
|
|
257
|
+
this.storage?.removeItem(`ch_conv_${chatbotId}`);
|
|
258
|
+
this.setState({
|
|
259
|
+
messages: welcomeMessage ? [{ role: "bot", content: welcomeMessage }] : [],
|
|
260
|
+
conversationId: null,
|
|
261
|
+
isLoading: false,
|
|
262
|
+
error: null
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
identify(payload) {
|
|
266
|
+
const { userId, email, name, phone, company, userHash, ...rest } = payload;
|
|
267
|
+
const customProperties = {};
|
|
268
|
+
for (const [k, v] of Object.entries(rest)) {
|
|
269
|
+
if (typeof v === "string" || typeof v === "number" || typeof v === "boolean") {
|
|
270
|
+
customProperties[k] = v;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
this.identityData = {
|
|
274
|
+
userId,
|
|
275
|
+
email,
|
|
276
|
+
name,
|
|
277
|
+
phone,
|
|
278
|
+
company,
|
|
279
|
+
userHash,
|
|
280
|
+
customProperties: Object.keys(customProperties).length > 0 ? customProperties : void 0
|
|
281
|
+
};
|
|
282
|
+
const { chatbotId, welcomeMessage } = this.state.config;
|
|
283
|
+
this.storage?.removeItem(`ch_conv_${chatbotId}`);
|
|
284
|
+
this.setState({
|
|
285
|
+
messages: welcomeMessage ? [{ role: "bot", content: welcomeMessage }] : [],
|
|
286
|
+
conversationId: null,
|
|
287
|
+
isLoading: false,
|
|
288
|
+
error: null,
|
|
289
|
+
identity: this.identityData
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
};
|
|
293
|
+
export {
|
|
294
|
+
CustomerHeroChat,
|
|
295
|
+
DEFAULTS
|
|
296
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@customerhero/js",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"private": false,
|
|
5
|
+
"description": "Framework-agnostic JavaScript client for the CustomerHero chat widget.",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"customerhero",
|
|
8
|
+
"chat",
|
|
9
|
+
"chatbot",
|
|
10
|
+
"customer-support",
|
|
11
|
+
"widget",
|
|
12
|
+
"sdk"
|
|
13
|
+
],
|
|
14
|
+
"homepage": "https://customerhero.app",
|
|
15
|
+
"repository": {
|
|
16
|
+
"type": "git",
|
|
17
|
+
"url": "git+https://github.com/customerhero/customerhero-sdk.git",
|
|
18
|
+
"directory": "packages/js"
|
|
19
|
+
},
|
|
20
|
+
"bugs": {
|
|
21
|
+
"url": "https://github.com/customerhero/customerhero-sdk/issues"
|
|
22
|
+
},
|
|
23
|
+
"license": "MIT",
|
|
24
|
+
"author": "CustomerHero",
|
|
25
|
+
"type": "module",
|
|
26
|
+
"sideEffects": false,
|
|
27
|
+
"exports": {
|
|
28
|
+
".": {
|
|
29
|
+
"import": {
|
|
30
|
+
"types": "./dist/index.d.ts",
|
|
31
|
+
"default": "./dist/index.js"
|
|
32
|
+
},
|
|
33
|
+
"require": {
|
|
34
|
+
"types": "./dist/index.d.cts",
|
|
35
|
+
"default": "./dist/index.cjs"
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
"main": "./dist/index.cjs",
|
|
40
|
+
"module": "./dist/index.js",
|
|
41
|
+
"types": "./dist/index.d.ts",
|
|
42
|
+
"files": [
|
|
43
|
+
"dist",
|
|
44
|
+
"README.md",
|
|
45
|
+
"LICENSE"
|
|
46
|
+
],
|
|
47
|
+
"engines": {
|
|
48
|
+
"node": ">=18"
|
|
49
|
+
},
|
|
50
|
+
"scripts": {
|
|
51
|
+
"build": "tsup",
|
|
52
|
+
"dev": "tsup --watch",
|
|
53
|
+
"typecheck": "tsc --noEmit",
|
|
54
|
+
"test": "vitest run"
|
|
55
|
+
},
|
|
56
|
+
"devDependencies": {
|
|
57
|
+
"tsup": "^8.4.0",
|
|
58
|
+
"typescript": "^5.7.0",
|
|
59
|
+
"vitest": "^2.1.9"
|
|
60
|
+
},
|
|
61
|
+
"publishConfig": {
|
|
62
|
+
"access": "public",
|
|
63
|
+
"registry": "https://registry.npmjs.org/"
|
|
64
|
+
}
|
|
65
|
+
}
|