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 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
- 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) ? {
20315
- token: result.data.realtime_token,
20316
- contact_id: result.data.contact_id,
20317
- cable_url: result.data.cable_url
20318
- } : null;
20319
- beginReplyWait({ realtimeData: realtimeFromSend });
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);