@ada-cx/messaging-bridge 1.3.0 → 1.3.3
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 +42 -3
- package/dist-npm/contract/state-keys.d.ts +1 -0
- package/dist-npm/contract/types.d.ts +4 -0
- package/dist-npm/{loader-CfCnvaTU.js → loader-BjjKuT42.js} +115 -18
- package/dist-npm/loader-BjjKuT42.js.map +1 -0
- package/dist-npm/npm/index.d.ts +1 -1
- package/dist-npm/npm/index.js +2 -2
- package/dist-npm/npm/loader.d.ts +9 -3
- package/dist-npm/npm/react.d.ts +1 -1
- package/dist-npm/npm/react.js +2 -2
- package/dist-npm/npm/testing.js +503 -474
- package/dist-npm/npm/testing.js.map +1 -1
- package/dist-npm/shared-utils/file-upload.d.ts +7 -1
- package/package.json +1 -1
- package/dist-npm/loader-CfCnvaTU.js.map +0 -1
|
@@ -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/**\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 while preserving prior messages. Applies a local\n\t * cooldown (default 5 s); `false` means no request was sent, while `true`\n\t * means only that it was sent. Also returns false while\n\t * `app.conversationStartPending` is true, without extending the cooldown.\n\t * Proactive requests follow resetApp's shared restart behavior below.\n\t */\n\tstartNewConversation(opts?: { cooldownMs?: number }): boolean;\n\t/**\n\t * Clear the transcript and start a new conversation. If proactive requests\n\t * are already pending, this and startNewConversation wait for them: success\n\t * completes the restart with the proactive's messages and existing transcript\n\t * intact, without an extra greeting. A completed success awaiting conversation\n\t * delivery can satisfy one restart too; another explicit restart after that\n\t * completion is a new intent, allowing recovery when no conversation arrives.\n\t * If all fail and no newer conversation,\n\t * session or accepted user send has superseded the request, the latest action\n\t * accepted by Core runs once: resetApp clears history; startNewConversation preserves it. Render\n\t * both controls busy while `app.conversationStartPending` is true; it is\n\t * optional on older cores. This void helper still sends while pending.\n\t * An accepted user send or retry cancels the waiting restart; a rejected send\n\t * does not.\n\t * Without pending work or an unused success awaiting delivery, resets begin immediately.\n\t */\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\tif (host.getState()?.[\"app.conversationStartPending\"] === true) {\n\t\t\t\treturn false;\n\t\t\t}\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":["../../../../shared/utils/src/browser-polyfills.ts","../../src/contract/operations.ts","../../src/npm/testing.ts"],"sourcesContent":["/**\n * Defence in depth for engines just below the declared browser floor.\n * The floor is defined in `messaging/vite.browser-targets.mjs`.\n *\n * Internal Ada code should prefer direct helpers, such as `hasOwn(...)` from\n * `guards.ts`. Every Ada bundle includes these fallbacks, including the host-page\n * SDK. These fallbacks also ship inside the customer-held npm tarballs of the SDK\n * and bridge. Each fallback defines a member only when it is missing. These\n * fallbacks do not extend browser support below the floor required by the shipped CSS.\n */\n\nconst objectHasOwnProperty = Object.prototype.hasOwnProperty;\n\nfunction defineValue(\n\ttarget: object,\n\tname: PropertyKey,\n\tvalue: (...args: unknown[]) => unknown,\n): void {\n\tObject.defineProperty(target, name, {\n\t\tvalue,\n\t\tconfigurable: true,\n\t\twritable: true,\n\t});\n}\n\nif (!Object.hasOwn) {\n\tdefineValue(Object, \"hasOwn\", (object: unknown, key: unknown) => {\n\t\tif (object == null) {\n\t\t\tthrow new TypeError(\n\t\t\t\t\"ada browser-polyfills: Cannot convert undefined or null to object\",\n\t\t\t);\n\t\t}\n\t\treturn objectHasOwnProperty.call(Object(object), key as PropertyKey);\n\t});\n}\n\nfunction at<T>(\n\tthis: { length: number; [index: number]: T },\n\tindex: unknown,\n): T | undefined {\n\tconst length = Math.trunc(Number(this.length)) || 0;\n\tlet relativeIndex = Math.trunc(Number(index)) || 0;\n\tif (relativeIndex < 0) {\n\t\trelativeIndex += length;\n\t}\n\treturn relativeIndex < 0 || relativeIndex >= length\n\t\t? undefined\n\t\t: this[relativeIndex];\n}\n\nfunction stringAt(this: string, index: unknown): string | undefined {\n\tconst value = String(this);\n\tconst length = value.length;\n\tlet relativeIndex = Math.trunc(Number(index)) || 0;\n\tif (relativeIndex < 0) {\n\t\trelativeIndex += length;\n\t}\n\treturn relativeIndex < 0 || relativeIndex >= length\n\t\t? undefined\n\t\t: value.charAt(relativeIndex);\n}\n\nconst typedArrayConstructors = [\n\tglobalThis.Int8Array,\n\tglobalThis.Uint8Array,\n\tglobalThis.Uint8ClampedArray,\n\tglobalThis.Int16Array,\n\tglobalThis.Uint16Array,\n\tglobalThis.Int32Array,\n\tglobalThis.Uint32Array,\n\tglobalThis.Float32Array,\n\tglobalThis.Float64Array,\n\tglobalThis.BigInt64Array,\n\tglobalThis.BigUint64Array,\n].filter(Boolean);\n\nif (!Array.prototype.at) {\n\tdefineValue(Array.prototype, \"at\", at);\n}\n\nif (!String.prototype.at) {\n\tdefineValue(String.prototype, \"at\", stringAt);\n}\n\nfor (const TypedArrayConstructor of typedArrayConstructors) {\n\tconst prototype = TypedArrayConstructor.prototype;\n\tif (!prototype.at) {\n\t\tdefineValue(prototype, \"at\", at);\n\t}\n}\n","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 while preserving prior messages. Applies a local\n\t * cooldown (default 5 s); `false` means no request was sent, while `true`\n\t * means only that it was sent. Also returns false while\n\t * `app.conversationStartPending` is true, without extending the cooldown.\n\t * Proactive requests follow resetApp's shared restart behavior below.\n\t */\n\tstartNewConversation(opts?: { cooldownMs?: number }): boolean;\n\t/**\n\t * Clear the transcript and start a new conversation. If proactive requests\n\t * are already pending, this and startNewConversation wait for them: success\n\t * completes the restart with the proactive's messages and existing transcript\n\t * intact, without an extra greeting. A completed success awaiting conversation\n\t * delivery can satisfy one restart too; another explicit restart after that\n\t * completion is a new intent, allowing recovery when no conversation arrives.\n\t * If all fail and no newer conversation,\n\t * session or accepted user send has superseded the request, the latest action\n\t * accepted by Core runs once: resetApp clears history; startNewConversation preserves it. Render\n\t * both controls busy while `app.conversationStartPending` is true; it is\n\t * optional on older cores. This void helper still sends while pending.\n\t * An accepted user send or retry cancels the waiting restart; a rejected send\n\t * does not.\n\t * Without pending work or an unused success awaiting delivery, resets begin immediately.\n\t */\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\tif (host.getState()?.[\"app.conversationStartPending\"] === true) {\n\t\t\t\treturn false;\n\t\t\t}\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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"}
|
|
@@ -24,8 +24,14 @@ export type FileUploadErrorReason =
|
|
|
24
24
|
/** Size cap of the bot image attachment: api's `MAX_FILE_SIZE_BYTES_FOR_STORAGE_UPLOAD`. */
|
|
25
25
|
export declare const IMAGE_ATTACHMENT_MAX_MB = 50;
|
|
26
26
|
export declare const IMAGE_ATTACHMENT_MAX_BYTES: number;
|
|
27
|
-
/** The image types api's attachments route accepts. */
|
|
27
|
+
/** The image types api's attachments route accepts; HEIC/HEIF are transcoded to JPEG there. */
|
|
28
28
|
export declare const IMAGE_ATTACHMENT_MIME_TYPES: readonly string[];
|
|
29
|
+
/**
|
|
30
|
+
* The picker's `accept` value. Extensions ride along with the MIME types because
|
|
31
|
+
* Windows and Android often report no type for `.heic`/`.heif`, which would grey
|
|
32
|
+
* them out in a MIME-only filter.
|
|
33
|
+
*/
|
|
34
|
+
export declare const IMAGE_ATTACHMENT_ACCEPT: string;
|
|
29
35
|
/** Typed bridge contract for file-upload failures shown to the chatter. */
|
|
30
36
|
export interface FileUploadErrorData {
|
|
31
37
|
reason?: FileUploadErrorReason;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ada-cx/messaging-bridge",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.3",
|
|
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",
|
|
@@ -1 +0,0 @@
|
|
|
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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"}
|