@customerhero/js 0.0.2 → 1.0.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
@@ -21,7 +21,16 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
23
  CustomerHeroChat: () => CustomerHeroChat,
24
- DEFAULTS: () => DEFAULTS
24
+ DEFAULTS: () => DEFAULTS,
25
+ SUPPORTED_LOCALES: () => SUPPORTED_LOCALES,
26
+ ScreenshotCancelled: () => ScreenshotCancelled,
27
+ ScreenshotUnavailable: () => ScreenshotUnavailable,
28
+ canCaptureScreenshot: () => canCaptureScreenshot,
29
+ captureScreenshot: () => captureScreenshot,
30
+ createTranslator: () => createTranslator,
31
+ detectLocale: () => detectLocale,
32
+ isRtlLocale: () => isRtlLocale,
33
+ resolveLocale: () => resolveLocale
25
34
  });
26
35
  module.exports = __toCommonJS(index_exports);
27
36
 
@@ -37,7 +46,7 @@ var DEFAULTS = {
37
46
  title: "CustomerHero"
38
47
  };
39
48
 
40
- // src/i18n.ts
49
+ // src/i18n/locales/en.ts
41
50
  var en = {
42
51
  online: "Online",
43
52
  typing: "Typing...",
@@ -49,8 +58,20 @@ var en = {
49
58
  send_message: "Send message",
50
59
  helpful: "Helpful",
51
60
  not_helpful: "Not helpful",
52
- menu: "Menu"
61
+ menu: "Menu",
62
+ action_approve: "Approve",
63
+ action_cancel: "Cancel",
64
+ action_what_will_happen: "What will happen?",
65
+ action_already_resolved: "This action has already been resolved.",
66
+ action_failed: "Couldn't complete that action. Please try again.",
67
+ status_sending: "Sending",
68
+ status_sent: "Sent",
69
+ status_failed: "Failed to send",
70
+ screenshot_capture: "Capture screenshot",
71
+ attachment_remove: "Remove attachment"
53
72
  };
73
+
74
+ // src/i18n/locales/es.ts
54
75
  var es = {
55
76
  online: "En l\xEDnea",
56
77
  typing: "Escribiendo...",
@@ -62,25 +83,471 @@ var es = {
62
83
  send_message: "Enviar mensaje",
63
84
  helpful: "\xDAtil",
64
85
  not_helpful: "No \xFAtil",
65
- menu: "Men\xFA"
86
+ menu: "Men\xFA",
87
+ action_approve: "Aprobar",
88
+ action_cancel: "Cancelar",
89
+ action_what_will_happen: "\xBFQu\xE9 pasar\xE1?",
90
+ action_already_resolved: "Esta acci\xF3n ya se ha resuelto.",
91
+ action_failed: "No se pudo completar la acci\xF3n. Int\xE9ntalo de nuevo.",
92
+ status_sending: "Enviando",
93
+ status_sent: "Enviado",
94
+ status_failed: "Error al enviar",
95
+ screenshot_capture: "Capturar pantalla",
96
+ attachment_remove: "Quitar adjunto"
97
+ };
98
+
99
+ // src/i18n/locales/pt-BR.ts
100
+ var ptBR = {
101
+ online: "Online",
102
+ typing: "Digitando...",
103
+ unable_to_load: "N\xE3o foi poss\xEDvel carregar o chat",
104
+ powered_by: "Desenvolvido por",
105
+ new_conversation: "Nova conversa",
106
+ open_chat: "Abrir chat",
107
+ close_chat: "Fechar chat",
108
+ send_message: "Enviar mensagem",
109
+ helpful: "\xDAtil",
110
+ not_helpful: "N\xE3o \xFAtil",
111
+ menu: "Menu",
112
+ action_approve: "Aprovar",
113
+ action_cancel: "Cancelar",
114
+ action_what_will_happen: "O que vai acontecer?",
115
+ action_already_resolved: "Esta a\xE7\xE3o j\xE1 foi resolvida.",
116
+ action_failed: "N\xE3o foi poss\xEDvel concluir a a\xE7\xE3o. Tente novamente.",
117
+ status_sending: "Enviando",
118
+ status_sent: "Enviado",
119
+ status_failed: "Falha no envio",
120
+ screenshot_capture: "Capturar tela",
121
+ attachment_remove: "Remover anexo"
122
+ };
123
+
124
+ // src/i18n/locales/pt-PT.ts
125
+ var ptPT = {
126
+ online: "Online",
127
+ typing: "A escrever...",
128
+ unable_to_load: "N\xE3o foi poss\xEDvel carregar o chat",
129
+ powered_by: "Desenvolvido por",
130
+ new_conversation: "Nova conversa",
131
+ open_chat: "Abrir chat",
132
+ close_chat: "Fechar chat",
133
+ send_message: "Enviar mensagem",
134
+ helpful: "\xDAtil",
135
+ not_helpful: "N\xE3o \xFAtil",
136
+ menu: "Menu",
137
+ action_approve: "Aprovar",
138
+ action_cancel: "Cancelar",
139
+ action_what_will_happen: "O que ir\xE1 acontecer?",
140
+ action_already_resolved: "Esta a\xE7\xE3o j\xE1 foi resolvida.",
141
+ action_failed: "N\xE3o foi poss\xEDvel concluir a a\xE7\xE3o. Tente novamente.",
142
+ status_sending: "A enviar",
143
+ status_sent: "Enviada",
144
+ status_failed: "Falha no envio",
145
+ screenshot_capture: "Capturar ecr\xE3",
146
+ attachment_remove: "Remover anexo"
147
+ };
148
+
149
+ // src/i18n/locales/fr.ts
150
+ var fr = {
151
+ online: "En ligne",
152
+ typing: "Saisie...",
153
+ unable_to_load: "Impossible de charger le chat",
154
+ powered_by: "Propuls\xE9 par",
155
+ new_conversation: "Nouvelle conversation",
156
+ open_chat: "Ouvrir le chat",
157
+ close_chat: "Fermer le chat",
158
+ send_message: "Envoyer le message",
159
+ helpful: "Utile",
160
+ not_helpful: "Pas utile",
161
+ menu: "Menu",
162
+ action_approve: "Approuver",
163
+ action_cancel: "Annuler",
164
+ action_what_will_happen: "Que va-t-il se passer ?",
165
+ action_already_resolved: "Cette action a d\xE9j\xE0 \xE9t\xE9 trait\xE9e.",
166
+ action_failed: "Impossible de r\xE9aliser cette action. Veuillez r\xE9essayer.",
167
+ status_sending: "Envoi\u2026",
168
+ status_sent: "Envoy\xE9",
169
+ status_failed: "\xC9chec de l'envoi",
170
+ screenshot_capture: "Capture d'\xE9cran",
171
+ attachment_remove: "Supprimer la pi\xE8ce jointe"
172
+ };
173
+
174
+ // src/i18n/locales/de.ts
175
+ var de = {
176
+ online: "Online",
177
+ typing: "Schreibt...",
178
+ unable_to_load: "Chat konnte nicht geladen werden",
179
+ powered_by: "Bereitgestellt von",
180
+ new_conversation: "Neue Unterhaltung",
181
+ open_chat: "Chat \xF6ffnen",
182
+ close_chat: "Chat schlie\xDFen",
183
+ send_message: "Nachricht senden",
184
+ helpful: "Hilfreich",
185
+ not_helpful: "Nicht hilfreich",
186
+ menu: "Men\xFC",
187
+ action_approve: "Best\xE4tigen",
188
+ action_cancel: "Abbrechen",
189
+ action_what_will_happen: "Was passiert dann?",
190
+ action_already_resolved: "Diese Aktion wurde bereits abgeschlossen.",
191
+ action_failed: "Aktion konnte nicht abgeschlossen werden. Bitte erneut versuchen.",
192
+ status_sending: "Wird gesendet",
193
+ status_sent: "Gesendet",
194
+ status_failed: "Senden fehlgeschlagen",
195
+ screenshot_capture: "Bildschirmfoto erstellen",
196
+ attachment_remove: "Anhang entfernen"
197
+ };
198
+
199
+ // src/i18n/locales/it.ts
200
+ var it = {
201
+ online: "Online",
202
+ typing: "Sta scrivendo...",
203
+ unable_to_load: "Impossibile caricare la chat",
204
+ powered_by: "Realizzato da",
205
+ new_conversation: "Nuova conversazione",
206
+ open_chat: "Apri chat",
207
+ close_chat: "Chiudi chat",
208
+ send_message: "Invia messaggio",
209
+ helpful: "Utile",
210
+ not_helpful: "Non utile",
211
+ menu: "Menu",
212
+ action_approve: "Approva",
213
+ action_cancel: "Annulla",
214
+ action_what_will_happen: "Cosa accadr\xE0?",
215
+ action_already_resolved: "Questa azione \xE8 gi\xE0 stata risolta.",
216
+ action_failed: "Impossibile completare l'azione. Riprova.",
217
+ status_sending: "Invio in corso",
218
+ status_sent: "Inviato",
219
+ status_failed: "Invio non riuscito",
220
+ screenshot_capture: "Cattura schermata",
221
+ attachment_remove: "Rimuovi allegato"
222
+ };
223
+
224
+ // src/i18n/locales/nl.ts
225
+ var nl = {
226
+ online: "Online",
227
+ typing: "Typt...",
228
+ unable_to_load: "Chat kon niet worden geladen",
229
+ powered_by: "Mogelijk gemaakt door",
230
+ new_conversation: "Nieuw gesprek",
231
+ open_chat: "Chat openen",
232
+ close_chat: "Chat sluiten",
233
+ send_message: "Bericht verzenden",
234
+ helpful: "Nuttig",
235
+ not_helpful: "Niet nuttig",
236
+ menu: "Menu",
237
+ action_approve: "Goedkeuren",
238
+ action_cancel: "Annuleren",
239
+ action_what_will_happen: "Wat gaat er gebeuren?",
240
+ action_already_resolved: "Deze actie is al afgehandeld.",
241
+ action_failed: "Actie kon niet worden voltooid. Probeer het opnieuw.",
242
+ status_sending: "Verzenden",
243
+ status_sent: "Verzonden",
244
+ status_failed: "Verzenden mislukt",
245
+ screenshot_capture: "Schermafbeelding maken",
246
+ attachment_remove: "Bijlage verwijderen"
247
+ };
248
+
249
+ // src/i18n/locales/pl.ts
250
+ var pl = {
251
+ online: "Online",
252
+ typing: "Pisze...",
253
+ unable_to_load: "Nie mo\u017Cna za\u0142adowa\u0107 czatu",
254
+ powered_by: "Dzia\u0142a dzi\u0119ki",
255
+ new_conversation: "Nowa rozmowa",
256
+ open_chat: "Otw\xF3rz czat",
257
+ close_chat: "Zamknij czat",
258
+ send_message: "Wy\u015Blij wiadomo\u015B\u0107",
259
+ helpful: "Pomocne",
260
+ not_helpful: "Niepomocne",
261
+ menu: "Menu",
262
+ action_approve: "Zatwierd\u017A",
263
+ action_cancel: "Anuluj",
264
+ action_what_will_happen: "Co si\u0119 stanie?",
265
+ action_already_resolved: "Ta akcja zosta\u0142a ju\u017C rozwi\u0105zana.",
266
+ action_failed: "Nie uda\u0142o si\u0119 wykona\u0107 akcji. Spr\xF3buj ponownie.",
267
+ status_sending: "Wysy\u0142anie",
268
+ status_sent: "Wys\u0142ano",
269
+ status_failed: "Nie uda\u0142o si\u0119 wys\u0142a\u0107",
270
+ screenshot_capture: "Zr\xF3b zrzut ekranu",
271
+ attachment_remove: "Usu\u0144 za\u0142\u0105cznik"
272
+ };
273
+
274
+ // src/i18n/locales/tr.ts
275
+ var tr = {
276
+ online: "\xC7evrimi\xE7i",
277
+ typing: "Yaz\u0131yor...",
278
+ unable_to_load: "Sohbet y\xFCklenemedi",
279
+ powered_by: "Sa\u011Flayan",
280
+ new_conversation: "Yeni sohbet",
281
+ open_chat: "Sohbeti a\xE7",
282
+ close_chat: "Sohbeti kapat",
283
+ send_message: "Mesaj g\xF6nder",
284
+ helpful: "Yararl\u0131",
285
+ not_helpful: "Yararl\u0131 de\u011Fil",
286
+ menu: "Men\xFC",
287
+ action_approve: "Onayla",
288
+ action_cancel: "\u0130ptal",
289
+ action_what_will_happen: "Ne olacak?",
290
+ action_already_resolved: "Bu i\u015Flem zaten \xE7\xF6z\xFCld\xFC.",
291
+ action_failed: "\u0130\u015Flem tamamlanamad\u0131. L\xFCtfen tekrar deneyin.",
292
+ status_sending: "G\xF6nderiliyor",
293
+ status_sent: "G\xF6nderildi",
294
+ status_failed: "G\xF6nderilemedi",
295
+ screenshot_capture: "Ekran g\xF6r\xFCnt\xFCs\xFC al",
296
+ attachment_remove: "Eki kald\u0131r"
297
+ };
298
+
299
+ // src/i18n/locales/ar.ts
300
+ var ar = {
301
+ online: "\u0645\u062A\u0635\u0644",
302
+ typing: "\u064A\u0643\u062A\u0628...",
303
+ unable_to_load: "\u062A\u0639\u0630\u0631 \u062A\u062D\u0645\u064A\u0644 \u0627\u0644\u0645\u062D\u0627\u062F\u062B\u0629",
304
+ powered_by: "\u0645\u062F\u0639\u0648\u0645 \u0645\u0646",
305
+ new_conversation: "\u0645\u062D\u0627\u062F\u062B\u0629 \u062C\u062F\u064A\u062F\u0629",
306
+ open_chat: "\u0641\u062A\u062D \u0627\u0644\u0645\u062D\u0627\u062F\u062B\u0629",
307
+ close_chat: "\u0625\u063A\u0644\u0627\u0642 \u0627\u0644\u0645\u062D\u0627\u062F\u062B\u0629",
308
+ send_message: "\u0625\u0631\u0633\u0627\u0644 \u0627\u0644\u0631\u0633\u0627\u0644\u0629",
309
+ helpful: "\u0645\u0641\u064A\u062F",
310
+ not_helpful: "\u063A\u064A\u0631 \u0645\u0641\u064A\u062F",
311
+ menu: "\u0627\u0644\u0642\u0627\u0626\u0645\u0629",
312
+ action_approve: "\u0645\u0648\u0627\u0641\u0642\u0629",
313
+ action_cancel: "\u0625\u0644\u063A\u0627\u0621",
314
+ action_what_will_happen: "\u0645\u0627\u0630\u0627 \u0633\u064A\u062D\u062F\u062B\u061F",
315
+ action_already_resolved: "\u062A\u0645 \u062D\u0644 \u0647\u0630\u0627 \u0627\u0644\u0625\u062C\u0631\u0627\u0621 \u0628\u0627\u0644\u0641\u0639\u0644.",
316
+ action_failed: "\u062A\u0639\u0630\u0631 \u0625\u0643\u0645\u0627\u0644 \u0627\u0644\u0625\u062C\u0631\u0627\u0621. \u064A\u0631\u062C\u0649 \u0627\u0644\u0645\u062D\u0627\u0648\u0644\u0629 \u0645\u0631\u0629 \u0623\u062E\u0631\u0649.",
317
+ status_sending: "\u062C\u0627\u0631\u064D \u0627\u0644\u0625\u0631\u0633\u0627\u0644",
318
+ status_sent: "\u062A\u0645 \u0627\u0644\u0625\u0631\u0633\u0627\u0644",
319
+ status_failed: "\u0641\u0634\u0644 \u0627\u0644\u0625\u0631\u0633\u0627\u0644",
320
+ screenshot_capture: "\u0627\u0644\u062A\u0642\u0627\u0637 \u0644\u0642\u0637\u0629 \u0634\u0627\u0634\u0629",
321
+ attachment_remove: "\u0625\u0632\u0627\u0644\u0629 \u0627\u0644\u0645\u0631\u0641\u0642"
322
+ };
323
+
324
+ // src/i18n/locales/ja.ts
325
+ var ja = {
326
+ online: "\u30AA\u30F3\u30E9\u30A4\u30F3",
327
+ typing: "\u5165\u529B\u4E2D...",
328
+ unable_to_load: "\u30C1\u30E3\u30C3\u30C8\u3092\u8AAD\u307F\u8FBC\u3081\u307E\u305B\u3093",
329
+ powered_by: "\u63D0\u4F9B\uFF1A",
330
+ new_conversation: "\u65B0\u3057\u3044\u4F1A\u8A71",
331
+ open_chat: "\u30C1\u30E3\u30C3\u30C8\u3092\u958B\u304F",
332
+ close_chat: "\u30C1\u30E3\u30C3\u30C8\u3092\u9589\u3058\u308B",
333
+ send_message: "\u30E1\u30C3\u30BB\u30FC\u30B8\u3092\u9001\u4FE1",
334
+ helpful: "\u5F79\u306B\u7ACB\u3063\u305F",
335
+ not_helpful: "\u5F79\u306B\u7ACB\u305F\u306A\u304B\u3063\u305F",
336
+ menu: "\u30E1\u30CB\u30E5\u30FC",
337
+ action_approve: "\u627F\u8A8D",
338
+ action_cancel: "\u30AD\u30E3\u30F3\u30BB\u30EB",
339
+ action_what_will_happen: "\u4F55\u304C\u8D77\u3053\u308A\u307E\u3059\u304B\uFF1F",
340
+ action_already_resolved: "\u3053\u306E\u30A2\u30AF\u30B7\u30E7\u30F3\u306F\u65E2\u306B\u51E6\u7406\u3055\u308C\u3066\u3044\u307E\u3059\u3002",
341
+ action_failed: "\u30A2\u30AF\u30B7\u30E7\u30F3\u3092\u5B8C\u4E86\u3067\u304D\u307E\u305B\u3093\u3067\u3057\u305F\u3002\u3082\u3046\u4E00\u5EA6\u304A\u8A66\u3057\u304F\u3060\u3055\u3044\u3002",
342
+ status_sending: "\u9001\u4FE1\u4E2D",
343
+ status_sent: "\u9001\u4FE1\u6E08\u307F",
344
+ status_failed: "\u9001\u4FE1\u306B\u5931\u6557\u3057\u307E\u3057\u305F",
345
+ screenshot_capture: "\u30B9\u30AF\u30EA\u30FC\u30F3\u30B7\u30E7\u30C3\u30C8\u3092\u64AE\u308B",
346
+ attachment_remove: "\u6DFB\u4ED8\u30D5\u30A1\u30A4\u30EB\u3092\u524A\u9664"
347
+ };
348
+
349
+ // src/i18n/locales/ko.ts
350
+ var ko = {
351
+ online: "\uC628\uB77C\uC778",
352
+ typing: "\uC785\uB825 \uC911...",
353
+ unable_to_load: "\uCC44\uD305\uC744 \uBD88\uB7EC\uC62C \uC218 \uC5C6\uC2B5\uB2C8\uB2E4",
354
+ powered_by: "\uC81C\uACF5:",
355
+ new_conversation: "\uC0C8 \uB300\uD654",
356
+ open_chat: "\uCC44\uD305 \uC5F4\uAE30",
357
+ close_chat: "\uCC44\uD305 \uB2EB\uAE30",
358
+ send_message: "\uBA54\uC2DC\uC9C0 \uBCF4\uB0B4\uAE30",
359
+ helpful: "\uB3C4\uC6C0\uC774 \uB428",
360
+ not_helpful: "\uB3C4\uC6C0\uC774 \uC548 \uB428",
361
+ menu: "\uBA54\uB274",
362
+ action_approve: "\uC2B9\uC778",
363
+ action_cancel: "\uCDE8\uC18C",
364
+ action_what_will_happen: "\uBB34\uC2A8 \uC77C\uC774 \uC77C\uC5B4\uB098\uB098\uC694?",
365
+ action_already_resolved: "\uC774 \uC791\uC5C5\uC740 \uC774\uBBF8 \uCC98\uB9AC\uB418\uC5C8\uC2B5\uB2C8\uB2E4.",
366
+ action_failed: "\uC791\uC5C5\uC744 \uC644\uB8CC\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. \uB2E4\uC2DC \uC2DC\uB3C4\uD574 \uC8FC\uC138\uC694.",
367
+ status_sending: "\uBCF4\uB0B4\uB294 \uC911",
368
+ status_sent: "\uBCF4\uB0C8\uC74C",
369
+ status_failed: "\uBCF4\uB0B4\uAE30 \uC2E4\uD328",
370
+ screenshot_capture: "\uC2A4\uD06C\uB9B0\uC0F7 \uCC0D\uAE30",
371
+ attachment_remove: "\uCCA8\uBD80 \uD30C\uC77C \uC81C\uAC70"
372
+ };
373
+
374
+ // src/i18n/locales/zh-CN.ts
375
+ var zhCN = {
376
+ online: "\u5728\u7EBF",
377
+ typing: "\u6B63\u5728\u8F93\u5165...",
378
+ unable_to_load: "\u65E0\u6CD5\u52A0\u8F7D\u804A\u5929",
379
+ powered_by: "\u6280\u672F\u652F\u6301\uFF1A",
380
+ new_conversation: "\u65B0\u5BF9\u8BDD",
381
+ open_chat: "\u6253\u5F00\u804A\u5929",
382
+ close_chat: "\u5173\u95ED\u804A\u5929",
383
+ send_message: "\u53D1\u9001\u6D88\u606F",
384
+ helpful: "\u6709\u7528",
385
+ not_helpful: "\u6CA1\u7528",
386
+ menu: "\u83DC\u5355",
387
+ action_approve: "\u6279\u51C6",
388
+ action_cancel: "\u53D6\u6D88",
389
+ action_what_will_happen: "\u4F1A\u53D1\u751F\u4EC0\u4E48\uFF1F",
390
+ action_already_resolved: "\u6B64\u64CD\u4F5C\u5DF2\u5904\u7406\u3002",
391
+ action_failed: "\u64CD\u4F5C\u672A\u80FD\u5B8C\u6210\uFF0C\u8BF7\u91CD\u8BD5\u3002",
392
+ status_sending: "\u53D1\u9001\u4E2D",
393
+ status_sent: "\u5DF2\u53D1\u9001",
394
+ status_failed: "\u53D1\u9001\u5931\u8D25",
395
+ screenshot_capture: "\u622A\u5C4F",
396
+ attachment_remove: "\u79FB\u9664\u9644\u4EF6"
397
+ };
398
+
399
+ // src/i18n/locales/zh-TW.ts
400
+ var zhTW = {
401
+ online: "\u5728\u7DDA",
402
+ typing: "\u6B63\u5728\u8F38\u5165...",
403
+ unable_to_load: "\u7121\u6CD5\u8F09\u5165\u804A\u5929",
404
+ powered_by: "\u6280\u8853\u652F\u63F4\uFF1A",
405
+ new_conversation: "\u65B0\u5C0D\u8A71",
406
+ open_chat: "\u958B\u555F\u804A\u5929",
407
+ close_chat: "\u95DC\u9589\u804A\u5929",
408
+ send_message: "\u50B3\u9001\u8A0A\u606F",
409
+ helpful: "\u6709\u7528",
410
+ not_helpful: "\u6C92\u7528",
411
+ menu: "\u9078\u55AE",
412
+ action_approve: "\u6838\u51C6",
413
+ action_cancel: "\u53D6\u6D88",
414
+ action_what_will_happen: "\u6703\u767C\u751F\u4EC0\u9EBC\uFF1F",
415
+ action_already_resolved: "\u6B64\u64CD\u4F5C\u5DF2\u8655\u7406\u3002",
416
+ action_failed: "\u64CD\u4F5C\u672A\u80FD\u5B8C\u6210\uFF0C\u8ACB\u91CD\u8A66\u3002",
417
+ status_sending: "\u50B3\u9001\u4E2D",
418
+ status_sent: "\u5DF2\u50B3\u9001",
419
+ status_failed: "\u50B3\u9001\u5931\u6557",
420
+ screenshot_capture: "\u64F7\u53D6\u756B\u9762",
421
+ attachment_remove: "\u79FB\u9664\u9644\u4EF6"
422
+ };
423
+
424
+ // src/i18n/index.ts
425
+ var SUPPORTED_LOCALES = [
426
+ "en",
427
+ "es",
428
+ "pt-BR",
429
+ "pt-PT",
430
+ "fr",
431
+ "de",
432
+ "it",
433
+ "nl",
434
+ "pl",
435
+ "tr",
436
+ "ar",
437
+ "ja",
438
+ "ko",
439
+ "zh-CN",
440
+ "zh-TW"
441
+ ];
442
+ var catalog = {
443
+ en,
444
+ es,
445
+ "pt-BR": ptBR,
446
+ "pt-PT": ptPT,
447
+ fr,
448
+ de,
449
+ it,
450
+ nl,
451
+ pl,
452
+ tr,
453
+ ar,
454
+ ja,
455
+ ko,
456
+ "zh-CN": zhCN,
457
+ "zh-TW": zhTW
66
458
  };
67
- var locales = { en, es };
68
- function createTranslate(locale) {
69
- const resolved = resolveLocale(locale);
70
- const translations = locales[resolved] ?? en;
71
- return (key) => translations[key] ?? en[key] ?? key;
459
+ var RTL_PRIMARY_LANGS = /* @__PURE__ */ new Set(["ar", "he", "fa", "ur"]);
460
+ function isRtlLocale(locale) {
461
+ return RTL_PRIMARY_LANGS.has(locale.split("-")[0].toLowerCase());
72
462
  }
73
463
  function resolveLocale(locale) {
74
- if (locale) {
75
- const base = locale.split("-")[0].toLowerCase();
76
- if (locales[base]) return base;
464
+ if (!locale) return "en";
465
+ if (SUPPORTED_LOCALES.includes(locale)) {
466
+ return locale;
467
+ }
468
+ const ciMatch = SUPPORTED_LOCALES.find(
469
+ (l) => l.toLowerCase() === locale.toLowerCase()
470
+ );
471
+ if (ciMatch) return ciMatch;
472
+ const base = locale.split("-")[0].toLowerCase();
473
+ if (base === "pt") return "pt-BR";
474
+ if (base === "zh") return "zh-CN";
475
+ if (SUPPORTED_LOCALES.includes(base)) {
476
+ return base;
477
+ }
478
+ return "en";
479
+ }
480
+ function detectLocale(explicit) {
481
+ if (explicit) return resolveLocale(explicit);
482
+ if (typeof window !== "undefined" && window.location?.search) {
483
+ try {
484
+ const params = new URLSearchParams(window.location.search);
485
+ const lang = params.get("lang");
486
+ if (lang) return resolveLocale(lang);
487
+ } catch {
488
+ }
77
489
  }
78
490
  if (typeof navigator !== "undefined" && navigator.language) {
79
- const base = navigator.language.split("-")[0].toLowerCase();
80
- if (locales[base]) return base;
491
+ return resolveLocale(navigator.language);
81
492
  }
82
493
  return "en";
83
494
  }
495
+ function createTranslator(locale, overrides) {
496
+ const translations = catalog[locale] ?? catalog.en;
497
+ return (key) => {
498
+ const ov = overrides?.[key];
499
+ if (ov) {
500
+ const localized = ov[locale];
501
+ if (typeof localized === "string") return localized;
502
+ const enOverride = ov.en;
503
+ if (typeof enOverride === "string") return enOverride;
504
+ }
505
+ return translations[key] ?? catalog.en[key] ?? key;
506
+ };
507
+ }
508
+
509
+ // src/sse.ts
510
+ async function* readSSEStream(stream) {
511
+ const reader = stream.getReader();
512
+ const decoder = new TextDecoder();
513
+ let buffer = "";
514
+ try {
515
+ while (true) {
516
+ const { done, value } = await reader.read();
517
+ if (done) break;
518
+ buffer += decoder.decode(value, { stream: true });
519
+ let sep = buffer.indexOf("\n\n");
520
+ while (sep !== -1) {
521
+ const raw = buffer.slice(0, sep);
522
+ buffer = buffer.slice(sep + 2);
523
+ const evt = parseSSERecord(raw);
524
+ if (evt) yield evt;
525
+ sep = buffer.indexOf("\n\n");
526
+ }
527
+ }
528
+ const remaining = buffer.trim();
529
+ if (remaining.length > 0) {
530
+ const evt = parseSSERecord(remaining);
531
+ if (evt) yield evt;
532
+ }
533
+ } finally {
534
+ reader.releaseLock();
535
+ }
536
+ }
537
+ function parseSSERecord(raw) {
538
+ let event = "";
539
+ let data = "";
540
+ for (const line of raw.split("\n")) {
541
+ if (line.startsWith("event:")) {
542
+ event = line.slice(6).trim();
543
+ } else if (line.startsWith("data:")) {
544
+ data = data.length > 0 ? `${data}
545
+ ${line.slice(5).trim()}` : line.slice(5).trim();
546
+ }
547
+ }
548
+ if (!event) return null;
549
+ return { event, data };
550
+ }
84
551
 
85
552
  // src/client.ts
86
553
  function resolveConfig(userConfig, fetched) {
@@ -95,7 +562,8 @@ function resolveConfig(userConfig, fetched) {
95
562
  welcomeMessage: userConfig.welcomeMessage ?? fetched?.welcomeMessage ?? DEFAULTS.welcomeMessage,
96
563
  title: userConfig.title ?? fetched?.title ?? DEFAULTS.title,
97
564
  avatarUrl: userConfig.avatarUrl ?? fetched?.avatarUrl,
98
- suggestedMessages: userConfig.suggestedMessages ?? fetched?.suggestedMessages ?? []
565
+ suggestedMessages: userConfig.suggestedMessages ?? fetched?.suggestedMessages ?? [],
566
+ stringOverrides: fetched?.stringOverrides
99
567
  };
100
568
  }
101
569
  function getStorage() {
@@ -111,12 +579,18 @@ var CustomerHeroChat = class {
111
579
  storage;
112
580
  userConfig;
113
581
  identityData = null;
582
+ // `t` is mutable: it gets rebuilt when locale changes or when the fetched
583
+ // widget config delivers `stringOverrides`. The React layer reads this
584
+ // property directly each render rather than caching it in a state snapshot,
585
+ // so a `setLocale` call propagates through `useSyncExternalStore` via the
586
+ // accompanying `setState({ locale, isRtl })` notification.
114
587
  t;
115
588
  constructor(config) {
116
589
  this.userConfig = config;
117
590
  this.storage = getStorage();
118
- this.t = createTranslate(config.locale);
591
+ const locale = detectLocale(config.locale);
119
592
  const resolved = resolveConfig(config);
593
+ this.t = createTranslator(locale, resolved.stringOverrides);
120
594
  const storedConvId = this.storage?.getItem(`ch_conv_${config.chatbotId}`);
121
595
  this.state = {
122
596
  messages: [],
@@ -127,9 +601,26 @@ var CustomerHeroChat = class {
127
601
  configLoaded: false,
128
602
  configError: null,
129
603
  error: null,
130
- identity: null
604
+ identity: null,
605
+ locale,
606
+ isRtl: isRtlLocale(locale)
131
607
  };
132
608
  }
609
+ // Switch the active locale at runtime. No-op when the resolved tag matches
610
+ // the current locale and `stringOverrides` is unchanged. Subscribers get a
611
+ // single state notification with the new `locale` / `isRtl`.
612
+ setLocale(tag) {
613
+ const next = detectLocale(tag);
614
+ if (next === this.state.locale) return;
615
+ this.t = createTranslator(next, this.state.config.stringOverrides);
616
+ this.setState({ locale: next, isRtl: isRtlLocale(next) });
617
+ }
618
+ rebuildTranslator() {
619
+ this.t = createTranslator(
620
+ this.state.locale,
621
+ this.state.config.stringOverrides
622
+ );
623
+ }
133
624
  subscribe(listener) {
134
625
  this.listeners.add(listener);
135
626
  return () => {
@@ -143,6 +634,18 @@ var CustomerHeroChat = class {
143
634
  this.state = { ...this.state, ...partial };
144
635
  this.notifyListeners();
145
636
  }
637
+ // Mutate the last message in place and notify. Used during streaming so
638
+ // listeners see tokens land without allocating a new messages array per
639
+ // token. The array itself is still replaced so consumers using
640
+ // structural equality (React's useSyncExternalStore) see a new reference.
641
+ patchLastMessage(patch) {
642
+ const { messages } = this.state;
643
+ if (messages.length === 0) return;
644
+ const next = messages.slice();
645
+ const last = next[next.length - 1];
646
+ next[next.length - 1] = { ...last, ...patch };
647
+ this.setState({ messages: next });
648
+ }
146
649
  notifyListeners() {
147
650
  for (const listener of this.listeners) {
148
651
  listener(this.state);
@@ -159,6 +662,7 @@ var CustomerHeroChat = class {
159
662
  const fetched = await response.json();
160
663
  const resolved = resolveConfig(this.userConfig, fetched);
161
664
  this.setState({ config: resolved, configLoaded: true });
665
+ if (resolved.stringOverrides) this.rebuildTranslator();
162
666
  } catch (error) {
163
667
  const errorMsg = error instanceof Error ? error.message : "Failed to load widget config";
164
668
  console.error("CustomerHero: Failed to fetch widget config", error);
@@ -186,68 +690,322 @@ var CustomerHeroChat = class {
186
690
  return;
187
691
  }
188
692
  const data = await response.json();
189
- const messages = (data.messages ?? []).map(
190
- (m) => ({
191
- id: m.id,
192
- role: m.role,
193
- content: m.content
194
- })
693
+ const raw = data.messages ?? [];
694
+ const messages = raw.map((m) => ({
695
+ id: m.id,
696
+ role: m.role,
697
+ content: m.content,
698
+ ...m.sources ? { sources: m.sources } : {},
699
+ ...m.blocks ? { blocks: m.blocks } : {},
700
+ ...m.suggestions ? { suggestions: m.suggestions } : {}
701
+ }));
702
+ const lastBotIndex = findLastIndex(
703
+ messages,
704
+ (m) => m.role === "bot" && !!m.suggestions?.length
195
705
  );
706
+ for (let i = 0; i < messages.length; i++) {
707
+ if (i !== lastBotIndex && messages[i].suggestions) {
708
+ const { suggestions: _s, ...rest } = messages[i];
709
+ void _s;
710
+ messages[i] = rest;
711
+ }
712
+ }
196
713
  if (messages.length > 0) {
197
714
  this.setState({ messages });
198
715
  }
199
716
  } catch {
200
717
  }
201
718
  }
202
- async sendMessage(message) {
719
+ async sendMessage(message, options) {
203
720
  const trimmed = message.trim();
721
+ const attachmentTokens = options?.attachmentTokens ?? [];
204
722
  if (!trimmed || this.state.isLoading) return;
205
- const userMsg = { role: "user", content: trimmed };
723
+ const userMsg = {
724
+ role: "user",
725
+ content: trimmed,
726
+ status: "sending"
727
+ };
728
+ const cleanedHistory = this.state.messages.map((m) => {
729
+ let next = m;
730
+ if (next.suggestions) next = stripSuggestions(next);
731
+ if (next.blocks?.some((b) => b.type === "action_confirmation")) {
732
+ next = stripActionConfirmationBlocks(next);
733
+ }
734
+ return next;
735
+ });
736
+ const userMsgIndex = cleanedHistory.length;
206
737
  this.setState({
207
- messages: [...this.state.messages, userMsg],
738
+ messages: [...cleanedHistory, userMsg],
208
739
  isLoading: true,
209
740
  error: null
210
741
  });
211
- const { chatbotId } = this.state.config;
212
- const apiBase = this.state.config.apiBase;
742
+ const { chatbotId, apiBase } = this.state.config;
743
+ let botMessageCreated = false;
213
744
  try {
214
745
  const response = await fetch(`${apiBase}/api/chat/${chatbotId}`, {
215
746
  method: "POST",
216
- headers: { "Content-Type": "application/json" },
747
+ headers: {
748
+ "Content-Type": "application/json",
749
+ Accept: "text/event-stream"
750
+ },
217
751
  body: JSON.stringify({
218
752
  message: trimmed,
219
753
  ...this.state.conversationId ? { conversationId: this.state.conversationId } : {},
220
- ...this.identityData ? { identity: this.identityData } : {}
754
+ ...this.identityData ? { identity: this.identityData } : {},
755
+ ...attachmentTokens.length > 0 ? { attachmentTokens } : {}
221
756
  })
222
757
  });
223
758
  if (!response.ok) {
224
- const data2 = await response.json().catch(() => null);
225
- const errorMsg = data2?.error ?? `Request failed: ${response.status}`;
759
+ const data = await response.json().catch(() => null);
760
+ const errorMsg = data?.error ?? `Request failed: ${response.status}`;
226
761
  throw new Error(errorMsg);
227
762
  }
228
- const data = await response.json();
229
- const botMsg = {
230
- id: data.messageId,
231
- role: "bot",
232
- content: data.message
233
- };
234
- const conversationId = data.conversationId ?? this.state.conversationId;
235
- if (conversationId) {
236
- this.storage?.setItem(`ch_conv_${chatbotId}`, conversationId);
763
+ if (!response.body) {
764
+ throw new Error("Empty response body");
237
765
  }
238
- this.setState({
239
- messages: [...this.state.messages, botMsg],
240
- isLoading: false,
241
- conversationId
242
- });
766
+ let fullContent = "";
767
+ let messageId;
768
+ for await (const evt of readSSEStream(response.body)) {
769
+ switch (evt.event) {
770
+ case "metadata": {
771
+ const meta = safeParse(evt.data);
772
+ if (meta?.conversationId) {
773
+ this.storage?.setItem(
774
+ `ch_conv_${chatbotId}`,
775
+ meta.conversationId
776
+ );
777
+ this.setState({ conversationId: meta.conversationId });
778
+ }
779
+ this.patchMessageAt(userMsgIndex, { status: "sent" });
780
+ if (meta?.messageId) {
781
+ messageId = meta.messageId;
782
+ }
783
+ break;
784
+ }
785
+ case "token": {
786
+ const tok = safeParse(evt.data);
787
+ const text = tok?.text ?? "";
788
+ fullContent += text;
789
+ if (!botMessageCreated) {
790
+ const botMsg = {
791
+ id: messageId,
792
+ role: "bot",
793
+ content: fullContent,
794
+ streaming: true
795
+ };
796
+ this.setState({
797
+ messages: [...this.state.messages, botMsg]
798
+ });
799
+ botMessageCreated = true;
800
+ } else {
801
+ this.patchLastMessage({ content: fullContent });
802
+ }
803
+ break;
804
+ }
805
+ case "sources": {
806
+ const payload = safeParse(evt.data);
807
+ if (payload?.sources?.length && botMessageCreated) {
808
+ this.patchLastMessage({ sources: payload.sources });
809
+ }
810
+ break;
811
+ }
812
+ case "block": {
813
+ const payload = safeParse(evt.data);
814
+ if (payload?.block && botMessageCreated) {
815
+ const existing = this.state.messages.at(-1)?.blocks ?? [];
816
+ this.patchLastMessage({
817
+ blocks: [...existing, payload.block]
818
+ });
819
+ }
820
+ break;
821
+ }
822
+ case "suggestions": {
823
+ const payload = safeParse(evt.data);
824
+ if (payload?.suggestions?.length && botMessageCreated) {
825
+ this.patchLastMessage({ suggestions: payload.suggestions });
826
+ }
827
+ break;
828
+ }
829
+ case "done": {
830
+ if (botMessageCreated) {
831
+ this.patchLastMessage({
832
+ id: messageId,
833
+ streaming: false
834
+ });
835
+ }
836
+ break;
837
+ }
838
+ case "error": {
839
+ const payload = safeParse(evt.data);
840
+ throw new Error(payload?.error ?? "Stream failed");
841
+ }
842
+ }
843
+ }
844
+ if (!botMessageCreated && !fullContent) {
845
+ }
846
+ this.setState({ isLoading: false });
243
847
  } catch (error) {
244
848
  const errorMsg = error instanceof Error ? error.message : "Something went wrong";
849
+ if (botMessageCreated) {
850
+ this.patchLastMessage({ streaming: false });
851
+ }
852
+ const userStatus = this.state.messages[userMsgIndex]?.status;
853
+ if (userStatus !== "sent") {
854
+ this.patchMessageAt(userMsgIndex, { status: "failed" });
855
+ }
245
856
  this.setState({
246
857
  isLoading: false,
247
858
  error: errorMsg
248
859
  });
249
860
  }
250
861
  }
862
+ // Upload a screenshot blob to the public attachments endpoint and return
863
+ // the token the caller should pass to `sendMessage(text, { attachmentTokens })`.
864
+ async uploadAttachment(blob, options) {
865
+ const { chatbotId, apiBase } = this.state.config;
866
+ const filename = options?.filename ?? `screenshot.${pickExtension(blob.type)}`;
867
+ const form = new FormData();
868
+ form.append("file", blob, filename);
869
+ const response = await fetch(
870
+ `${apiBase}/api/chat/${chatbotId}/attachments`,
871
+ { method: "POST", body: form }
872
+ );
873
+ if (!response.ok) {
874
+ const data = await response.json().catch(() => null);
875
+ const errorMsg = data?.error ?? `Upload failed: ${response.status}`;
876
+ throw new Error(errorMsg);
877
+ }
878
+ const json = await response.json();
879
+ return json;
880
+ }
881
+ approveAction(pendingId) {
882
+ return this.sendDecision(pendingId, "approve");
883
+ }
884
+ cancelAction(pendingId) {
885
+ return this.sendDecision(pendingId, "cancel");
886
+ }
887
+ // Locate the bot bubble that carries the action_confirmation block for
888
+ // `pendingId`, strip the block, mark the bubble streaming, then POST the
889
+ // decision and stream tokens back into the same bubble.
890
+ async sendDecision(pendingId, decision) {
891
+ const targetIndex = this.findActionConfirmationMessageIndex(pendingId);
892
+ if (targetIndex === -1) {
893
+ this.setState({ error: this.t("action_already_resolved") });
894
+ await this.loadHistory();
895
+ return;
896
+ }
897
+ const messages = this.state.messages.slice();
898
+ const original = messages[targetIndex];
899
+ messages[targetIndex] = {
900
+ ...stripActionConfirmationBlocks(original),
901
+ streaming: true
902
+ };
903
+ this.setState({ messages, error: null });
904
+ const { chatbotId, apiBase } = this.state.config;
905
+ const url = `${apiBase}/api/chat/${chatbotId}/tool-calls/${pendingId}/decision`;
906
+ try {
907
+ const response = await fetch(url, {
908
+ method: "POST",
909
+ headers: {
910
+ "Content-Type": "application/json",
911
+ Accept: "text/event-stream"
912
+ },
913
+ body: JSON.stringify({
914
+ decision,
915
+ ...this.identityData ? { identity: this.identityData } : {}
916
+ })
917
+ });
918
+ if (!response.ok) {
919
+ const data = await response.json().catch(() => null);
920
+ const errorMsg = data?.error ?? `Request failed: ${response.status}`;
921
+ throw new Error(errorMsg);
922
+ }
923
+ if (!response.body) throw new Error("Empty response body");
924
+ for await (const evt of readSSEStream(response.body)) {
925
+ switch (evt.event) {
926
+ case "metadata": {
927
+ const meta = safeParse(evt.data);
928
+ if (meta?.conversationId) {
929
+ this.storage?.setItem(
930
+ `ch_conv_${chatbotId}`,
931
+ meta.conversationId
932
+ );
933
+ this.setState({ conversationId: meta.conversationId });
934
+ }
935
+ break;
936
+ }
937
+ case "token": {
938
+ const tok = safeParse(evt.data);
939
+ const text = tok?.text ?? "";
940
+ if (text) {
941
+ this.appendToMessageAt(targetIndex, text);
942
+ }
943
+ break;
944
+ }
945
+ case "block": {
946
+ const payload = safeParse(evt.data);
947
+ if (payload?.block) {
948
+ this.appendBlockToMessageAt(targetIndex, payload.block);
949
+ }
950
+ break;
951
+ }
952
+ case "done": {
953
+ this.patchMessageAt(targetIndex, { streaming: false });
954
+ break;
955
+ }
956
+ case "error": {
957
+ const payload = safeParse(
958
+ evt.data
959
+ );
960
+ if (payload?.kind === "already_resolved") {
961
+ this.patchMessageAt(targetIndex, { streaming: false });
962
+ this.setState({ error: this.t("action_already_resolved") });
963
+ await this.loadHistory();
964
+ return;
965
+ }
966
+ throw new Error(payload?.error ?? "Stream failed");
967
+ }
968
+ }
969
+ }
970
+ } catch (error) {
971
+ const errorMsg = error instanceof Error ? error.message : this.t("action_failed");
972
+ this.patchMessageAt(targetIndex, { streaming: false });
973
+ this.setState({ error: errorMsg });
974
+ }
975
+ }
976
+ findActionConfirmationMessageIndex(pendingId) {
977
+ const { messages } = this.state;
978
+ for (let i = messages.length - 1; i >= 0; i--) {
979
+ const blocks = messages[i].blocks;
980
+ if (blocks?.some(
981
+ (b) => b.type === "action_confirmation" && b.pendingToolCallId === pendingId
982
+ )) {
983
+ return i;
984
+ }
985
+ }
986
+ return -1;
987
+ }
988
+ patchMessageAt(index, patch) {
989
+ const messages = this.state.messages.slice();
990
+ if (!messages[index]) return;
991
+ messages[index] = { ...messages[index], ...patch };
992
+ this.setState({ messages });
993
+ }
994
+ appendToMessageAt(index, text) {
995
+ const messages = this.state.messages.slice();
996
+ const target = messages[index];
997
+ if (!target) return;
998
+ messages[index] = { ...target, content: target.content + text };
999
+ this.setState({ messages });
1000
+ }
1001
+ appendBlockToMessageAt(index, block) {
1002
+ const messages = this.state.messages.slice();
1003
+ const target = messages[index];
1004
+ if (!target) return;
1005
+ const existing = target.blocks ?? [];
1006
+ messages[index] = { ...target, blocks: [...existing, block] };
1007
+ this.setState({ messages });
1008
+ }
251
1009
  async rateMessage(messageId, rating) {
252
1010
  const { chatbotId, apiBase } = this.state.config;
253
1011
  const { conversationId } = this.state;
@@ -317,8 +1075,186 @@ var CustomerHeroChat = class {
317
1075
  });
318
1076
  }
319
1077
  };
1078
+ function safeParse(data) {
1079
+ try {
1080
+ return JSON.parse(data);
1081
+ } catch {
1082
+ return null;
1083
+ }
1084
+ }
1085
+ function stripSuggestions(message) {
1086
+ const { suggestions: _s, ...rest } = message;
1087
+ void _s;
1088
+ return rest;
1089
+ }
1090
+ function stripActionConfirmationBlocks(message) {
1091
+ if (!message.blocks?.length) return message;
1092
+ const blocks = message.blocks.filter(
1093
+ (b) => b.type !== "action_confirmation"
1094
+ );
1095
+ if (blocks.length === message.blocks.length) return message;
1096
+ if (blocks.length === 0) {
1097
+ const { blocks: _b, ...rest } = message;
1098
+ void _b;
1099
+ return rest;
1100
+ }
1101
+ return { ...message, blocks };
1102
+ }
1103
+ function findLastIndex(arr, pred) {
1104
+ for (let i = arr.length - 1; i >= 0; i--) {
1105
+ if (pred(arr[i])) return i;
1106
+ }
1107
+ return -1;
1108
+ }
1109
+ function pickExtension(mime) {
1110
+ if (mime === "image/png") return "png";
1111
+ if (mime === "image/webp") return "webp";
1112
+ return "jpg";
1113
+ }
1114
+
1115
+ // src/screenshot.ts
1116
+ var ScreenshotCancelled = class extends Error {
1117
+ constructor(message = "Screenshot cancelled") {
1118
+ super(message);
1119
+ this.name = "ScreenshotCancelled";
1120
+ }
1121
+ };
1122
+ var ScreenshotUnavailable = class extends Error {
1123
+ constructor(message = "Screenshot unavailable") {
1124
+ super(message);
1125
+ this.name = "ScreenshotUnavailable";
1126
+ }
1127
+ };
1128
+ var MAX_DIMENSION = 2048;
1129
+ var SIZE_BUDGET = 5 * 1024 * 1024;
1130
+ function canCaptureScreenshot() {
1131
+ if (typeof navigator === "undefined" || typeof window === "undefined") {
1132
+ return false;
1133
+ }
1134
+ if (!navigator.mediaDevices?.getDisplayMedia) return false;
1135
+ if (typeof window.matchMedia !== "function") return false;
1136
+ return window.matchMedia("(pointer: fine)").matches;
1137
+ }
1138
+ async function captureScreenshot() {
1139
+ if (typeof navigator === "undefined" || !navigator.mediaDevices) {
1140
+ throw new ScreenshotUnavailable("Screen capture not supported");
1141
+ }
1142
+ let stream;
1143
+ try {
1144
+ stream = await navigator.mediaDevices.getDisplayMedia({
1145
+ video: true,
1146
+ audio: false
1147
+ });
1148
+ } catch (e) {
1149
+ if (e instanceof Error && (e.name === "NotAllowedError" || e.name === "AbortError")) {
1150
+ throw new ScreenshotCancelled(e.message);
1151
+ }
1152
+ const msg = e instanceof Error ? e.message : "Screen capture failed";
1153
+ throw new ScreenshotUnavailable(msg);
1154
+ }
1155
+ try {
1156
+ const bitmap = await grabFrame(stream);
1157
+ return await encodeJpeg(bitmap);
1158
+ } finally {
1159
+ for (const track of stream.getTracks()) track.stop();
1160
+ }
1161
+ }
1162
+ async function grabFrame(stream) {
1163
+ const [track] = stream.getVideoTracks();
1164
+ if (!track) throw new ScreenshotUnavailable("No video track");
1165
+ const ImageCaptureCtor = globalThis.ImageCapture;
1166
+ if (ImageCaptureCtor) {
1167
+ try {
1168
+ const cap = new ImageCaptureCtor(track);
1169
+ const bitmap = await cap.grabFrame();
1170
+ if (bitmap.width > 0 && bitmap.height > 0) return bitmap;
1171
+ } catch {
1172
+ }
1173
+ }
1174
+ return await grabFrameFromVideo(stream);
1175
+ }
1176
+ async function grabFrameFromVideo(stream) {
1177
+ const video = document.createElement("video");
1178
+ video.autoplay = true;
1179
+ video.muted = true;
1180
+ video.playsInline = true;
1181
+ video.srcObject = stream;
1182
+ try {
1183
+ await new Promise((resolve, reject) => {
1184
+ const onReady = () => {
1185
+ video.removeEventListener("loadedmetadata", onReady);
1186
+ resolve();
1187
+ };
1188
+ const onError = () => {
1189
+ video.removeEventListener("error", onError);
1190
+ reject(new ScreenshotUnavailable("Video element failed"));
1191
+ };
1192
+ video.addEventListener("loadedmetadata", onReady);
1193
+ video.addEventListener("error", onError);
1194
+ });
1195
+ await video.play().catch(() => {
1196
+ });
1197
+ const w = video.videoWidth;
1198
+ const h = video.videoHeight;
1199
+ if (w === 0 || h === 0) throw new ScreenshotUnavailable("Empty frame");
1200
+ const canvas = document.createElement("canvas");
1201
+ canvas.width = w;
1202
+ canvas.height = h;
1203
+ const ctx = canvas.getContext("2d");
1204
+ if (!ctx) throw new ScreenshotUnavailable("No 2D context");
1205
+ ctx.drawImage(video, 0, 0, w, h);
1206
+ return await createImageBitmap(canvas);
1207
+ } finally {
1208
+ video.srcObject = null;
1209
+ }
1210
+ }
1211
+ async function encodeJpeg(bitmap) {
1212
+ const { width, height } = scaleToFit(
1213
+ bitmap.width,
1214
+ bitmap.height,
1215
+ MAX_DIMENSION
1216
+ );
1217
+ const canvas = typeof OffscreenCanvas !== "undefined" ? new OffscreenCanvas(width, height) : Object.assign(document.createElement("canvas"), { width, height });
1218
+ const ctx = canvas.getContext("2d");
1219
+ if (!ctx) throw new ScreenshotUnavailable("No 2D context");
1220
+ ctx.drawImage(bitmap, 0, 0, width, height);
1221
+ bitmap.close?.();
1222
+ const blob = await canvasToBlob(canvas, 0.85);
1223
+ if (blob.size <= SIZE_BUDGET) return blob;
1224
+ const slim = await canvasToBlob(canvas, 0.7);
1225
+ return slim;
1226
+ }
1227
+ function scaleToFit(w, h, max) {
1228
+ if (w <= max && h <= max) return { width: w, height: h };
1229
+ const ratio = w >= h ? max / w : max / h;
1230
+ return { width: Math.round(w * ratio), height: Math.round(h * ratio) };
1231
+ }
1232
+ async function canvasToBlob(canvas, quality) {
1233
+ if ("convertToBlob" in canvas) {
1234
+ return await canvas.convertToBlob({
1235
+ type: "image/jpeg",
1236
+ quality
1237
+ });
1238
+ }
1239
+ return await new Promise((resolve, reject) => {
1240
+ canvas.toBlob(
1241
+ (b) => b ? resolve(b) : reject(new ScreenshotUnavailable("Encode failed")),
1242
+ "image/jpeg",
1243
+ quality
1244
+ );
1245
+ });
1246
+ }
320
1247
  // Annotate the CommonJS export names for ESM import in node:
321
1248
  0 && (module.exports = {
322
1249
  CustomerHeroChat,
323
- DEFAULTS
1250
+ DEFAULTS,
1251
+ SUPPORTED_LOCALES,
1252
+ ScreenshotCancelled,
1253
+ ScreenshotUnavailable,
1254
+ canCaptureScreenshot,
1255
+ captureScreenshot,
1256
+ createTranslator,
1257
+ detectLocale,
1258
+ isRtlLocale,
1259
+ resolveLocale
324
1260
  });