@buni.ai/chatbot-core 1.0.19 → 1.0.21
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/dist/index.esm.js +213 -25
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +213 -25
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +20 -0
- package/dist/widget.d.ts +9 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -4,6 +4,8 @@ Object.defineProperty(exports, '__esModule', { value: true });
|
|
|
4
4
|
|
|
5
5
|
// Default BuniAI platform URL
|
|
6
6
|
const BUNI_PLATFORM_URL = "https://www.buni.ai";
|
|
7
|
+
const PARENT_HANDSHAKE_TYPE = "buni:parent_handshake";
|
|
8
|
+
const IFRAME_HANDSHAKE_ACK_TYPE = "buni:iframe_handshake_ack";
|
|
7
9
|
// Core widget loader class
|
|
8
10
|
class BuniChatWidget {
|
|
9
11
|
constructor() {
|
|
@@ -19,6 +21,34 @@ class BuniChatWidget {
|
|
|
19
21
|
this.chatIframe = null;
|
|
20
22
|
this.customerData = null;
|
|
21
23
|
this.sessionVariables = null;
|
|
24
|
+
this.chatTargetOrigin = null;
|
|
25
|
+
this.handshakeComplete = false;
|
|
26
|
+
this.outboundMessageQueue = [];
|
|
27
|
+
this.displayMode = "hidden";
|
|
28
|
+
this.serverBehavior = {};
|
|
29
|
+
}
|
|
30
|
+
isMinimalModeEnabled() {
|
|
31
|
+
var _a, _b;
|
|
32
|
+
if (typeof ((_b = (_a = this.options) === null || _a === void 0 ? void 0 : _a.config) === null || _b === void 0 ? void 0 : _b.enableMinimalMode) === "boolean") {
|
|
33
|
+
return this.options.config.enableMinimalMode;
|
|
34
|
+
}
|
|
35
|
+
return Boolean(this.serverBehavior.enableMinimalMode);
|
|
36
|
+
}
|
|
37
|
+
syncServerBehaviorFromData(data) {
|
|
38
|
+
if (!data || typeof data !== "object") {
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
const behaviorCandidate = data.behavior && typeof data.behavior === "object"
|
|
42
|
+
? data.behavior
|
|
43
|
+
: data;
|
|
44
|
+
if (typeof behaviorCandidate.enableMinimalMode === "boolean") {
|
|
45
|
+
this.serverBehavior.enableMinimalMode = behaviorCandidate.enableMinimalMode;
|
|
46
|
+
}
|
|
47
|
+
if (behaviorCandidate.defaultMode === "full" ||
|
|
48
|
+
behaviorCandidate.defaultMode === "minimal" ||
|
|
49
|
+
behaviorCandidate.defaultMode === "hidden") {
|
|
50
|
+
this.serverBehavior.defaultMode = behaviorCandidate.defaultMode;
|
|
51
|
+
}
|
|
22
52
|
}
|
|
23
53
|
async initialize(options) {
|
|
24
54
|
this.options = options;
|
|
@@ -182,6 +212,15 @@ class BuniChatWidget {
|
|
|
182
212
|
params.set("startButtonText", config.startButtonText);
|
|
183
213
|
if (config.preChatFormFields)
|
|
184
214
|
params.set("preChatFormFields", JSON.stringify(config.preChatFormFields));
|
|
215
|
+
if (config.enableMinimalMode !== undefined) {
|
|
216
|
+
params.set("enableMinimalMode", String(config.enableMinimalMode));
|
|
217
|
+
}
|
|
218
|
+
if (config.defaultMode) {
|
|
219
|
+
params.set("defaultMode", config.defaultMode);
|
|
220
|
+
}
|
|
221
|
+
if (config.autoMessages && Array.isArray(config.autoMessages)) {
|
|
222
|
+
params.set("autoMessages", JSON.stringify(config.autoMessages));
|
|
223
|
+
}
|
|
185
224
|
chatIframe.src = `${this.getBaseUrl()}/embed/chat?${params.toString()}`;
|
|
186
225
|
// Chat iframe styling - initially hidden
|
|
187
226
|
chatIframe.style.cssText = `
|
|
@@ -205,52 +244,103 @@ class BuniChatWidget {
|
|
|
205
244
|
chatIframe.onload = () => {
|
|
206
245
|
// Set visibility hidden after iframe loads to allow content initialization
|
|
207
246
|
chatIframe.style.visibility = "hidden";
|
|
247
|
+
// Compute the iframe origin once we know the final src.
|
|
248
|
+
try {
|
|
249
|
+
this.chatTargetOrigin = new URL(chatIframe.src).origin;
|
|
250
|
+
}
|
|
251
|
+
catch (_a) {
|
|
252
|
+
this.chatTargetOrigin = this.getBaseUrl();
|
|
253
|
+
}
|
|
254
|
+
// Reset handshake state for this iframe instance.
|
|
255
|
+
this.handshakeComplete = false;
|
|
256
|
+
this.outboundMessageQueue = [];
|
|
208
257
|
this.setupPostMessageAPI(chatIframe);
|
|
258
|
+
this.sendParentHandshake();
|
|
209
259
|
// Now that both iframes are loaded, resolve the promise
|
|
210
260
|
this.widgetElement = container;
|
|
211
261
|
this.triggerIframe = triggerIframe;
|
|
212
262
|
this.chatIframe = chatIframe;
|
|
213
263
|
this.state.isMinimized = true;
|
|
264
|
+
this.state.displayMode = "hidden";
|
|
265
|
+
this.displayMode = "hidden";
|
|
214
266
|
this.state.isLoaded = true;
|
|
215
267
|
resolve();
|
|
216
268
|
};
|
|
217
269
|
};
|
|
218
270
|
// Listen for trigger and connection events
|
|
219
271
|
window.addEventListener("message", (event) => {
|
|
220
|
-
var _a;
|
|
221
|
-
|
|
272
|
+
var _a, _b, _c, _d, _e, _f;
|
|
273
|
+
const payload = event.data;
|
|
274
|
+
if (!payload || typeof payload !== "object")
|
|
275
|
+
return;
|
|
276
|
+
if (typeof payload.type !== "string")
|
|
222
277
|
return;
|
|
223
|
-
switch (
|
|
278
|
+
switch (payload.type) {
|
|
224
279
|
case "connection_ready":
|
|
225
280
|
// Check if message is from chat iframe
|
|
226
|
-
if (event.source === ((_a = this.chatIframe) === null || _a === void 0 ? void 0 : _a.contentWindow)
|
|
281
|
+
if (event.source === ((_a = this.chatIframe) === null || _a === void 0 ? void 0 : _a.contentWindow) &&
|
|
282
|
+
event.origin === this.getBaseUrl()) {
|
|
283
|
+
this.syncServerBehaviorFromData(payload.data);
|
|
227
284
|
// Connection is ready, show the trigger button
|
|
228
285
|
if (container && !config.hideDefaultTrigger) {
|
|
229
286
|
container.style.display = "block";
|
|
230
287
|
}
|
|
231
288
|
// Emit connection_ready event for consumers
|
|
232
|
-
this.emit("connection_ready", {
|
|
289
|
+
this.emit("connection_ready", {
|
|
290
|
+
timestamp: Date.now(),
|
|
291
|
+
behavior: (_b = payload.data) === null || _b === void 0 ? void 0 : _b.behavior,
|
|
292
|
+
});
|
|
233
293
|
if (this.options.onConnectionReady &&
|
|
234
294
|
typeof this.options.onConnectionReady === "function") {
|
|
235
|
-
this.options.onConnectionReady({
|
|
295
|
+
this.options.onConnectionReady({
|
|
296
|
+
timestamp: Date.now(),
|
|
297
|
+
behavior: (_c = payload.data) === null || _c === void 0 ? void 0 : _c.behavior,
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
break;
|
|
302
|
+
case "chatbot:close":
|
|
303
|
+
if (event.source === ((_d = this.chatIframe) === null || _d === void 0 ? void 0 : _d.contentWindow) &&
|
|
304
|
+
event.origin === this.getBaseUrl()) {
|
|
305
|
+
this.closeChat();
|
|
306
|
+
this.displayMode = "hidden";
|
|
307
|
+
this.state.displayMode = "hidden";
|
|
308
|
+
}
|
|
309
|
+
break;
|
|
310
|
+
case "chatbot:minimize":
|
|
311
|
+
if (event.source === ((_e = this.chatIframe) === null || _e === void 0 ? void 0 : _e.contentWindow) &&
|
|
312
|
+
event.origin === this.getBaseUrl()) {
|
|
313
|
+
if (this.isMinimalModeEnabled()) {
|
|
314
|
+
if (!this.state.isOpen) {
|
|
315
|
+
void this.openChat();
|
|
316
|
+
}
|
|
317
|
+
this.postMessageToWidget("minimize");
|
|
318
|
+
this.displayMode = "minimal";
|
|
319
|
+
this.state.displayMode = "minimal";
|
|
320
|
+
}
|
|
321
|
+
else {
|
|
322
|
+
this.closeChat();
|
|
323
|
+
this.displayMode = "hidden";
|
|
324
|
+
this.state.displayMode = "hidden";
|
|
236
325
|
}
|
|
237
326
|
}
|
|
238
327
|
break;
|
|
239
328
|
case "trigger_clicked":
|
|
240
|
-
this.
|
|
329
|
+
if (event.source === ((_f = this.triggerIframe) === null || _f === void 0 ? void 0 : _f.contentWindow) &&
|
|
330
|
+
event.origin === window.location.origin) {
|
|
331
|
+
this.openChat();
|
|
332
|
+
}
|
|
241
333
|
break;
|
|
242
334
|
}
|
|
243
335
|
});
|
|
244
336
|
// Trigger the load event
|
|
245
337
|
// if by 10 seconds the ready message is not received, we want to manually, set the container to visible
|
|
246
338
|
setTimeout(() => {
|
|
247
|
-
if (
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
container.style.display = "block";
|
|
253
|
-
}
|
|
339
|
+
if (container &&
|
|
340
|
+
!config.hideDefaultTrigger &&
|
|
341
|
+
container.style.display === "none" &&
|
|
342
|
+
this.state.isOpen === false) {
|
|
343
|
+
container.style.display = "block";
|
|
254
344
|
}
|
|
255
345
|
}, 10000);
|
|
256
346
|
triggerIframe.src = "about:blank";
|
|
@@ -352,11 +442,15 @@ class BuniChatWidget {
|
|
|
352
442
|
</div>
|
|
353
443
|
<script>
|
|
354
444
|
function handleClick() {
|
|
355
|
-
|
|
445
|
+
// Trigger iframe is same-origin with parent (about:blank + document.write),
|
|
446
|
+
// so we can safely target the parent's origin.
|
|
447
|
+
window.parent.postMessage({ type: 'trigger_clicked' }, window.location.origin);
|
|
356
448
|
}
|
|
357
449
|
|
|
358
450
|
// Listen for unread count updates
|
|
359
451
|
window.addEventListener('message', function(event) {
|
|
452
|
+
if (event.source !== window.parent) return;
|
|
453
|
+
if (event.origin !== window.location.origin) return;
|
|
360
454
|
if (event.data.type === 'updateUnreadCount') {
|
|
361
455
|
const badge = document.getElementById('badge');
|
|
362
456
|
const count = event.data.count || 0;
|
|
@@ -423,6 +517,8 @@ class BuniChatWidget {
|
|
|
423
517
|
this.chatIframe.style.visibility = "visible";
|
|
424
518
|
this.state.isMinimized = false;
|
|
425
519
|
this.state.isOpen = true;
|
|
520
|
+
this.displayMode = "full";
|
|
521
|
+
this.state.displayMode = "full";
|
|
426
522
|
this.emit("maximized", { timestamp: Date.now() });
|
|
427
523
|
}
|
|
428
524
|
closeChat() {
|
|
@@ -455,6 +551,8 @@ class BuniChatWidget {
|
|
|
455
551
|
this.triggerIframe.style.display = "block";
|
|
456
552
|
this.state.isMinimized = true;
|
|
457
553
|
this.state.isOpen = false;
|
|
554
|
+
this.displayMode = "hidden";
|
|
555
|
+
this.state.displayMode = "hidden";
|
|
458
556
|
this.emit("minimized", { timestamp: Date.now() });
|
|
459
557
|
}
|
|
460
558
|
getPositionStyles(position, isMinimized = false) {
|
|
@@ -504,12 +602,24 @@ class BuniChatWidget {
|
|
|
504
602
|
// Listen for messages from the iframe
|
|
505
603
|
window.addEventListener("message", (event) => {
|
|
506
604
|
var _a;
|
|
605
|
+
if (event.source !== iframe.contentWindow) {
|
|
606
|
+
return;
|
|
607
|
+
}
|
|
507
608
|
// Verify origin for security
|
|
508
609
|
if (event.origin !== this.getBaseUrl()) {
|
|
509
610
|
return;
|
|
510
611
|
}
|
|
511
|
-
const
|
|
612
|
+
const payload = event.data;
|
|
613
|
+
if (!payload || typeof payload !== "object")
|
|
614
|
+
return;
|
|
615
|
+
const { type, data } = payload;
|
|
616
|
+
if (typeof type !== "string")
|
|
617
|
+
return;
|
|
512
618
|
switch (type) {
|
|
619
|
+
case IFRAME_HANDSHAKE_ACK_TYPE:
|
|
620
|
+
this.handshakeComplete = true;
|
|
621
|
+
this.flushOutboundMessageQueue();
|
|
622
|
+
break;
|
|
513
623
|
case "ready":
|
|
514
624
|
this.state.isLoaded = true;
|
|
515
625
|
this.emit("ready", data);
|
|
@@ -534,14 +644,24 @@ class BuniChatWidget {
|
|
|
534
644
|
}
|
|
535
645
|
break;
|
|
536
646
|
case "minimized":
|
|
537
|
-
|
|
538
|
-
this.
|
|
647
|
+
this.syncServerBehaviorFromData(data);
|
|
648
|
+
if (this.isMinimalModeEnabled()) {
|
|
649
|
+
if (!this.state.isOpen) {
|
|
650
|
+
void this.openChat();
|
|
651
|
+
}
|
|
652
|
+
this.displayMode = "minimal";
|
|
653
|
+
this.state.displayMode = "minimal";
|
|
654
|
+
}
|
|
655
|
+
else {
|
|
656
|
+
// Backward-compatible behavior.
|
|
657
|
+
this.closeChat();
|
|
658
|
+
}
|
|
539
659
|
break;
|
|
540
660
|
case "new_unread_message":
|
|
541
661
|
// Update unread count in trigger
|
|
542
662
|
this.state.unreadCount++;
|
|
543
663
|
if ((_a = this.triggerIframe) === null || _a === void 0 ? void 0 : _a.contentWindow) {
|
|
544
|
-
this.triggerIframe.contentWindow.postMessage({ type: "updateUnreadCount", count: this.state.unreadCount },
|
|
664
|
+
this.triggerIframe.contentWindow.postMessage({ type: "updateUnreadCount", count: this.state.unreadCount }, window.location.origin);
|
|
545
665
|
}
|
|
546
666
|
break;
|
|
547
667
|
case "customer_data_updated":
|
|
@@ -561,17 +681,69 @@ class BuniChatWidget {
|
|
|
561
681
|
}
|
|
562
682
|
});
|
|
563
683
|
}
|
|
684
|
+
sendParentHandshake() {
|
|
685
|
+
var _a, _b;
|
|
686
|
+
const iframeWindow = (_a = this.chatIframe) === null || _a === void 0 ? void 0 : _a.contentWindow;
|
|
687
|
+
if (!iframeWindow)
|
|
688
|
+
return;
|
|
689
|
+
const targetOrigin = (_b = this.chatTargetOrigin) !== null && _b !== void 0 ? _b : this.getBaseUrl();
|
|
690
|
+
try {
|
|
691
|
+
iframeWindow.postMessage({
|
|
692
|
+
type: PARENT_HANDSHAKE_TYPE,
|
|
693
|
+
data: { origin: window.location.origin },
|
|
694
|
+
}, targetOrigin);
|
|
695
|
+
}
|
|
696
|
+
catch (_c) {
|
|
697
|
+
// Best-effort; iframe may fall back to document.referrer for origin discovery.
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
flushOutboundMessageQueue() {
|
|
701
|
+
var _a, _b;
|
|
702
|
+
const iframeWindow = (_a = this.chatIframe) === null || _a === void 0 ? void 0 : _a.contentWindow;
|
|
703
|
+
if (!iframeWindow)
|
|
704
|
+
return;
|
|
705
|
+
const targetOrigin = (_b = this.chatTargetOrigin) !== null && _b !== void 0 ? _b : this.getBaseUrl();
|
|
706
|
+
const queued = this.outboundMessageQueue;
|
|
707
|
+
if (queued.length === 0)
|
|
708
|
+
return;
|
|
709
|
+
this.outboundMessageQueue = [];
|
|
710
|
+
for (const msg of queued) {
|
|
711
|
+
iframeWindow.postMessage({ type: msg.type, data: msg.data }, targetOrigin);
|
|
712
|
+
}
|
|
713
|
+
}
|
|
564
714
|
postMessageToWidget(type, data) {
|
|
565
|
-
var _a;
|
|
566
|
-
|
|
567
|
-
|
|
715
|
+
var _a, _b;
|
|
716
|
+
const iframeWindow = (_a = this.chatIframe) === null || _a === void 0 ? void 0 : _a.contentWindow;
|
|
717
|
+
if (!iframeWindow)
|
|
718
|
+
return;
|
|
719
|
+
const targetOrigin = (_b = this.chatTargetOrigin) !== null && _b !== void 0 ? _b : this.getBaseUrl();
|
|
720
|
+
// Ensure handshake is sent before any other commands; queue until ack.
|
|
721
|
+
if (!this.handshakeComplete && type !== PARENT_HANDSHAKE_TYPE) {
|
|
722
|
+
const queue = this.outboundMessageQueue;
|
|
723
|
+
if (queue.length < 50) {
|
|
724
|
+
queue.push({ type, data });
|
|
725
|
+
}
|
|
726
|
+
else {
|
|
727
|
+
this.outboundMessageQueue = [...queue.slice(-49), { type, data }];
|
|
728
|
+
}
|
|
729
|
+
this.sendParentHandshake();
|
|
730
|
+
return;
|
|
568
731
|
}
|
|
732
|
+
iframeWindow.postMessage({ type, data }, targetOrigin);
|
|
569
733
|
}
|
|
570
734
|
getBaseUrl() {
|
|
735
|
+
var _a, _b;
|
|
571
736
|
// Return the base URL for the BuniAI platform
|
|
572
737
|
// Priority: 1. config.baseUrl, 2. global BUNI_API_URL, 3. default platform URL
|
|
738
|
+
const configUrl = (_b = (_a = this.options) === null || _a === void 0 ? void 0 : _a.config) === null || _b === void 0 ? void 0 : _b.baseUrl;
|
|
573
739
|
const globalUrl = globalThis.BUNI_API_URL;
|
|
574
|
-
|
|
740
|
+
const rawUrl = configUrl || globalUrl || BUNI_PLATFORM_URL;
|
|
741
|
+
try {
|
|
742
|
+
return new URL(rawUrl).origin;
|
|
743
|
+
}
|
|
744
|
+
catch (_c) {
|
|
745
|
+
return rawUrl;
|
|
746
|
+
}
|
|
575
747
|
}
|
|
576
748
|
destroy() {
|
|
577
749
|
this.postMessageToWidget("destroy");
|
|
@@ -618,10 +790,21 @@ class BuniChatWidget {
|
|
|
618
790
|
}
|
|
619
791
|
}
|
|
620
792
|
minimize() {
|
|
793
|
+
if (this.isMinimalModeEnabled()) {
|
|
794
|
+
if (!this.state.isOpen) {
|
|
795
|
+
void this.openChat();
|
|
796
|
+
}
|
|
797
|
+
this.postMessageToWidget("minimize");
|
|
798
|
+
this.displayMode = "minimal";
|
|
799
|
+
this.state.displayMode = "minimal";
|
|
800
|
+
return;
|
|
801
|
+
}
|
|
621
802
|
this.closeChat();
|
|
622
803
|
}
|
|
623
804
|
maximize() {
|
|
624
|
-
this.openChat();
|
|
805
|
+
void this.openChat();
|
|
806
|
+
this.displayMode = "full";
|
|
807
|
+
this.state.displayMode = "full";
|
|
625
808
|
}
|
|
626
809
|
setCustomerData(data) {
|
|
627
810
|
this.customerData = { ...this.customerData, ...data };
|
|
@@ -662,6 +845,8 @@ class BuniChatWidget {
|
|
|
662
845
|
}
|
|
663
846
|
close() {
|
|
664
847
|
this.closeChat();
|
|
848
|
+
this.displayMode = "hidden";
|
|
849
|
+
this.state.displayMode = "hidden";
|
|
665
850
|
}
|
|
666
851
|
on(event, callback) {
|
|
667
852
|
if (!this.eventListeners.has(event)) {
|
|
@@ -697,7 +882,10 @@ class BuniChatWidget {
|
|
|
697
882
|
}
|
|
698
883
|
}
|
|
699
884
|
getState() {
|
|
700
|
-
return {
|
|
885
|
+
return {
|
|
886
|
+
...this.state,
|
|
887
|
+
displayMode: this.displayMode,
|
|
888
|
+
};
|
|
701
889
|
}
|
|
702
890
|
isReady() {
|
|
703
891
|
return this.state.isLoaded;
|