@ada-cx/messaging-sdk
v1.0.1
Published
TypeScript types, test mocks, and a thin CDN loader for the Ada Messaging SDK. The runtime always resolves from Ada's CDN and is never bundled into this package.
Readme
@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-sdkQuick 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"
}
}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.
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>" },
);Use cdnBase for staging validation only. Production pages must keep the
default.
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.
