@clivly/chat-widget 0.16.0 → 0.17.0
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/browser-mount.cjs +113 -23
- package/dist/browser-mount.d.cts +22 -13
- package/dist/browser-mount.d.mts +22 -13
- package/dist/browser-mount.mjs +113 -23
- package/dist/browser.cjs +114 -25
- package/dist/browser.d.cts +22 -15
- package/dist/browser.d.mts +22 -15
- package/dist/browser.mjs +114 -25
- package/dist/index.cjs +114 -25
- package/dist/index.d.cts +21 -19
- package/dist/index.d.mts +21 -19
- package/dist/index.mjs +114 -25
- package/dist/widget.global.js +11 -11
- package/package.json +2 -2
package/dist/browser.cjs
CHANGED
|
@@ -2,7 +2,7 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
|
2
2
|
let react_dom_client = require("react-dom/client");
|
|
3
3
|
let react = require("react");
|
|
4
4
|
let react_jsx_runtime = require("react/jsx-runtime");
|
|
5
|
-
|
|
5
|
+
// src/theme.ts
|
|
6
6
|
const DARK_MEDIA_QUERY = "(prefers-color-scheme: dark)";
|
|
7
7
|
/** The attribute set by next-themes, daisyUI, and most `data-*` theme setups. */
|
|
8
8
|
const THEME_ATTRIBUTE = "data-theme";
|
|
@@ -110,8 +110,51 @@ function parseTheme(value) {
|
|
|
110
110
|
if (value === "auto" || value === "dark" || value === "light") return value;
|
|
111
111
|
return "auto";
|
|
112
112
|
}
|
|
113
|
-
|
|
114
|
-
|
|
113
|
+
// src/visitor-token-storage.ts
|
|
114
|
+
/** Normalised the way the server matches contacts: trimmed, lower-case. */
|
|
115
|
+
function normalizeVisitorEmail(email) {
|
|
116
|
+
return email.trim().toLowerCase();
|
|
117
|
+
}
|
|
118
|
+
const HASH_MODULUS = 4294967291;
|
|
119
|
+
/**
|
|
120
|
+
* A polynomial string hash, reduced mod a prime below 2^32: stable,
|
|
121
|
+
* synchronous, and exact in doubles (every intermediate stays under 2^53).
|
|
122
|
+
* Not a security primitive.
|
|
123
|
+
*/
|
|
124
|
+
function shortHash(value) {
|
|
125
|
+
let hash = 5381;
|
|
126
|
+
for (const char of value) hash = (hash * 33 + (char.codePointAt(0) ?? 0)) % HASH_MODULUS;
|
|
127
|
+
return hash.toString(16).padStart(8, "0");
|
|
128
|
+
}
|
|
129
|
+
function visitorTokenKey(widgetId, email) {
|
|
130
|
+
return `clivly-chat-widget:${widgetId}:visitor-token:${shortHash(normalizeVisitorEmail(email))}`;
|
|
131
|
+
}
|
|
132
|
+
/** The single per-widget key every widget before this change wrote. */
|
|
133
|
+
function legacyVisitorTokenKey(widgetId) {
|
|
134
|
+
return `clivly-chat-widget:${widgetId}:visitor-token`;
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* The token to offer for this person. Falls back to the legacy per-widget
|
|
138
|
+
* token once, so visitors who chatted before this change still resume — the
|
|
139
|
+
* server only honours it if they are the identity that thread belongs to.
|
|
140
|
+
*/
|
|
141
|
+
function readVisitorToken(storage, widgetId, email) {
|
|
142
|
+
if (!storage) return null;
|
|
143
|
+
try {
|
|
144
|
+
return storage.getItem(visitorTokenKey(widgetId, email)) ?? storage.getItem(legacyVisitorTokenKey(widgetId));
|
|
145
|
+
} catch {
|
|
146
|
+
return null;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
/** Store this person's token and retire the legacy shared one. */
|
|
150
|
+
function writeVisitorToken(storage, widgetId, email, token) {
|
|
151
|
+
if (!storage) return;
|
|
152
|
+
try {
|
|
153
|
+
storage.setItem(visitorTokenKey(widgetId, email), token);
|
|
154
|
+
storage.removeItem?.(legacyVisitorTokenKey(widgetId));
|
|
155
|
+
} catch {}
|
|
156
|
+
}
|
|
157
|
+
// src/chat-widget.tsx
|
|
115
158
|
const DELIVERY_LABELS = {
|
|
116
159
|
failed: "Failed to send",
|
|
117
160
|
pending: "Sending…",
|
|
@@ -533,9 +576,6 @@ function createLocalMessage(message, delivery = "sent") {
|
|
|
533
576
|
delivery
|
|
534
577
|
};
|
|
535
578
|
}
|
|
536
|
-
function createStorageKey(widgetId) {
|
|
537
|
-
return `clivly-chat-widget:${widgetId}:visitor-token`;
|
|
538
|
-
}
|
|
539
579
|
function formatTimestamp(value) {
|
|
540
580
|
const date = new Date(value);
|
|
541
581
|
if (Number.isNaN(date.getTime())) return "";
|
|
@@ -961,6 +1001,33 @@ function useLiveConnection({ actions, createTransport, refreshSession, session,
|
|
|
961
1001
|
}, [createTransport, session]);
|
|
962
1002
|
return reconnecting;
|
|
963
1003
|
}
|
|
1004
|
+
/** Who the host says is signed in: their normalised email, or null. */
|
|
1005
|
+
function hostIdentityOf(defaultVisitor) {
|
|
1006
|
+
return isCompleteVisitor(defaultVisitor) ? normalizeVisitorEmail(defaultVisitor.email) : null;
|
|
1007
|
+
}
|
|
1008
|
+
/**
|
|
1009
|
+
* The host's signed-in visitor changed under an open session — a sign-out, or
|
|
1010
|
+
* a different account signing in without a page reload. The session on
|
|
1011
|
+
* screen is the previous person's: drop it, its transcript and its socket, so
|
|
1012
|
+
* the next open starts as whoever is signed in now (chat widget session
|
|
1013
|
+
* review 2026-10-28, S1). A session the visitor started from the pre-chat
|
|
1014
|
+
* form is theirs and is left alone.
|
|
1015
|
+
*/
|
|
1016
|
+
function useResetOnHostIdentityChange({ generationRef, hostIdentity, identityRef, reset, sessionSourceRef }) {
|
|
1017
|
+
const resetRef = (0, react.useRef)(reset);
|
|
1018
|
+
(0, react.useEffect)(() => {
|
|
1019
|
+
resetRef.current = reset;
|
|
1020
|
+
});
|
|
1021
|
+
(0, react.useEffect)(() => {
|
|
1022
|
+
const current = identityRef.current;
|
|
1023
|
+
if (!(current && sessionSourceRef.current === "host")) return;
|
|
1024
|
+
if (hostIdentity === normalizeVisitorEmail(current.email)) return;
|
|
1025
|
+
generationRef.current += 1;
|
|
1026
|
+
identityRef.current = null;
|
|
1027
|
+
sessionSourceRef.current = null;
|
|
1028
|
+
resetRef.current();
|
|
1029
|
+
}, [hostIdentity]);
|
|
1030
|
+
}
|
|
964
1031
|
function ChatWidget({ className, createTransport, defaultVisitor, getSession, initiallyOpen = false, launcher = "auto", launcherLabel = "Chat with us", placement = "bottom-right", storage = DEFAULT_STORAGE, style, theme = "auto", widgetId }) {
|
|
965
1032
|
const [isOpen, setIsOpen] = (0, react.useState)(initiallyOpen);
|
|
966
1033
|
const panelId = (0, react.useId)();
|
|
@@ -979,6 +1046,11 @@ function ChatWidget({ className, createTransport, defaultVisitor, getSession, in
|
|
|
979
1046
|
const transportRef = (0, react.useRef)(null);
|
|
980
1047
|
const listRef = (0, react.useRef)(null);
|
|
981
1048
|
const identityRef = (0, react.useRef)(null);
|
|
1049
|
+
const sessionSourceRef = (0, react.useRef)(null);
|
|
1050
|
+
const generationRef = (0, react.useRef)(0);
|
|
1051
|
+
const hostIdentity = hostIdentityOf(defaultVisitor);
|
|
1052
|
+
const latestHostIdentityRef = (0, react.useRef)(hostIdentity);
|
|
1053
|
+
latestHostIdentityRef.current = hostIdentity;
|
|
982
1054
|
(0, react.useEffect)(() => {
|
|
983
1055
|
if (createTransport) return;
|
|
984
1056
|
console.warn("[clivly] <ChatWidget> was given no createTransport, so messages cannot be sent. Pass createTransport={createClivlyTransport()} — see https://www.npmjs.com/package/@clivly/chat-widget#five-minute-quick-start");
|
|
@@ -1005,12 +1077,14 @@ function ChatWidget({ className, createTransport, defaultVisitor, getSession, in
|
|
|
1005
1077
|
refreshSession: async (current) => {
|
|
1006
1078
|
const identity = identityRef.current;
|
|
1007
1079
|
if (!identity) throw new Error("No visitor to reconnect as.");
|
|
1080
|
+
const generation = generationRef.current;
|
|
1008
1081
|
const next = await getSession({
|
|
1009
1082
|
visitor: identity,
|
|
1010
1083
|
visitorToken: current.visitorToken,
|
|
1011
1084
|
widgetId
|
|
1012
1085
|
});
|
|
1013
|
-
|
|
1086
|
+
if (generation !== generationRef.current) return;
|
|
1087
|
+
writeVisitorToken(storage, widgetId, identity.email, next.visitorToken);
|
|
1014
1088
|
setSupportState(next.support?.state ?? null);
|
|
1015
1089
|
setSession(next);
|
|
1016
1090
|
},
|
|
@@ -1035,31 +1109,53 @@ function ChatWidget({ className, createTransport, defaultVisitor, getSession, in
|
|
|
1035
1109
|
isReady,
|
|
1036
1110
|
messages
|
|
1037
1111
|
]);
|
|
1038
|
-
const startSession = (0, react.useCallback)(async (identity) => {
|
|
1112
|
+
const startSession = (0, react.useCallback)(async (identity, source = "form") => {
|
|
1039
1113
|
setError(null);
|
|
1040
1114
|
setIsConnecting(true);
|
|
1115
|
+
const generation = generationRef.current;
|
|
1116
|
+
const isStale = () => generation !== generationRef.current || source === "host" && normalizeVisitorEmail(identity.email) !== latestHostIdentityRef.current;
|
|
1041
1117
|
try {
|
|
1042
1118
|
const nextSession = await getSession({
|
|
1043
1119
|
visitor: identity,
|
|
1044
|
-
visitorToken: storage
|
|
1120
|
+
visitorToken: readVisitorToken(storage, widgetId, identity.email),
|
|
1045
1121
|
widgetId
|
|
1046
1122
|
});
|
|
1047
|
-
|
|
1123
|
+
if (isStale()) return;
|
|
1124
|
+
writeVisitorToken(storage, widgetId, identity.email, nextSession.visitorToken);
|
|
1048
1125
|
identityRef.current = identity;
|
|
1126
|
+
sessionSourceRef.current = source;
|
|
1049
1127
|
setSupportState(nextSession.support?.state ?? null);
|
|
1050
1128
|
setSession(nextSession);
|
|
1051
1129
|
setMessages(sortMessages(nextSession.messages.map((message) => createLocalMessage(message))));
|
|
1052
1130
|
} catch (sessionError) {
|
|
1131
|
+
if (isStale()) return;
|
|
1053
1132
|
const resolved = sessionError instanceof Error ? sessionError : /* @__PURE__ */ new Error("Failed to start the conversation.");
|
|
1054
1133
|
setError(resolved.message);
|
|
1055
1134
|
} finally {
|
|
1056
|
-
setIsConnecting(false);
|
|
1135
|
+
if (generation === generationRef.current) setIsConnecting(false);
|
|
1057
1136
|
}
|
|
1058
1137
|
}, [
|
|
1059
1138
|
getSession,
|
|
1060
1139
|
storage,
|
|
1061
1140
|
widgetId
|
|
1062
1141
|
]);
|
|
1142
|
+
useResetOnHostIdentityChange({
|
|
1143
|
+
generationRef,
|
|
1144
|
+
hostIdentity,
|
|
1145
|
+
identityRef,
|
|
1146
|
+
reset: () => {
|
|
1147
|
+
setSession(null);
|
|
1148
|
+
setMessages([]);
|
|
1149
|
+
setSupportState(null);
|
|
1150
|
+
setError(null);
|
|
1151
|
+
setIsConnecting(false);
|
|
1152
|
+
setVisitor({
|
|
1153
|
+
email: defaultVisitor?.email ?? "",
|
|
1154
|
+
name: defaultVisitor?.name ?? ""
|
|
1155
|
+
});
|
|
1156
|
+
},
|
|
1157
|
+
sessionSourceRef
|
|
1158
|
+
});
|
|
1063
1159
|
const submitVisitor = async (event) => {
|
|
1064
1160
|
event.preventDefault();
|
|
1065
1161
|
await startSession(visitor);
|
|
@@ -1070,7 +1166,7 @@ function ChatWidget({ className, createTransport, defaultVisitor, getSession, in
|
|
|
1070
1166
|
startSession({
|
|
1071
1167
|
email: defaultVisitor.email.trim(),
|
|
1072
1168
|
name: defaultVisitor.name.trim()
|
|
1073
|
-
}).catch(() => {});
|
|
1169
|
+
}, "host").catch(() => {});
|
|
1074
1170
|
}, [
|
|
1075
1171
|
defaultVisitor,
|
|
1076
1172
|
error,
|
|
@@ -1299,8 +1395,7 @@ function ChatWidget({ className, createTransport, defaultVisitor, getSession, in
|
|
|
1299
1395
|
]
|
|
1300
1396
|
});
|
|
1301
1397
|
}
|
|
1302
|
-
|
|
1303
|
-
//#region src/transport.ts
|
|
1398
|
+
// src/transport.ts
|
|
1304
1399
|
function isServerEvent(value) {
|
|
1305
1400
|
if (typeof value !== "object" || value === null) return false;
|
|
1306
1401
|
return typeof value.type === "string";
|
|
@@ -1345,8 +1440,7 @@ function createWebSocketTransport({ createSocket = (url, protocols) => new WebSo
|
|
|
1345
1440
|
};
|
|
1346
1441
|
};
|
|
1347
1442
|
}
|
|
1348
|
-
|
|
1349
|
-
//#region src/clivly-transport.ts
|
|
1443
|
+
// src/clivly-transport.ts
|
|
1350
1444
|
/**
|
|
1351
1445
|
* The transport a host app should use against a real Clivly backend.
|
|
1352
1446
|
*
|
|
@@ -1363,8 +1457,7 @@ function createClivlyTransport(options = {}) {
|
|
|
1363
1457
|
}
|
|
1364
1458
|
});
|
|
1365
1459
|
}
|
|
1366
|
-
|
|
1367
|
-
//#region src/layout.ts
|
|
1460
|
+
// src/layout.ts
|
|
1368
1461
|
const PLACEMENTS = [
|
|
1369
1462
|
"bottom-left",
|
|
1370
1463
|
"bottom-right",
|
|
@@ -1383,8 +1476,7 @@ function parsePlacement(value) {
|
|
|
1383
1476
|
function parseLauncher(value) {
|
|
1384
1477
|
return LAUNCHERS.find((launcher) => launcher === value) ?? "auto";
|
|
1385
1478
|
}
|
|
1386
|
-
|
|
1387
|
-
//#region src/session.ts
|
|
1479
|
+
// src/session.ts
|
|
1388
1480
|
function isWidgetSessionPayload(value) {
|
|
1389
1481
|
return typeof value === "object" && value !== null;
|
|
1390
1482
|
}
|
|
@@ -1406,8 +1498,7 @@ function createSessionFetcher({ fetchImpl = fetch, sessionUrl }) {
|
|
|
1406
1498
|
return data;
|
|
1407
1499
|
};
|
|
1408
1500
|
}
|
|
1409
|
-
|
|
1410
|
-
//#region src/browser-mount.tsx
|
|
1501
|
+
// src/browser-mount.tsx
|
|
1411
1502
|
const MOUNTED_DATA_ATTR = "clivlyMounted";
|
|
1412
1503
|
/**
|
|
1413
1504
|
* The identity a script-tag host already knows, read off the tag.
|
|
@@ -1499,13 +1590,11 @@ function autoMountChatWidgetScript$1(script = document.currentScript) {
|
|
|
1499
1590
|
if (!script || scriptAlreadyMounted(script)) return null;
|
|
1500
1591
|
return mountChatWidgetScript$1(script);
|
|
1501
1592
|
}
|
|
1502
|
-
|
|
1503
|
-
//#region src/browser.tsx
|
|
1593
|
+
// src/browser.tsx
|
|
1504
1594
|
const mountChatWidget = mountChatWidget$1;
|
|
1505
1595
|
const mountChatWidgetScript = mountChatWidgetScript$1;
|
|
1506
1596
|
const autoMountChatWidgetScript = autoMountChatWidgetScript$1;
|
|
1507
1597
|
if (typeof document !== "undefined") autoMountChatWidgetScript();
|
|
1508
|
-
//#endregion
|
|
1509
1598
|
exports.autoMountChatWidgetScript = autoMountChatWidgetScript;
|
|
1510
1599
|
exports.mountChatWidget = mountChatWidget;
|
|
1511
1600
|
exports.mountChatWidgetScript = mountChatWidgetScript;
|
package/dist/browser.d.cts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { Root } from "react-dom/client";
|
|
2
2
|
import { CSSProperties } from "react";
|
|
3
|
-
//#region src/layout.d.ts
|
|
4
3
|
/**
|
|
5
4
|
* Where the widget sits on the host page, and what its launcher looks like.
|
|
6
5
|
*
|
|
@@ -20,8 +19,6 @@ type ChatWidgetPlacement = "bottom-left" | "bottom-right" | "inline";
|
|
|
20
19
|
* The label stays the button's accessible name in every variant.
|
|
21
20
|
*/
|
|
22
21
|
type ChatWidgetLauncher = "auto" | "icon" | "pill";
|
|
23
|
-
//#endregion
|
|
24
|
-
//#region src/theme.d.ts
|
|
25
22
|
/**
|
|
26
23
|
* Theme resolution for the widget.
|
|
27
24
|
*
|
|
@@ -32,8 +29,6 @@ type ChatWidgetLauncher = "auto" | "icon" | "pill";
|
|
|
32
29
|
* does, and why it is a plain DOM module rather than a stylesheet.
|
|
33
30
|
*/
|
|
34
31
|
type ChatWidgetTheme = "auto" | "dark" | "light";
|
|
35
|
-
//#endregion
|
|
36
|
-
//#region src/types.d.ts
|
|
37
32
|
type WidgetSenderType = "visitor" | "operator" | "system";
|
|
38
33
|
interface WidgetConfig {
|
|
39
34
|
brandColor?: string | null;
|
|
@@ -145,8 +140,6 @@ interface WidgetErrorEvent {
|
|
|
145
140
|
type: "error";
|
|
146
141
|
}
|
|
147
142
|
type WidgetServerEvent = WidgetAssistantStatusEvent | WidgetConversationSnapshotEvent | WidgetErrorEvent | WidgetMessageAckEvent | WidgetMessageCreatedEvent | WidgetSessionReadyEvent | WidgetSupportStateEvent;
|
|
148
|
-
//#endregion
|
|
149
|
-
//#region src/transport.d.ts
|
|
150
143
|
interface ChatTransportHandlers {
|
|
151
144
|
onClose: () => void;
|
|
152
145
|
onError: (error: Error) => void;
|
|
@@ -173,8 +166,21 @@ interface CreateChatTransportOptions {
|
|
|
173
166
|
session: WidgetSessionPayload;
|
|
174
167
|
}
|
|
175
168
|
type CreateChatTransport = (options: CreateChatTransportOptions) => ChatTransportConnection;
|
|
176
|
-
|
|
177
|
-
|
|
169
|
+
/**
|
|
170
|
+
* Where the widget keeps the visitor token that resumes a conversation.
|
|
171
|
+
*
|
|
172
|
+
* Keyed by widget AND by who the token was issued to. One key per widget used
|
|
173
|
+
* to hand the token to whoever opened the widget next on that browser — after
|
|
174
|
+
* a sign-out, or between two people who don't sign in at all — and the server
|
|
175
|
+
* resumed the previous person's thread for them (chat widget session review
|
|
176
|
+
* 2026-10-28, S1). The server now refuses that resume on its own; this keeps
|
|
177
|
+
* each person's token to themselves so a returning visitor still resumes.
|
|
178
|
+
*
|
|
179
|
+
* The identity part is a short hash of the normalised email, so the address
|
|
180
|
+
* itself isn't written into storage keys. It only partitions storage: the
|
|
181
|
+
* server's identity check is the security boundary, not this.
|
|
182
|
+
*/
|
|
183
|
+
type VisitorTokenStorage = Pick<Storage, "getItem" | "setItem"> & Partial<Pick<Storage, "removeItem">>;
|
|
178
184
|
interface ChatWidgetProps {
|
|
179
185
|
className?: string;
|
|
180
186
|
createTransport?: CreateChatTransport;
|
|
@@ -209,7 +215,12 @@ interface ChatWidgetProps {
|
|
|
209
215
|
* `style` to sit beneath the host's own dialogs.
|
|
210
216
|
*/
|
|
211
217
|
placement?: ChatWidgetPlacement;
|
|
212
|
-
|
|
218
|
+
/**
|
|
219
|
+
* Where the visitor token is kept, per widget and per visitor (see
|
|
220
|
+
* visitor-token-storage.ts). `removeItem` is optional; with it, the
|
|
221
|
+
* pre-0.16 shared token is retired after its first use.
|
|
222
|
+
*/
|
|
223
|
+
storage?: VisitorTokenStorage;
|
|
213
224
|
/**
|
|
214
225
|
* Inline styles on the widget root. The intended use is overriding palette
|
|
215
226
|
* tokens — `{ "--clivly-chat-panel": "#0b0f19" }` — which works because
|
|
@@ -224,8 +235,6 @@ interface ChatWidgetProps {
|
|
|
224
235
|
theme?: ChatWidgetTheme;
|
|
225
236
|
widgetId: string;
|
|
226
237
|
}
|
|
227
|
-
//#endregion
|
|
228
|
-
//#region src/browser-mount.d.ts
|
|
229
238
|
interface MountChatWidgetOptions$1 extends Omit<ChatWidgetProps, "getSession" | "widgetId"> {
|
|
230
239
|
container: Element | ShadowRoot;
|
|
231
240
|
sessionUrl: string;
|
|
@@ -250,12 +259,10 @@ interface ScriptMountResult$1 extends ChatWidgetMount$1 {
|
|
|
250
259
|
}
|
|
251
260
|
declare function mountChatWidgetScript$1(script?: HTMLScriptElement): ScriptMountResult$1;
|
|
252
261
|
declare function autoMountChatWidgetScript$1(script?: HTMLScriptElement | null): ScriptMountResult$1 | null;
|
|
253
|
-
//#endregion
|
|
254
|
-
//#region src/browser.d.ts
|
|
255
262
|
export type ChatWidgetMount = ChatWidgetMount$1;
|
|
256
263
|
export type MountChatWidgetOptions = MountChatWidgetOptions$1;
|
|
257
264
|
export type ScriptMountResult = ScriptMountResult$1;
|
|
258
265
|
export declare const mountChatWidget: typeof mountChatWidget$1;
|
|
259
266
|
export declare const mountChatWidgetScript: typeof mountChatWidgetScript$1;
|
|
260
267
|
export declare const autoMountChatWidgetScript: typeof autoMountChatWidgetScript$1;
|
|
261
|
-
|
|
268
|
+
export {};
|
package/dist/browser.d.mts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { Root } from "react-dom/client";
|
|
2
2
|
import { CSSProperties } from "react";
|
|
3
|
-
//#region src/layout.d.ts
|
|
4
3
|
/**
|
|
5
4
|
* Where the widget sits on the host page, and what its launcher looks like.
|
|
6
5
|
*
|
|
@@ -20,8 +19,6 @@ type ChatWidgetPlacement = "bottom-left" | "bottom-right" | "inline";
|
|
|
20
19
|
* The label stays the button's accessible name in every variant.
|
|
21
20
|
*/
|
|
22
21
|
type ChatWidgetLauncher = "auto" | "icon" | "pill";
|
|
23
|
-
//#endregion
|
|
24
|
-
//#region src/theme.d.ts
|
|
25
22
|
/**
|
|
26
23
|
* Theme resolution for the widget.
|
|
27
24
|
*
|
|
@@ -32,8 +29,6 @@ type ChatWidgetLauncher = "auto" | "icon" | "pill";
|
|
|
32
29
|
* does, and why it is a plain DOM module rather than a stylesheet.
|
|
33
30
|
*/
|
|
34
31
|
type ChatWidgetTheme = "auto" | "dark" | "light";
|
|
35
|
-
//#endregion
|
|
36
|
-
//#region src/types.d.ts
|
|
37
32
|
type WidgetSenderType = "visitor" | "operator" | "system";
|
|
38
33
|
interface WidgetConfig {
|
|
39
34
|
brandColor?: string | null;
|
|
@@ -145,8 +140,6 @@ interface WidgetErrorEvent {
|
|
|
145
140
|
type: "error";
|
|
146
141
|
}
|
|
147
142
|
type WidgetServerEvent = WidgetAssistantStatusEvent | WidgetConversationSnapshotEvent | WidgetErrorEvent | WidgetMessageAckEvent | WidgetMessageCreatedEvent | WidgetSessionReadyEvent | WidgetSupportStateEvent;
|
|
148
|
-
//#endregion
|
|
149
|
-
//#region src/transport.d.ts
|
|
150
143
|
interface ChatTransportHandlers {
|
|
151
144
|
onClose: () => void;
|
|
152
145
|
onError: (error: Error) => void;
|
|
@@ -173,8 +166,21 @@ interface CreateChatTransportOptions {
|
|
|
173
166
|
session: WidgetSessionPayload;
|
|
174
167
|
}
|
|
175
168
|
type CreateChatTransport = (options: CreateChatTransportOptions) => ChatTransportConnection;
|
|
176
|
-
|
|
177
|
-
|
|
169
|
+
/**
|
|
170
|
+
* Where the widget keeps the visitor token that resumes a conversation.
|
|
171
|
+
*
|
|
172
|
+
* Keyed by widget AND by who the token was issued to. One key per widget used
|
|
173
|
+
* to hand the token to whoever opened the widget next on that browser — after
|
|
174
|
+
* a sign-out, or between two people who don't sign in at all — and the server
|
|
175
|
+
* resumed the previous person's thread for them (chat widget session review
|
|
176
|
+
* 2026-10-28, S1). The server now refuses that resume on its own; this keeps
|
|
177
|
+
* each person's token to themselves so a returning visitor still resumes.
|
|
178
|
+
*
|
|
179
|
+
* The identity part is a short hash of the normalised email, so the address
|
|
180
|
+
* itself isn't written into storage keys. It only partitions storage: the
|
|
181
|
+
* server's identity check is the security boundary, not this.
|
|
182
|
+
*/
|
|
183
|
+
type VisitorTokenStorage = Pick<Storage, "getItem" | "setItem"> & Partial<Pick<Storage, "removeItem">>;
|
|
178
184
|
interface ChatWidgetProps {
|
|
179
185
|
className?: string;
|
|
180
186
|
createTransport?: CreateChatTransport;
|
|
@@ -209,7 +215,12 @@ interface ChatWidgetProps {
|
|
|
209
215
|
* `style` to sit beneath the host's own dialogs.
|
|
210
216
|
*/
|
|
211
217
|
placement?: ChatWidgetPlacement;
|
|
212
|
-
|
|
218
|
+
/**
|
|
219
|
+
* Where the visitor token is kept, per widget and per visitor (see
|
|
220
|
+
* visitor-token-storage.ts). `removeItem` is optional; with it, the
|
|
221
|
+
* pre-0.16 shared token is retired after its first use.
|
|
222
|
+
*/
|
|
223
|
+
storage?: VisitorTokenStorage;
|
|
213
224
|
/**
|
|
214
225
|
* Inline styles on the widget root. The intended use is overriding palette
|
|
215
226
|
* tokens — `{ "--clivly-chat-panel": "#0b0f19" }` — which works because
|
|
@@ -224,8 +235,6 @@ interface ChatWidgetProps {
|
|
|
224
235
|
theme?: ChatWidgetTheme;
|
|
225
236
|
widgetId: string;
|
|
226
237
|
}
|
|
227
|
-
//#endregion
|
|
228
|
-
//#region src/browser-mount.d.ts
|
|
229
238
|
interface MountChatWidgetOptions$1 extends Omit<ChatWidgetProps, "getSession" | "widgetId"> {
|
|
230
239
|
container: Element | ShadowRoot;
|
|
231
240
|
sessionUrl: string;
|
|
@@ -250,12 +259,10 @@ interface ScriptMountResult$1 extends ChatWidgetMount$1 {
|
|
|
250
259
|
}
|
|
251
260
|
declare function mountChatWidgetScript$1(script?: HTMLScriptElement): ScriptMountResult$1;
|
|
252
261
|
declare function autoMountChatWidgetScript$1(script?: HTMLScriptElement | null): ScriptMountResult$1 | null;
|
|
253
|
-
//#endregion
|
|
254
|
-
//#region src/browser.d.ts
|
|
255
262
|
export type ChatWidgetMount = ChatWidgetMount$1;
|
|
256
263
|
export type MountChatWidgetOptions = MountChatWidgetOptions$1;
|
|
257
264
|
export type ScriptMountResult = ScriptMountResult$1;
|
|
258
265
|
export declare const mountChatWidget: typeof mountChatWidget$1;
|
|
259
266
|
export declare const mountChatWidgetScript: typeof mountChatWidgetScript$1;
|
|
260
267
|
export declare const autoMountChatWidgetScript: typeof autoMountChatWidgetScript$1;
|
|
261
|
-
|
|
268
|
+
export {};
|