@customerhero/js 2.1.1 → 2.2.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/index.cjs CHANGED
@@ -75,6 +75,8 @@ var en = {
75
75
  attach_menu_open: "Add attachment",
76
76
  attach_photo: "Choose file",
77
77
  drop_files_here: "Drop files here",
78
+ incident_dismiss: "Dismiss",
79
+ incident_default_link_label: "Learn more",
78
80
  attachment_unsupported_type: "Unsupported file type"
79
81
  };
80
82
 
@@ -104,6 +106,8 @@ var es = {
104
106
  attach_menu_open: "A\xF1adir adjunto",
105
107
  attach_photo: "Elegir archivo",
106
108
  drop_files_here: "Suelta los archivos aqu\xED",
109
+ incident_dismiss: "Descartar",
110
+ incident_default_link_label: "M\xE1s informaci\xF3n",
107
111
  attachment_unsupported_type: "Tipo de archivo no admitido"
108
112
  };
109
113
 
@@ -133,6 +137,8 @@ var ptBR = {
133
137
  attach_menu_open: "Adicionar anexo",
134
138
  attach_photo: "Escolher arquivo",
135
139
  drop_files_here: "Solte os arquivos aqui",
140
+ incident_dismiss: "Dispensar",
141
+ incident_default_link_label: "Saiba mais",
136
142
  attachment_unsupported_type: "Tipo de arquivo n\xE3o suportado"
137
143
  };
138
144
 
@@ -162,6 +168,8 @@ var ptPT = {
162
168
  attach_menu_open: "Adicionar anexo",
163
169
  attach_photo: "Escolher ficheiro",
164
170
  drop_files_here: "Largue os ficheiros aqui",
171
+ incident_dismiss: "Dispensar",
172
+ incident_default_link_label: "Saiba mais",
165
173
  attachment_unsupported_type: "Tipo de ficheiro n\xE3o suportado"
166
174
  };
167
175
 
@@ -191,6 +199,8 @@ var fr = {
191
199
  attach_menu_open: "Ajouter une pi\xE8ce jointe",
192
200
  attach_photo: "Choisir un fichier",
193
201
  drop_files_here: "D\xE9posez les fichiers ici",
202
+ incident_dismiss: "Ignorer",
203
+ incident_default_link_label: "En savoir plus",
194
204
  attachment_unsupported_type: "Type de fichier non pris en charge"
195
205
  };
196
206
 
@@ -220,6 +230,8 @@ var de = {
220
230
  attach_menu_open: "Anhang hinzuf\xFCgen",
221
231
  attach_photo: "Datei ausw\xE4hlen",
222
232
  drop_files_here: "Dateien hier ablegen",
233
+ incident_dismiss: "Schlie\xDFen",
234
+ incident_default_link_label: "Mehr erfahren",
223
235
  attachment_unsupported_type: "Dateityp nicht unterst\xFCtzt"
224
236
  };
225
237
 
@@ -249,6 +261,8 @@ var it = {
249
261
  attach_menu_open: "Aggiungi allegato",
250
262
  attach_photo: "Scegli file",
251
263
  drop_files_here: "Trascina qui i file",
264
+ incident_dismiss: "Chiudi",
265
+ incident_default_link_label: "Scopri di pi\xF9",
252
266
  attachment_unsupported_type: "Tipo di file non supportato"
253
267
  };
254
268
 
@@ -278,6 +292,8 @@ var nl = {
278
292
  attach_menu_open: "Bijlage toevoegen",
279
293
  attach_photo: "Bestand kiezen",
280
294
  drop_files_here: "Sleep bestanden hier",
295
+ incident_dismiss: "Sluiten",
296
+ incident_default_link_label: "Meer informatie",
281
297
  attachment_unsupported_type: "Bestandstype niet ondersteund"
282
298
  };
283
299
 
@@ -307,6 +323,8 @@ var pl = {
307
323
  attach_menu_open: "Dodaj za\u0142\u0105cznik",
308
324
  attach_photo: "Wybierz plik",
309
325
  drop_files_here: "Upu\u015B\u0107 pliki tutaj",
326
+ incident_dismiss: "Zamknij",
327
+ incident_default_link_label: "Dowiedz si\u0119 wi\u0119cej",
310
328
  attachment_unsupported_type: "Nieobs\u0142ugiwany typ pliku"
311
329
  };
312
330
 
@@ -336,6 +354,8 @@ var tr = {
336
354
  attach_menu_open: "Ek ekle",
337
355
  attach_photo: "Dosya se\xE7",
338
356
  drop_files_here: "Dosyalar\u0131 buraya b\u0131rak",
357
+ incident_dismiss: "Kapat",
358
+ incident_default_link_label: "Daha fazla bilgi",
339
359
  attachment_unsupported_type: "Desteklenmeyen dosya t\xFCr\xFC"
340
360
  };
341
361
 
