@ada-cx/messaging-sdk
TypeScript types, test mocks, and a thin loader for the Ada Messaging SDK.
This package never contains the SDK runtime. The loader imports the runtime
from Ada's CDN at page load. Ada ships runtime fixes to the CDN, and every
page receives them on the next load. This is the same model as
@stripe/stripe-js.
Install
npm install @ada-cx/messaging-sdk
Quick start
Call loadAdaMessaging with your bot handle. It loads the runtime, creates
the messaging interface, starts the widget, and returns the interface.
import { loadAdaMessaging } from "@ada-cx/messaging-sdk";
// Module scope: your code holds the only reference.
const ada = await loadAdaMessaging({ handle: "my-bot" });
loadAdaMessaging never assigns the interface to window or any other
global. Keep the returned reference in module scope, where only your own code
can reach it. A global is reachable by every script on the page. The
createAdaEmbedInterface export exists only as the legacy window.adaEmbed
compat facade for pages migrating from the script-tag setup.
Use loadMessagingSdk when you want the runtime module without starting a
widget, for example to preload it before the widget is needed:
import { loadMessagingSdk } from "@ada-cx/messaging-sdk";
const sdkModule = await loadMessagingSdk();
loadMessagingSdk memoizes the in-flight import. Concurrent calls share one
load. Both loaders reject a failed load with a MessagingSdkLoadError and
permit a retry:
import { MessagingSdkLoadError } from "@ada-cx/messaging-sdk";
try {
await loadAdaMessaging({ handle: "my-bot" });
} catch (error) {
if (error instanceof MessagingSdkLoadError) {
console.error(error.code); // "invalid_cdn_base" | "invalid_build_sha" | "unstamped_build_sha" | "sdk_import_failed" | "sdk_module_invalid"
}
}
If both imports fail, cause holds the default-root typed error and readonly fallbackCause holds the fallback typed error.
Both errors retain their original cause chains.
An invalid fallback module uses the same error properties and preserves sdk_module_invalid and the fallback error message.
One widget per page
Only one messaging interface can run on a page, and loadAdaMessaging
enforces this for you. Repeated calls return the same memoized promise and
resolve to the same started interface. The first call's settings win, and
later calls never start a second widget. When a later call passes different
settings, the loader logs a console warning and ignores the new settings.
The call is therefore safe in code that runs more than once, such as React
Strict Mode development effects. You do not need to cache the promise
yourself.
A failed load or start clears the memo, so a later call can retry.
Control the widget lifecycle through the returned interface. ada.stop()
tears the widget down, and a later ada.start(settings) on the same
interface starts it again. A loadAdaMessaging call after stop() returns
the same stopped interface. It does not restart the widget. The loader logs
a console warning when it returns a stopped interface, so call
start(settings) on that interface to run the widget again.
Module formats
The package ships ESM and CommonJS builds of both entries. Bundlers and
ESM runtimes use the import condition. Jest and other CommonJS runners
use the require condition. TypeScript resolves both entries under
moduleResolution bundler, node16, and the legacy node10 setting.
Types
Import any public type directly. Type imports add zero runtime code.
import type {
AdaMessagingInterface,
AdaMessagingSettings,
AdaMessagingStartOptions,
PublicMessage,
ResetParams,
} from "@ada-cx/messaging-sdk";
AdaMessagingInterface is the interface loadAdaMessaging resolves. The
AdaEmbedInterface name remains available as its legacy alias.
Runtime symbols such as AdaMessagingClient and createAdaEmbedInterface
are exported as types only. Get their implementations from
loadMessagingSdk(). createAdaEmbedInterface exists for migrations from
the legacy window.adaEmbed contract; new integrations use
loadAdaMessaging.
window.adaSettings, new AdaMessagingClient(config), createAdaEmbedInterface(initialSettings), and adaEmbed.start(settings) accept crossWindowPersistence as a boolean or { enabled: boolean }.
The SDK uses the boolean value of enabled, so { enabled: false } disables persistence.
For invalid values, the SDK logs a console warning once per page load.
Invalid overrides preserve the retained host setting or the window.adaSettings fallback.
Invalid constructor values preserve the window.adaSettings fallback.
Without either setting, persistence defaults to on.
The TypeScript setting remains boolean.
Testing your integration
The ./testing entry provides an in-memory mock of the messaging interface.
Your unit tests never touch the CDN.
import { createMockAdaMessaging } from "@ada-cx/messaging-sdk/testing";
const ada = createMockAdaMessaging();
await ada.sendMessage("hello");
expect(ada.calls).toContainEqual({
method: "sendMessage",
args: ["hello"],
});
// Simulate an SDK event for your subscribers.
await ada.subscribeEvent("ada:conversation:message", onMessage);
ada.$emit("ada:conversation:message", { message_id: "m1" });
The mock records every call in calls. It keeps light state: sent messages
appear in getMessages(), and open/close drive isOpen(). Pass
overrides to replace any method with your own spy:
const ada = createMockAdaMessaging({ getMessages: vi.fn(async () => []) });
Bundler note
The loader imports the runtime with a fully dynamic URL. The import is
pre-annotated with /* webpackIgnore: true */ and /* @vite-ignore */, so
webpack and Vite leave it for the browser to resolve. Do not copy the import
into your own code without those annotations. If your bundler still rewrites
dynamic imports, configure it to ignore imports of https: URLs.
Versioning and staging validation
The npm version covers only the loader, the types, and the mocks. It never pins the runtime. The runtime always resolves from Ada's CDN rollout at page load.
To validate a specific runtime build on any page, add the
?ada-messaging-version=<version> query parameter to the page URL. The value
accepts a pr-<N> preview build or a git SHA from a main build. An invalid
or unavailable value falls back to the stable rollout.
To validate against a different asset host, pass its root to the loader. Ada gives you the host value when you arrange a preproduction validation:
await loadAdaMessaging(
{ handle: "my-bot" },
{ cdnBase: "<ada-provided-asset-host>" },
);
The preferred production root is https://messaging-assets.ada.support/.
For production pages whose CSP already allows static.ada.support, use
cdnBase: "https://static.ada.support/messaging/".
The package exports this supported production root as FALLBACK_MESSAGING_CDN_BASE.
Without cdnBase, the loader automatically retries once from the static root
when the default import fails, including CSP and network failures.
If the default bootstrap rejects after manifest or runtime failures, it evicts its shared window.__AdaMessagingSdkBootstrap promise.
The static bootstrap can then resolve independently.
If resolution is pending or successful, bootstrap copies share the promise.
The retry preserves pinBuildSha and its <sha>/sdk/index.js path.
Successful unpinned fallback uses the static root for the manifest and frames.
An explicit cdnBase disables automatic fallback.
Module-invalid and validation errors retain their original codes and messages.
If both imports fail, sdk_import_failed names both attempted URLs.
After a successful fallback, the loader logs one warning per page, without debug logging.
The warning includes the original error as its second console argument.
A TypeError from the default import produces the fetch-failure warning:
[Ada] Loaded the Messaging SDK from https://static.ada.support/messaging/ because the default root https://messaging-assets.ada.support/ was blocked or unavailable. Add https://messaging-assets.ada.support to script-src, connect-src and frame-src, or pass cdnBase: "https://static.ada.support/messaging/".
For a CSP block, update your host's policy as shown in the fetch-failure warning.
Other errors produce the evaluation-failure warning:
[Ada] Loaded the Messaging SDK from https://static.ada.support/messaging/ because the module at https://messaging-assets.ada.support/sdk.js failed to evaluate (Error: boom). This is an Ada-side problem, not your host's CSP. Report it to Ada.
For an evaluation-failure warning, report the original error to Ada.
Network failures also produce the fetch-failure warning.
A module that throws TypeError during evaluation also produces the fetch-failure warning, so the classification does not prove a CSP block.
Successful loads from the requested root produce no fallback warning.
Pin the CDN build
The package exports CDN_BUILD_SHA. The value is the git commit SHA of the
monorepo commit this npm version was published from. Ada deploys each
commit's runtime under an immutable SHA root on the CDN. The value therefore
names the CDN build associated with this npm version.
Pass the SHA as the pinBuildSha loader option. The loader then skips the
rollout bootstrap and imports that build's immutable entry,
<cdnBase>/<sha>/sdk/index.js, directly:
import {
CDN_BUILD_SHA,
isCdnBuildShaStamped,
loadAdaMessaging,
} from "@ada-cx/messaging-sdk";
if (isCdnBuildShaStamped()) {
await loadAdaMessaging({ handle: "my-bot" }, { pinBuildSha: CDN_BUILD_SHA });
}
Pinning is not recommended for production. A pinned runtime misses Ada's fixes and rollouts. A pinned runtime can also predate later server contract changes and stop working. Use a pin only to debug an issue, to validate a staged build, or to reproduce a report against a known runtime.
The option accepts any full 40-character hex git SHA of a deployed main
build. The loader rejects any other value with the invalid_build_sha error
code. Only published packages carry a real SHA. In the repository, and in a
locally built copy, CDN_BUILD_SHA is a 40-zero placeholder and
isCdnBuildShaStamped() returns false. The loader rejects the placeholder
with the unstamped_build_sha error code.
The npm publish and the CDN deploy of the same commit run in parallel. In
the first minutes after a release, a pin can fail with sdk_import_failed
until the deploy completes. If the deploy of that commit failed, the pinned
build never exists. The unpinned default is unaffected in both cases.
pinBuildSha composes with cdnBase. The pinned entry resolves under the
asset host you pass.