@eshal-bot/chat-widget 0.2.19 → 0.2.20

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.
@@ -1 +1 @@
1
- export{e as default}from"./chat-widget-index-ClEAcv15.esm.js";import"react";import"react-dom/client";import"react/jsx-runtime";import"react-dom";
1
+ export{e as default}from"./chat-widget-index-CjJ4shXp.esm.js";import"react";import"react-dom/client";import"react/jsx-runtime";import"react-dom";
@@ -7955,6 +7955,22 @@
7955
7955
  }
7956
7956
  }
7957
7957
 
7958
+ /** HTTP status the backend answers for an erased conversation. */
7959
+ const CONVERSATION_ERASED_STATUS = 410;
7960
+
7961
+ /**
7962
+ * Thrown when the conversation this widget holds was erased in the console
7963
+ * ("Erase permanently", on the conversation or its contact). The backend
7964
+ * answers every widget call on it with 410 `conversation_erased`; the caller
7965
+ * starts a new conversation instead of treating it as a failed send.
7966
+ */
7967
+ class ConversationErasedError extends Error {
7968
+ constructor(message = 'This conversation was erased') {
7969
+ super(message);
7970
+ this.name = 'ConversationErasedError';
7971
+ }
7972
+ }
7973
+
7958
7974
  // ============================================================================
7959
7975
  // NETWORK
7960
7976
  // ============================================================================
@@ -7991,6 +8007,9 @@
7991
8007
  signal
7992
8008
  } : {})
7993
8009
  });
8010
+ if (response.status === CONVERSATION_ERASED_STATUS) {
8011
+ throw new ConversationErasedError();
8012
+ }
7994
8013
  if (!response.ok) {
7995
8014
  throw new Error(`HTTP error! status: ${response.status}`);
7996
8015
  }
@@ -12242,6 +12261,16 @@
12242
12261
  return generateWidgetSessionId();
12243
12262
  });
12244
12263
 
12264
+ // Erase permanently (console): the conversation this widget holds was
12265
+ // erased, and every widget call on it now answers 410 conversation_erased.
12266
+ // Set below once resetConversation exists; the history fetch, the live
12267
+ // stream, the hand-off and sendMessage all call it to start a new
12268
+ // conversation instead of showing or reviving the erased one.
12269
+ const conversationErasedRef = reactExports.useRef(() => {});
12270
+ // The visitor's message whose send was refused because the conversation
12271
+ // was erased. Re-sent on the new conversation once it exists.
12272
+ const pendingResendRef = reactExports.useRef(null);
12273
+
12245
12274
  // Handoff EventSource - receives human agent messages in real-time
12246
12275
  const handoffEventSourceRef = reactExports.useRef(null);
12247
12276
  const HANDOFF_TRIGGER = "Connect with a human representative";
@@ -12413,6 +12442,10 @@
12413
12442
  headers,
12414
12443
  credentials: 'include'
12415
12444
  });
12445
+ if (res.status === CONVERSATION_ERASED_STATUS) {
12446
+ conversationErasedRef.current(null);
12447
+ return;
12448
+ }
12416
12449
  if (!res.ok) return;
12417
12450
  const data = await res.json();
12418
12451
  const serverMsgs = Array.isArray(data?.messages) ? data.messages : [];
@@ -12449,6 +12482,13 @@
12449
12482
  es.onmessage = event => {
12450
12483
  try {
12451
12484
  const data = JSON.parse(event.data);
12485
+ // Erased in the console while open: stop listening and start over.
12486
+ if (data.type === 'conversation_erased') {
12487
+ es.close();
12488
+ if (handoffEventSourceRef.current === es) handoffEventSourceRef.current = null;
12489
+ conversationErasedRef.current(null);
12490
+ return;
12491
+ }
12452
12492
  if (data.type === 'message_created' && data.message) {
12453
12493
  // Advance the backfill watermark so a later reconnect doesn't
12454
12494
  // re-request everything we have already rendered live.
@@ -12729,7 +12769,13 @@
12729
12769
  headers,
12730
12770
  credentials: 'include'
12731
12771
  });
12732
- if (!res.ok || cancelled) return;
12772
+ if (cancelled) return;
12773
+ // Erased in the console: the saved transcript must not be shown.
12774
+ if (res.status === CONVERSATION_ERASED_STATUS) {
12775
+ conversationErasedRef.current(null);
12776
+ return;
12777
+ }
12778
+ if (!res.ok) return;
12733
12779
  const data = await res.json();
12734
12780
  const serverMsgs = Array.isArray(data?.messages) ? data.messages : [];
12735
12781
  const agentMsgs = serverMsgs.filter(isRepliedOutsideStream);
@@ -12942,6 +12988,10 @@
12942
12988
  body: JSON.stringify({
12943
12989
  reason: 'User clicked handoff button'
12944
12990
  })
12991
+ }).then(res => {
12992
+ // Erased: nobody to hand off to. Start a new conversation,
12993
+ // where the visitor can ask for a person again.
12994
+ if (res.status === CONVERSATION_ERASED_STATUS) conversationErasedRef.current(null);
12945
12995
  }).catch(() => {});