@@ -365,6 +385,8 @@ var ar = {
365
385
  attach_menu_open: "\u0625\u0636\u0627\u0641\u0629 \u0645\u0631\u0641\u0642",
366
386
  attach_photo: "\u0627\u062E\u062A\u0631 \u0645\u0644\u0641\u064B\u0627",
367
387
  drop_files_here: "\u0623\u0641\u0644\u062A \u0627\u0644\u0645\u0644\u0641\u0627\u062A \u0647\u0646\u0627",
388
+ incident_dismiss: "\u0625\u063A\u0644\u0627\u0642",
389
+ incident_default_link_label: "\u0627\u0639\u0631\u0641 \u0627\u0644\u0645\u0632\u064A\u062F",
368
390
  attachment_unsupported_type: "\u0646\u0648\u0639 \u0627\u0644\u0645\u0644\u0641 \u063A\u064A\u0631 \u0645\u062F\u0639\u0648\u0645"
369
391
  };
370
392
 
@@ -394,6 +416,8 @@ var ja = {
394
416
  attach_menu_open: "\u6DFB\u4ED8\u30D5\u30A1\u30A4\u30EB\u3092\u8FFD\u52A0",
395
417
  attach_photo: "\u30D5\u30A1\u30A4\u30EB\u3092\u9078\u629E",
396
418
  drop_files_here: "\u30D5\u30A1\u30A4\u30EB\u3092\u3053\u3053\u306B\u30C9\u30ED\u30C3\u30D7",
419
+ incident_dismiss: "\u9589\u3058\u308B",
420
+ incident_default_link_label: "\u8A73\u7D30\u3092\u898B\u308B",
397
421
  attachment_unsupported_type: "\u30B5\u30DD\u30FC\u30C8\u3055\u308C\u3066\u3044\u306A\u3044\u30D5\u30A1\u30A4\u30EB\u5F62\u5F0F"
398
422
  };
399
423
 
@@ -423,6 +447,8 @@ var ko = {
423
447
  attach_menu_open: "\uCCA8\uBD80 \uD30C\uC77C \uCD94\uAC00",
424
448
  attach_photo: "\uD30C\uC77C \uC120\uD0DD",
425
449
  drop_files_here: "\uD30C\uC77C\uC744 \uC5EC\uAE30\uC5D0 \uB193\uAE30",
450
+ incident_dismiss: "\uB2EB\uAE30",
451
+ incident_default_link_label: "\uC790\uC138\uD788 \uC54C\uC544\uBCF4\uAE30",
426
452
  attachment_unsupported_type: "\uC9C0\uC6D0\uB418\uC9C0 \uC54A\uB294 \uD30C\uC77C \uD615\uC2DD"
427
453
  };
428
454
 
@@ -452,6 +478,8 @@ var zhCN = {
452
478
  attach_menu_open: "\u6DFB\u52A0\u9644\u4EF6",
453
479
  attach_photo: "\u9009\u62E9\u6587\u4EF6",
454
480
  drop_files_here: "\u5C06\u6587\u4EF6\u62D6\u653E\u5230\u6B64\u5904",
481
+ incident_dismiss: "\u5173\u95ED",
482
+ incident_default_link_label: "\u4E86\u89E3\u66F4\u591A",
455
483
  attachment_unsupported_type: "\u4E0D\u652F\u6301\u7684\u6587\u4EF6\u7C7B\u578B"
456
484
  };
457
485
 
@@ -481,6 +509,8 @@ var zhTW = {
481
509
  attach_menu_open: "\u65B0\u589E\u9644\u4EF6",
482
510
  attach_photo: "\u9078\u64C7\u6A94\u6848",
483
511
  drop_files_here: "\u5C07\u6A94\u6848\u62D6\u653E\u5230\u6B64\u8655",
512
+ incident_dismiss: "\u95DC\u9589",
513
+ incident_default_link_label: "\u77AD\u89E3\u66F4\u591A",
484
514
  attachment_unsupported_type: "\u4E0D\u652F\u63F4\u7684\u6A94\u6848\u985E\u578B"
485
515
  };
486
516
 
@@ -1000,9 +1030,50 @@ function resolveConfig(userConfig, fetched) {
1000
1030
  stringOverrides: fetched?.stringOverrides
1001
1031
  };
1002
1032
  }
