@cossistant/react 0.0.3 → 0.0.5
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 +74 -0
- package/_virtual/rolldown_runtime.js +19 -0
- package/conversation.d.ts +26 -3
- package/conversation.d.ts.map +1 -1
- package/hooks/index.d.ts +5 -3
- package/hooks/index.js +7 -5
- package/hooks/private/store/use-conversations-store.d.ts +8 -0
- package/hooks/private/store/use-conversations-store.d.ts.map +1 -1
- package/hooks/private/store/use-conversations-store.js +8 -0
- package/hooks/private/store/use-conversations-store.js.map +1 -1
- package/hooks/private/store/use-store-selector.d.ts +4 -0
- package/hooks/private/store/use-store-selector.d.ts.map +1 -1
- package/hooks/private/store/use-store-selector.js +5 -2
- package/hooks/private/store/use-store-selector.js.map +1 -1
- package/hooks/private/store/use-website-store.d.ts +4 -0
- package/hooks/private/store/use-website-store.d.ts.map +1 -1
- package/hooks/private/store/use-website-store.js +6 -3
- package/hooks/private/store/use-website-store.js.map +1 -1
- package/hooks/private/typing.d.ts +35 -0
- package/hooks/private/typing.d.ts.map +1 -0
- package/hooks/private/typing.js +49 -0
- package/hooks/private/typing.js.map +1 -0
- package/hooks/private/use-client-query.d.ts +5 -0
- package/hooks/private/use-client-query.d.ts.map +1 -1
- package/hooks/private/use-client-query.js +5 -0
- package/hooks/private/use-client-query.js.map +1 -1
- package/hooks/private/use-grouped-messages.d.ts +10 -4
- package/hooks/private/use-grouped-messages.d.ts.map +1 -1
- package/hooks/private/use-grouped-messages.js +24 -4
- package/hooks/private/use-grouped-messages.js.map +1 -1
- package/hooks/private/use-multimodal-input.d.ts.map +1 -1
- package/hooks/private/use-rest-client.d.ts.map +1 -1
- package/hooks/private/use-visitor-typing-reporter.d.ts +6 -0
- package/hooks/private/use-visitor-typing-reporter.d.ts.map +1 -1
- package/hooks/private/use-visitor-typing-reporter.js +6 -0
- package/hooks/private/use-visitor-typing-reporter.js.map +1 -1
- package/hooks/use-composer-refocus.d.ts.map +1 -1
- package/hooks/use-conversation-auto-seen.d.ts +9 -0
- package/hooks/use-conversation-auto-seen.d.ts.map +1 -1
- package/hooks/use-conversation-auto-seen.js +44 -3
- package/hooks/use-conversation-auto-seen.js.map +1 -1
- package/hooks/use-conversation-history-page.d.ts.map +1 -1
- package/hooks/use-conversation-history-page.js +16 -18
- package/hooks/use-conversation-history-page.js.map +1 -1
- package/hooks/use-conversation-lifecycle.d.ts.map +1 -1
- package/hooks/use-conversation-lifecycle.js +2 -4
- package/hooks/use-conversation-lifecycle.js.map +1 -1
- package/hooks/use-conversation-page.d.ts +6 -0
- package/hooks/use-conversation-page.d.ts.map +1 -1
- package/hooks/use-conversation-page.js +41 -3
- package/hooks/use-conversation-page.js.map +1 -1
- package/hooks/use-conversation-preview.d.ts +61 -0
- package/hooks/use-conversation-preview.d.ts.map +1 -0
- package/hooks/use-conversation-preview.js +173 -0
- package/hooks/use-conversation-preview.js.map +1 -0
- package/hooks/use-conversation-seen.d.ts +4 -0
- package/hooks/use-conversation-seen.d.ts.map +1 -1
- package/hooks/use-conversation-seen.js +4 -0
- package/hooks/use-conversation-seen.js.map +1 -1
- package/hooks/use-conversation-timeline-items.d.ts +4 -0
- package/hooks/use-conversation-timeline-items.d.ts.map +1 -1
- package/hooks/use-conversation-timeline-items.js +4 -0
- package/hooks/use-conversation-timeline-items.js.map +1 -1
- package/hooks/use-conversation-timeline.d.ts +32 -0
- package/hooks/use-conversation-timeline.d.ts.map +1 -0
- package/hooks/use-conversation-timeline.js +41 -0
- package/hooks/use-conversation-timeline.js.map +1 -0
- package/hooks/use-conversation-typing.d.ts +4 -0
- package/hooks/use-conversation-typing.d.ts.map +1 -1
- package/hooks/use-conversation-typing.js +4 -0
- package/hooks/use-conversation-typing.js.map +1 -1
- package/hooks/use-conversation.d.ts +11 -0
- package/hooks/use-conversation.d.ts.map +1 -1
- package/hooks/use-conversation.js +11 -0
- package/hooks/use-conversation.js.map +1 -1
- package/hooks/use-conversations.d.ts +12 -0
- package/hooks/use-conversations.d.ts.map +1 -1
- package/hooks/use-conversations.js +12 -0
- package/hooks/use-conversations.js.map +1 -1
- package/hooks/use-create-conversation.d.ts +5 -0
- package/hooks/use-create-conversation.d.ts.map +1 -1
- package/hooks/use-create-conversation.js +12 -9
- package/hooks/use-create-conversation.js.map +1 -1
- package/hooks/use-home-page.d.ts.map +1 -1
- package/hooks/use-home-page.js +6 -4
- package/hooks/use-home-page.js.map +1 -1
- package/hooks/use-message-composer.d.ts.map +1 -1
- package/hooks/use-realtime-support.d.ts.map +1 -1
- package/hooks/use-send-message.d.ts +9 -0
- package/hooks/use-send-message.d.ts.map +1 -1
- package/hooks/use-send-message.js +15 -13
- package/hooks/use-send-message.js.map +1 -1
- package/hooks/use-visitor.d.ts.map +1 -1
- package/hooks/use-visitor.js +28 -30
- package/hooks/use-visitor.js.map +1 -1
- package/hooks/use-window-visibility-focus.d.ts +4 -0
- package/hooks/use-window-visibility-focus.d.ts.map +1 -1
- package/hooks/use-window-visibility-focus.js +5 -2
- package/hooks/use-window-visibility-focus.js.map +1 -1
- package/identify-visitor.d.ts +12 -3
- package/identify-visitor.d.ts.map +1 -1
- package/identify-visitor.js +58 -9
- package/identify-visitor.js.map +1 -1
- package/index.d.ts +10 -7
- package/index.js +10 -9
- package/package.json +14 -17
- package/primitives/avatar/avatar.d.ts.map +1 -1
- package/primitives/avatar/fallback.d.ts.map +1 -1
- package/primitives/avatar/fallback.js +1 -3
- package/primitives/avatar/fallback.js.map +1 -1
- package/primitives/avatar/image.d.ts.map +1 -1
- package/primitives/avatar/index.d.ts +1 -0
- package/primitives/bubble.d.ts +2 -0
- package/primitives/bubble.d.ts.map +1 -1
- package/primitives/bubble.js +8 -2
- package/primitives/bubble.js.map +1 -1
- package/primitives/button.d.ts.map +1 -1
- package/primitives/conversation-timeline.d.ts.map +1 -1
- package/primitives/conversation-timeline.js +58 -5
- package/primitives/conversation-timeline.js.map +1 -1
- package/primitives/index.d.ts +1 -0
- package/primitives/index.parts.d.ts +1 -0
- package/primitives/multimodal-input.d.ts.map +1 -1
- package/primitives/timeline-item-group.d.ts +7 -7
- package/primitives/timeline-item-group.d.ts.map +1 -1
- package/primitives/timeline-item-group.js.map +1 -1
- package/primitives/timeline-item.d.ts +1 -1
- package/primitives/timeline-item.d.ts.map +1 -1
- package/primitives/timeline-item.js +7 -1
- package/primitives/timeline-item.js.map +1 -1
- package/primitives/window.d.ts +1 -1
- package/primitives/window.d.ts.map +1 -1
- package/primitives/window.js +4 -4
- package/primitives/window.js.map +1 -1
- package/provider.d.ts +23 -43
- package/provider.d.ts.map +1 -1
- package/provider.js +152 -49
- package/provider.js.map +1 -1
- package/realtime/event-filter.d.ts +4 -0
- package/realtime/event-filter.d.ts.map +1 -1
- package/realtime/event-filter.js +4 -0
- package/realtime/event-filter.js.map +1 -1
- package/realtime/index.js +1 -1
- package/realtime/provider.d.ts +7 -2
- package/realtime/provider.d.ts.map +1 -1
- package/realtime/provider.js +23 -1
- package/realtime/provider.js.map +1 -1
- package/realtime/seen-store.d.ts +13 -0
- package/realtime/seen-store.d.ts.map +1 -1
- package/realtime/seen-store.js +14 -2
- package/realtime/seen-store.js.map +1 -1
- package/realtime/support-provider.d.ts +1 -2
- package/realtime/support-provider.d.ts.map +1 -1
- package/realtime/support-provider.js +19 -20
- package/realtime/support-provider.js.map +1 -1
- package/realtime/typing-store.d.ts +18 -0
- package/realtime/typing-store.d.ts.map +1 -1
- package/realtime/typing-store.js +19 -2
- package/realtime/typing-store.js.map +1 -1
- package/realtime/use-realtime.d.ts +8 -4
- package/realtime/use-realtime.d.ts.map +1 -1
- package/realtime/use-realtime.js +4 -0
- package/realtime/use-realtime.js.map +1 -1
- package/realtime-events.d.ts +17 -3
- package/realtime-events.d.ts.map +1 -1
- package/schemas.d.ts +7 -1
- package/schemas.d.ts.map +1 -1
- package/support/components/avatar-stack.d.ts +8 -4
- package/support/components/avatar-stack.d.ts.map +1 -1
- package/support/components/avatar-stack.js +4 -0
- package/support/components/avatar-stack.js.map +1 -1
- package/support/components/avatar.d.ts +11 -6
- package/support/components/avatar.d.ts.map +1 -1
- package/support/components/avatar.js +4 -0
- package/support/components/avatar.js.map +1 -1
- package/support/components/bubble.d.ts.map +1 -1
- package/support/components/bubble.js +29 -6
- package/support/components/bubble.js.map +1 -1
- package/support/components/button.d.ts +8 -5
- package/support/components/button.d.ts.map +1 -1
- package/support/components/button.js +5 -1
- package/support/components/button.js.map +1 -1
- package/support/components/container.d.ts +0 -1
- package/support/components/container.d.ts.map +1 -1
- package/support/components/container.js +2 -8
- package/support/components/container.js.map +1 -1
- package/support/components/conversation-button-link.d.ts +8 -21
- package/support/components/conversation-button-link.d.ts.map +1 -1
- package/support/components/conversation-button-link.js +62 -178
- package/support/components/conversation-button-link.js.map +1 -1
- package/support/components/conversation-event.d.ts.map +1 -1
- package/support/components/conversation-event.js +4 -0
- package/support/components/conversation-event.js.map +1 -1
- package/support/components/conversation-timeline.d.ts +10 -1
- package/support/components/conversation-timeline.d.ts.map +1 -1
- package/support/components/conversation-timeline.js +63 -57
- package/support/components/conversation-timeline.js.map +1 -1
- package/support/components/cossistant-branding.d.ts +5 -2
- package/support/components/cossistant-branding.d.ts.map +1 -1
- package/support/components/cossistant-branding.js +3 -0
- package/support/components/cossistant-branding.js.map +1 -1
- package/support/components/header.d.ts.map +1 -1
- package/support/components/header.js +2 -2
- package/support/components/header.js.map +1 -1
- package/support/components/icons.d.ts.map +1 -1
- package/support/components/multimodal-input.d.ts.map +1 -1
- package/support/components/multimodal-input.js +5 -24
- package/support/components/multimodal-input.js.map +1 -1
- package/support/components/navigation-tab.d.ts +7 -2
- package/support/components/navigation-tab.d.ts.map +1 -1
- package/support/components/navigation-tab.js +4 -0
- package/support/components/navigation-tab.js.map +1 -1
- package/support/components/support-content.d.ts +1 -1
- package/support/components/support-content.d.ts.map +1 -1
- package/support/components/support-content.js +7 -10
- package/support/components/support-content.js.map +1 -1
- package/support/components/text-effect.d.ts +5 -2
- package/support/components/text-effect.d.ts.map +1 -1
- package/support/components/text-effect.js +4 -0
- package/support/components/text-effect.js.map +1 -1
- package/support/components/timeline-identification-tool.d.ts +7 -0
- package/support/components/timeline-identification-tool.d.ts.map +1 -0
- package/support/components/timeline-identification-tool.js +139 -0
- package/support/components/timeline-identification-tool.js.map +1 -0
- package/support/components/timeline-message-group.d.ts +2 -1
- package/support/components/timeline-message-group.d.ts.map +1 -1
- package/support/components/timeline-message-group.js +4 -19
- package/support/components/timeline-message-group.js.map +1 -1
- package/support/components/timeline-message-item.d.ts +6 -2
- package/support/components/timeline-message-item.d.ts.map +1 -1
- package/support/components/timeline-message-item.js +8 -4
- package/support/components/timeline-message-item.js.map +1 -1
- package/support/components/typing-indicator.d.ts +5 -2
- package/support/components/typing-indicator.d.ts.map +1 -1
- package/support/components/typing-indicator.js +4 -4
- package/support/components/typing-indicator.js.map +1 -1
- package/support/components/watermark.d.ts.map +1 -1
- package/support/context/websocket.d.ts +8 -0
- package/support/context/websocket.d.ts.map +1 -1
- package/support/context/websocket.js +12 -6
- package/support/context/websocket.js.map +1 -1
- package/support/index.d.ts +8 -8
- package/support/index.d.ts.map +1 -1
- package/support/index.js +18 -18
- package/support/index.js.map +1 -1
- package/support/pages/conversation-history.js +46 -54
- package/support/pages/conversation-history.js.map +1 -1
- package/support/pages/conversation.d.ts +3 -6
- package/support/pages/conversation.d.ts.map +1 -1
- package/support/pages/conversation.js +19 -9
- package/support/pages/conversation.js.map +1 -1
- package/support/pages/home.d.ts +2 -2
- package/support/pages/home.d.ts.map +1 -1
- package/support/pages/home.js +64 -77
- package/support/pages/home.js.map +1 -1
- package/support/store/support-store.d.ts +18 -2
- package/support/store/support-store.d.ts.map +1 -1
- package/support/store/support-store.js +20 -5
- package/support/store/support-store.js.map +1 -1
- package/support/{support-CMoDLQoC.css → support-Ck4jy29i.css} +1 -2
- package/support/support-Ck4jy29i.css.map +1 -0
- package/support/text/index.d.ts +15 -2
- package/support/text/index.d.ts.map +1 -1
- package/support/text/index.js +15 -2
- package/support/text/index.js.map +1 -1
- package/support/text/locales/en.js +22 -4
- package/support/text/locales/en.js.map +1 -1
- package/support/text/locales/es.js +18 -0
- package/support/text/locales/es.js.map +1 -1
- package/support/text/locales/fr.js +18 -0
- package/support/text/locales/fr.js.map +1 -1
- package/support/text/locales/keys.d.ts +69 -9
- package/support/text/locales/keys.d.ts.map +1 -1
- package/support/text/locales/keys.js +18 -0
- package/support/text/locales/keys.js.map +1 -1
- package/support/text/runtime.d.ts +21 -0
- package/support/text/runtime.d.ts.map +1 -1
- package/support/text/runtime.js +21 -0
- package/support/text/runtime.js.map +1 -1
- package/support/utils/index.d.ts +4 -0
- package/support/utils/index.d.ts.map +1 -1
- package/support/utils/index.js +4 -1
- package/support/utils/index.js.map +1 -1
- package/support/utils/time.d.ts +3 -0
- package/support/utils/time.d.ts.map +1 -1
- package/support/utils/time.js +3 -0
- package/support/utils/time.js.map +1 -1
- package/support-config.d.ts +2 -1
- package/support-config.d.ts.map +1 -1
- package/support-config.js.map +1 -1
- package/support.css +2 -2
- package/timeline-item.d.ts +10 -0
- package/timeline-item.d.ts.map +1 -1
- package/utils/conversation.d.ts +7 -0
- package/utils/conversation.d.ts.map +1 -0
- package/utils/conversation.js +18 -0
- package/utils/conversation.js.map +1 -0
- package/utils/id.d.ts +3 -0
- package/utils/id.d.ts.map +1 -1
- package/utils/id.js +3 -0
- package/utils/id.js.map +1 -1
- package/utils/index.d.ts +2 -1
- package/utils/index.js +2 -1
- package/utils/metadata-hash.d.ts +12 -0
- package/utils/metadata-hash.d.ts.map +1 -0
- package/utils/metadata-hash.js +26 -0
- package/utils/metadata-hash.js.map +1 -0
- package/utils/text.d.ts +3 -0
- package/utils/text.d.ts.map +1 -1
- package/utils/text.js +3 -0
- package/utils/text.js.map +1 -1
- package/utils/use-render-element.d.ts +3 -0
- package/utils/use-render-element.d.ts.map +1 -1
- package/utils/use-render-element.js +3 -0
- package/utils/use-render-element.js.map +1 -1
- package/support/context/config.d.ts +0 -32
- package/support/context/config.d.ts.map +0 -1
- package/support/context/config.js +0 -27
- package/support/context/config.js.map +0 -1
- package/support/support-CMoDLQoC.css.map +0 -1
package/provider.d.ts
CHANGED
|
@@ -1,12 +1,10 @@
|
|
|
1
|
-
import
|
|
1
|
+
import React from "react";
|
|
2
2
|
import { CossistantClient } from "@cossistant/core";
|
|
3
3
|
import { DefaultMessage, PublicWebsiteResponse } from "@cossistant/types";
|
|
4
|
-
import * as react_jsx_runtime0 from "react/jsx-runtime";
|
|
5
|
-
import { QueryClient } from "@tanstack/react-query";
|
|
6
4
|
|
|
7
5
|
//#region src/provider.d.ts
|
|
8
6
|
type SupportProviderProps = {
|
|
9
|
-
children: React
|
|
7
|
+
children: React.ReactNode;
|
|
10
8
|
defaultOpen?: boolean;
|
|
11
9
|
apiUrl?: string;
|
|
12
10
|
wsUrl?: string;
|
|
@@ -17,7 +15,7 @@ type SupportProviderProps = {
|
|
|
17
15
|
onWsConnect?: () => void;
|
|
18
16
|
onWsDisconnect?: () => void;
|
|
19
17
|
onWsError?: (error: Error) => void;
|
|
20
|
-
|
|
18
|
+
size?: "normal" | "larger";
|
|
21
19
|
};
|
|
22
20
|
type CossistantProviderProps = SupportProviderProps;
|
|
23
21
|
type CossistantContextValue = {
|
|
@@ -31,7 +29,22 @@ type CossistantContextValue = {
|
|
|
31
29
|
isLoading: boolean;
|
|
32
30
|
error: Error | null;
|
|
33
31
|
client: CossistantClient;
|
|
32
|
+
isOpen: boolean;
|
|
33
|
+
open: () => void;
|
|
34
|
+
close: () => void;
|
|
35
|
+
toggle: () => void;
|
|
34
36
|
};
|
|
37
|
+
type WebsiteData = NonNullable<CossistantContextValue["website"]>;
|
|
38
|
+
type VisitorWithLocale = WebsiteData["visitor"] extends null | undefined ? undefined : NonNullable<WebsiteData["visitor"]> & {
|
|
39
|
+
locale: string | null;
|
|
40
|
+
};
|
|
41
|
+
type UseSupportValue = CossistantContextValue & {
|
|
42
|
+
availableHumanAgents: NonNullable<WebsiteData["availableHumanAgents"]> | [];
|
|
43
|
+
availableAIAgents: NonNullable<WebsiteData["availableAIAgents"]> | [];
|
|
44
|
+
visitor?: VisitorWithLocale;
|
|
45
|
+
size: "normal" | "larger";
|
|
46
|
+
};
|
|
47
|
+
declare const SupportContext: React.Context<CossistantContextValue | undefined>;
|
|
35
48
|
/**
|
|
36
49
|
* Hosts the entire customer support widget ecosystem by handing out context
|
|
37
50
|
* about the current website, visitor, unread counts, realtime subscriptions
|
|
@@ -49,47 +62,14 @@ declare function SupportProvider({
|
|
|
49
62
|
onWsConnect,
|
|
50
63
|
onWsDisconnect,
|
|
51
64
|
onWsError,
|
|
52
|
-
|
|
53
|
-
|
|
65
|
+
size,
|
|
66
|
+
defaultOpen
|
|
67
|
+
}: SupportProviderProps): React.ReactElement;
|
|
54
68
|
/**
|
|
55
69
|
* Convenience hook that exposes the aggregated support context. Throws when it
|
|
56
70
|
* is consumed outside of `SupportProvider` to catch integration mistakes.
|
|
57
71
|
*/
|
|
58
|
-
declare function useSupport():
|
|
59
|
-
availableHumanAgents: {
|
|
60
|
-
id: string;
|
|
61
|
-
name: string;
|
|
62
|
-
image: string | null;
|
|
63
|
-
lastSeenAt: string | null;
|
|
64
|
-
}[];
|
|
65
|
-
availableAIAgents: {
|
|
66
|
-
id: string;
|
|
67
|
-
name: string;
|
|
68
|
-
image: string | null;
|
|
69
|
-
}[];
|
|
70
|
-
visitor: {
|
|
71
|
-
locale: string | null;
|
|
72
|
-
id: string;
|
|
73
|
-
isBlocked: boolean;
|
|
74
|
-
language: string | null;
|
|
75
|
-
contact: {
|
|
76
|
-
id: string;
|
|
77
|
-
name: string | null;
|
|
78
|
-
email: string | null;
|
|
79
|
-
image: string | null;
|
|
80
|
-
} | null;
|
|
81
|
-
} | undefined;
|
|
82
|
-
website: PublicWebsiteResponse | null;
|
|
83
|
-
defaultMessages: DefaultMessage[];
|
|
84
|
-
quickOptions: string[];
|
|
85
|
-
setDefaultMessages: (messages: DefaultMessage[]) => void;
|
|
86
|
-
setQuickOptions: (options: string[]) => void;
|
|
87
|
-
unreadCount: number;
|
|
88
|
-
setUnreadCount: (count: number) => void;
|
|
89
|
-
isLoading: boolean;
|
|
90
|
-
error: Error | null;
|
|
91
|
-
client: CossistantClient;
|
|
92
|
-
};
|
|
72
|
+
declare function useSupport(): UseSupportValue;
|
|
93
73
|
//#endregion
|
|
94
|
-
export { CossistantContextValue, CossistantProviderProps, SupportProvider, SupportProviderProps, useSupport };
|
|
74
|
+
export { CossistantContextValue, CossistantProviderProps, SupportContext, SupportProvider, SupportProviderProps, UseSupportValue, useSupport };
|
|
95
75
|
//# sourceMappingURL=provider.d.ts.map
|
package/provider.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"provider.d.ts","names":[],"sources":["../src/provider.tsx"],"sourcesContent":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"provider.d.ts","names":[],"sources":["../src/provider.tsx"],"sourcesContent":[],"mappings":";;;;;KAgBY,oBAAA;YACD,KAAA,CAAM;EADL,WAAA,CAAA,EAAA,OAAA;EACD,MAAM,CAAA,EAAA,MAAA;EAKE,KAAA,CAAA,EAAA,MAAA;EAKE,SAAA,CAAA,EAAA,MAAA;EAAK,eAAA,CAAA,EALP,cAKO,EAAA;EAId,YAAA,CAAA,EAAA,MAAA,EAAA;EAEA,WAAA,CAAA,EAAA,OAAA;EACF,WAAA,CAAA,EAAA,GAAA,GAAA,IAAA;EACQ,cAAA,CAAA,EAAA,GAAA,GAAA,IAAA;EAEc,SAAA,CAAA,EAAA,CAAA,KAAA,EAVX,KAUW,EAAA,GAAA,IAAA;EAKxB,IAAA,CAAA,EAAA,QAAA,GAAA,QAAA;CACC;AAAgB,KAZb,uBAAA,GAA0B,oBAYb;AAOpB,KAjBO,sBAAA,GAiBmB;EAE1B,OAAA,EAlBK,qBAkBY,GAAA,IAAA;EAAG,eAAA,EAjBP,cAiBO,EAAA;EAEV,YAAA,EAAA,MAAA,EAAA;EAAZ,kBAAA,EAAA,CAAA,QAAA,EAjB6B,cAiB7B,EAAA,EAAA,GAAA,IAAA;EAAW,eAAA,EAAA,CAAA,OAAA,EAAA,MAAA,EAAA,EAAA,GAAA,IAAA;EAwCF,WAAA,EAAA,MAAe;EAAG,cAAA,EAAA,CAAA,KAAA,EAAA,MAAA,EAAA,GAAA,IAAA;EACK,SAAA,EAAA,OAAA;EAAZ,KAAA,EArDf,KAqDe,GAAA,IAAA;EACS,MAAA,EArDvB,gBAqDuB;EAAZ,MAAA,EAAA,OAAA;EACT,IAAA,EAAA,GAAA,GAAA,IAAA;EAAiB,KAAA,EAAA,GAAA,GAAA,IAAA;EAIf,MAAA,EAAA,GAAA,GAAA,IAED;AAiRZ,CAAA;KAtUK,WAAA,GAAc,WAuUlB,CAvU8B,sBAuU9B,CAAA,SAAA,CAAA,CAAA;KArUI,iBAAA,GAAoB,WAsUxB,CAAA,SAAA,CAAA,SAAA,IAAA,GAAA,SAAA,GAAA,SAAA,GApUE,WAoUF,CApUc,WAoUd,CAAA,SAAA,CAAA,CAAA,GAAA;EACA,MAAA,EAAA,MAAA,GAAA,IAAA;CACA;AACA,KA/RW,eAAA,GAAkB,sBA+R7B,GAAA;EACA,oBAAA,EA/RsB,WA+RtB,CA/RkC,WA+RlC,CAAA,sBAAA,CAAA,CAAA,GAAA,EAAA;EACA,iBAAA,EA/RmB,WA+RnB,CA/R+B,WA+R/B,CAAA,mBAAA,CAAA,CAAA,GAAA,EAAA;EACA,OAAA,CAAA,EA/RU,iBA+RV;EACA,IAAA,EAAA,QAAA,GAAA,QAAA;CACA;AACA,cA9RY,cA8RZ,EA9R0B,KAAA,CAAA,OA8R1B,CA9R0B,sBA8R1B,GAAA,SAAA,CAAA;;;;;AA0BD;;iBArCgB,eAAA;;;;;;;;;;;;;GAab,uBAAuB,KAAA,CAAM;;;;;iBAwBhB,UAAA,CAAA,GAAc"}
|
package/provider.js
CHANGED
|
@@ -1,48 +1,145 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { QueryClientProvider } from "./node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js";
|
|
1
|
+
import { useStoreSelector } from "./hooks/private/store/use-store-selector.js";
|
|
3
2
|
import { useWebsiteStore } from "./hooks/private/store/use-website-store.js";
|
|
4
3
|
import { useClient } from "./hooks/private/use-rest-client.js";
|
|
4
|
+
import { useSeenStore } from "./realtime/seen-store.js";
|
|
5
|
+
import { initializeSupportStore, useSupportStore } from "./support/store/support-store.js";
|
|
5
6
|
import { WebSocketProvider } from "./support/context/websocket.js";
|
|
6
|
-
import
|
|
7
|
+
import React from "react";
|
|
7
8
|
import { normalizeLocale } from "@cossistant/core";
|
|
9
|
+
import { ConversationTimelineType } from "@cossistant/types/enums";
|
|
8
10
|
import { jsx } from "react/jsx-runtime";
|
|
9
11
|
|
|
10
12
|
//#region src/provider.tsx
|
|
11
|
-
|
|
13
|
+
function areConversationSnapshotsEqual(a, b) {
|
|
14
|
+
if (a === b) return true;
|
|
15
|
+
if (a.length !== b.length) return false;
|
|
16
|
+
for (let index = 0; index < a.length; index += 1) {
|
|
17
|
+
const snapshotA = a[index];
|
|
18
|
+
const snapshotB = b[index];
|
|
19
|
+
if (!snapshotA) return false;
|
|
20
|
+
if (!snapshotB) return false;
|
|
21
|
+
const aLastCreatedAt = snapshotA.lastTimelineItem?.createdAt ?? null;
|
|
22
|
+
const bLastCreatedAt = snapshotB.lastTimelineItem?.createdAt ?? null;
|
|
23
|
+
if (snapshotA.id !== snapshotB.id || aLastCreatedAt !== bLastCreatedAt) return false;
|
|
24
|
+
}
|
|
25
|
+
return true;
|
|
26
|
+
}
|
|
27
|
+
const SupportContext = React.createContext(void 0);
|
|
12
28
|
/**
|
|
13
|
-
* Internal implementation that wires the
|
|
14
|
-
*
|
|
29
|
+
* Internal implementation that wires the REST client and websocket provider
|
|
30
|
+
* together before exposing the combined context.
|
|
15
31
|
*/
|
|
16
|
-
function SupportProviderInner({ children, apiUrl, wsUrl, publicKey, defaultMessages, quickOptions, autoConnect, onWsConnect, onWsDisconnect, onWsError }) {
|
|
17
|
-
const [unreadCount, setUnreadCount] = React
|
|
18
|
-
const
|
|
19
|
-
const [
|
|
20
|
-
React
|
|
21
|
-
|
|
32
|
+
function SupportProviderInner({ children, apiUrl, wsUrl, publicKey, defaultMessages, quickOptions, autoConnect, onWsConnect, onWsDisconnect, onWsError, size = "normal", defaultOpen = false }) {
|
|
33
|
+
const [unreadCount, setUnreadCount] = React.useState(0);
|
|
34
|
+
const prefetchedVisitorRef = React.useRef(null);
|
|
35
|
+
const [_defaultMessages, _setDefaultMessages] = React.useState(defaultMessages ?? []);
|
|
36
|
+
const [_quickOptions, _setQuickOptions] = React.useState(quickOptions ?? []);
|
|
37
|
+
React.useEffect(() => {
|
|
38
|
+
initializeSupportStore({
|
|
39
|
+
size,
|
|
40
|
+
defaultOpen
|
|
41
|
+
});
|
|
42
|
+
}, [size, defaultOpen]);
|
|
43
|
+
const { config, open, close, toggle } = useSupportStore();
|
|
44
|
+
React.useEffect(() => {
|
|
45
|
+
if (defaultMessages?.length) _setDefaultMessages(defaultMessages);
|
|
22
46
|
}, [defaultMessages]);
|
|
23
|
-
React
|
|
24
|
-
if (quickOptions
|
|
47
|
+
React.useEffect(() => {
|
|
48
|
+
if (quickOptions?.length) _setQuickOptions(quickOptions);
|
|
25
49
|
}, [quickOptions]);
|
|
26
50
|
const { client } = useClient(publicKey, apiUrl, wsUrl);
|
|
27
51
|
const { website, isLoading, error: websiteError } = useWebsiteStore(client);
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
52
|
+
const isVisitorBlocked = website?.visitor?.isBlocked ?? false;
|
|
53
|
+
const visitorId = website?.visitor?.id ?? null;
|
|
54
|
+
const seenEntriesByConversation = useSeenStore(React.useCallback((state) => state.conversations, []));
|
|
55
|
+
const conversationSnapshots = useStoreSelector(client.conversationsStore, React.useCallback((state) => state.ids.map((id) => {
|
|
56
|
+
const conversation = state.byId[id];
|
|
57
|
+
if (!conversation) return null;
|
|
58
|
+
return {
|
|
59
|
+
id: conversation.id,
|
|
60
|
+
lastTimelineItem: conversation.lastTimelineItem ?? null
|
|
61
|
+
};
|
|
62
|
+
}).filter((snapshot) => snapshot !== null), []), areConversationSnapshotsEqual);
|
|
63
|
+
const derivedUnreadCount = React.useMemo(() => {
|
|
64
|
+
if (!visitorId) return 0;
|
|
65
|
+
let count = 0;
|
|
66
|
+
for (const { id: conversationId, lastTimelineItem } of conversationSnapshots) {
|
|
67
|
+
if (!lastTimelineItem) continue;
|
|
68
|
+
if (lastTimelineItem.type !== ConversationTimelineType.MESSAGE) continue;
|
|
69
|
+
if (lastTimelineItem.visitorId && lastTimelineItem.visitorId === visitorId) continue;
|
|
70
|
+
const createdAtTime = Date.parse(lastTimelineItem.createdAt);
|
|
71
|
+
if (Number.isNaN(createdAtTime)) continue;
|
|
72
|
+
const seenEntries = seenEntriesByConversation[conversationId];
|
|
73
|
+
if (seenEntries) {
|
|
74
|
+
const visitorSeenEntry = Object.values(seenEntries).find((entry) => entry.actorType === "visitor" && entry.actorId === visitorId);
|
|
75
|
+
if (visitorSeenEntry) {
|
|
76
|
+
const lastSeenTime = Date.parse(visitorSeenEntry.lastSeenAt);
|
|
77
|
+
if (!Number.isNaN(lastSeenTime) && createdAtTime <= lastSeenTime) continue;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
count += 1;
|
|
81
|
+
}
|
|
82
|
+
return count;
|
|
83
|
+
}, [
|
|
84
|
+
conversationSnapshots,
|
|
85
|
+
seenEntriesByConversation,
|
|
86
|
+
visitorId
|
|
87
|
+
]);
|
|
88
|
+
React.useEffect(() => {
|
|
89
|
+
setUnreadCount(derivedUnreadCount);
|
|
90
|
+
}, [derivedUnreadCount, setUnreadCount]);
|
|
91
|
+
React.useEffect(() => {
|
|
92
|
+
if (!website) return;
|
|
93
|
+
client.setWebsiteContext(website.id, website.visitor?.id ?? void 0);
|
|
31
94
|
}, [client, website]);
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
95
|
+
React.useEffect(() => {
|
|
96
|
+
if (isVisitorBlocked) {
|
|
97
|
+
prefetchedVisitorRef.current = null;
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
if (!autoConnect) return;
|
|
101
|
+
if (!website) return;
|
|
102
|
+
if (!visitorId) return;
|
|
103
|
+
if (prefetchedVisitorRef.current === visitorId) return;
|
|
104
|
+
const hasExistingConversations = client.conversationsStore.getState().ids.length > 0;
|
|
105
|
+
prefetchedVisitorRef.current = visitorId;
|
|
106
|
+
if (hasExistingConversations) return;
|
|
107
|
+
client.listConversations().catch((err) => {
|
|
108
|
+
console.error("[SupportProvider] Failed to prefetch conversations", err);
|
|
109
|
+
prefetchedVisitorRef.current = null;
|
|
110
|
+
});
|
|
111
|
+
}, [
|
|
112
|
+
autoConnect,
|
|
113
|
+
client,
|
|
114
|
+
isVisitorBlocked,
|
|
115
|
+
visitorId,
|
|
116
|
+
website
|
|
117
|
+
]);
|
|
118
|
+
const error = websiteError;
|
|
119
|
+
React.useEffect(() => {
|
|
120
|
+
client.setVisitorBlocked(isVisitorBlocked);
|
|
121
|
+
}, [client, isVisitorBlocked]);
|
|
122
|
+
const setDefaultMessages = React.useCallback((messages) => {
|
|
123
|
+
_setDefaultMessages(messages);
|
|
124
|
+
}, []);
|
|
125
|
+
const setQuickOptions = React.useCallback((options) => {
|
|
126
|
+
_setQuickOptions(options);
|
|
127
|
+
}, []);
|
|
128
|
+
const value = React.useMemo(() => ({
|
|
36
129
|
website,
|
|
37
130
|
unreadCount,
|
|
38
|
-
setUnreadCount
|
|
131
|
+
setUnreadCount,
|
|
39
132
|
isLoading,
|
|
40
133
|
error,
|
|
41
134
|
client,
|
|
42
135
|
defaultMessages: _defaultMessages,
|
|
43
136
|
setDefaultMessages,
|
|
44
137
|
quickOptions: _quickOptions,
|
|
45
|
-
setQuickOptions
|
|
138
|
+
setQuickOptions,
|
|
139
|
+
isOpen: config.isOpen,
|
|
140
|
+
open,
|
|
141
|
+
close,
|
|
142
|
+
toggle
|
|
46
143
|
}), [
|
|
47
144
|
website,
|
|
48
145
|
unreadCount,
|
|
@@ -53,21 +150,30 @@ function SupportProviderInner({ children, apiUrl, wsUrl, publicKey, defaultMessa
|
|
|
53
150
|
_quickOptions,
|
|
54
151
|
setDefaultMessages,
|
|
55
152
|
setQuickOptions,
|
|
56
|
-
|
|
153
|
+
config.isOpen,
|
|
154
|
+
open,
|
|
155
|
+
close,
|
|
156
|
+
toggle
|
|
57
157
|
]);
|
|
158
|
+
const webSocketKey = React.useMemo(() => {
|
|
159
|
+
if (!website) return "no-website";
|
|
160
|
+
const visitorKey = website.visitor?.id ?? "anonymous";
|
|
161
|
+
const blockedState = isVisitorBlocked ? "blocked" : "active";
|
|
162
|
+
return `${website.id}:${visitorKey}:${blockedState}`;
|
|
163
|
+
}, [isVisitorBlocked, website]);
|
|
58
164
|
return /* @__PURE__ */ jsx(SupportContext.Provider, {
|
|
59
165
|
value,
|
|
60
166
|
children: /* @__PURE__ */ jsx(WebSocketProvider, {
|
|
61
|
-
autoConnect,
|
|
167
|
+
autoConnect: autoConnect && !isVisitorBlocked,
|
|
62
168
|
onConnect: onWsConnect,
|
|
63
169
|
onDisconnect: onWsDisconnect,
|
|
64
170
|
onError: onWsError,
|
|
65
171
|
publicKey,
|
|
66
|
-
visitorId: website?.visitor?.id,
|
|
172
|
+
visitorId: isVisitorBlocked ? void 0 : website?.visitor?.id,
|
|
67
173
|
websiteId: website?.id,
|
|
68
174
|
wsUrl,
|
|
69
175
|
children
|
|
70
|
-
})
|
|
176
|
+
}, webSocketKey)
|
|
71
177
|
});
|
|
72
178
|
}
|
|
73
179
|
/**
|
|
@@ -76,25 +182,20 @@ function SupportProviderInner({ children, apiUrl, wsUrl, publicKey, defaultMessa
|
|
|
76
182
|
* and the REST client. Provide your Cossistant public key plus optional
|
|
77
183
|
* defaults to configure the widget behaviour.
|
|
78
184
|
*/
|
|
79
|
-
function SupportProvider({ children, apiUrl = "https://api.cossistant.com/v1", wsUrl = "wss://api.cossistant.com/ws", publicKey, defaultMessages, quickOptions, autoConnect = true, onWsConnect, onWsDisconnect, onWsError,
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
publicKey,
|
|
94
|
-
quickOptions,
|
|
95
|
-
wsUrl,
|
|
96
|
-
children
|
|
97
|
-
})
|
|
185
|
+
function SupportProvider({ children, apiUrl = "https://api.cossistant.com/v1", wsUrl = "wss://api.cossistant.com/ws", publicKey, defaultMessages, quickOptions, autoConnect = true, onWsConnect, onWsDisconnect, onWsError, size = "normal", defaultOpen = false }) {
|
|
186
|
+
return /* @__PURE__ */ jsx(SupportProviderInner, {
|
|
187
|
+
apiUrl,
|
|
188
|
+
autoConnect,
|
|
189
|
+
defaultMessages,
|
|
190
|
+
defaultOpen,
|
|
191
|
+
onWsConnect,
|
|
192
|
+
onWsDisconnect,
|
|
193
|
+
onWsError,
|
|
194
|
+
publicKey,
|
|
195
|
+
quickOptions,
|
|
196
|
+
size,
|
|
197
|
+
wsUrl,
|
|
198
|
+
children
|
|
98
199
|
});
|
|
99
200
|
}
|
|
100
201
|
/**
|
|
@@ -102,11 +203,12 @@ function SupportProvider({ children, apiUrl = "https://api.cossistant.com/v1", w
|
|
|
102
203
|
* is consumed outside of `SupportProvider` to catch integration mistakes.
|
|
103
204
|
*/
|
|
104
205
|
function useSupport() {
|
|
105
|
-
const context = React
|
|
206
|
+
const context = React.useContext(SupportContext);
|
|
106
207
|
if (!context) throw new Error("useSupport must be used within a cossistant SupportProvider");
|
|
107
208
|
const availableHumanAgents = context.website?.availableHumanAgents || [];
|
|
108
209
|
const availableAIAgents = context.website?.availableAIAgents || [];
|
|
109
210
|
const visitorLanguage = context.website?.visitor?.language || null;
|
|
211
|
+
const { config } = useSupportStore();
|
|
110
212
|
const visitor = context.website?.visitor ? {
|
|
111
213
|
...context.website.visitor,
|
|
112
214
|
locale: normalizeLocale(visitorLanguage)
|
|
@@ -115,10 +217,11 @@ function useSupport() {
|
|
|
115
217
|
...context,
|
|
116
218
|
availableHumanAgents,
|
|
117
219
|
availableAIAgents,
|
|
118
|
-
visitor
|
|
220
|
+
visitor,
|
|
221
|
+
size: config.size
|
|
119
222
|
};
|
|
120
223
|
}
|
|
121
224
|
|
|
122
225
|
//#endregion
|
|
123
|
-
export { SupportProvider, useSupport };
|
|
226
|
+
export { SupportContext, SupportProvider, useSupport };
|
|
124
227
|
//# sourceMappingURL=provider.js.map
|
package/provider.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"provider.js","names":["React"],"sources":["../src/provider.tsx"],"sourcesContent":["import type { CossistantClient } from \"@cossistant/core\";\nimport { normalizeLocale } from \"@cossistant/core\";\nimport type { DefaultMessage, PublicWebsiteResponse } from \"@cossistant/types\";\nimport { QueryClient, QueryClientProvider } from \"@tanstack/react-query\";\nimport * as React from \"react\";\nimport { useWebsiteStore } from \"./hooks/private/store/use-website-store\";\nimport { useClient } from \"./hooks/private/use-rest-client\";\nimport { WebSocketProvider } from \"./support\";\n\nexport type SupportProviderProps = {\n\tchildren: React.ReactNode;\n\tdefaultOpen?: boolean;\n\tapiUrl?: string;\n\twsUrl?: string;\n\tpublicKey?: string;\n\tdefaultMessages?: DefaultMessage[];\n\tquickOptions?: string[];\n\tautoConnect?: boolean;\n\tonWsConnect?: () => void;\n\tonWsDisconnect?: () => void;\n\tonWsError?: (error: Error) => void;\n\tqueryClient?: QueryClient;\n};\n\nexport type CossistantProviderProps = SupportProviderProps;\n\nexport type CossistantContextValue = {\n\twebsite: PublicWebsiteResponse | null;\n\tdefaultMessages: DefaultMessage[];\n\tquickOptions: string[];\n\tsetDefaultMessages: (messages: DefaultMessage[]) => void;\n\tsetQuickOptions: (options: string[]) => void;\n\tunreadCount: number;\n\tsetUnreadCount: (count: number) => void;\n\tisLoading: boolean;\n\terror: Error | null;\n\tclient: CossistantClient;\n};\n\nconst SupportContext = React.createContext<CossistantContextValue | undefined>(\n\tundefined\n);\n\n/**\n * Internal implementation that wires the React Query cache, REST client and\n * websocket provider together before exposing the combined context.\n */\nfunction SupportProviderInner({\n\tchildren,\n\tapiUrl,\n\twsUrl,\n\tpublicKey,\n\tdefaultMessages,\n\tquickOptions,\n\tautoConnect,\n\tonWsConnect,\n\tonWsDisconnect,\n\tonWsError,\n}: SupportProviderProps) {\n\tconst [unreadCount, setUnreadCount] = React.useState(0);\n\tconst [_defaultMessages, _setDefaultMessages] = React.useState<\n\t\tDefaultMessage[]\n\t>(defaultMessages || []);\n\tconst [_quickOptions, _setQuickOptions] = React.useState<string[]>(\n\t\tquickOptions || []\n\t);\n\t// Update state when props change (for initial values from provider)\n\tReact.useEffect(() => {\n\t\tif (defaultMessages && defaultMessages.length > 0) {\n\t\t\t_setDefaultMessages(defaultMessages);\n\t\t}\n\t}, [defaultMessages]);\n\n\tReact.useEffect(() => {\n\t\tif (quickOptions && quickOptions.length > 0) {\n\t\t\t_setQuickOptions(quickOptions);\n\t\t}\n\t}, [quickOptions]);\n\n\tconst { client } = useClient(publicKey, apiUrl, wsUrl);\n\tconst { website, isLoading, error: websiteError } = useWebsiteStore(client);\n\n\t// Prefetch conversations\n\t// useConversations(client, {\n\t// enabled: !!website && !!website.visitor && isClientPrimed,\n\t// });\n\n\tconst error = websiteError;\n\n\t// Prime REST client with website/visitor context so headers are sent reliably\n\tReact.useEffect(() => {\n\t\tif (website) {\n\t\t\t// @ts-expect-error internal priming: safe in our library context\n\t\t\tclient.restClient?.setWebsiteContext?.(website.id, website.visitor?.id);\n\t\t}\n\t}, [client, website]);\n\n\tconst setDefaultMessages = React.useCallback(\n\t\t(messages: DefaultMessage[]) => _setDefaultMessages(messages),\n\t\t[]\n\t);\n\n\tconst setQuickOptions = React.useCallback(\n\t\t(options: string[]) => _setQuickOptions(options),\n\t\t[]\n\t);\n\n\tconst setUnreadCountStable = React.useCallback(\n\t\t(count: number) => setUnreadCount(count),\n\t\t[]\n\t);\n\n\tconst value = React.useMemo<CossistantContextValue>(\n\t\t() => ({\n\t\t\twebsite,\n\t\t\tunreadCount,\n\t\t\tsetUnreadCount: setUnreadCountStable,\n\t\t\tisLoading,\n\t\t\terror,\n\t\t\tclient,\n\t\t\tdefaultMessages: _defaultMessages,\n\t\t\tsetDefaultMessages,\n\t\t\tquickOptions: _quickOptions,\n\t\t\tsetQuickOptions,\n\t\t}),\n\t\t[\n\t\t\twebsite,\n\t\t\tunreadCount,\n\t\t\tisLoading,\n\t\t\terror,\n\t\t\tclient,\n\t\t\t_defaultMessages,\n\t\t\t_quickOptions,\n\t\t\tsetDefaultMessages,\n\t\t\tsetQuickOptions,\n\t\t\tsetUnreadCountStable,\n\t\t]\n\t);\n\n\treturn (\n\t\t<SupportContext.Provider value={value}>\n\t\t\t<WebSocketProvider\n\t\t\t\tautoConnect={autoConnect}\n\t\t\t\tonConnect={onWsConnect}\n\t\t\t\tonDisconnect={onWsDisconnect}\n\t\t\t\tonError={onWsError}\n\t\t\t\tpublicKey={publicKey}\n\t\t\t\tvisitorId={website?.visitor?.id}\n\t\t\t\twebsiteId={website?.id}\n\t\t\t\twsUrl={wsUrl}\n\t\t\t>\n\t\t\t\t{children}\n\t\t\t</WebSocketProvider>\n\t\t</SupportContext.Provider>\n\t);\n}\n\n/**\n * Hosts the entire customer support widget ecosystem by handing out context\n * about the current website, visitor, unread counts, realtime subscriptions\n * and the REST client. Provide your Cossistant public key plus optional\n * defaults to configure the widget behaviour.\n */\nexport function SupportProvider({\n\tchildren,\n\tapiUrl = \"https://api.cossistant.com/v1\",\n\twsUrl = \"wss://api.cossistant.com/ws\",\n\tpublicKey,\n\tdefaultMessages,\n\tquickOptions,\n\tautoConnect = true,\n\tonWsConnect,\n\tonWsDisconnect,\n\tonWsError,\n\tqueryClient,\n}: SupportProviderProps) {\n\t// Create a default QueryClient if none provided\n\tconst [defaultQueryClient] = React.useState(\n\t\t() =>\n\t\t\tnew QueryClient({\n\t\t\t\tdefaultOptions: {\n\t\t\t\t\tqueries: {\n\t\t\t\t\t\tstaleTime: 5 * 60 * 1000, // 5 minutes\n\t\t\t\t\t\tgcTime: 10 * 60 * 1000, // 10 minutes\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t})\n\t);\n\n\tconst activeQueryClient = queryClient || defaultQueryClient;\n\n\treturn (\n\t\t<QueryClientProvider client={activeQueryClient}>\n\t\t\t<SupportProviderInner\n\t\t\t\tapiUrl={apiUrl}\n\t\t\t\tautoConnect={autoConnect}\n\t\t\t\tdefaultMessages={defaultMessages}\n\t\t\t\tonWsConnect={onWsConnect}\n\t\t\t\tonWsDisconnect={onWsDisconnect}\n\t\t\t\tonWsError={onWsError}\n\t\t\t\tpublicKey={publicKey}\n\t\t\t\tquickOptions={quickOptions}\n\t\t\t\twsUrl={wsUrl}\n\t\t\t>\n\t\t\t\t{children}\n\t\t\t</SupportProviderInner>\n\t\t</QueryClientProvider>\n\t);\n}\n\n/**\n * Convenience hook that exposes the aggregated support context. Throws when it\n * is consumed outside of `SupportProvider` to catch integration mistakes.\n */\nexport function useSupport() {\n\tconst context = React.useContext(SupportContext);\n\tif (!context) {\n\t\tthrow new Error(\n\t\t\t\"useSupport must be used within a cossistant SupportProvider\"\n\t\t);\n\t}\n\n\tconst availableHumanAgents = context.website?.availableHumanAgents || [];\n\tconst availableAIAgents = context.website?.availableAIAgents || [];\n\tconst visitorLanguage = context.website?.visitor?.language || null;\n\n\t// Create visitor object with normalized locale\n\tconst visitor = context.website?.visitor\n\t\t? {\n\t\t\t\t...context.website.visitor,\n\t\t\t\tlocale: normalizeLocale(visitorLanguage),\n\t\t\t}\n\t\t: undefined;\n\n\treturn {\n\t\t...context,\n\t\tavailableHumanAgents,\n\t\tavailableAIAgents,\n\t\tvisitor,\n\t};\n}\n"],"mappings":";;;;;;;;;;AAuCA,MAAM,iBAAiBA,QAAM,cAC5B,OACA;;;;;AAMD,SAAS,qBAAqB,EAC7B,UACA,QACA,OACA,WACA,iBACA,cACA,aACA,aACA,gBACA,aACwB;CACxB,MAAM,CAAC,aAAa,kBAAkBA,QAAM,SAAS,EAAE;CACvD,MAAM,CAAC,kBAAkB,uBAAuBA,QAAM,SAEpD,mBAAmB,EAAE,CAAC;CACxB,MAAM,CAAC,eAAe,oBAAoBA,QAAM,SAC/C,gBAAgB,EAAE,CAClB;AAED,SAAM,gBAAgB;AACrB,MAAI,mBAAmB,gBAAgB,SAAS,EAC/C,qBAAoB,gBAAgB;IAEnC,CAAC,gBAAgB,CAAC;AAErB,SAAM,gBAAgB;AACrB,MAAI,gBAAgB,aAAa,SAAS,EACzC,kBAAiB,aAAa;IAE7B,CAAC,aAAa,CAAC;CAElB,MAAM,EAAE,WAAW,UAAU,WAAW,QAAQ,MAAM;CACtD,MAAM,EAAE,SAAS,WAAW,OAAO,iBAAiB,gBAAgB,OAAO;CAO3E,MAAM,QAAQ;AAGd,SAAM,gBAAgB;AACrB,MAAI,QAEH,QAAO,YAAY,oBAAoB,QAAQ,IAAI,QAAQ,SAAS,GAAG;IAEtE,CAAC,QAAQ,QAAQ,CAAC;CAErB,MAAM,qBAAqBA,QAAM,aAC/B,aAA+B,oBAAoB,SAAS,EAC7D,EAAE,CACF;CAED,MAAM,kBAAkBA,QAAM,aAC5B,YAAsB,iBAAiB,QAAQ,EAChD,EAAE,CACF;CAED,MAAM,uBAAuBA,QAAM,aACjC,UAAkB,eAAe,MAAM,EACxC,EAAE,CACF;CAED,MAAM,QAAQA,QAAM,eACZ;EACN;EACA;EACA,gBAAgB;EAChB;EACA;EACA;EACA,iBAAiB;EACjB;EACA,cAAc;EACd;EACA,GACD;EACC;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,CACD;AAED,QACC,oBAAC,eAAe;EAAgB;YAC/B,oBAAC;GACa;GACb,WAAW;GACX,cAAc;GACd,SAAS;GACE;GACX,WAAW,SAAS,SAAS;GAC7B,WAAW,SAAS;GACb;GAEN;IACkB;GACK;;;;;;;;AAU5B,SAAgB,gBAAgB,EAC/B,UACA,SAAS,iCACT,QAAQ,+BACR,WACA,iBACA,cACA,cAAc,MACd,aACA,gBACA,WACA,eACwB;CAExB,MAAM,CAAC,sBAAsBA,QAAM,eAEjC,IAAI,YAAY,EACf,gBAAgB,EACf,SAAS;EACR,WAAW,MAAS;EACpB,QAAQ,MAAU;EAClB,EACD,EACD,CAAC,CACH;AAID,QACC,oBAAC;EAAoB,QAHI,eAAe;YAIvC,oBAAC;GACQ;GACK;GACI;GACJ;GACG;GACL;GACA;GACG;GACP;GAEN;IACqB;GACF;;;;;;AAQxB,SAAgB,aAAa;CAC5B,MAAM,UAAUA,QAAM,WAAW,eAAe;AAChD,KAAI,CAAC,QACJ,OAAM,IAAI,MACT,8DACA;CAGF,MAAM,uBAAuB,QAAQ,SAAS,wBAAwB,EAAE;CACxE,MAAM,oBAAoB,QAAQ,SAAS,qBAAqB,EAAE;CAClE,MAAM,kBAAkB,QAAQ,SAAS,SAAS,YAAY;CAG9D,MAAM,UAAU,QAAQ,SAAS,UAC9B;EACA,GAAG,QAAQ,QAAQ;EACnB,QAAQ,gBAAgB,gBAAgB;EACxC,GACA;AAEH,QAAO;EACN,GAAG;EACH;EACA;EACA;EACA"}
|
|
1
|
+
{"version":3,"file":"provider.js","names":[],"sources":["../src/provider.tsx"],"sourcesContent":["import type { CossistantClient } from \"@cossistant/core\";\nimport { normalizeLocale } from \"@cossistant/core\";\nimport type { DefaultMessage, PublicWebsiteResponse } from \"@cossistant/types\";\nimport type { TimelineItem } from \"@cossistant/types/api/timeline-item\";\nimport { ConversationTimelineType } from \"@cossistant/types/enums\";\nimport React from \"react\";\nimport { useStoreSelector } from \"./hooks/private/store/use-store-selector\";\nimport { useWebsiteStore } from \"./hooks/private/store/use-website-store\";\nimport { useClient } from \"./hooks/private/use-rest-client\";\nimport { useSeenStore } from \"./realtime/seen-store\";\nimport { WebSocketProvider } from \"./support\";\nimport {\n\tinitializeSupportStore,\n\tuseSupportStore,\n} from \"./support/store/support-store\";\n\nexport type SupportProviderProps = {\n\tchildren: React.ReactNode;\n\tdefaultOpen?: boolean;\n\tapiUrl?: string;\n\twsUrl?: string;\n\tpublicKey?: string;\n\tdefaultMessages?: DefaultMessage[];\n\tquickOptions?: string[];\n\tautoConnect?: boolean;\n\tonWsConnect?: () => void;\n\tonWsDisconnect?: () => void;\n\tonWsError?: (error: Error) => void;\n\tsize?: \"normal\" | \"larger\";\n};\n\nexport type CossistantProviderProps = SupportProviderProps;\n\nexport type CossistantContextValue = {\n\twebsite: PublicWebsiteResponse | null;\n\tdefaultMessages: DefaultMessage[];\n\tquickOptions: string[];\n\tsetDefaultMessages: (messages: DefaultMessage[]) => void;\n\tsetQuickOptions: (options: string[]) => void;\n\tunreadCount: number;\n\tsetUnreadCount: (count: number) => void;\n\tisLoading: boolean;\n\terror: Error | null;\n\tclient: CossistantClient;\n\tisOpen: boolean;\n\topen: () => void;\n\tclose: () => void;\n\ttoggle: () => void;\n};\n\ntype WebsiteData = NonNullable<CossistantContextValue[\"website\"]>;\n\ntype VisitorWithLocale = WebsiteData[\"visitor\"] extends null | undefined\n\t? undefined\n\t: NonNullable<WebsiteData[\"visitor\"]> & { locale: string | null };\n\ntype ConversationSnapshot = {\n\tid: string;\n\tlastTimelineItem: TimelineItem | null;\n};\n\nfunction areConversationSnapshotsEqual(\n\ta: ConversationSnapshot[],\n\tb: ConversationSnapshot[]\n): boolean {\n\tif (a === b) {\n\t\treturn true;\n\t}\n\n\tif (a.length !== b.length) {\n\t\treturn false;\n\t}\n\n\tfor (let index = 0; index < a.length; index += 1) {\n\t\tconst snapshotA = a[index];\n\t\tconst snapshotB = b[index];\n\n\t\tif (!snapshotA) {\n\t\t\treturn false;\n\t\t}\n\t\tif (!snapshotB) {\n\t\t\treturn false;\n\t\t}\n\n\t\tconst aLastCreatedAt = snapshotA.lastTimelineItem?.createdAt ?? null;\n\t\tconst bLastCreatedAt = snapshotB.lastTimelineItem?.createdAt ?? null;\n\t\tif (snapshotA.id !== snapshotB.id || aLastCreatedAt !== bLastCreatedAt) {\n\t\t\treturn false;\n\t\t}\n\t}\n\n\treturn true;\n}\n\nexport type UseSupportValue = CossistantContextValue & {\n\tavailableHumanAgents: NonNullable<WebsiteData[\"availableHumanAgents\"]> | [];\n\tavailableAIAgents: NonNullable<WebsiteData[\"availableAIAgents\"]> | [];\n\tvisitor?: VisitorWithLocale;\n\tsize: \"normal\" | \"larger\";\n};\n\nexport const SupportContext = React.createContext<\n\tCossistantContextValue | undefined\n>(undefined);\n\n/**\n * Internal implementation that wires the REST client and websocket provider\n * together before exposing the combined context.\n */\nfunction SupportProviderInner({\n\tchildren,\n\tapiUrl,\n\twsUrl,\n\tpublicKey,\n\tdefaultMessages,\n\tquickOptions,\n\tautoConnect,\n\tonWsConnect,\n\tonWsDisconnect,\n\tonWsError,\n\tsize = \"normal\",\n\tdefaultOpen = false,\n}: SupportProviderProps) {\n\tconst [unreadCount, setUnreadCount] = React.useState(0);\n\tconst prefetchedVisitorRef = React.useRef<string | null>(null);\n\tconst [_defaultMessages, _setDefaultMessages] = React.useState<\n\t\tDefaultMessage[]\n\t>(defaultMessages ?? []);\n\tconst [_quickOptions, _setQuickOptions] = React.useState<string[]>(\n\t\tquickOptions ?? []\n\t);\n\n\t// Initialize support store with configuration\n\tReact.useEffect(() => {\n\t\tinitializeSupportStore({ size, defaultOpen });\n\t}, [size, defaultOpen]);\n\n\t// Get support store state and actions\n\tconst { config, open, close, toggle } = useSupportStore();\n\n\t// Update state when props change (for initial values from provider)\n\tReact.useEffect(() => {\n\t\tif (defaultMessages?.length) {\n\t\t\t_setDefaultMessages(defaultMessages);\n\t\t}\n\t}, [defaultMessages]);\n\n\tReact.useEffect(() => {\n\t\tif (quickOptions?.length) {\n\t\t\t_setQuickOptions(quickOptions);\n\t\t}\n\t}, [quickOptions]);\n\n\tconst { client } = useClient(publicKey, apiUrl, wsUrl);\n\tconst { website, isLoading, error: websiteError } = useWebsiteStore(client);\n\tconst isVisitorBlocked = website?.visitor?.isBlocked ?? false;\n\tconst visitorId = website?.visitor?.id ?? null;\n\n\tconst seenEntriesByConversation = useSeenStore(\n\t\tReact.useCallback((state) => state.conversations, [])\n\t);\n\n\tconst conversationSnapshots = useStoreSelector(\n\t\tclient.conversationsStore,\n\t\tReact.useCallback(\n\t\t\t(state) =>\n\t\t\t\tstate.ids\n\t\t\t\t\t.map((id) => {\n\t\t\t\t\t\tconst conversation = state.byId[id];\n\n\t\t\t\t\t\tif (!conversation) {\n\t\t\t\t\t\t\treturn null;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\tid: conversation.id,\n\t\t\t\t\t\t\tlastTimelineItem: conversation.lastTimelineItem ?? null,\n\t\t\t\t\t\t} satisfies ConversationSnapshot;\n\t\t\t\t\t})\n\t\t\t\t\t.filter(\n\t\t\t\t\t\t(snapshot): snapshot is ConversationSnapshot => snapshot !== null\n\t\t\t\t\t),\n\t\t\t[]\n\t\t),\n\t\tareConversationSnapshotsEqual\n\t);\n\n\tconst derivedUnreadCount = React.useMemo(() => {\n\t\tif (!visitorId) {\n\t\t\treturn 0;\n\t\t}\n\n\t\tlet count = 0;\n\n\t\tfor (const {\n\t\t\tid: conversationId,\n\t\t\tlastTimelineItem,\n\t\t} of conversationSnapshots) {\n\t\t\tif (!lastTimelineItem) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (lastTimelineItem.type !== ConversationTimelineType.MESSAGE) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (\n\t\t\t\tlastTimelineItem.visitorId &&\n\t\t\t\tlastTimelineItem.visitorId === visitorId\n\t\t\t) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tconst createdAtTime = Date.parse(lastTimelineItem.createdAt);\n\n\t\t\tif (Number.isNaN(createdAtTime)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tconst seenEntries = seenEntriesByConversation[conversationId];\n\n\t\t\tif (seenEntries) {\n\t\t\t\tconst visitorSeenEntry = Object.values(seenEntries).find(\n\t\t\t\t\t(entry) =>\n\t\t\t\t\t\tentry.actorType === \"visitor\" && entry.actorId === visitorId\n\t\t\t\t);\n\n\t\t\t\tif (visitorSeenEntry) {\n\t\t\t\t\tconst lastSeenTime = Date.parse(visitorSeenEntry.lastSeenAt);\n\n\t\t\t\t\tif (!Number.isNaN(lastSeenTime) && createdAtTime <= lastSeenTime) {\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tcount += 1;\n\t\t}\n\n\t\treturn count;\n\t}, [conversationSnapshots, seenEntriesByConversation, visitorId]);\n\n\tReact.useEffect(() => {\n\t\tsetUnreadCount(derivedUnreadCount);\n\t}, [derivedUnreadCount, setUnreadCount]);\n\n\t// Prime REST client with website/visitor context so headers are sent reliably\n\tReact.useEffect(() => {\n\t\tif (!website) {\n\t\t\treturn;\n\t\t}\n\n\t\tclient.setWebsiteContext(website.id, website.visitor?.id ?? undefined);\n\t}, [client, website]);\n\n\tReact.useEffect(() => {\n\t\tif (isVisitorBlocked) {\n\t\t\tprefetchedVisitorRef.current = null;\n\t\t\treturn;\n\t\t}\n\n\t\tif (!autoConnect) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (!website) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (!visitorId) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (prefetchedVisitorRef.current === visitorId) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst hasExistingConversations =\n\t\t\tclient.conversationsStore.getState().ids.length > 0;\n\n\t\tprefetchedVisitorRef.current = visitorId;\n\n\t\tif (hasExistingConversations) {\n\t\t\treturn;\n\t\t}\n\n\t\tvoid client.listConversations().catch((err) => {\n\t\t\tconsole.error(\"[SupportProvider] Failed to prefetch conversations\", err);\n\t\t\tprefetchedVisitorRef.current = null;\n\t\t});\n\t}, [autoConnect, client, isVisitorBlocked, visitorId, website]);\n\n\tconst error = websiteError;\n\n\tReact.useEffect(() => {\n\t\tclient.setVisitorBlocked(isVisitorBlocked);\n\t}, [client, isVisitorBlocked]);\n\n\tconst setDefaultMessages = React.useCallback((messages: DefaultMessage[]) => {\n\t\t_setDefaultMessages(messages);\n\t}, []);\n\n\tconst setQuickOptions = React.useCallback((options: string[]) => {\n\t\t_setQuickOptions(options);\n\t}, []);\n\n\tconst value = React.useMemo<CossistantContextValue>(\n\t\t() => ({\n\t\t\twebsite,\n\t\t\tunreadCount,\n\t\t\tsetUnreadCount,\n\t\t\tisLoading,\n\t\t\terror,\n\t\t\tclient,\n\t\t\tdefaultMessages: _defaultMessages,\n\t\t\tsetDefaultMessages,\n\t\t\tquickOptions: _quickOptions,\n\t\t\tsetQuickOptions,\n\t\t\tisOpen: config.isOpen,\n\t\t\topen,\n\t\t\tclose,\n\t\t\ttoggle,\n\t\t}),\n\t\t[\n\t\t\twebsite,\n\t\t\tunreadCount,\n\t\t\tisLoading,\n\t\t\terror,\n\t\t\tclient,\n\t\t\t_defaultMessages,\n\t\t\t_quickOptions,\n\t\t\tsetDefaultMessages,\n\t\t\tsetQuickOptions,\n\t\t\tconfig.isOpen,\n\t\t\topen,\n\t\t\tclose,\n\t\t\ttoggle,\n\t\t]\n\t);\n\n\tconst webSocketKey = React.useMemo(() => {\n\t\tif (!website) {\n\t\t\treturn \"no-website\";\n\t\t}\n\n\t\tconst visitorKey = website.visitor?.id ?? \"anonymous\";\n\t\tconst blockedState = isVisitorBlocked ? \"blocked\" : \"active\";\n\n\t\treturn `${website.id}:${visitorKey}:${blockedState}`;\n\t}, [isVisitorBlocked, website]);\n\n\treturn (\n\t\t<SupportContext.Provider value={value}>\n\t\t\t<WebSocketProvider\n\t\t\t\tautoConnect={autoConnect && !isVisitorBlocked}\n\t\t\t\tkey={webSocketKey}\n\t\t\t\tonConnect={onWsConnect}\n\t\t\t\tonDisconnect={onWsDisconnect}\n\t\t\t\tonError={onWsError}\n\t\t\t\tpublicKey={publicKey}\n\t\t\t\tvisitorId={isVisitorBlocked ? undefined : website?.visitor?.id}\n\t\t\t\twebsiteId={website?.id}\n\t\t\t\twsUrl={wsUrl}\n\t\t\t>\n\t\t\t\t{children}\n\t\t\t</WebSocketProvider>\n\t\t</SupportContext.Provider>\n\t);\n}\n\n/**\n * Hosts the entire customer support widget ecosystem by handing out context\n * about the current website, visitor, unread counts, realtime subscriptions\n * and the REST client. Provide your Cossistant public key plus optional\n * defaults to configure the widget behaviour.\n */\nexport function SupportProvider({\n\tchildren,\n\tapiUrl = \"https://api.cossistant.com/v1\",\n\twsUrl = \"wss://api.cossistant.com/ws\",\n\tpublicKey,\n\tdefaultMessages,\n\tquickOptions,\n\tautoConnect = true,\n\tonWsConnect,\n\tonWsDisconnect,\n\tonWsError,\n\tsize = \"normal\",\n\tdefaultOpen = false,\n}: SupportProviderProps): React.ReactElement {\n\treturn (\n\t\t<SupportProviderInner\n\t\t\tapiUrl={apiUrl}\n\t\t\tautoConnect={autoConnect}\n\t\t\tdefaultMessages={defaultMessages}\n\t\t\tdefaultOpen={defaultOpen}\n\t\t\tonWsConnect={onWsConnect}\n\t\t\tonWsDisconnect={onWsDisconnect}\n\t\t\tonWsError={onWsError}\n\t\t\tpublicKey={publicKey}\n\t\t\tquickOptions={quickOptions}\n\t\t\tsize={size}\n\t\t\twsUrl={wsUrl}\n\t\t>\n\t\t\t{children}\n\t\t</SupportProviderInner>\n\t);\n}\n\n/**\n * Convenience hook that exposes the aggregated support context. Throws when it\n * is consumed outside of `SupportProvider` to catch integration mistakes.\n */\nexport function useSupport(): UseSupportValue {\n\tconst context = React.useContext(SupportContext);\n\tif (!context) {\n\t\tthrow new Error(\n\t\t\t\"useSupport must be used within a cossistant SupportProvider\"\n\t\t);\n\t}\n\n\tconst availableHumanAgents = context.website?.availableHumanAgents || [];\n\tconst availableAIAgents = context.website?.availableAIAgents || [];\n\tconst visitorLanguage = context.website?.visitor?.language || null;\n\n\t// Get additional config from support store\n\tconst { config } = useSupportStore();\n\n\t// Create visitor object with normalized locale\n\tconst visitor = context.website?.visitor\n\t\t? {\n\t\t\t\t...context.website.visitor,\n\t\t\t\tlocale: normalizeLocale(visitorLanguage),\n\t\t\t}\n\t\t: undefined;\n\n\treturn {\n\t\t...context,\n\t\tavailableHumanAgents,\n\t\tavailableAIAgents,\n\t\tvisitor,\n\t\tsize: config.size,\n\t};\n}\n"],"mappings":"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"}
|
|
@@ -2,6 +2,10 @@ import { AnyRealtimeEvent } from "../realtime-events.js";
|
|
|
2
2
|
|
|
3
3
|
//#region src/realtime/event-filter.d.ts
|
|
4
4
|
declare function getTargetVisitorId(event: AnyRealtimeEvent): string | null;
|
|
5
|
+
/**
|
|
6
|
+
* Determines whether a realtime event should be processed based on website and
|
|
7
|
+
* visitor identifiers.
|
|
8
|
+
*/
|
|
5
9
|
declare function shouldDeliverEvent(event: AnyRealtimeEvent, websiteId: string | null, visitorId: string | null): boolean;
|
|
6
10
|
//#endregion
|
|
7
11
|
export { getTargetVisitorId, shouldDeliverEvent };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"event-filter.d.ts","names":[],"sources":["../../src/realtime/event-filter.ts"],"sourcesContent":[],"mappings":";;;iBAES,kBAAA,QAA0B;
|
|
1
|
+
{"version":3,"file":"event-filter.d.ts","names":[],"sources":["../../src/realtime/event-filter.ts"],"sourcesContent":[],"mappings":";;;iBAES,kBAAA,QAA0B;;AAFuC;AAwB1E;;iBAAgB,kBAAA,QACR"}
|
package/realtime/event-filter.js
CHANGED
|
@@ -8,6 +8,10 @@ function getTargetVisitorId(event) {
|
|
|
8
8
|
}
|
|
9
9
|
return null;
|
|
10
10
|
}
|
|
11
|
+
/**
|
|
12
|
+
* Determines whether a realtime event should be processed based on website and
|
|
13
|
+
* visitor identifiers.
|
|
14
|
+
*/
|
|
11
15
|
function shouldDeliverEvent(event, websiteId, visitorId) {
|
|
12
16
|
if (websiteId && event.payload.websiteId !== websiteId) return false;
|
|
13
17
|
if (!visitorId) return true;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"event-filter.js","names":[],"sources":["../../src/realtime/event-filter.ts"],"sourcesContent":["import type { AnyRealtimeEvent } from \"@cossistant/types/realtime-events\";\n\nfunction getTargetVisitorId(event: AnyRealtimeEvent): string | null {\n\tconst payloadVisitorId = event.payload.visitorId;\n\n\tif (typeof payloadVisitorId === \"string\" && payloadVisitorId.length > 0) {\n\t\treturn payloadVisitorId;\n\t}\n\n\tif (event.type === \"timelineItemCreated\") {\n\t\tconst itemVisitorId = event.payload.item.visitorId;\n\n\t\tif (typeof itemVisitorId === \"string\" && itemVisitorId.length > 0) {\n\t\t\treturn itemVisitorId;\n\t\t}\n\t}\n\n\treturn null;\n}\n\nexport function shouldDeliverEvent(\n\tevent: AnyRealtimeEvent,\n\twebsiteId: string | null,\n\tvisitorId: string | null\n): boolean {\n\tif (websiteId && event.payload.websiteId !== websiteId) {\n\t\treturn false;\n\t}\n\n\tif (!visitorId) {\n\t\treturn true;\n\t}\n\n\tconst targetVisitorId = getTargetVisitorId(event);\n\n\tif (targetVisitorId && targetVisitorId !== visitorId) {\n\t\treturn false;\n\t}\n\n\treturn true;\n}\n\nexport { getTargetVisitorId };\n"],"mappings":";AAEA,SAAS,mBAAmB,OAAwC;CACnE,MAAM,mBAAmB,MAAM,QAAQ;AAEvC,KAAI,OAAO,qBAAqB,YAAY,iBAAiB,SAAS,EACrE,QAAO;AAGR,KAAI,MAAM,SAAS,uBAAuB;EACzC,MAAM,gBAAgB,MAAM,QAAQ,KAAK;AAEzC,MAAI,OAAO,kBAAkB,YAAY,cAAc,SAAS,EAC/D,QAAO;;AAIT,QAAO
|
|
1
|
+
{"version":3,"file":"event-filter.js","names":[],"sources":["../../src/realtime/event-filter.ts"],"sourcesContent":["import type { AnyRealtimeEvent } from \"@cossistant/types/realtime-events\";\n\nfunction getTargetVisitorId(event: AnyRealtimeEvent): string | null {\n\tconst payloadVisitorId = event.payload.visitorId;\n\n\tif (typeof payloadVisitorId === \"string\" && payloadVisitorId.length > 0) {\n\t\treturn payloadVisitorId;\n\t}\n\n\tif (event.type === \"timelineItemCreated\") {\n\t\tconst itemVisitorId = event.payload.item.visitorId;\n\n\t\tif (typeof itemVisitorId === \"string\" && itemVisitorId.length > 0) {\n\t\t\treturn itemVisitorId;\n\t\t}\n\t}\n\n\treturn null;\n}\n\n/**\n * Determines whether a realtime event should be processed based on website and\n * visitor identifiers.\n */\nexport function shouldDeliverEvent(\n\tevent: AnyRealtimeEvent,\n\twebsiteId: string | null,\n\tvisitorId: string | null\n): boolean {\n\tif (websiteId && event.payload.websiteId !== websiteId) {\n\t\treturn false;\n\t}\n\n\tif (!visitorId) {\n\t\treturn true;\n\t}\n\n\tconst targetVisitorId = getTargetVisitorId(event);\n\n\tif (targetVisitorId && targetVisitorId !== visitorId) {\n\t\treturn false;\n\t}\n\n\treturn true;\n}\n\nexport { getTargetVisitorId };\n"],"mappings":";AAEA,SAAS,mBAAmB,OAAwC;CACnE,MAAM,mBAAmB,MAAM,QAAQ;AAEvC,KAAI,OAAO,qBAAqB,YAAY,iBAAiB,SAAS,EACrE,QAAO;AAGR,KAAI,MAAM,SAAS,uBAAuB;EACzC,MAAM,gBAAgB,MAAM,QAAQ,KAAK;AAEzC,MAAI,OAAO,kBAAkB,YAAY,cAAc,SAAS,EAC/D,QAAO;;AAIT,QAAO;;;;;;AAOR,SAAgB,mBACf,OACA,WACA,WACU;AACV,KAAI,aAAa,MAAM,QAAQ,cAAc,UAC5C,QAAO;AAGR,KAAI,CAAC,UACJ,QAAO;CAGR,MAAM,kBAAkB,mBAAmB,MAAM;AAEjD,KAAI,mBAAmB,oBAAoB,UAC1C,QAAO;AAGR,QAAO"}
|
package/realtime/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { RealtimeProvider, useRealtimeConnection } from "./provider.js";
|
|
2
1
|
import { applyConversationSeenEvent, hydrateConversationSeen, upsertConversationSeen } from "./seen-store.js";
|
|
2
|
+
import { RealtimeProvider, useRealtimeConnection } from "./provider.js";
|
|
3
3
|
import { applyConversationTypingEvent, clearTypingFromTimelineItem, clearTypingState, setTypingState } from "./typing-store.js";
|
|
4
4
|
import { useRealtime } from "./use-realtime.js";
|
|
5
5
|
import { SupportRealtimeProvider } from "./support-provider.js";
|
package/realtime/provider.d.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { AnyRealtimeEvent, RealtimeEvent } from "../realtime-events.js";
|
|
2
2
|
import React from "react";
|
|
3
|
-
import * as react_jsx_runtime0 from "react/jsx-runtime";
|
|
4
3
|
|
|
5
4
|
//#region src/realtime/provider.d.ts
|
|
6
5
|
type SubscribeHandler = (event: AnyRealtimeEvent) => void;
|
|
@@ -42,6 +41,9 @@ type RealtimeContextValue = RealtimeConnectionState & {
|
|
|
42
41
|
websiteId: string | null;
|
|
43
42
|
userId: string | null;
|
|
44
43
|
};
|
|
44
|
+
/**
|
|
45
|
+
* Provides websocket connectivity and heartbeating logic for realtime events.
|
|
46
|
+
*/
|
|
45
47
|
declare function RealtimeProvider({
|
|
46
48
|
children,
|
|
47
49
|
wsUrl,
|
|
@@ -50,7 +52,10 @@ declare function RealtimeProvider({
|
|
|
50
52
|
onConnect,
|
|
51
53
|
onDisconnect,
|
|
52
54
|
onError
|
|
53
|
-
}: RealtimeProviderProps):
|
|
55
|
+
}: RealtimeProviderProps): React.ReactElement;
|
|
56
|
+
/**
|
|
57
|
+
* Returns the realtime connection context.
|
|
58
|
+
*/
|
|
54
59
|
declare function useRealtimeConnection(): RealtimeContextValue;
|
|
55
60
|
//#endregion
|
|
56
61
|
export { type RealtimeAuthConfig, type RealtimeContextValue, type RealtimeEvent, RealtimeProvider, type RealtimeProviderProps, useRealtimeConnection };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"provider.d.ts","names":[],"sources":["../../src/realtime/provider.tsx"],"sourcesContent":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"provider.d.ts","names":[],"sources":["../../src/realtime/provider.tsx"],"sourcesContent":[],"mappings":";;;;KAuBK,gBAAA,WAA2B;KA+B3B,iBAAA;EA/BA,IAAA,EAAA,SAAA;EA+BA,SAAA,EAAA,MAAA,GAAiB,IAAA;EAOjB,SAAA,CAAA,EAAA,MAAA,GAAiB,IAAA;EAOjB,SAAA,CAAA,EAAA,MAAA,GAAkB,IAAA;AAAwC,CAAA;KAP1D,iBAAA,GAmBY;EAEV,IAAA,EAAA,SAAA;EAIY,YAAA,EAAA,MAAA,GAAA,IAAA;EAAK,SAAA,CAAA,EAAA,MAAA,GAAA,IAAA;EAGnB,MAAA,CAAA,EAAA,MAAA,GAAA,IAAA;CAGG;KAxBH,kBAAA,GAAqB,iBAyBX,GAzB+B,iBAyB/B;KAdV,qBAAA,GAgBiB;EACV,QAAA,EAhBD,KAAA,CAAM,SAgBL;EAAgB,KAAA,CAAA,EAAA,MAAA;EAKvB,IAAA,EAnBE,kBAmBkB,GAAA,IAAA;EA8QT,WAAA,CAAA,EAAA,OAAgB;EAC/B,SAAA,CAAA,EAAA,GAAA,GAAA,IAAA;EACA,YAAA,CAAA,EAAA,GAAA,GAAA,IAAA;EACA,OAAA,CAAA,EAAA,CAAA,KAAA,EAhSkB,KAgSlB,EAAA,GAAA,IAAA;CACA;KA9RI,uBAAA,GA+RJ;EACA,WAAA,EAAA,OAAA;EACA,YAAA,EAAA,OAAA;EACE,KAAA,EA/RK,KA+RL,GAAA,IAAA;EAAwB,IAAM,EAAA,CAAA,KAAA,EA9RlB,gBA8RkB,EAAA,GAAA,IAAA;EAAY,OAAA,EAAA,CAAA,IAAA,EAAA,MAAA,EAAA,GAAA,IAAA;EA6S7B,SAAA,EAAA,CAAA,OAAA,EAzkBM,gBAykBmB,EAAA,GAAA,GAAA,GAAA,IAAA;aAxkB7B;;;;KAKP,oBAAA,GAAuB;;;;;;;;iBA8QZ,gBAAA;;;;;;;;GAQb,wBAAwB,KAAA,CAAM;;;;iBA6SjB,qBAAA,CAAA,GAAyB"}
|
package/realtime/provider.js
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
|
|
4
4
|
import { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
|
|
5
|
-
import { jsx } from "react/jsx-runtime";
|
|
6
5
|
import { isValidEventType, validateRealtimeEvent } from "@cossistant/types/realtime-events";
|
|
7
6
|
import useWebSocket, { ReadyState } from "react-use-websocket";
|
|
7
|
+
import { jsx } from "react/jsx-runtime";
|
|
8
8
|
|
|
9
9
|
//#region src/realtime/provider.tsx
|
|
10
10
|
const DEFAULT_HEARTBEAT_INTERVAL_MS = 15e3;
|
|
@@ -180,11 +180,16 @@ function buildSocketUrl(baseUrl, auth) {
|
|
|
180
180
|
return null;
|
|
181
181
|
}
|
|
182
182
|
}
|
|
183
|
+
/**
|
|
184
|
+
* Provides websocket connectivity and heartbeating logic for realtime events.
|
|
185
|
+
*/
|
|
183
186
|
function RealtimeProvider({ children, wsUrl = DEFAULT_WS_URL, auth, autoConnect = true, onConnect, onDisconnect, onError }) {
|
|
184
187
|
const normalizedAuth = normalizeAuth(auth);
|
|
185
188
|
const socketUrl = buildSocketUrl(wsUrl, normalizedAuth);
|
|
186
189
|
const eventHandlersRef = useRef(/* @__PURE__ */ new Set());
|
|
187
190
|
const lastHeartbeatRef = useRef(Date.now());
|
|
191
|
+
const hasOpenedRef = useRef(false);
|
|
192
|
+
const previousUrlRef = useRef(null);
|
|
188
193
|
const [connectionError, setConnectionError] = useState(null);
|
|
189
194
|
const [lastEvent, setLastEvent] = useState(null);
|
|
190
195
|
const [connectionId, setConnectionId] = useState(null);
|
|
@@ -192,6 +197,12 @@ function RealtimeProvider({ children, wsUrl = DEFAULT_WS_URL, auth, autoConnect
|
|
|
192
197
|
const heartbeatTimeoutMs = DEFAULT_HEARTBEAT_TIMEOUT_MS;
|
|
193
198
|
const canConnect = Boolean(autoConnect && socketUrl);
|
|
194
199
|
const connectionUrl = canConnect ? socketUrl : null;
|
|
200
|
+
useEffect(() => {
|
|
201
|
+
if (connectionUrl !== previousUrlRef.current) {
|
|
202
|
+
previousUrlRef.current = connectionUrl;
|
|
203
|
+
hasOpenedRef.current = false;
|
|
204
|
+
}
|
|
205
|
+
}, [connectionUrl]);
|
|
195
206
|
const { sendMessage, sendJsonMessage, lastMessage, readyState, getWebSocket } = useWebSocket(connectionUrl, {
|
|
196
207
|
shouldReconnect: (closeEvent) => {
|
|
197
208
|
if (!canConnect) return false;
|
|
@@ -210,6 +221,7 @@ function RealtimeProvider({ children, wsUrl = DEFAULT_WS_URL, auth, autoConnect
|
|
|
210
221
|
},
|
|
211
222
|
retryOnError: false,
|
|
212
223
|
onOpen: () => {
|
|
224
|
+
hasOpenedRef.current = true;
|
|
213
225
|
setConnectionError(null);
|
|
214
226
|
lastHeartbeatRef.current = Date.now();
|
|
215
227
|
onConnect?.();
|
|
@@ -219,6 +231,13 @@ function RealtimeProvider({ children, wsUrl = DEFAULT_WS_URL, auth, autoConnect
|
|
|
219
231
|
onDisconnect?.();
|
|
220
232
|
},
|
|
221
233
|
onError: (event) => {
|
|
234
|
+
if (!canConnect) return;
|
|
235
|
+
const socketLike = event.target;
|
|
236
|
+
const currentSocket = getWebSocket();
|
|
237
|
+
const socketState = typeof WebSocket !== "undefined" && socketLike instanceof WebSocket ? socketLike.readyState : void 0;
|
|
238
|
+
const isThisProvidersSocket = currentSocket === socketLike;
|
|
239
|
+
if (!isThisProvidersSocket && currentSocket || !connectionUrl || isThisProvidersSocket && !hasOpenedRef.current && (socketState === WebSocket.CLOSING || socketState === WebSocket.CLOSED)) return;
|
|
240
|
+
if (isThisProvidersSocket && !hasOpenedRef.current && socketState === WebSocket.CONNECTING && connectionUrl !== previousUrlRef.current) return;
|
|
222
241
|
const err = /* @__PURE__ */ new Error(`WebSocket error: ${event.type}`);
|
|
223
242
|
setConnectionError(err);
|
|
224
243
|
onError?.(err);
|
|
@@ -340,6 +359,9 @@ function RealtimeProvider({ children, wsUrl = DEFAULT_WS_URL, auth, autoConnect
|
|
|
340
359
|
children
|
|
341
360
|
});
|
|
342
361
|
}
|
|
362
|
+
/**
|
|
363
|
+
* Returns the realtime connection context.
|
|
364
|
+
*/
|
|
343
365
|
function useRealtimeConnection() {
|
|
344
366
|
const context = useContext(RealtimeContext);
|
|
345
367
|
if (!context) throw new Error("useRealtimeConnection must be used within RealtimeProvider");
|