12946
12996
  }
12947
12997
  };
@@ -13184,6 +13234,8 @@
13184
13234
  if (done) break;
13185
13235
  }
13186
13236
  } catch (err) {
13237
+ // Erased: handled once, below, like any other send.
13238
+ if (err instanceof ConversationErasedError) throw err;
13187
13239
  // L4: this catch used to swallow the failure - but in a handed-off
13188
13240
  // conversation this POST is the ONLY path to the human agent, so a
13189
13241
  // dropped send meant the customer's message never reached anyone
@@ -13265,6 +13317,21 @@
13265
13317
  success: true
13266
13318
  };
13267
13319
  } catch (error) {
13320
+ // Erased in the console: not a failed send. Start a new conversation
13321
+ // and send this message again there, with no error bubble.
13322
+ if (error instanceof ConversationErasedError) {
13323
+ const isChat = apiType === 'inquiry' || apiType === 'complaint' || apiType === 'technical';
13324
+ conversationErasedRef.current(isChat ? {
13325
+ text: trimmed,
13326
+ attachments: attachmentsPayload,
13327
+ sessionId: bidiSessionId
13328
+ } : null);
13329
+ return {
13330
+ success: false,
13331
+ error: 'conversation_erased',
13332
+ erased: true
13333
+ };
13334
+ }
13268
13335
  console.error("Error sending message:", error);
13269
13336
  if (apiType === 'inquiry' || apiType === 'complaint' || apiType === 'technical') {
13270
13337
  setIsLoading(false);
@@ -13317,6 +13384,16 @@
13317
13384
  // bubble first, then routes back through sendMessage so the retry gets the
13318
13385
  // full pipeline (concurrency guard, stream retries, delivery state) instead
13319
13386
  // of a bespoke resend path.
13387
+ // A send refused because the conversation was erased goes out again on the
13388
+ // new conversation, once the reset has rotated the session id.
13389
+ reactExports.useEffect(() => {
13390
+ const pending = pendingResendRef.current;
13391
+ if (!pending || pending.sessionId === bidiSessionId) return;
13392
+ pendingResendRef.current = null;
13393
+ void sendMessage(pending.text, undefined, {
13394
+ attachments: pending.attachments
13395
+ });
13396
+ }, [bidiSessionId, sendMessage]);
13320
13397
  const retryMessage = reactExports.useCallback(failedMessage => {
13321
13398
  if (!failedMessage || failedMessage.status !== 'failed') {
13322
13399
  return Promise.resolve({
@@ -14688,6 +14765,22 @@
14688
14765
  resetConversationRef.current = resetConversation;
14689
14766
  }, [resetConversation]);
14690
14767
 
14768
+ // The conversation was erased in the console: drop the saved transcript and
14769
+ // start a new conversation, the same reset as after CSAT. Idempotent per
14770
+ // conversation id, since the history fetch, the stream and a send can all
14771
+ // learn it at once. `resend` is the visitor's refused message, sent again
14772
+ // on the new conversation unless onboarding has to run first.
14773
+ const erasedHandledForRef = reactExports.useRef(null);
14774
+ reactExports.useEffect(() => {
14775
+ conversationErasedRef.current = resend => {
14776
+ if (erasedHandledForRef.current === bidiSessionId) return;
14777
+ erasedHandledForRef.current = bidiSessionId;
14778
+ const needsOnboarding = onboardingEnabled && getOnboardingQuestions(onboardingQuestions).length > 0;
14779
+ pendingResendRef.current = resend && !needsOnboarding ? resend : null;
14780
+ resetConversationRef.current();
14781
+ };
14782
+ }, [bidiSessionId, onboardingEnabled, onboardingQuestions]);
14783
+
14691
14784
  // Precise inactivity timeout: schedules a setTimeout for exact remaining time,
14692
14785
  // then reschedules itself after each check (handles activity updates in localStorage).
14693
14786
  //
@@ -21773,7 +21866,7 @@
21773
21866
  * value by its own test (that constant sat at 0.1.113 while this package was
21774
21867
  * at 0.2.2 precisely because nothing enforced it - EV-2431).
21775
21868
  */
21776
- const WIDGET_VERSION = '0.2.19';
21869
+ const WIDGET_VERSION = '0.2.20';
21777
21870
 
21778
21871
  /**
21779
21872
  * API utility functions