1033
+ function sanitizeIncidentBanner(input) {
1034
+ if (!input || typeof input !== "object") return null;
1035
+ const raw = input;
1036
+ const sev = raw.severity;
1037
+ if (sev !== "info" && sev !== "warning" && sev !== "outage") return null;
1038
+ if (typeof raw.title !== "string" || raw.title.length === 0) return null;
1039
+ if (typeof raw.expiresAt === "string") {
1040
+ const t = Date.parse(raw.expiresAt);
1041
+ if (!Number.isNaN(t) && t <= Date.now()) return null;
1042
+ }
1043
+ const out = { severity: sev, title: raw.title };
1044
+ if (typeof raw.body === "string") out.body = raw.body;
1045
+ if (typeof raw.eta === "string") out.eta = raw.eta;
1046
+ if (raw.link && typeof raw.link === "object") {
1047
+ const link = raw.link;
1048
+ if (typeof link.url === "string") {
1049
+ out.link = { url: link.url };
1050
+ if (typeof link.label === "string") out.link.label = link.label;
1051
+ }
1052
+ }
1053
+ if (typeof raw.expiresAt === "string") out.expiresAt = raw.expiresAt;
1054
+ return out;
1055
+ }
1056
+ function bannerKey(banner) {
1057
+ if (!banner) return null;
1058
+ return JSON.stringify([
1059
+ banner.severity,
1060
+ banner.title,
1061
+ banner.body ?? "",
1062
+ banner.eta ?? "",
1063
+ banner.link?.url ?? "",
1064
+ banner.link?.label ?? "",
1065
+ banner.expiresAt ?? ""
1066
+ ]);
1067
+ }
1003
1068
  function getStorage() {
1004
1069
  try {
1005
- return typeof window !== "undefined" ? window.localStorage : null;
1070
+ if (typeof window !== "undefined" && window.localStorage)
1071
+ return window.localStorage;
1072
+ if (typeof globalThis !== "undefined") {
1073
+ const ls = globalThis.localStorage;
1074
+ if (ls) return ls;
1075
+ }
1076
+ return null;
1006
1077
  } catch {
1007
1078
  return null;
1008
1079
  }
@@ -1044,7 +1115,9 @@ var CustomerHeroChat = class {
1044
1115
  preChatSubmission: null,
1045
1116
  consent: this.readStoredConsent(),
1046
1117
  pendingTriggerId: null,
1047
- pendingPrefill: null
1118
+ pendingPrefill: null,
1119
+ incidentBanner: null,
1120
+ incidentBannerDismissed: false
1048
1121
  };
1049
1122
  }
1050
1123
  // ── Proactive engagement state ─────────────────────────────────────
