keystone-design-bootstrap 1.0.104 → 1.0.105
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +22 -1
- package/dist/index.js +46 -8
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/design_system/components/ChatWidget.tsx +66 -10
- package/src/lib/chat-backend-constants.ts +8 -0
- package/src/lib/chat-backend.ts +37 -0
- package/src/next/layouts/root-layout.tsx +14 -0
- package/src/next/routes/chat.ts +215 -18
package/dist/index.d.ts
CHANGED
|
@@ -42,6 +42,15 @@ interface ResolvedCtaUrls {
|
|
|
42
42
|
}
|
|
43
43
|
declare function resolveCtaUrls(companyInformation?: CompanyInformation | null): ResolvedCtaUrls;
|
|
44
44
|
|
|
45
|
+
/**
|
|
46
|
+
* Chat backend identifiers — isomorphic (safe to import from client
|
|
47
|
+
* components; no server-only dependencies). Backend *resolution* lives in
|
|
48
|
+
* ./chat-backend (server-only: reads env, logs via server-log).
|
|
49
|
+
*/
|
|
50
|
+
declare const RAILS_BACKEND: "rails";
|
|
51
|
+
declare const SOR_BACKEND: "sor";
|
|
52
|
+
type ChatBackend = typeof RAILS_BACKEND | typeof SOR_BACKEND;
|
|
53
|
+
|
|
45
54
|
interface TeamMember {
|
|
46
55
|
id: number;
|
|
47
56
|
name: string;
|
|
@@ -50,6 +59,18 @@ interface TeamMember {
|
|
|
50
59
|
}
|
|
51
60
|
interface ChatWidgetProps {
|
|
52
61
|
position?: 'bottom-right' | 'bottom-left';
|
|
62
|
+
/**
|
|
63
|
+
* Which backend the site's `/api/chat` proxy talks to. The two backends have
|
|
64
|
+
* architecturally different reply delivery: 'rails' (keystone-mono-proto)
|
|
65
|
+
* pushes replies over ActionCable realtime; 'sor' (sor-service) has no
|
|
66
|
+
* realtime channel and the widget polls for the reply instead. The paths are
|
|
67
|
+
* mutually exclusive — a 'sor' widget never probes/subscribes realtime, a
|
|
68
|
+
* 'rails' widget never polls.
|
|
69
|
+
*
|
|
70
|
+
* TODO(webchat): default is 'rails' while keystone-mono-proto is still a live
|
|
71
|
+
* backend; flip the default to 'sor' once the Rails backend is scrapped.
|
|
72
|
+
*/
|
|
73
|
+
chatBackend?: ChatBackend;
|
|
53
74
|
primaryColor?: string;
|
|
54
75
|
/**
|
|
55
76
|
* Anonymous session identifier. Used when the visitor is not authenticated.
|
|
@@ -65,6 +86,6 @@ interface ChatWidgetProps {
|
|
|
65
86
|
displayName?: string;
|
|
66
87
|
teamMembers?: TeamMember[];
|
|
67
88
|
}
|
|
68
|
-
declare function ChatWidget({ position, primaryColor, sessionId: providedSessionId, contactId, displayName: providedDisplayName, teamMembers }: ChatWidgetProps): React__default.JSX.Element;
|
|
89
|
+
declare function ChatWidget({ position, primaryColor, chatBackend, sessionId: providedSessionId, contactId, displayName: providedDisplayName, teamMembers }: ChatWidgetProps): React__default.JSX.Element;
|
|
69
90
|
|
|
70
91
|
export { ChatWidget, type ResolvedCtaUrls, isExternalCtaUrl, resolveCtaUrls, submitContactFormAction, submitLeadFormAction };
|
package/dist/index.js
CHANGED
|
@@ -20175,6 +20175,10 @@ function useRealtimeReplyOrchestrator({
|
|
|
20175
20175
|
};
|
|
20176
20176
|
}
|
|
20177
20177
|
|
|
20178
|
+
// src/lib/chat-backend-constants.ts
|
|
20179
|
+
var RAILS_BACKEND = "rails";
|
|
20180
|
+
var SOR_BACKEND = "sor";
|
|
20181
|
+
|
|
20178
20182
|
// src/design_system/components/ChatWidget.tsx
|
|
20179
20183
|
var formatTime = (isoString) => {
|
|
20180
20184
|
const date = new Date(isoString);
|
|
@@ -20194,6 +20198,7 @@ var formatTime = (isoString) => {
|
|
|
20194
20198
|
function ChatWidget({
|
|
20195
20199
|
position = "bottom-right",
|
|
20196
20200
|
primaryColor,
|
|
20201
|
+
chatBackend = RAILS_BACKEND,
|
|
20197
20202
|
sessionId: providedSessionId,
|
|
20198
20203
|
contactId,
|
|
20199
20204
|
displayName: providedDisplayName,
|
|
@@ -20250,8 +20255,10 @@ function ChatWidget({
|
|
|
20250
20255
|
const latest = list[list.length - 1];
|
|
20251
20256
|
return (latest == null ? void 0 : latest.sender_type) === "agent" && (latest == null ? void 0 : latest.body) != null && String(latest.body).trim() !== "";
|
|
20252
20257
|
}, []);
|
|
20258
|
+
const isSorBackend = chatBackend === SOR_BACKEND;
|
|
20253
20259
|
const fetchRealtimeData = useCallback12(async () => {
|
|
20254
20260
|
var _a, _b, _c;
|
|
20261
|
+
if (isSorBackend) return null;
|
|
20255
20262
|
if (!contactId && !sessionId) return null;
|
|
20256
20263
|
const query = contactId ? `contact_id=${encodeURIComponent(contactId)}` : `identifier=${encodeURIComponent(sessionId)}`;
|
|
20257
20264
|
const response = await fetch(`/api/chat/?action=realtime_token&${query}`);
|
|
@@ -20262,7 +20269,7 @@ function ChatWidget({
|
|
|
20262
20269
|
const cableUrl = (_c = result == null ? void 0 : result.data) == null ? void 0 : _c.cable_url;
|
|
20263
20270
|
if (!token || !contactIdForStream || !cableUrl) return null;
|
|
20264
20271
|
return { token, contact_id: contactIdForStream, cable_url: cableUrl };
|
|
20265
|
-
}, [contactId, sessionId]);
|
|
20272
|
+
}, [contactId, isSorBackend, sessionId]);
|
|
20266
20273
|
const {
|
|
20267
20274
|
beginReplyWait,
|
|
20268
20275
|
ensureRealtimeSubscription
|
|
@@ -20280,10 +20287,39 @@ function ChatWidget({
|
|
|
20280
20287
|
});
|
|
20281
20288
|
const hasPersistedMessages = messages.some((message) => !String(message.id).startsWith("temp_"));
|
|
20282
20289
|
useEffect14(() => {
|
|
20290
|
+
if (isSorBackend) return;
|
|
20283
20291
|
if (!isOpen || !contactId && !sessionId) return;
|
|
20284
20292
|
if (!contactId && !hasPersistedMessages) return;
|
|
20285
20293
|
ensureRealtimeSubscription();
|
|
20286
|
-
}, [contactId, ensureRealtimeSubscription, hasPersistedMessages, isOpen, sessionId]);
|
|
20294
|
+
}, [contactId, ensureRealtimeSubscription, hasPersistedMessages, isOpen, isSorBackend, sessionId]);
|
|
20295
|
+
useEffect14(() => {
|
|
20296
|
+
if (!isSorBackend || !waitingForReply) return;
|
|
20297
|
+
let cancelled = false;
|
|
20298
|
+
let inFlight = false;
|
|
20299
|
+
let attempts = 0;
|
|
20300
|
+
const maxAttempts = 40;
|
|
20301
|
+
const interval = setInterval(async () => {
|
|
20302
|
+
if (inFlight) return;
|
|
20303
|
+
inFlight = true;
|
|
20304
|
+
attempts += 1;
|
|
20305
|
+
try {
|
|
20306
|
+
const msgs = await loadMessages();
|
|
20307
|
+
if (cancelled) return;
|
|
20308
|
+
if (hasAgentReplyWithBody(msgs) || attempts >= maxAttempts) {
|
|
20309
|
+
clearInterval(interval);
|
|
20310
|
+
setWaitingForReply(false);
|
|
20311
|
+
setIsLoading(false);
|
|
20312
|
+
}
|
|
20313
|
+
} catch (e) {
|
|
20314
|
+
} finally {
|
|
20315
|
+
inFlight = false;
|
|
20316
|
+
}
|
|
20317
|
+
}, 1500);
|
|
20318
|
+
return () => {
|
|
20319
|
+
cancelled = true;
|
|
20320
|
+
clearInterval(interval);
|
|
20321
|
+
};
|
|
20322
|
+
}, [isSorBackend, waitingForReply, loadMessages, hasAgentReplyWithBody]);
|
|
20287
20323
|
const sendMessage = async () => {
|
|
20288
20324
|
var _a, _b, _c, _d, _e, _f;
|
|
20289
20325
|
if (!inputValue.trim() || !contactId && !sessionId) return;
|
|
@@ -20311,12 +20347,14 @@ function ChatWidget({
|
|
|
20311
20347
|
captureEvent("chat_message_sent", { is_authenticated: Boolean(contactId) });
|
|
20312
20348
|
if ((_a = result.data) == null ? void 0 : _a.job_id) {
|
|
20313
20349
|
setWaitingForReply(true);
|
|
20314
|
-
|
|
20315
|
-
|
|
20316
|
-
|
|
20317
|
-
|
|
20318
|
-
|
|
20319
|
-
|
|
20350
|
+
if (!isSorBackend) {
|
|
20351
|
+
const realtimeFromSend = ((_b = result.data) == null ? void 0 : _b.realtime_token) && ((_c = result.data) == null ? void 0 : _c.contact_id) && ((_d = result.data) == null ? void 0 : _d.cable_url) ? {
|
|
20352
|
+
token: result.data.realtime_token,
|
|
20353
|
+
contact_id: result.data.contact_id,
|
|
20354
|
+
cable_url: result.data.cable_url
|
|
20355
|
+
} : null;
|
|
20356
|
+
beginReplyWait({ realtimeData: realtimeFromSend });
|
|
20357
|
+
}
|
|
20320
20358
|
} else if (((_e = result.data) == null ? void 0 : _e.status) === "agent_unavailable" || ((_f = result.data) == null ? void 0 : _f.status) === "no_auto_reply") {
|
|
20321
20359
|
setIsLoading(false);
|
|
20322
20360
|
setWaitingForReply(false);
|