@ada-cx/messaging-bridge 1.0.0-setup.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.
@@ -0,0 +1,849 @@
1
+ import type { CsatFeedbackOption, CsatSurveySettings, FileUploadErrorData } from "./shared-utils/index.js";
2
+ /**
3
+ * Bridge type definitions describing the state and events that flow between
4
+ * the messaging core frame and the display layer (app or native WebView).
5
+ *
6
+ * These types are the source of truth for the `@ada-cx/messaging-bridge`
7
+ * package and must stay in sync with the AsyncAPI spec at asyncapi.yml.
8
+ */
9
+ export interface FallbackUiLink {
10
+ label: string;
11
+ url: string;
12
+ }
13
+ export type LocalizedFallbackUiMessage = string | Record<string, string>;
14
+ export type LocalizedFallbackUiLinks = FallbackUiLink[] | Record<string, FallbackUiLink[]>;
15
+ export interface FallbackUiConfig {
16
+ icon: {
17
+ isDefault: boolean;
18
+ image: string | null;
19
+ };
20
+ message: LocalizedFallbackUiMessage;
21
+ links: LocalizedFallbackUiLinks;
22
+ }
23
+ export interface SamlLoginButtonConfig {
24
+ backgroundColor: string | null;
25
+ fontFamily: string | null;
26
+ header: string | null;
27
+ headerMessage: string | null;
28
+ title: string | null;
29
+ text: string | null;
30
+ }
31
+ export type { CsatFeedbackOption, CsatSurveySettings };
32
+ export interface CsatSubmitData {
33
+ score?: number;
34
+ style?: string;
35
+ feedback?: string[];
36
+ resolved?: boolean;
37
+ comment?: string;
38
+ customerEffortScore?: number;
39
+ netPromoterScore?: number;
40
+ }
41
+ export interface CaptureData {
42
+ placeholder: string;
43
+ validationErrorMessage: string;
44
+ isOptional: boolean;
45
+ cancelCaptureResponseId: string;
46
+ cancelCaptureLabel: string;
47
+ dataId: string;
48
+ /** True while a submit for this capture is in flight. Owned by core, so a
49
+ * replayed or reordered capture-state event can never re-enable a field whose
50
+ * submit has not resolved. Optional because a native host may hydrate a snapshot
51
+ * cached before this field existed; absent reads as "not submitting". */
52
+ isSubmitting?: boolean;
53
+ /**
54
+ * The latest submit for this capture that core failed or dropped WITHOUT a
55
+ * server validation verdict: no session to round-trip, an over-length
56
+ * value, a transport failure, a duplicate submit while one is in flight,
57
+ * or a host-driven reset/engagement gate. `submitId` echoes the key the
58
+ * submitter sent with `capture.submit` (null when it sent none), so
59
+ * `operations.submitCapture` handles settle only on their OWN submit's
60
+ * failure. Server validation rejections ride `validationErrorMessage`
61
+ * instead. Optional: absent on a core document that predates it — those
62
+ * cores report these failures nowhere capture-scoped, so pass `timeoutMs`.
63
+ */
64
+ lastSubmitFailure?: {
65
+ submitId: string | null;
66
+ message: string;
67
+ } | null;
68
+ }
69
+ export interface ListSelectionData {
70
+ cancelCaptureResponseId: string;
71
+ cancelCaptureLabel: string;
72
+ isOptional: boolean;
73
+ showExitListOptionBlock: boolean;
74
+ dataId: string;
75
+ }
76
+ /**
77
+ * `waiting_for_data`: the bot is blocked on an external system and api asked the
78
+ * client to say so. Mirrors core's `conversation.stateData.liveChat` payload.
79
+ */
80
+ export interface LiveChatStateData {
81
+ notificationMessage: string;
82
+ notificationMessageSubtext: string;
83
+ showBanner: boolean;
84
+ timeoutAfterMs: string;
85
+ }
86
+ interface BaseMessage {
87
+ id: string;
88
+ sender: "user" | "bot" | "agent";
89
+ timestamp: number;
90
+ body: string;
91
+ /** The conversation this message belongs to. Stamped on creation/ingest, and
92
+ * backfilled once the id exists. Core no longer reads it directly — End Chat
93
+ * availability moved to the `chat.userHasSentInCurrentConversation` flag (EXP-380).
94
+ * Mirrors core's `BaseMessage` (kept in sync by hand). */
95
+ conversationId?: string;
96
+ reviewable?: boolean;
97
+ /** Persisted response-review vote restored from the conversation log. */
98
+ reaction?: 1 | -1;
99
+ /** The message's durable `_id` cursor (ObjectId string): the loss-tolerant
100
+ * read watermark key. Absent on optimistic bubbles / non-durable frames. */
101
+ cursor?: string;
102
+ streamId?: string;
103
+ isStreaming?: boolean;
104
+ /** Stable render key that survives the optimistic→durable id swap, so a
105
+ * reconciled user message's bubble does not replay its entrance animation. */
106
+ clientKey?: string;
107
+ /** The live agent who authored this message, stamped by `toBotMessage` on both the
108
+ * live and history-restore paths. Preferred over `agent.activeAgent` when naming a
109
+ * sender — restored history outlives an agent's session.
110
+ * Mirrors core's `BaseMessage` (kept in sync by hand). */
111
+ agentInfo?: AgentInfo;
112
+ /** A client-side send failed; retry eligibility is reported separately. */
113
+ deliveryStatus?: "failed";
114
+ /** Core still owns the trusted send envelope needed to retry this failure. */
115
+ isRetryable?: boolean;
116
+ }
117
+ export interface TextMessage extends BaseMessage {
118
+ type: "text";
119
+ attachments?: Array<{
120
+ url: string;
121
+ filename: string;
122
+ mimeType: string;
123
+ }>;
124
+ }
125
+ export interface QuickReply {
126
+ label: string;
127
+ /** Empty on an `accepted_value` chip — there is no Response behind it. */
128
+ target: string;
129
+ /** `accepted_value` sends the visible label as a plain user utterance instead
130
+ * of triggering a Response; the Ask step resolves it to the accepted value. */
131
+ buttonType: "suggestion" | "switch" | "accepted_value";
132
+ /** Raw accepted value, used as the utterance only when the label is absent. */
133
+ value?: string;
134
+ }
135
+ export interface QuickRepliesMessage extends BaseMessage {
136
+ type: "quick_replies";
137
+ quickReplies: QuickReply[];
138
+ isForced: boolean;
139
+ /** Chip layout authored on the playbook Ask step. Overrides the bot-wide
140
+ * `config.buttonLayout*` on both viewports when present. */
141
+ buttonLayout?: "stacked" | "carousel";
142
+ /** Authored on the Ask step: true shows every chip, false collapses past
143
+ * `MAX_COLLAPSED_QUICK_REPLIES`. Undefined inherits the bot-wide setting. */
144
+ showAll?: boolean;
145
+ }
146
+ export interface LinkMessage extends BaseMessage {
147
+ type: "link";
148
+ url: string;
149
+ linkText: string;
150
+ title: string;
151
+ linkDescription: string;
152
+ linkIcon: string;
153
+ altText: string;
154
+ newWindow: boolean;
155
+ articleId: string | null;
156
+ /** True when this normalized link originated as legacy's sized-popup block. */
157
+ isWebWindow?: boolean;
158
+ windowHeight?: number;
159
+ windowWidth?: number;
160
+ }
161
+ export interface PictureMessage extends BaseMessage {
162
+ type: "picture";
163
+ picUrl: string;
164
+ altText: string;
165
+ }
166
+ export interface VideoMessage extends BaseMessage {
167
+ type: "video";
168
+ vidUrl: string;
169
+ altText: string;
170
+ }
171
+ export interface DownloadableMessage extends BaseMessage {
172
+ type: "downloadable";
173
+ url: string;
174
+ name: string;
175
+ mimeType: string | null;
176
+ }
177
+ export interface AgentInfo {
178
+ name: string;
179
+ imageUrl: string | null;
180
+ internalId: string | null;
181
+ defaultImage: boolean;
182
+ }
183
+ export interface PresenceMessage extends BaseMessage {
184
+ type: "presence";
185
+ event: string;
186
+ agentInfo?: AgentInfo;
187
+ }
188
+ export interface OptionItem {
189
+ id: string;
190
+ label: string;
191
+ description?: string;
192
+ datetime?: string;
193
+ }
194
+ export interface OptionsMessage extends BaseMessage {
195
+ type: "options_message";
196
+ options: OptionItem[];
197
+ }
198
+ export interface SelectableListItem {
199
+ id: string;
200
+ text: string;
201
+ }
202
+ export interface SelectableListMessage extends BaseMessage {
203
+ type: "surfaceable_list_selection";
204
+ variableId: string;
205
+ selectables: SelectableListItem[];
206
+ multiple: boolean;
207
+ prompt: string;
208
+ }
209
+ export interface QueueBotBlockMessage extends BaseMessage {
210
+ type: "queue_bot_block";
211
+ queuePosition?: number | null;
212
+ waitMessage?: string;
213
+ platform?: string;
214
+ }
215
+ export interface WidgetMessage extends BaseMessage {
216
+ type: "widget";
217
+ widgetName: string;
218
+ ariaLabel: string;
219
+ widgetUrl: string;
220
+ linkText: string;
221
+ linkSubtext: string;
222
+ inlineInChat: boolean;
223
+ }
224
+ export interface SignInMessage extends BaseMessage {
225
+ type: "sign_in";
226
+ authButtonLabel: string;
227
+ authLink: string;
228
+ authProvider: string;
229
+ authLinkExpired: boolean;
230
+ messageLead: string;
231
+ }
232
+ /**
233
+ * Silent OAuth. api emits this when a Sign In block resolves to a provider the
234
+ * chatter is already authenticated with in this browser.
235
+ *
236
+ * There is no visible UI — the row loads `authLink` in a hidden iframe so the
237
+ * provider's existing session cookie completes the flow and api resumes the
238
+ * conversation on its own.
239
+ */
240
+ export interface SeamlessOAuthMessage extends BaseMessage {
241
+ type: "seamless_oauth";
242
+ authLink: string;
243
+ authProvider: string;
244
+ authLinkExpired: boolean;
245
+ /** Response the bot falls back to if the silent flow fails. */
246
+ errorResponseId: string;
247
+ }
248
+ /**
249
+ * Web Push opt-in prompt, emitted from a Notification block during a live-agent
250
+ * handoff so the chatter can be told when an agent replies after tabbing away.
251
+ */
252
+ export interface NotificationPermissionMessage extends BaseMessage {
253
+ type: "local_notification_permission";
254
+ /** Prompt text authored in the Notification block. */
255
+ body: string;
256
+ }
257
+ export type CsatScaleType = "thumbs" | "emoji" | "1-5" | "1-7" | "0-10" | "1-10";
258
+ export interface CsatMessage extends BaseMessage {
259
+ type: "csat";
260
+ surveyType: string;
261
+ scaleType: CsatScaleType;
262
+ score: number | null;
263
+ submitted: boolean;
264
+ feedback?: string[];
265
+ resolved?: boolean | null;
266
+ comment?: string | null;
267
+ customerEffortScore?: number | null;
268
+ netPromoterScore?: number | null;
269
+ isPositive?: boolean | null;
270
+ }
271
+ export type Message = TextMessage | QuickRepliesMessage | LinkMessage | PictureMessage | VideoMessage | DownloadableMessage | PresenceMessage | QueueBotBlockMessage | SelectableListMessage | OptionsMessage | NotificationPermissionMessage | SeamlessOAuthMessage | SignInMessage | WidgetMessage | CsatMessage;
272
+ /** Native SDK wrapper hosting the widget; null on the web. */
273
+ export type SdkPlatform = "IOS" | "ANDROID" | "REACTNATIVE" | null;
274
+ /**
275
+ * The derived display state core publishes to the app frame on every
276
+ * `core.state.update`. Keys are dotted strings grouped by prefix:
277
+ *
278
+ * - `app.*` — frame lifecycle: loading, initialized, session id.
279
+ * - `config.*` — bot configuration and theming resolved from client config.
280
+ * - `appearance.*` — user-chosen in-widget theme/text-size overrides.
281
+ * - `saml.*` — SAML sign-in gate state and login-button styling.
282
+ * - `transcript.*` — email/download transcript request status.
283
+ * - `connection.*` — realtime transport connectivity.
284
+ * - `chat.*` — the conversation: messages, cursors, composer, send status.
285
+ * - `conversation.*` — server conversation-state blocks (capture, list
286
+ * selection, live-chat wait states).
287
+ * - `liveAgent.*` / `agent.*` — handoff lifecycle, queue position, and the
288
+ * agents present in the conversation.
289
+ * - `zendesk.*` / `zendeskMessaging.*` — Zendesk handoff SDK status.
290
+ * - `file.*` — handoff file-upload availability, progress, and errors.
291
+ * - `outage.*` — response-delay and connectivity-loss banners.
292
+ * - `error.*` — fatal error surface.
293
+ * - `chatter.*` — end-user preferences: language, notifications, translations.
294
+ * - `ui.*` — transient UI signals (typing indicators, dialogs, toasts).
295
+ * - `voice.*` — Web Speech voice input/TTS state.
296
+ * - `csat.*` / `endChat.*` — survey settings, eligibility, and End Chat gates.
297
+ * - `security.*` — Turnstile challenge state.
298
+ *
299
+ * Read values by key, or use the `STATE` constants for typo-safe access. This
300
+ * contract is shared with `packages/core` and must stay in sync with it.
301
+ *
302
+ * Update cadence: core coalesces to at most ONE `core.state.update` per
303
+ * microtask, and skips the post entirely when the derived state is unchanged.
304
+ * Unchanged keys keep their reference identity across updates, so `Object.is`
305
+ * per key is a sound change detector (see `BridgeClient.subscribeKey`) — the
306
+ * exception is `chat.messages`, whose array identity changes on every
307
+ * streaming delta.
308
+ */
309
+ export interface AppDisplayState {
310
+ "app.loading": boolean;
311
+ "app.initialized": boolean;
312
+ "app.testModeBannerLabel"?: string | null;
313
+ "app.sessionId": string;
314
+ "config.handle": string;
315
+ "config.features": Record<string, boolean>;
316
+ "config.clientDefaultLanguage": string;
317
+ "config.translatedLanguages": string[];
318
+ "config.emailTranscriptEnabled": boolean;
319
+ "config.downloadTranscriptEnabled": boolean;
320
+ "config.sdkPlatform": SdkPlatform;
321
+ "config.sdkSupportsDownloadLink": boolean;
322
+ "config.showBranding": boolean;
323
+ /** Reseller/partner name appended after the Ada logomark in the footer. */
324
+ "config.adaPartnerName": string | null;
325
+ "config.botName": string | null;
326
+ "config.botDescription": string | null;
327
+ "config.botDescriptionEnabled": boolean;
328
+ "config.botAvatarUrl": string | null;
329
+ "config.tintColor": string | null;
330
+ "config.style": "round" | "rectangular";
331
+ "config.textSize": "small" | "default" | "large";
332
+ "config.headerColorEnabled": boolean;
333
+ "config.headerColor": string | null;
334
+ "config.headerTextColor": string | null;
335
+ "config.chatEnabled": boolean;
336
+ "config.privacyLink": string | null;
337
+ "config.privacy": boolean;
338
+ "config.theme": "light" | "dark" | "auto";
339
+ "config.advancedColorsEnabled": boolean;
340
+ "config.textOverAccentColor": string | null;
341
+ "config.buttonLayoutMobile": "carousel" | "stacked";
342
+ "config.buttonLayoutDesktop": "carousel" | "stacked";
343
+ "config.showAllQuickReplies": boolean;
344
+ "config.isMobileViewport": boolean;
345
+ "appearance.userTheme": "light" | "dark" | "auto" | null;
346
+ "appearance.userTextSize": "small" | "default" | "large" | null;
347
+ "config.fallbackUi": FallbackUiConfig | null;
348
+ "config.buttonSize": number;
349
+ "config.buttonBackgroundColor": string | null;
350
+ "config.avatar": {
351
+ letter: string;
352
+ image: string | null;
353
+ useLetter: boolean;
354
+ };
355
+ "config.agentAvatar": {
356
+ isDefault: boolean;
357
+ image: string | null;
358
+ };
359
+ "config.allowedProtocols": string[];
360
+ "config.customRedactions": Array<{
361
+ pattern: string;
362
+ replacement?: string;
363
+ }>;
364
+ "saml.locked": boolean;
365
+ "saml.url": string | null;
366
+ "saml.loginButton": SamlLoginButtonConfig;
367
+ /** `idle -> pending` on `settings.transcript.email.send`, then
368
+ * `-> success | error` when the request resolves. Act on the
369
+ * `pending -> terminal` transition, not on the standing value. */
370
+ "transcript.email.status": "idle" | "pending" | "success" | "error";
371
+ /** Advances on EVERY `transcript.email.status` write. A request core drops
372
+ * before it goes `pending` (a host-driven reset in progress) writes its
373
+ * terminal status inside one broadcast batch, so the `pending -> terminal`
374
+ * edge never appears — correlate on this advancing to a terminal status
375
+ * instead. Optional: absent on a core document that predates it. */
376
+ "transcript.email.status.seq"?: number;
377
+ /** Same lifecycle as `transcript.email.status`, for
378
+ * `settings.transcript.download.request`. */
379
+ "transcript.download.status": "idle" | "pending" | "success" | "error";
380
+ /** Same contract as `transcript.email.status.seq`, for downloads. */
381
+ "transcript.download.status.seq"?: number;
382
+ "connection.socket.connected": boolean;
383
+ "connection.pusher.channelConnected": boolean;
384
+ "chat.responseLoading": boolean;
385
+ /** True from the moment a user message lands until the bot's reply arrives
386
+ * (or the send fails). This — not `chat.isSending` — is the "a reply is
387
+ * coming" signal to key composure/typing UI on. */
388
+ "chat.isGenerating": boolean;
389
+ /** Optional while core/app assets roll independently. */
390
+ "chat.isUnifiedReasonerGenerating"?: boolean;
391
+ "chat.isConversationActive"?: boolean;
392
+ "chat.canLoadMore": boolean;
393
+ "chat.loadingPreviousMessages": boolean;
394
+ /** Bumped each time restored/baseline history is (re)loaded; drives the
395
+ * app's one-shot "land at the bottom of restored history" scroll on reload. */
396
+ "chat.recentMessagesLoadSeq": number;
397
+ /** Highest message `_id` cursor the user has read in the current conversation
398
+ * (persisted across reloads); seeds the unread divider + restore scroll. */
399
+ "chat.lastReadCursor": string;
400
+ /** Active conversation id; lets read-tracking reset its per-conversation
401
+ * watermark accumulator (cursor spaces are per conversation). Arrives
402
+ * asynchronously via the realtime channel, NOT with the send response —
403
+ * null until the conversation is established server-side. */
404
+ "chat.conversationId"?: string | null;
405
+ /** Monotonic conversation/reset boundary from core. Optional for compatibility
406
+ * with an older core asset during rollout. */
407
+ "chat.conversationGeneration"?: number;
408
+ "chat.messages.reactions": Record<string, number>;
409
+ /**
410
+ * True while api reports any active conversation state. Optional for rolling
411
+ * compatibility with an older core document; the app defaults it to false.
412
+ */
413
+ "conversation.inState"?: boolean;
414
+ "conversation.stateData.capture": CaptureData | null;
415
+ "conversation.stateData.listSelection": ListSelectionData | null;
416
+ /** `waiting_for_data`: null unless the bot is blocked on an external system. */
417
+ "conversation.stateData.liveChat": LiveChatStateData | null;
418
+ /** The transcript. Updates on any append/upsert/stream delta/reaction/
419
+ * history load; array identity changes on EVERY streaming delta, so
420
+ * derive a signature (or use a custom `equals`) before diffing. An
421
+ * optimistic user row appears here immediately on send; its durable
422
+ * server echo later reconciles IN PLACE, swapping `id` while carrying
423
+ * `clientKey` forward — key on `clientKey ?? id` (see `messageKey`). */
424
+ "chat.messages": Message[];
425
+ /** Rarely observable as `true` on the bot path: it is set and cleared
426
+ * within one synchronous dispatch batch, and only one state update per
427
+ * batch is posted. It stays true only across a genuine failure gap.
428
+ * Watch `chat.isGenerating` for "in flight" UI instead. */
429
+ "chat.isSending": boolean;
430
+ /** Set when a send fails (also clears the sending/generating flags).
431
+ * Cleared ONLY by the `chat.error.dismiss` event — clear it in core
432
+ * rather than hiding it locally, or the session's second error renders
433
+ * nothing. */
434
+ "chat.error": string | null;
435
+ /** Monotonic counter that advances every time core SETS `chat.error` —
436
+ * including when the new text is identical to the current one (the same
437
+ * over-length send repeated back-to-back), which `chat.error` alone cannot
438
+ * surface. Correlate send failures on this advancing and read the text
439
+ * from `chat.error`; `operations.sendMessage` handles do exactly that.
440
+ * Optional: absent on a core document that predates it — fall back to
441
+ * watching `chat.error` transitions. */
442
+ "chat.error.seq"?: number;
443
+ /** The `tempMessageUuid` of the `operations.sendMessage` send that raised
444
+ * `chat.error`, or null when the error is not attributable to one keyed
445
+ * send (capture failures, transcript failures, non-bridge sends). Send
446
+ * handles reject only when the key matches their own, so an unrelated
447
+ * failure in flight never falsely rejects a delivered send. Optional:
448
+ * absent on a core document that predates it — fall back to
449
+ * `chat.error.seq`. */
450
+ "chat.error.clientKey"?: string | null;
451
+ "chat.composerText"?: {
452
+ text: string;
453
+ revision: number;
454
+ } | null;
455
+ /** Interactive blocks (quick replies, options) the chatter has already
456
+ * answered. Includes blocks a later same-conversation user message
457
+ * superseded, so the set survives a reload. Filter interactive rows on
458
+ * it before enabling them. */
459
+ "chat.answeredInteractiveIds": string[];
460
+ /** A playbook step is running, so the composer must refuse to send. */
461
+ "chat.playbookStepExecuting": boolean;
462
+ "liveAgent.inLiveChat"?: boolean;
463
+ "liveAgent.inPending"?: boolean;
464
+ "liveAgent.isActive"?: boolean;
465
+ "liveAgent.hasConversationEnded"?: boolean;
466
+ "liveAgent.conversationEndedByChatter"?: boolean;
467
+ "liveAgent.platform.name"?: string | null;
468
+ "liveAgent.platform.departmentId"?: number | null;
469
+ "liveAgent.queuePosition"?: number | null;
470
+ /** A queue-cancel POST is in flight; the banner's Cancel is disabled while true. */
471
+ "liveAgent.queueCancelPending"?: boolean;
472
+ "liveAgent.handoff.lastEvent"?: {
473
+ userQuestion: string | null;
474
+ transcriptUrl: string | null;
475
+ queueName: string | null;
476
+ lang: string | null;
477
+ variables: Record<string, unknown> | null;
478
+ } | null;
479
+ "liveAgent.handoff.retryRequestedCount"?: number;
480
+ "agent.list"?: Array<{
481
+ id: string;
482
+ name: string;
483
+ imageUrl: string | null;
484
+ defaultImage: boolean;
485
+ isActive: boolean;
486
+ isTyping: boolean;
487
+ }>;
488
+ /** The agent currently active in the conversation, or null. Prefer a
489
+ * message's own `agentInfo` when naming its sender — restored history
490
+ * outlives an agent's session. */
491
+ "agent.activeAgent"?: {
492
+ id: string;
493
+ name: string;
494
+ imageUrl: string | null;
495
+ defaultImage: boolean;
496
+ isActive: boolean;
497
+ isTyping: boolean;
498
+ } | null;
499
+ "zendesk.sdk.initialized"?: boolean;
500
+ "zendesk.sdk.disconnected"?: boolean;
501
+ "zendesk.sdk.reconnecting"?: boolean;
502
+ "zendesk.sessionId"?: string | null;
503
+ "zendesk.department.status"?: "ONLINE" | "OFFLINE" | null;
504
+ "zendesk.department.id"?: number | null;
505
+ "zendesk.tags"?: string[] | null;
506
+ "zendesk.fatalRequestErrorReached"?: boolean;
507
+ "ui.fatalHandoffDialog.pending"?: boolean;
508
+ "ui.fatalHandoffDialog.cancelFailed"?: boolean;
509
+ "zendeskMessaging.sdk.initializationStatus"?: 0 | 1 | 2;
510
+ "zendeskMessaging.sdk.initialized"?: boolean;
511
+ "zendeskMessaging.sdk.disconnected"?: boolean;
512
+ "zendeskMessaging.sdk.reconnecting"?: boolean;
513
+ "zendeskMessaging.conversationId"?: string | null;
514
+ "file.uploadVisible"?: boolean;
515
+ "file.uploadEnabled"?: boolean;
516
+ /** A live-agent upload is in flight. Core can reject a selection and go
517
+ * straight to `file.uploadError` WITHOUT ever setting this true, so an
518
+ * upload observer must watch both keys. */
519
+ "file.isUploading"?: boolean;
520
+ "file.uploadError"?: {
521
+ errorKey: "FILE_UPLOAD_UNSUPPORTED_TYPE" | "FILE_UPLOAD_UNKNOWN_ERROR" | "FILE_UPLOAD_EXCEED_SIZE_LIMIT" | "FILE_UPLOAD_ERROR" | "FILE_UPLOAD_MULTIPLE_FILES";
522
+ errorData: FileUploadErrorData;
523
+ } | null;
524
+ "file.transfer"?: {
525
+ progress?: number;
526
+ fileName?: string;
527
+ fileSize?: number;
528
+ } | null;
529
+ "outage.responseDelayed"?: boolean;
530
+ "outage.showServiceRestoredToast"?: boolean;
531
+ "outage.networkConnectivityToast"?: "offline" | "online" | null;
532
+ /** Connectivity is lost per core's verdict — unaffected by the toast being dismissed. */
533
+ "outage.connectivityLost"?: boolean;
534
+ "error.hasErrors": boolean;
535
+ "error.type": string | null;
536
+ "chatter.language": string;
537
+ "chatter.alertSoundEnabled": boolean;
538
+ "chatter.deviceNotificationsEnabled": boolean;
539
+ /** Host-origin Web Notification permission, as reported by the SDK. Permission
540
+ * is per-origin, so the widget frame's own value is not the one that governs
541
+ * the prompt. */
542
+ "chatter.notificationPermission": "default" | "granted" | "denied";
543
+ /** False when the host browser exposes no Notification API. */
544
+ "chatter.notificationsSupported": boolean;
545
+ "chatter.smsNotificationsEnabled": boolean;
546
+ "chatter.translations": Record<string, string | null>;
547
+ "ui.typingIndicator.visible": boolean;
548
+ /** api's bot `typing_indicator`; separate from the above because the UI gates
549
+ * that one on live chat. */
550
+ "ui.botTyping.visible": boolean;
551
+ "ui.showRetryDialog"?: boolean;
552
+ "ui.retryDialog.pending"?: boolean;
553
+ "voice.enabled"?: boolean;
554
+ "voice.isRecording"?: boolean;
555
+ "voice.mode"?: "toggle" | "push-to-talk";
556
+ "voice.currentTranscript"?: string;
557
+ "voice.isFinal"?: boolean;
558
+ "voice.audioLevel"?: number;
559
+ "voice.error"?: string | null;
560
+ "voice.ttsEnabled"?: boolean;
561
+ "csat.autoTrigger"?: boolean;
562
+ "csat.showAfterEndChat"?: boolean;
563
+ "csat.botSettings"?: CsatSurveySettings | null;
564
+ "csat.agentSettings"?: CsatSurveySettings | null;
565
+ "csat.settings"?: CsatSurveySettings | null;
566
+ /** Set on a failed (or invalid-for-scale) survey submit; cleared to null
567
+ * at the start of every non-partial submit. Match it together with the
568
+ * two error scope keys below — the same two-part guard the success triple
569
+ * needs. */
570
+ "csat.submitError"?: string | null;
571
+ "csat.submitErrorConversationId"?: string | null;
572
+ "csat.submitErrorSurveyType"?: string | null;
573
+ /** Advances every time `csat.submitError` is set to a message — including
574
+ * a repeat of the identical text (the same submit dropped twice), which
575
+ * the text alone cannot surface. Correlate error verdicts on its advance
576
+ * plus the surveyType/conversation match. Optional: absent on a core
577
+ * document that predates it — fall back to text transitions. */
578
+ "csat.submitError.seq"?: number;
579
+ /** Monotonic counter bumped on each TERMINAL survey submit success (a
580
+ * `partial` stage-1 submit does not bump it). Correlate by latching the
581
+ * seq before submitting and matching surveyType AND conversationId when
582
+ * it advances — type alone is insufficient: a late response from a dead
583
+ * conversation would close the new conversation's survey of the same
584
+ * type. `operations.submitCsat().settled()` implements this. */
585
+ "csat.submitSuccess.seq"?: number;
586
+ "csat.submitSuccess.surveyType"?: string | null;
587
+ "csat.submitSuccess.conversationId"?: string | null;
588
+ /** Request->response with no request id: `csat.checkEndChatEligibility`
589
+ * sets status `loading`, and the answer sets `ready` and bumps the seq.
590
+ * Latch the seq when asking and treat yourself as pending until it
591
+ * advances — a `ready -> ready` re-check changes no other observable
592
+ * state. `operations.checkEndChatEligibility()` implements this. */
593
+ "csat.endChatEligibility.seq"?: number;
594
+ "csat.endChatEligibility.status"?: "idle" | "loading" | "ready";
595
+ "csat.endChatEligible"?: boolean | null;
596
+ "csat.endChatSurveyTarget"?: "bot" | "agent" | null;
597
+ /** Whether End Chat is offered: the conversation has not ended, the user
598
+ * has sent in it, and a conversation id exists. Gates the header close
599
+ * affordance. */
600
+ "endChat.canEnd"?: boolean;
601
+ "config.isParentElementMode"?: boolean;
602
+ "config.hideDefaultHeader"?: boolean;
603
+ "ui.secretMessage.isOpen"?: boolean;
604
+ "ui.secretMessage.buttonEnabled"?: boolean;
605
+ "security.turnstileSiteKey"?: string | null;
606
+ "security.challengeEnabled"?: boolean;
607
+ "security.challengeError"?: string | null;
608
+ }
609
+ /**
610
+ * Events the display layer sends to core through `sendEvent`. Each key maps to
611
+ * its payload type (`undefined` = no payload). Grouped by prefix:
612
+ *
613
+ * - `app.*` — lifecycle: `app.initialize` is the REQUIRED mount handshake
614
+ * (send it within 15 seconds of the frame loading, or core unmounts the app
615
+ * frame), plus reset, new-conversation, and error reporting.
616
+ * - `chat.*` — conversation actions: send/retry messages, select quick
617
+ * replies/options/lists, report read cursors, react, load history.
618
+ * - `capture.*` / `listSelection.*` — answer or cancel conversation-state
619
+ * data blocks.
620
+ * - `liveAgent.*` — request/cancel/end a handoff, agent-directed messages,
621
+ * typing edges, visitor info.
622
+ * - `zendesk.*` / `zendeskMessaging.*` — Zendesk handoff SDK control.
623
+ * - `file.*` — handoff file-upload start/cancel/retry/error-dismiss.
624
+ * - `voice.*` — voice recording, mode, transcript, and TTS control.
625
+ * - `ui.*` — widget chrome interactions (close, minimize, dialogs, toasts,
626
+ * viewport, composer-changed edge).
627
+ * - `settings.*` / `appearance.*` — end-user preference changes.
628
+ * - `csat.*` / `endChat.*` — survey submission, shown-tracking, eligibility.
629
+ * - `outage.*` — retry after a response outage.
630
+ * - `security.*` — Turnstile verify/error/expire.
631
+ *
632
+ * This contract is shared with `packages/core` and must stay in sync with it.
633
+ */
634
+ export interface AppEvents {
635
+ /** Send a chat message. When `tempMessageUuid` is a RFC 4122 uuid, core
636
+ * seeds the optimistic message's `clientKey` from it, so the sender can
637
+ * find its own send in `chat.messages` (the durable echo keeps the key
638
+ * across the id swap). `messageType: "secret"` masks the send: the raw
639
+ * body never enters the transcript, so no correlatable row exists.
640
+ * Prefer `operations.sendMessage`, which handles all of this. */
641
+ "chat.message.send": {
642
+ body: string;
643
+ messageType: string;
644
+ tempMessageUuid: string;
645
+ };
646
+ "chat.message.retry": {
647
+ message: {
648
+ id?: string;
649
+ body?: string;
650
+ type: string;
651
+ tempMessageUuid?: string;
652
+ };
653
+ };
654
+ "chat.quickReply.select": {
655
+ label: string;
656
+ target: string;
657
+ buttonType: string;
658
+ /** Raw accepted value. Set only on target-less `accepted_value` chips and
659
+ * used as the utterance only when the visible label is absent. */
660
+ value?: string;
661
+ messageId: string;
662
+ };
663
+ /** Highest message `_id` cursor the user has scrolled into view; advances the
664
+ * persisted read watermark for the conversation it was observed in. */
665
+ "chat.messages.read": {
666
+ cursor: string;
667
+ conversationId?: string;
668
+ };
669
+ /** The user activated a `link` message's preview card. Core resolves the stored
670
+ * message for tracking and, for a validated same-tab link, host navigation. */
671
+ "chat.link.click": {
672
+ messageId: string;
673
+ navigateInHost?: boolean;
674
+ };
675
+ /** Ask the unsandboxed web host to print a validated transcript image. */
676
+ "image.print.request": {
677
+ url: string;
678
+ };
679
+ /** Ask the SDK host to expand or restore the app's viewport surface. */
680
+ "ui.hostViewport.set": {
681
+ expanded: boolean;
682
+ };
683
+ "liveAgent.handoff.request": {
684
+ platformData: {
685
+ platform: string;
686
+ departmentId?: number;
687
+ tags?: string[];
688
+ };
689
+ };
690
+ "liveAgent.handoff.cancel": undefined;
691
+ "liveAgent.chat.end": undefined;
692
+ "liveAgent.message.send": {
693
+ message: string;
694
+ };
695
+ "liveAgent.typing.start": undefined;
696
+ "liveAgent.typing.stop": undefined;
697
+ "liveAgent.visitor.setInfo": {
698
+ displayName?: string;
699
+ email?: string;
700
+ phone?: string;
701
+ };
702
+ "zendesk.sdk.initialize": {
703
+ accountKey: string;
704
+ options?: Record<string, unknown>;
705
+ };
706
+ "zendesk.sdk.teardown": undefined;
707
+ "zendesk.department.set": {
708
+ departmentId: number;
709
+ };
710
+ "zendesk.transcript.send": {
711
+ transcript: string[];
712
+ externalChatId: string;
713
+ };
714
+ "zendesk.transcript.clear": undefined;
715
+ "zendeskMessaging.sdk.initialize": undefined;
716
+ "zendeskMessaging.sdk.destroy": undefined;
717
+ "zendeskMessaging.conversation.load": undefined;
718
+ "file.upload.start": {
719
+ file: File;
720
+ uploadId?: string;
721
+ platform?: string;
722
+ };
723
+ "file.upload.cancel": undefined;
724
+ "file.upload.errorDismiss": undefined;
725
+ "file.upload.retry": {
726
+ file: File;
727
+ uploadId?: string;
728
+ };
729
+ "voice.recording.start": undefined;
730
+ "voice.recording.stop": undefined;
731
+ "voice.mode.set": {
732
+ mode: "toggle" | "push-to-talk";
733
+ };
734
+ "voice.transcript.apply": {
735
+ transcript: string;
736
+ };
737
+ "voice.tts.toggle": {
738
+ enabled: boolean;
739
+ };
740
+ "ui.closeButton.click": undefined;
741
+ "ui.minimizeButton.click": undefined;
742
+ "ui.toast.dismiss": undefined;
743
+ "ui.retryDialog.confirm": undefined;
744
+ "ui.retryDialog.dismiss": undefined;
745
+ /** Close a retry/fatal recovery sheet without posting a handoff control request. */
746
+ "ui.handoffDialog.dismiss": undefined;
747
+ "ui.fatalHandoffDialog.cancel": undefined;
748
+ /** Leave a live-chat queue from the queue banner, outside the retry prompt. */
749
+ "ui.queueCancel.request": undefined;
750
+ "outage.retry": undefined;
751
+ "settings.language.change": {
752
+ language: string;
753
+ };
754
+ "settings.alertSound.toggle": {
755
+ enabled: boolean;
756
+ };
757
+ "settings.notifications.toggle": {
758
+ enabled: boolean;
759
+ };
760
+ "settings.smsNotifications.toggle": {
761
+ enabled: boolean;
762
+ };
763
+ "settings.transcript.email.send": {
764
+ email: string;
765
+ };
766
+ "settings.transcript.download.request": undefined;
767
+ "settings.notifications.requestPermission": undefined;
768
+ "appearance.setTheme": {
769
+ theme: "light" | "dark" | "auto";
770
+ };
771
+ "appearance.setTextSize": {
772
+ textSize: "small" | "default" | "large";
773
+ };
774
+ "app.initialize": undefined;
775
+ "app.reset": undefined;
776
+ "app.startNewConversation": undefined;
777
+ "app.error.report": {
778
+ message: string;
779
+ stack: string;
780
+ componentStack: string;
781
+ };
782
+ "chat.message.reaction.add": {
783
+ messageId: string;
784
+ reaction: number;
785
+ };
786
+ "capture.submit": {
787
+ value: string;
788
+ dataId: string;
789
+ /** Optional uuid-shaped key for this submit. Core echoes it on
790
+ * `conversation.stateData.capture.lastSubmitFailure.submitId` when it
791
+ * fails or drops the submit without a server validation verdict, so
792
+ * the submitter can settle on its own submit's failure.
793
+ * `operations.submitCapture` mints one per call. */
794
+ submitId?: string;
795
+ };
796
+ "capture.cancel": {
797
+ cancelResponseId: string;
798
+ cancelLabel: string;
799
+ dataId: string;
800
+ };
801
+ "listSelection.cancel": {
802
+ cancelResponseId: string;
803
+ cancelLabel: string;
804
+ dataId: string;
805
+ };
806
+ "chat.logs.loadMore": undefined;
807
+ /** The chatter dismissed the error toast. Clears `chat.error`. */
808
+ "chat.error.dismiss": undefined;
809
+ "csat.submit": {
810
+ data: CsatSubmitData;
811
+ surveyType: string;
812
+ partial?: boolean;
813
+ /**
814
+ * The conversation this survey rates. Sent for a survey rendered IN the
815
+ * transcript, whose row can outlive its conversation — the transcript is
816
+ * preserved across a conversation change, so the held id is not a safe proxy.
817
+ * Optional: omitted by the End Chat / proactive flows, where core resolves it.
818
+ */
819
+ conversationId?: string | null;
820
+ };
821
+ "csat.shown": {
822
+ surveyType: string;
823
+ conversationId?: string | null;
824
+ };
825
+ "csat.checkEndChatEligibility": undefined;
826
+ "endChat.skipCsat": undefined;
827
+ "ui.secretMessage.toggle": {
828
+ open: boolean;
829
+ };
830
+ /** Composer text changed — drives the content-free handoff typing edge. The
831
+ * event is deliberately payload-free: core never reads the text, so the
832
+ * composer contents must not cross the frame boundary (secret mode exists to
833
+ * keep exactly that content out of this channel). */
834
+ "ui.composer.textChanged": undefined;
835
+ "chat.option.select": {
836
+ optionId: string;
837
+ optionLabel?: string;
838
+ messageId: string;
839
+ };
840
+ "chat.selectableList.submit": {
841
+ selectedIds: string[];
842
+ dataId: string;
843
+ };
844
+ "security.turnstile.verify": {
845
+ token: string;
846
+ };
847
+ "security.turnstile.error": undefined;
848
+ "security.turnstile.expire": undefined;
849
+ }