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