@ada-cx/messaging-bridge 1.0.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -111,6 +111,16 @@ Highlights:
111
111
  - `submitCapture(value).settled()` reports the server verdict for the
112
112
  active capture field.
113
113
  - `checkEndChatEligibility()` resolves with the End Chat survey decision.
114
+ - `endLiveChat()` ends only the live-agent leg. On a bot with
115
+ `config.features.endLiveChat`, call it — not `skipCsatAndEndChat()` or an
116
+ `end_chat` `submitCsat()` — when End Chat is confirmed during a live chat
117
+ (`liveAgent.inLiveChat`), and offer no end-chat survey: api routes the
118
+ post-agent survey as an inline `live_chat` row once the leg has ended. Once
119
+ `liveAgent.liveChatEndedInConversation` is true on such a bot, End Chat
120
+ calls `skipCsatAndEndChat()` directly, with no confirmation or eligibility
121
+ check.
122
+ - `trackCsatShown("live_chat")` is dropped by core: api records that survey as
123
+ shown when it routes its row.
114
124
  - `markRead(cursor)` debounces 400 ms and keeps a monotonic watermark. Do
115
125
  not reimplement read tracking.
116
126
  - `startNewConversation()` applies a 5 second cooldown and returns `false`
@@ -145,8 +155,24 @@ client.subscribeKey("chat.isGenerating", (generating) => {
145
155
 
146
156
  The loaded module exports pure helpers mined from Ada's reference app:
147
157
  `messageKey`, `isHistoricalRow`, `findFirstUnread`, `selectUnread`,
148
- `groupMessages`, `filterDisplayable`, `resolveBotName`, `isAgentTyping`,
149
- and `isConnectivityLost`.
158
+ `groupMessages`, `filterDisplayable`, `resolveBotName`,
159
+ `resolveDisplayBotName`, `isAgentTyping`, and `isConnectivityLost`.
160
+
161
+ `resolveBotName` is the attribution name. Use it for sender labels and
162
+ announcements. It falls back to the bot handle.
163
+
164
+ `resolveDisplayBotName` is the display name for your own chrome, such as a
165
+ header title or a greeting. It is empty when the customer configured no bot
166
+ name, so a handle never reaches the page as a name.
167
+
168
+ `resolveDisplayBotName` is the one optional helper. The mounting core frame
169
+ picks the runtime build, not your installed package version. A build older than
170
+ this helper can therefore load. This happens during a rollout, behind an
171
+ `?ada-messaging-version` pin, or after a rollback. Guard the call:
172
+
173
+ ```ts
174
+ const name = bridge.resolveDisplayBotName?.(state) ?? "";
175
+ ```
150
176
 
151
177
  ```ts
152
178
  const { messageKey, filterDisplayable, groupMessages } = bridge;
@@ -106,8 +106,10 @@ export declare function groupMessages(messages: Message[]): MessageGroupingEntry
106
106
  * the unread boundary and count describe the rows you render.
107
107
  */
108
108
  export declare function filterDisplayable(messages: Message[], isConversationActive: boolean): Message[];
109
- /** Display name for the bot: `config.botName`, else `config.handle`, else "Ada". */
109
+ /** Attribution name (sender labels, announcer): `config.botName`, else `config.handle`, else "Ada". */
110
110
  export declare function resolveBotName(state: AppDisplayState | null): string;
111
+ /** Display name for chrome (header title, greeting): `config.botName`, else empty. */
112
+ export declare function resolveDisplayBotName(state: AppDisplayState | null): string;
111
113
  /** Someone is composing a reply (core's typing verdict + active agent). */
112
114
  export declare function isAgentTyping(state: AppDisplayState | null): boolean;
113
115
  /** Core's connectivity verdict — never substitute `navigator.onLine`. */
@@ -207,7 +207,12 @@ export interface BridgeOperations {
207
207
  partial?: boolean;
208
208
  conversationId?: string | null;
209
209
  }): CsatSubmitHandle;
210
- /** Analytics: report that a survey was displayed. Fire-and-forget. */
210
+ /**
211
+ * Analytics: report that a survey was displayed. Fire-and-forget. Core drops
212
+ * it for post-agent survey types (`live_chat`): api records that survey as
213
+ * shown when it routes its row into the transcript, so a client record would
214
+ * count the same prompt twice.
215
+ */
211
216
  trackCsatShown(surveyType: string, conversationId?: string | null): void;
212
217
  /**
213
218
  * Ask core whether End Chat should present a survey. Correlated by
@@ -216,8 +221,26 @@ export interface BridgeOperations {
216
221
  * observable state).
217
222
  */
218
223
  checkEndChatEligibility(opts?: SettleOptions): Promise<EndChatEligibilityResult>;
219
- /** End the chat without presenting a survey. */
224
+ /**
225
+ * End the conversation now with no survey. On a bot with
226
+ * `config.features.endLiveChat` this is also the End Chat that follows a live
227
+ * chat (`liveAgent.liveChatEndedInConversation` is true): call it straight
228
+ * from the End Chat control — no confirmation, no `checkEndChatEligibility()`,
229
+ * no survey — as legacy `END_LIVE_CHAT` does. While such a bot is still in a
230
+ * live chat, call `endLiveChat()` instead.
231
+ */
220
232
  skipCsatAndEndChat(): void;
233
+ /**
234
+ * End only the live-agent leg (legacy `END_LIVE_CHAT`). On a bot with
235
+ * `config.features.endLiveChat`, an End Chat confirmed while
236
+ * `liveAgent.inLiveChat` MUST use this rather than `skipCsatAndEndChat()` or
237
+ * an `end_chat` `submitCsat()`, and offer no end-chat survey: the conversation
238
+ * stays open and api routes the post-agent (`live_chat`) survey as an inline
239
+ * row once the leg has ended, whereas an end-chat submission there is stored
240
+ * as the agent's rating under the wrong survey type. Core ignores it outside
241
+ * a live-agent leg.
242
+ */
243
+ endLiveChat(): void;
221
244
  /**
222
245
  * Start a new conversation. Applies a local cooldown (default 5 s) that
223
246
  * swallows rapid re-clicks; returns `false` while cooling down.
@@ -31,6 +31,9 @@ export declare const STATE: {
31
31
  readonly CHAT_IS_SENDING: "chat.isSending";
32
32
  readonly CHAT_MESSAGES: "chat.messages";
33
33
  readonly CHAT_COMPOSER_TEXT: "chat.composerText";
34
+ readonly CHAT_STAGED_ATTACHMENT: "chat.stagedAttachment";
35
+ readonly CHAT_ATTACHMENT_REJECTION: "chat.attachmentRejection";
36
+ readonly CHAT_IMAGE_UPLOAD_VISIBLE: "chat.imageUploadVisible";
34
37
  readonly CHAT_ANSWERED_INTERACTIVE_IDS: "chat.answeredInteractiveIds";
35
38
  readonly CHAT_PLAYBOOK_STEP_EXECUTING: "chat.playbookStepExecuting";
36
39
  readonly CHATTER_ALERT_SOUND_ENABLED: "chatter.alertSoundEnabled";
@@ -78,6 +81,7 @@ export declare const STATE: {
78
81
  readonly FILE_TRANSFER: "file.transfer";
79
82
  readonly FILE_UPLOAD_ERROR: "file.uploadError";
80
83
  readonly LIVE_AGENT_IN_LIVE_CHAT: "liveAgent.inLiveChat";
84
+ readonly LIVE_AGENT_LIVE_CHAT_ENDED_IN_CONVERSATION: "liveAgent.liveChatEndedInConversation";
81
85
  readonly TRANSCRIPT_DOWNLOAD_STATUS: "transcript.download.status";
82
86
  readonly TRANSCRIPT_EMAIL_STATUS: "transcript.email.status";
83
87
  readonly UI_FATAL_HANDOFF_DIALOG_CANCEL_FAILED: "ui.fatalHandoffDialog.cancelFailed";
@@ -168,11 +168,30 @@ export interface VideoMessage extends BaseMessage {
168
168
  vidUrl: string;
169
169
  altText: string;
170
170
  }
171
+ /** An image picked in the composer, awaiting the send that carries it. */
172
+ export interface StagedAttachment {
173
+ /** Client-minted per pick; a result for a replaced or removed pick is dropped. */
174
+ id: string;
175
+ name: string;
176
+ mimeType: string;
177
+ size: number;
178
+ status: "uploading" | "ready";
179
+ /** Hosted URL, present once `status` is `ready`. */
180
+ url?: string;
181
+ }
182
+ /** A pick the composer could not stage, kept apart from `chat.stagedAttachment`
183
+ * so it never displaces a staged image. Replaced by the next pick. */
184
+ export interface StagedAttachmentRejection {
185
+ id: string;
186
+ error: "unsupported_type" | "too_large" | "unavailable" | "upload_failed";
187
+ }
171
188
  export interface DownloadableMessage extends BaseMessage {
172
189
  type: "downloadable";
173
190
  url: string;
174
191
  name: string;
175
192
  mimeType: string | null;
193
+ /** Text sent with the file, when the chatter typed a message alongside it. */
194
+ caption?: string;
176
195
  }
177
196
  export interface AgentInfo {
178
197
  name: string;
@@ -462,6 +481,16 @@ export interface AppDisplayState {
462
481
  text: string;
463
482
  revision: number;
464
483
  } | null;
484
+ /** The image the chatter picked for the next bot send. Hosted as soon as it is
485
+ * picked; the composer shows it as a chip until the send consumes it. Absent on
486
+ * a core document that predates image attachments. Core keeps the storage key
487
+ * to itself. */
488
+ "chat.stagedAttachment"?: StagedAttachment | null;
489
+ /** The most recent pick that could not be staged, beside whatever is staged. */
490
+ "chat.attachmentRejection"?: StagedAttachmentRejection | null;
491
+ /** The composer may offer the image attach control (flag, customer setting,
492
+ * bot conversation with a known id). Absent on an older core document. */
493
+ "chat.imageUploadVisible"?: boolean;
465
494
  /** Interactive blocks (quick replies, options) the chatter has already
466
495
  * answered. Includes blocks a later same-conversation user message
467
496
  * superseded, so the set survives a reload. Filter interactive rows on
@@ -474,6 +503,15 @@ export interface AppDisplayState {
474
503
  "liveAgent.isActive"?: boolean;
475
504
  "liveAgent.hasConversationEnded"?: boolean;
476
505
  "liveAgent.conversationEndedByChatter"?: boolean;
506
+ /**
507
+ * A live chat in this conversation has ended and the chatter is back with the
508
+ * bot; cleared by the next live chat, on conversation reset and when the
509
+ * conversation is left for another, and never persisted across a reload. On a
510
+ * bot with the `endLiveChat` feature the next End Chat ends the conversation
511
+ * outright (`endChat.skipCsat`) — no confirmation sheet, eligibility check or
512
+ * survey.
513
+ */
514
+ "liveAgent.liveChatEndedInConversation"?: boolean;
477
515
  "liveAgent.platform.name"?: string | null;
478
516
  "liveAgent.platform.departmentId"?: number | null;
479
517
  "liveAgent.queuePosition"?: number | null;
@@ -682,6 +720,18 @@ export interface AppEvents {
682
720
  messageId: string;
683
721
  navigateInHost?: boolean;
684
722
  };
723
+ /** The user activated a link inside a `widget` message's carousel. Core resolves
724
+ * the stored widget message and reports the click as a carousel link-click event.
725
+ * `actionType` is the widget's classification of the link: `link` or `web_window`
726
+ * (`article` is refused: api's article branch needs an `article_id` a carousel click
727
+ * cannot carry). `url` must pass the same link-protocol policy as a link in a message
728
+ * (`isSafeLinkUrl` with the bot's `allowedProtocols`) and be at most 2048 characters.
729
+ * Core drops a click that fails either check. */
730
+ "chat.widget.carouselLinkClick": {
731
+ messageId: string;
732
+ url: string;
733
+ actionType: string;
734
+ };
685
735
  /** Ask the unsandboxed web host to print a validated transcript image. */
686
736
  "image.print.request": {
687
737
  url: string;
@@ -732,6 +782,14 @@ export interface AppEvents {
732
782
  };
733
783
  "file.upload.cancel": undefined;
734
784
  "file.upload.errorDismiss": undefined;
785
+ /** Host the picked image for the next bot send. */
786
+ "chat.attachment.stage": {
787
+ file: File;
788
+ };
789
+ /** Drop the staged image named by `id`; ignored once another pick replaced it. */
790
+ "chat.attachment.remove": {
791
+ id: string;
792
+ };
735
793
  "file.upload.retry": {
736
794
  file: File;
737
795
  uploadId?: string;
@@ -828,12 +886,36 @@ export interface AppEvents {
828
886
  */
829
887
  conversationId?: string | null;
830
888
  };
889
+ /**
890
+ * Analytics: a survey was displayed. Dropped for post-agent survey types
891
+ * (`live_chat`): api records that survey as shown when it routes its row into
892
+ * the transcript, so a client record would count the same prompt twice.
893
+ */
831
894
  "csat.shown": {
832
895
  surveyType: string;
833
896
  conversationId?: string | null;
834
897
  };
835
898
  "csat.checkEndChatEligibility": undefined;
899
+ /** The user confirmed End Chat. Core publishes `ada:close_chat` here, before any
900
+ * end-chat survey; the conversation still ends through `endChat.skipCsat` or an
901
+ * end-chat `csat.submit`, which publish it only when no confirm opened the attempt.
902
+ * Send it on every confirm: a confirm covers one attempt, released by the next confirm
903
+ * or by the user sending another message; `csat.checkEndChatEligibility` may come
904
+ * before or after it. */
905
+ "endChat.confirm": undefined;
906
+ /**
907
+ * End the conversation now with no survey: the End Chat survey was skipped or
908
+ * closed, or — on a bot with the `endLiveChat` feature — End Chat was pressed
909
+ * after the live chat had already ended (`liveAgent.liveChatEndedInConversation`).
910
+ */
836
911
  "endChat.skipCsat": undefined;
912
+ /**
913
+ * End Chat confirmed during a live chat on a bot with the `endLiveChat`
914
+ * feature. Ends the agent leg only — the conversation stays open, with no
915
+ * end-chat survey — and api routes the post-agent (`live_chat`) survey once
916
+ * that leg has ended.
917
+ */
918
+ "endChat.endLiveChat": undefined;
837
919
  "ui.secretMessage.toggle": {
838
920
  open: boolean;
839
921
  };
@@ -1 +1 @@
1
- {"version":3,"file":"loader-CfCnvaTU.js","names":[],"sources":["../src/npm/loader.ts"],"sourcesContent":["import type { createBridgeClient } from \"../contract/bridge-client\";\nimport type {\n\tfilterDisplayable,\n\tfindFirstUnread,\n\tgroupMessages,\n\tisAgentTyping,\n\tisConnectivityLost,\n\tisHistoricalRow,\n\tmessageKey,\n\tresolveBotName,\n\tselectUnread,\n} from \"../contract/derive\";\nimport type { STATE } from \"../contract/state-keys\";\n\nconst DEFAULT_CDN_BASE = \"https://messaging-assets.ada.support\";\n// Keep aligned with packages/core/src/app-bridge.ts; pinned by\n// scripts/core-html-contract.test.mjs.\nconst CUSTOM_APP_FRAME_NAME = \"ada-custom-app\";\nconst BRIDGE_ENTRY_PATH = \"bridge/bridge.js\";\nconst DEV_BUILD_MARKER = \"dev\";\n\n// Only canonical build identifiers may become part of an executable asset URL.\nconst PINNED_BUILD_SHA_PATTERN = /^[0-9a-f]{40}$/i;\n// Deliberately narrower than scripts/sdk.js parseVersionOverride (7-40 hex,\n// case-insensitive): deployed keys are always lowercase `pr-<N>`, and a short\n// SHA never names an uploaded tree, so the narrower grammar loses no reachable\n// build while keeping the executable-path grammar minimal.\nconst PR_BUILD_ALIAS_PATTERN = /^pr-\\d+$/;\n\n/** Why a {@link MessagingBridgeLoadError} was raised. */\nexport type MessagingBridgeLoadErrorCode =\n\t/** The `cdnBase` option violates the HTTPS or loopback HTTP policy. */\n\t| \"invalid_cdn_base\"\n\t/** The frame's `window.name` carries no core build marker. */\n\t| \"missing_build_sha\"\n\t/** The frame-name marker is not a supported bare build identifier. */\n\t| \"invalid_build_sha\"\n\t/** The `dev` marker was paired with a non-loopback CDN base. */\n\t| \"dev_marker_not_loopback\"\n\t/** The dynamic import of the CDN asset failed. */\n\t| \"bridge_import_failed\"\n\t/** The imported module does not expose the Ada bridge surface. */\n\t| \"bridge_module_invalid\";\n\n/** Typed failure raised by {@link loadMessagingBridge}. */\nexport class MessagingBridgeLoadError extends Error {\n\treadonly code: MessagingBridgeLoadErrorCode;\n\n\tconstructor(\n\t\tmessage: string,\n\t\tcode: MessagingBridgeLoadErrorCode,\n\t\tcause?: unknown,\n\t) {\n\t\tsuper(message);\n\t\tthis.name = \"MessagingBridgeLoadError\";\n\t\tthis.code = code;\n\t\tif (cause !== undefined) {\n\t\t\tthis.cause = cause;\n\t\t}\n\t}\n}\n\n/** The module shape served by the SHA-matched bridge CDN asset. */\nexport interface MessagingBridgeModule {\n\tcreateBridgeClient: typeof createBridgeClient;\n\tSTATE: typeof STATE;\n\tmessageKey: typeof messageKey;\n\tisHistoricalRow: typeof isHistoricalRow;\n\tfindFirstUnread: typeof findFirstUnread;\n\tselectUnread: typeof selectUnread;\n\tgroupMessages: typeof groupMessages;\n\tfilterDisplayable: typeof filterDisplayable;\n\tresolveBotName: typeof resolveBotName;\n\tisAgentTyping: typeof isAgentTyping;\n\tisConnectivityLost: typeof isConnectivityLost;\n}\n\ntype ImportBridgeModule = (url: string) => Promise<unknown>;\n\n/** Options for {@link loadMessagingBridge}. */\nexport interface LoadMessagingBridgeOptions {\n\t/**\n\t * Asset base for staged validation. It must use HTTPS. Plain HTTP is\n\t * accepted only for loopback hosts.\n\t */\n\tcdnBase?: string;\n\t/** Import seam for unit tests. */\n\timportModule?: ImportBridgeModule;\n}\n\n// Both annotations are load-bearing. Customer bundlers must leave the\n// browser-resolved CDN URL untouched.\nconst defaultImportModule: ImportBridgeModule = (url) =>\n\timport(/* webpackIgnore: true */ /* @vite-ignore */ url);\n\nconst inFlightByImporter = new WeakMap<\n\tImportBridgeModule,\n\tMap<string, Promise<MessagingBridgeModule>>\n>();\n\nconst LOOPBACK_HOSTS = new Set([\"localhost\", \"127.0.0.1\", \"[::1]\", \"::1\"]);\n\nfunction isLoopbackHostname(hostname: string): boolean {\n\tconst normalized = hostname.trim().toLowerCase();\n\treturn LOOPBACK_HOSTS.has(normalized) || normalized.endsWith(\".localhost\");\n}\n\nfunction trimTrailingSlashes(value: string): string {\n\tlet end = value.length;\n\twhile (end > 0 && value[end - 1] === \"/\") {\n\t\tend -= 1;\n\t}\n\treturn value.slice(0, end);\n}\n\ntype BridgeBuild =\n\t| { kind: \"sha\"; identifier: string }\n\t| { kind: \"alias\"; identifier: string }\n\t| { kind: \"dev\" };\n\nfunction bridgeFrameOnlyMessage(message: string): string {\n\treturn `${message} The bridge only runs inside an Ada-mounted custom-app frame.`;\n}\n\nconst FRAME_NAME_MARKER_PREFIX = `${CUSTOM_APP_FRAME_NAME}:`;\n\nfunction frameNameCarriesBuild(name: string): boolean {\n\treturn name.startsWith(FRAME_NAME_MARKER_PREFIX);\n}\n\n/**\n * Read the build marker core stamps into the frame's `window.name`\n * (`ada-custom-app:<marker>`). The name is Ada-owned for this frame, so the\n * marker never touches customer-owned URL state such as a hash route.\n */\nexport function resolveBridgeBuildFromFrameName(name: string): BridgeBuild {\n\tif (name === CUSTOM_APP_FRAME_NAME) {\n\t\tthrow new MessagingBridgeLoadError(\n\t\t\t\"The mounting Ada core predates build-marker delivery, so it cannot select a versioned bridge. The Ada core rollout must include the marker-stamping core before the npm bridge can load.\",\n\t\t\t\"missing_build_sha\",\n\t\t);\n\t}\n\tif (!frameNameCarriesBuild(name)) {\n\t\tthrow new MessagingBridgeLoadError(\n\t\t\t`${bridgeFrameOnlyMessage(\n\t\t\t\t\"This document was not mounted by an Ada core frame: window.name carries no ada-custom-app build marker.\",\n\t\t\t)} If this frame is Ada-mounted, make sure @ada-cx/messaging-bridge (or its /react entry) is imported from a module your page evaluates at startup — a lazily loaded chunk evaluates after customer code could overwrite window.name.`,\n\t\t\t\"missing_build_sha\",\n\t\t);\n\t}\n\tconst value = name.slice(FRAME_NAME_MARKER_PREFIX.length);\n\tif (value === DEV_BUILD_MARKER) {\n\t\treturn { kind: \"dev\" };\n\t}\n\tif (PR_BUILD_ALIAS_PATTERN.test(value)) {\n\t\treturn { kind: \"alias\", identifier: value };\n\t}\n\tif (!PINNED_BUILD_SHA_PATTERN.test(value)) {\n\t\tthrow new MessagingBridgeLoadError(\n\t\t\tbridgeFrameOnlyMessage(\n\t\t\t\t`The window.name build marker must be exactly a bare 40-character hex git SHA or a lowercase pr-<N> alias. URLs and paths are not allowed: \"${value}\".`,\n\t\t\t),\n\t\t\t\"invalid_build_sha\",\n\t\t);\n\t}\n\treturn { kind: \"sha\", identifier: value.toLowerCase() };\n}\n\n// Latch window.name at module evaluation: core stamps the marker on the\n// frame element before the document loads, and customer code — for example a\n// library that uses window.name as a storage channel — could overwrite it\n// after boot. The latch therefore only holds when this package is imported\n// from the page's entry module rather than a lazily loaded chunk — the\n// boundary README_NPM.md documents and the snapshot tests in loader.test.ts\n// pin.\nconst initialFrameName = typeof window === \"undefined\" ? \"\" : window.name;\nconst initialFrameNameHasBuild = frameNameCarriesBuild(initialFrameName);\n\nfunction currentFrameName(): string {\n\tif (initialFrameNameHasBuild) {\n\t\treturn initialFrameName;\n\t}\n\treturn typeof window === \"undefined\" ? \"\" : window.name;\n}\n\nfunction resolveCdnBase(cdnBase: string | undefined): {\n\tbase: string;\n\thostname: string;\n} {\n\tconst base = trimTrailingSlashes((cdnBase ?? DEFAULT_CDN_BASE).trim());\n\tlet url: URL;\n\ttry {\n\t\turl = new URL(base);\n\t} catch (cause) {\n\t\tthrow new MessagingBridgeLoadError(\n\t\t\t`The cdnBase option is not a valid URL: \"${base}\".`,\n\t\t\t\"invalid_cdn_base\",\n\t\t\tcause,\n\t\t);\n\t}\n\tconst isHttps = url.protocol === \"https:\";\n\tconst isLoopbackHttp =\n\t\turl.protocol === \"http:\" && isLoopbackHostname(url.hostname);\n\tif (!isHttps && !isLoopbackHttp) {\n\t\tthrow new MessagingBridgeLoadError(\n\t\t\t`The cdnBase option must use https (plain http is allowed only for loopback hosts): \"${base}\".`,\n\t\t\t\"invalid_cdn_base\",\n\t\t);\n\t}\n\treturn { base, hostname: url.hostname };\n}\n\n/**\n * Resolve the bridge asset URL from this frame's name marker and the CDN\n * base. Production builds always resolve the immutable SHA-rooted asset.\n * PR aliases stay under the configured CDN base. A production-base alias that\n * does not exist fails closed through the normal bridge import error.\n * Local development resolves `/bridge/bridge.js` only on a loopback base.\n */\nexport function resolveBridgeCdnUrl(\n\toptions?: Pick<LoadMessagingBridgeOptions, \"cdnBase\">,\n): string {\n\tconst build = resolveBridgeBuildFromFrameName(currentFrameName());\n\tconst { base, hostname } = resolveCdnBase(options?.cdnBase);\n\tif (build.kind === \"dev\" && !isLoopbackHostname(hostname)) {\n\t\tthrow new MessagingBridgeLoadError(\n\t\t\tbridgeFrameOnlyMessage(\n\t\t\t\t`The ${FRAME_NAME_MARKER_PREFIX}${DEV_BUILD_MARKER} frame-name marker requires a loopback cdnBase.`,\n\t\t\t),\n\t\t\t\"dev_marker_not_loopback\",\n\t\t);\n\t}\n\tconst assetPath =\n\t\tbuild.kind === \"dev\"\n\t\t\t? `/${BRIDGE_ENTRY_PATH}`\n\t\t\t: `/${build.identifier}/${BRIDGE_ENTRY_PATH}`;\n\treturn new URL(`${base}${assetPath}`).toString();\n}\n\n// A partial CDN module (mid-deploy, or a rolled-back build missing a newer\n// export) must fail here as a typed bridge_module_invalid error, not later as\n// a TypeError on the first missing call — so every export is checked. The\n// `satisfies` clause rejects names that are not interface members; the\n// exhaustiveness type below is what forces this list to grow with it.\nconst REQUIRED_BRIDGE_FUNCTION_EXPORTS = [\n\t\"createBridgeClient\",\n\t\"messageKey\",\n\t\"isHistoricalRow\",\n\t\"findFirstUnread\",\n\t\"selectUnread\",\n\t\"groupMessages\",\n\t\"filterDisplayable\",\n\t\"resolveBotName\",\n\t\"isAgentTyping\",\n\t\"isConnectivityLost\",\n] as const satisfies readonly (keyof MessagingBridgeModule)[];\n\ntype MissingRequiredBridgeExport = Exclude<\n\tkeyof MessagingBridgeModule,\n\t(typeof REQUIRED_BRIDGE_FUNCTION_EXPORTS)[number] | \"STATE\"\n>;\n// Compile-time exhaustiveness: adding a MessagingBridgeModule member without\n// listing it in REQUIRED_BRIDGE_FUNCTION_EXPORTS (or checking it explicitly,\n// like STATE) makes the Exclude non-`never`, so the `true` assignment below\n// stops compiling.\ntype AllBridgeExportsListed = MissingRequiredBridgeExport extends never\n\t? true\n\t: never;\nconst _allBridgeExportsListed: AllBridgeExportsListed = true;\nvoid _allBridgeExportsListed;\n\nfunction isMessagingBridgeModule(\n\tvalue: unknown,\n): value is MessagingBridgeModule {\n\tif (typeof value !== \"object\" || value === null) {\n\t\treturn false;\n\t}\n\tconst candidate = value as Record<string, unknown>;\n\t// STATE is a constant object, not a function, so it gets a presence check.\n\treturn (\n\t\tcandidate.STATE != null &&\n\t\tREQUIRED_BRIDGE_FUNCTION_EXPORTS.every(\n\t\t\t(key) => typeof candidate[key] === \"function\",\n\t\t)\n\t);\n}\n\nasync function importBridgeModule(\n\turl: string,\n\timportModule: ImportBridgeModule,\n): Promise<MessagingBridgeModule> {\n\tlet loaded: unknown;\n\ttry {\n\t\tloaded = await importModule(url);\n\t} catch (cause) {\n\t\tthrow new MessagingBridgeLoadError(\n\t\t\t`The Ada bridge runtime failed to load from ${url}.`,\n\t\t\t\"bridge_import_failed\",\n\t\t\tcause,\n\t\t);\n\t}\n\tif (!isMessagingBridgeModule(loaded)) {\n\t\tthrow new MessagingBridgeLoadError(\n\t\t\t`The module at ${url} does not expose the full Ada bridge surface; it is not a complete Ada bridge build.`,\n\t\t\t\"bridge_module_invalid\",\n\t\t);\n\t}\n\treturn loaded;\n}\n\n/**\n * Load the bridge runtime that matches the mounting core frame. Loads are\n * memoized per resolved URL and importer. A failed load is evicted for retry.\n */\nexport function loadMessagingBridge(\n\toptions: LoadMessagingBridgeOptions = {},\n): Promise<MessagingBridgeModule> {\n\tconst importModule = options.importModule ?? defaultImportModule;\n\tlet url: string;\n\ttry {\n\t\turl = resolveBridgeCdnUrl(options);\n\t} catch (error) {\n\t\treturn Promise.reject(error);\n\t}\n\n\tlet inFlight = inFlightByImporter.get(importModule);\n\tif (!inFlight) {\n\t\tinFlight = new Map();\n\t\tinFlightByImporter.set(importModule, inFlight);\n\t}\n\tconst existing = inFlight.get(url);\n\tif (existing) {\n\t\treturn existing;\n\t}\n\n\tconst cache = inFlight;\n\tconst pending = importBridgeModule(url, importModule);\n\tcache.set(url, pending);\n\tpending.catch(() => {\n\t\tif (cache.get(url) === pending) {\n\t\t\tcache.delete(url);\n\t\t}\n\t});\n\treturn pending;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"loader-CfCnvaTU.js","names":[],"sources":["../src/npm/loader.ts"],"sourcesContent":["import type { createBridgeClient } from \"../contract/bridge-client\";\nimport type {\n\tfilterDisplayable,\n\tfindFirstUnread,\n\tgroupMessages,\n\tisAgentTyping,\n\tisConnectivityLost,\n\tisHistoricalRow,\n\tmessageKey,\n\tresolveBotName,\n\tresolveDisplayBotName,\n\tselectUnread,\n} from \"../contract/derive\";\nimport type { STATE } from \"../contract/state-keys\";\n\nconst DEFAULT_CDN_BASE = \"https://messaging-assets.ada.support\";\n// Keep aligned with packages/core/src/app-bridge.ts; pinned by\n// scripts/core-html-contract.test.mjs.\nconst CUSTOM_APP_FRAME_NAME = \"ada-custom-app\";\nconst BRIDGE_ENTRY_PATH = \"bridge/bridge.js\";\nconst DEV_BUILD_MARKER = \"dev\";\n\n// Only canonical build identifiers may become part of an executable asset URL.\nconst PINNED_BUILD_SHA_PATTERN = /^[0-9a-f]{40}$/i;\n// Deliberately narrower than scripts/sdk.js parseVersionOverride (7-40 hex,\n// case-insensitive): deployed keys are always lowercase `pr-<N>`, and a short\n// SHA never names an uploaded tree, so the narrower grammar loses no reachable\n// build while keeping the executable-path grammar minimal.\nconst PR_BUILD_ALIAS_PATTERN = /^pr-\\d+$/;\n\n/** Why a {@link MessagingBridgeLoadError} was raised. */\nexport type MessagingBridgeLoadErrorCode =\n\t/** The `cdnBase` option violates the HTTPS or loopback HTTP policy. */\n\t| \"invalid_cdn_base\"\n\t/** The frame's `window.name` carries no core build marker. */\n\t| \"missing_build_sha\"\n\t/** The frame-name marker is not a supported bare build identifier. */\n\t| \"invalid_build_sha\"\n\t/** The `dev` marker was paired with a non-loopback CDN base. */\n\t| \"dev_marker_not_loopback\"\n\t/** The dynamic import of the CDN asset failed. */\n\t| \"bridge_import_failed\"\n\t/** The imported module does not expose the Ada bridge surface. */\n\t| \"bridge_module_invalid\";\n\n/** Typed failure raised by {@link loadMessagingBridge}. */\nexport class MessagingBridgeLoadError extends Error {\n\treadonly code: MessagingBridgeLoadErrorCode;\n\n\tconstructor(\n\t\tmessage: string,\n\t\tcode: MessagingBridgeLoadErrorCode,\n\t\tcause?: unknown,\n\t) {\n\t\tsuper(message);\n\t\tthis.name = \"MessagingBridgeLoadError\";\n\t\tthis.code = code;\n\t\tif (cause !== undefined) {\n\t\t\tthis.cause = cause;\n\t\t}\n\t}\n}\n\n/** The module shape served by the SHA-matched bridge CDN asset. */\nexport interface MessagingBridgeModule {\n\tcreateBridgeClient: typeof createBridgeClient;\n\tSTATE: typeof STATE;\n\tmessageKey: typeof messageKey;\n\tisHistoricalRow: typeof isHistoricalRow;\n\tfindFirstUnread: typeof findFirstUnread;\n\tselectUnread: typeof selectUnread;\n\tgroupMessages: typeof groupMessages;\n\tfilterDisplayable: typeof filterDisplayable;\n\tresolveBotName: typeof resolveBotName;\n\t/**\n\t * Absent on a CDN build published before this helper existed. The npm\n\t * version does not choose the runtime, so guard before calling it.\n\t */\n\tresolveDisplayBotName?: typeof resolveDisplayBotName;\n\tisAgentTyping: typeof isAgentTyping;\n\tisConnectivityLost: typeof isConnectivityLost;\n}\n\ntype ImportBridgeModule = (url: string) => Promise<unknown>;\n\n/** Options for {@link loadMessagingBridge}. */\nexport interface LoadMessagingBridgeOptions {\n\t/**\n\t * Asset base for staged validation. It must use HTTPS. Plain HTTP is\n\t * accepted only for loopback hosts.\n\t */\n\tcdnBase?: string;\n\t/** Import seam for unit tests. */\n\timportModule?: ImportBridgeModule;\n}\n\n// Both annotations are load-bearing. Customer bundlers must leave the\n// browser-resolved CDN URL untouched.\nconst defaultImportModule: ImportBridgeModule = (url) =>\n\timport(/* webpackIgnore: true */ /* @vite-ignore */ url);\n\nconst inFlightByImporter = new WeakMap<\n\tImportBridgeModule,\n\tMap<string, Promise<MessagingBridgeModule>>\n>();\n\nconst LOOPBACK_HOSTS = new Set([\"localhost\", \"127.0.0.1\", \"[::1]\", \"::1\"]);\n\nfunction isLoopbackHostname(hostname: string): boolean {\n\tconst normalized = hostname.trim().toLowerCase();\n\treturn LOOPBACK_HOSTS.has(normalized) || normalized.endsWith(\".localhost\");\n}\n\nfunction trimTrailingSlashes(value: string): string {\n\tlet end = value.length;\n\twhile (end > 0 && value[end - 1] === \"/\") {\n\t\tend -= 1;\n\t}\n\treturn value.slice(0, end);\n}\n\ntype BridgeBuild =\n\t| { kind: \"sha\"; identifier: string }\n\t| { kind: \"alias\"; identifier: string }\n\t| { kind: \"dev\" };\n\nfunction bridgeFrameOnlyMessage(message: string): string {\n\treturn `${message} The bridge only runs inside an Ada-mounted custom-app frame.`;\n}\n\nconst FRAME_NAME_MARKER_PREFIX = `${CUSTOM_APP_FRAME_NAME}:`;\n\nfunction frameNameCarriesBuild(name: string): boolean {\n\treturn name.startsWith(FRAME_NAME_MARKER_PREFIX);\n}\n\n/**\n * Read the build marker core stamps into the frame's `window.name`\n * (`ada-custom-app:<marker>`). The name is Ada-owned for this frame, so the\n * marker never touches customer-owned URL state such as a hash route.\n */\nexport function resolveBridgeBuildFromFrameName(name: string): BridgeBuild {\n\tif (name === CUSTOM_APP_FRAME_NAME) {\n\t\tthrow new MessagingBridgeLoadError(\n\t\t\t\"The mounting Ada core predates build-marker delivery, so it cannot select a versioned bridge. The Ada core rollout must include the marker-stamping core before the npm bridge can load.\",\n\t\t\t\"missing_build_sha\",\n\t\t);\n\t}\n\tif (!frameNameCarriesBuild(name)) {\n\t\tthrow new MessagingBridgeLoadError(\n\t\t\t`${bridgeFrameOnlyMessage(\n\t\t\t\t\"This document was not mounted by an Ada core frame: window.name carries no ada-custom-app build marker.\",\n\t\t\t)} If this frame is Ada-mounted, make sure @ada-cx/messaging-bridge (or its /react entry) is imported from a module your page evaluates at startup — a lazily loaded chunk evaluates after customer code could overwrite window.name.`,\n\t\t\t\"missing_build_sha\",\n\t\t);\n\t}\n\tconst value = name.slice(FRAME_NAME_MARKER_PREFIX.length);\n\tif (value === DEV_BUILD_MARKER) {\n\t\treturn { kind: \"dev\" };\n\t}\n\tif (PR_BUILD_ALIAS_PATTERN.test(value)) {\n\t\treturn { kind: \"alias\", identifier: value };\n\t}\n\tif (!PINNED_BUILD_SHA_PATTERN.test(value)) {\n\t\tthrow new MessagingBridgeLoadError(\n\t\t\tbridgeFrameOnlyMessage(\n\t\t\t\t`The window.name build marker must be exactly a bare 40-character hex git SHA or a lowercase pr-<N> alias. URLs and paths are not allowed: \"${value}\".`,\n\t\t\t),\n\t\t\t\"invalid_build_sha\",\n\t\t);\n\t}\n\treturn { kind: \"sha\", identifier: value.toLowerCase() };\n}\n\n// Latch window.name at module evaluation: core stamps the marker on the\n// frame element before the document loads, and customer code — for example a\n// library that uses window.name as a storage channel — could overwrite it\n// after boot. The latch therefore only holds when this package is imported\n// from the page's entry module rather than a lazily loaded chunk — the\n// boundary README_NPM.md documents and the snapshot tests in loader.test.ts\n// pin.\nconst initialFrameName = typeof window === \"undefined\" ? \"\" : window.name;\nconst initialFrameNameHasBuild = frameNameCarriesBuild(initialFrameName);\n\nfunction currentFrameName(): string {\n\tif (initialFrameNameHasBuild) {\n\t\treturn initialFrameName;\n\t}\n\treturn typeof window === \"undefined\" ? \"\" : window.name;\n}\n\nfunction resolveCdnBase(cdnBase: string | undefined): {\n\tbase: string;\n\thostname: string;\n} {\n\tconst base = trimTrailingSlashes((cdnBase ?? DEFAULT_CDN_BASE).trim());\n\tlet url: URL;\n\ttry {\n\t\turl = new URL(base);\n\t} catch (cause) {\n\t\tthrow new MessagingBridgeLoadError(\n\t\t\t`The cdnBase option is not a valid URL: \"${base}\".`,\n\t\t\t\"invalid_cdn_base\",\n\t\t\tcause,\n\t\t);\n\t}\n\tconst isHttps = url.protocol === \"https:\";\n\tconst isLoopbackHttp =\n\t\turl.protocol === \"http:\" && isLoopbackHostname(url.hostname);\n\tif (!isHttps && !isLoopbackHttp) {\n\t\tthrow new MessagingBridgeLoadError(\n\t\t\t`The cdnBase option must use https (plain http is allowed only for loopback hosts): \"${base}\".`,\n\t\t\t\"invalid_cdn_base\",\n\t\t);\n\t}\n\treturn { base, hostname: url.hostname };\n}\n\n/**\n * Resolve the bridge asset URL from this frame's name marker and the CDN\n * base. Production builds always resolve the immutable SHA-rooted asset.\n * PR aliases stay under the configured CDN base. A production-base alias that\n * does not exist fails closed through the normal bridge import error.\n * Local development resolves `/bridge/bridge.js` only on a loopback base.\n */\nexport function resolveBridgeCdnUrl(\n\toptions?: Pick<LoadMessagingBridgeOptions, \"cdnBase\">,\n): string {\n\tconst build = resolveBridgeBuildFromFrameName(currentFrameName());\n\tconst { base, hostname } = resolveCdnBase(options?.cdnBase);\n\tif (build.kind === \"dev\" && !isLoopbackHostname(hostname)) {\n\t\tthrow new MessagingBridgeLoadError(\n\t\t\tbridgeFrameOnlyMessage(\n\t\t\t\t`The ${FRAME_NAME_MARKER_PREFIX}${DEV_BUILD_MARKER} frame-name marker requires a loopback cdnBase.`,\n\t\t\t),\n\t\t\t\"dev_marker_not_loopback\",\n\t\t);\n\t}\n\tconst assetPath =\n\t\tbuild.kind === \"dev\"\n\t\t\t? `/${BRIDGE_ENTRY_PATH}`\n\t\t\t: `/${build.identifier}/${BRIDGE_ENTRY_PATH}`;\n\treturn new URL(`${base}${assetPath}`).toString();\n}\n\n// A partial CDN module (mid-deploy, or a rolled-back build missing a newer\n// export) must fail here as a typed bridge_module_invalid error, not later as\n// a TypeError on the first missing call — so every export is checked. The\n// `satisfies` clause rejects names that are not interface members; the\n// exhaustiveness type below is what forces this list to grow with it.\n//\n// This list is the set published with 1.0.0, and adding to it is a breaking\n// change even though adding an export is not. A page's runtime build comes from\n// the core frame's `window.name`, never from the installed npm version, so a\n// 1.x consumer legitimately resolves an older bridge — during a rollout, behind\n// an `?ada-messaging-version` pin, or after a CDN revert. Requiring a newer\n// export would turn every one of those into \"no bridge at all\" for a customer\n// who never calls it. Declare a helper added after 1.0.0 optional on\n// `MessagingBridgeModule` and exempt it below instead.\nconst REQUIRED_BRIDGE_FUNCTION_EXPORTS = [\n\t\"createBridgeClient\",\n\t\"messageKey\",\n\t\"isHistoricalRow\",\n\t\"findFirstUnread\",\n\t\"selectUnread\",\n\t\"groupMessages\",\n\t\"filterDisplayable\",\n\t\"resolveBotName\",\n\t\"isAgentTyping\",\n\t\"isConnectivityLost\",\n] as const satisfies readonly (keyof MessagingBridgeModule)[];\n\ntype MissingRequiredBridgeExport = Exclude<\n\tkeyof MessagingBridgeModule,\n\t| (typeof REQUIRED_BRIDGE_FUNCTION_EXPORTS)[number]\n\t| \"STATE\"\n\t// Optional by design: added after 1.0.0, so an older runtime will not have\n\t// it. Callers get `undefined` and degrade; they do not lose the bridge.\n\t| \"resolveDisplayBotName\"\n>;\n// Compile-time exhaustiveness: adding a MessagingBridgeModule member without\n// listing it in REQUIRED_BRIDGE_FUNCTION_EXPORTS (or checking it explicitly,\n// like STATE) makes the Exclude non-`never`, so the `true` assignment below\n// stops compiling.\ntype AllBridgeExportsListed = MissingRequiredBridgeExport extends never\n\t? true\n\t: never;\nconst _allBridgeExportsListed: AllBridgeExportsListed = true;\nvoid _allBridgeExportsListed;\n\nfunction isMessagingBridgeModule(\n\tvalue: unknown,\n): value is MessagingBridgeModule {\n\tif (typeof value !== \"object\" || value === null) {\n\t\treturn false;\n\t}\n\tconst candidate = value as Record<string, unknown>;\n\t// STATE is a constant object, not a function, so it gets a presence check.\n\treturn (\n\t\tcandidate.STATE != null &&\n\t\tREQUIRED_BRIDGE_FUNCTION_EXPORTS.every(\n\t\t\t(key) => typeof candidate[key] === \"function\",\n\t\t)\n\t);\n}\n\nasync function importBridgeModule(\n\turl: string,\n\timportModule: ImportBridgeModule,\n): Promise<MessagingBridgeModule> {\n\tlet loaded: unknown;\n\ttry {\n\t\tloaded = await importModule(url);\n\t} catch (cause) {\n\t\tthrow new MessagingBridgeLoadError(\n\t\t\t`The Ada bridge runtime failed to load from ${url}.`,\n\t\t\t\"bridge_import_failed\",\n\t\t\tcause,\n\t\t);\n\t}\n\tif (!isMessagingBridgeModule(loaded)) {\n\t\tthrow new MessagingBridgeLoadError(\n\t\t\t`The module at ${url} does not expose the full Ada bridge surface; it is not a complete Ada bridge build.`,\n\t\t\t\"bridge_module_invalid\",\n\t\t);\n\t}\n\treturn loaded;\n}\n\n/**\n * Load the bridge runtime that matches the mounting core frame. Loads are\n * memoized per resolved URL and importer. A failed load is evicted for retry.\n */\nexport function loadMessagingBridge(\n\toptions: LoadMessagingBridgeOptions = {},\n): Promise<MessagingBridgeModule> {\n\tconst importModule = options.importModule ?? defaultImportModule;\n\tlet url: string;\n\ttry {\n\t\turl = resolveBridgeCdnUrl(options);\n\t} catch (error) {\n\t\treturn Promise.reject(error);\n\t}\n\n\tlet inFlight = inFlightByImporter.get(importModule);\n\tif (!inFlight) {\n\t\tinFlight = new Map();\n\t\tinFlightByImporter.set(importModule, inFlight);\n\t}\n\tconst existing = inFlight.get(url);\n\tif (existing) {\n\t\treturn existing;\n\t}\n\n\tconst cache = inFlight;\n\tconst pending = importBridgeModule(url, importModule);\n\tcache.set(url, pending);\n\tpending.catch(() => {\n\t\tif (cache.get(url) === pending) {\n\t\t\tcache.delete(url);\n\t\t}\n\t});\n\treturn pending;\n}\n"],"mappings":"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"}
@@ -11,6 +11,6 @@ export type { BridgeClient } from "../contract/bridge-client.js";
11
11
  export type { MessageGroupingEntry, UnreadSelection } from "../contract/derive.js";
12
12
  export type { BridgeClientDestroyedError, BridgeOperations, BridgeOperationsHost, CaptureHandle, CsatSubmitHandle, EndChatEligibilityResult, FileUploadHandle, ObserveOptions, OperationResult, SendHandle, SettleOptions, } from "../contract/operations.js";
13
13
  export type { StateKey } from "../contract/state-keys.js";
14
- export type { AppDisplayState, AppEvents, CaptureData, CsatMessage, CsatScaleType, DownloadableMessage, FallbackUiConfig, LinkMessage, ListSelectionData, LiveChatStateData, Message, NotificationPermissionMessage, OptionItem, OptionsMessage, PictureMessage, PresenceMessage, QueueBotBlockMessage, QuickRepliesMessage, QuickReply, SdkPlatform, SeamlessOAuthMessage, SelectableListItem, SelectableListMessage, SignInMessage, TextMessage, VideoMessage, WidgetMessage, } from "../contract/types.js";
14
+ export type { AppDisplayState, AppEvents, CaptureData, CsatMessage, CsatScaleType, DownloadableMessage, FallbackUiConfig, LinkMessage, ListSelectionData, LiveChatStateData, Message, NotificationPermissionMessage, OptionItem, OptionsMessage, PictureMessage, PresenceMessage, QueueBotBlockMessage, QuickRepliesMessage, QuickReply, SdkPlatform, SeamlessOAuthMessage, SelectableListItem, SelectableListMessage, SignInMessage, StagedAttachment, StagedAttachmentRejection, TextMessage, VideoMessage, WidgetMessage, } from "../contract/types.js";
15
15
  export type { LoadMessagingBridgeOptions, MessagingBridgeLoadErrorCode, MessagingBridgeModule, } from "./loader.js";
16
16
  export { loadMessagingBridge, MessagingBridgeLoadError, resolveBridgeCdnUrl, } from "./loader.js";
@@ -1,5 +1,5 @@
1
1
  import type { createBridgeClient } from "../contract/bridge-client.js";
2
- import type { filterDisplayable, findFirstUnread, groupMessages, isAgentTyping, isConnectivityLost, isHistoricalRow, messageKey, resolveBotName, selectUnread } from "../contract/derive.js";
2
+ import type { filterDisplayable, findFirstUnread, groupMessages, isAgentTyping, isConnectivityLost, isHistoricalRow, messageKey, resolveBotName, resolveDisplayBotName, selectUnread } from "../contract/derive.js";
3
3
  import type { STATE } from "../contract/state-keys.js";
4
4
  /** Why a {@link MessagingBridgeLoadError} was raised. */
5
5
  export type MessagingBridgeLoadErrorCode =
@@ -31,6 +31,11 @@ export interface MessagingBridgeModule {
31
31
  groupMessages: typeof groupMessages;
32
32
  filterDisplayable: typeof filterDisplayable;
33
33
  resolveBotName: typeof resolveBotName;
34
+ /**
35
+ * Absent on a CDN build published before this helper existed. The npm
36
+ * version does not choose the runtime, so guard before calling it.
37
+ */
38
+ resolveDisplayBotName?: typeof resolveDisplayBotName;
34
39
  isAgentTyping: typeof isAgentTyping;
35
40
  isConnectivityLost: typeof isConnectivityLost;
36
41
  }
@@ -22,16 +22,16 @@ function T(a, s, t, i) {
22
22
  f(new B());
23
23
  return;
24
24
  }
25
- let m = !1, g = null, p = null;
25
+ let m = !1, g = null, v = null;
26
26
  const S = (o) => {
27
27
  C({ error: o });
28
28
  }, C = (o) => {
29
- m || (m = !0, s.unregister(S), g?.(), p !== null && clearTimeout(p), "value" in o ? d(o.value) : f(o.error));
29
+ m || (m = !0, s.unregister(S), g?.(), v !== null && clearTimeout(v), "value" in o ? d(o.value) : f(o.error));
30
30
  }, q = () => {
31
31
  const o = t(a.getState());
32
32
  o && C(o);
33
33
  };
34
- q(), !m && (s.register(S), g = a.subscribe(q), i?.timeoutMs !== void 0 && (p = setTimeout(() => {
34
+ q(), !m && (s.register(S), g = a.subscribe(q), i?.timeoutMs !== void 0 && (v = setTimeout(() => {
35
35
  C({ error: /* @__PURE__ */ new Error(`Bridge operation timed out after ${i.timeoutMs}ms.`) });
36
36
  }, i.timeoutMs)));
37
37
  });
@@ -41,10 +41,10 @@ function O(a) {
41
41
  const m = f?.equals ?? Object.is;
42
42
  let g = i(a.getState());
43
43
  return f?.emitInitial === !0 && d(g, void 0), a.subscribe(() => {
44
- const p = i(a.getState());
45
- if (m(p, g)) return;
44
+ const v = i(a.getState());
45
+ if (m(v, g)) return;
46
46
  const S = g;
47
- g = p, d(p, S);
47
+ g = v, d(v, S);
48
48
  });
49
49
  }
50
50
  function t(i, d, f) {
@@ -73,7 +73,7 @@ function A(a) {
73
73
  i.delete(e);
74
74
  }
75
75
  };
76
- let f = "", m = "", g = null, p = null, S = null;
76
+ let f = "", m = "", g = null, v = null, S = null;
77
77
  function C() {
78
78
  f = "", m = "", g !== null && (clearTimeout(g), g = null);
79
79
  }
@@ -82,40 +82,40 @@ function A(a) {
82
82
  let e = t.getState()?.["chat.conversationId"] ?? null;
83
83
  S = t.subscribe(() => {
84
84
  const r = t.getState()?.["chat.conversationId"] ?? null;
85
- r !== e && (e = r, C(), p = r);
85
+ r !== e && (e = r, C(), v = r);
86
86
  });
87
87
  }
88
88
  function o() {
89
89
  g === null && (g = setTimeout(() => {
90
90
  g = null, f > m && (m = f, t.sendEvent("chat.messages.read", {
91
91
  cursor: f,
92
- ...p ? { conversationId: p } : {}
92
+ ...v ? { conversationId: v } : {}
93
93
  }));
94
94
  }, L));
95
95
  }
96
96
  let w = 0;
97
97
  const D = {
98
98
  sendMessage(e, r) {
99
- const n = typeof e == "string" ? e.trim() : "", h = r?.secret === !0, v = R();
99
+ const n = typeof e == "string" ? e.trim() : "", h = r?.secret === !0, p = R();
100
100
  let y = null, b = null;
101
101
  if (n) {
102
102
  const l = t.getState();
103
103
  y = l?.["chat.error"] ?? null, b = l?.["chat.error.seq"] ?? null, t.sendEvent("chat.message.send", {
104
104
  body: n,
105
105
  messageType: h ? "secret" : "text",
106
- tempMessageUuid: v
106
+ tempMessageUuid: p
107
107
  });
108
108
  }
109
109
  return {
110
- tempMessageUuid: v,
110
+ tempMessageUuid: p,
111
111
  settled(l) {
112
112
  return n ? h ? T(t, d, () => {
113
113
  }, l) : T(t, d, (c) => {
114
- const I = (c?.["chat.messages"] ?? []).find((M) => M.clientKey === v);
114
+ const I = (c?.["chat.messages"] ?? []).find((M) => M.clientKey === p);
115
115
  if (I) return { value: I };
116
116
  const u = c?.["chat.error"] ?? null;
117
117
  if (c !== null && "chat.error.clientKey" in c)
118
- return u !== null && c["chat.error.clientKey"] === v ? { error: new Error(u) } : void 0;
118
+ return u !== null && c["chat.error.clientKey"] === p ? { error: new Error(u) } : void 0;
119
119
  const E = c?.["chat.error.seq"];
120
120
  if (typeof E == "number") {
121
121
  if (b === null) {
@@ -183,7 +183,7 @@ function A(a) {
183
183
  dataId: n,
184
184
  submitId: h
185
185
  });
186
- let v = !1;
186
+ let p = !1;
187
187
  return { settled: (y) => T(t, d, (b) => {
188
188
  const l = b?.["conversation.stateData.capture"] ?? null;
189
189
  if (!l || l.dataId !== n) return { value: { ok: !0 } };
@@ -193,10 +193,10 @@ function A(a) {
193
193
  message: c.message
194
194
  } };
195
195
  if (l.isSubmitting === !0) {
196
- v = !0;
196
+ p = !0;
197
197
  return;
198
198
  }
199
- if (v) return l.validationErrorMessage ? { value: {
199
+ if (p) return l.validationErrorMessage ? { value: {
200
200
  ok: !1,
201
201
  message: l.validationErrorMessage
202
202
  } } : { value: { ok: !0 } };
@@ -226,7 +226,7 @@ function A(a) {
226
226
  if (typeof e != "string" || e === "") return;
227
227
  q();
228
228
  const n = r?.conversationId ?? t.getState()?.["chat.conversationId"] ?? null;
229
- n !== p && (C(), p = n), e > f && (f = e, o());
229
+ n !== v && (C(), v = n), e > f && (f = e, o());
230
230
  },
231
231
  loadOlderMessages() {
232
232
  const e = t.getState();
@@ -240,15 +240,15 @@ function A(a) {
240
240
  });
241
241
  },
242
242
  submitCsat(e, r) {
243
- const { surveyType: n, partial: h, conversationId: v } = r, y = t.getState()?.["csat.submitSuccess.seq"] ?? 0;
243
+ const { surveyType: n, partial: h, conversationId: p } = r, y = t.getState()?.["csat.submitSuccess.seq"] ?? 0;
244
244
  let b = t.getState()?.["csat.submitError"] ?? null, l = t.getState()?.["csat.submitError.seq"] ?? null;
245
245
  t.sendEvent("csat.submit", {
246
246
  data: e,
247
247
  surveyType: n,
248
248
  ...h !== void 0 ? { partial: h } : {},
249
- ...v != null ? { conversationId: v } : {}
249
+ ...p != null ? { conversationId: p } : {}
250
250
  });
251
- const c = (I) => v == null || (I ?? null) === v;
251
+ const c = (I) => p == null || (I ?? null) === p;
252
252
  return { settled: (I) => T(t, d, (u) => {
253
253
  if ((u?.["csat.submitSuccess.seq"] ?? 0) > y && u?.["csat.submitSuccess.surveyType"] === n && c(u?.["csat.submitSuccess.conversationId"])) return { value: { ok: !0 } };
254
254
  const E = u?.["csat.submitError"] ?? null, M = u?.["csat.submitError.seq"];
@@ -291,6 +291,9 @@ function A(a) {
291
291
  skipCsatAndEndChat() {
292
292
  t.sendEvent("endChat.skipCsat");
293
293
  },
294
+ endLiveChat() {
295
+ t.sendEvent("endChat.endLiveChat");
296
+ },
294
297
  startNewConversation(e) {
295
298
  const r = Date.now();
296
299
  return r < w ? !1 : (w = r + (e?.cooldownMs ?? x), t.sendEvent("app.startNewConversation"), !0);
@@ -373,7 +376,7 @@ function A(a) {
373
376
  };
374
377
  function k(e, r, n) {
375
378
  const h = `${e}.seq`;
376
- let v = t.getState()?.[e] ?? "idle", y = t.getState()?.[h] ?? null, b = t.getState()?.["transcript.email.conflict.seq"] ?? null;
379
+ let p = t.getState()?.[e] ?? "idle", y = t.getState()?.[h] ?? null, b = t.getState()?.["transcript.email.conflict.seq"] ?? null;
377
380
  return T(t, d, (l) => {
378
381
  if (n !== void 0) {
379
382
  const E = l?.["transcript.email.conflict.seq"];
@@ -390,8 +393,8 @@ function A(a) {
390
393
  }
391
394
  return I === y ? void 0 : c === "success" ? { value: void 0 } : c === "error" ? { error: /* @__PURE__ */ new Error(`${e} reported an error.`) } : void 0;
392
395
  }
393
- const u = v;
394
- if (v = c, u === "pending") {
396
+ const u = p;
397
+ if (p = c, u === "pending") {
395
398
  if (c === "success") return { value: void 0 };
396
399
  if (c === "error") return { error: /* @__PURE__ */ new Error(`${e} reported an error.`) };
397
400
  }
@@ -426,7 +429,7 @@ function U(a = null) {
426
429
  i.delete(o);
427
430
  };
428
431
  }
429
- function p(o, ...w) {
432
+ function v(o, ...w) {
430
433
  t || d.push({
431
434
  event: o,
432
435
  payload: w[0]
@@ -435,12 +438,12 @@ function U(a = null) {
435
438
  const S = {
436
439
  getState: m,
437
440
  subscribe: g,
438
- sendEvent: p
441
+ sendEvent: v
439
442
  }, C = O(S), q = A(S);
440
443
  return {
441
444
  getState: m,
442
445
  subscribe: g,
443
- sendEvent: p,
446
+ sendEvent: v,
444
447
  subscribeKey: C.subscribeKey,
445
448
  select: C.select,
446
449
  operations: q.operations,
@@ -1 +1 @@
1
- {"version":3,"file":"testing.js","names":[],"sources":["../../src/contract/operations.ts","../../src/npm/testing.ts"],"sourcesContent":["import type {\n\tAppDisplayState,\n\tAppEvents,\n\tCsatSubmitData,\n\tMessage,\n\tOptionItem,\n\tQuickReply,\n} from \"./types\";\n\n/**\n * `client.operations` — typed helpers over the raw event/state contract,\n * mined from Ada's reference app so custom apps inherit its validated\n * behavior (debounces, guards, correlation) instead of reimplementing it.\n *\n * Implementation note: the npm test mock and CDN runtime share this module.\n * The mock reuses it over its own in-memory host so mocked handles resolve\n * from scripted state, but the published tarball never carries transport\n * logic — every helper here speaks through `sendEvent`/`subscribe` alone.\n */\n\n/** Debounce window for {@link BridgeOperations.markRead} reports. */\nconst READ_REPORT_DEBOUNCE_MS = 400;\n/** Default {@link BridgeOperations.startNewConversation} cooldown. */\nconst START_NEW_CONVERSATION_COOLDOWN_MS = 5_000;\n\n/**\n * The minimal client surface the operations layer builds on. Satisfied by the\n * real `BridgeClient` and by the npm test mock.\n */\nexport interface BridgeOperationsHost {\n\tgetState(): AppDisplayState | null;\n\tsubscribe(callback: () => void): () => void;\n\tsendEvent<K extends keyof AppEvents>(\n\t\tevent: K,\n\t\t...args: AppEvents[K] extends undefined ? [] : [payload: AppEvents[K]]\n\t): void;\n}\n\n/** Outcome of a correlated submit: accepted, or rejected with a message. */\nexport type OperationResult = { ok: true } | { ok: false; message: string };\n\nexport interface SettleOptions {\n\t/** Reject with an `Error` if the operation has not settled in time. */\n\ttimeoutMs?: number;\n}\n\n/**\n * Rejection every in-flight `settled()` promise receives when the client is\n * destroyed, so teardown is the layer's bound of last resort even without a\n * `timeoutMs`. The class identity does not survive the npm/CDN module split,\n * so detect it by `error.name === \"BridgeClientDestroyedError\"` (or via\n * {@link BridgeClientDestroyedError.is}), never by `instanceof` alone.\n */\nexport class BridgeClientDestroyedError extends Error {\n\tconstructor(message = \"The bridge client was destroyed.\") {\n\t\tsuper(message);\n\t\tthis.name = \"BridgeClientDestroyedError\";\n\t}\n\n\tstatic is(error: unknown): error is BridgeClientDestroyedError {\n\t\treturn (\n\t\t\terror instanceof Error && error.name === \"BridgeClientDestroyedError\"\n\t\t);\n\t}\n}\n\nexport interface SendHandle {\n\t/**\n\t * The uuid put on the wire as `tempMessageUuid`. Core seeds the optimistic\n\t * message's `clientKey` from it, so the sent message is findable in\n\t * `chat.messages` by `clientKey === tempMessageUuid`.\n\t */\n\treadonly tempMessageUuid: string;\n\t/**\n\t * Resolves with the message once it appears in `chat.messages` (the\n\t * optimistic row; the durable server echo keeps the same `clientKey`).\n\t * Rejects when core reports THIS send's error before the row appears —\n\t * rate limit, over-length, session not ready, dispatch failure —\n\t * correlated exactly on `chat.error.clientKey` matching the minted\n\t * `tempMessageUuid`, so an unrelated failure in the same window never\n\t * falsely rejects a delivered send. On a core document that predates\n\t * `chat.error.clientKey`, falls back to the positional\n\t * `chat.error.seq`-advance detection, and before that to `chat.error`\n\t * text transitions. A secret send NEVER resolves — core deliberately\n\t * writes no transcript row for it — so pass `timeoutMs` or skip\n\t * `settled()` for secret sends. Destroying the client rejects every\n\t * pending `settled()` with a {@link BridgeClientDestroyedError}.\n\t */\n\tsettled(opts?: SettleOptions): Promise<Message>;\n}\n\nexport interface CaptureHandle {\n\t/**\n\t * Resolves on the verdict for THIS capture submit: `{ ok: true }` when the\n\t * value was accepted (core removes the capture block), or\n\t * `{ ok: false, message }` when it was rejected — a server validation\n\t * rejection (observed on the `isSubmitting` falling edge, with the\n\t * validation error text), or a failure/drop core stamps on the capture's\n\t * `lastSubmitFailure` keyed to this submit's own minted `submitId` (no\n\t * session to round-trip, value over the length cap, transport failure, a\n\t * duplicate submit while one is in flight, a host-driven reset). The\n\t * correlation is exact: the handle never reads the global `chat.error`,\n\t * so an unrelated failure in the same window can never masquerade as this\n\t * capture's verdict. On a core document that predates `lastSubmitFailure`,\n\t * those failures are not capture-attributable at all and the handle stays\n\t * pending — pass `timeoutMs` when targeting older cores.\n\t */\n\tsettled(opts?: SettleOptions): Promise<OperationResult>;\n}\n\nexport interface CsatSubmitHandle {\n\t/**\n\t * Resolves when core reports this survey's outcome, correlated on the\n\t * success seq advance x surveyType x conversationId triple (and the\n\t * mirror-image error triple). A `partial` submit reports no terminal\n\t * outcome, so `settled()` on one only settles via `timeoutMs`.\n\t */\n\tsettled(opts?: SettleOptions): Promise<OperationResult>;\n}\n\nexport interface FileUploadHandle {\n\t/** The minted upload id sent with `file.upload.start`. */\n\treadonly uploadId: string;\n\t/**\n\t * Re-send the EXACT same `{ file, uploadId }` pair. Core correlates the\n\t * retry to the failed attempt by that pair, so a fresh pair would orphan\n\t * the original error state.\n\t */\n\tretry(): void;\n\t/** Dismiss a reported upload error (`file.uploadError`). */\n\tdismissError(): void;\n}\n\nexport interface EndChatEligibilityResult {\n\t/** `csat.endChatEligible` at the moment the answer arrived. */\n\teligible: boolean | null;\n\t/** Which survey End Chat should present, when eligible. */\n\tsurveyTarget: \"bot\" | \"agent\" | null;\n}\n\nexport interface ObserveOptions<T> {\n\t/** Change detector; defaults to `Object.is`. */\n\tequals?: (a: T, b: T) => boolean;\n\t/** Also invoke the callback immediately with the current value. */\n\temitInitial?: boolean;\n}\n\n/**\n * Typed helpers over the bridge event/state contract. Obtain via\n * `client.operations`. Fire-and-forget helpers return `void`; correlated\n * helpers return a handle or Promise that settles from state transitions\n * (there is no request/response channel — core answers through state).\n */\nexport interface BridgeOperations {\n\t/**\n\t * Send a chat message. The body is trimmed; an empty result sends nothing\n\t * (the returned handle then rejects). `secret: true` sends a masked\n\t * message: the raw body never enters the transcript and the handle never\n\t * resolves. While `ui.secretMessage.isOpen` is true you likely want\n\t * `secret: true` to match the composer's mode.\n\t */\n\tsendMessage(body: string, opts?: { secret?: boolean }): SendHandle;\n\t/** Retry a failed send by message id (see {@link canRetryMessage}). */\n\tretryMessage(messageId: string): void;\n\t/**\n\t * Whether the retry affordance should show for a message: a `text`\n\t * message core marked `isRetryable`, no disconnected Zendesk SDK, and the\n\t * message belongs to the current conversation (or predates stamping).\n\t */\n\tcanRetryMessage(message: Message): boolean;\n\n\t/** Answer a quick-reply chip. Pass the chip and its message id verbatim. */\n\tselectQuickReply(reply: QuickReply, messageId: string): void;\n\t/** Answer an options block. The label falls back to the option id. */\n\tselectOption(option: OptionItem, messageId: string): void;\n\t/**\n\t * Submit the active selectable list (`conversation.stateData.listSelection`\n\t * supplies the `dataId`). No-op when no list selection is active.\n\t */\n\tsubmitListSelection(selectedIds: string[]): void;\n\t/**\n\t * Cancel the active selectable list. Uses the block's own cancel response\n\t * and label (`label` overrides the visible label). No-op when no list\n\t * selection is active or it has no cancel response.\n\t */\n\tcancelListSelection(label?: string): void;\n\t/**\n\t * Submit the active capture field (`conversation.stateData.capture`\n\t * supplies the `dataId`). Each call mints its own `submitId`, so the\n\t * handle settles only on ITS OWN submit's verdict or failure — a second\n\t * submit sent while one is in flight is dropped by core and its handle\n\t * resolves `{ ok: false }` on its own terms rather than inheriting the\n\t * in-flight submit's verdict.\n\t */\n\tsubmitCapture(value: string): CaptureHandle;\n\t/**\n\t * Cancel the active capture field. `label` overrides the block's own\n\t * cancel label. No-op when no capture is active or it has no cancel\n\t * response.\n\t */\n\tcancelCapture(label?: string): void;\n\n\t/** React to a bot message with a thumbs vote. */\n\taddReaction(messageId: string, reaction: 1 | -1): void;\n\t/**\n\t * Report that the user activated a `link` message. Core resolves the\n\t * stored message for tracking and, with `navigateInHost`, validated\n\t * same-tab host navigation.\n\t */\n\treportLinkClick(messageId: string, opts?: { navigateInHost?: boolean }): void;\n\n\t/**\n\t * Advance the persisted read watermark. Reports are debounced 400 ms, the\n\t * watermark is a monotonic lexical max over `_id` cursor strings (calling\n\t * with an older cursor never lowers it), and the accumulator resets when\n\t * `chat.conversationId` changes so a pending cursor from conversation A\n\t * can never commit against B. Call with `message.cursor` for each message\n\t * that scrolls into view; messages stamped with a DIFFERENT\n\t * `conversationId` must be excluded by the caller (the transcript\n\t * preserves the previous conversation's rows).\n\t */\n\tmarkRead(cursor: string, opts?: { conversationId?: string }): void;\n\n\t/**\n\t * Request an older page of history. No-op unless `chat.canLoadMore` is\n\t * true and no load is already in flight.\n\t */\n\tloadOlderMessages(): void;\n\t/**\n\t * Invoke `callback` each time restored/baseline history (re)loads\n\t * (`chat.recentMessagesLoadSeq` advances) — the one-shot trigger for\n\t * \"scroll to the bottom of restored history\" and for seeding an unread\n\t * divider. Returns an unsubscribe function.\n\t */\n\tonHistoryLoaded(callback: (seq: number) => void): () => void;\n\n\t/**\n\t * Submit a CSAT survey. Pass `conversationId: message.conversationId` for\n\t * a survey rendered IN the transcript (the row can outlive its\n\t * conversation); omit it — or pass `null`, which is treated as omitted on\n\t * the wire and in correlation — for End Chat / proactive flows, where\n\t * core resolves the conversation itself.\n\t */\n\tsubmitCsat(\n\t\tdata: CsatSubmitData,\n\t\topts: {\n\t\t\tsurveyType: string;\n\t\t\tpartial?: boolean;\n\t\t\tconversationId?: string | null;\n\t\t},\n\t): CsatSubmitHandle;\n\t/** Analytics: report that a survey was displayed. Fire-and-forget. */\n\ttrackCsatShown(surveyType: string, conversationId?: string | null): void;\n\n\t/**\n\t * Ask core whether End Chat should present a survey. Correlated by\n\t * latching `csat.endChatEligibility.seq` and resolving when it advances\n\t * (there is no request id; a `ready -> ready` re-check changes no other\n\t * observable state).\n\t */\n\tcheckEndChatEligibility(\n\t\topts?: SettleOptions,\n\t): Promise<EndChatEligibilityResult>;\n\t/** End the chat without presenting a survey. */\n\tskipCsatAndEndChat(): void;\n\t/**\n\t * Start a new conversation. Applies a local cooldown (default 5 s) that\n\t * swallows rapid re-clicks; returns `false` while cooling down.\n\t */\n\tstartNewConversation(opts?: { cooldownMs?: number }): boolean;\n\t/** Reset the app frame's conversation state. */\n\tresetApp(): void;\n\n\t/**\n\t * Report a content-free composer-changed edge (drives live-agent typing\n\t * indicators). The event is payload-free by contract — composer text must\n\t * never cross the frame boundary — and this helper suppresses it entirely\n\t * in secret mode (pass `secret: true`, or leave it to the\n\t * `ui.secretMessage.isOpen` state check): even a content-free typing edge\n\t * is a signal about the secret being typed.\n\t */\n\tnotifyComposerChanged(opts?: { secret?: boolean }): void;\n\t/** Open or close the masked (secret) composer mode. */\n\ttoggleSecretMessage(open: boolean): void;\n\n\t/**\n\t * Start a live-agent file upload. Mints and retains the `{ file,\n\t * uploadId }` pair so `retry()` replays exactly it. Progress and errors\n\t * land on `file.isUploading`, `file.transfer`, and `file.uploadError`\n\t * (core can report an error without ever setting `isUploading`).\n\t */\n\tstartFileUpload(file: File): FileUploadHandle;\n\n\t/** Change the conversation language (BCP 47 tag the bot offers). */\n\tsetLanguage(language: string): void;\n\t/**\n\t * Email the transcript. Resolves/rejects when `transcript.email.status`\n\t * reaches `success | error` with its `.seq` advanced past this call's\n\t * baseline — a request core drops without ever broadcasting `pending`\n\t * (a host-driven reset in progress) still rejects. On a core document\n\t * that predates the seq, falls back to the `pending -> terminal` edge.\n\t * The email is trimmed; an empty result rejects immediately without\n\t * sending (core drops an empty email silently, so nothing would ever\n\t * settle it). The request is single-flight and address-aware: a call for\n\t * the SAME address as an in-flight request joins it and settles on its\n\t * verdict; a call for a DIFFERENT address is rejected address-matched on\n\t * `transcript.email.conflict.*` — ONLY the conflicting call's promise\n\t * rejects, while the in-flight request still completes, writes the final\n\t * status, and settles its own handle with the true outcome (a rejection\n\t * here never means the in-flight email was not sent). On a core document\n\t * that predates the conflict channel, a conflicting call settles on the\n\t * in-flight verdict instead.\n\t */\n\temailTranscript(email: string, opts?: SettleOptions): Promise<void>;\n\t/**\n\t * Download the transcript. Same settle contract as\n\t * {@link BridgeOperations.emailTranscript}, on\n\t * `transcript.download.status`.\n\t */\n\tdownloadTranscript(opts?: SettleOptions): Promise<void>;\n\t/** Toggle the new-message alert sound. */\n\tsetAlertSound(enabled: boolean): void;\n\t/**\n\t * Ask the HOST page to request Web Notification permission (permission is\n\t * per-origin, so the widget frame's own value is not the governing one).\n\t * Result lands on `chatter.notificationPermission`.\n\t */\n\trequestNotificationPermission(): void;\n\t/** Set the user's in-widget theme override. */\n\tsetTheme(theme: \"light\" | \"dark\" | \"auto\"): void;\n\t/** Set the user's in-widget text-size override. */\n\tsetTextSize(size: \"small\" | \"default\" | \"large\"): void;\n\n\t/** The header close affordance (ends the chat when `endChat.canEnd`). */\n\tclose(): void;\n\t/** Minimize the widget. */\n\tminimize(): void;\n\t/** Dismiss the visible toast. */\n\tdismissToast(): void;\n\t/**\n\t * Clear `chat.error` in core. Clear core state rather than hiding the\n\t * error locally, or the session's second error renders nothing.\n\t */\n\tdismissError(): void;\n\t/** Ask the SDK host to expand or restore the app's viewport surface. */\n\tsetHostViewportExpanded(expanded: boolean): void;\n\t/** Retry after a response outage banner. */\n\tretryAfterOutage(): void;\n\t/**\n\t * Report a fatal app error to core (renders the fallback UI and reports\n\t * telemetry). Callable from anywhere with a client reference — including\n\t * a class error boundary outside any React provider.\n\t */\n\treportAppError(error: unknown, componentStack?: string): void;\n}\n\nfunction mintUuid(): string {\n\tconst cryptoObj = typeof crypto === \"undefined\" ? undefined : crypto;\n\tif (typeof cryptoObj?.randomUUID === \"function\") {\n\t\treturn cryptoObj.randomUUID();\n\t}\n\tconst bytes = new Uint8Array(16);\n\tif (typeof cryptoObj?.getRandomValues === \"function\") {\n\t\tcryptoObj.getRandomValues(bytes);\n\t} else {\n\t\tfor (let index = 0; index < bytes.length; index += 1) {\n\t\t\tbytes[index] = Math.floor(Math.random() * 256);\n\t\t}\n\t}\n\t// RFC 4122 version/variant bits so core's uuid-shape validation accepts it.\n\tbytes[6] = ((bytes[6] as number) & 0x0f) | 0x40;\n\tbytes[8] = ((bytes[8] as number) & 0x3f) | 0x80;\n\tconst hex = Array.from(bytes, (byte) => byte.toString(16).padStart(2, \"0\"));\n\treturn `${hex.slice(0, 4).join(\"\")}-${hex.slice(4, 6).join(\"\")}-${hex\n\t\t.slice(6, 8)\n\t\t.join(\"\")}-${hex.slice(8, 10).join(\"\")}-${hex.slice(10).join(\"\")}`;\n}\n\ntype Outcome<T> = { value: T } | { error: Error };\n\n/**\n * The disposal hooks a settle promise registers with, so `dispose()` can\n * reject every in-flight `settled()` with a typed\n * {@link BridgeClientDestroyedError} — the layer's bound of last resort when\n * no `timeoutMs` was passed.\n */\ninterface SettleLifecycle {\n\tisDisposed(): boolean;\n\tregister(abort: (error: Error) => void): void;\n\tunregister(abort: (error: Error) => void): void;\n}\n\n/**\n * Settle a promise from state transitions: `inspect` runs once immediately\n * and then on every state update until it returns an outcome. `timeoutMs`\n * bounds the wait; without it an operation whose answer never arrives keeps\n * its subscription until it settles or the client is destroyed, which\n * rejects it with a {@link BridgeClientDestroyedError}.\n */\nfunction settleFromState<T>(\n\thost: BridgeOperationsHost,\n\tlifecycle: SettleLifecycle,\n\tinspect: (state: AppDisplayState | null) => Outcome<T> | undefined,\n\topts?: SettleOptions,\n): Promise<T> {\n\treturn new Promise<T>((resolve, reject) => {\n\t\tif (lifecycle.isDisposed()) {\n\t\t\treject(new BridgeClientDestroyedError());\n\t\t\treturn;\n\t\t}\n\t\tlet done = false;\n\t\tlet unsubscribe: (() => void) | null = null;\n\t\tlet timer: ReturnType<typeof setTimeout> | null = null;\n\t\tconst abort = (error: Error): void => {\n\t\t\tfinish({ error });\n\t\t};\n\t\tconst finish = (outcome: Outcome<T>): void => {\n\t\t\tif (done) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tdone = true;\n\t\t\tlifecycle.unregister(abort);\n\t\t\tunsubscribe?.();\n\t\t\tif (timer !== null) {\n\t\t\t\tclearTimeout(timer);\n\t\t\t}\n\t\t\tif (\"value\" in outcome) {\n\t\t\t\tresolve(outcome.value);\n\t\t\t} else {\n\t\t\t\treject(outcome.error);\n\t\t\t}\n\t\t};\n\t\tconst run = (): void => {\n\t\t\tconst outcome = inspect(host.getState());\n\t\t\tif (outcome) {\n\t\t\t\tfinish(outcome);\n\t\t\t}\n\t\t};\n\t\trun();\n\t\tif (done) {\n\t\t\treturn;\n\t\t}\n\t\tlifecycle.register(abort);\n\t\tunsubscribe = host.subscribe(run);\n\t\tif (opts?.timeoutMs !== undefined) {\n\t\t\ttimer = setTimeout(() => {\n\t\t\t\tfinish({\n\t\t\t\t\terror: new Error(\n\t\t\t\t\t\t`Bridge operation timed out after ${opts.timeoutMs}ms.`,\n\t\t\t\t\t),\n\t\t\t\t});\n\t\t\t}, opts.timeoutMs);\n\t\t}\n\t});\n}\n\ninterface StateObservation {\n\tsubscribeKey<K extends keyof AppDisplayState>(\n\t\tkey: K,\n\t\tcallback: (\n\t\t\tvalue: AppDisplayState[K] | undefined,\n\t\t\tprevious: AppDisplayState[K] | undefined,\n\t\t) => void,\n\t\topts?: ObserveOptions<AppDisplayState[K] | undefined>,\n\t): () => void;\n\tselect<T>(\n\t\tselector: (state: AppDisplayState | null) => T,\n\t\tcallback: (value: T, previous: T | undefined) => void,\n\t\topts?: ObserveOptions<T>,\n\t): () => void;\n}\n\n/**\n * Build the `subscribeKey`/`select` observation primitives over a host.\n * `Object.is` is a sound default equality for `subscribeKey`: state crosses\n * the frame boundary by structured clone, so the real client restores each\n * structurally unchanged key's previous reference before notifying (the test\n * mock shares references in memory to begin with). For `select`, whose\n * selector typically MINTS a new object per call, pass a custom `equals`\n * (or select a primitive/signature string) when projecting objects.\n */\nexport function createStateObservation(\n\thost: BridgeOperationsHost,\n): StateObservation {\n\tfunction select<T>(\n\t\tselector: (state: AppDisplayState | null) => T,\n\t\tcallback: (value: T, previous: T | undefined) => void,\n\t\topts?: ObserveOptions<T>,\n\t): () => void {\n\t\tconst equals = opts?.equals ?? Object.is;\n\t\tlet previous = selector(host.getState());\n\t\tif (opts?.emitInitial === true) {\n\t\t\tcallback(previous, undefined);\n\t\t}\n\t\treturn host.subscribe(() => {\n\t\t\tconst next = selector(host.getState());\n\t\t\tif (equals(next, previous)) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst before = previous;\n\t\t\tprevious = next;\n\t\t\tcallback(next, before);\n\t\t});\n\t}\n\n\tfunction subscribeKey<K extends keyof AppDisplayState>(\n\t\tkey: K,\n\t\tcallback: (\n\t\t\tvalue: AppDisplayState[K] | undefined,\n\t\t\tprevious: AppDisplayState[K] | undefined,\n\t\t) => void,\n\t\topts?: ObserveOptions<AppDisplayState[K] | undefined>,\n\t): () => void {\n\t\treturn select((state) => state?.[key], callback, opts);\n\t}\n\n\treturn { subscribeKey, select };\n}\n\nexport interface BridgeOperationsBundle {\n\toperations: BridgeOperations;\n\t/** Release the layer's own subscriptions and timers (client teardown). */\n\tdispose(): void;\n}\n\nexport function createBridgeOperations(\n\trawHost: BridgeOperationsHost,\n): BridgeOperationsBundle {\n\tlet disposed = false;\n\n\t// Disposal must actually stop the layer: after `dispose()` no helper may\n\t// send another event or take a new subscription (the client's transport\n\t// is gone). Guarding the host once covers every helper below.\n\tconst host: BridgeOperationsHost = {\n\t\tgetState: () => rawHost.getState(),\n\t\tsubscribe: (callback) =>\n\t\t\tdisposed\n\t\t\t\t? () => {\n\t\t\t\t\t\t// nothing was subscribed\n\t\t\t\t\t}\n\t\t\t\t: rawHost.subscribe(callback),\n\t\tsendEvent: (event, ...args) => {\n\t\t\tif (!disposed) {\n\t\t\t\trawHost.sendEvent(event, ...args);\n\t\t\t}\n\t\t},\n\t};\n\n\tconst liveSettleAborts = new Set<(error: Error) => void>();\n\tconst lifecycle: SettleLifecycle = {\n\t\tisDisposed: () => disposed,\n\t\tregister: (abort) => {\n\t\t\tliveSettleAborts.add(abort);\n\t\t},\n\t\tunregister: (abort) => {\n\t\t\tliveSettleAborts.delete(abort);\n\t\t},\n\t};\n\n\t// markRead accumulator (mirrors the reference app's useReadTracking).\n\tlet readMax = \"\";\n\tlet readReported = \"\";\n\tlet readTimer: ReturnType<typeof setTimeout> | null = null;\n\tlet readConversationId: string | null = null;\n\tlet readResetUnsubscribe: (() => void) | null = null;\n\n\tfunction resetReadAccumulator(): void {\n\t\treadMax = \"\";\n\t\treadReported = \"\";\n\t\tif (readTimer !== null) {\n\t\t\tclearTimeout(readTimer);\n\t\t\treadTimer = null;\n\t\t}\n\t}\n\n\t// Cursor spaces are per conversation: when the pin changes, drop the\n\t// accumulator AND any pending debounced report so a cursor collected in\n\t// conversation A can never be committed against B.\n\tfunction ensureReadResetSubscription(): void {\n\t\tif (readResetUnsubscribe) {\n\t\t\treturn;\n\t\t}\n\t\tlet previousPin = host.getState()?.[\"chat.conversationId\"] ?? null;\n\t\treadResetUnsubscribe = host.subscribe(() => {\n\t\t\tconst pin = host.getState()?.[\"chat.conversationId\"] ?? null;\n\t\t\tif (pin !== previousPin) {\n\t\t\t\tpreviousPin = pin;\n\t\t\t\tresetReadAccumulator();\n\t\t\t\treadConversationId = pin;\n\t\t\t}\n\t\t});\n\t}\n\n\tfunction scheduleReadReport(): void {\n\t\tif (readTimer !== null) {\n\t\t\treturn;\n\t\t}\n\t\treadTimer = setTimeout(() => {\n\t\t\treadTimer = null;\n\t\t\tif (readMax > readReported) {\n\t\t\t\treadReported = readMax;\n\t\t\t\thost.sendEvent(\"chat.messages.read\", {\n\t\t\t\t\tcursor: readMax,\n\t\t\t\t\t...(readConversationId ? { conversationId: readConversationId } : {}),\n\t\t\t\t});\n\t\t\t}\n\t\t}, READ_REPORT_DEBOUNCE_MS);\n\t}\n\n\tlet newConversationBlockedUntil = 0;\n\n\tconst operations: BridgeOperations = {\n\t\tsendMessage(body, opts) {\n\t\t\tconst trimmed = typeof body === \"string\" ? body.trim() : \"\";\n\t\t\tconst secret = opts?.secret === true;\n\t\t\tconst tempMessageUuid = mintUuid();\n\t\t\tlet errorBaseline: string | null = null;\n\t\t\tlet errorSeqBaseline: number | null = null;\n\t\t\tif (trimmed) {\n\t\t\t\tconst preSend = host.getState();\n\t\t\t\terrorBaseline = preSend?.[\"chat.error\"] ?? null;\n\t\t\t\terrorSeqBaseline = preSend?.[\"chat.error.seq\"] ?? null;\n\t\t\t\thost.sendEvent(\"chat.message.send\", {\n\t\t\t\t\tbody: trimmed,\n\t\t\t\t\tmessageType: secret ? \"secret\" : \"text\",\n\t\t\t\t\ttempMessageUuid,\n\t\t\t\t});\n\t\t\t}\n\t\t\treturn {\n\t\t\t\ttempMessageUuid,\n\t\t\t\tsettled(settleOpts) {\n\t\t\t\t\tif (!trimmed) {\n\t\t\t\t\t\treturn Promise.reject(\n\t\t\t\t\t\t\tnew Error(\"sendMessage requires a non-empty body.\"),\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t\tif (secret) {\n\t\t\t\t\t\t// No optimistic row exists for a secret send by design, so\n\t\t\t\t\t\t// there is nothing to resolve with. Time-bound or skip; the\n\t\t\t\t\t\t// wait is still bounded by client destroy.\n\t\t\t\t\t\treturn settleFromState<Message>(\n\t\t\t\t\t\t\thost,\n\t\t\t\t\t\t\tlifecycle,\n\t\t\t\t\t\t\t() => undefined,\n\t\t\t\t\t\t\tsettleOpts,\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t\treturn settleFromState<Message>(\n\t\t\t\t\t\thost,\n\t\t\t\t\t\tlifecycle,\n\t\t\t\t\t\t(state) => {\n\t\t\t\t\t\t\tconst messages = state?.[\"chat.messages\"] ?? [];\n\t\t\t\t\t\t\tconst match = messages.find(\n\t\t\t\t\t\t\t\t(message) => message.clientKey === tempMessageUuid,\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\tif (match) {\n\t\t\t\t\t\t\t\treturn { value: match };\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tconst error = state?.[\"chat.error\"] ?? null;\n\t\t\t\t\t\t\t// A core that stamps `chat.error.clientKey` (always present,\n\t\t\t\t\t\t\t// null when unattributed) attributes every pre-row send\n\t\t\t\t\t\t\t// failure to the send's own key, so the correlation is exact:\n\t\t\t\t\t\t\t// only THIS send's error rejects, and an unrelated failure\n\t\t\t\t\t\t\t// landing between the send and its optimistic row (the seq\n\t\t\t\t\t\t\t// fallback's false-rejection window) is ignored.\n\t\t\t\t\t\t\tif (state !== null && \"chat.error.clientKey\" in state) {\n\t\t\t\t\t\t\t\tif (\n\t\t\t\t\t\t\t\t\terror !== null &&\n\t\t\t\t\t\t\t\t\tstate[\"chat.error.clientKey\"] === tempMessageUuid\n\t\t\t\t\t\t\t\t) {\n\t\t\t\t\t\t\t\t\treturn { error: new Error(error) };\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\treturn undefined;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tconst errorSeq = state?.[\"chat.error.seq\"];\n\t\t\t\t\t\t\tif (typeof errorSeq === \"number\") {\n\t\t\t\t\t\t\t\tif (errorSeqBaseline === null) {\n\t\t\t\t\t\t\t\t\t// The send raced state hydration (no state yet when the\n\t\t\t\t\t\t\t\t\t// baseline was captured): latch the first observed seq\n\t\t\t\t\t\t\t\t\t// and treat the current error as pre-existing.\n\t\t\t\t\t\t\t\t\terrorSeqBaseline = errorSeq;\n\t\t\t\t\t\t\t\t\treturn undefined;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t// The seq advances on EVERY error set — including a repeat\n\t\t\t\t\t\t\t\t// of the identical text, which the fallback below cannot\n\t\t\t\t\t\t\t\t// see. `!==` rather than `>`, so a counter restarted by a\n\t\t\t\t\t\t\t\t// full core reset still rejects.\n\t\t\t\t\t\t\t\tif (error !== null && errorSeq !== errorSeqBaseline) {\n\t\t\t\t\t\t\t\t\treturn { error: new Error(error) };\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\treturn undefined;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t// Core document predates chat.error.seq: fall back to text\n\t\t\t\t\t\t\t// transitions (a repeated identical error is invisible here).\n\t\t\t\t\t\t\tif (error === null) {\n\t\t\t\t\t\t\t\terrorBaseline = null;\n\t\t\t\t\t\t\t\treturn undefined;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (error !== errorBaseline) {\n\t\t\t\t\t\t\t\treturn { error: new Error(error) };\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\treturn undefined;\n\t\t\t\t\t\t},\n\t\t\t\t\t\tsettleOpts,\n\t\t\t\t\t);\n\t\t\t\t},\n\t\t\t};\n\t\t},\n\n\t\tretryMessage(messageId) {\n\t\t\thost.sendEvent(\"chat.message.retry\", {\n\t\t\t\tmessage: { id: messageId, type: \"text\" },\n\t\t\t});\n\t\t},\n\n\t\tcanRetryMessage(message) {\n\t\t\tconst state = host.getState();\n\t\t\treturn (\n\t\t\t\tmessage.type === \"text\" &&\n\t\t\t\tmessage.isRetryable === true &&\n\t\t\t\tstate?.[\"zendesk.sdk.disconnected\"] !== true &&\n\t\t\t\t(message.conversationId == null ||\n\t\t\t\t\tmessage.conversationId === (state?.[\"chat.conversationId\"] ?? null))\n\t\t\t);\n\t\t},\n\n\t\tselectQuickReply(reply, messageId) {\n\t\t\thost.sendEvent(\"chat.quickReply.select\", {\n\t\t\t\tlabel: reply.label,\n\t\t\t\ttarget: reply.target,\n\t\t\t\tbuttonType: reply.buttonType,\n\t\t\t\t...(reply.value !== undefined ? { value: reply.value } : {}),\n\t\t\t\tmessageId,\n\t\t\t});\n\t\t},\n\n\t\tselectOption(option, messageId) {\n\t\t\thost.sendEvent(\"chat.option.select\", {\n\t\t\t\toptionId: option.id,\n\t\t\t\toptionLabel: option.label || option.id,\n\t\t\t\tmessageId,\n\t\t\t});\n\t\t},\n\n\t\tsubmitListSelection(selectedIds) {\n\t\t\tconst listSelection =\n\t\t\t\thost.getState()?.[\"conversation.stateData.listSelection\"];\n\t\t\tif (!listSelection) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\thost.sendEvent(\"chat.selectableList.submit\", {\n\t\t\t\tselectedIds,\n\t\t\t\tdataId: listSelection.dataId,\n\t\t\t});\n\t\t},\n\n\t\tcancelListSelection(label) {\n\t\t\tconst listSelection =\n\t\t\t\thost.getState()?.[\"conversation.stateData.listSelection\"];\n\t\t\t// Core drops a cancel without a response id, so don't send one.\n\t\t\tif (!listSelection?.cancelCaptureResponseId) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\thost.sendEvent(\"listSelection.cancel\", {\n\t\t\t\tcancelResponseId: listSelection.cancelCaptureResponseId,\n\t\t\t\tcancelLabel: label ?? listSelection.cancelCaptureLabel,\n\t\t\t\tdataId: listSelection.dataId,\n\t\t\t});\n\t\t},\n\n\t\tsubmitCapture(value) {\n\t\t\tconst capture = host.getState()?.[\"conversation.stateData.capture\"];\n\t\t\tif (!capture) {\n\t\t\t\treturn {\n\t\t\t\t\tsettled: () =>\n\t\t\t\t\t\tPromise.reject(new Error(\"No capture field is active.\")),\n\t\t\t\t};\n\t\t\t}\n\t\t\tconst dataId = capture.dataId;\n\t\t\tconst submitId = mintUuid();\n\t\t\thost.sendEvent(\"capture.submit\", { value, dataId, submitId });\n\t\t\t// The verdict rides the capture object itself: `isSubmitting` is\n\t\t\t// core-owned, flips true while the submit is in flight, and the\n\t\t\t// falling edge carries `validationErrorMessage`; an ACCEPTED value\n\t\t\t// removes the whole object (or replaces it with the next block).\n\t\t\t// Every failure or drop WITHOUT a server validation verdict — a\n\t\t\t// pre-flight drop that raises and settles `isSubmitting` inside one\n\t\t\t// broadcast batch, a transport failure, a duplicate submit dropped\n\t\t\t// while another is in flight — lands on `lastSubmitFailure` keyed\n\t\t\t// with this call's own `submitId`, so the correlation is exact and\n\t\t\t// the global `chat.error` is never consulted.\n\t\t\tlet sawSubmitting = false;\n\t\t\treturn {\n\t\t\t\tsettled: (settleOpts) =>\n\t\t\t\t\tsettleFromState<OperationResult>(\n\t\t\t\t\t\thost,\n\t\t\t\t\t\tlifecycle,\n\t\t\t\t\t\t(state) => {\n\t\t\t\t\t\t\tconst current = state?.[\"conversation.stateData.capture\"] ?? null;\n\t\t\t\t\t\t\tif (!current || current.dataId !== dataId) {\n\t\t\t\t\t\t\t\treturn { value: { ok: true } };\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t// Keyed failure BEFORE the in-flight latch: a duplicate\n\t\t\t\t\t\t\t// submit's drop is stamped while the OTHER submit still holds\n\t\t\t\t\t\t\t// `isSubmitting`, and latching that flag here would make this\n\t\t\t\t\t\t\t// handle inherit the other submit's verdict.\n\t\t\t\t\t\t\tconst failure = current.lastSubmitFailure;\n\t\t\t\t\t\t\tif (failure && failure.submitId === submitId) {\n\t\t\t\t\t\t\t\treturn { value: { ok: false, message: failure.message } };\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (current.isSubmitting === true) {\n\t\t\t\t\t\t\t\tsawSubmitting = true;\n\t\t\t\t\t\t\t\treturn undefined;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (sawSubmitting) {\n\t\t\t\t\t\t\t\treturn current.validationErrorMessage\n\t\t\t\t\t\t\t\t\t? {\n\t\t\t\t\t\t\t\t\t\t\tvalue: {\n\t\t\t\t\t\t\t\t\t\t\t\tok: false,\n\t\t\t\t\t\t\t\t\t\t\t\tmessage: current.validationErrorMessage,\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t: { value: { ok: true } };\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\treturn undefined;\n\t\t\t\t\t\t},\n\t\t\t\t\t\tsettleOpts,\n\t\t\t\t\t),\n\t\t\t};\n\t\t},\n\n\t\tcancelCapture(label) {\n\t\t\tconst capture = host.getState()?.[\"conversation.stateData.capture\"];\n\t\t\tif (!capture?.cancelCaptureResponseId) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\thost.sendEvent(\"capture.cancel\", {\n\t\t\t\tcancelResponseId: capture.cancelCaptureResponseId,\n\t\t\t\tcancelLabel: label ?? capture.cancelCaptureLabel,\n\t\t\t\tdataId: capture.dataId,\n\t\t\t});\n\t\t},\n\n\t\taddReaction(messageId, reaction) {\n\t\t\thost.sendEvent(\"chat.message.reaction.add\", { messageId, reaction });\n\t\t},\n\n\t\treportLinkClick(messageId, opts) {\n\t\t\thost.sendEvent(\"chat.link.click\", {\n\t\t\t\tmessageId,\n\t\t\t\t...(opts?.navigateInHost !== undefined\n\t\t\t\t\t? { navigateInHost: opts.navigateInHost }\n\t\t\t\t\t: {}),\n\t\t\t});\n\t\t},\n\n\t\tmarkRead(cursor, opts) {\n\t\t\tif (typeof cursor !== \"string\" || cursor === \"\") {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tensureReadResetSubscription();\n\t\t\tconst conversationId =\n\t\t\t\topts?.conversationId ??\n\t\t\t\thost.getState()?.[\"chat.conversationId\"] ??\n\t\t\t\tnull;\n\t\t\tif (conversationId !== readConversationId) {\n\t\t\t\tresetReadAccumulator();\n\t\t\t\treadConversationId = conversationId;\n\t\t\t}\n\t\t\tif (cursor > readMax) {\n\t\t\t\treadMax = cursor;\n\t\t\t\tscheduleReadReport();\n\t\t\t}\n\t\t},\n\n\t\tloadOlderMessages() {\n\t\t\tconst state = host.getState();\n\t\t\tif (\n\t\t\t\tstate?.[\"chat.canLoadMore\"] !== true ||\n\t\t\t\tstate[\"chat.loadingPreviousMessages\"] === true\n\t\t\t) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\thost.sendEvent(\"chat.logs.loadMore\");\n\t\t},\n\n\t\tonHistoryLoaded(callback) {\n\t\t\tlet previous = host.getState()?.[\"chat.recentMessagesLoadSeq\"] ?? 0;\n\t\t\treturn host.subscribe(() => {\n\t\t\t\tconst seq = host.getState()?.[\"chat.recentMessagesLoadSeq\"] ?? 0;\n\t\t\t\tif (seq > previous) {\n\t\t\t\t\tprevious = seq;\n\t\t\t\t\tcallback(seq);\n\t\t\t\t} else if (seq < previous) {\n\t\t\t\t\t// Returned to 0 on a chat clear; re-arm without firing.\n\t\t\t\t\tprevious = seq;\n\t\t\t\t}\n\t\t\t});\n\t\t},\n\n\t\tsubmitCsat(data, opts) {\n\t\t\tconst { surveyType, partial, conversationId } = opts;\n\t\t\tconst baselineSeq = host.getState()?.[\"csat.submitSuccess.seq\"] ?? 0;\n\t\t\tlet errorBaseline = host.getState()?.[\"csat.submitError\"] ?? null;\n\t\t\tlet errorSeqBaseline = host.getState()?.[\"csat.submitError.seq\"] ?? null;\n\t\t\t// `null` means \"omitted\" on the wire AND in correlation: core's\n\t\t\t// resolver treats a falsy id as absent and reports the SESSION's id\n\t\t\t// back, so a null-scoped predicate could never match it and the\n\t\t\t// handle would hang on a fully successful submit.\n\t\t\thost.sendEvent(\"csat.submit\", {\n\t\t\t\tdata,\n\t\t\t\tsurveyType,\n\t\t\t\t...(partial !== undefined ? { partial } : {}),\n\t\t\t\t...(conversationId != null ? { conversationId } : {}),\n\t\t\t});\n\t\t\t// When the caller omitted conversationId (End Chat / proactive),\n\t\t\t// core resolves the conversation itself, so match on the type +\n\t\t\t// seq alone; a caller-scoped survey matches its exact conversation.\n\t\t\tconst matchesConversation = (\n\t\t\t\tcandidate: string | null | undefined,\n\t\t\t): boolean =>\n\t\t\t\tconversationId == null || (candidate ?? null) === conversationId;\n\t\t\treturn {\n\t\t\t\tsettled: (settleOpts) =>\n\t\t\t\t\tsettleFromState<OperationResult>(\n\t\t\t\t\t\thost,\n\t\t\t\t\t\tlifecycle,\n\t\t\t\t\t\t(state) => {\n\t\t\t\t\t\t\tconst seq = state?.[\"csat.submitSuccess.seq\"] ?? 0;\n\t\t\t\t\t\t\tif (\n\t\t\t\t\t\t\t\tseq > baselineSeq &&\n\t\t\t\t\t\t\t\tstate?.[\"csat.submitSuccess.surveyType\"] === surveyType &&\n\t\t\t\t\t\t\t\tmatchesConversation(\n\t\t\t\t\t\t\t\t\tstate?.[\"csat.submitSuccess.conversationId\"],\n\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t) {\n\t\t\t\t\t\t\t\treturn { value: { ok: true } };\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tconst error = state?.[\"csat.submitError\"] ?? null;\n\t\t\t\t\t\t\tconst errorSeq = state?.[\"csat.submitError.seq\"];\n\t\t\t\t\t\t\tif (typeof errorSeq === \"number\") {\n\t\t\t\t\t\t\t\t// The seq advances on EVERY error set — including a repeat\n\t\t\t\t\t\t\t\t// of the identical text (the same submit dropped twice at a\n\t\t\t\t\t\t\t\t// core gate), which the text transition below cannot see.\n\t\t\t\t\t\t\t\tif (errorSeqBaseline === null) {\n\t\t\t\t\t\t\t\t\t// The submit raced state hydration: latch the first\n\t\t\t\t\t\t\t\t\t// observed seq and treat the current error as\n\t\t\t\t\t\t\t\t\t// pre-existing.\n\t\t\t\t\t\t\t\t\terrorSeqBaseline = errorSeq;\n\t\t\t\t\t\t\t\t\treturn undefined;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif (\n\t\t\t\t\t\t\t\t\terror !== null &&\n\t\t\t\t\t\t\t\t\terrorSeq !== errorSeqBaseline &&\n\t\t\t\t\t\t\t\t\tstate?.[\"csat.submitErrorSurveyType\"] === surveyType &&\n\t\t\t\t\t\t\t\t\tmatchesConversation(state?.[\"csat.submitErrorConversationId\"])\n\t\t\t\t\t\t\t\t) {\n\t\t\t\t\t\t\t\t\treturn { value: { ok: false, message: error } };\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\treturn undefined;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t// Core document predates csat.submitError.seq: fall back to\n\t\t\t\t\t\t\t// text transitions.\n\t\t\t\t\t\t\tif (error === null) {\n\t\t\t\t\t\t\t\t// Core clears the error at the start of every\n\t\t\t\t\t\t\t\t// non-partial submit; from here any error is ours.\n\t\t\t\t\t\t\t\terrorBaseline = null;\n\t\t\t\t\t\t\t\treturn undefined;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (\n\t\t\t\t\t\t\t\terror !== errorBaseline &&\n\t\t\t\t\t\t\t\tstate?.[\"csat.submitErrorSurveyType\"] === surveyType &&\n\t\t\t\t\t\t\t\tmatchesConversation(state?.[\"csat.submitErrorConversationId\"])\n\t\t\t\t\t\t\t) {\n\t\t\t\t\t\t\t\treturn { value: { ok: false, message: error } };\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\treturn undefined;\n\t\t\t\t\t\t},\n\t\t\t\t\t\tsettleOpts,\n\t\t\t\t\t),\n\t\t\t};\n\t\t},\n\n\t\ttrackCsatShown(surveyType, conversationId) {\n\t\t\thost.sendEvent(\"csat.shown\", {\n\t\t\t\tsurveyType,\n\t\t\t\t...(conversationId != null ? { conversationId } : {}),\n\t\t\t});\n\t\t},\n\n\t\tcheckEndChatEligibility(opts) {\n\t\t\tconst baselineSeq = host.getState()?.[\"csat.endChatEligibility.seq\"] ?? 0;\n\t\t\thost.sendEvent(\"csat.checkEndChatEligibility\");\n\t\t\treturn settleFromState<EndChatEligibilityResult>(\n\t\t\t\thost,\n\t\t\t\tlifecycle,\n\t\t\t\t(state) => {\n\t\t\t\t\tconst seq = state?.[\"csat.endChatEligibility.seq\"] ?? 0;\n\t\t\t\t\tif (seq <= baselineSeq) {\n\t\t\t\t\t\treturn undefined;\n\t\t\t\t\t}\n\t\t\t\t\treturn {\n\t\t\t\t\t\tvalue: {\n\t\t\t\t\t\t\teligible: state?.[\"csat.endChatEligible\"] ?? null,\n\t\t\t\t\t\t\tsurveyTarget: state?.[\"csat.endChatSurveyTarget\"] ?? null,\n\t\t\t\t\t\t},\n\t\t\t\t\t};\n\t\t\t\t},\n\t\t\t\topts,\n\t\t\t);\n\t\t},\n\n\t\tskipCsatAndEndChat() {\n\t\t\thost.sendEvent(\"endChat.skipCsat\");\n\t\t},\n\n\t\tstartNewConversation(opts) {\n\t\t\tconst now = Date.now();\n\t\t\tif (now < newConversationBlockedUntil) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\tnewConversationBlockedUntil =\n\t\t\t\tnow + (opts?.cooldownMs ?? START_NEW_CONVERSATION_COOLDOWN_MS);\n\t\t\thost.sendEvent(\"app.startNewConversation\");\n\t\t\treturn true;\n\t\t},\n\n\t\tresetApp() {\n\t\t\thost.sendEvent(\"app.reset\");\n\t\t},\n\n\t\tnotifyComposerChanged(opts) {\n\t\t\t// Even a content-free typing edge is a signal that a secret is\n\t\t\t// being typed, so it stays suppressed at the source.\n\t\t\tif (\n\t\t\t\topts?.secret === true ||\n\t\t\t\thost.getState()?.[\"ui.secretMessage.isOpen\"] === true\n\t\t\t) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\thost.sendEvent(\"ui.composer.textChanged\");\n\t\t},\n\n\t\ttoggleSecretMessage(open) {\n\t\t\thost.sendEvent(\"ui.secretMessage.toggle\", { open });\n\t\t},\n\n\t\tstartFileUpload(file) {\n\t\t\tconst uploadId = mintUuid();\n\t\t\thost.sendEvent(\"file.upload.start\", { file, uploadId });\n\t\t\treturn {\n\t\t\t\tuploadId,\n\t\t\t\tretry() {\n\t\t\t\t\thost.sendEvent(\"file.upload.retry\", { file, uploadId });\n\t\t\t\t},\n\t\t\t\tdismissError() {\n\t\t\t\t\thost.sendEvent(\"file.upload.errorDismiss\");\n\t\t\t\t},\n\t\t\t};\n\t\t},\n\n\t\tsetLanguage(language) {\n\t\t\thost.sendEvent(\"settings.language.change\", { language });\n\t\t},\n\n\t\temailTranscript(email, opts) {\n\t\t\t// Core drops an empty email silently — no status transition ever —\n\t\t\t// so validate here and reject immediately instead of hanging.\n\t\t\tconst trimmed = typeof email === \"string\" ? email.trim() : \"\";\n\t\t\tif (!trimmed) {\n\t\t\t\treturn Promise.reject(\n\t\t\t\t\tnew Error(\"emailTranscript requires a non-empty email.\"),\n\t\t\t\t);\n\t\t\t}\n\t\t\thost.sendEvent(\"settings.transcript.email.send\", { email: trimmed });\n\t\t\treturn transcriptSettled(\"transcript.email.status\", opts, trimmed);\n\t\t},\n\n\t\tdownloadTranscript(opts) {\n\t\t\thost.sendEvent(\"settings.transcript.download.request\");\n\t\t\treturn transcriptSettled(\"transcript.download.status\", opts);\n\t\t},\n\n\t\tsetAlertSound(enabled) {\n\t\t\thost.sendEvent(\"settings.alertSound.toggle\", { enabled });\n\t\t},\n\n\t\trequestNotificationPermission() {\n\t\t\thost.sendEvent(\"settings.notifications.requestPermission\");\n\t\t},\n\n\t\tsetTheme(theme) {\n\t\t\thost.sendEvent(\"appearance.setTheme\", { theme });\n\t\t},\n\n\t\tsetTextSize(size) {\n\t\t\thost.sendEvent(\"appearance.setTextSize\", { textSize: size });\n\t\t},\n\n\t\tclose() {\n\t\t\thost.sendEvent(\"ui.closeButton.click\");\n\t\t},\n\n\t\tminimize() {\n\t\t\thost.sendEvent(\"ui.minimizeButton.click\");\n\t\t},\n\n\t\tdismissToast() {\n\t\t\thost.sendEvent(\"ui.toast.dismiss\");\n\t\t},\n\n\t\tdismissError() {\n\t\t\thost.sendEvent(\"chat.error.dismiss\");\n\t\t},\n\n\t\tsetHostViewportExpanded(expanded) {\n\t\t\thost.sendEvent(\"ui.hostViewport.set\", { expanded });\n\t\t},\n\n\t\tretryAfterOutage() {\n\t\t\thost.sendEvent(\"outage.retry\");\n\t\t},\n\n\t\treportAppError(error, componentStack) {\n\t\t\tconst err = error instanceof Error ? error : null;\n\t\t\thost.sendEvent(\"app.error.report\", {\n\t\t\t\tmessage: err?.message ?? String(error),\n\t\t\t\tstack: err?.stack ?? \"\",\n\t\t\t\tcomponentStack: componentStack ?? \"\",\n\t\t\t});\n\t\t},\n\t};\n\n\tfunction transcriptSettled(\n\t\tkey: \"transcript.email.status\" | \"transcript.download.status\",\n\t\topts?: SettleOptions,\n\t\t// Email only: reject THIS call when core rejects a conflicting request\n\t\t// for exactly this address on `transcript.email.conflict.*` — the\n\t\t// shared status channel never carries the conflict, so the in-flight\n\t\t// caller settles on its own verdict.\n\t\tconflictEmail?: string,\n\t): Promise<void> {\n\t\tconst seqKey = `${key}.seq` as\n\t\t\t| \"transcript.email.status.seq\"\n\t\t\t| \"transcript.download.status.seq\";\n\t\tlet previous = host.getState()?.[key] ?? \"idle\";\n\t\tlet seqBaseline = host.getState()?.[seqKey] ?? null;\n\t\tlet conflictSeqBaseline =\n\t\t\thost.getState()?.[\"transcript.email.conflict.seq\"] ?? null;\n\t\treturn settleFromState<void>(\n\t\t\thost,\n\t\t\tlifecycle,\n\t\t\t(state) => {\n\t\t\t\tif (conflictEmail !== undefined) {\n\t\t\t\t\tconst conflictSeq = state?.[\"transcript.email.conflict.seq\"];\n\t\t\t\t\tif (typeof conflictSeq === \"number\") {\n\t\t\t\t\t\tif (conflictSeqBaseline === null) {\n\t\t\t\t\t\t\t// The request raced state hydration: latch the first\n\t\t\t\t\t\t\t// observed seq, mirroring the status-seq baseline below.\n\t\t\t\t\t\t\t// Residual: if that first snapshot already carries THIS\n\t\t\t\t\t\t\t// call's own conflict, the reject branch never fires and\n\t\t\t\t\t\t\t// the handle falls through to the shared-status\n\t\t\t\t\t\t\t// correlation — the same bounded degradation as the\n\t\t\t\t\t\t\t// documented old-core (no conflict keys) skew. Not\n\t\t\t\t\t\t\t// closable from here: with no pre-call baseline, a\n\t\t\t\t\t\t\t// matching-address conflict in the first snapshot is\n\t\t\t\t\t\t\t// indistinguishable from a pre-existing one, and\n\t\t\t\t\t\t\t// treating it as ours would false-reject.\n\t\t\t\t\t\t\tconflictSeqBaseline = conflictSeq;\n\t\t\t\t\t\t} else if (\n\t\t\t\t\t\t\tconflictSeq !== conflictSeqBaseline &&\n\t\t\t\t\t\t\tstate?.[\"transcript.email.conflict.email\"] === conflictEmail\n\t\t\t\t\t\t) {\n\t\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\t\terror: new Error(\n\t\t\t\t\t\t\t\t\t\"emailTranscript was rejected: a request for a different \" +\n\t\t\t\t\t\t\t\t\t\t\"address is already in flight. The in-flight email \" +\n\t\t\t\t\t\t\t\t\t\t\"still completes with its own outcome.\",\n\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tconst status = state?.[key] ?? \"idle\";\n\t\t\t\tconst seq = state?.[seqKey];\n\t\t\t\tif (typeof seq === \"number\") {\n\t\t\t\t\t// The seq advances on every status write, so a request core\n\t\t\t\t\t// drops straight to a terminal status (never `pending` in a\n\t\t\t\t\t// broadcast snapshot) and a repeat of the previous terminal\n\t\t\t\t\t// status both stay observable.\n\t\t\t\t\tif (seqBaseline === null) {\n\t\t\t\t\t\t// The request raced state hydration: latch the first observed\n\t\t\t\t\t\t// seq and treat the current status as pre-existing.\n\t\t\t\t\t\tseqBaseline = seq;\n\t\t\t\t\t\treturn undefined;\n\t\t\t\t\t}\n\t\t\t\t\tif (seq === seqBaseline) {\n\t\t\t\t\t\treturn undefined;\n\t\t\t\t\t}\n\t\t\t\t\tif (status === \"success\") {\n\t\t\t\t\t\treturn { value: undefined };\n\t\t\t\t\t}\n\t\t\t\t\tif (status === \"error\") {\n\t\t\t\t\t\treturn { error: new Error(`${key} reported an error.`) };\n\t\t\t\t\t}\n\t\t\t\t\treturn undefined;\n\t\t\t\t}\n\t\t\t\t// Core document predates the seq key: fall back to the\n\t\t\t\t// pending -> terminal edge.\n\t\t\t\tconst was = previous;\n\t\t\t\tprevious = status;\n\t\t\t\tif (was !== \"pending\") {\n\t\t\t\t\treturn undefined;\n\t\t\t\t}\n\t\t\t\tif (status === \"success\") {\n\t\t\t\t\treturn { value: undefined };\n\t\t\t\t}\n\t\t\t\tif (status === \"error\") {\n\t\t\t\t\treturn { error: new Error(`${key} reported an error.`) };\n\t\t\t\t}\n\t\t\t\treturn undefined;\n\t\t\t},\n\t\t\topts,\n\t\t);\n\t}\n\n\treturn {\n\t\toperations,\n\t\tdispose() {\n\t\t\tif (disposed) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tdisposed = true;\n\t\t\tresetReadAccumulator();\n\t\t\treadResetUnsubscribe?.();\n\t\t\treadResetUnsubscribe = null;\n\t\t\t// Reject every in-flight settled() promise: the transport is gone,\n\t\t\t// so no state transition can ever answer it. Snapshot first — each\n\t\t\t// abort unregisters itself from the live set.\n\t\t\tfor (const abort of [...liveSettleAborts]) {\n\t\t\t\tabort(new BridgeClientDestroyedError());\n\t\t\t}\n\t\t\tliveSettleAborts.clear();\n\t\t},\n\t};\n}\n","import type { BridgeClient } from \"../contract/bridge-client\";\nimport {\n\tcreateBridgeOperations,\n\tcreateStateObservation,\n} from \"../contract/operations\";\nimport type { AppDisplayState, AppEvents } from \"../contract/types\";\n\n/** One recorded `sendEvent` call. */\nexport interface SentBridgeEvent<K extends keyof AppEvents = keyof AppEvents> {\n\tevent: K;\n\tpayload: AppEvents[K] | undefined;\n}\n\n/**\n * A scriptable, in-memory {@link BridgeClient} for unit tests.\n *\n * No postMessage, no origin checks, no network: tests drive state through\n * `setState`/`updateState` and assert on the events the component under test\n * sent through `sentEvents`. The `operations`, `subscribeKey`, and `select`\n * surfaces are the REAL implementations running over this in-memory state:\n * every operation records its events in `sentEvents`, and a correlated\n * handle (`sendMessage().settled()`, `submitCsat().settled()`, ...) resolves\n * when the test scripts the answering state transition via `updateState`.\n */\nexport interface MockBridgeClient extends BridgeClient {\n\t/** Replace the state snapshot and notify subscribers. */\n\tsetState(state: AppDisplayState | null): void;\n\t/**\n\t * Shallow-merge a partial snapshot over the current state (or over an empty\n\t * object when no state is set) and notify subscribers. Seeds\n\t * `\"chat.error.clientKey\": null` when the key is absent — production core\n\t * always publishes it (null when no keyed send owns the error), and\n\t * `operations` send handles take the keyed correlation path only when it is\n\t * present. Script a `chat.error` fixture with the send's `tempMessageUuid`\n\t * as `chat.error.clientKey` to reject that handle; use `setState` to script\n\t * an older core document without the key.\n\t */\n\tupdateState(partial: Partial<AppDisplayState>): void;\n\t/**\n\t * Every event sent through the client, in order. Each read returns a fresh\n\t * copy, so a captured array survives `clearSentEvents()`.\n\t */\n\treadonly sentEvents: readonly SentBridgeEvent[];\n\t/** Forget recorded events without touching state or subscribers. */\n\tclearSentEvents(): void;\n\t/** Number of active subscribers. */\n\treadonly subscriberCount: number;\n\t/** True once `destroy()` has been called. */\n\treadonly destroyed: boolean;\n}\n\n/**\n * Create a {@link MockBridgeClient}, optionally seeded with an initial state\n * snapshot. Mirrors the real client's teardown semantics: after `destroy()`,\n * state is `null`, subscribers are cleared, `sendEvent` is a no-op, and every\n * in-flight `operations` `settled()` promise rejects with a\n * `BridgeClientDestroyedError`. Notification also mirrors the real client:\n * a throwing subscriber is caught and logged, never aborting the pass.\n *\n * The derivation helpers (`filterDisplayable`, `groupMessages`, `messageKey`,\n * `selectUnread`, ...) are runtime-only: this package types them on\n * `MessagingBridgeModule` but ships no implementation, so there is no mock\n * module to destructure them from. In unit tests, inject them into the code\n * under test (plain function parameters or props); to exercise the real\n * implementations, load the module with `loadMessagingBridge()` in an\n * integration test. Do not re-implement them — a copy drifts from the\n * runtime's edge cases.\n */\nexport function createMockBridgeClient(\n\tinitialState: AppDisplayState | null = null,\n): MockBridgeClient {\n\tlet state = initialState;\n\tlet destroyed = false;\n\tconst subscribers = new Set<() => void>();\n\tconst sentEvents: SentBridgeEvent[] = [];\n\n\tfunction notify(): void {\n\t\t// Mirrors the real client's notify(): one throwing subscriber must not\n\t\t// abort the pass (a settled() promise resolved from a later subscriber\n\t\t// would otherwise hang) or skip subscribers that unsubscribe mid-pass.\n\t\tfor (const callback of [...subscribers]) {\n\t\t\ttry {\n\t\t\t\tcallback();\n\t\t\t} catch (err) {\n\t\t\t\tconsole.error(\"[AdaMessaging] Bridge subscriber threw:\", err);\n\t\t\t}\n\t\t}\n\t}\n\n\tfunction getState(): AppDisplayState | null {\n\t\treturn state;\n\t}\n\n\tfunction subscribe(callback: () => void): () => void {\n\t\tsubscribers.add(callback);\n\t\treturn () => {\n\t\t\tsubscribers.delete(callback);\n\t\t};\n\t}\n\n\tfunction sendEvent<K extends keyof AppEvents>(\n\t\tevent: K,\n\t\t...args: AppEvents[K] extends undefined ? [] : [payload: AppEvents[K]]\n\t): void {\n\t\tif (destroyed) {\n\t\t\treturn;\n\t\t}\n\t\tsentEvents.push({ event, payload: args[0] });\n\t}\n\n\tconst host = { getState, subscribe, sendEvent };\n\tconst observation = createStateObservation(host);\n\tconst operationsBundle = createBridgeOperations(host);\n\n\treturn {\n\t\tgetState,\n\t\tsubscribe,\n\t\tsendEvent,\n\t\tsubscribeKey: observation.subscribeKey,\n\t\tselect: observation.select,\n\t\toperations: operationsBundle.operations,\n\n\t\tdestroy(): void {\n\t\t\tdestroyed = true;\n\t\t\toperationsBundle.dispose();\n\t\t\tstate = null;\n\t\t\tsubscribers.clear();\n\t\t},\n\n\t\tsetState(next: AppDisplayState | null): void {\n\t\t\tstate = next;\n\t\t\tnotify();\n\t\t},\n\n\t\tupdateState(partial: Partial<AppDisplayState>): void {\n\t\t\t// Production core always publishes `chat.error.clientKey` (null when\n\t\t\t// unattributed), and `operations` feature-detects on its presence.\n\t\t\t// Seed it so a scripted failure exercises the keyed path production\n\t\t\t// takes; override it in `partial` (or use setState) to script an\n\t\t\t// older core document.\n\t\t\tstate = {\n\t\t\t\t\"chat.error.clientKey\": null,\n\t\t\t\t...(state ?? {}),\n\t\t\t\t...partial,\n\t\t\t} as AppDisplayState;\n\t\t\tnotify();\n\t\t},\n\n\t\tget sentEvents(): readonly SentBridgeEvent[] {\n\t\t\treturn [...sentEvents];\n\t\t},\n\n\t\tclearSentEvents(): void {\n\t\t\tsentEvents.length = 0;\n\t\t},\n\n\t\tget subscriberCount(): number {\n\t\t\treturn subscribers.size;\n\t\t},\n\n\t\tget destroyed(): boolean {\n\t\t\treturn destroyed;\n\t\t},\n\t};\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"testing.js","names":[],"sources":["../../src/contract/operations.ts","../../src/npm/testing.ts"],"sourcesContent":["import type {\n\tAppDisplayState,\n\tAppEvents,\n\tCsatSubmitData,\n\tMessage,\n\tOptionItem,\n\tQuickReply,\n} from \"./types\";\n\n/**\n * `client.operations` — typed helpers over the raw event/state contract,\n * mined from Ada's reference app so custom apps inherit its validated\n * behavior (debounces, guards, correlation) instead of reimplementing it.\n *\n * Implementation note: the npm test mock and CDN runtime share this module.\n * The mock reuses it over its own in-memory host so mocked handles resolve\n * from scripted state, but the published tarball never carries transport\n * logic — every helper here speaks through `sendEvent`/`subscribe` alone.\n */\n\n/** Debounce window for {@link BridgeOperations.markRead} reports. */\nconst READ_REPORT_DEBOUNCE_MS = 400;\n/** Default {@link BridgeOperations.startNewConversation} cooldown. */\nconst START_NEW_CONVERSATION_COOLDOWN_MS = 5_000;\n\n/**\n * The minimal client surface the operations layer builds on. Satisfied by the\n * real `BridgeClient` and by the npm test mock.\n */\nexport interface BridgeOperationsHost {\n\tgetState(): AppDisplayState | null;\n\tsubscribe(callback: () => void): () => void;\n\tsendEvent<K extends keyof AppEvents>(\n\t\tevent: K,\n\t\t...args: AppEvents[K] extends undefined ? [] : [payload: AppEvents[K]]\n\t): void;\n}\n\n/** Outcome of a correlated submit: accepted, or rejected with a message. */\nexport type OperationResult = { ok: true } | { ok: false; message: string };\n\nexport interface SettleOptions {\n\t/** Reject with an `Error` if the operation has not settled in time. */\n\ttimeoutMs?: number;\n}\n\n/**\n * Rejection every in-flight `settled()` promise receives when the client is\n * destroyed, so teardown is the layer's bound of last resort even without a\n * `timeoutMs`. The class identity does not survive the npm/CDN module split,\n * so detect it by `error.name === \"BridgeClientDestroyedError\"` (or via\n * {@link BridgeClientDestroyedError.is}), never by `instanceof` alone.\n */\nexport class BridgeClientDestroyedError extends Error {\n\tconstructor(message = \"The bridge client was destroyed.\") {\n\t\tsuper(message);\n\t\tthis.name = \"BridgeClientDestroyedError\";\n\t}\n\n\tstatic is(error: unknown): error is BridgeClientDestroyedError {\n\t\treturn (\n\t\t\terror instanceof Error && error.name === \"BridgeClientDestroyedError\"\n\t\t);\n\t}\n}\n\nexport interface SendHandle {\n\t/**\n\t * The uuid put on the wire as `tempMessageUuid`. Core seeds the optimistic\n\t * message's `clientKey` from it, so the sent message is findable in\n\t * `chat.messages` by `clientKey === tempMessageUuid`.\n\t */\n\treadonly tempMessageUuid: string;\n\t/**\n\t * Resolves with the message once it appears in `chat.messages` (the\n\t * optimistic row; the durable server echo keeps the same `clientKey`).\n\t * Rejects when core reports THIS send's error before the row appears —\n\t * rate limit, over-length, session not ready, dispatch failure —\n\t * correlated exactly on `chat.error.clientKey` matching the minted\n\t * `tempMessageUuid`, so an unrelated failure in the same window never\n\t * falsely rejects a delivered send. On a core document that predates\n\t * `chat.error.clientKey`, falls back to the positional\n\t * `chat.error.seq`-advance detection, and before that to `chat.error`\n\t * text transitions. A secret send NEVER resolves — core deliberately\n\t * writes no transcript row for it — so pass `timeoutMs` or skip\n\t * `settled()` for secret sends. Destroying the client rejects every\n\t * pending `settled()` with a {@link BridgeClientDestroyedError}.\n\t */\n\tsettled(opts?: SettleOptions): Promise<Message>;\n}\n\nexport interface CaptureHandle {\n\t/**\n\t * Resolves on the verdict for THIS capture submit: `{ ok: true }` when the\n\t * value was accepted (core removes the capture block), or\n\t * `{ ok: false, message }` when it was rejected — a server validation\n\t * rejection (observed on the `isSubmitting` falling edge, with the\n\t * validation error text), or a failure/drop core stamps on the capture's\n\t * `lastSubmitFailure` keyed to this submit's own minted `submitId` (no\n\t * session to round-trip, value over the length cap, transport failure, a\n\t * duplicate submit while one is in flight, a host-driven reset). The\n\t * correlation is exact: the handle never reads the global `chat.error`,\n\t * so an unrelated failure in the same window can never masquerade as this\n\t * capture's verdict. On a core document that predates `lastSubmitFailure`,\n\t * those failures are not capture-attributable at all and the handle stays\n\t * pending — pass `timeoutMs` when targeting older cores.\n\t */\n\tsettled(opts?: SettleOptions): Promise<OperationResult>;\n}\n\nexport interface CsatSubmitHandle {\n\t/**\n\t * Resolves when core reports this survey's outcome, correlated on the\n\t * success seq advance x surveyType x conversationId triple (and the\n\t * mirror-image error triple). A `partial` submit reports no terminal\n\t * outcome, so `settled()` on one only settles via `timeoutMs`.\n\t */\n\tsettled(opts?: SettleOptions): Promise<OperationResult>;\n}\n\nexport interface FileUploadHandle {\n\t/** The minted upload id sent with `file.upload.start`. */\n\treadonly uploadId: string;\n\t/**\n\t * Re-send the EXACT same `{ file, uploadId }` pair. Core correlates the\n\t * retry to the failed attempt by that pair, so a fresh pair would orphan\n\t * the original error state.\n\t */\n\tretry(): void;\n\t/** Dismiss a reported upload error (`file.uploadError`). */\n\tdismissError(): void;\n}\n\nexport interface EndChatEligibilityResult {\n\t/** `csat.endChatEligible` at the moment the answer arrived. */\n\teligible: boolean | null;\n\t/** Which survey End Chat should present, when eligible. */\n\tsurveyTarget: \"bot\" | \"agent\" | null;\n}\n\nexport interface ObserveOptions<T> {\n\t/** Change detector; defaults to `Object.is`. */\n\tequals?: (a: T, b: T) => boolean;\n\t/** Also invoke the callback immediately with the current value. */\n\temitInitial?: boolean;\n}\n\n/**\n * Typed helpers over the bridge event/state contract. Obtain via\n * `client.operations`. Fire-and-forget helpers return `void`; correlated\n * helpers return a handle or Promise that settles from state transitions\n * (there is no request/response channel — core answers through state).\n */\nexport interface BridgeOperations {\n\t/**\n\t * Send a chat message. The body is trimmed; an empty result sends nothing\n\t * (the returned handle then rejects). `secret: true` sends a masked\n\t * message: the raw body never enters the transcript and the handle never\n\t * resolves. While `ui.secretMessage.isOpen` is true you likely want\n\t * `secret: true` to match the composer's mode.\n\t */\n\tsendMessage(body: string, opts?: { secret?: boolean }): SendHandle;\n\t/** Retry a failed send by message id (see {@link canRetryMessage}). */\n\tretryMessage(messageId: string): void;\n\t/**\n\t * Whether the retry affordance should show for a message: a `text`\n\t * message core marked `isRetryable`, no disconnected Zendesk SDK, and the\n\t * message belongs to the current conversation (or predates stamping).\n\t */\n\tcanRetryMessage(message: Message): boolean;\n\n\t/** Answer a quick-reply chip. Pass the chip and its message id verbatim. */\n\tselectQuickReply(reply: QuickReply, messageId: string): void;\n\t/** Answer an options block. The label falls back to the option id. */\n\tselectOption(option: OptionItem, messageId: string): void;\n\t/**\n\t * Submit the active selectable list (`conversation.stateData.listSelection`\n\t * supplies the `dataId`). No-op when no list selection is active.\n\t */\n\tsubmitListSelection(selectedIds: string[]): void;\n\t/**\n\t * Cancel the active selectable list. Uses the block's own cancel response\n\t * and label (`label` overrides the visible label). No-op when no list\n\t * selection is active or it has no cancel response.\n\t */\n\tcancelListSelection(label?: string): void;\n\t/**\n\t * Submit the active capture field (`conversation.stateData.capture`\n\t * supplies the `dataId`). Each call mints its own `submitId`, so the\n\t * handle settles only on ITS OWN submit's verdict or failure — a second\n\t * submit sent while one is in flight is dropped by core and its handle\n\t * resolves `{ ok: false }` on its own terms rather than inheriting the\n\t * in-flight submit's verdict.\n\t */\n\tsubmitCapture(value: string): CaptureHandle;\n\t/**\n\t * Cancel the active capture field. `label` overrides the block's own\n\t * cancel label. No-op when no capture is active or it has no cancel\n\t * response.\n\t */\n\tcancelCapture(label?: string): void;\n\n\t/** React to a bot message with a thumbs vote. */\n\taddReaction(messageId: string, reaction: 1 | -1): void;\n\t/**\n\t * Report that the user activated a `link` message. Core resolves the\n\t * stored message for tracking and, with `navigateInHost`, validated\n\t * same-tab host navigation.\n\t */\n\treportLinkClick(messageId: string, opts?: { navigateInHost?: boolean }): void;\n\n\t/**\n\t * Advance the persisted read watermark. Reports are debounced 400 ms, the\n\t * watermark is a monotonic lexical max over `_id` cursor strings (calling\n\t * with an older cursor never lowers it), and the accumulator resets when\n\t * `chat.conversationId` changes so a pending cursor from conversation A\n\t * can never commit against B. Call with `message.cursor` for each message\n\t * that scrolls into view; messages stamped with a DIFFERENT\n\t * `conversationId` must be excluded by the caller (the transcript\n\t * preserves the previous conversation's rows).\n\t */\n\tmarkRead(cursor: string, opts?: { conversationId?: string }): void;\n\n\t/**\n\t * Request an older page of history. No-op unless `chat.canLoadMore` is\n\t * true and no load is already in flight.\n\t */\n\tloadOlderMessages(): void;\n\t/**\n\t * Invoke `callback` each time restored/baseline history (re)loads\n\t * (`chat.recentMessagesLoadSeq` advances) — the one-shot trigger for\n\t * \"scroll to the bottom of restored history\" and for seeding an unread\n\t * divider. Returns an unsubscribe function.\n\t */\n\tonHistoryLoaded(callback: (seq: number) => void): () => void;\n\n\t/**\n\t * Submit a CSAT survey. Pass `conversationId: message.conversationId` for\n\t * a survey rendered IN the transcript (the row can outlive its\n\t * conversation); omit it — or pass `null`, which is treated as omitted on\n\t * the wire and in correlation — for End Chat / proactive flows, where\n\t * core resolves the conversation itself.\n\t */\n\tsubmitCsat(\n\t\tdata: CsatSubmitData,\n\t\topts: {\n\t\t\tsurveyType: string;\n\t\t\tpartial?: boolean;\n\t\t\tconversationId?: string | null;\n\t\t},\n\t): CsatSubmitHandle;\n\t/**\n\t * Analytics: report that a survey was displayed. Fire-and-forget. Core drops\n\t * it for post-agent survey types (`live_chat`): api records that survey as\n\t * shown when it routes its row into the transcript, so a client record would\n\t * count the same prompt twice.\n\t */\n\ttrackCsatShown(surveyType: string, conversationId?: string | null): void;\n\n\t/**\n\t * Ask core whether End Chat should present a survey. Correlated by\n\t * latching `csat.endChatEligibility.seq` and resolving when it advances\n\t * (there is no request id; a `ready -> ready` re-check changes no other\n\t * observable state).\n\t */\n\tcheckEndChatEligibility(\n\t\topts?: SettleOptions,\n\t): Promise<EndChatEligibilityResult>;\n\t/**\n\t * End the conversation now with no survey. On a bot with\n\t * `config.features.endLiveChat` this is also the End Chat that follows a live\n\t * chat (`liveAgent.liveChatEndedInConversation` is true): call it straight\n\t * from the End Chat control — no confirmation, no `checkEndChatEligibility()`,\n\t * no survey — as legacy `END_LIVE_CHAT` does. While such a bot is still in a\n\t * live chat, call `endLiveChat()` instead.\n\t */\n\tskipCsatAndEndChat(): void;\n\t/**\n\t * End only the live-agent leg (legacy `END_LIVE_CHAT`). On a bot with\n\t * `config.features.endLiveChat`, an End Chat confirmed while\n\t * `liveAgent.inLiveChat` MUST use this rather than `skipCsatAndEndChat()` or\n\t * an `end_chat` `submitCsat()`, and offer no end-chat survey: the conversation\n\t * stays open and api routes the post-agent (`live_chat`) survey as an inline\n\t * row once the leg has ended, whereas an end-chat submission there is stored\n\t * as the agent's rating under the wrong survey type. Core ignores it outside\n\t * a live-agent leg.\n\t */\n\tendLiveChat(): void;\n\t/**\n\t * Start a new conversation. Applies a local cooldown (default 5 s) that\n\t * swallows rapid re-clicks; returns `false` while cooling down.\n\t */\n\tstartNewConversation(opts?: { cooldownMs?: number }): boolean;\n\t/** Reset the app frame's conversation state. */\n\tresetApp(): void;\n\n\t/**\n\t * Report a content-free composer-changed edge (drives live-agent typing\n\t * indicators). The event is payload-free by contract — composer text must\n\t * never cross the frame boundary — and this helper suppresses it entirely\n\t * in secret mode (pass `secret: true`, or leave it to the\n\t * `ui.secretMessage.isOpen` state check): even a content-free typing edge\n\t * is a signal about the secret being typed.\n\t */\n\tnotifyComposerChanged(opts?: { secret?: boolean }): void;\n\t/** Open or close the masked (secret) composer mode. */\n\ttoggleSecretMessage(open: boolean): void;\n\n\t/**\n\t * Start a live-agent file upload. Mints and retains the `{ file,\n\t * uploadId }` pair so `retry()` replays exactly it. Progress and errors\n\t * land on `file.isUploading`, `file.transfer`, and `file.uploadError`\n\t * (core can report an error without ever setting `isUploading`).\n\t */\n\tstartFileUpload(file: File): FileUploadHandle;\n\n\t/** Change the conversation language (BCP 47 tag the bot offers). */\n\tsetLanguage(language: string): void;\n\t/**\n\t * Email the transcript. Resolves/rejects when `transcript.email.status`\n\t * reaches `success | error` with its `.seq` advanced past this call's\n\t * baseline — a request core drops without ever broadcasting `pending`\n\t * (a host-driven reset in progress) still rejects. On a core document\n\t * that predates the seq, falls back to the `pending -> terminal` edge.\n\t * The email is trimmed; an empty result rejects immediately without\n\t * sending (core drops an empty email silently, so nothing would ever\n\t * settle it). The request is single-flight and address-aware: a call for\n\t * the SAME address as an in-flight request joins it and settles on its\n\t * verdict; a call for a DIFFERENT address is rejected address-matched on\n\t * `transcript.email.conflict.*` — ONLY the conflicting call's promise\n\t * rejects, while the in-flight request still completes, writes the final\n\t * status, and settles its own handle with the true outcome (a rejection\n\t * here never means the in-flight email was not sent). On a core document\n\t * that predates the conflict channel, a conflicting call settles on the\n\t * in-flight verdict instead.\n\t */\n\temailTranscript(email: string, opts?: SettleOptions): Promise<void>;\n\t/**\n\t * Download the transcript. Same settle contract as\n\t * {@link BridgeOperations.emailTranscript}, on\n\t * `transcript.download.status`.\n\t */\n\tdownloadTranscript(opts?: SettleOptions): Promise<void>;\n\t/** Toggle the new-message alert sound. */\n\tsetAlertSound(enabled: boolean): void;\n\t/**\n\t * Ask the HOST page to request Web Notification permission (permission is\n\t * per-origin, so the widget frame's own value is not the governing one).\n\t * Result lands on `chatter.notificationPermission`.\n\t */\n\trequestNotificationPermission(): void;\n\t/** Set the user's in-widget theme override. */\n\tsetTheme(theme: \"light\" | \"dark\" | \"auto\"): void;\n\t/** Set the user's in-widget text-size override. */\n\tsetTextSize(size: \"small\" | \"default\" | \"large\"): void;\n\n\t/** The header close affordance (ends the chat when `endChat.canEnd`). */\n\tclose(): void;\n\t/** Minimize the widget. */\n\tminimize(): void;\n\t/** Dismiss the visible toast. */\n\tdismissToast(): void;\n\t/**\n\t * Clear `chat.error` in core. Clear core state rather than hiding the\n\t * error locally, or the session's second error renders nothing.\n\t */\n\tdismissError(): void;\n\t/** Ask the SDK host to expand or restore the app's viewport surface. */\n\tsetHostViewportExpanded(expanded: boolean): void;\n\t/** Retry after a response outage banner. */\n\tretryAfterOutage(): void;\n\t/**\n\t * Report a fatal app error to core (renders the fallback UI and reports\n\t * telemetry). Callable from anywhere with a client reference — including\n\t * a class error boundary outside any React provider.\n\t */\n\treportAppError(error: unknown, componentStack?: string): void;\n}\n\nfunction mintUuid(): string {\n\tconst cryptoObj = typeof crypto === \"undefined\" ? undefined : crypto;\n\tif (typeof cryptoObj?.randomUUID === \"function\") {\n\t\treturn cryptoObj.randomUUID();\n\t}\n\tconst bytes = new Uint8Array(16);\n\tif (typeof cryptoObj?.getRandomValues === \"function\") {\n\t\tcryptoObj.getRandomValues(bytes);\n\t} else {\n\t\tfor (let index = 0; index < bytes.length; index += 1) {\n\t\t\tbytes[index] = Math.floor(Math.random() * 256);\n\t\t}\n\t}\n\t// RFC 4122 version/variant bits so core's uuid-shape validation accepts it.\n\tbytes[6] = ((bytes[6] as number) & 0x0f) | 0x40;\n\tbytes[8] = ((bytes[8] as number) & 0x3f) | 0x80;\n\tconst hex = Array.from(bytes, (byte) => byte.toString(16).padStart(2, \"0\"));\n\treturn `${hex.slice(0, 4).join(\"\")}-${hex.slice(4, 6).join(\"\")}-${hex\n\t\t.slice(6, 8)\n\t\t.join(\"\")}-${hex.slice(8, 10).join(\"\")}-${hex.slice(10).join(\"\")}`;\n}\n\ntype Outcome<T> = { value: T } | { error: Error };\n\n/**\n * The disposal hooks a settle promise registers with, so `dispose()` can\n * reject every in-flight `settled()` with a typed\n * {@link BridgeClientDestroyedError} — the layer's bound of last resort when\n * no `timeoutMs` was passed.\n */\ninterface SettleLifecycle {\n\tisDisposed(): boolean;\n\tregister(abort: (error: Error) => void): void;\n\tunregister(abort: (error: Error) => void): void;\n}\n\n/**\n * Settle a promise from state transitions: `inspect` runs once immediately\n * and then on every state update until it returns an outcome. `timeoutMs`\n * bounds the wait; without it an operation whose answer never arrives keeps\n * its subscription until it settles or the client is destroyed, which\n * rejects it with a {@link BridgeClientDestroyedError}.\n */\nfunction settleFromState<T>(\n\thost: BridgeOperationsHost,\n\tlifecycle: SettleLifecycle,\n\tinspect: (state: AppDisplayState | null) => Outcome<T> | undefined,\n\topts?: SettleOptions,\n): Promise<T> {\n\treturn new Promise<T>((resolve, reject) => {\n\t\tif (lifecycle.isDisposed()) {\n\t\t\treject(new BridgeClientDestroyedError());\n\t\t\treturn;\n\t\t}\n\t\tlet done = false;\n\t\tlet unsubscribe: (() => void) | null = null;\n\t\tlet timer: ReturnType<typeof setTimeout> | null = null;\n\t\tconst abort = (error: Error): void => {\n\t\t\tfinish({ error });\n\t\t};\n\t\tconst finish = (outcome: Outcome<T>): void => {\n\t\t\tif (done) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tdone = true;\n\t\t\tlifecycle.unregister(abort);\n\t\t\tunsubscribe?.();\n\t\t\tif (timer !== null) {\n\t\t\t\tclearTimeout(timer);\n\t\t\t}\n\t\t\tif (\"value\" in outcome) {\n\t\t\t\tresolve(outcome.value);\n\t\t\t} else {\n\t\t\t\treject(outcome.error);\n\t\t\t}\n\t\t};\n\t\tconst run = (): void => {\n\t\t\tconst outcome = inspect(host.getState());\n\t\t\tif (outcome) {\n\t\t\t\tfinish(outcome);\n\t\t\t}\n\t\t};\n\t\trun();\n\t\tif (done) {\n\t\t\treturn;\n\t\t}\n\t\tlifecycle.register(abort);\n\t\tunsubscribe = host.subscribe(run);\n\t\tif (opts?.timeoutMs !== undefined) {\n\t\t\ttimer = setTimeout(() => {\n\t\t\t\tfinish({\n\t\t\t\t\terror: new Error(\n\t\t\t\t\t\t`Bridge operation timed out after ${opts.timeoutMs}ms.`,\n\t\t\t\t\t),\n\t\t\t\t});\n\t\t\t}, opts.timeoutMs);\n\t\t}\n\t});\n}\n\ninterface StateObservation {\n\tsubscribeKey<K extends keyof AppDisplayState>(\n\t\tkey: K,\n\t\tcallback: (\n\t\t\tvalue: AppDisplayState[K] | undefined,\n\t\t\tprevious: AppDisplayState[K] | undefined,\n\t\t) => void,\n\t\topts?: ObserveOptions<AppDisplayState[K] | undefined>,\n\t): () => void;\n\tselect<T>(\n\t\tselector: (state: AppDisplayState | null) => T,\n\t\tcallback: (value: T, previous: T | undefined) => void,\n\t\topts?: ObserveOptions<T>,\n\t): () => void;\n}\n\n/**\n * Build the `subscribeKey`/`select` observation primitives over a host.\n * `Object.is` is a sound default equality for `subscribeKey`: state crosses\n * the frame boundary by structured clone, so the real client restores each\n * structurally unchanged key's previous reference before notifying (the test\n * mock shares references in memory to begin with). For `select`, whose\n * selector typically MINTS a new object per call, pass a custom `equals`\n * (or select a primitive/signature string) when projecting objects.\n */\nexport function createStateObservation(\n\thost: BridgeOperationsHost,\n): StateObservation {\n\tfunction select<T>(\n\t\tselector: (state: AppDisplayState | null) => T,\n\t\tcallback: (value: T, previous: T | undefined) => void,\n\t\topts?: ObserveOptions<T>,\n\t): () => void {\n\t\tconst equals = opts?.equals ?? Object.is;\n\t\tlet previous = selector(host.getState());\n\t\tif (opts?.emitInitial === true) {\n\t\t\tcallback(previous, undefined);\n\t\t}\n\t\treturn host.subscribe(() => {\n\t\t\tconst next = selector(host.getState());\n\t\t\tif (equals(next, previous)) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst before = previous;\n\t\t\tprevious = next;\n\t\t\tcallback(next, before);\n\t\t});\n\t}\n\n\tfunction subscribeKey<K extends keyof AppDisplayState>(\n\t\tkey: K,\n\t\tcallback: (\n\t\t\tvalue: AppDisplayState[K] | undefined,\n\t\t\tprevious: AppDisplayState[K] | undefined,\n\t\t) => void,\n\t\topts?: ObserveOptions<AppDisplayState[K] | undefined>,\n\t): () => void {\n\t\treturn select((state) => state?.[key], callback, opts);\n\t}\n\n\treturn { subscribeKey, select };\n}\n\nexport interface BridgeOperationsBundle {\n\toperations: BridgeOperations;\n\t/** Release the layer's own subscriptions and timers (client teardown). */\n\tdispose(): void;\n}\n\nexport function createBridgeOperations(\n\trawHost: BridgeOperationsHost,\n): BridgeOperationsBundle {\n\tlet disposed = false;\n\n\t// Disposal must actually stop the layer: after `dispose()` no helper may\n\t// send another event or take a new subscription (the client's transport\n\t// is gone). Guarding the host once covers every helper below.\n\tconst host: BridgeOperationsHost = {\n\t\tgetState: () => rawHost.getState(),\n\t\tsubscribe: (callback) =>\n\t\t\tdisposed\n\t\t\t\t? () => {\n\t\t\t\t\t\t// nothing was subscribed\n\t\t\t\t\t}\n\t\t\t\t: rawHost.subscribe(callback),\n\t\tsendEvent: (event, ...args) => {\n\t\t\tif (!disposed) {\n\t\t\t\trawHost.sendEvent(event, ...args);\n\t\t\t}\n\t\t},\n\t};\n\n\tconst liveSettleAborts = new Set<(error: Error) => void>();\n\tconst lifecycle: SettleLifecycle = {\n\t\tisDisposed: () => disposed,\n\t\tregister: (abort) => {\n\t\t\tliveSettleAborts.add(abort);\n\t\t},\n\t\tunregister: (abort) => {\n\t\t\tliveSettleAborts.delete(abort);\n\t\t},\n\t};\n\n\t// markRead accumulator (mirrors the reference app's useReadTracking).\n\tlet readMax = \"\";\n\tlet readReported = \"\";\n\tlet readTimer: ReturnType<typeof setTimeout> | null = null;\n\tlet readConversationId: string | null = null;\n\tlet readResetUnsubscribe: (() => void) | null = null;\n\n\tfunction resetReadAccumulator(): void {\n\t\treadMax = \"\";\n\t\treadReported = \"\";\n\t\tif (readTimer !== null) {\n\t\t\tclearTimeout(readTimer);\n\t\t\treadTimer = null;\n\t\t}\n\t}\n\n\t// Cursor spaces are per conversation: when the pin changes, drop the\n\t// accumulator AND any pending debounced report so a cursor collected in\n\t// conversation A can never be committed against B.\n\tfunction ensureReadResetSubscription(): void {\n\t\tif (readResetUnsubscribe) {\n\t\t\treturn;\n\t\t}\n\t\tlet previousPin = host.getState()?.[\"chat.conversationId\"] ?? null;\n\t\treadResetUnsubscribe = host.subscribe(() => {\n\t\t\tconst pin = host.getState()?.[\"chat.conversationId\"] ?? null;\n\t\t\tif (pin !== previousPin) {\n\t\t\t\tpreviousPin = pin;\n\t\t\t\tresetReadAccumulator();\n\t\t\t\treadConversationId = pin;\n\t\t\t}\n\t\t});\n\t}\n\n\tfunction scheduleReadReport(): void {\n\t\tif (readTimer !== null) {\n\t\t\treturn;\n\t\t}\n\t\treadTimer = setTimeout(() => {\n\t\t\treadTimer = null;\n\t\t\tif (readMax > readReported) {\n\t\t\t\treadReported = readMax;\n\t\t\t\thost.sendEvent(\"chat.messages.read\", {\n\t\t\t\t\tcursor: readMax,\n\t\t\t\t\t...(readConversationId ? { conversationId: readConversationId } : {}),\n\t\t\t\t});\n\t\t\t}\n\t\t}, READ_REPORT_DEBOUNCE_MS);\n\t}\n\n\tlet newConversationBlockedUntil = 0;\n\n\tconst operations: BridgeOperations = {\n\t\tsendMessage(body, opts) {\n\t\t\tconst trimmed = typeof body === \"string\" ? body.trim() : \"\";\n\t\t\tconst secret = opts?.secret === true;\n\t\t\tconst tempMessageUuid = mintUuid();\n\t\t\tlet errorBaseline: string | null = null;\n\t\t\tlet errorSeqBaseline: number | null = null;\n\t\t\tif (trimmed) {\n\t\t\t\tconst preSend = host.getState();\n\t\t\t\terrorBaseline = preSend?.[\"chat.error\"] ?? null;\n\t\t\t\terrorSeqBaseline = preSend?.[\"chat.error.seq\"] ?? null;\n\t\t\t\thost.sendEvent(\"chat.message.send\", {\n\t\t\t\t\tbody: trimmed,\n\t\t\t\t\tmessageType: secret ? \"secret\" : \"text\",\n\t\t\t\t\ttempMessageUuid,\n\t\t\t\t});\n\t\t\t}\n\t\t\treturn {\n\t\t\t\ttempMessageUuid,\n\t\t\t\tsettled(settleOpts) {\n\t\t\t\t\tif (!trimmed) {\n\t\t\t\t\t\treturn Promise.reject(\n\t\t\t\t\t\t\tnew Error(\"sendMessage requires a non-empty body.\"),\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t\tif (secret) {\n\t\t\t\t\t\t// No optimistic row exists for a secret send by design, so\n\t\t\t\t\t\t// there is nothing to resolve with. Time-bound or skip; the\n\t\t\t\t\t\t// wait is still bounded by client destroy.\n\t\t\t\t\t\treturn settleFromState<Message>(\n\t\t\t\t\t\t\thost,\n\t\t\t\t\t\t\tlifecycle,\n\t\t\t\t\t\t\t() => undefined,\n\t\t\t\t\t\t\tsettleOpts,\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t\treturn settleFromState<Message>(\n\t\t\t\t\t\thost,\n\t\t\t\t\t\tlifecycle,\n\t\t\t\t\t\t(state) => {\n\t\t\t\t\t\t\tconst messages = state?.[\"chat.messages\"] ?? [];\n\t\t\t\t\t\t\tconst match = messages.find(\n\t\t\t\t\t\t\t\t(message) => message.clientKey === tempMessageUuid,\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\tif (match) {\n\t\t\t\t\t\t\t\treturn { value: match };\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tconst error = state?.[\"chat.error\"] ?? null;\n\t\t\t\t\t\t\t// A core that stamps `chat.error.clientKey` (always present,\n\t\t\t\t\t\t\t// null when unattributed) attributes every pre-row send\n\t\t\t\t\t\t\t// failure to the send's own key, so the correlation is exact:\n\t\t\t\t\t\t\t// only THIS send's error rejects, and an unrelated failure\n\t\t\t\t\t\t\t// landing between the send and its optimistic row (the seq\n\t\t\t\t\t\t\t// fallback's false-rejection window) is ignored.\n\t\t\t\t\t\t\tif (state !== null && \"chat.error.clientKey\" in state) {\n\t\t\t\t\t\t\t\tif (\n\t\t\t\t\t\t\t\t\terror !== null &&\n\t\t\t\t\t\t\t\t\tstate[\"chat.error.clientKey\"] === tempMessageUuid\n\t\t\t\t\t\t\t\t) {\n\t\t\t\t\t\t\t\t\treturn { error: new Error(error) };\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\treturn undefined;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tconst errorSeq = state?.[\"chat.error.seq\"];\n\t\t\t\t\t\t\tif (typeof errorSeq === \"number\") {\n\t\t\t\t\t\t\t\tif (errorSeqBaseline === null) {\n\t\t\t\t\t\t\t\t\t// The send raced state hydration (no state yet when the\n\t\t\t\t\t\t\t\t\t// baseline was captured): latch the first observed seq\n\t\t\t\t\t\t\t\t\t// and treat the current error as pre-existing.\n\t\t\t\t\t\t\t\t\terrorSeqBaseline = errorSeq;\n\t\t\t\t\t\t\t\t\treturn undefined;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t// The seq advances on EVERY error set — including a repeat\n\t\t\t\t\t\t\t\t// of the identical text, which the fallback below cannot\n\t\t\t\t\t\t\t\t// see. `!==` rather than `>`, so a counter restarted by a\n\t\t\t\t\t\t\t\t// full core reset still rejects.\n\t\t\t\t\t\t\t\tif (error !== null && errorSeq !== errorSeqBaseline) {\n\t\t\t\t\t\t\t\t\treturn { error: new Error(error) };\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\treturn undefined;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t// Core document predates chat.error.seq: fall back to text\n\t\t\t\t\t\t\t// transitions (a repeated identical error is invisible here).\n\t\t\t\t\t\t\tif (error === null) {\n\t\t\t\t\t\t\t\terrorBaseline = null;\n\t\t\t\t\t\t\t\treturn undefined;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (error !== errorBaseline) {\n\t\t\t\t\t\t\t\treturn { error: new Error(error) };\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\treturn undefined;\n\t\t\t\t\t\t},\n\t\t\t\t\t\tsettleOpts,\n\t\t\t\t\t);\n\t\t\t\t},\n\t\t\t};\n\t\t},\n\n\t\tretryMessage(messageId) {\n\t\t\thost.sendEvent(\"chat.message.retry\", {\n\t\t\t\tmessage: { id: messageId, type: \"text\" },\n\t\t\t});\n\t\t},\n\n\t\tcanRetryMessage(message) {\n\t\t\tconst state = host.getState();\n\t\t\treturn (\n\t\t\t\tmessage.type === \"text\" &&\n\t\t\t\tmessage.isRetryable === true &&\n\t\t\t\tstate?.[\"zendesk.sdk.disconnected\"] !== true &&\n\t\t\t\t(message.conversationId == null ||\n\t\t\t\t\tmessage.conversationId === (state?.[\"chat.conversationId\"] ?? null))\n\t\t\t);\n\t\t},\n\n\t\tselectQuickReply(reply, messageId) {\n\t\t\thost.sendEvent(\"chat.quickReply.select\", {\n\t\t\t\tlabel: reply.label,\n\t\t\t\ttarget: reply.target,\n\t\t\t\tbuttonType: reply.buttonType,\n\t\t\t\t...(reply.value !== undefined ? { value: reply.value } : {}),\n\t\t\t\tmessageId,\n\t\t\t});\n\t\t},\n\n\t\tselectOption(option, messageId) {\n\t\t\thost.sendEvent(\"chat.option.select\", {\n\t\t\t\toptionId: option.id,\n\t\t\t\toptionLabel: option.label || option.id,\n\t\t\t\tmessageId,\n\t\t\t});\n\t\t},\n\n\t\tsubmitListSelection(selectedIds) {\n\t\t\tconst listSelection =\n\t\t\t\thost.getState()?.[\"conversation.stateData.listSelection\"];\n\t\t\tif (!listSelection) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\thost.sendEvent(\"chat.selectableList.submit\", {\n\t\t\t\tselectedIds,\n\t\t\t\tdataId: listSelection.dataId,\n\t\t\t});\n\t\t},\n\n\t\tcancelListSelection(label) {\n\t\t\tconst listSelection =\n\t\t\t\thost.getState()?.[\"conversation.stateData.listSelection\"];\n\t\t\t// Core drops a cancel without a response id, so don't send one.\n\t\t\tif (!listSelection?.cancelCaptureResponseId) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\thost.sendEvent(\"listSelection.cancel\", {\n\t\t\t\tcancelResponseId: listSelection.cancelCaptureResponseId,\n\t\t\t\tcancelLabel: label ?? listSelection.cancelCaptureLabel,\n\t\t\t\tdataId: listSelection.dataId,\n\t\t\t});\n\t\t},\n\n\t\tsubmitCapture(value) {\n\t\t\tconst capture = host.getState()?.[\"conversation.stateData.capture\"];\n\t\t\tif (!capture) {\n\t\t\t\treturn {\n\t\t\t\t\tsettled: () =>\n\t\t\t\t\t\tPromise.reject(new Error(\"No capture field is active.\")),\n\t\t\t\t};\n\t\t\t}\n\t\t\tconst dataId = capture.dataId;\n\t\t\tconst submitId = mintUuid();\n\t\t\thost.sendEvent(\"capture.submit\", { value, dataId, submitId });\n\t\t\t// The verdict rides the capture object itself: `isSubmitting` is\n\t\t\t// core-owned, flips true while the submit is in flight, and the\n\t\t\t// falling edge carries `validationErrorMessage`; an ACCEPTED value\n\t\t\t// removes the whole object (or replaces it with the next block).\n\t\t\t// Every failure or drop WITHOUT a server validation verdict — a\n\t\t\t// pre-flight drop that raises and settles `isSubmitting` inside one\n\t\t\t// broadcast batch, a transport failure, a duplicate submit dropped\n\t\t\t// while another is in flight — lands on `lastSubmitFailure` keyed\n\t\t\t// with this call's own `submitId`, so the correlation is exact and\n\t\t\t// the global `chat.error` is never consulted.\n\t\t\tlet sawSubmitting = false;\n\t\t\treturn {\n\t\t\t\tsettled: (settleOpts) =>\n\t\t\t\t\tsettleFromState<OperationResult>(\n\t\t\t\t\t\thost,\n\t\t\t\t\t\tlifecycle,\n\t\t\t\t\t\t(state) => {\n\t\t\t\t\t\t\tconst current = state?.[\"conversation.stateData.capture\"] ?? null;\n\t\t\t\t\t\t\tif (!current || current.dataId !== dataId) {\n\t\t\t\t\t\t\t\treturn { value: { ok: true } };\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t// Keyed failure BEFORE the in-flight latch: a duplicate\n\t\t\t\t\t\t\t// submit's drop is stamped while the OTHER submit still holds\n\t\t\t\t\t\t\t// `isSubmitting`, and latching that flag here would make this\n\t\t\t\t\t\t\t// handle inherit the other submit's verdict.\n\t\t\t\t\t\t\tconst failure = current.lastSubmitFailure;\n\t\t\t\t\t\t\tif (failure && failure.submitId === submitId) {\n\t\t\t\t\t\t\t\treturn { value: { ok: false, message: failure.message } };\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (current.isSubmitting === true) {\n\t\t\t\t\t\t\t\tsawSubmitting = true;\n\t\t\t\t\t\t\t\treturn undefined;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (sawSubmitting) {\n\t\t\t\t\t\t\t\treturn current.validationErrorMessage\n\t\t\t\t\t\t\t\t\t? {\n\t\t\t\t\t\t\t\t\t\t\tvalue: {\n\t\t\t\t\t\t\t\t\t\t\t\tok: false,\n\t\t\t\t\t\t\t\t\t\t\t\tmessage: current.validationErrorMessage,\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t: { value: { ok: true } };\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\treturn undefined;\n\t\t\t\t\t\t},\n\t\t\t\t\t\tsettleOpts,\n\t\t\t\t\t),\n\t\t\t};\n\t\t},\n\n\t\tcancelCapture(label) {\n\t\t\tconst capture = host.getState()?.[\"conversation.stateData.capture\"];\n\t\t\tif (!capture?.cancelCaptureResponseId) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\thost.sendEvent(\"capture.cancel\", {\n\t\t\t\tcancelResponseId: capture.cancelCaptureResponseId,\n\t\t\t\tcancelLabel: label ?? capture.cancelCaptureLabel,\n\t\t\t\tdataId: capture.dataId,\n\t\t\t});\n\t\t},\n\n\t\taddReaction(messageId, reaction) {\n\t\t\thost.sendEvent(\"chat.message.reaction.add\", { messageId, reaction });\n\t\t},\n\n\t\treportLinkClick(messageId, opts) {\n\t\t\thost.sendEvent(\"chat.link.click\", {\n\t\t\t\tmessageId,\n\t\t\t\t...(opts?.navigateInHost !== undefined\n\t\t\t\t\t? { navigateInHost: opts.navigateInHost }\n\t\t\t\t\t: {}),\n\t\t\t});\n\t\t},\n\n\t\tmarkRead(cursor, opts) {\n\t\t\tif (typeof cursor !== \"string\" || cursor === \"\") {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tensureReadResetSubscription();\n\t\t\tconst conversationId =\n\t\t\t\topts?.conversationId ??\n\t\t\t\thost.getState()?.[\"chat.conversationId\"] ??\n\t\t\t\tnull;\n\t\t\tif (conversationId !== readConversationId) {\n\t\t\t\tresetReadAccumulator();\n\t\t\t\treadConversationId = conversationId;\n\t\t\t}\n\t\t\tif (cursor > readMax) {\n\t\t\t\treadMax = cursor;\n\t\t\t\tscheduleReadReport();\n\t\t\t}\n\t\t},\n\n\t\tloadOlderMessages() {\n\t\t\tconst state = host.getState();\n\t\t\tif (\n\t\t\t\tstate?.[\"chat.canLoadMore\"] !== true ||\n\t\t\t\tstate[\"chat.loadingPreviousMessages\"] === true\n\t\t\t) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\thost.sendEvent(\"chat.logs.loadMore\");\n\t\t},\n\n\t\tonHistoryLoaded(callback) {\n\t\t\tlet previous = host.getState()?.[\"chat.recentMessagesLoadSeq\"] ?? 0;\n\t\t\treturn host.subscribe(() => {\n\t\t\t\tconst seq = host.getState()?.[\"chat.recentMessagesLoadSeq\"] ?? 0;\n\t\t\t\tif (seq > previous) {\n\t\t\t\t\tprevious = seq;\n\t\t\t\t\tcallback(seq);\n\t\t\t\t} else if (seq < previous) {\n\t\t\t\t\t// Returned to 0 on a chat clear; re-arm without firing.\n\t\t\t\t\tprevious = seq;\n\t\t\t\t}\n\t\t\t});\n\t\t},\n\n\t\tsubmitCsat(data, opts) {\n\t\t\tconst { surveyType, partial, conversationId } = opts;\n\t\t\tconst baselineSeq = host.getState()?.[\"csat.submitSuccess.seq\"] ?? 0;\n\t\t\tlet errorBaseline = host.getState()?.[\"csat.submitError\"] ?? null;\n\t\t\tlet errorSeqBaseline = host.getState()?.[\"csat.submitError.seq\"] ?? null;\n\t\t\t// `null` means \"omitted\" on the wire AND in correlation: core's\n\t\t\t// resolver treats a falsy id as absent and reports the SESSION's id\n\t\t\t// back, so a null-scoped predicate could never match it and the\n\t\t\t// handle would hang on a fully successful submit.\n\t\t\thost.sendEvent(\"csat.submit\", {\n\t\t\t\tdata,\n\t\t\t\tsurveyType,\n\t\t\t\t...(partial !== undefined ? { partial } : {}),\n\t\t\t\t...(conversationId != null ? { conversationId } : {}),\n\t\t\t});\n\t\t\t// When the caller omitted conversationId (End Chat / proactive),\n\t\t\t// core resolves the conversation itself, so match on the type +\n\t\t\t// seq alone; a caller-scoped survey matches its exact conversation.\n\t\t\tconst matchesConversation = (\n\t\t\t\tcandidate: string | null | undefined,\n\t\t\t): boolean =>\n\t\t\t\tconversationId == null || (candidate ?? null) === conversationId;\n\t\t\treturn {\n\t\t\t\tsettled: (settleOpts) =>\n\t\t\t\t\tsettleFromState<OperationResult>(\n\t\t\t\t\t\thost,\n\t\t\t\t\t\tlifecycle,\n\t\t\t\t\t\t(state) => {\n\t\t\t\t\t\t\tconst seq = state?.[\"csat.submitSuccess.seq\"] ?? 0;\n\t\t\t\t\t\t\tif (\n\t\t\t\t\t\t\t\tseq > baselineSeq &&\n\t\t\t\t\t\t\t\tstate?.[\"csat.submitSuccess.surveyType\"] === surveyType &&\n\t\t\t\t\t\t\t\tmatchesConversation(\n\t\t\t\t\t\t\t\t\tstate?.[\"csat.submitSuccess.conversationId\"],\n\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t) {\n\t\t\t\t\t\t\t\treturn { value: { ok: true } };\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tconst error = state?.[\"csat.submitError\"] ?? null;\n\t\t\t\t\t\t\tconst errorSeq = state?.[\"csat.submitError.seq\"];\n\t\t\t\t\t\t\tif (typeof errorSeq === \"number\") {\n\t\t\t\t\t\t\t\t// The seq advances on EVERY error set — including a repeat\n\t\t\t\t\t\t\t\t// of the identical text (the same submit dropped twice at a\n\t\t\t\t\t\t\t\t// core gate), which the text transition below cannot see.\n\t\t\t\t\t\t\t\tif (errorSeqBaseline === null) {\n\t\t\t\t\t\t\t\t\t// The submit raced state hydration: latch the first\n\t\t\t\t\t\t\t\t\t// observed seq and treat the current error as\n\t\t\t\t\t\t\t\t\t// pre-existing.\n\t\t\t\t\t\t\t\t\terrorSeqBaseline = errorSeq;\n\t\t\t\t\t\t\t\t\treturn undefined;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif (\n\t\t\t\t\t\t\t\t\terror !== null &&\n\t\t\t\t\t\t\t\t\terrorSeq !== errorSeqBaseline &&\n\t\t\t\t\t\t\t\t\tstate?.[\"csat.submitErrorSurveyType\"] === surveyType &&\n\t\t\t\t\t\t\t\t\tmatchesConversation(state?.[\"csat.submitErrorConversationId\"])\n\t\t\t\t\t\t\t\t) {\n\t\t\t\t\t\t\t\t\treturn { value: { ok: false, message: error } };\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\treturn undefined;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t// Core document predates csat.submitError.seq: fall back to\n\t\t\t\t\t\t\t// text transitions.\n\t\t\t\t\t\t\tif (error === null) {\n\t\t\t\t\t\t\t\t// Core clears the error at the start of every\n\t\t\t\t\t\t\t\t// non-partial submit; from here any error is ours.\n\t\t\t\t\t\t\t\terrorBaseline = null;\n\t\t\t\t\t\t\t\treturn undefined;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (\n\t\t\t\t\t\t\t\terror !== errorBaseline &&\n\t\t\t\t\t\t\t\tstate?.[\"csat.submitErrorSurveyType\"] === surveyType &&\n\t\t\t\t\t\t\t\tmatchesConversation(state?.[\"csat.submitErrorConversationId\"])\n\t\t\t\t\t\t\t) {\n\t\t\t\t\t\t\t\treturn { value: { ok: false, message: error } };\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\treturn undefined;\n\t\t\t\t\t\t},\n\t\t\t\t\t\tsettleOpts,\n\t\t\t\t\t),\n\t\t\t};\n\t\t},\n\n\t\ttrackCsatShown(surveyType, conversationId) {\n\t\t\thost.sendEvent(\"csat.shown\", {\n\t\t\t\tsurveyType,\n\t\t\t\t...(conversationId != null ? { conversationId } : {}),\n\t\t\t});\n\t\t},\n\n\t\tcheckEndChatEligibility(opts) {\n\t\t\tconst baselineSeq = host.getState()?.[\"csat.endChatEligibility.seq\"] ?? 0;\n\t\t\thost.sendEvent(\"csat.checkEndChatEligibility\");\n\t\t\treturn settleFromState<EndChatEligibilityResult>(\n\t\t\t\thost,\n\t\t\t\tlifecycle,\n\t\t\t\t(state) => {\n\t\t\t\t\tconst seq = state?.[\"csat.endChatEligibility.seq\"] ?? 0;\n\t\t\t\t\tif (seq <= baselineSeq) {\n\t\t\t\t\t\treturn undefined;\n\t\t\t\t\t}\n\t\t\t\t\treturn {\n\t\t\t\t\t\tvalue: {\n\t\t\t\t\t\t\teligible: state?.[\"csat.endChatEligible\"] ?? null,\n\t\t\t\t\t\t\tsurveyTarget: state?.[\"csat.endChatSurveyTarget\"] ?? null,\n\t\t\t\t\t\t},\n\t\t\t\t\t};\n\t\t\t\t},\n\t\t\t\topts,\n\t\t\t);\n\t\t},\n\n\t\tskipCsatAndEndChat() {\n\t\t\thost.sendEvent(\"endChat.skipCsat\");\n\t\t},\n\n\t\tendLiveChat() {\n\t\t\thost.sendEvent(\"endChat.endLiveChat\");\n\t\t},\n\n\t\tstartNewConversation(opts) {\n\t\t\tconst now = Date.now();\n\t\t\tif (now < newConversationBlockedUntil) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\tnewConversationBlockedUntil =\n\t\t\t\tnow + (opts?.cooldownMs ?? START_NEW_CONVERSATION_COOLDOWN_MS);\n\t\t\thost.sendEvent(\"app.startNewConversation\");\n\t\t\treturn true;\n\t\t},\n\n\t\tresetApp() {\n\t\t\thost.sendEvent(\"app.reset\");\n\t\t},\n\n\t\tnotifyComposerChanged(opts) {\n\t\t\t// Even a content-free typing edge is a signal that a secret is\n\t\t\t// being typed, so it stays suppressed at the source.\n\t\t\tif (\n\t\t\t\topts?.secret === true ||\n\t\t\t\thost.getState()?.[\"ui.secretMessage.isOpen\"] === true\n\t\t\t) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\thost.sendEvent(\"ui.composer.textChanged\");\n\t\t},\n\n\t\ttoggleSecretMessage(open) {\n\t\t\thost.sendEvent(\"ui.secretMessage.toggle\", { open });\n\t\t},\n\n\t\tstartFileUpload(file) {\n\t\t\tconst uploadId = mintUuid();\n\t\t\thost.sendEvent(\"file.upload.start\", { file, uploadId });\n\t\t\treturn {\n\t\t\t\tuploadId,\n\t\t\t\tretry() {\n\t\t\t\t\thost.sendEvent(\"file.upload.retry\", { file, uploadId });\n\t\t\t\t},\n\t\t\t\tdismissError() {\n\t\t\t\t\thost.sendEvent(\"file.upload.errorDismiss\");\n\t\t\t\t},\n\t\t\t};\n\t\t},\n\n\t\tsetLanguage(language) {\n\t\t\thost.sendEvent(\"settings.language.change\", { language });\n\t\t},\n\n\t\temailTranscript(email, opts) {\n\t\t\t// Core drops an empty email silently — no status transition ever —\n\t\t\t// so validate here and reject immediately instead of hanging.\n\t\t\tconst trimmed = typeof email === \"string\" ? email.trim() : \"\";\n\t\t\tif (!trimmed) {\n\t\t\t\treturn Promise.reject(\n\t\t\t\t\tnew Error(\"emailTranscript requires a non-empty email.\"),\n\t\t\t\t);\n\t\t\t}\n\t\t\thost.sendEvent(\"settings.transcript.email.send\", { email: trimmed });\n\t\t\treturn transcriptSettled(\"transcript.email.status\", opts, trimmed);\n\t\t},\n\n\t\tdownloadTranscript(opts) {\n\t\t\thost.sendEvent(\"settings.transcript.download.request\");\n\t\t\treturn transcriptSettled(\"transcript.download.status\", opts);\n\t\t},\n\n\t\tsetAlertSound(enabled) {\n\t\t\thost.sendEvent(\"settings.alertSound.toggle\", { enabled });\n\t\t},\n\n\t\trequestNotificationPermission() {\n\t\t\thost.sendEvent(\"settings.notifications.requestPermission\");\n\t\t},\n\n\t\tsetTheme(theme) {\n\t\t\thost.sendEvent(\"appearance.setTheme\", { theme });\n\t\t},\n\n\t\tsetTextSize(size) {\n\t\t\thost.sendEvent(\"appearance.setTextSize\", { textSize: size });\n\t\t},\n\n\t\tclose() {\n\t\t\thost.sendEvent(\"ui.closeButton.click\");\n\t\t},\n\n\t\tminimize() {\n\t\t\thost.sendEvent(\"ui.minimizeButton.click\");\n\t\t},\n\n\t\tdismissToast() {\n\t\t\thost.sendEvent(\"ui.toast.dismiss\");\n\t\t},\n\n\t\tdismissError() {\n\t\t\thost.sendEvent(\"chat.error.dismiss\");\n\t\t},\n\n\t\tsetHostViewportExpanded(expanded) {\n\t\t\thost.sendEvent(\"ui.hostViewport.set\", { expanded });\n\t\t},\n\n\t\tretryAfterOutage() {\n\t\t\thost.sendEvent(\"outage.retry\");\n\t\t},\n\n\t\treportAppError(error, componentStack) {\n\t\t\tconst err = error instanceof Error ? error : null;\n\t\t\thost.sendEvent(\"app.error.report\", {\n\t\t\t\tmessage: err?.message ?? String(error),\n\t\t\t\tstack: err?.stack ?? \"\",\n\t\t\t\tcomponentStack: componentStack ?? \"\",\n\t\t\t});\n\t\t},\n\t};\n\n\tfunction transcriptSettled(\n\t\tkey: \"transcript.email.status\" | \"transcript.download.status\",\n\t\topts?: SettleOptions,\n\t\t// Email only: reject THIS call when core rejects a conflicting request\n\t\t// for exactly this address on `transcript.email.conflict.*` — the\n\t\t// shared status channel never carries the conflict, so the in-flight\n\t\t// caller settles on its own verdict.\n\t\tconflictEmail?: string,\n\t): Promise<void> {\n\t\tconst seqKey = `${key}.seq` as\n\t\t\t| \"transcript.email.status.seq\"\n\t\t\t| \"transcript.download.status.seq\";\n\t\tlet previous = host.getState()?.[key] ?? \"idle\";\n\t\tlet seqBaseline = host.getState()?.[seqKey] ?? null;\n\t\tlet conflictSeqBaseline =\n\t\t\thost.getState()?.[\"transcript.email.conflict.seq\"] ?? null;\n\t\treturn settleFromState<void>(\n\t\t\thost,\n\t\t\tlifecycle,\n\t\t\t(state) => {\n\t\t\t\tif (conflictEmail !== undefined) {\n\t\t\t\t\tconst conflictSeq = state?.[\"transcript.email.conflict.seq\"];\n\t\t\t\t\tif (typeof conflictSeq === \"number\") {\n\t\t\t\t\t\tif (conflictSeqBaseline === null) {\n\t\t\t\t\t\t\t// The request raced state hydration: latch the first\n\t\t\t\t\t\t\t// observed seq, mirroring the status-seq baseline below.\n\t\t\t\t\t\t\t// Residual: if that first snapshot already carries THIS\n\t\t\t\t\t\t\t// call's own conflict, the reject branch never fires and\n\t\t\t\t\t\t\t// the handle falls through to the shared-status\n\t\t\t\t\t\t\t// correlation — the same bounded degradation as the\n\t\t\t\t\t\t\t// documented old-core (no conflict keys) skew. Not\n\t\t\t\t\t\t\t// closable from here: with no pre-call baseline, a\n\t\t\t\t\t\t\t// matching-address conflict in the first snapshot is\n\t\t\t\t\t\t\t// indistinguishable from a pre-existing one, and\n\t\t\t\t\t\t\t// treating it as ours would false-reject.\n\t\t\t\t\t\t\tconflictSeqBaseline = conflictSeq;\n\t\t\t\t\t\t} else if (\n\t\t\t\t\t\t\tconflictSeq !== conflictSeqBaseline &&\n\t\t\t\t\t\t\tstate?.[\"transcript.email.conflict.email\"] === conflictEmail\n\t\t\t\t\t\t) {\n\t\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\t\terror: new Error(\n\t\t\t\t\t\t\t\t\t\"emailTranscript was rejected: a request for a different \" +\n\t\t\t\t\t\t\t\t\t\t\"address is already in flight. The in-flight email \" +\n\t\t\t\t\t\t\t\t\t\t\"still completes with its own outcome.\",\n\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tconst status = state?.[key] ?? \"idle\";\n\t\t\t\tconst seq = state?.[seqKey];\n\t\t\t\tif (typeof seq === \"number\") {\n\t\t\t\t\t// The seq advances on every status write, so a request core\n\t\t\t\t\t// drops straight to a terminal status (never `pending` in a\n\t\t\t\t\t// broadcast snapshot) and a repeat of the previous terminal\n\t\t\t\t\t// status both stay observable.\n\t\t\t\t\tif (seqBaseline === null) {\n\t\t\t\t\t\t// The request raced state hydration: latch the first observed\n\t\t\t\t\t\t// seq and treat the current status as pre-existing.\n\t\t\t\t\t\tseqBaseline = seq;\n\t\t\t\t\t\treturn undefined;\n\t\t\t\t\t}\n\t\t\t\t\tif (seq === seqBaseline) {\n\t\t\t\t\t\treturn undefined;\n\t\t\t\t\t}\n\t\t\t\t\tif (status === \"success\") {\n\t\t\t\t\t\treturn { value: undefined };\n\t\t\t\t\t}\n\t\t\t\t\tif (status === \"error\") {\n\t\t\t\t\t\treturn { error: new Error(`${key} reported an error.`) };\n\t\t\t\t\t}\n\t\t\t\t\treturn undefined;\n\t\t\t\t}\n\t\t\t\t// Core document predates the seq key: fall back to the\n\t\t\t\t// pending -> terminal edge.\n\t\t\t\tconst was = previous;\n\t\t\t\tprevious = status;\n\t\t\t\tif (was !== \"pending\") {\n\t\t\t\t\treturn undefined;\n\t\t\t\t}\n\t\t\t\tif (status === \"success\") {\n\t\t\t\t\treturn { value: undefined };\n\t\t\t\t}\n\t\t\t\tif (status === \"error\") {\n\t\t\t\t\treturn { error: new Error(`${key} reported an error.`) };\n\t\t\t\t}\n\t\t\t\treturn undefined;\n\t\t\t},\n\t\t\topts,\n\t\t);\n\t}\n\n\treturn {\n\t\toperations,\n\t\tdispose() {\n\t\t\tif (disposed) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tdisposed = true;\n\t\t\tresetReadAccumulator();\n\t\t\treadResetUnsubscribe?.();\n\t\t\treadResetUnsubscribe = null;\n\t\t\t// Reject every in-flight settled() promise: the transport is gone,\n\t\t\t// so no state transition can ever answer it. Snapshot first — each\n\t\t\t// abort unregisters itself from the live set.\n\t\t\tfor (const abort of [...liveSettleAborts]) {\n\t\t\t\tabort(new BridgeClientDestroyedError());\n\t\t\t}\n\t\t\tliveSettleAborts.clear();\n\t\t},\n\t};\n}\n","import type { BridgeClient } from \"../contract/bridge-client\";\nimport {\n\tcreateBridgeOperations,\n\tcreateStateObservation,\n} from \"../contract/operations\";\nimport type { AppDisplayState, AppEvents } from \"../contract/types\";\n\n/** One recorded `sendEvent` call. */\nexport interface SentBridgeEvent<K extends keyof AppEvents = keyof AppEvents> {\n\tevent: K;\n\tpayload: AppEvents[K] | undefined;\n}\n\n/**\n * A scriptable, in-memory {@link BridgeClient} for unit tests.\n *\n * No postMessage, no origin checks, no network: tests drive state through\n * `setState`/`updateState` and assert on the events the component under test\n * sent through `sentEvents`. The `operations`, `subscribeKey`, and `select`\n * surfaces are the REAL implementations running over this in-memory state:\n * every operation records its events in `sentEvents`, and a correlated\n * handle (`sendMessage().settled()`, `submitCsat().settled()`, ...) resolves\n * when the test scripts the answering state transition via `updateState`.\n */\nexport interface MockBridgeClient extends BridgeClient {\n\t/** Replace the state snapshot and notify subscribers. */\n\tsetState(state: AppDisplayState | null): void;\n\t/**\n\t * Shallow-merge a partial snapshot over the current state (or over an empty\n\t * object when no state is set) and notify subscribers. Seeds\n\t * `\"chat.error.clientKey\": null` when the key is absent — production core\n\t * always publishes it (null when no keyed send owns the error), and\n\t * `operations` send handles take the keyed correlation path only when it is\n\t * present. Script a `chat.error` fixture with the send's `tempMessageUuid`\n\t * as `chat.error.clientKey` to reject that handle; use `setState` to script\n\t * an older core document without the key.\n\t */\n\tupdateState(partial: Partial<AppDisplayState>): void;\n\t/**\n\t * Every event sent through the client, in order. Each read returns a fresh\n\t * copy, so a captured array survives `clearSentEvents()`.\n\t */\n\treadonly sentEvents: readonly SentBridgeEvent[];\n\t/** Forget recorded events without touching state or subscribers. */\n\tclearSentEvents(): void;\n\t/** Number of active subscribers. */\n\treadonly subscriberCount: number;\n\t/** True once `destroy()` has been called. */\n\treadonly destroyed: boolean;\n}\n\n/**\n * Create a {@link MockBridgeClient}, optionally seeded with an initial state\n * snapshot. Mirrors the real client's teardown semantics: after `destroy()`,\n * state is `null`, subscribers are cleared, `sendEvent` is a no-op, and every\n * in-flight `operations` `settled()` promise rejects with a\n * `BridgeClientDestroyedError`. Notification also mirrors the real client:\n * a throwing subscriber is caught and logged, never aborting the pass.\n *\n * The derivation helpers (`filterDisplayable`, `groupMessages`, `messageKey`,\n * `selectUnread`, ...) are runtime-only: this package types them on\n * `MessagingBridgeModule` but ships no implementation, so there is no mock\n * module to destructure them from. In unit tests, inject them into the code\n * under test (plain function parameters or props); to exercise the real\n * implementations, load the module with `loadMessagingBridge()` in an\n * integration test. Do not re-implement them — a copy drifts from the\n * runtime's edge cases.\n */\nexport function createMockBridgeClient(\n\tinitialState: AppDisplayState | null = null,\n): MockBridgeClient {\n\tlet state = initialState;\n\tlet destroyed = false;\n\tconst subscribers = new Set<() => void>();\n\tconst sentEvents: SentBridgeEvent[] = [];\n\n\tfunction notify(): void {\n\t\t// Mirrors the real client's notify(): one throwing subscriber must not\n\t\t// abort the pass (a settled() promise resolved from a later subscriber\n\t\t// would otherwise hang) or skip subscribers that unsubscribe mid-pass.\n\t\tfor (const callback of [...subscribers]) {\n\t\t\ttry {\n\t\t\t\tcallback();\n\t\t\t} catch (err) {\n\t\t\t\tconsole.error(\"[AdaMessaging] Bridge subscriber threw:\", err);\n\t\t\t}\n\t\t}\n\t}\n\n\tfunction getState(): AppDisplayState | null {\n\t\treturn state;\n\t}\n\n\tfunction subscribe(callback: () => void): () => void {\n\t\tsubscribers.add(callback);\n\t\treturn () => {\n\t\t\tsubscribers.delete(callback);\n\t\t};\n\t}\n\n\tfunction sendEvent<K extends keyof AppEvents>(\n\t\tevent: K,\n\t\t...args: AppEvents[K] extends undefined ? [] : [payload: AppEvents[K]]\n\t): void {\n\t\tif (destroyed) {\n\t\t\treturn;\n\t\t}\n\t\tsentEvents.push({ event, payload: args[0] });\n\t}\n\n\tconst host = { getState, subscribe, sendEvent };\n\tconst observation = createStateObservation(host);\n\tconst operationsBundle = createBridgeOperations(host);\n\n\treturn {\n\t\tgetState,\n\t\tsubscribe,\n\t\tsendEvent,\n\t\tsubscribeKey: observation.subscribeKey,\n\t\tselect: observation.select,\n\t\toperations: operationsBundle.operations,\n\n\t\tdestroy(): void {\n\t\t\tdestroyed = true;\n\t\t\toperationsBundle.dispose();\n\t\t\tstate = null;\n\t\t\tsubscribers.clear();\n\t\t},\n\n\t\tsetState(next: AppDisplayState | null): void {\n\t\t\tstate = next;\n\t\t\tnotify();\n\t\t},\n\n\t\tupdateState(partial: Partial<AppDisplayState>): void {\n\t\t\t// Production core always publishes `chat.error.clientKey` (null when\n\t\t\t// unattributed), and `operations` feature-detects on its presence.\n\t\t\t// Seed it so a scripted failure exercises the keyed path production\n\t\t\t// takes; override it in `partial` (or use setState) to script an\n\t\t\t// older core document.\n\t\t\tstate = {\n\t\t\t\t\"chat.error.clientKey\": null,\n\t\t\t\t...(state ?? {}),\n\t\t\t\t...partial,\n\t\t\t} as AppDisplayState;\n\t\t\tnotify();\n\t\t},\n\n\t\tget sentEvents(): readonly SentBridgeEvent[] {\n\t\t\treturn [...sentEvents];\n\t\t},\n\n\t\tclearSentEvents(): void {\n\t\t\tsentEvents.length = 0;\n\t\t},\n\n\t\tget subscriberCount(): number {\n\t\t\treturn subscribers.size;\n\t\t},\n\n\t\tget destroyed(): boolean {\n\t\t\treturn destroyed;\n\t\t},\n\t};\n}\n"],"mappings":"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"}
@@ -21,6 +21,11 @@ export type FileUploadErrorReason =
21
21
  | "timeout"
22
22
  | "unsupported_platform"
23
23
  | "upload_not_enabled";
24
+ /** Size cap of the bot image attachment: api's `MAX_FILE_SIZE_BYTES_FOR_STORAGE_UPLOAD`. */
25
+ export declare const IMAGE_ATTACHMENT_MAX_MB = 50;
26
+ export declare const IMAGE_ATTACHMENT_MAX_BYTES: number;
27
+ /** The image types api's attachments route accepts. */
28
+ export declare const IMAGE_ATTACHMENT_MIME_TYPES: readonly string[];
24
29
  /** Typed bridge contract for file-upload failures shown to the chatter. */
25
30
  export interface FileUploadErrorData {
26
31
  reason?: FileUploadErrorReason;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ada-cx/messaging-bridge",
3
- "version": "1.0.0",
3
+ "version": "1.2.0",
4
4
  "description": "Types, test mocks, and a thin CDN loader for building a custom app UI on Ada Messaging's bridge contract. The bridge runtime always loads from Ada's CDN.",
5
5
  "type": "module",
6
6
  "license": "ISC",