@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,323 @@
1
+ import type { AppDisplayState, AppEvents, CsatSubmitData, Message, OptionItem, QuickReply } from "./types.js";
2
+ /**
3
+ * The minimal client surface the operations layer builds on. Satisfied by the
4
+ * real `BridgeClient` and by the npm test mock.
5
+ */
6
+ export interface BridgeOperationsHost {
7
+ getState(): AppDisplayState | null;
8
+ subscribe(callback: () => void): () => void;
9
+ sendEvent<K extends keyof AppEvents>(event: K, ...args: AppEvents[K] extends undefined ? [] : [payload: AppEvents[K]]): void;
10
+ }
11
+ /** Outcome of a correlated submit: accepted, or rejected with a message. */
12
+ export type OperationResult = {
13
+ ok: true;
14
+ } | {
15
+ ok: false;
16
+ message: string;
17
+ };
18
+ export interface SettleOptions {
19
+ /** Reject with an `Error` if the operation has not settled in time. */
20
+ timeoutMs?: number;
21
+ }
22
+ /**
23
+ * Rejection every in-flight `settled()` promise receives when the client is
24
+ * destroyed, so teardown is the layer's bound of last resort even without a
25
+ * `timeoutMs`. The class identity does not survive the npm/CDN module split,
26
+ * so detect it by `error.name === "BridgeClientDestroyedError"` (or via
27
+ * {@link BridgeClientDestroyedError.is}), never by `instanceof` alone.
28
+ */
29
+ export declare class BridgeClientDestroyedError extends Error {
30
+ constructor(message?: string);
31
+ static is(error: unknown): error is BridgeClientDestroyedError;
32
+ }
33
+ export interface SendHandle {
34
+ /**
35
+ * The uuid put on the wire as `tempMessageUuid`. Core seeds the optimistic
36
+ * message's `clientKey` from it, so the sent message is findable in
37
+ * `chat.messages` by `clientKey === tempMessageUuid`.
38
+ */
39
+ readonly tempMessageUuid: string;
40
+ /**
41
+ * Resolves with the message once it appears in `chat.messages` (the
42
+ * optimistic row; the durable server echo keeps the same `clientKey`).
43
+ * Rejects when core reports THIS send's error before the row appears —
44
+ * rate limit, over-length, session not ready, dispatch failure —
45
+ * correlated exactly on `chat.error.clientKey` matching the minted
46
+ * `tempMessageUuid`, so an unrelated failure in the same window never
47
+ * falsely rejects a delivered send. On a core document that predates
48
+ * `chat.error.clientKey`, falls back to the positional
49
+ * `chat.error.seq`-advance detection, and before that to `chat.error`
50
+ * text transitions. A secret send NEVER resolves — core deliberately
51
+ * writes no transcript row for it — so pass `timeoutMs` or skip
52
+ * `settled()` for secret sends. Destroying the client rejects every
53
+ * pending `settled()` with a {@link BridgeClientDestroyedError}.
54
+ */
55
+ settled(opts?: SettleOptions): Promise<Message>;
56
+ }
57
+ export interface CaptureHandle {
58
+ /**
59
+ * Resolves on the verdict for THIS capture submit: `{ ok: true }` when the
60
+ * value was accepted (core removes the capture block), or
61
+ * `{ ok: false, message }` when it was rejected — a server validation
62
+ * rejection (observed on the `isSubmitting` falling edge, with the
63
+ * validation error text), or a failure/drop core stamps on the capture's
64
+ * `lastSubmitFailure` keyed to this submit's own minted `submitId` (no
65
+ * session to round-trip, value over the length cap, transport failure, a
66
+ * duplicate submit while one is in flight, a host-driven reset). The
67
+ * correlation is exact: the handle never reads the global `chat.error`,
68
+ * so an unrelated failure in the same window can never masquerade as this
69
+ * capture's verdict. On a core document that predates `lastSubmitFailure`,
70
+ * those failures are not capture-attributable at all and the handle stays
71
+ * pending — pass `timeoutMs` when targeting older cores.
72
+ */
73
+ settled(opts?: SettleOptions): Promise<OperationResult>;
74
+ }
75
+ export interface CsatSubmitHandle {
76
+ /**
77
+ * Resolves when core reports this survey's outcome, correlated on the
78
+ * success seq advance x surveyType x conversationId triple (and the
79
+ * mirror-image error triple). A `partial` submit reports no terminal
80
+ * outcome, so `settled()` on one only settles via `timeoutMs`.
81
+ */
82
+ settled(opts?: SettleOptions): Promise<OperationResult>;
83
+ }
84
+ export interface FileUploadHandle {
85
+ /** The minted upload id sent with `file.upload.start`. */
86
+ readonly uploadId: string;
87
+ /**
88
+ * Re-send the EXACT same `{ file, uploadId }` pair. Core correlates the
89
+ * retry to the failed attempt by that pair, so a fresh pair would orphan
90
+ * the original error state.
91
+ */
92
+ retry(): void;
93
+ /** Dismiss a reported upload error (`file.uploadError`). */
94
+ dismissError(): void;
95
+ }
96
+ export interface EndChatEligibilityResult {
97
+ /** `csat.endChatEligible` at the moment the answer arrived. */
98
+ eligible: boolean | null;
99
+ /** Which survey End Chat should present, when eligible. */
100
+ surveyTarget: "bot" | "agent" | null;
101
+ }
102
+ export interface ObserveOptions<T> {
103
+ /** Change detector; defaults to `Object.is`. */
104
+ equals?: (a: T, b: T) => boolean;
105
+ /** Also invoke the callback immediately with the current value. */
106
+ emitInitial?: boolean;
107
+ }
108
+ /**
109
+ * Typed helpers over the bridge event/state contract. Obtain via
110
+ * `client.operations`. Fire-and-forget helpers return `void`; correlated
111
+ * helpers return a handle or Promise that settles from state transitions
112
+ * (there is no request/response channel — core answers through state).
113
+ */
114
+ export interface BridgeOperations {
115
+ /**
116
+ * Send a chat message. The body is trimmed; an empty result sends nothing
117
+ * (the returned handle then rejects). `secret: true` sends a masked
118
+ * message: the raw body never enters the transcript and the handle never
119
+ * resolves. While `ui.secretMessage.isOpen` is true you likely want
120
+ * `secret: true` to match the composer's mode.
121
+ */
122
+ sendMessage(body: string, opts?: {
123
+ secret?: boolean;
124
+ }): SendHandle;
125
+ /** Retry a failed send by message id (see {@link canRetryMessage}). */
126
+ retryMessage(messageId: string): void;
127
+ /**
128
+ * Whether the retry affordance should show for a message: a `text`
129
+ * message core marked `isRetryable`, no disconnected Zendesk SDK, and the
130
+ * message belongs to the current conversation (or predates stamping).
131
+ */
132
+ canRetryMessage(message: Message): boolean;
133
+ /** Answer a quick-reply chip. Pass the chip and its message id verbatim. */
134
+ selectQuickReply(reply: QuickReply, messageId: string): void;
135
+ /** Answer an options block. The label falls back to the option id. */
136
+ selectOption(option: OptionItem, messageId: string): void;
137
+ /**
138
+ * Submit the active selectable list (`conversation.stateData.listSelection`
139
+ * supplies the `dataId`). No-op when no list selection is active.
140
+ */
141
+ submitListSelection(selectedIds: string[]): void;
142
+ /**
143
+ * Cancel the active selectable list. Uses the block's own cancel response
144
+ * and label (`label` overrides the visible label). No-op when no list
145
+ * selection is active or it has no cancel response.
146
+ */
147
+ cancelListSelection(label?: string): void;
148
+ /**
149
+ * Submit the active capture field (`conversation.stateData.capture`
150
+ * supplies the `dataId`). Each call mints its own `submitId`, so the
151
+ * handle settles only on ITS OWN submit's verdict or failure — a second
152
+ * submit sent while one is in flight is dropped by core and its handle
153
+ * resolves `{ ok: false }` on its own terms rather than inheriting the
154
+ * in-flight submit's verdict.
155
+ */
156
+ submitCapture(value: string): CaptureHandle;
157
+ /**
158
+ * Cancel the active capture field. `label` overrides the block's own
159
+ * cancel label. No-op when no capture is active or it has no cancel
160
+ * response.
161
+ */
162
+ cancelCapture(label?: string): void;
163
+ /** React to a bot message with a thumbs vote. */
164
+ addReaction(messageId: string, reaction: 1 | -1): void;
165
+ /**
166
+ * Report that the user activated a `link` message. Core resolves the
167
+ * stored message for tracking and, with `navigateInHost`, validated
168
+ * same-tab host navigation.
169
+ */
170
+ reportLinkClick(messageId: string, opts?: {
171
+ navigateInHost?: boolean;
172
+ }): void;
173
+ /**
174
+ * Advance the persisted read watermark. Reports are debounced 400 ms, the
175
+ * watermark is a monotonic lexical max over `_id` cursor strings (calling
176
+ * with an older cursor never lowers it), and the accumulator resets when
177
+ * `chat.conversationId` changes so a pending cursor from conversation A
178
+ * can never commit against B. Call with `message.cursor` for each message
179
+ * that scrolls into view; messages stamped with a DIFFERENT
180
+ * `conversationId` must be excluded by the caller (the transcript
181
+ * preserves the previous conversation's rows).
182
+ */
183
+ markRead(cursor: string, opts?: {
184
+ conversationId?: string;
185
+ }): void;
186
+ /**
187
+ * Request an older page of history. No-op unless `chat.canLoadMore` is
188
+ * true and no load is already in flight.
189
+ */
190
+ loadOlderMessages(): void;
191
+ /**
192
+ * Invoke `callback` each time restored/baseline history (re)loads
193
+ * (`chat.recentMessagesLoadSeq` advances) — the one-shot trigger for
194
+ * "scroll to the bottom of restored history" and for seeding an unread
195
+ * divider. Returns an unsubscribe function.
196
+ */
197
+ onHistoryLoaded(callback: (seq: number) => void): () => void;
198
+ /**
199
+ * Submit a CSAT survey. Pass `conversationId: message.conversationId` for
200
+ * a survey rendered IN the transcript (the row can outlive its
201
+ * conversation); omit it — or pass `null`, which is treated as omitted on
202
+ * the wire and in correlation — for End Chat / proactive flows, where
203
+ * core resolves the conversation itself.
204
+ */
205
+ submitCsat(data: CsatSubmitData, opts: {
206
+ surveyType: string;
207
+ partial?: boolean;
208
+ conversationId?: string | null;
209
+ }): CsatSubmitHandle;
210
+ /** Analytics: report that a survey was displayed. Fire-and-forget. */
211
+ trackCsatShown(surveyType: string, conversationId?: string | null): void;
212
+ /**
213
+ * Ask core whether End Chat should present a survey. Correlated by
214
+ * latching `csat.endChatEligibility.seq` and resolving when it advances
215
+ * (there is no request id; a `ready -> ready` re-check changes no other
216
+ * observable state).
217
+ */
218
+ checkEndChatEligibility(opts?: SettleOptions): Promise<EndChatEligibilityResult>;
219
+ /** End the chat without presenting a survey. */
220
+ skipCsatAndEndChat(): void;
221
+ /**
222
+ * Start a new conversation. Applies a local cooldown (default 5 s) that
223
+ * swallows rapid re-clicks; returns `false` while cooling down.
224
+ */
225
+ startNewConversation(opts?: {
226
+ cooldownMs?: number;
227
+ }): boolean;
228
+ /** Reset the app frame's conversation state. */
229
+ resetApp(): void;
230
+ /**
231
+ * Report a content-free composer-changed edge (drives live-agent typing
232
+ * indicators). The event is payload-free by contract — composer text must
233
+ * never cross the frame boundary — and this helper suppresses it entirely
234
+ * in secret mode (pass `secret: true`, or leave it to the
235
+ * `ui.secretMessage.isOpen` state check): even a content-free typing edge
236
+ * is a signal about the secret being typed.
237
+ */
238
+ notifyComposerChanged(opts?: {
239
+ secret?: boolean;
240
+ }): void;
241
+ /** Open or close the masked (secret) composer mode. */
242
+ toggleSecretMessage(open: boolean): void;
243
+ /**
244
+ * Start a live-agent file upload. Mints and retains the `{ file,
245
+ * uploadId }` pair so `retry()` replays exactly it. Progress and errors
246
+ * land on `file.isUploading`, `file.transfer`, and `file.uploadError`
247
+ * (core can report an error without ever setting `isUploading`).
248
+ */
249
+ startFileUpload(file: File): FileUploadHandle;
250
+ /** Change the conversation language (BCP 47 tag the bot offers). */
251
+ setLanguage(language: string): void;
252
+ /**
253
+ * Email the transcript. Resolves/rejects when `transcript.email.status`
254
+ * reaches `success | error` with its `.seq` advanced past this call's
255
+ * baseline — a request core drops without ever broadcasting `pending`
256
+ * (a host-driven reset in progress) still rejects. On a core document
257
+ * that predates the seq, falls back to the `pending -> terminal` edge.
258
+ * The email is trimmed; an empty result rejects immediately without
259
+ * sending (core drops an empty email silently, so nothing would ever
260
+ * settle it).
261
+ */
262
+ emailTranscript(email: string, opts?: SettleOptions): Promise<void>;
263
+ /**
264
+ * Download the transcript. Same settle contract as
265
+ * {@link BridgeOperations.emailTranscript}, on
266
+ * `transcript.download.status`.
267
+ */
268
+ downloadTranscript(opts?: SettleOptions): Promise<void>;
269
+ /** Toggle the new-message alert sound. */
270
+ setAlertSound(enabled: boolean): void;
271
+ /**
272
+ * Ask the HOST page to request Web Notification permission (permission is
273
+ * per-origin, so the widget frame's own value is not the governing one).
274
+ * Result lands on `chatter.notificationPermission`.
275
+ */
276
+ requestNotificationPermission(): void;
277
+ /** Set the user's in-widget theme override. */
278
+ setTheme(theme: "light" | "dark" | "auto"): void;
279
+ /** Set the user's in-widget text-size override. */
280
+ setTextSize(size: "small" | "default" | "large"): void;
281
+ /** The header close affordance (ends the chat when `endChat.canEnd`). */
282
+ close(): void;
283
+ /** Minimize the widget. */
284
+ minimize(): void;
285
+ /** Dismiss the visible toast. */
286
+ dismissToast(): void;
287
+ /**
288
+ * Clear `chat.error` in core. Clear core state rather than hiding the
289
+ * error locally, or the session's second error renders nothing.
290
+ */
291
+ dismissError(): void;
292
+ /** Ask the SDK host to expand or restore the app's viewport surface. */
293
+ setHostViewportExpanded(expanded: boolean): void;
294
+ /** Retry after a response outage banner. */
295
+ retryAfterOutage(): void;
296
+ /**
297
+ * Report a fatal app error to core (renders the fallback UI and reports
298
+ * telemetry). Callable from anywhere with a client reference — including
299
+ * a class error boundary outside any React provider.
300
+ */
301
+ reportAppError(error: unknown, componentStack?: string): void;
302
+ }
303
+ interface StateObservation {
304
+ subscribeKey<K extends keyof AppDisplayState>(key: K, callback: (value: AppDisplayState[K] | undefined, previous: AppDisplayState[K] | undefined) => void, opts?: ObserveOptions<AppDisplayState[K] | undefined>): () => void;
305
+ select<T>(selector: (state: AppDisplayState | null) => T, callback: (value: T, previous: T | undefined) => void, opts?: ObserveOptions<T>): () => void;
306
+ }
307
+ /**
308
+ * Build the `subscribeKey`/`select` observation primitives over a host.
309
+ * `Object.is` is a sound default equality for `subscribeKey`: state crosses
310
+ * the frame boundary by structured clone, so the real client restores each
311
+ * structurally unchanged key's previous reference before notifying (the test
312
+ * mock shares references in memory to begin with). For `select`, whose
313
+ * selector typically MINTS a new object per call, pass a custom `equals`
314
+ * (or select a primitive/signature string) when projecting objects.
315
+ */
316
+ export declare function createStateObservation(host: BridgeOperationsHost): StateObservation;
317
+ export interface BridgeOperationsBundle {
318
+ operations: BridgeOperations;
319
+ /** Release the layer's own subscriptions and timers (client teardown). */
320
+ dispose(): void;
321
+ }
322
+ export declare function createBridgeOperations(rawHost: BridgeOperationsHost): BridgeOperationsBundle;
323
+ export {};
@@ -0,0 +1,61 @@
1
+ export interface CsatFeedbackOption {
2
+ id: string;
3
+ label: string;
4
+ }
5
+ export interface CsatSurveySettings {
6
+ satisfactionIsEnabled: boolean;
7
+ satisfactionLabel: string;
8
+ satisfactionRange: number;
9
+ satisfactionStyle: string;
10
+ followUpFeedbackIsEnabled: boolean;
11
+ followUpFeedbackPositiveLabel: string;
12
+ followUpFeedbackNegativeLabel: string;
13
+ followUpFeedbackPositiveOptions: CsatFeedbackOption[];
14
+ followUpFeedbackNegativeOptions: CsatFeedbackOption[];
15
+ resolutionIsEnabled: boolean;
16
+ resolutionLabel: string;
17
+ openFeedbackIsEnabled: boolean;
18
+ openFeedbackLabel: string;
19
+ openFeedbackHint?: string;
20
+ customerEffortScoreIsEnabled: boolean;
21
+ customerEffortScoreRange: number;
22
+ customerEffortScoreLabel?: string;
23
+ netPromoterScoreIsEnabled: boolean;
24
+ netPromoterScoreLabel?: string;
25
+ order: string[];
26
+ required?: string[];
27
+ respondToNegativeFeedback?: boolean;
28
+ respondToPositiveFeedback?: boolean;
29
+ anytimeSurveyIsEnabled?: boolean;
30
+ postChatSurveyIsEnabled?: boolean;
31
+ }
32
+ export declare const DEFAULT_CSAT_ORDER: string[];
33
+ export type TranslationMap = Record<string, string>;
34
+ export interface StoredCsatFeedbackOption extends CsatFeedbackOption {
35
+ translations?: TranslationMap;
36
+ }
37
+ export interface CsatLabelTranslations {
38
+ satisfactionLabel?: TranslationMap;
39
+ followUpFeedbackPositiveLabel?: TranslationMap;
40
+ followUpFeedbackNegativeLabel?: TranslationMap;
41
+ resolutionLabel?: TranslationMap;
42
+ openFeedbackLabel?: TranslationMap;
43
+ openFeedbackHint?: TranslationMap;
44
+ customerEffortScoreLabel?: TranslationMap;
45
+ netPromoterScoreLabel?: TranslationMap;
46
+ }
47
+ export interface StoredCsatSurveySettings extends CsatSurveySettings {
48
+ followUpFeedbackPositiveOptions: StoredCsatFeedbackOption[];
49
+ followUpFeedbackNegativeOptions: StoredCsatFeedbackOption[];
50
+ labelTranslations?: CsatLabelTranslations;
51
+ }
52
+ /** Preserve API translation maps so already-fetched surveys follow language changes. */
53
+ export declare function parseCsatSettings(
54
+ data: unknown,
55
+ language?: string,
56
+ ): StoredCsatSurveySettings | null;
57
+ /** Strip internal maps while resolving the public app contract for this render. */
58
+ export declare function localizeCsatSettings(
59
+ settings: StoredCsatSurveySettings | null,
60
+ language: string,
61
+ ): CsatSurveySettings | null;
@@ -0,0 +1,36 @@
1
+ export type FileUploadErrorReason =
2
+ | "delivery_unknown"
3
+ | "durability_failed"
4
+ | "durability_unknown"
5
+ | "handoff_ended"
6
+ | "http_400"
7
+ | "http_413"
8
+ | "http_415"
9
+ | "http_error"
10
+ | "integration_error"
11
+ | "integration_frame_error"
12
+ | "integration_frame_load_error"
13
+ | "integration_result_timeout"
14
+ | "invalid_context"
15
+ | "invalid_response"
16
+ | "message_send_failed"
17
+ | "missing_platform"
18
+ | "missing_transfer_data"
19
+ | "network_error"
20
+ | "provider_rejected"
21
+ | "timeout"
22
+ | "unsupported_platform"
23
+ | "upload_not_enabled";
24
+ /** Typed bridge contract for file-upload failures shown to the chatter. */
25
+ export interface FileUploadErrorData {
26
+ reason?: FileUploadErrorReason;
27
+ deliveryConfirmed?: boolean;
28
+ indeterminate?: boolean;
29
+ /** Core owns whether replaying the retained operation is safe and meaningful. */
30
+ retryable?: boolean;
31
+ maxFileSizeInMB?: number;
32
+ status?: number | string;
33
+ integrationErrorCode?: string;
34
+ platform?: string;
35
+ details?: Record<string, unknown>;
36
+ }
@@ -0,0 +1,2 @@
1
+ export type * from "./csat-settings.js";
2
+ export type * from "./file-upload.js";
@@ -0,0 +1,105 @@
1
+ /**
2
+ * Typed constants for all bridge state keys.
3
+ *
4
+ * Using these constants prevents silent typos — a misspelled string literal
5
+ * compiles without error but returns `undefined` at runtime.
6
+ *
7
+ * This is a catalogue for consumers of the package, not a list of keys this
8
+ * repo happens to use: most entries have no in-repo reader, and a new one
9
+ * having none is expected rather than dead code.
10
+ *
11
+ * The `satisfies` clause is what makes that true. This map is a third
12
+ * hand-maintained mirror of the state contract, and an unused constant never
13
+ * typechecks on its own — without the clause a value that no longer names a
14
+ * real `AppDisplayState` key would compile clean and only fail at the first
15
+ * read. It also keeps the literal types, so `StateKey` stays a union of the
16
+ * exact strings rather than widening to `string`.
17
+ *
18
+ * @example
19
+ * import { STATE } from "@ada-cx/messaging-bridge";
20
+ * const botName = state?.[STATE.CONFIG_BOT_NAME] ?? "Ada";
21
+ */
22
+ export declare const STATE: {
23
+ readonly AGENT_ACTIVE_AGENT: "agent.activeAgent";
24
+ readonly APP_LOADING: "app.loading";
25
+ readonly CHAT_CAN_LOAD_MORE: "chat.canLoadMore";
26
+ readonly CHAT_ERROR: "chat.error";
27
+ readonly CHAT_ERROR_SEQ: "chat.error.seq";
28
+ readonly CHAT_ERROR_CLIENT_KEY: "chat.error.clientKey";
29
+ readonly CHAT_IS_GENERATING: "chat.isGenerating";
30
+ readonly CHAT_IS_UNIFIED_REASONER_GENERATING: "chat.isUnifiedReasonerGenerating";
31
+ readonly CHAT_IS_SENDING: "chat.isSending";
32
+ readonly CHAT_MESSAGES: "chat.messages";
33
+ readonly CHAT_COMPOSER_TEXT: "chat.composerText";
34
+ readonly CHAT_ANSWERED_INTERACTIVE_IDS: "chat.answeredInteractiveIds";
35
+ readonly CHAT_PLAYBOOK_STEP_EXECUTING: "chat.playbookStepExecuting";
36
+ readonly CHATTER_ALERT_SOUND_ENABLED: "chatter.alertSoundEnabled";
37
+ readonly CHATTER_DEVICE_NOTIFICATIONS_ENABLED: "chatter.deviceNotificationsEnabled";
38
+ readonly CHATTER_LANGUAGE: "chatter.language";
39
+ readonly CHATTER_SMS_NOTIFICATIONS_ENABLED: "chatter.smsNotificationsEnabled";
40
+ readonly CHATTER_TRANSLATIONS: "chatter.translations";
41
+ readonly CONFIG_ADVANCED_COLORS_ENABLED: "config.advancedColorsEnabled";
42
+ readonly CONFIG_ALLOWED_PROTOCOLS: "config.allowedProtocols";
43
+ readonly CONFIG_BOT_AVATAR_URL: "config.botAvatarUrl";
44
+ readonly CONFIG_BOT_DESCRIPTION: "config.botDescription";
45
+ readonly CONFIG_BOT_DESCRIPTION_ENABLED: "config.botDescriptionEnabled";
46
+ readonly CONFIG_BOT_NAME: "config.botName";
47
+ readonly CONFIG_BUTTON_LAYOUT_DESKTOP: "config.buttonLayoutDesktop";
48
+ readonly CONFIG_BUTTON_LAYOUT_MOBILE: "config.buttonLayoutMobile";
49
+ readonly CONFIG_CHAT_ENABLED: "config.chatEnabled";
50
+ readonly CONFIG_CUSTOM_REDACTIONS: "config.customRedactions";
51
+ readonly CONFIG_DOWNLOAD_TRANSCRIPT_ENABLED: "config.downloadTranscriptEnabled";
52
+ readonly CONFIG_EMAIL_TRANSCRIPT_ENABLED: "config.emailTranscriptEnabled";
53
+ readonly CONFIG_FALLBACK_UI: "config.fallbackUi";
54
+ readonly CONFIG_FEATURES: "config.features";
55
+ readonly CONFIG_HANDLE: "config.handle";
56
+ readonly CONFIG_HEADER_COLOR: "config.headerColor";
57
+ readonly CONFIG_HEADER_COLOR_ENABLED: "config.headerColorEnabled";
58
+ readonly CONFIG_HEADER_TEXT_COLOR: "config.headerTextColor";
59
+ readonly CONFIG_IS_MOBILE_VIEWPORT: "config.isMobileViewport";
60
+ readonly CONFIG_PRIVACY_LINK: "config.privacyLink";
61
+ readonly CONFIG_PRIVACY: "config.privacy";
62
+ readonly CONFIG_SDK_PLATFORM: "config.sdkPlatform";
63
+ readonly CONFIG_SDK_SUPPORTS_DOWNLOAD_LINK: "config.sdkSupportsDownloadLink";
64
+ readonly CONFIG_SHOW_ALL_QUICK_REPLIES: "config.showAllQuickReplies";
65
+ readonly CONFIG_SHOW_BRANDING: "config.showBranding";
66
+ readonly CONFIG_ADA_PARTNER_NAME: "config.adaPartnerName";
67
+ readonly CONFIG_STYLE: "config.style";
68
+ readonly CONFIG_TEXT_OVER_ACCENT_COLOR: "config.textOverAccentColor";
69
+ readonly CONFIG_TEXT_SIZE: "config.textSize";
70
+ readonly CONFIG_THEME: "config.theme";
71
+ readonly CONFIG_TINT_COLOR: "config.tintColor";
72
+ readonly CONFIG_TRANSLATED_LANGUAGES: "config.translatedLanguages";
73
+ readonly CONFIG_CLIENT_DEFAULT_LANGUAGE: "config.clientDefaultLanguage";
74
+ readonly CONVERSATION_IN_STATE: "conversation.inState";
75
+ readonly CONVERSATION_STATE_DATA_CAPTURE: "conversation.stateData.capture";
76
+ readonly CONVERSATION_STATE_DATA_LIST_SELECTION: "conversation.stateData.listSelection";
77
+ readonly CSAT_AUTO_TRIGGER: "csat.autoTrigger";
78
+ readonly FILE_TRANSFER: "file.transfer";
79
+ readonly FILE_UPLOAD_ERROR: "file.uploadError";
80
+ readonly LIVE_AGENT_IN_LIVE_CHAT: "liveAgent.inLiveChat";
81
+ readonly TRANSCRIPT_DOWNLOAD_STATUS: "transcript.download.status";
82
+ readonly TRANSCRIPT_EMAIL_STATUS: "transcript.email.status";
83
+ readonly UI_FATAL_HANDOFF_DIALOG_CANCEL_FAILED: "ui.fatalHandoffDialog.cancelFailed";
84
+ readonly UI_FATAL_HANDOFF_DIALOG_PENDING: "ui.fatalHandoffDialog.pending";
85
+ readonly UI_RETRY_DIALOG_PENDING: "ui.retryDialog.pending";
86
+ readonly UI_SECRET_MESSAGE_BUTTON_ENABLED: "ui.secretMessage.buttonEnabled";
87
+ readonly UI_SECRET_MESSAGE_IS_OPEN: "ui.secretMessage.isOpen";
88
+ readonly UI_SHOW_RETRY_DIALOG: "ui.showRetryDialog";
89
+ readonly UI_TYPING_INDICATOR_VISIBLE: "ui.typingIndicator.visible";
90
+ readonly SECURITY_CHALLENGE_ENABLED: "security.challengeEnabled";
91
+ readonly SECURITY_CHALLENGE_ERROR: "security.challengeError";
92
+ readonly SECURITY_TURNSTILE_SITE_KEY: "security.turnstileSiteKey";
93
+ readonly SAML_LOCKED: "saml.locked";
94
+ readonly SAML_URL: "saml.url";
95
+ readonly SAML_LOGIN_BUTTON: "saml.loginButton";
96
+ readonly VOICE_AUDIO_LEVEL: "voice.audioLevel";
97
+ readonly VOICE_CURRENT_TRANSCRIPT: "voice.currentTranscript";
98
+ readonly VOICE_ENABLED: "voice.enabled";
99
+ readonly VOICE_ERROR: "voice.error";
100
+ readonly VOICE_IS_FINAL: "voice.isFinal";
101
+ readonly VOICE_IS_RECORDING: "voice.isRecording";
102
+ readonly VOICE_MODE: "voice.mode";
103
+ readonly VOICE_TTS_ENABLED: "voice.ttsEnabled";
104
+ };
105
+ export type StateKey = (typeof STATE)[keyof typeof STATE];
@@ -0,0 +1,53 @@
1
+ import type { BridgeClient } from "./bridge-client.js";
2
+ import type { AppDisplayState, AppEvents } from "./types.js";
3
+ /** One recorded `sendEvent` call. */
4
+ export interface SentBridgeEvent<K extends keyof AppEvents = keyof AppEvents> {
5
+ event: K;
6
+ payload: AppEvents[K] | undefined;
7
+ }
8
+ /**
9
+ * A scriptable, in-memory {@link BridgeClient} for unit tests.
10
+ *
11
+ * No postMessage, no origin checks, no network: tests drive state through
12
+ * `setState`/`updateState` and assert on the events the component under test
13
+ * sent through `sentEvents`. The `operations`, `subscribeKey`, and `select`
14
+ * surfaces are the REAL implementations running over this in-memory state:
15
+ * every operation records its events in `sentEvents`, and a correlated
16
+ * handle (`sendMessage().settled()`, `submitCsat().settled()`, ...) resolves
17
+ * when the test scripts the answering state transition via `updateState`.
18
+ */
19
+ export interface MockBridgeClient extends BridgeClient {
20
+ /** Replace the state snapshot and notify subscribers. */
21
+ setState(state: AppDisplayState | null): void;
22
+ /**
23
+ * Shallow-merge a partial snapshot over the current state (or over an empty
24
+ * object when no state is set) and notify subscribers. Seeds
25
+ * `"chat.error.clientKey": null` when the key is absent — production core
26
+ * always publishes it (null when no keyed send owns the error), and
27
+ * `operations` send handles take the keyed correlation path only when it is
28
+ * present. Script a `chat.error` fixture with the send's `tempMessageUuid`
29
+ * as `chat.error.clientKey` to reject that handle; use `setState` to script
30
+ * an older core document without the key.
31
+ */
32
+ updateState(partial: Partial<AppDisplayState>): void;
33
+ /**
34
+ * Every event sent through the client, in order. Each read returns a fresh
35
+ * copy, so a captured array survives `clearSentEvents()`.
36
+ */
37
+ readonly sentEvents: readonly SentBridgeEvent[];
38
+ /** Forget recorded events without touching state or subscribers. */
39
+ clearSentEvents(): void;
40
+ /** Number of active subscribers. */
41
+ readonly subscriberCount: number;
42
+ /** True once `destroy()` has been called. */
43
+ readonly destroyed: boolean;
44
+ }
45
+ /**
46
+ * Create a {@link MockBridgeClient}, optionally seeded with an initial state
47
+ * snapshot. Mirrors the real client's teardown semantics: after `destroy()`,
48
+ * state is `null`, subscribers are cleared, `sendEvent` is a no-op, and every
49
+ * in-flight `operations` `settled()` promise rejects with a
50
+ * `BridgeClientDestroyedError`. Notification also mirrors the real client:
51
+ * a throwing subscriber is caught and logged, never aborting the pass.
52
+ */
53
+ export declare function createMockBridgeClient(initialState?: AppDisplayState | null): MockBridgeClient;