@helpway/react 0.3.4 → 0.4.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/README.md +69 -86
- package/dist/index.d.ts +60 -47
- package/dist/index.js +214 -23
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -221,8 +221,10 @@ function createWidgetStore() {
|
|
|
221
221
|
typingUsers: /* @__PURE__ */ new Map(),
|
|
222
222
|
aiProcessing: /* @__PURE__ */ new Map(),
|
|
223
223
|
isOpen: false,
|
|
224
|
+
isDismissed: false,
|
|
224
225
|
view: "home",
|
|
225
226
|
incomingPreview: null,
|
|
227
|
+
newConversationPrefill: null,
|
|
226
228
|
contentCache: /* @__PURE__ */ new Map(),
|
|
227
229
|
// ── Lifecycle ────────────────────────────────────
|
|
228
230
|
setReady(visitor, website, agentConfig) {
|
|
@@ -377,12 +379,17 @@ function createWidgetStore() {
|
|
|
377
379
|
set((s) => ({
|
|
378
380
|
isOpen: open,
|
|
379
381
|
incomingPreview: open ? null : s.incomingPreview
|
|
380
|
-
// dismiss preview when opening
|
|
381
382
|
}));
|
|
382
383
|
},
|
|
384
|
+
setDismissed(dismissed) {
|
|
385
|
+
set({ isDismissed: dismissed });
|
|
386
|
+
},
|
|
383
387
|
setView(view) {
|
|
384
388
|
set({ view });
|
|
385
389
|
},
|
|
390
|
+
setNewConversationPrefill(text) {
|
|
391
|
+
set({ newConversationPrefill: text });
|
|
392
|
+
},
|
|
386
393
|
dismissPreview() {
|
|
387
394
|
set({ incomingPreview: null });
|
|
388
395
|
},
|
|
@@ -488,6 +495,97 @@ function createWidgetStore() {
|
|
|
488
495
|
}));
|
|
489
496
|
}
|
|
490
497
|
|
|
498
|
+
// ../widget-core/src/api.ts
|
|
499
|
+
function createHelpwayAPI(deps) {
|
|
500
|
+
const { store, emitter, identify, apiKey } = deps;
|
|
501
|
+
return {
|
|
502
|
+
open() {
|
|
503
|
+
store.getState().setOpen(true);
|
|
504
|
+
store.getState().setDismissed(false);
|
|
505
|
+
},
|
|
506
|
+
close() {
|
|
507
|
+
store.getState().setOpen(false);
|
|
508
|
+
},
|
|
509
|
+
toggle() {
|
|
510
|
+
const s = store.getState();
|
|
511
|
+
if (s.isDismissed) {
|
|
512
|
+
s.setDismissed(false);
|
|
513
|
+
s.setOpen(true);
|
|
514
|
+
emitter.emit("show");
|
|
515
|
+
} else {
|
|
516
|
+
s.setOpen(!s.isOpen);
|
|
517
|
+
}
|
|
518
|
+
},
|
|
519
|
+
dismiss() {
|
|
520
|
+
store.getState().setOpen(false);
|
|
521
|
+
store.getState().setDismissed(true);
|
|
522
|
+
emitter.emit("dismiss");
|
|
523
|
+
},
|
|
524
|
+
show() {
|
|
525
|
+
store.getState().setDismissed(false);
|
|
526
|
+
emitter.emit("show");
|
|
527
|
+
},
|
|
528
|
+
shutdown() {
|
|
529
|
+
store.getState().setOpen(false);
|
|
530
|
+
store.getState().setDismissed(false);
|
|
531
|
+
clearVisitorId(apiKey);
|
|
532
|
+
if (typeof document !== "undefined") {
|
|
533
|
+
document.cookie = "hw_visitor=; Path=/; Max-Age=0";
|
|
534
|
+
}
|
|
535
|
+
},
|
|
536
|
+
identify,
|
|
537
|
+
newConversation(prefill) {
|
|
538
|
+
const s = store.getState();
|
|
539
|
+
s.setDismissed(false);
|
|
540
|
+
s.setOpen(true);
|
|
541
|
+
s.setNewConversationPrefill(prefill ?? "");
|
|
542
|
+
},
|
|
543
|
+
openConversation(id) {
|
|
544
|
+
const s = store.getState();
|
|
545
|
+
s.setDismissed(false);
|
|
546
|
+
s.setActiveConversation(id);
|
|
547
|
+
s.setOpen(true);
|
|
548
|
+
},
|
|
549
|
+
get unreadCount() {
|
|
550
|
+
return store.getState().conversations.reduce((sum, c) => sum + c.unreadCount, 0);
|
|
551
|
+
},
|
|
552
|
+
get isOpen() {
|
|
553
|
+
return store.getState().isOpen;
|
|
554
|
+
},
|
|
555
|
+
on(event, fn) {
|
|
556
|
+
emitter.on(event, fn);
|
|
557
|
+
},
|
|
558
|
+
off(event, fn) {
|
|
559
|
+
emitter.off(event, fn);
|
|
560
|
+
}
|
|
561
|
+
};
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
// ../widget-core/src/events.ts
|
|
565
|
+
var WidgetEventEmitter = class {
|
|
566
|
+
listeners = /* @__PURE__ */ new Map();
|
|
567
|
+
on(event, fn) {
|
|
568
|
+
if (!this.listeners.has(event)) this.listeners.set(event, /* @__PURE__ */ new Set());
|
|
569
|
+
this.listeners.get(event).add(fn);
|
|
570
|
+
}
|
|
571
|
+
off(event, fn) {
|
|
572
|
+
this.listeners.get(event)?.delete(fn);
|
|
573
|
+
}
|
|
574
|
+
emit(event, ...args) {
|
|
575
|
+
const data = args[0];
|
|
576
|
+
this.listeners.get(event)?.forEach((fn) => {
|
|
577
|
+
try {
|
|
578
|
+
fn(data);
|
|
579
|
+
} catch (err) {
|
|
580
|
+
console.error(`[Helpway] Event handler error (${event}):`, err);
|
|
581
|
+
}
|
|
582
|
+
});
|
|
583
|
+
}
|
|
584
|
+
removeAll() {
|
|
585
|
+
this.listeners.clear();
|
|
586
|
+
}
|
|
587
|
+
};
|
|
588
|
+
|
|
491
589
|
// ../widget-core/src/i18n.ts
|
|
492
590
|
var en = {
|
|
493
591
|
// Navigation & tabs
|
|
@@ -758,6 +856,10 @@ function SupportProvider({
|
|
|
758
856
|
locale,
|
|
759
857
|
initialConversationId,
|
|
760
858
|
onReady,
|
|
859
|
+
customLauncherSelector,
|
|
860
|
+
onOpen,
|
|
861
|
+
onClose,
|
|
862
|
+
onUnreadChange,
|
|
761
863
|
children
|
|
762
864
|
}) {
|
|
763
865
|
const initRef = useRef(null);
|
|
@@ -771,6 +873,7 @@ function SupportProvider({
|
|
|
771
873
|
apiKey
|
|
772
874
|
});
|
|
773
875
|
const store2 = createWidgetStore();
|
|
876
|
+
const emitter2 = new WidgetEventEmitter();
|
|
774
877
|
const ws2 = new WebSocketManager({
|
|
775
878
|
apiUrl: resolvedApiUrl,
|
|
776
879
|
visitorId: "",
|
|
@@ -794,9 +897,10 @@ function SupportProvider({
|
|
|
794
897
|
console.error("[Helpway] identify failed:", err);
|
|
795
898
|
}
|
|
796
899
|
};
|
|
797
|
-
|
|
900
|
+
const api2 = createHelpwayAPI({ store: store2, emitter: emitter2, identify: identify2, apiKey });
|
|
901
|
+
initRef.current = { client: client2, store: store2, ws: ws2, identify: identify2, api: api2, emitter: emitter2 };
|
|
798
902
|
}
|
|
799
|
-
const { client, store, ws, identify } = initRef.current;
|
|
903
|
+
const { client, store, ws, identify, api, emitter } = initRef.current;
|
|
800
904
|
const retryBootstrap = useCallback(() => {
|
|
801
905
|
setHasBootstrapError(false);
|
|
802
906
|
setBootstrapAttempt((n) => n + 1);
|
|
@@ -851,11 +955,8 @@ function SupportProvider({
|
|
|
851
955
|
store.getState().setActiveConversation(initialConversationId);
|
|
852
956
|
store.getState().setOpen(true);
|
|
853
957
|
}
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
store.getState().setOpen(true);
|
|
857
|
-
};
|
|
858
|
-
onReady?.({ identify: initRef.current.identify, openConversation });
|
|
958
|
+
emitter.emit("ready");
|
|
959
|
+
onReady?.(initRef.current.api);
|
|
859
960
|
} catch (err) {
|
|
860
961
|
console.error("[Helpway] Bootstrap failed:", err);
|
|
861
962
|
if (!cancelled) {
|
|
@@ -879,6 +980,13 @@ function SupportProvider({
|
|
|
879
980
|
if (event.type === "timelineItemCreated") {
|
|
880
981
|
const item = event.item;
|
|
881
982
|
const isPublicIncoming = item?.visibility === "public" && !item.visitorId;
|
|
983
|
+
if (isPublicIncoming && item?.text) {
|
|
984
|
+
emitter.emit("messageReceived", {
|
|
985
|
+
conversationId: event.conversationId,
|
|
986
|
+
text: item.text,
|
|
987
|
+
senderName: "aiAgentId" in item && item.aiAgentId ? "AI" : null
|
|
988
|
+
});
|
|
989
|
+
}
|
|
882
990
|
if (!isPublicIncoming) return;
|
|
883
991
|
const s = store.getState();
|
|
884
992
|
const isViewing = s.isOpen && s.activeConversationId === event.conversationId;
|
|
@@ -901,11 +1009,70 @@ function SupportProvider({
|
|
|
901
1009
|
setLocale(locale);
|
|
902
1010
|
}
|
|
903
1011
|
}, [locale]);
|
|
1012
|
+
useEffect(() => {
|
|
1013
|
+
const unsub = store.subscribe((state, prev) => {
|
|
1014
|
+
if (state.isOpen && !prev.isOpen) {
|
|
1015
|
+
emitter.emit("open");
|
|
1016
|
+
onOpen?.();
|
|
1017
|
+
}
|
|
1018
|
+
if (!state.isOpen && prev.isOpen) {
|
|
1019
|
+
emitter.emit("close");
|
|
1020
|
+
onClose?.();
|
|
1021
|
+
}
|
|
1022
|
+
const prevUnread = prev.conversations.reduce((s, c) => s + c.unreadCount, 0);
|
|
1023
|
+
const nextUnread = state.conversations.reduce((s, c) => s + c.unreadCount, 0);
|
|
1024
|
+
if (nextUnread !== prevUnread) {
|
|
1025
|
+
emitter.emit("unreadChange", nextUnread);
|
|
1026
|
+
onUnreadChange?.(nextUnread);
|
|
1027
|
+
}
|
|
1028
|
+
});
|
|
1029
|
+
return unsub;
|
|
1030
|
+
}, [store, emitter, onOpen, onClose, onUnreadChange]);
|
|
1031
|
+
useEffect(() => {
|
|
1032
|
+
if (!customLauncherSelector) return;
|
|
1033
|
+
const handler = (e) => {
|
|
1034
|
+
e.preventDefault();
|
|
1035
|
+
api.toggle();
|
|
1036
|
+
};
|
|
1037
|
+
const attach = () => {
|
|
1038
|
+
const el2 = document.querySelector(customLauncherSelector);
|
|
1039
|
+
if (el2) {
|
|
1040
|
+
el2.addEventListener("click", handler);
|
|
1041
|
+
return el2;
|
|
1042
|
+
}
|
|
1043
|
+
return null;
|
|
1044
|
+
};
|
|
1045
|
+
let el = attach();
|
|
1046
|
+
let observer = null;
|
|
1047
|
+
if (!el) {
|
|
1048
|
+
observer = new MutationObserver(() => {
|
|
1049
|
+
el = attach();
|
|
1050
|
+
if (el) observer?.disconnect();
|
|
1051
|
+
});
|
|
1052
|
+
observer.observe(document.body, { childList: true, subtree: true });
|
|
1053
|
+
}
|
|
1054
|
+
return () => {
|
|
1055
|
+
el?.removeEventListener("click", handler);
|
|
1056
|
+
observer?.disconnect();
|
|
1057
|
+
};
|
|
1058
|
+
}, [customLauncherSelector, api]);
|
|
1059
|
+
useEffect(() => {
|
|
1060
|
+
if (typeof window === "undefined") return;
|
|
1061
|
+
window.Helpway = api;
|
|
1062
|
+
return () => {
|
|
1063
|
+
if (window.Helpway === api) {
|
|
1064
|
+
window.Helpway = void 0;
|
|
1065
|
+
}
|
|
1066
|
+
emitter.removeAll();
|
|
1067
|
+
};
|
|
1068
|
+
}, [api, emitter]);
|
|
904
1069
|
const ctxValue = {
|
|
905
1070
|
client,
|
|
906
1071
|
store,
|
|
907
1072
|
ws,
|
|
908
1073
|
identify,
|
|
1074
|
+
api,
|
|
1075
|
+
emitter,
|
|
909
1076
|
hasBootstrapError,
|
|
910
1077
|
wsDisconnected,
|
|
911
1078
|
retryBootstrap
|
|
@@ -3381,12 +3548,13 @@ function AgentPresetIcon({ presetId }) {
|
|
|
3381
3548
|
)
|
|
3382
3549
|
);
|
|
3383
3550
|
}
|
|
3384
|
-
function DefaultWidget({
|
|
3551
|
+
function DefaultWidget({ hideLauncher = false } = {}) {
|
|
3385
3552
|
const { isReady, website, agentConfig } = useWidget();
|
|
3386
3553
|
useI18n();
|
|
3387
3554
|
const { client, store, hasBootstrapError, wsDisconnected, retryBootstrap } = useWidgetContext();
|
|
3388
3555
|
const { conversations, create, isCreating, setActiveConversation } = useConversations();
|
|
3389
3556
|
const activeId = useWidgetStore((s) => s.activeConversationId);
|
|
3557
|
+
const isDismissed = useWidgetStore((s) => s.isDismissed);
|
|
3390
3558
|
const { totalUnread } = useUnread();
|
|
3391
3559
|
const contentTabs = useWidgetStore((s) => s.agentConfig?.contentTabs) ?? EMPTY_CONTENT_TABS;
|
|
3392
3560
|
const visitorContact = useWidgetStore((s) => s.visitor?.contact);
|
|
@@ -3397,13 +3565,30 @@ function DefaultWidget({ initialOpen = false } = {}) {
|
|
|
3397
3565
|
activeId
|
|
3398
3566
|
});
|
|
3399
3567
|
}
|
|
3400
|
-
const
|
|
3568
|
+
const storeIsOpen = useWidgetStore((s) => s.isOpen);
|
|
3401
3569
|
const [isClosing, setIsClosing] = useState12(false);
|
|
3570
|
+
const isOpen = storeIsOpen || isClosing;
|
|
3402
3571
|
const [activeTab, setActiveTab] = useState12("home");
|
|
3403
3572
|
const [subView, setSubView] = useState12(null);
|
|
3404
3573
|
const [newText, setNewText] = useState12("");
|
|
3405
3574
|
const [contentDetailSlug, setContentDetailSlug] = useState12(null);
|
|
3406
3575
|
const [contentCategoryName, setContentCategoryName] = useState12(null);
|
|
3576
|
+
const prevActiveIdRef = useRef13(activeId);
|
|
3577
|
+
useEffect11(() => {
|
|
3578
|
+
if (activeId && activeId !== prevActiveIdRef.current && subView !== "conversation") {
|
|
3579
|
+
setSubView("conversation");
|
|
3580
|
+
}
|
|
3581
|
+
prevActiveIdRef.current = activeId;
|
|
3582
|
+
}, [activeId, subView]);
|
|
3583
|
+
const prefill = useWidgetStore((s) => s.newConversationPrefill);
|
|
3584
|
+
useEffect11(() => {
|
|
3585
|
+
if (prefill !== null) {
|
|
3586
|
+
if (prefill.length > 0) setNewText(prefill);
|
|
3587
|
+
setSubView("new");
|
|
3588
|
+
setActiveTab("home");
|
|
3589
|
+
store.getState().setNewConversationPrefill(null);
|
|
3590
|
+
}
|
|
3591
|
+
}, [prefill, store]);
|
|
3407
3592
|
const closeTimerRef = useRef13(void 0);
|
|
3408
3593
|
const welcomeMsgRaw = agentConfig?.welcomeMessage;
|
|
3409
3594
|
const welcomeIsFactoryDefault = !welcomeMsgRaw || welcomeMsgRaw === DEFAULT_EN_WELCOME;
|
|
@@ -3482,9 +3667,9 @@ function DefaultWidget({ initialOpen = false } = {}) {
|
|
|
3482
3667
|
},
|
|
3483
3668
|
[activeTab, subView, activeId, setActiveConversation]
|
|
3484
3669
|
);
|
|
3485
|
-
const
|
|
3670
|
+
const setStoreOpen = useWidgetStore((s) => s.setOpen);
|
|
3486
3671
|
const handleBubbleClick = useCallback12(() => {
|
|
3487
|
-
if (
|
|
3672
|
+
if (storeIsOpen) {
|
|
3488
3673
|
const leavingId = store.getState().activeConversationId;
|
|
3489
3674
|
if (leavingId && !leavingId.startsWith("temp-")) {
|
|
3490
3675
|
store.getState().clearUnread(leavingId);
|
|
@@ -3493,18 +3678,16 @@ function DefaultWidget({ initialOpen = false } = {}) {
|
|
|
3493
3678
|
}
|
|
3494
3679
|
setIsClosing(true);
|
|
3495
3680
|
closeTimerRef.current = setTimeout(() => {
|
|
3496
|
-
setIsOpen(false);
|
|
3497
3681
|
setIsClosing(false);
|
|
3498
|
-
|
|
3682
|
+
setStoreOpen(false);
|
|
3499
3683
|
}, 200);
|
|
3500
3684
|
} else {
|
|
3501
|
-
setIsOpen(true);
|
|
3502
3685
|
setIsClosing(false);
|
|
3503
3686
|
setSubView(null);
|
|
3504
3687
|
setActiveTab("home");
|
|
3505
|
-
|
|
3688
|
+
setStoreOpen(true);
|
|
3506
3689
|
}
|
|
3507
|
-
}, [
|
|
3690
|
+
}, [storeIsOpen, setStoreOpen, client, store]);
|
|
3508
3691
|
const handleClose = useCallback12(() => {
|
|
3509
3692
|
const leavingId = store.getState().activeConversationId;
|
|
3510
3693
|
if (leavingId && !leavingId.startsWith("temp-")) {
|
|
@@ -3514,11 +3697,10 @@ function DefaultWidget({ initialOpen = false } = {}) {
|
|
|
3514
3697
|
}
|
|
3515
3698
|
setIsClosing(true);
|
|
3516
3699
|
closeTimerRef.current = setTimeout(() => {
|
|
3517
|
-
setIsOpen(false);
|
|
3518
3700
|
setIsClosing(false);
|
|
3519
|
-
|
|
3701
|
+
setStoreOpen(false);
|
|
3520
3702
|
}, 200);
|
|
3521
|
-
}, [
|
|
3703
|
+
}, [setStoreOpen, client, store]);
|
|
3522
3704
|
const handleNewConversation = useCallback12(async () => {
|
|
3523
3705
|
const trimmed = newText.trim();
|
|
3524
3706
|
if (!trimmed || isCreating) return;
|
|
@@ -3890,9 +4072,9 @@ function DefaultWidget({ initialOpen = false } = {}) {
|
|
|
3890
4072
|
]
|
|
3891
4073
|
}
|
|
3892
4074
|
),
|
|
3893
|
-
!isOpen && /* @__PURE__ */ jsx13(MessagePreview, { onOpen: handleBubbleClick }),
|
|
4075
|
+
!isOpen && !isDismissed && !hideLauncher && /* @__PURE__ */ jsx13(MessagePreview, { onOpen: handleBubbleClick }),
|
|
3894
4076
|
/* @__PURE__ */ jsx13(WidgetDebugOverlay, {}),
|
|
3895
|
-
/* @__PURE__ */ jsxs8(
|
|
4077
|
+
!isDismissed && !hideLauncher && /* @__PURE__ */ jsxs8(
|
|
3896
4078
|
"button",
|
|
3897
4079
|
{
|
|
3898
4080
|
ref: bubbleRef,
|
|
@@ -4427,6 +4609,11 @@ function HelpwayWidget({
|
|
|
4427
4609
|
locale,
|
|
4428
4610
|
initialConversationId,
|
|
4429
4611
|
onReady,
|
|
4612
|
+
hideLauncher,
|
|
4613
|
+
customLauncherSelector,
|
|
4614
|
+
onOpen,
|
|
4615
|
+
onClose,
|
|
4616
|
+
onUnreadChange,
|
|
4430
4617
|
// eslint-disable-next-line @typescript-eslint/no-deprecated
|
|
4431
4618
|
src
|
|
4432
4619
|
}) {
|
|
@@ -4439,7 +4626,11 @@ function HelpwayWidget({
|
|
|
4439
4626
|
locale,
|
|
4440
4627
|
initialConversationId,
|
|
4441
4628
|
onReady,
|
|
4442
|
-
|
|
4629
|
+
customLauncherSelector,
|
|
4630
|
+
onOpen,
|
|
4631
|
+
onClose,
|
|
4632
|
+
onUnreadChange,
|
|
4633
|
+
children: /* @__PURE__ */ jsx14(DefaultWidget, { hideLauncher })
|
|
4443
4634
|
}
|
|
4444
4635
|
);
|
|
4445
4636
|
}
|