@@ -1123,11 +1196,18 @@ var CustomerHeroChat = class {
1123
1196
  const resolved = resolveConfig(this.userConfig, fetched);
1124
1197
  const triggers = Array.isArray(fetched.triggers) ? fetched.triggers : [];
1125
1198
  const preChatForm = fetched.preChatForm ?? null;
1199
+ const incidentBanner = sanitizeIncidentBanner(fetched.incidentBanner);
1200
+ const prevBannerKey = bannerKey(this.state.incidentBanner);
1201
+ const nextBannerKey = bannerKey(incidentBanner);
1202
+ const dismissedFromStorage = nextBannerKey && nextBannerKey === this.readStoredBannerDismissal();
1203
+ const incidentBannerDismissed = nextBannerKey === prevBannerKey ? this.state.incidentBannerDismissed || !!dismissedFromStorage : !!dismissedFromStorage;
1126
1204
  this.setState({
1127
1205
  config: resolved,
1128
1206
  configLoaded: true,
1129
1207
  triggers,
1130
- preChatForm
1208
+ preChatForm,
1209
+ incidentBanner,
1210
+ incidentBannerDismissed
1131
1211
  });
1132
1212
  if (resolved.stringOverrides) this.rebuildTranslator();
1133
1213
  this.startTriggersRuntimeIfPossible();
@@ -1552,6 +1632,34 @@ var CustomerHeroChat = class {
1552
1632
  setTraits(traits) {
1553
1633
  this.triggersRuntime?.setTraits(traits);
1554
1634
  }
1635
+ /** Hide the active incident banner for this visitor. Persisted in
1636
+ * localStorage so a refresh keeps it dismissed; resets automatically
1637
+ * when the operator changes the banner content. No-op when no banner
1638
+ * is showing. */
1639
+ dismissIncidentBanner() {
1640
+ const key = bannerKey(this.state.incidentBanner);
1641
+ if (!key) return;
1642
+ this.writeStoredBannerDismissal(key);
1643
+ this.setState({ incidentBannerDismissed: true });
1644
+ }
1645
+ readStoredBannerDismissal() {
1646
+ try {
1647
+ return this.storage?.getItem(
1648
+ `ch_incident_dismissed_${this.userConfig.chatbotId}`
1649
+ ) ?? null;
1650
+ } catch {
1651
+ return null;
1652
+ }
1653
+ }
1654
+ writeStoredBannerDismissal(key) {
1655
+ try {
1656
+ this.storage?.setItem(
1657
+ `ch_incident_dismissed_${this.userConfig.chatbotId}`,
1658
+ key
1659
+ );
1660
+ } catch {
1661
+ }
1662
+ }
1555
1663
  /** Submit pre-chat form answers. Synthesizes a customer record server-side
1556
1664
  * on the next sendMessage. Resumes any pending message that was deferred
1557
1665
  * while the form was open. */
package/dist/index.d.cts CHANGED
@@ -1,4 +1,4 @@
1
- type TranslationKey = "online" | "typing" | "unable_to_load" | "powered_by" | "new_conversation" | "open_chat" | "close_chat" | "send_message" | "helpful" | "not_helpful" | "menu" | "action_approve" | "action_cancel" | "action_what_will_happen" | "action_already_resolved" | "action_failed" | "status_sending" | "status_sent" | "status_failed" | "screenshot_capture" | "attachment_remove" | "attach_menu_open" | "attach_photo" | "drop_files_here" | "attachment_unsupported_type";
1
+ type TranslationKey = "online" | "typing" | "unable_to_load" | "powered_by" | "new_conversation" | "open_chat" | "close_chat" | "send_message" | "helpful" | "not_helpful" | "menu" | "action_approve" | "action_cancel" | "action_what_will_happen" | "action_already_resolved" | "action_failed" | "status_sending" | "status_sent" | "status_failed" | "screenshot_capture" | "attachment_remove" | "attach_menu_open" | "attach_photo" | "drop_files_here" | "attachment_unsupported_type" | "incident_dismiss" | "incident_default_link_label";
2
2
  type Translations = Record<TranslationKey, string>;
3
3
 
4
4
  declare const SUPPORTED_LOCALES: readonly ["en", "es", "pt-BR", "pt-PT", "fr", "de", "it", "nl", "pl", "tr", "ar", "ja", "ko", "zh-CN", "zh-TW"];
@@ -244,6 +244,20 @@ interface PreChatSubmission {
244
244
  /** Keyed answers from text/textarea/select/consent fields. */
245
245
  properties?: Record<string, string | number | boolean>;
246
246
  }
247
+ interface IncidentBanner {
248
+ severity: "info" | "warning" | "outage";
249
+ title: string;
250
+ body?: string;
251
+ /** Free-text human ETA, e.g. "Back online by 14:00 UTC". */
252
+ eta?: string;
253
+ /** Optional CTA — typically a status-page incident URL. */
254
+ link?: {
255
+ url: string;
256
+ label?: string;
257
+ };
258
+ /** ISO 8601 UTC. After this time the widget hides the banner. */
259
+ expiresAt?: string;
260
+ }
247
261
  interface ConsentSettings {
248
262
  /** When true, all condition kinds are evaluated. When false (default), only
249
263
  * direct launcher clicks fire — URL/time/scroll/exit-intent/trait
@@ -284,6 +298,11 @@ interface ChatState {
284
298
  /** When set, the host should preload this text into the input. Cleared
285
299
  * once the host consumes it (or when the conversation starts). */
286
300
  pendingPrefill: string | null;
301
+ /** Operator-controlled incident banner. `null` when none is active. */
302
+ incidentBanner: IncidentBanner | null;
303
+ /** True when the visitor has dismissed the active banner this session.
304
+ * Reset whenever a new banner (different content) lands. */
305
+ incidentBannerDismissed: boolean;
287
306
  }
288
307
 
289
308
  type Listener = (state: ChatState) => void;
@@ -339,6 +358,13 @@ declare class CustomerHeroChat {
339
358
  * values are kept in memory (not persisted) so the integrator decides
340
359
  * the source of truth. */
341
360
  setTraits(traits: Record<string, string | number | boolean>): void;
361
+ /** Hide the active incident banner for this visitor. Persisted in
362
+ * localStorage so a refresh keeps it dismissed; resets automatically
363
+ * when the operator changes the banner content. No-op when no banner
364
+ * is showing. */
365
+ dismissIncidentBanner(): void;
366
+ private readStoredBannerDismissal;
367
+ private writeStoredBannerDismissal;
342
368
  /** Submit pre-chat form answers. Synthesizes a customer record server-side
343
369
  * on the next sendMessage. Resumes any pending message that was deferred
344
370
  * while the form was open. */
@@ -438,4 +464,4 @@ interface StartTriggersRuntimeOptions {
438
464
  }
439
465
  declare function startTriggersRuntime(options: StartTriggersRuntimeOptions): TriggersRuntimeHandle;
440
466
 
441
- export { type ActionConfirmationBlock, type ChatMessage, type ChatState, type ConsentSettings, CustomerHeroChat, type CustomerHeroChatConfig, DEFAULTS, type IdentifyPayload, type IdentityData, type MessageBlock, type MessageRating, type MessageSource, type MessageStatus, type PreChatField, type PreChatFieldKind, type PreChatFormConfig, type PreChatSubmission, type QuickRepliesBlock, type ResolvedConfig, SUPPORTED_LOCALES, ScreenshotCancelled, ScreenshotUnavailable, type StringOverrides, type SupportedLocale, type TranslateFn, type TranslationKey, type Translations, type TriggerAction, type TriggerConditionLeaf, type TriggerConditionNode, type TriggerDefinition, type TriggerFrequency, type TriggersRuntimeHandle, type VisitorContext, canCaptureScreenshot, captureScreenshot, createTranslator, detectLocale, evaluate, isRtlLocale, pickFire, resolveLocale, startTriggersRuntime };
467
+ export { type ActionConfirmationBlock, type ChatMessage, type ChatState, type ConsentSettings, CustomerHeroChat, type CustomerHeroChatConfig, DEFAULTS, type IdentifyPayload, type IdentityData, type IncidentBanner, type MessageBlock, type MessageRating, type MessageSource, type MessageStatus, type PreChatField, type PreChatFieldKind, type PreChatFormConfig, type PreChatSubmission, type QuickRepliesBlock, type ResolvedConfig, SUPPORTED_LOCALES, ScreenshotCancelled, ScreenshotUnavailable, type StringOverrides, type SupportedLocale, type TranslateFn, type TranslationKey, type Translations, type TriggerAction, type TriggerConditionLeaf, type TriggerConditionNode, type TriggerDefinition, type TriggerFrequency, type TriggersRuntimeHandle, type VisitorContext, canCaptureScreenshot, captureScreenshot, createTranslator, detectLocale, evaluate, isRtlLocale, pickFire, resolveLocale, startTriggersRuntime };
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- type TranslationKey = "online" | "typing" | "unable_to_load" | "powered_by" | "new_conversation" | "open_chat" | "close_chat" | "send_message" | "helpful" | "not_helpful" | "menu" | "action_approve" | "action_cancel" | "action_what_will_happen" | "action_already_resolved" | "action_failed" | "status_sending" | "status_sent" | "status_failed" | "screenshot_capture" | "attachment_remove" | "attach_menu_open" | "attach_photo" | "drop_files_here" | "attachment_unsupported_type";
1
+ type TranslationKey = "online" | "typing" | "unable_to_load" | "powered_by" | "new_conversation" | "open_chat" | "close_chat" | "send_message" | "helpful" | "not_helpful" | "menu" | "action_approve" | "action_cancel" | "action_what_will_happen" | "action_already_resolved" | "action_failed" | "status_sending" | "status_sent" | "status_failed" | "screenshot_capture" | "attachment_remove" | "attach_menu_open" | "attach_photo" | "drop_files_here" | "attachment_unsupported_type" | "incident_dismiss" | "incident_default_link_label";
2
2
  type Translations = Record<TranslationKey, string>;
3
3
 
4
4
  declare const SUPPORTED_LOCALES: readonly ["en", "es", "pt-BR", "pt-PT", "fr", "de", "it", "nl", "pl", "tr", "ar", "ja", "ko", "zh-CN", "zh-TW"];
@@ -244,6 +244,20 @@ interface PreChatSubmission {
244
244
  /** Keyed answers from text/textarea/select/consent fields. */
245
245
  properties?: Record<string, string | number | boolean>;
246
246
  }
247
+ interface IncidentBanner {
248
+ severity: "info" | "warning" | "outage";
249
+ title: string;
250
+ body?: string;
251
+ /** Free-text human ETA, e.g. "Back online by 14:00 UTC". */
252
+ eta?: string;
253
+ /** Optional CTA — typically a status-page incident URL. */
254
+ link?: {
255
+ url: string;
256
+ label?: string;
257
+ };
258
+ /** ISO 8601 UTC. After this time the widget hides the banner. */
259
+ expiresAt?: string;
260
+ }
247
261
  interface ConsentSettings {
248
262
  /** When true, all condition kinds are evaluated. When false (default), only
249
263
  * direct launcher clicks fire — URL/time/scroll/exit-intent/trait
@@ -284,6 +298,11 @@ interface ChatState {
284
298
  /** When set, the host should preload this text into the input. Cleared
285
299
  * once the host consumes it (or when the conversation starts). */
286
300
  pendingPrefill: string | null;
301
+ /** Operator-controlled incident banner. `null` when none is active. */
302
+ incidentBanner: IncidentBanner | null;
303
+ /** True when the visitor has dismissed the active banner this session.
304
+ * Reset whenever a new banner (different content) lands. */
305
+ incidentBannerDismissed: boolean;
287
306
  }
288
307
 
289
308
  type Listener = (state: ChatState) => void;
@@ -339,6 +358,13 @@ declare class CustomerHeroChat {
339
358
  * values are kept in memory (not persisted) so the integrator decides
340
359
  * the source of truth. */
341
360
  setTraits(traits: Record<string, string | number | boolean>): void;
361
+ /** Hide the active incident banner for this visitor. Persisted in
362
+ * localStorage so a refresh keeps it dismissed; resets automatically
363
+ * when the operator changes the banner content. No-op when no banner
364
+ * is showing. */
365
+ dismissIncidentBanner(): void;
366
+ private readStoredBannerDismissal;
367
+ private writeStoredBannerDismissal;
342
368
  /** Submit pre-chat form answers. Synthesizes a customer record server-side
343
369
  * on the next sendMessage. Resumes any pending message that was deferred
344
370
  * while the form was open. */
@@ -438,4 +464,4 @@ interface StartTriggersRuntimeOptions {
438
464
  }
439
465
  declare function startTriggersRuntime(options: StartTriggersRuntimeOptions): TriggersRuntimeHandle;
440
466
 
441
- export { type ActionConfirmationBlock, type ChatMessage, type ChatState, type ConsentSettings, CustomerHeroChat, type CustomerHeroChatConfig, DEFAULTS, type IdentifyPayload, type IdentityData, type MessageBlock, type MessageRating, type MessageSource, type MessageStatus, type PreChatField, type PreChatFieldKind, type PreChatFormConfig, type PreChatSubmission, type QuickRepliesBlock, type ResolvedConfig, SUPPORTED_LOCALES, ScreenshotCancelled, ScreenshotUnavailable, type StringOverrides, type SupportedLocale, type TranslateFn, type TranslationKey, type Translations, type TriggerAction, type TriggerConditionLeaf, type TriggerConditionNode, type TriggerDefinition, type TriggerFrequency, type TriggersRuntimeHandle, type VisitorContext, canCaptureScreenshot, captureScreenshot, createTranslator, detectLocale, evaluate, isRtlLocale, pickFire, resolveLocale, startTriggersRuntime };
467
+ export { type ActionConfirmationBlock, type ChatMessage, type ChatState, type ConsentSettings, CustomerHeroChat, type CustomerHeroChatConfig, DEFAULTS, type IdentifyPayload, type IdentityData, type IncidentBanner, type MessageBlock, type MessageRating, type MessageSource, type MessageStatus, type PreChatField, type PreChatFieldKind, type PreChatFormConfig, type PreChatSubmission, type QuickRepliesBlock, type ResolvedConfig, SUPPORTED_LOCALES, ScreenshotCancelled, ScreenshotUnavailable, type StringOverrides, type SupportedLocale, type TranslateFn, type TranslationKey, type Translations, type TriggerAction, type TriggerConditionLeaf, type TriggerConditionNode, type TriggerDefinition, type TriggerFrequency, type TriggersRuntimeHandle, type VisitorContext, canCaptureScreenshot, captureScreenshot, createTranslator, detectLocale, evaluate, isRtlLocale, pickFire, resolveLocale, startTriggersRuntime };
package/dist/index.js CHANGED
@@ -36,6 +36,8 @@ var en = {
36
36
  attach_menu_open: "Add attachment",
37
37
  attach_photo: "Choose file",
38
38
  drop_files_here: "Drop files here",
39
+ incident_dismiss: "Dismiss",
40
+ incident_default_link_label: "Learn more",
39
41
  attachment_unsupported_type: "Unsupported file type"
40
42
  };
41
43
 
@@ -65,6 +67,8 @@ var es = {
65
67
  attach_menu_open: "A\xF1adir adjunto",
66
68
  attach_photo: "Elegir archivo",
67
69
  drop_files_here: "Suelta los archivos aqu\xED",
70
+ incident_dismiss: "Descartar",
71
+ incident_default_link_label: "M\xE1s informaci\xF3n",
68
72
  attachment_unsupported_type: "Tipo de archivo no admitido"
69
73
  };
70
74
 
@@ -94,6 +98,8 @@ var ptBR = {
94
98
  attach_menu_open: "Adicionar anexo",
95
99
  attach_photo: "Escolher arquivo",
96
100
  drop_files_here: "Solte os arquivos aqui",
101
+ incident_dismiss: "Dispensar",
102
+ incident_default_link_label: "Saiba mais",
97
103
  attachment_unsupported_type: "Tipo de arquivo n\xE3o suportado"
98
104
  };
99
105
 
@@ -123,6 +129,8 @@ var ptPT = {
123
129
  attach_menu_open: "Adicionar anexo",
124
130
  attach_photo: "Escolher ficheiro",
125
131
  drop_files_here: "Largue os ficheiros aqui",
132
+ incident_dismiss: "Dispensar",
133
+ incident_default_link_label: "Saiba mais",
126
134
  attachment_unsupported_type: "Tipo de ficheiro n\xE3o suportado"
127
135
  };
128
136
 
@@ -152,6 +160,8 @@ var fr = {
152
160
  attach_menu_open: "Ajouter une pi\xE8ce jointe",
153
161
  attach_photo: "Choisir un fichier",
154
162
  drop_files_here: "D\xE9posez les fichiers ici",
163
+ incident_dismiss: "Ignorer",
164
+ incident_default_link_label: "En savoir plus",
155
165
  attachment_unsupported_type: "Type de fichier non pris en charge"
156
166
  };
157
167
 
@@ -181,6 +191,8 @@ var de = {
181
191
  attach_menu_open: "Anhang hinzuf\xFCgen",
182
192
  attach_photo: "Datei ausw\xE4hlen",
183
193
  drop_files_here: "Dateien hier ablegen",
194
+ incident_dismiss: "Schlie\xDFen",
195
+ incident_default_link_label: "Mehr erfahren",
184
196
  attachment_unsupported_type: "Dateityp nicht unterst\xFCtzt"
185
197
  };
186
198
 
@@ -210,6 +222,8 @@ var it = {
210
222
  attach_menu_open: "Aggiungi allegato",
211
223
  attach_photo: "Scegli file",
212
224
  drop_files_here: "Trascina qui i file",
225
+ incident_dismiss: "Chiudi",
226
+ incident_default_link_label: "Scopri di pi\xF9",
213
227
  attachment_unsupported_type: "Tipo di file non supportato"
214
228
  };
215
229
 
@@ -239,6 +253,8 @@ var nl = {
239
253
  attach_menu_open: "Bijlage toevoegen",
240
254
  attach_photo: "Bestand kiezen",
241
255
  drop_files_here: "Sleep bestanden hier",
256
+ incident_dismiss: "Sluiten",
257
+ incident_default_link_label: "Meer informatie",
242
258
  attachment_unsupported_type: "Bestandstype niet ondersteund"
243
259
  };
244
260
 
@@ -268,6 +284,8 @@ var pl = {
268
284
  attach_menu_open: "Dodaj za\u0142\u0105cznik",
269
285
  attach_photo: "Wybierz plik",
270
286
  drop_files_here: "Upu\u015B\u0107 pliki tutaj",
287
+ incident_dismiss: "Zamknij",
288
+ incident_default_link_label: "Dowiedz si\u0119 wi\u0119cej",
271
289
  attachment_unsupported_type: "Nieobs\u0142ugiwany typ pliku"
272
290
  };
273
291
 
@@ -297,6 +315,8 @@ var tr = {
297
315
  attach_menu_open: "Ek ekle",
298
316
  attach_photo: "Dosya se\xE7",
299
317
  drop_files_here: "Dosyalar\u0131 buraya b\u0131rak",
318
+ incident_dismiss: "Kapat",
319
+ incident_default_link_label: "Daha fazla bilgi",
300
320
  attachment_unsupported_type: "Desteklenmeyen dosya t\xFCr\xFC"
301
321
  };
302
322
 
@@ -326,6 +346,8 @@ var ar = {
326
346
  attach_menu_open: "\u0625\u0636\u0627\u0641\u0629 \u0645\u0631\u0641\u0642",
327
347
  attach_photo: "\u0627\u062E\u062A\u0631 \u0645\u0644\u0641\u064B\u0627",
328
348
  drop_files_here: "\u0623\u0641\u0644\u062A \u0627\u0644\u0645\u0644\u0641\u0627\u062A \u0647\u0646\u0627",
349
+ incident_dismiss: "\u0625\u063A\u0644\u0627\u0642",
350
+ incident_default_link_label: "\u0627\u0639\u0631\u0641 \u0627\u0644\u0645\u0632\u064A\u062F",
329
351
  attachment_unsupported_type: "\u0646\u0648\u0639 \u0627\u0644\u0645\u0644\u0641 \u063A\u064A\u0631 \u0645\u062F\u0639\u0648\u0645"
330
352
  };
331
353
 
@@ -355,6 +377,8 @@ var ja = {
355
377
  attach_menu_open: "\u6DFB\u4ED8\u30D5\u30A1\u30A4\u30EB\u3092\u8FFD\u52A0",
356
378
  attach_photo: "\u30D5\u30A1\u30A4\u30EB\u3092\u9078\u629E",
357
379
  drop_files_here: "\u30D5\u30A1\u30A4\u30EB\u3092\u3053\u3053\u306B\u30C9\u30ED\u30C3\u30D7",
380
+ incident_dismiss: "\u9589\u3058\u308B",
381
+ incident_default_link_label: "\u8A73\u7D30\u3092\u898B\u308B",
358
382
  attachment_unsupported_type: "\u30B5\u30DD\u30FC\u30C8\u3055\u308C\u3066\u3044\u306A\u3044\u30D5\u30A1\u30A4\u30EB\u5F62\u5F0F"
359
383
  };
360
384
 
@@ -384,6 +408,8 @@ var ko = {
384
408
  attach_menu_open: "\uCCA8\uBD80 \uD30C\uC77C \uCD94\uAC00",
385
409
  attach_photo: "\uD30C\uC77C \uC120\uD0DD",
386
410
  drop_files_here: "\uD30C\uC77C\uC744 \uC5EC\uAE30\uC5D0 \uB193\uAE30",
411
+ incident_dismiss: "\uB2EB\uAE30",
412
+ incident_default_link_label: "\uC790\uC138\uD788 \uC54C\uC544\uBCF4\uAE30",
387
413
  attachment_unsupported_type: "\uC9C0\uC6D0\uB418\uC9C0 \uC54A\uB294 \uD30C\uC77C \uD615\uC2DD"
388
414
  };
389
415
 
@@ -413,6 +439,8 @@ var zhCN = {
413
439
  attach_menu_open: "\u6DFB\u52A0\u9644\u4EF6",
414
440
  attach_photo: "\u9009\u62E9\u6587\u4EF6",
415
441
  drop_files_here: "\u5C06\u6587\u4EF6\u62D6\u653E\u5230\u6B64\u5904",
442
+ incident_dismiss: "\u5173\u95ED",
443
+ incident_default_link_label: "\u4E86\u89E3\u66F4\u591A",
416
444
  attachment_unsupported_type: "\u4E0D\u652F\u6301\u7684\u6587\u4EF6\u7C7B\u578B"
417
445
  };
418
446
 
@@ -442,6 +470,8 @@ var zhTW = {
442
470
  attach_menu_open: "\u65B0\u589E\u9644\u4EF6",
443
471
  attach_photo: "\u9078\u64C7\u6A94\u6848",
444
472
  drop_files_here: "\u5C07\u6A94\u6848\u62D6\u653E\u5230\u6B64\u8655",
473
+ incident_dismiss: "\u95DC\u9589",
474
+ incident_default_link_label: "\u77AD\u89E3\u66F4\u591A",
445
475
  attachment_unsupported_type: "\u4E0D\u652F\u63F4\u7684\u6A94\u6848\u985E\u578B"
446
476
  };
447
477
 
@@ -961,9 +991,50 @@ function resolveConfig(userConfig, fetched) {
961
991
  stringOverrides: fetched?.stringOverrides
962
992
  };
963
993
  }
994
+ function sanitizeIncidentBanner(input) {
995
+ if (!input || typeof input !== "object") return null;
996
+ const raw = input;
997
+ const sev = raw.severity;
998
+ if (sev !== "info" && sev !== "warning" && sev !== "outage") return null;
999
+ if (typeof raw.title !== "string" || raw.title.length === 0) return null;
1000
+ if (typeof raw.expiresAt === "string") {
1001
+ const t = Date.parse(raw.expiresAt);
1002
+ if (!Number.isNaN(t) && t <= Date.now()) return null;
1003
+ }
1004
+ const out = { severity: sev, title: raw.title };
1005
+ if (typeof raw.body === "string") out.body = raw.body;
1006
+ if (typeof raw.eta === "string") out.eta = raw.eta;
1007
+ if (raw.link && typeof raw.link === "object") {
1008
+ const link = raw.link;
1009
+ if (typeof link.url === "string") {
1010
+ out.link = { url: link.url };
1011
+ if (typeof link.label === "string") out.link.label = link.label;
1012
+ }
1013
+ }
1014
+ if (typeof raw.expiresAt === "string") out.expiresAt = raw.expiresAt;
1015
+ return out;
1016
+ }
1017
+ function bannerKey(banner) {
1018
+ if (!banner) return null;
1019
+ return JSON.stringify([
1020
+ banner.severity,
1021
+ banner.title,
1022
+ banner.body ?? "",
1023
+ banner.eta ?? "",
1024
+ banner.link?.url ?? "",
1025
+ banner.link?.label ?? "",
1026
+ banner.expiresAt ?? ""
1027
+ ]);
1028
+ }
964
1029
  function getStorage() {
965
1030
  try {
966
- return typeof window !== "undefined" ? window.localStorage : null;
1031
+ if (typeof window !== "undefined" && window.localStorage)
1032
+ return window.localStorage;
1033
+ if (typeof globalThis !== "undefined") {
1034
+ const ls = globalThis.localStorage;
1035
+ if (ls) return ls;
1036
+ }
1037
+ return null;
967
1038
  } catch {
968
1039
  return null;
969
1040
  }
@@ -1005,7 +1076,9 @@ var CustomerHeroChat = class {
1005
1076
  preChatSubmission: null,
1006
1077
  consent: this.readStoredConsent(),
1007
1078
  pendingTriggerId: null,
1008
- pendingPrefill: null
1079
+ pendingPrefill: null,
1080
+ incidentBanner: null,
1081
+ incidentBannerDismissed: false
1009
1082
  };
1010
1083
  }
1011
1084
  // ── Proactive engagement state ─────────────────────────────────────
@@ -1084,11 +1157,18 @@ var CustomerHeroChat = class {
1084
1157
  const resolved = resolveConfig(this.userConfig, fetched);
1085
1158
  const triggers = Array.isArray(fetched.triggers) ? fetched.triggers : [];
1086
1159
  const preChatForm = fetched.preChatForm ?? null;
1160
+ const incidentBanner = sanitizeIncidentBanner(fetched.incidentBanner);
1161
+ const prevBannerKey = bannerKey(this.state.incidentBanner);
1162
+ const nextBannerKey = bannerKey(incidentBanner);
1163
+ const dismissedFromStorage = nextBannerKey && nextBannerKey === this.readStoredBannerDismissal();
1164
+ const incidentBannerDismissed = nextBannerKey === prevBannerKey ? this.state.incidentBannerDismissed || !!dismissedFromStorage : !!dismissedFromStorage;
1087
1165
  this.setState({
1088
1166
  config: resolved,
1089
1167
  configLoaded: true,
1090
1168
  triggers,
1091
- preChatForm
1169
+ preChatForm,
1170
+ incidentBanner,
1171
+ incidentBannerDismissed
1092
1172
  });
1093
1173
  if (resolved.stringOverrides) this.rebuildTranslator();
1094
1174
  this.startTriggersRuntimeIfPossible();
@@ -1513,6 +1593,34 @@ var CustomerHeroChat = class {
1513
1593
  setTraits(traits) {
1514
1594
  this.triggersRuntime?.setTraits(traits);
1515
1595
  }
1596
+ /** Hide the active incident banner for this visitor. Persisted in
1597
+ * localStorage so a refresh keeps it dismissed; resets automatically
1598
+ * when the operator changes the banner content. No-op when no banner
1599
+ * is showing. */
1600
+ dismissIncidentBanner() {
1601
+ const key = bannerKey(this.state.incidentBanner);
1602
+ if (!key) return;
1603
+ this.writeStoredBannerDismissal(key);
1604
+ this.setState({ incidentBannerDismissed: true });
1605
+ }
1606
+ readStoredBannerDismissal() {
1607
+ try {
1608
+ return this.storage?.getItem(
1609
+ `ch_incident_dismissed_${this.userConfig.chatbotId}`
1610
+ ) ?? null;
1611
+ } catch {
1612
+ return null;
1613
+ }
1614
+ }
1615
+ writeStoredBannerDismissal(key) {
1616
+ try {
1617
+ this.storage?.setItem(
1618
+ `ch_incident_dismissed_${this.userConfig.chatbotId}`,
1619
+ key
1620
+ );
1621
+ } catch {
1622
+ }
1623
+ }
1516
1624
  /** Submit pre-chat form answers. Synthesizes a customer record server-side
1517
1625
  * on the next sendMessage. Resumes any pending message that was deferred
1518
1626
  * while the form was open. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@customerhero/js",
3
- "version": "2.1.1",
3
+ "version": "2.2.0",
4
4
  "private": false,
5
5
  "description": "Framework-agnostic JavaScript client for the CustomerHero chat widget.",
6
6
  "keywords": [