@ada-cx/messaging-sdk 1.0.1 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client/client-host-runtime.d.ts +2 -0
- package/dist/client/client.d.ts +13 -0
- package/dist/client/embed-interface.d.ts +18 -0
- package/dist/client/internal-session-mirror.d.ts +44 -0
- package/dist/contract-source-test-helpers.d.ts +21 -0
- package/dist/frame-channel/types.d.ts +18 -0
- package/dist/mobile-webview-runtime.d.ts +32 -4
- package/dist/npm/index.cjs +1 -1
- package/dist/npm/index.cjs.map +1 -1
- package/dist/npm/index.js +1 -1
- package/dist/npm/index.js.map +1 -1
- package/dist/npm/testing.cjs +2 -0
- package/dist/npm/testing.cjs.map +1 -1
- package/dist/npm/testing.js +2 -0
- package/dist/npm/testing.js.map +1 -1
- package/dist/shared-utils/clusters.d.ts +40 -0
- package/package.json +1 -1
|
@@ -32,6 +32,8 @@ export interface DeliverConfigRuntimeOptions {
|
|
|
32
32
|
clearPersistenceStorageModes?: Array<"normal" | "session" | "private">;
|
|
33
33
|
/** Sticky SDK-owned fact matching legacy Embed2 `wasIntroShown`. */
|
|
34
34
|
introShown?: boolean;
|
|
35
|
+
/** Internal (EXP-1082): the mobile webview runtime mirrors session state natively. */
|
|
36
|
+
nativeSessionMirror?: boolean;
|
|
35
37
|
}
|
|
36
38
|
/** Send the client configuration to a frame. */
|
|
37
39
|
export declare function deliverConfigRuntime(channel: FrameChannel, config: DeliverConfigRuntimeOptions): void;
|
package/dist/client/client.d.ts
CHANGED
|
@@ -181,6 +181,8 @@ declare class AdaMessagingClient {
|
|
|
181
181
|
private _introSurfaceSeq;
|
|
182
182
|
private _currentIntroSurface;
|
|
183
183
|
private _internalHostTelemetry?;
|
|
184
|
+
/** Internal (EXP-1082): set only via the webview runtime's armed grant. */
|
|
185
|
+
private _nativeSessionMirror;
|
|
184
186
|
private _onIdentityTokenConsumed?;
|
|
185
187
|
private _isEmbedLoadedPublicationActive?;
|
|
186
188
|
private _zdChatterAuthCallback;
|
|
@@ -589,6 +591,17 @@ declare class AdaMessagingClient {
|
|
|
589
591
|
private _openProactiveCtaUrl;
|
|
590
592
|
/** Send the client configuration to a frame. */
|
|
591
593
|
private _deliverConfig;
|
|
594
|
+
/**
|
|
595
|
+
* Internal (EXP-1082): route a native `ada.sessionMirrorClearAck` to core's
|
|
596
|
+
* pending durable-clear barrier. Called by the mobile webview runtime only.
|
|
597
|
+
*/
|
|
598
|
+
acknowledgeSessionMirrorClear(requestId: string): void;
|
|
599
|
+
/**
|
|
600
|
+
* Internal (EXP-1107): route a native boot-seed reply (`sdk.sessionMirror.seed`)
|
|
601
|
+
* to core's pending `requestSeed()` barrier. Called by the mobile webview
|
|
602
|
+
* runtime only. `seed` is the mirror blob native read live, or null when absent.
|
|
603
|
+
*/
|
|
604
|
+
deliverSessionMirrorSeed(requestId: string, seed: unknown): void;
|
|
592
605
|
/** Safely invoke a window-level callback if it exists and is a function */
|
|
593
606
|
private _invokeWindowCallback;
|
|
594
607
|
/**
|
|
@@ -101,6 +101,24 @@ export interface AdaEmbedInterface {
|
|
|
101
101
|
setTheme: (theme: "light" | "dark" | "auto") => Promise<void>;
|
|
102
102
|
/** Register a push-notification device token for this chatter. */
|
|
103
103
|
setDeviceToken: (token: string) => Promise<void>;
|
|
104
|
+
/**
|
|
105
|
+
* Internal (EXP-1082): route a native session-mirror clear acknowledgement
|
|
106
|
+
* to core's pending durable-clear barrier. Called by the mobile webview
|
|
107
|
+
* runtime only; a no-op signal for any other caller. Optional on the public
|
|
108
|
+
* interface — it carries no host-page meaning, so a structural implementer
|
|
109
|
+
* (test double, wrapper, typed adapter) need not provide it. The concrete
|
|
110
|
+
* client still implements it for the mobile webview bridge.
|
|
111
|
+
*/
|
|
112
|
+
acknowledgeSessionMirrorClear?: (requestId: string) => Promise<void>;
|
|
113
|
+
/**
|
|
114
|
+
* Internal (EXP-1107): route a native boot-seed reply to core's pending
|
|
115
|
+
* `requestSeed()` barrier. Called by the mobile webview runtime only; a no-op
|
|
116
|
+
* signal for any other caller. Optional on the public interface — it carries
|
|
117
|
+
* no host-page meaning, so a structural implementer (test double, wrapper,
|
|
118
|
+
* typed adapter) need not provide it. The concrete client still implements it
|
|
119
|
+
* for the mobile webview bridge.
|
|
120
|
+
*/
|
|
121
|
+
deliverSessionMirrorSeed?: (requestId: string, seed: unknown) => Promise<void>;
|
|
104
122
|
/**
|
|
105
123
|
* Send a message into the conversation as the user. Requires
|
|
106
124
|
* `enableProgrammaticControl`; resolves the accepted message id.
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Internal start-config channel for the native session mirror (EXP-1082).
|
|
3
|
+
*
|
|
4
|
+
* The `ada.sdk.internal.` config namespace is reserved: the client strips any
|
|
5
|
+
* host-supplied key with that prefix from constructor config,
|
|
6
|
+
* `window.adaSettings`, and start()/reset() payloads before merging, so no
|
|
7
|
+
* browser embed can enable the mobile identified-session carve-out. Only
|
|
8
|
+
* `mobile-webview-runtime.ts` can set it — it arms this module immediately
|
|
9
|
+
* before calling `adaEmbed.start()`, and the client consumes the grant during
|
|
10
|
+
* initialization. This module is not exported from the package index.
|
|
11
|
+
*/
|
|
12
|
+
export declare const INTERNAL_CONFIG_KEY_PREFIX = "ada.sdk.internal.";
|
|
13
|
+
export declare const INTERNAL_NATIVE_SESSION_MIRROR_CONFIG_KEY = "ada.sdk.internal.nativeSessionMirror";
|
|
14
|
+
export interface InternalSessionMirrorStartConfig {
|
|
15
|
+
nativeSessionMirror: true;
|
|
16
|
+
}
|
|
17
|
+
/** Called only by the mobile webview runtime, immediately before start(). */
|
|
18
|
+
export declare function armInternalSessionMirrorStartConfig(config: InternalSessionMirrorStartConfig): void;
|
|
19
|
+
/**
|
|
20
|
+
* Drop an armed grant that was never consumed. The webview runtime calls this
|
|
21
|
+
* after start() settles: a failed start (e.g. a duplicate-client claim throwing
|
|
22
|
+
* before initialization) must not leave a stale credential-bearing grant for a
|
|
23
|
+
* later, unrelated client construction to consume. Idempotent — a consumed
|
|
24
|
+
* grant is already gone.
|
|
25
|
+
*/
|
|
26
|
+
export declare function disarmInternalSessionMirrorStartConfig(): void;
|
|
27
|
+
/**
|
|
28
|
+
* One-shot read by the client during initialization. Returns null for every
|
|
29
|
+
* ordinary browser embed. The grant sits on a page-reachable window global, so
|
|
30
|
+
* its mere presence cannot be trusted: a browser page could set it to forge the
|
|
31
|
+
* mobile carve-out. Honour it
|
|
32
|
+
* only in the privileged webview document that arming itself requires — the same
|
|
33
|
+
* `isPrivilegedWebViewLocation` structural signal `isPrivilegedWebViewDocument`
|
|
34
|
+
* gates arming on — so a forged grant on a browser embed reads null. The read
|
|
35
|
+
* always clears the global, so no later script or client construction can
|
|
36
|
+
* observe it even on the unhonoured path.
|
|
37
|
+
*/
|
|
38
|
+
export declare function consumeInternalSessionMirrorStartConfig(): InternalSessionMirrorStartConfig | null;
|
|
39
|
+
/**
|
|
40
|
+
* Remove every reserved `ada.sdk.internal.`-prefixed key from a host-supplied
|
|
41
|
+
* config record. Applied to constructor config (which start() and
|
|
42
|
+
* window.adaSettings merge into) and reset payload ingestion.
|
|
43
|
+
*/
|
|
44
|
+
export declare function stripInternalConfigKeys<T extends object>(record: T): T;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Removes `//` and (nesting) block comments while preserving string, char and
|
|
3
|
+
* template literals, so a pin can never be satisfied by prose.
|
|
4
|
+
*/
|
|
5
|
+
export declare function stripComments(source: string): string;
|
|
6
|
+
export declare function compact(source: string): string;
|
|
7
|
+
export declare function code(source: string): string;
|
|
8
|
+
/** One `?raw`-pinned source, with a code anchor and a prose-only anchor. */
|
|
9
|
+
export interface PinnedRawSource {
|
|
10
|
+
name: string;
|
|
11
|
+
raw: string;
|
|
12
|
+
stripped: string;
|
|
13
|
+
codeAnchor: string;
|
|
14
|
+
proseAnchor: string;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* The stripper self-test every `?raw` pinning suite runs over its own sources.
|
|
18
|
+
* Call it first inside the suite so a broken stripper reports itself rather
|
|
19
|
+
* than turning the pins below it into no-ops.
|
|
20
|
+
*/
|
|
21
|
+
export declare function describeCommentStripping(sources: readonly PinnedRawSource[]): void;
|
|
@@ -177,6 +177,8 @@ export interface SdkToFrameEvents {
|
|
|
177
177
|
* page can measure this; a desktop widget frame is capped at 375px.
|
|
178
178
|
*/
|
|
179
179
|
isMobileViewport?: boolean;
|
|
180
|
+
/** Internal (EXP-1082): the mobile webview runtime mirrors session state natively. */
|
|
181
|
+
nativeSessionMirror?: boolean;
|
|
180
182
|
/** One-shot bootstrap flag to clear persisted chatter state before prepare(). */
|
|
181
183
|
clearPersistenceOnPrepare?: boolean;
|
|
182
184
|
/** Correlate a reset/deleteHistory clear across a replacement core frame. */
|
|
@@ -358,6 +360,22 @@ export interface SdkToFrameEvents {
|
|
|
358
360
|
"sdk.history.delete": {
|
|
359
361
|
requestId?: string;
|
|
360
362
|
} | undefined;
|
|
363
|
+
/**
|
|
364
|
+
* Native acknowledged a durable session-mirror delete; releases core's
|
|
365
|
+
* pending clear barrier (EXP-1082).
|
|
366
|
+
*/
|
|
367
|
+
"sdk.sessionMirror.clearAck": {
|
|
368
|
+
requestId: string;
|
|
369
|
+
};
|
|
370
|
+
/**
|
|
371
|
+
* Native answered the boot-time seed pull (EXP-1107); resolves core's pending
|
|
372
|
+
* `requestSeed()`. `seed` is the mirror blob native read live, or null/omitted
|
|
373
|
+
* when absent (a clear ordered before the read yields no seed).
|
|
374
|
+
*/
|
|
375
|
+
"sdk.sessionMirror.seed": {
|
|
376
|
+
requestId: string;
|
|
377
|
+
seed?: unknown;
|
|
378
|
+
};
|
|
361
379
|
/** Register a device push notification token (SDK → core) */
|
|
362
380
|
"sdk.notifications.deviceToken.set": {
|
|
363
381
|
token: string;
|
|
@@ -52,6 +52,24 @@ export interface AdaInjectedWebViewConfig {
|
|
|
52
52
|
identityToken?: string;
|
|
53
53
|
/** Custom app frame URL, merged into `adaEmbed.start()` as `appUrl`. The app origin must be in the bot's Allowed Websites list. */
|
|
54
54
|
appUrl?: string;
|
|
55
|
+
/**
|
|
56
|
+
* Native session-mirror capability handshake (EXP-1082). Set `true` only by a
|
|
57
|
+
* native version that implements the `sdk.session.mirror` /
|
|
58
|
+
* `sdk.session.mirrorClear` handlers AND emits `ada.sessionMirrorClearAck`.
|
|
59
|
+
* The web runtime is CDN-only and unversioned, so it loads inside every
|
|
60
|
+
* already-shipped native wrapper — including old versions with no mirror
|
|
61
|
+
* handlers. Without this native-vouched signal the mirror and its durable
|
|
62
|
+
* clear ack-barrier stay disarmed, so a reset/deleteHistory on an old native
|
|
63
|
+
* never waits on an ack that host can never send. Readers treat absent or
|
|
64
|
+
* `!== true` as unsupported.
|
|
65
|
+
*/
|
|
66
|
+
nativeSessionMirrorSupported?: boolean;
|
|
67
|
+
/**
|
|
68
|
+
* Reserved companion to {@link nativeSessionMirrorSupported} for future
|
|
69
|
+
* protocol evolution. Not read today; present so a later version can advertise
|
|
70
|
+
* a higher mirror protocol without a second capability field.
|
|
71
|
+
*/
|
|
72
|
+
sessionMirrorProtocolVersion?: number;
|
|
55
73
|
}
|
|
56
74
|
/**
|
|
57
75
|
* sessionStorage key recording that an injected `identityToken` was consumed
|
|
@@ -67,6 +85,8 @@ export interface AdaInjectedWebViewConfig {
|
|
|
67
85
|
*/
|
|
68
86
|
export declare const IDENTITY_TOKEN_CONSUMED_STORAGE_KEY = "__ada_identity_token_consumed__";
|
|
69
87
|
type AdaWebViewRuntimeAssetConfig = Pick<AdaWebViewRuntimeConfig, "cluster" | "greeting" | "handle" | "hostTelemetry" | "language" | "metaFields" | "webSdk">;
|
|
88
|
+
/** The `sdk/webview.html` document a native wrapper loaded for its environment. */
|
|
89
|
+
export type WebViewDocumentLocation = Pick<Location, "hostname" | "origin" | "pathname" | "protocol">;
|
|
70
90
|
export interface ResolvedAdaWebViewRuntimeAssets {
|
|
71
91
|
webSdk: AdaWebSdkMode;
|
|
72
92
|
scriptUrl?: string;
|
|
@@ -83,6 +103,8 @@ interface LegacyAdaEmbed {
|
|
|
83
103
|
setSensitiveMetaFields?: (fields: Record<string, unknown>) => unknown;
|
|
84
104
|
setDeviceToken?: (token: string) => unknown;
|
|
85
105
|
deleteHistory?: () => unknown;
|
|
106
|
+
acknowledgeSessionMirrorClear?: (requestId: string) => unknown;
|
|
107
|
+
deliverSessionMirrorSeed?: (requestId: string, seed: unknown) => unknown;
|
|
86
108
|
subscribeAll?: (callback: (key: string, payload: unknown) => void) => (() => void) | undefined;
|
|
87
109
|
subscribeEvent?: (key: string, callback: (payload: unknown) => void) => number | Promise<number>;
|
|
88
110
|
}
|
|
@@ -97,11 +119,17 @@ declare global {
|
|
|
97
119
|
}
|
|
98
120
|
/**
|
|
99
121
|
* Resolve which web runtime assets a native wrapper should load for the given
|
|
100
|
-
* webview config: the Messaging SDK bundle URL
|
|
101
|
-
*
|
|
102
|
-
*
|
|
122
|
+
* webview config: the Messaging SDK bundle URL, the static embed2 script for
|
|
123
|
+
* non-local legacy, or the local Chat iframe assets for localhost legacy
|
|
124
|
+
* development.
|
|
125
|
+
*
|
|
126
|
+
* The Messaging bundle is served from the origin of the `sdk/webview.html`
|
|
127
|
+
* document the wrapper loaded, which the wrapper chose from the host-supplied
|
|
128
|
+
* environment — pass `documentLocation` when resolving from outside that
|
|
129
|
+
* document; it defaults to the executing one. The cluster decides only when
|
|
130
|
+
* there is no such document to read.
|
|
103
131
|
*/
|
|
104
|
-
export declare function resolveAdaWebViewRuntimeAssets(config: AdaWebViewRuntimeAssetConfig): ResolvedAdaWebViewRuntimeAssets;
|
|
132
|
+
export declare function resolveAdaWebViewRuntimeAssets(config: AdaWebViewRuntimeAssetConfig, documentLocation?: WebViewDocumentLocation | undefined): ResolvedAdaWebViewRuntimeAssets;
|
|
105
133
|
/**
|
|
106
134
|
* Mount the Ada web runtime inside a native mobile webview (iOS, Android,
|
|
107
135
|
* React Native). Loads the Messaging SDK (or the legacy embed2/Chat runtime),
|
package/dist/npm/index.cjs
CHANGED
|
@@ -13,7 +13,7 @@ var UNSTAMPED_SHA_PATTERN = /^0{40}$/;
|
|
|
13
13
|
* unstamped 40-zero placeholder ({@link isCdnBuildShaStamped} returns
|
|
14
14
|
* `false`); only published tarballs carry a real SHA.
|
|
15
15
|
*/
|
|
16
|
-
var CDN_BUILD_SHA = "
|
|
16
|
+
var CDN_BUILD_SHA = "29ecde8f0b3086ec3181c66e929f2416a8fd881d";
|
|
17
17
|
/**
|
|
18
18
|
* Whether `sha` (default {@link CDN_BUILD_SHA}) is a stamped full git commit
|
|
19
19
|
* SHA rather than the unstamped repo placeholder.
|
package/dist/npm/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","names":[],"sources":["../../src/npm/build-info.ts","../../src/client/embed-lifecycle.ts","../../src/npm/loader.ts"],"sourcesContent":["// The repo copy carries the 40-zero placeholder; the publish workflow\n// (.github/workflows/messaging-publish-web-packages.yml) stamps the real\n// commit SHA into the built output via scripts/stamp-cdn-build-sha.mjs and\n// verifies the packed tarball before `npm publish`. Keep the placeholder as\n// a single 40-zero string literal: the stamp is a same-length text\n// replacement, so source maps stay valid.\nconst GIT_SHA_PATTERN = /^[0-9a-f]{40}$/;\nconst UNSTAMPED_SHA_PATTERN = /^0{40}$/;\n\n/**\n * Git commit SHA of the monorepo commit this npm version was published from.\n * The CDN pipeline deploys each main commit's runtime under an immutable\n * SHA root, so this value names the CDN build associated with this npm\n * version. Pass it as the `pinBuildSha` loader option to load exactly that\n * runtime build — not recommended for production (see the README).\n *\n * In the repository, and in any locally built copy, the value is the\n * unstamped 40-zero placeholder ({@link isCdnBuildShaStamped} returns\n * `false`); only published tarballs carry a real SHA.\n */\nexport const CDN_BUILD_SHA: string = \"811ee8fe6daeb693bc1b95e3a41ccb82a9d763eb\";\n\n/**\n * Whether `sha` (default {@link CDN_BUILD_SHA}) is a stamped full git commit\n * SHA rather than the unstamped repo placeholder.\n */\nexport function isCdnBuildShaStamped(sha: string = CDN_BUILD_SHA): boolean {\n\tconst normalized = sha.trim().toLowerCase();\n\treturn (\n\t\tGIT_SHA_PATTERN.test(normalized) && !UNSTAMPED_SHA_PATTERN.test(normalized)\n\t);\n}\n","/**\n * Lifecycle channel between `createAdaEmbedInterface` and the npm loader's\n * memo: a non-enumerable getter over closure state, so the loader never\n * mutates the public surface and `Object.freeze` on the facade still works.\n * `Symbol.for` because the loader and the CDN runtime are separate builds;\n * a runtime that predates the getter yields `undefined` — degrade gracefully.\n */\nexport const EMBED_LIFECYCLE_STOPPED = Symbol.for(\n\t\"ada-cx/messaging-sdk/embed-stopped\",\n);\n\n/** Read the facade's stopped flag; `undefined` when the runtime predates it. */\nexport function readEmbedStoppedFlag(ada: object): boolean | undefined {\n\tconst value = (ada as Record<symbol, unknown>)[EMBED_LIFECYCLE_STOPPED];\n\treturn typeof value === \"boolean\" ? value : undefined;\n}\n\n/** Define the getter at facade creation time, before any host freeze. */\nexport function defineEmbedStoppedFlag(\n\tada: object,\n\treadStopped: () => boolean,\n): void {\n\tObject.defineProperty(ada, EMBED_LIFECYCLE_STOPPED, {\n\t\tget: readStopped,\n\t\tenumerable: false,\n\t\tconfigurable: false,\n\t});\n}\n","import type { AdaMessagingClient } from \"../client/client\";\nimport type { AdaMessagingStartOptions } from \"../client/client-types\";\nimport type {\n\tAdaEmbedInterface,\n\tcreateAdaEmbedInterface,\n\tdispatchEmbedLoaderInitialActionQueue,\n\tdispatchEmbedLoaderInitialActionQueueForAtomicHandoff,\n} from \"../client/embed-interface\";\nimport { readEmbedStoppedFlag } from \"../client/embed-lifecycle\";\nimport type {\n\tmountAdaWebViewRuntime,\n\tresolveAdaWebViewRuntimeAssets,\n} from \"../mobile-webview-runtime\";\n\n/**\n * Production CDN root the loader resolves the SDK bootstrap from when no\n * `cdnBase` override is given. The npm package never pins a runtime version;\n * the bootstrap at this root resolves the current rollout at load time.\n */\nexport const DEFAULT_MESSAGING_CDN_BASE =\n\t\"https://messaging-assets.ada.support/\";\n\nconst SDK_BOOTSTRAP_FILENAME = \"sdk.js\";\n\n// The CDN pipeline deploys each main commit's runtime as an immutable\n// SHA-rooted entry (`<cdnBase>/<sha>/sdk/index.js`); the mutable `sdk.js`\n// bootstrap resolves the current rollout to one of those roots at load time.\n// A pin bypasses the bootstrap and imports one root directly.\nconst PINNED_SDK_ENTRY_PATH = \"sdk/index.js\";\n\n// Strict on purpose: the pin is interpolated into a CDN path, so only a full\n// lowercase 40-hex git SHA may pass (mirrors scripts/sdk.js's\n// VERSION_OVERRIDE_PATTERN security boundary, minus the short-SHA and pr-N\n// forms — the npm surface pins exactly what CDN_BUILD_SHA carries).\nconst PINNED_BUILD_SHA_PATTERN = /^[0-9a-f]{40}$/;\nconst UNSTAMPED_BUILD_SHA_PATTERN = /^0{40}$/;\n\n/** Why a {@link MessagingSdkLoadError} was raised. */\nexport type MessagingSdkLoadErrorCode =\n\t/**\n\t * The `cdnBase` option is not a valid `https:` URL (plain `http:` is\n\t * allowed only for loopback hosts).\n\t */\n\t| \"invalid_cdn_base\"\n\t/** The `pinBuildSha` option is not a full 40-character hex git SHA. */\n\t| \"invalid_build_sha\"\n\t/**\n\t * The `pinBuildSha` option is the unstamped 40-zero placeholder: this\n\t * copy of the package was built from the repository, not published, so\n\t * its `CDN_BUILD_SHA` names no CDN build.\n\t */\n\t| \"unstamped_build_sha\"\n\t/** The dynamic import of the CDN bootstrap failed (network, CSP, 404). */\n\t| \"sdk_import_failed\"\n\t/** The imported module does not expose the Ada Messaging SDK surface. */\n\t| \"sdk_module_invalid\";\n\n/** Typed failure raised by {@link loadMessagingSdk} and {@link loadAdaMessaging}. */\nexport class MessagingSdkLoadError extends Error {\n\treadonly code: MessagingSdkLoadErrorCode;\n\n\tconstructor(\n\t\tmessage: string,\n\t\tcode: MessagingSdkLoadErrorCode,\n\t\tcause?: unknown,\n\t) {\n\t\tsuper(message);\n\t\tthis.name = \"MessagingSdkLoadError\";\n\t\tthis.code = code;\n\t\tif (cause !== undefined) {\n\t\t\tthis.cause = cause;\n\t\t}\n\t}\n}\n\n/**\n * The programmatic messaging interface {@link loadAdaMessaging} resolves. An\n * alias of the runtime's `AdaEmbedInterface`, which keeps that name because it\n * is also the legacy `window.adaEmbed` compat facade.\n */\nexport type AdaMessagingInterface = AdaEmbedInterface;\n\n/**\n * The public surface of the CDN-resolved SDK module, as loaded from\n * `<cdnBase>/sdk.js`. Types only — the implementation always comes from the\n * CDN at load time and is never part of this npm package.\n */\nexport interface MessagingSdkModule {\n\tAdaMessagingClient: typeof AdaMessagingClient;\n\tcreateAdaEmbedInterface: typeof createAdaEmbedInterface;\n\tdispatchEmbedLoaderInitialActionQueue: typeof dispatchEmbedLoaderInitialActionQueue;\n\tdispatchEmbedLoaderInitialActionQueueForAtomicHandoff: typeof dispatchEmbedLoaderInitialActionQueueForAtomicHandoff;\n\tmountAdaWebViewRuntime: typeof mountAdaWebViewRuntime;\n\tresolveAdaWebViewRuntimeAssets: typeof resolveAdaWebViewRuntimeAssets;\n}\n\ntype ImportSdkModule = (url: string) => Promise<unknown>;\n\nexport interface LoadMessagingSdkOptions {\n\t/**\n\t * CDN root to resolve `sdk.js` from. Must be an `https:` URL; plain\n\t * `http:` is accepted only for loopback hosts (`localhost`, `127.0.0.1`,\n\t * `[::1]`, `*.localhost`) during local development. Defaults to the\n\t * production root ({@link DEFAULT_MESSAGING_CDN_BASE}). Override it with\n\t * the Ada-provided asset host for a staging validation window only;\n\t * production pages must keep the default.\n\t */\n\tcdnBase?: string;\n\t/**\n\t * Full 40-character git commit SHA of a deployed CDN build. When set, the\n\t * loader skips the rollout bootstrap and imports that build's immutable\n\t * entry (`<cdnBase>/<sha>/sdk/index.js`) directly. Pass the package's\n\t * `CDN_BUILD_SHA` export to pin the build associated with this npm\n\t * version. NOT recommended for production: a pinned runtime misses Ada's\n\t * fixes and rollouts, and may predate server contract changes. Composes\n\t * with `cdnBase` for staged validation.\n\t */\n\tpinBuildSha?: string;\n\t/**\n\t * Import seam for unit tests. Defaults to a native dynamic `import()` of\n\t * the resolved bootstrap URL.\n\t */\n\timportModule?: ImportSdkModule;\n}\n\n// Both annotations are load-bearing: bundlers rewrite bare dynamic imports,\n// and this specifier must reach the browser untouched so the runtime always\n// resolves from Ada's CDN. The npm build for this entry is unminified so the\n// annotations survive into the published output.\nconst defaultImportModule: ImportSdkModule = (url) =>\n\timport(/* webpackIgnore: true */ /* @vite-ignore */ url);\n\n// Keyed by import seam so production memoizes on the module-level default\n// while each injected test seam gets an isolated cache.\nconst inFlightByImporter = new WeakMap<\n\tImportSdkModule,\n\tMap<string, Promise<MessagingSdkModule>>\n>();\n\n// Mirrors the loopback semantics of the shared `isLoopbackHost` /\n// `validateMessagingEndpoint` (shared/utils/src/url.ts) and the bridge\n// loader's copy (packages/bridge/src/npm/loader.ts). Inlined because the\n// published loader must stay dependency-free; keep the host set in sync.\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\t// `*.localhost` resolves to the loopback interface and is \"potentially\n\t// trustworthy\" per the Secure Contexts spec; no production host ends in\n\t// `.localhost`.\n\treturn LOOPBACK_HOSTS.has(normalized) || normalized.endsWith(\".localhost\");\n}\n\nfunction resolvePinnedBuildSha(pinBuildSha: string): string {\n\tconst sha = pinBuildSha.trim().toLowerCase();\n\tif (!PINNED_BUILD_SHA_PATTERN.test(sha)) {\n\t\tthrow new MessagingSdkLoadError(\n\t\t\t`The pinBuildSha option must be a full 40-character hex git SHA: \"${pinBuildSha}\".`,\n\t\t\t\"invalid_build_sha\",\n\t\t);\n\t}\n\tif (UNSTAMPED_BUILD_SHA_PATTERN.test(sha)) {\n\t\tthrow new MessagingSdkLoadError(\n\t\t\t\"The pinBuildSha option is the unstamped CDN_BUILD_SHA placeholder. Only the published npm package carries a stamped SHA; a repository or local build cannot pin a CDN build.\",\n\t\t\t\"unstamped_build_sha\",\n\t\t);\n\t}\n\treturn sha;\n}\n\n/**\n * The runtime this URL serves carries the SDK's security logic (endpoint\n * validation, session handling, the origin checks), so a cleartext transport\n * would let an on-path attacker replace it wholesale: `https:` is required,\n * with a loopback-only `http:` exception for local development.\n */\nfunction resolveSdkBootstrapUrl(\n\tcdnBase: string | undefined,\n\tpinBuildSha: string | undefined,\n): string {\n\tconst base = (cdnBase ?? DEFAULT_MESSAGING_CDN_BASE).trim();\n\t// Resolving against a base without a trailing slash would drop its last\n\t// path segment, so normalize before building the bootstrap URL.\n\tconst normalizedBase = base.endsWith(\"/\") ? base : `${base}/`;\n\tconst entryPath =\n\t\tpinBuildSha === undefined\n\t\t\t? SDK_BOOTSTRAP_FILENAME\n\t\t\t: `${resolvePinnedBuildSha(pinBuildSha)}/${PINNED_SDK_ENTRY_PATH}`;\n\tlet url: URL;\n\ttry {\n\t\turl = new URL(entryPath, normalizedBase);\n\t} catch (cause) {\n\t\tthrow new MessagingSdkLoadError(\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 MessagingSdkLoadError(\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 url.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 sdk_module_invalid error, not later as a\n// TypeError on the first missing call — so every export is checked. The\n// `satisfies` clause rejects names that are not interface members; the\n// exhaustiveness type below is what forces this list to grow with it.\nconst REQUIRED_SDK_EXPORTS = [\n\t\"AdaMessagingClient\",\n\t\"createAdaEmbedInterface\",\n\t\"dispatchEmbedLoaderInitialActionQueue\",\n\t\"dispatchEmbedLoaderInitialActionQueueForAtomicHandoff\",\n\t\"mountAdaWebViewRuntime\",\n\t\"resolveAdaWebViewRuntimeAssets\",\n] as const satisfies readonly (keyof MessagingSdkModule)[];\n\n// Compile-time exhaustiveness: adding a MessagingSdkModule member without\n// listing it in REQUIRED_SDK_EXPORTS turns this type into `never`, failing\n// the `true` assignment below.\ntype AllSdkExportsListed =\n\tExclude<\n\t\tkeyof MessagingSdkModule,\n\t\t(typeof REQUIRED_SDK_EXPORTS)[number]\n\t> extends never\n\t\t? true\n\t\t: never;\nconst _allSdkExportsListed: AllSdkExportsListed = true;\nvoid _allSdkExportsListed;\n\nfunction isMessagingSdkModule(value: unknown): value is MessagingSdkModule {\n\tif (typeof value !== \"object\" || value === null) {\n\t\treturn false;\n\t}\n\tconst candidate = value as Record<string, unknown>;\n\treturn REQUIRED_SDK_EXPORTS.every(\n\t\t(key) => typeof candidate[key] === \"function\",\n\t);\n}\n\nasync function importSdkModule(\n\turl: string,\n\timportModule: ImportSdkModule,\n): Promise<MessagingSdkModule> {\n\tlet loaded: unknown;\n\ttry {\n\t\tloaded = await importModule(url);\n\t} catch (cause) {\n\t\tthrow new MessagingSdkLoadError(\n\t\t\t`The Ada Messaging SDK failed to load from ${url}.`,\n\t\t\t\"sdk_import_failed\",\n\t\t\tcause,\n\t\t);\n\t}\n\tif (!isMessagingSdkModule(loaded)) {\n\t\tthrow new MessagingSdkLoadError(\n\t\t\t`The module at ${url} does not expose the Ada Messaging SDK interface.`,\n\t\t\t\"sdk_module_invalid\",\n\t\t);\n\t}\n\treturn loaded;\n}\n\n/**\n * Load the Ada Messaging SDK module from Ada's CDN.\n *\n * The returned promise is memoized per resolved URL: concurrent and repeated\n * calls share one in-flight import. A failed load clears the memo so a later\n * call retries. Rejections are always {@link MessagingSdkLoadError}.\n *\n * @example\n * const { createAdaEmbedInterface } = await loadMessagingSdk();\n */\nexport function loadMessagingSdk(\n\toptions: LoadMessagingSdkOptions = {},\n): Promise<MessagingSdkModule> {\n\tconst importModule = options.importModule ?? defaultImportModule;\n\tlet url: string;\n\ttry {\n\t\turl = resolveSdkBootstrapUrl(options.cdnBase, options.pinBuildSha);\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 = importSdkModule(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\n// Keyed by import seam like the module memo above: production memoizes on\n// the module-level default while each injected test seam gets an isolated\n// cache. `ada` is set when the promise resolves, so the stopped-interface\n// diagnostic can consult the resolved facade without wrapping it.\ninterface StartedInterfaceMemo {\n\tpromise: Promise<AdaMessagingInterface>;\n\tsettings: AdaMessagingStartOptions;\n\tada?: AdaMessagingInterface;\n}\n\nconst startedInterfaceByImporter = new WeakMap<\n\tImportSdkModule,\n\tStartedInterfaceMemo\n>();\n\n// Ungated like the runtime logger's dropped-host-value channel: both call\n// sites fire only on host misuse, never on an ordinary page load.\nfunction warnHost(message: string): void {\n\ttry {\n\t\t// biome-ignore lint/suspicious/noConsole: the loader's sanctioned host-page diagnostic sink\n\t\tconsole.warn(`[Ada] ${message}`);\n\t} catch {\n\t\t// A host may replace console methods; diagnostics must not throw.\n\t}\n}\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n\tif (typeof value !== \"object\" || value === null) {\n\t\treturn false;\n\t}\n\tconst prototype = Object.getPrototypeOf(value);\n\treturn prototype === Object.prototype || prototype === null;\n}\n\n// Structural equality over the settings surface. Two functions compare equal:\n// fresh inline callbacks on a re-run (React Strict Mode effects) are not a\n// settings change worth a warning. Exotic objects compare by reference.\nfunction settingsEqual(a: unknown, b: unknown): boolean {\n\tif (Object.is(a, b)) {\n\t\treturn true;\n\t}\n\tif (typeof a === \"function\" && typeof b === \"function\") {\n\t\treturn true;\n\t}\n\tif (Array.isArray(a) && Array.isArray(b)) {\n\t\treturn (\n\t\t\ta.length === b.length &&\n\t\t\ta.every((item, index) => settingsEqual(item, b[index]))\n\t\t);\n\t}\n\tif (isPlainObject(a) && isPlainObject(b)) {\n\t\tconst aKeys = Object.keys(a);\n\t\tconst bKeys = new Set(Object.keys(b));\n\t\treturn (\n\t\t\taKeys.length === bKeys.size &&\n\t\t\taKeys.every((key) => bKeys.has(key) && settingsEqual(a[key], b[key]))\n\t\t);\n\t}\n\treturn false;\n}\n\n// Observes the facade's symbol-keyed lifecycle getter (embed-lifecycle.ts)\n// instead of wrapping stop/start, so the customer's interface is never\n// mutated; a pinned runtime that predates the getter yields no flag and the\n// stopped-interface diagnostic is skipped rather than guessed.\nfunction isInterfaceStopped(ada: AdaMessagingInterface): boolean {\n\treturn readEmbedStoppedFlag(ada) === true;\n}\n\nasync function startAdaMessaging(\n\tsettings: AdaMessagingStartOptions,\n\toptions: LoadMessagingSdkOptions,\n): Promise<AdaMessagingInterface> {\n\tconst sdkModule = await loadMessagingSdk(options);\n\tconst ada = sdkModule.createAdaEmbedInterface();\n\tawait ada.start(settings);\n\treturn ada;\n}\n\n/**\n * Load the SDK, create the messaging interface, and start the widget —\n * the npm-first equivalent of the script-tag + `window.adaSettings` setup.\n *\n * One widget per page: the returned promise is memoized, so repeated and\n * concurrent calls resolve to the same started interface and the first\n * call's settings win. A failed load or start clears the memo so a later\n * call retries. Manage the running widget through the returned interface:\n * `stop()` tears it down and a later `start(settings)` on the same interface\n * runs again. Calling {@link loadAdaMessaging} after `stop()` returns the\n * same stopped interface without restarting it. Both memo outcomes log a\n * console warning: ignored differing settings, and a resolved interface\n * whose widget was stopped.\n *\n * Never assigns the interface to `window` or any other global: the returned\n * reference is the only handle, so hold it in module scope. A reference in\n * module scope is reachable only by your own code, never by other scripts on\n * the page.\n *\n * @example\n * // module scope: your code keeps the only reference\n * const ada = await loadAdaMessaging({\n * handle: \"my-bot\",\n * enableProgrammaticControl: true,\n * });\n * await ada.sendMessage(\"Hello\");\n */\nexport function loadAdaMessaging(\n\tsettings: AdaMessagingStartOptions,\n\toptions: LoadMessagingSdkOptions = {},\n): Promise<AdaMessagingInterface> {\n\tconst importModule = options.importModule ?? defaultImportModule;\n\tconst existing = startedInterfaceByImporter.get(importModule);\n\tif (existing) {\n\t\tif (!settingsEqual(settings, existing.settings)) {\n\t\t\twarnHost(\n\t\t\t\t\"loadAdaMessaging() was already called: the new settings are ignored and the existing interface is returned. To apply new settings, call stop() then start(settings) on it.\",\n\t\t\t);\n\t\t}\n\t\tif (existing.ada && isInterfaceStopped(existing.ada)) {\n\t\t\twarnHost(\n\t\t\t\t\"loadAdaMessaging() resolved an interface whose widget was stopped and never restarts it. Call start(settings) on it to run the widget again.\",\n\t\t\t);\n\t\t}\n\t\treturn existing.promise;\n\t}\n\tconst memo: StartedInterfaceMemo = {\n\t\tpromise: startAdaMessaging(settings, options),\n\t\tsettings,\n\t};\n\tstartedInterfaceByImporter.set(importModule, memo);\n\tmemo.promise.then(\n\t\t(ada) => {\n\t\t\tmemo.ada = ada;\n\t\t},\n\t\t() => {\n\t\t\tif (startedInterfaceByImporter.get(importModule) === memo) {\n\t\t\t\tstartedInterfaceByImporter.delete(importModule);\n\t\t\t}\n\t\t},\n\t);\n\treturn memo.promise;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.cjs","names":[],"sources":["../../src/npm/build-info.ts","../../src/client/embed-lifecycle.ts","../../src/npm/loader.ts"],"sourcesContent":["// The repo copy carries the 40-zero placeholder; the publish workflow\n// (.github/workflows/messaging-publish-web-packages.yml) stamps the real\n// commit SHA into the built output via scripts/stamp-cdn-build-sha.mjs and\n// verifies the packed tarball before `npm publish`. Keep the placeholder as\n// a single 40-zero string literal: the stamp is a same-length text\n// replacement, so source maps stay valid.\nconst GIT_SHA_PATTERN = /^[0-9a-f]{40}$/;\nconst UNSTAMPED_SHA_PATTERN = /^0{40}$/;\n\n/**\n * Git commit SHA of the monorepo commit this npm version was published from.\n * The CDN pipeline deploys each main commit's runtime under an immutable\n * SHA root, so this value names the CDN build associated with this npm\n * version. Pass it as the `pinBuildSha` loader option to load exactly that\n * runtime build — not recommended for production (see the README).\n *\n * In the repository, and in any locally built copy, the value is the\n * unstamped 40-zero placeholder ({@link isCdnBuildShaStamped} returns\n * `false`); only published tarballs carry a real SHA.\n */\nexport const CDN_BUILD_SHA: string = \"29ecde8f0b3086ec3181c66e929f2416a8fd881d\";\n\n/**\n * Whether `sha` (default {@link CDN_BUILD_SHA}) is a stamped full git commit\n * SHA rather than the unstamped repo placeholder.\n */\nexport function isCdnBuildShaStamped(sha: string = CDN_BUILD_SHA): boolean {\n\tconst normalized = sha.trim().toLowerCase();\n\treturn (\n\t\tGIT_SHA_PATTERN.test(normalized) && !UNSTAMPED_SHA_PATTERN.test(normalized)\n\t);\n}\n","/**\n * Lifecycle channel between `createAdaEmbedInterface` and the npm loader's\n * memo: a non-enumerable getter over closure state, so the loader never\n * mutates the public surface and `Object.freeze` on the facade still works.\n * `Symbol.for` because the loader and the CDN runtime are separate builds;\n * a runtime that predates the getter yields `undefined` — degrade gracefully.\n */\nexport const EMBED_LIFECYCLE_STOPPED = Symbol.for(\n\t\"ada-cx/messaging-sdk/embed-stopped\",\n);\n\n/** Read the facade's stopped flag; `undefined` when the runtime predates it. */\nexport function readEmbedStoppedFlag(ada: object): boolean | undefined {\n\tconst value = (ada as Record<symbol, unknown>)[EMBED_LIFECYCLE_STOPPED];\n\treturn typeof value === \"boolean\" ? value : undefined;\n}\n\n/** Define the getter at facade creation time, before any host freeze. */\nexport function defineEmbedStoppedFlag(\n\tada: object,\n\treadStopped: () => boolean,\n): void {\n\tObject.defineProperty(ada, EMBED_LIFECYCLE_STOPPED, {\n\t\tget: readStopped,\n\t\tenumerable: false,\n\t\tconfigurable: false,\n\t});\n}\n","import type { AdaMessagingClient } from \"../client/client\";\nimport type { AdaMessagingStartOptions } from \"../client/client-types\";\nimport type {\n\tAdaEmbedInterface,\n\tcreateAdaEmbedInterface,\n\tdispatchEmbedLoaderInitialActionQueue,\n\tdispatchEmbedLoaderInitialActionQueueForAtomicHandoff,\n} from \"../client/embed-interface\";\nimport { readEmbedStoppedFlag } from \"../client/embed-lifecycle\";\nimport type {\n\tmountAdaWebViewRuntime,\n\tresolveAdaWebViewRuntimeAssets,\n} from \"../mobile-webview-runtime\";\n\n/**\n * Production CDN root the loader resolves the SDK bootstrap from when no\n * `cdnBase` override is given. The npm package never pins a runtime version;\n * the bootstrap at this root resolves the current rollout at load time.\n */\nexport const DEFAULT_MESSAGING_CDN_BASE =\n\t\"https://messaging-assets.ada.support/\";\n\nconst SDK_BOOTSTRAP_FILENAME = \"sdk.js\";\n\n// The CDN pipeline deploys each main commit's runtime as an immutable\n// SHA-rooted entry (`<cdnBase>/<sha>/sdk/index.js`); the mutable `sdk.js`\n// bootstrap resolves the current rollout to one of those roots at load time.\n// A pin bypasses the bootstrap and imports one root directly.\nconst PINNED_SDK_ENTRY_PATH = \"sdk/index.js\";\n\n// Strict on purpose: the pin is interpolated into a CDN path, so only a full\n// lowercase 40-hex git SHA may pass (mirrors scripts/sdk.js's\n// VERSION_OVERRIDE_PATTERN security boundary, minus the short-SHA and pr-N\n// forms — the npm surface pins exactly what CDN_BUILD_SHA carries).\nconst PINNED_BUILD_SHA_PATTERN = /^[0-9a-f]{40}$/;\nconst UNSTAMPED_BUILD_SHA_PATTERN = /^0{40}$/;\n\n/** Why a {@link MessagingSdkLoadError} was raised. */\nexport type MessagingSdkLoadErrorCode =\n\t/**\n\t * The `cdnBase` option is not a valid `https:` URL (plain `http:` is\n\t * allowed only for loopback hosts).\n\t */\n\t| \"invalid_cdn_base\"\n\t/** The `pinBuildSha` option is not a full 40-character hex git SHA. */\n\t| \"invalid_build_sha\"\n\t/**\n\t * The `pinBuildSha` option is the unstamped 40-zero placeholder: this\n\t * copy of the package was built from the repository, not published, so\n\t * its `CDN_BUILD_SHA` names no CDN build.\n\t */\n\t| \"unstamped_build_sha\"\n\t/** The dynamic import of the CDN bootstrap failed (network, CSP, 404). */\n\t| \"sdk_import_failed\"\n\t/** The imported module does not expose the Ada Messaging SDK surface. */\n\t| \"sdk_module_invalid\";\n\n/** Typed failure raised by {@link loadMessagingSdk} and {@link loadAdaMessaging}. */\nexport class MessagingSdkLoadError extends Error {\n\treadonly code: MessagingSdkLoadErrorCode;\n\n\tconstructor(\n\t\tmessage: string,\n\t\tcode: MessagingSdkLoadErrorCode,\n\t\tcause?: unknown,\n\t) {\n\t\tsuper(message);\n\t\tthis.name = \"MessagingSdkLoadError\";\n\t\tthis.code = code;\n\t\tif (cause !== undefined) {\n\t\t\tthis.cause = cause;\n\t\t}\n\t}\n}\n\n/**\n * The programmatic messaging interface {@link loadAdaMessaging} resolves. An\n * alias of the runtime's `AdaEmbedInterface`, which keeps that name because it\n * is also the legacy `window.adaEmbed` compat facade.\n */\nexport type AdaMessagingInterface = AdaEmbedInterface;\n\n/**\n * The public surface of the CDN-resolved SDK module, as loaded from\n * `<cdnBase>/sdk.js`. Types only — the implementation always comes from the\n * CDN at load time and is never part of this npm package.\n */\nexport interface MessagingSdkModule {\n\tAdaMessagingClient: typeof AdaMessagingClient;\n\tcreateAdaEmbedInterface: typeof createAdaEmbedInterface;\n\tdispatchEmbedLoaderInitialActionQueue: typeof dispatchEmbedLoaderInitialActionQueue;\n\tdispatchEmbedLoaderInitialActionQueueForAtomicHandoff: typeof dispatchEmbedLoaderInitialActionQueueForAtomicHandoff;\n\tmountAdaWebViewRuntime: typeof mountAdaWebViewRuntime;\n\tresolveAdaWebViewRuntimeAssets: typeof resolveAdaWebViewRuntimeAssets;\n}\n\ntype ImportSdkModule = (url: string) => Promise<unknown>;\n\nexport interface LoadMessagingSdkOptions {\n\t/**\n\t * CDN root to resolve `sdk.js` from. Must be an `https:` URL; plain\n\t * `http:` is accepted only for loopback hosts (`localhost`, `127.0.0.1`,\n\t * `[::1]`, `*.localhost`) during local development. Defaults to the\n\t * production root ({@link DEFAULT_MESSAGING_CDN_BASE}). Override it with\n\t * the Ada-provided asset host for a staging validation window only;\n\t * production pages must keep the default.\n\t */\n\tcdnBase?: string;\n\t/**\n\t * Full 40-character git commit SHA of a deployed CDN build. When set, the\n\t * loader skips the rollout bootstrap and imports that build's immutable\n\t * entry (`<cdnBase>/<sha>/sdk/index.js`) directly. Pass the package's\n\t * `CDN_BUILD_SHA` export to pin the build associated with this npm\n\t * version. NOT recommended for production: a pinned runtime misses Ada's\n\t * fixes and rollouts, and may predate server contract changes. Composes\n\t * with `cdnBase` for staged validation.\n\t */\n\tpinBuildSha?: string;\n\t/**\n\t * Import seam for unit tests. Defaults to a native dynamic `import()` of\n\t * the resolved bootstrap URL.\n\t */\n\timportModule?: ImportSdkModule;\n}\n\n// Both annotations are load-bearing: bundlers rewrite bare dynamic imports,\n// and this specifier must reach the browser untouched so the runtime always\n// resolves from Ada's CDN. The npm build for this entry is unminified so the\n// annotations survive into the published output.\nconst defaultImportModule: ImportSdkModule = (url) =>\n\timport(/* webpackIgnore: true */ /* @vite-ignore */ url);\n\n// Keyed by import seam so production memoizes on the module-level default\n// while each injected test seam gets an isolated cache.\nconst inFlightByImporter = new WeakMap<\n\tImportSdkModule,\n\tMap<string, Promise<MessagingSdkModule>>\n>();\n\n// Mirrors the loopback semantics of the shared `isLoopbackHost` /\n// `validateMessagingEndpoint` (shared/utils/src/url.ts) and the bridge\n// loader's copy (packages/bridge/src/npm/loader.ts). Inlined because the\n// published loader must stay dependency-free; keep the host set in sync.\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\t// `*.localhost` resolves to the loopback interface and is \"potentially\n\t// trustworthy\" per the Secure Contexts spec; no production host ends in\n\t// `.localhost`.\n\treturn LOOPBACK_HOSTS.has(normalized) || normalized.endsWith(\".localhost\");\n}\n\nfunction resolvePinnedBuildSha(pinBuildSha: string): string {\n\tconst sha = pinBuildSha.trim().toLowerCase();\n\tif (!PINNED_BUILD_SHA_PATTERN.test(sha)) {\n\t\tthrow new MessagingSdkLoadError(\n\t\t\t`The pinBuildSha option must be a full 40-character hex git SHA: \"${pinBuildSha}\".`,\n\t\t\t\"invalid_build_sha\",\n\t\t);\n\t}\n\tif (UNSTAMPED_BUILD_SHA_PATTERN.test(sha)) {\n\t\tthrow new MessagingSdkLoadError(\n\t\t\t\"The pinBuildSha option is the unstamped CDN_BUILD_SHA placeholder. Only the published npm package carries a stamped SHA; a repository or local build cannot pin a CDN build.\",\n\t\t\t\"unstamped_build_sha\",\n\t\t);\n\t}\n\treturn sha;\n}\n\n/**\n * The runtime this URL serves carries the SDK's security logic (endpoint\n * validation, session handling, the origin checks), so a cleartext transport\n * would let an on-path attacker replace it wholesale: `https:` is required,\n * with a loopback-only `http:` exception for local development.\n */\nfunction resolveSdkBootstrapUrl(\n\tcdnBase: string | undefined,\n\tpinBuildSha: string | undefined,\n): string {\n\tconst base = (cdnBase ?? DEFAULT_MESSAGING_CDN_BASE).trim();\n\t// Resolving against a base without a trailing slash would drop its last\n\t// path segment, so normalize before building the bootstrap URL.\n\tconst normalizedBase = base.endsWith(\"/\") ? base : `${base}/`;\n\tconst entryPath =\n\t\tpinBuildSha === undefined\n\t\t\t? SDK_BOOTSTRAP_FILENAME\n\t\t\t: `${resolvePinnedBuildSha(pinBuildSha)}/${PINNED_SDK_ENTRY_PATH}`;\n\tlet url: URL;\n\ttry {\n\t\turl = new URL(entryPath, normalizedBase);\n\t} catch (cause) {\n\t\tthrow new MessagingSdkLoadError(\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 MessagingSdkLoadError(\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 url.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 sdk_module_invalid error, not later as a\n// TypeError on the first missing call — so every export is checked. The\n// `satisfies` clause rejects names that are not interface members; the\n// exhaustiveness type below is what forces this list to grow with it.\nconst REQUIRED_SDK_EXPORTS = [\n\t\"AdaMessagingClient\",\n\t\"createAdaEmbedInterface\",\n\t\"dispatchEmbedLoaderInitialActionQueue\",\n\t\"dispatchEmbedLoaderInitialActionQueueForAtomicHandoff\",\n\t\"mountAdaWebViewRuntime\",\n\t\"resolveAdaWebViewRuntimeAssets\",\n] as const satisfies readonly (keyof MessagingSdkModule)[];\n\n// Compile-time exhaustiveness: adding a MessagingSdkModule member without\n// listing it in REQUIRED_SDK_EXPORTS turns this type into `never`, failing\n// the `true` assignment below.\ntype AllSdkExportsListed =\n\tExclude<\n\t\tkeyof MessagingSdkModule,\n\t\t(typeof REQUIRED_SDK_EXPORTS)[number]\n\t> extends never\n\t\t? true\n\t\t: never;\nconst _allSdkExportsListed: AllSdkExportsListed = true;\nvoid _allSdkExportsListed;\n\nfunction isMessagingSdkModule(value: unknown): value is MessagingSdkModule {\n\tif (typeof value !== \"object\" || value === null) {\n\t\treturn false;\n\t}\n\tconst candidate = value as Record<string, unknown>;\n\treturn REQUIRED_SDK_EXPORTS.every(\n\t\t(key) => typeof candidate[key] === \"function\",\n\t);\n}\n\nasync function importSdkModule(\n\turl: string,\n\timportModule: ImportSdkModule,\n): Promise<MessagingSdkModule> {\n\tlet loaded: unknown;\n\ttry {\n\t\tloaded = await importModule(url);\n\t} catch (cause) {\n\t\tthrow new MessagingSdkLoadError(\n\t\t\t`The Ada Messaging SDK failed to load from ${url}.`,\n\t\t\t\"sdk_import_failed\",\n\t\t\tcause,\n\t\t);\n\t}\n\tif (!isMessagingSdkModule(loaded)) {\n\t\tthrow new MessagingSdkLoadError(\n\t\t\t`The module at ${url} does not expose the Ada Messaging SDK interface.`,\n\t\t\t\"sdk_module_invalid\",\n\t\t);\n\t}\n\treturn loaded;\n}\n\n/**\n * Load the Ada Messaging SDK module from Ada's CDN.\n *\n * The returned promise is memoized per resolved URL: concurrent and repeated\n * calls share one in-flight import. A failed load clears the memo so a later\n * call retries. Rejections are always {@link MessagingSdkLoadError}.\n *\n * @example\n * const { createAdaEmbedInterface } = await loadMessagingSdk();\n */\nexport function loadMessagingSdk(\n\toptions: LoadMessagingSdkOptions = {},\n): Promise<MessagingSdkModule> {\n\tconst importModule = options.importModule ?? defaultImportModule;\n\tlet url: string;\n\ttry {\n\t\turl = resolveSdkBootstrapUrl(options.cdnBase, options.pinBuildSha);\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 = importSdkModule(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\n// Keyed by import seam like the module memo above: production memoizes on\n// the module-level default while each injected test seam gets an isolated\n// cache. `ada` is set when the promise resolves, so the stopped-interface\n// diagnostic can consult the resolved facade without wrapping it.\ninterface StartedInterfaceMemo {\n\tpromise: Promise<AdaMessagingInterface>;\n\tsettings: AdaMessagingStartOptions;\n\tada?: AdaMessagingInterface;\n}\n\nconst startedInterfaceByImporter = new WeakMap<\n\tImportSdkModule,\n\tStartedInterfaceMemo\n>();\n\n// Ungated like the runtime logger's dropped-host-value channel: both call\n// sites fire only on host misuse, never on an ordinary page load.\nfunction warnHost(message: string): void {\n\ttry {\n\t\t// biome-ignore lint/suspicious/noConsole: the loader's sanctioned host-page diagnostic sink\n\t\tconsole.warn(`[Ada] ${message}`);\n\t} catch {\n\t\t// A host may replace console methods; diagnostics must not throw.\n\t}\n}\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n\tif (typeof value !== \"object\" || value === null) {\n\t\treturn false;\n\t}\n\tconst prototype = Object.getPrototypeOf(value);\n\treturn prototype === Object.prototype || prototype === null;\n}\n\n// Structural equality over the settings surface. Two functions compare equal:\n// fresh inline callbacks on a re-run (React Strict Mode effects) are not a\n// settings change worth a warning. Exotic objects compare by reference.\nfunction settingsEqual(a: unknown, b: unknown): boolean {\n\tif (Object.is(a, b)) {\n\t\treturn true;\n\t}\n\tif (typeof a === \"function\" && typeof b === \"function\") {\n\t\treturn true;\n\t}\n\tif (Array.isArray(a) && Array.isArray(b)) {\n\t\treturn (\n\t\t\ta.length === b.length &&\n\t\t\ta.every((item, index) => settingsEqual(item, b[index]))\n\t\t);\n\t}\n\tif (isPlainObject(a) && isPlainObject(b)) {\n\t\tconst aKeys = Object.keys(a);\n\t\tconst bKeys = new Set(Object.keys(b));\n\t\treturn (\n\t\t\taKeys.length === bKeys.size &&\n\t\t\taKeys.every((key) => bKeys.has(key) && settingsEqual(a[key], b[key]))\n\t\t);\n\t}\n\treturn false;\n}\n\n// Observes the facade's symbol-keyed lifecycle getter (embed-lifecycle.ts)\n// instead of wrapping stop/start, so the customer's interface is never\n// mutated; a pinned runtime that predates the getter yields no flag and the\n// stopped-interface diagnostic is skipped rather than guessed.\nfunction isInterfaceStopped(ada: AdaMessagingInterface): boolean {\n\treturn readEmbedStoppedFlag(ada) === true;\n}\n\nasync function startAdaMessaging(\n\tsettings: AdaMessagingStartOptions,\n\toptions: LoadMessagingSdkOptions,\n): Promise<AdaMessagingInterface> {\n\tconst sdkModule = await loadMessagingSdk(options);\n\tconst ada = sdkModule.createAdaEmbedInterface();\n\tawait ada.start(settings);\n\treturn ada;\n}\n\n/**\n * Load the SDK, create the messaging interface, and start the widget —\n * the npm-first equivalent of the script-tag + `window.adaSettings` setup.\n *\n * One widget per page: the returned promise is memoized, so repeated and\n * concurrent calls resolve to the same started interface and the first\n * call's settings win. A failed load or start clears the memo so a later\n * call retries. Manage the running widget through the returned interface:\n * `stop()` tears it down and a later `start(settings)` on the same interface\n * runs again. Calling {@link loadAdaMessaging} after `stop()` returns the\n * same stopped interface without restarting it. Both memo outcomes log a\n * console warning: ignored differing settings, and a resolved interface\n * whose widget was stopped.\n *\n * Never assigns the interface to `window` or any other global: the returned\n * reference is the only handle, so hold it in module scope. A reference in\n * module scope is reachable only by your own code, never by other scripts on\n * the page.\n *\n * @example\n * // module scope: your code keeps the only reference\n * const ada = await loadAdaMessaging({\n * handle: \"my-bot\",\n * enableProgrammaticControl: true,\n * });\n * await ada.sendMessage(\"Hello\");\n */\nexport function loadAdaMessaging(\n\tsettings: AdaMessagingStartOptions,\n\toptions: LoadMessagingSdkOptions = {},\n): Promise<AdaMessagingInterface> {\n\tconst importModule = options.importModule ?? defaultImportModule;\n\tconst existing = startedInterfaceByImporter.get(importModule);\n\tif (existing) {\n\t\tif (!settingsEqual(settings, existing.settings)) {\n\t\t\twarnHost(\n\t\t\t\t\"loadAdaMessaging() was already called: the new settings are ignored and the existing interface is returned. To apply new settings, call stop() then start(settings) on it.\",\n\t\t\t);\n\t\t}\n\t\tif (existing.ada && isInterfaceStopped(existing.ada)) {\n\t\t\twarnHost(\n\t\t\t\t\"loadAdaMessaging() resolved an interface whose widget was stopped and never restarts it. Call start(settings) on it to run the widget again.\",\n\t\t\t);\n\t\t}\n\t\treturn existing.promise;\n\t}\n\tconst memo: StartedInterfaceMemo = {\n\t\tpromise: startAdaMessaging(settings, options),\n\t\tsettings,\n\t};\n\tstartedInterfaceByImporter.set(importModule, memo);\n\tmemo.promise.then(\n\t\t(ada) => {\n\t\t\tmemo.ada = ada;\n\t\t},\n\t\t() => {\n\t\t\tif (startedInterfaceByImporter.get(importModule) === memo) {\n\t\t\t\tstartedInterfaceByImporter.delete(importModule);\n\t\t\t}\n\t\t},\n\t);\n\treturn memo.promise;\n}\n"],"mappings":"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"}
|
package/dist/npm/index.js
CHANGED
|
@@ -12,7 +12,7 @@ var UNSTAMPED_SHA_PATTERN = /^0{40}$/;
|
|
|
12
12
|
* unstamped 40-zero placeholder ({@link isCdnBuildShaStamped} returns
|
|
13
13
|
* `false`); only published tarballs carry a real SHA.
|
|
14
14
|
*/
|
|
15
|
-
var CDN_BUILD_SHA = "
|
|
15
|
+
var CDN_BUILD_SHA = "29ecde8f0b3086ec3181c66e929f2416a8fd881d";
|
|
16
16
|
/**
|
|
17
17
|
* Whether `sha` (default {@link CDN_BUILD_SHA}) is a stamped full git commit
|
|
18
18
|
* SHA rather than the unstamped repo placeholder.
|
package/dist/npm/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../../src/npm/build-info.ts","../../src/client/embed-lifecycle.ts","../../src/npm/loader.ts"],"sourcesContent":["// The repo copy carries the 40-zero placeholder; the publish workflow\n// (.github/workflows/messaging-publish-web-packages.yml) stamps the real\n// commit SHA into the built output via scripts/stamp-cdn-build-sha.mjs and\n// verifies the packed tarball before `npm publish`. Keep the placeholder as\n// a single 40-zero string literal: the stamp is a same-length text\n// replacement, so source maps stay valid.\nconst GIT_SHA_PATTERN = /^[0-9a-f]{40}$/;\nconst UNSTAMPED_SHA_PATTERN = /^0{40}$/;\n\n/**\n * Git commit SHA of the monorepo commit this npm version was published from.\n * The CDN pipeline deploys each main commit's runtime under an immutable\n * SHA root, so this value names the CDN build associated with this npm\n * version. Pass it as the `pinBuildSha` loader option to load exactly that\n * runtime build — not recommended for production (see the README).\n *\n * In the repository, and in any locally built copy, the value is the\n * unstamped 40-zero placeholder ({@link isCdnBuildShaStamped} returns\n * `false`); only published tarballs carry a real SHA.\n */\nexport const CDN_BUILD_SHA: string = \"811ee8fe6daeb693bc1b95e3a41ccb82a9d763eb\";\n\n/**\n * Whether `sha` (default {@link CDN_BUILD_SHA}) is a stamped full git commit\n * SHA rather than the unstamped repo placeholder.\n */\nexport function isCdnBuildShaStamped(sha: string = CDN_BUILD_SHA): boolean {\n\tconst normalized = sha.trim().toLowerCase();\n\treturn (\n\t\tGIT_SHA_PATTERN.test(normalized) && !UNSTAMPED_SHA_PATTERN.test(normalized)\n\t);\n}\n","/**\n * Lifecycle channel between `createAdaEmbedInterface` and the npm loader's\n * memo: a non-enumerable getter over closure state, so the loader never\n * mutates the public surface and `Object.freeze` on the facade still works.\n * `Symbol.for` because the loader and the CDN runtime are separate builds;\n * a runtime that predates the getter yields `undefined` — degrade gracefully.\n */\nexport const EMBED_LIFECYCLE_STOPPED = Symbol.for(\n\t\"ada-cx/messaging-sdk/embed-stopped\",\n);\n\n/** Read the facade's stopped flag; `undefined` when the runtime predates it. */\nexport function readEmbedStoppedFlag(ada: object): boolean | undefined {\n\tconst value = (ada as Record<symbol, unknown>)[EMBED_LIFECYCLE_STOPPED];\n\treturn typeof value === \"boolean\" ? value : undefined;\n}\n\n/** Define the getter at facade creation time, before any host freeze. */\nexport function defineEmbedStoppedFlag(\n\tada: object,\n\treadStopped: () => boolean,\n): void {\n\tObject.defineProperty(ada, EMBED_LIFECYCLE_STOPPED, {\n\t\tget: readStopped,\n\t\tenumerable: false,\n\t\tconfigurable: false,\n\t});\n}\n","import type { AdaMessagingClient } from \"../client/client\";\nimport type { AdaMessagingStartOptions } from \"../client/client-types\";\nimport type {\n\tAdaEmbedInterface,\n\tcreateAdaEmbedInterface,\n\tdispatchEmbedLoaderInitialActionQueue,\n\tdispatchEmbedLoaderInitialActionQueueForAtomicHandoff,\n} from \"../client/embed-interface\";\nimport { readEmbedStoppedFlag } from \"../client/embed-lifecycle\";\nimport type {\n\tmountAdaWebViewRuntime,\n\tresolveAdaWebViewRuntimeAssets,\n} from \"../mobile-webview-runtime\";\n\n/**\n * Production CDN root the loader resolves the SDK bootstrap from when no\n * `cdnBase` override is given. The npm package never pins a runtime version;\n * the bootstrap at this root resolves the current rollout at load time.\n */\nexport const DEFAULT_MESSAGING_CDN_BASE =\n\t\"https://messaging-assets.ada.support/\";\n\nconst SDK_BOOTSTRAP_FILENAME = \"sdk.js\";\n\n// The CDN pipeline deploys each main commit's runtime as an immutable\n// SHA-rooted entry (`<cdnBase>/<sha>/sdk/index.js`); the mutable `sdk.js`\n// bootstrap resolves the current rollout to one of those roots at load time.\n// A pin bypasses the bootstrap and imports one root directly.\nconst PINNED_SDK_ENTRY_PATH = \"sdk/index.js\";\n\n// Strict on purpose: the pin is interpolated into a CDN path, so only a full\n// lowercase 40-hex git SHA may pass (mirrors scripts/sdk.js's\n// VERSION_OVERRIDE_PATTERN security boundary, minus the short-SHA and pr-N\n// forms — the npm surface pins exactly what CDN_BUILD_SHA carries).\nconst PINNED_BUILD_SHA_PATTERN = /^[0-9a-f]{40}$/;\nconst UNSTAMPED_BUILD_SHA_PATTERN = /^0{40}$/;\n\n/** Why a {@link MessagingSdkLoadError} was raised. */\nexport type MessagingSdkLoadErrorCode =\n\t/**\n\t * The `cdnBase` option is not a valid `https:` URL (plain `http:` is\n\t * allowed only for loopback hosts).\n\t */\n\t| \"invalid_cdn_base\"\n\t/** The `pinBuildSha` option is not a full 40-character hex git SHA. */\n\t| \"invalid_build_sha\"\n\t/**\n\t * The `pinBuildSha` option is the unstamped 40-zero placeholder: this\n\t * copy of the package was built from the repository, not published, so\n\t * its `CDN_BUILD_SHA` names no CDN build.\n\t */\n\t| \"unstamped_build_sha\"\n\t/** The dynamic import of the CDN bootstrap failed (network, CSP, 404). */\n\t| \"sdk_import_failed\"\n\t/** The imported module does not expose the Ada Messaging SDK surface. */\n\t| \"sdk_module_invalid\";\n\n/** Typed failure raised by {@link loadMessagingSdk} and {@link loadAdaMessaging}. */\nexport class MessagingSdkLoadError extends Error {\n\treadonly code: MessagingSdkLoadErrorCode;\n\n\tconstructor(\n\t\tmessage: string,\n\t\tcode: MessagingSdkLoadErrorCode,\n\t\tcause?: unknown,\n\t) {\n\t\tsuper(message);\n\t\tthis.name = \"MessagingSdkLoadError\";\n\t\tthis.code = code;\n\t\tif (cause !== undefined) {\n\t\t\tthis.cause = cause;\n\t\t}\n\t}\n}\n\n/**\n * The programmatic messaging interface {@link loadAdaMessaging} resolves. An\n * alias of the runtime's `AdaEmbedInterface`, which keeps that name because it\n * is also the legacy `window.adaEmbed` compat facade.\n */\nexport type AdaMessagingInterface = AdaEmbedInterface;\n\n/**\n * The public surface of the CDN-resolved SDK module, as loaded from\n * `<cdnBase>/sdk.js`. Types only — the implementation always comes from the\n * CDN at load time and is never part of this npm package.\n */\nexport interface MessagingSdkModule {\n\tAdaMessagingClient: typeof AdaMessagingClient;\n\tcreateAdaEmbedInterface: typeof createAdaEmbedInterface;\n\tdispatchEmbedLoaderInitialActionQueue: typeof dispatchEmbedLoaderInitialActionQueue;\n\tdispatchEmbedLoaderInitialActionQueueForAtomicHandoff: typeof dispatchEmbedLoaderInitialActionQueueForAtomicHandoff;\n\tmountAdaWebViewRuntime: typeof mountAdaWebViewRuntime;\n\tresolveAdaWebViewRuntimeAssets: typeof resolveAdaWebViewRuntimeAssets;\n}\n\ntype ImportSdkModule = (url: string) => Promise<unknown>;\n\nexport interface LoadMessagingSdkOptions {\n\t/**\n\t * CDN root to resolve `sdk.js` from. Must be an `https:` URL; plain\n\t * `http:` is accepted only for loopback hosts (`localhost`, `127.0.0.1`,\n\t * `[::1]`, `*.localhost`) during local development. Defaults to the\n\t * production root ({@link DEFAULT_MESSAGING_CDN_BASE}). Override it with\n\t * the Ada-provided asset host for a staging validation window only;\n\t * production pages must keep the default.\n\t */\n\tcdnBase?: string;\n\t/**\n\t * Full 40-character git commit SHA of a deployed CDN build. When set, the\n\t * loader skips the rollout bootstrap and imports that build's immutable\n\t * entry (`<cdnBase>/<sha>/sdk/index.js`) directly. Pass the package's\n\t * `CDN_BUILD_SHA` export to pin the build associated with this npm\n\t * version. NOT recommended for production: a pinned runtime misses Ada's\n\t * fixes and rollouts, and may predate server contract changes. Composes\n\t * with `cdnBase` for staged validation.\n\t */\n\tpinBuildSha?: string;\n\t/**\n\t * Import seam for unit tests. Defaults to a native dynamic `import()` of\n\t * the resolved bootstrap URL.\n\t */\n\timportModule?: ImportSdkModule;\n}\n\n// Both annotations are load-bearing: bundlers rewrite bare dynamic imports,\n// and this specifier must reach the browser untouched so the runtime always\n// resolves from Ada's CDN. The npm build for this entry is unminified so the\n// annotations survive into the published output.\nconst defaultImportModule: ImportSdkModule = (url) =>\n\timport(/* webpackIgnore: true */ /* @vite-ignore */ url);\n\n// Keyed by import seam so production memoizes on the module-level default\n// while each injected test seam gets an isolated cache.\nconst inFlightByImporter = new WeakMap<\n\tImportSdkModule,\n\tMap<string, Promise<MessagingSdkModule>>\n>();\n\n// Mirrors the loopback semantics of the shared `isLoopbackHost` /\n// `validateMessagingEndpoint` (shared/utils/src/url.ts) and the bridge\n// loader's copy (packages/bridge/src/npm/loader.ts). Inlined because the\n// published loader must stay dependency-free; keep the host set in sync.\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\t// `*.localhost` resolves to the loopback interface and is \"potentially\n\t// trustworthy\" per the Secure Contexts spec; no production host ends in\n\t// `.localhost`.\n\treturn LOOPBACK_HOSTS.has(normalized) || normalized.endsWith(\".localhost\");\n}\n\nfunction resolvePinnedBuildSha(pinBuildSha: string): string {\n\tconst sha = pinBuildSha.trim().toLowerCase();\n\tif (!PINNED_BUILD_SHA_PATTERN.test(sha)) {\n\t\tthrow new MessagingSdkLoadError(\n\t\t\t`The pinBuildSha option must be a full 40-character hex git SHA: \"${pinBuildSha}\".`,\n\t\t\t\"invalid_build_sha\",\n\t\t);\n\t}\n\tif (UNSTAMPED_BUILD_SHA_PATTERN.test(sha)) {\n\t\tthrow new MessagingSdkLoadError(\n\t\t\t\"The pinBuildSha option is the unstamped CDN_BUILD_SHA placeholder. Only the published npm package carries a stamped SHA; a repository or local build cannot pin a CDN build.\",\n\t\t\t\"unstamped_build_sha\",\n\t\t);\n\t}\n\treturn sha;\n}\n\n/**\n * The runtime this URL serves carries the SDK's security logic (endpoint\n * validation, session handling, the origin checks), so a cleartext transport\n * would let an on-path attacker replace it wholesale: `https:` is required,\n * with a loopback-only `http:` exception for local development.\n */\nfunction resolveSdkBootstrapUrl(\n\tcdnBase: string | undefined,\n\tpinBuildSha: string | undefined,\n): string {\n\tconst base = (cdnBase ?? DEFAULT_MESSAGING_CDN_BASE).trim();\n\t// Resolving against a base without a trailing slash would drop its last\n\t// path segment, so normalize before building the bootstrap URL.\n\tconst normalizedBase = base.endsWith(\"/\") ? base : `${base}/`;\n\tconst entryPath =\n\t\tpinBuildSha === undefined\n\t\t\t? SDK_BOOTSTRAP_FILENAME\n\t\t\t: `${resolvePinnedBuildSha(pinBuildSha)}/${PINNED_SDK_ENTRY_PATH}`;\n\tlet url: URL;\n\ttry {\n\t\turl = new URL(entryPath, normalizedBase);\n\t} catch (cause) {\n\t\tthrow new MessagingSdkLoadError(\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 MessagingSdkLoadError(\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 url.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 sdk_module_invalid error, not later as a\n// TypeError on the first missing call — so every export is checked. The\n// `satisfies` clause rejects names that are not interface members; the\n// exhaustiveness type below is what forces this list to grow with it.\nconst REQUIRED_SDK_EXPORTS = [\n\t\"AdaMessagingClient\",\n\t\"createAdaEmbedInterface\",\n\t\"dispatchEmbedLoaderInitialActionQueue\",\n\t\"dispatchEmbedLoaderInitialActionQueueForAtomicHandoff\",\n\t\"mountAdaWebViewRuntime\",\n\t\"resolveAdaWebViewRuntimeAssets\",\n] as const satisfies readonly (keyof MessagingSdkModule)[];\n\n// Compile-time exhaustiveness: adding a MessagingSdkModule member without\n// listing it in REQUIRED_SDK_EXPORTS turns this type into `never`, failing\n// the `true` assignment below.\ntype AllSdkExportsListed =\n\tExclude<\n\t\tkeyof MessagingSdkModule,\n\t\t(typeof REQUIRED_SDK_EXPORTS)[number]\n\t> extends never\n\t\t? true\n\t\t: never;\nconst _allSdkExportsListed: AllSdkExportsListed = true;\nvoid _allSdkExportsListed;\n\nfunction isMessagingSdkModule(value: unknown): value is MessagingSdkModule {\n\tif (typeof value !== \"object\" || value === null) {\n\t\treturn false;\n\t}\n\tconst candidate = value as Record<string, unknown>;\n\treturn REQUIRED_SDK_EXPORTS.every(\n\t\t(key) => typeof candidate[key] === \"function\",\n\t);\n}\n\nasync function importSdkModule(\n\turl: string,\n\timportModule: ImportSdkModule,\n): Promise<MessagingSdkModule> {\n\tlet loaded: unknown;\n\ttry {\n\t\tloaded = await importModule(url);\n\t} catch (cause) {\n\t\tthrow new MessagingSdkLoadError(\n\t\t\t`The Ada Messaging SDK failed to load from ${url}.`,\n\t\t\t\"sdk_import_failed\",\n\t\t\tcause,\n\t\t);\n\t}\n\tif (!isMessagingSdkModule(loaded)) {\n\t\tthrow new MessagingSdkLoadError(\n\t\t\t`The module at ${url} does not expose the Ada Messaging SDK interface.`,\n\t\t\t\"sdk_module_invalid\",\n\t\t);\n\t}\n\treturn loaded;\n}\n\n/**\n * Load the Ada Messaging SDK module from Ada's CDN.\n *\n * The returned promise is memoized per resolved URL: concurrent and repeated\n * calls share one in-flight import. A failed load clears the memo so a later\n * call retries. Rejections are always {@link MessagingSdkLoadError}.\n *\n * @example\n * const { createAdaEmbedInterface } = await loadMessagingSdk();\n */\nexport function loadMessagingSdk(\n\toptions: LoadMessagingSdkOptions = {},\n): Promise<MessagingSdkModule> {\n\tconst importModule = options.importModule ?? defaultImportModule;\n\tlet url: string;\n\ttry {\n\t\turl = resolveSdkBootstrapUrl(options.cdnBase, options.pinBuildSha);\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 = importSdkModule(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\n// Keyed by import seam like the module memo above: production memoizes on\n// the module-level default while each injected test seam gets an isolated\n// cache. `ada` is set when the promise resolves, so the stopped-interface\n// diagnostic can consult the resolved facade without wrapping it.\ninterface StartedInterfaceMemo {\n\tpromise: Promise<AdaMessagingInterface>;\n\tsettings: AdaMessagingStartOptions;\n\tada?: AdaMessagingInterface;\n}\n\nconst startedInterfaceByImporter = new WeakMap<\n\tImportSdkModule,\n\tStartedInterfaceMemo\n>();\n\n// Ungated like the runtime logger's dropped-host-value channel: both call\n// sites fire only on host misuse, never on an ordinary page load.\nfunction warnHost(message: string): void {\n\ttry {\n\t\t// biome-ignore lint/suspicious/noConsole: the loader's sanctioned host-page diagnostic sink\n\t\tconsole.warn(`[Ada] ${message}`);\n\t} catch {\n\t\t// A host may replace console methods; diagnostics must not throw.\n\t}\n}\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n\tif (typeof value !== \"object\" || value === null) {\n\t\treturn false;\n\t}\n\tconst prototype = Object.getPrototypeOf(value);\n\treturn prototype === Object.prototype || prototype === null;\n}\n\n// Structural equality over the settings surface. Two functions compare equal:\n// fresh inline callbacks on a re-run (React Strict Mode effects) are not a\n// settings change worth a warning. Exotic objects compare by reference.\nfunction settingsEqual(a: unknown, b: unknown): boolean {\n\tif (Object.is(a, b)) {\n\t\treturn true;\n\t}\n\tif (typeof a === \"function\" && typeof b === \"function\") {\n\t\treturn true;\n\t}\n\tif (Array.isArray(a) && Array.isArray(b)) {\n\t\treturn (\n\t\t\ta.length === b.length &&\n\t\t\ta.every((item, index) => settingsEqual(item, b[index]))\n\t\t);\n\t}\n\tif (isPlainObject(a) && isPlainObject(b)) {\n\t\tconst aKeys = Object.keys(a);\n\t\tconst bKeys = new Set(Object.keys(b));\n\t\treturn (\n\t\t\taKeys.length === bKeys.size &&\n\t\t\taKeys.every((key) => bKeys.has(key) && settingsEqual(a[key], b[key]))\n\t\t);\n\t}\n\treturn false;\n}\n\n// Observes the facade's symbol-keyed lifecycle getter (embed-lifecycle.ts)\n// instead of wrapping stop/start, so the customer's interface is never\n// mutated; a pinned runtime that predates the getter yields no flag and the\n// stopped-interface diagnostic is skipped rather than guessed.\nfunction isInterfaceStopped(ada: AdaMessagingInterface): boolean {\n\treturn readEmbedStoppedFlag(ada) === true;\n}\n\nasync function startAdaMessaging(\n\tsettings: AdaMessagingStartOptions,\n\toptions: LoadMessagingSdkOptions,\n): Promise<AdaMessagingInterface> {\n\tconst sdkModule = await loadMessagingSdk(options);\n\tconst ada = sdkModule.createAdaEmbedInterface();\n\tawait ada.start(settings);\n\treturn ada;\n}\n\n/**\n * Load the SDK, create the messaging interface, and start the widget —\n * the npm-first equivalent of the script-tag + `window.adaSettings` setup.\n *\n * One widget per page: the returned promise is memoized, so repeated and\n * concurrent calls resolve to the same started interface and the first\n * call's settings win. A failed load or start clears the memo so a later\n * call retries. Manage the running widget through the returned interface:\n * `stop()` tears it down and a later `start(settings)` on the same interface\n * runs again. Calling {@link loadAdaMessaging} after `stop()` returns the\n * same stopped interface without restarting it. Both memo outcomes log a\n * console warning: ignored differing settings, and a resolved interface\n * whose widget was stopped.\n *\n * Never assigns the interface to `window` or any other global: the returned\n * reference is the only handle, so hold it in module scope. A reference in\n * module scope is reachable only by your own code, never by other scripts on\n * the page.\n *\n * @example\n * // module scope: your code keeps the only reference\n * const ada = await loadAdaMessaging({\n * handle: \"my-bot\",\n * enableProgrammaticControl: true,\n * });\n * await ada.sendMessage(\"Hello\");\n */\nexport function loadAdaMessaging(\n\tsettings: AdaMessagingStartOptions,\n\toptions: LoadMessagingSdkOptions = {},\n): Promise<AdaMessagingInterface> {\n\tconst importModule = options.importModule ?? defaultImportModule;\n\tconst existing = startedInterfaceByImporter.get(importModule);\n\tif (existing) {\n\t\tif (!settingsEqual(settings, existing.settings)) {\n\t\t\twarnHost(\n\t\t\t\t\"loadAdaMessaging() was already called: the new settings are ignored and the existing interface is returned. To apply new settings, call stop() then start(settings) on it.\",\n\t\t\t);\n\t\t}\n\t\tif (existing.ada && isInterfaceStopped(existing.ada)) {\n\t\t\twarnHost(\n\t\t\t\t\"loadAdaMessaging() resolved an interface whose widget was stopped and never restarts it. Call start(settings) on it to run the widget again.\",\n\t\t\t);\n\t\t}\n\t\treturn existing.promise;\n\t}\n\tconst memo: StartedInterfaceMemo = {\n\t\tpromise: startAdaMessaging(settings, options),\n\t\tsettings,\n\t};\n\tstartedInterfaceByImporter.set(importModule, memo);\n\tmemo.promise.then(\n\t\t(ada) => {\n\t\t\tmemo.ada = ada;\n\t\t},\n\t\t() => {\n\t\t\tif (startedInterfaceByImporter.get(importModule) === memo) {\n\t\t\t\tstartedInterfaceByImporter.delete(importModule);\n\t\t\t}\n\t\t},\n\t);\n\treturn memo.promise;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../src/npm/build-info.ts","../../src/client/embed-lifecycle.ts","../../src/npm/loader.ts"],"sourcesContent":["// The repo copy carries the 40-zero placeholder; the publish workflow\n// (.github/workflows/messaging-publish-web-packages.yml) stamps the real\n// commit SHA into the built output via scripts/stamp-cdn-build-sha.mjs and\n// verifies the packed tarball before `npm publish`. Keep the placeholder as\n// a single 40-zero string literal: the stamp is a same-length text\n// replacement, so source maps stay valid.\nconst GIT_SHA_PATTERN = /^[0-9a-f]{40}$/;\nconst UNSTAMPED_SHA_PATTERN = /^0{40}$/;\n\n/**\n * Git commit SHA of the monorepo commit this npm version was published from.\n * The CDN pipeline deploys each main commit's runtime under an immutable\n * SHA root, so this value names the CDN build associated with this npm\n * version. Pass it as the `pinBuildSha` loader option to load exactly that\n * runtime build — not recommended for production (see the README).\n *\n * In the repository, and in any locally built copy, the value is the\n * unstamped 40-zero placeholder ({@link isCdnBuildShaStamped} returns\n * `false`); only published tarballs carry a real SHA.\n */\nexport const CDN_BUILD_SHA: string = \"29ecde8f0b3086ec3181c66e929f2416a8fd881d\";\n\n/**\n * Whether `sha` (default {@link CDN_BUILD_SHA}) is a stamped full git commit\n * SHA rather than the unstamped repo placeholder.\n */\nexport function isCdnBuildShaStamped(sha: string = CDN_BUILD_SHA): boolean {\n\tconst normalized = sha.trim().toLowerCase();\n\treturn (\n\t\tGIT_SHA_PATTERN.test(normalized) && !UNSTAMPED_SHA_PATTERN.test(normalized)\n\t);\n}\n","/**\n * Lifecycle channel between `createAdaEmbedInterface` and the npm loader's\n * memo: a non-enumerable getter over closure state, so the loader never\n * mutates the public surface and `Object.freeze` on the facade still works.\n * `Symbol.for` because the loader and the CDN runtime are separate builds;\n * a runtime that predates the getter yields `undefined` — degrade gracefully.\n */\nexport const EMBED_LIFECYCLE_STOPPED = Symbol.for(\n\t\"ada-cx/messaging-sdk/embed-stopped\",\n);\n\n/** Read the facade's stopped flag; `undefined` when the runtime predates it. */\nexport function readEmbedStoppedFlag(ada: object): boolean | undefined {\n\tconst value = (ada as Record<symbol, unknown>)[EMBED_LIFECYCLE_STOPPED];\n\treturn typeof value === \"boolean\" ? value : undefined;\n}\n\n/** Define the getter at facade creation time, before any host freeze. */\nexport function defineEmbedStoppedFlag(\n\tada: object,\n\treadStopped: () => boolean,\n): void {\n\tObject.defineProperty(ada, EMBED_LIFECYCLE_STOPPED, {\n\t\tget: readStopped,\n\t\tenumerable: false,\n\t\tconfigurable: false,\n\t});\n}\n","import type { AdaMessagingClient } from \"../client/client\";\nimport type { AdaMessagingStartOptions } from \"../client/client-types\";\nimport type {\n\tAdaEmbedInterface,\n\tcreateAdaEmbedInterface,\n\tdispatchEmbedLoaderInitialActionQueue,\n\tdispatchEmbedLoaderInitialActionQueueForAtomicHandoff,\n} from \"../client/embed-interface\";\nimport { readEmbedStoppedFlag } from \"../client/embed-lifecycle\";\nimport type {\n\tmountAdaWebViewRuntime,\n\tresolveAdaWebViewRuntimeAssets,\n} from \"../mobile-webview-runtime\";\n\n/**\n * Production CDN root the loader resolves the SDK bootstrap from when no\n * `cdnBase` override is given. The npm package never pins a runtime version;\n * the bootstrap at this root resolves the current rollout at load time.\n */\nexport const DEFAULT_MESSAGING_CDN_BASE =\n\t\"https://messaging-assets.ada.support/\";\n\nconst SDK_BOOTSTRAP_FILENAME = \"sdk.js\";\n\n// The CDN pipeline deploys each main commit's runtime as an immutable\n// SHA-rooted entry (`<cdnBase>/<sha>/sdk/index.js`); the mutable `sdk.js`\n// bootstrap resolves the current rollout to one of those roots at load time.\n// A pin bypasses the bootstrap and imports one root directly.\nconst PINNED_SDK_ENTRY_PATH = \"sdk/index.js\";\n\n// Strict on purpose: the pin is interpolated into a CDN path, so only a full\n// lowercase 40-hex git SHA may pass (mirrors scripts/sdk.js's\n// VERSION_OVERRIDE_PATTERN security boundary, minus the short-SHA and pr-N\n// forms — the npm surface pins exactly what CDN_BUILD_SHA carries).\nconst PINNED_BUILD_SHA_PATTERN = /^[0-9a-f]{40}$/;\nconst UNSTAMPED_BUILD_SHA_PATTERN = /^0{40}$/;\n\n/** Why a {@link MessagingSdkLoadError} was raised. */\nexport type MessagingSdkLoadErrorCode =\n\t/**\n\t * The `cdnBase` option is not a valid `https:` URL (plain `http:` is\n\t * allowed only for loopback hosts).\n\t */\n\t| \"invalid_cdn_base\"\n\t/** The `pinBuildSha` option is not a full 40-character hex git SHA. */\n\t| \"invalid_build_sha\"\n\t/**\n\t * The `pinBuildSha` option is the unstamped 40-zero placeholder: this\n\t * copy of the package was built from the repository, not published, so\n\t * its `CDN_BUILD_SHA` names no CDN build.\n\t */\n\t| \"unstamped_build_sha\"\n\t/** The dynamic import of the CDN bootstrap failed (network, CSP, 404). */\n\t| \"sdk_import_failed\"\n\t/** The imported module does not expose the Ada Messaging SDK surface. */\n\t| \"sdk_module_invalid\";\n\n/** Typed failure raised by {@link loadMessagingSdk} and {@link loadAdaMessaging}. */\nexport class MessagingSdkLoadError extends Error {\n\treadonly code: MessagingSdkLoadErrorCode;\n\n\tconstructor(\n\t\tmessage: string,\n\t\tcode: MessagingSdkLoadErrorCode,\n\t\tcause?: unknown,\n\t) {\n\t\tsuper(message);\n\t\tthis.name = \"MessagingSdkLoadError\";\n\t\tthis.code = code;\n\t\tif (cause !== undefined) {\n\t\t\tthis.cause = cause;\n\t\t}\n\t}\n}\n\n/**\n * The programmatic messaging interface {@link loadAdaMessaging} resolves. An\n * alias of the runtime's `AdaEmbedInterface`, which keeps that name because it\n * is also the legacy `window.adaEmbed` compat facade.\n */\nexport type AdaMessagingInterface = AdaEmbedInterface;\n\n/**\n * The public surface of the CDN-resolved SDK module, as loaded from\n * `<cdnBase>/sdk.js`. Types only — the implementation always comes from the\n * CDN at load time and is never part of this npm package.\n */\nexport interface MessagingSdkModule {\n\tAdaMessagingClient: typeof AdaMessagingClient;\n\tcreateAdaEmbedInterface: typeof createAdaEmbedInterface;\n\tdispatchEmbedLoaderInitialActionQueue: typeof dispatchEmbedLoaderInitialActionQueue;\n\tdispatchEmbedLoaderInitialActionQueueForAtomicHandoff: typeof dispatchEmbedLoaderInitialActionQueueForAtomicHandoff;\n\tmountAdaWebViewRuntime: typeof mountAdaWebViewRuntime;\n\tresolveAdaWebViewRuntimeAssets: typeof resolveAdaWebViewRuntimeAssets;\n}\n\ntype ImportSdkModule = (url: string) => Promise<unknown>;\n\nexport interface LoadMessagingSdkOptions {\n\t/**\n\t * CDN root to resolve `sdk.js` from. Must be an `https:` URL; plain\n\t * `http:` is accepted only for loopback hosts (`localhost`, `127.0.0.1`,\n\t * `[::1]`, `*.localhost`) during local development. Defaults to the\n\t * production root ({@link DEFAULT_MESSAGING_CDN_BASE}). Override it with\n\t * the Ada-provided asset host for a staging validation window only;\n\t * production pages must keep the default.\n\t */\n\tcdnBase?: string;\n\t/**\n\t * Full 40-character git commit SHA of a deployed CDN build. When set, the\n\t * loader skips the rollout bootstrap and imports that build's immutable\n\t * entry (`<cdnBase>/<sha>/sdk/index.js`) directly. Pass the package's\n\t * `CDN_BUILD_SHA` export to pin the build associated with this npm\n\t * version. NOT recommended for production: a pinned runtime misses Ada's\n\t * fixes and rollouts, and may predate server contract changes. Composes\n\t * with `cdnBase` for staged validation.\n\t */\n\tpinBuildSha?: string;\n\t/**\n\t * Import seam for unit tests. Defaults to a native dynamic `import()` of\n\t * the resolved bootstrap URL.\n\t */\n\timportModule?: ImportSdkModule;\n}\n\n// Both annotations are load-bearing: bundlers rewrite bare dynamic imports,\n// and this specifier must reach the browser untouched so the runtime always\n// resolves from Ada's CDN. The npm build for this entry is unminified so the\n// annotations survive into the published output.\nconst defaultImportModule: ImportSdkModule = (url) =>\n\timport(/* webpackIgnore: true */ /* @vite-ignore */ url);\n\n// Keyed by import seam so production memoizes on the module-level default\n// while each injected test seam gets an isolated cache.\nconst inFlightByImporter = new WeakMap<\n\tImportSdkModule,\n\tMap<string, Promise<MessagingSdkModule>>\n>();\n\n// Mirrors the loopback semantics of the shared `isLoopbackHost` /\n// `validateMessagingEndpoint` (shared/utils/src/url.ts) and the bridge\n// loader's copy (packages/bridge/src/npm/loader.ts). Inlined because the\n// published loader must stay dependency-free; keep the host set in sync.\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\t// `*.localhost` resolves to the loopback interface and is \"potentially\n\t// trustworthy\" per the Secure Contexts spec; no production host ends in\n\t// `.localhost`.\n\treturn LOOPBACK_HOSTS.has(normalized) || normalized.endsWith(\".localhost\");\n}\n\nfunction resolvePinnedBuildSha(pinBuildSha: string): string {\n\tconst sha = pinBuildSha.trim().toLowerCase();\n\tif (!PINNED_BUILD_SHA_PATTERN.test(sha)) {\n\t\tthrow new MessagingSdkLoadError(\n\t\t\t`The pinBuildSha option must be a full 40-character hex git SHA: \"${pinBuildSha}\".`,\n\t\t\t\"invalid_build_sha\",\n\t\t);\n\t}\n\tif (UNSTAMPED_BUILD_SHA_PATTERN.test(sha)) {\n\t\tthrow new MessagingSdkLoadError(\n\t\t\t\"The pinBuildSha option is the unstamped CDN_BUILD_SHA placeholder. Only the published npm package carries a stamped SHA; a repository or local build cannot pin a CDN build.\",\n\t\t\t\"unstamped_build_sha\",\n\t\t);\n\t}\n\treturn sha;\n}\n\n/**\n * The runtime this URL serves carries the SDK's security logic (endpoint\n * validation, session handling, the origin checks), so a cleartext transport\n * would let an on-path attacker replace it wholesale: `https:` is required,\n * with a loopback-only `http:` exception for local development.\n */\nfunction resolveSdkBootstrapUrl(\n\tcdnBase: string | undefined,\n\tpinBuildSha: string | undefined,\n): string {\n\tconst base = (cdnBase ?? DEFAULT_MESSAGING_CDN_BASE).trim();\n\t// Resolving against a base without a trailing slash would drop its last\n\t// path segment, so normalize before building the bootstrap URL.\n\tconst normalizedBase = base.endsWith(\"/\") ? base : `${base}/`;\n\tconst entryPath =\n\t\tpinBuildSha === undefined\n\t\t\t? SDK_BOOTSTRAP_FILENAME\n\t\t\t: `${resolvePinnedBuildSha(pinBuildSha)}/${PINNED_SDK_ENTRY_PATH}`;\n\tlet url: URL;\n\ttry {\n\t\turl = new URL(entryPath, normalizedBase);\n\t} catch (cause) {\n\t\tthrow new MessagingSdkLoadError(\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 MessagingSdkLoadError(\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 url.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 sdk_module_invalid error, not later as a\n// TypeError on the first missing call — so every export is checked. The\n// `satisfies` clause rejects names that are not interface members; the\n// exhaustiveness type below is what forces this list to grow with it.\nconst REQUIRED_SDK_EXPORTS = [\n\t\"AdaMessagingClient\",\n\t\"createAdaEmbedInterface\",\n\t\"dispatchEmbedLoaderInitialActionQueue\",\n\t\"dispatchEmbedLoaderInitialActionQueueForAtomicHandoff\",\n\t\"mountAdaWebViewRuntime\",\n\t\"resolveAdaWebViewRuntimeAssets\",\n] as const satisfies readonly (keyof MessagingSdkModule)[];\n\n// Compile-time exhaustiveness: adding a MessagingSdkModule member without\n// listing it in REQUIRED_SDK_EXPORTS turns this type into `never`, failing\n// the `true` assignment below.\ntype AllSdkExportsListed =\n\tExclude<\n\t\tkeyof MessagingSdkModule,\n\t\t(typeof REQUIRED_SDK_EXPORTS)[number]\n\t> extends never\n\t\t? true\n\t\t: never;\nconst _allSdkExportsListed: AllSdkExportsListed = true;\nvoid _allSdkExportsListed;\n\nfunction isMessagingSdkModule(value: unknown): value is MessagingSdkModule {\n\tif (typeof value !== \"object\" || value === null) {\n\t\treturn false;\n\t}\n\tconst candidate = value as Record<string, unknown>;\n\treturn REQUIRED_SDK_EXPORTS.every(\n\t\t(key) => typeof candidate[key] === \"function\",\n\t);\n}\n\nasync function importSdkModule(\n\turl: string,\n\timportModule: ImportSdkModule,\n): Promise<MessagingSdkModule> {\n\tlet loaded: unknown;\n\ttry {\n\t\tloaded = await importModule(url);\n\t} catch (cause) {\n\t\tthrow new MessagingSdkLoadError(\n\t\t\t`The Ada Messaging SDK failed to load from ${url}.`,\n\t\t\t\"sdk_import_failed\",\n\t\t\tcause,\n\t\t);\n\t}\n\tif (!isMessagingSdkModule(loaded)) {\n\t\tthrow new MessagingSdkLoadError(\n\t\t\t`The module at ${url} does not expose the Ada Messaging SDK interface.`,\n\t\t\t\"sdk_module_invalid\",\n\t\t);\n\t}\n\treturn loaded;\n}\n\n/**\n * Load the Ada Messaging SDK module from Ada's CDN.\n *\n * The returned promise is memoized per resolved URL: concurrent and repeated\n * calls share one in-flight import. A failed load clears the memo so a later\n * call retries. Rejections are always {@link MessagingSdkLoadError}.\n *\n * @example\n * const { createAdaEmbedInterface } = await loadMessagingSdk();\n */\nexport function loadMessagingSdk(\n\toptions: LoadMessagingSdkOptions = {},\n): Promise<MessagingSdkModule> {\n\tconst importModule = options.importModule ?? defaultImportModule;\n\tlet url: string;\n\ttry {\n\t\turl = resolveSdkBootstrapUrl(options.cdnBase, options.pinBuildSha);\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 = importSdkModule(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\n// Keyed by import seam like the module memo above: production memoizes on\n// the module-level default while each injected test seam gets an isolated\n// cache. `ada` is set when the promise resolves, so the stopped-interface\n// diagnostic can consult the resolved facade without wrapping it.\ninterface StartedInterfaceMemo {\n\tpromise: Promise<AdaMessagingInterface>;\n\tsettings: AdaMessagingStartOptions;\n\tada?: AdaMessagingInterface;\n}\n\nconst startedInterfaceByImporter = new WeakMap<\n\tImportSdkModule,\n\tStartedInterfaceMemo\n>();\n\n// Ungated like the runtime logger's dropped-host-value channel: both call\n// sites fire only on host misuse, never on an ordinary page load.\nfunction warnHost(message: string): void {\n\ttry {\n\t\t// biome-ignore lint/suspicious/noConsole: the loader's sanctioned host-page diagnostic sink\n\t\tconsole.warn(`[Ada] ${message}`);\n\t} catch {\n\t\t// A host may replace console methods; diagnostics must not throw.\n\t}\n}\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n\tif (typeof value !== \"object\" || value === null) {\n\t\treturn false;\n\t}\n\tconst prototype = Object.getPrototypeOf(value);\n\treturn prototype === Object.prototype || prototype === null;\n}\n\n// Structural equality over the settings surface. Two functions compare equal:\n// fresh inline callbacks on a re-run (React Strict Mode effects) are not a\n// settings change worth a warning. Exotic objects compare by reference.\nfunction settingsEqual(a: unknown, b: unknown): boolean {\n\tif (Object.is(a, b)) {\n\t\treturn true;\n\t}\n\tif (typeof a === \"function\" && typeof b === \"function\") {\n\t\treturn true;\n\t}\n\tif (Array.isArray(a) && Array.isArray(b)) {\n\t\treturn (\n\t\t\ta.length === b.length &&\n\t\t\ta.every((item, index) => settingsEqual(item, b[index]))\n\t\t);\n\t}\n\tif (isPlainObject(a) && isPlainObject(b)) {\n\t\tconst aKeys = Object.keys(a);\n\t\tconst bKeys = new Set(Object.keys(b));\n\t\treturn (\n\t\t\taKeys.length === bKeys.size &&\n\t\t\taKeys.every((key) => bKeys.has(key) && settingsEqual(a[key], b[key]))\n\t\t);\n\t}\n\treturn false;\n}\n\n// Observes the facade's symbol-keyed lifecycle getter (embed-lifecycle.ts)\n// instead of wrapping stop/start, so the customer's interface is never\n// mutated; a pinned runtime that predates the getter yields no flag and the\n// stopped-interface diagnostic is skipped rather than guessed.\nfunction isInterfaceStopped(ada: AdaMessagingInterface): boolean {\n\treturn readEmbedStoppedFlag(ada) === true;\n}\n\nasync function startAdaMessaging(\n\tsettings: AdaMessagingStartOptions,\n\toptions: LoadMessagingSdkOptions,\n): Promise<AdaMessagingInterface> {\n\tconst sdkModule = await loadMessagingSdk(options);\n\tconst ada = sdkModule.createAdaEmbedInterface();\n\tawait ada.start(settings);\n\treturn ada;\n}\n\n/**\n * Load the SDK, create the messaging interface, and start the widget —\n * the npm-first equivalent of the script-tag + `window.adaSettings` setup.\n *\n * One widget per page: the returned promise is memoized, so repeated and\n * concurrent calls resolve to the same started interface and the first\n * call's settings win. A failed load or start clears the memo so a later\n * call retries. Manage the running widget through the returned interface:\n * `stop()` tears it down and a later `start(settings)` on the same interface\n * runs again. Calling {@link loadAdaMessaging} after `stop()` returns the\n * same stopped interface without restarting it. Both memo outcomes log a\n * console warning: ignored differing settings, and a resolved interface\n * whose widget was stopped.\n *\n * Never assigns the interface to `window` or any other global: the returned\n * reference is the only handle, so hold it in module scope. A reference in\n * module scope is reachable only by your own code, never by other scripts on\n * the page.\n *\n * @example\n * // module scope: your code keeps the only reference\n * const ada = await loadAdaMessaging({\n * handle: \"my-bot\",\n * enableProgrammaticControl: true,\n * });\n * await ada.sendMessage(\"Hello\");\n */\nexport function loadAdaMessaging(\n\tsettings: AdaMessagingStartOptions,\n\toptions: LoadMessagingSdkOptions = {},\n): Promise<AdaMessagingInterface> {\n\tconst importModule = options.importModule ?? defaultImportModule;\n\tconst existing = startedInterfaceByImporter.get(importModule);\n\tif (existing) {\n\t\tif (!settingsEqual(settings, existing.settings)) {\n\t\t\twarnHost(\n\t\t\t\t\"loadAdaMessaging() was already called: the new settings are ignored and the existing interface is returned. To apply new settings, call stop() then start(settings) on it.\",\n\t\t\t);\n\t\t}\n\t\tif (existing.ada && isInterfaceStopped(existing.ada)) {\n\t\t\twarnHost(\n\t\t\t\t\"loadAdaMessaging() resolved an interface whose widget was stopped and never restarts it. Call start(settings) on it to run the widget again.\",\n\t\t\t);\n\t\t}\n\t\treturn existing.promise;\n\t}\n\tconst memo: StartedInterfaceMemo = {\n\t\tpromise: startAdaMessaging(settings, options),\n\t\tsettings,\n\t};\n\tstartedInterfaceByImporter.set(importModule, memo);\n\tmemo.promise.then(\n\t\t(ada) => {\n\t\t\tmemo.ada = ada;\n\t\t},\n\t\t() => {\n\t\t\tif (startedInterfaceByImporter.get(importModule) === memo) {\n\t\t\t\tstartedInterfaceByImporter.delete(importModule);\n\t\t\t}\n\t\t},\n\t);\n\treturn memo.promise;\n}\n"],"mappings":"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"}
|
package/dist/npm/testing.cjs
CHANGED
|
@@ -102,6 +102,8 @@ function createMockAdaMessaging(overrides = {}) {
|
|
|
102
102
|
setLanguage: async () => void 0,
|
|
103
103
|
setTheme: async () => void 0,
|
|
104
104
|
setDeviceToken: async () => void 0,
|
|
105
|
+
acknowledgeSessionMirrorClear: async () => void 0,
|
|
106
|
+
deliverSessionMirrorSeed: async () => void 0,
|
|
105
107
|
sendMessage: async (body) => {
|
|
106
108
|
const id = `mock-message-${nextMessageId++}`;
|
|
107
109
|
conversationId = conversationId ?? "mock-conversation";
|
package/dist/npm/testing.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"testing.cjs","names":[],"sources":["../../src/npm/testing.ts"],"sourcesContent":["import type {\n\tAdaSdkEventCallback,\n\tMetaFieldValue,\n\tPublicMessage,\n} from \"../client/client-types\";\nimport type { AdaMessagingInterface } from \"./loader\";\n\n/** One recorded method invocation on a {@link MockAdaMessaging}. */\nexport interface RecordedAdaMessagingCall {\n\tmethod: string;\n\targs: unknown[];\n}\n\n/**\n * The mock returned by {@link createMockAdaMessaging}: the full\n * {@link AdaMessagingInterface} plus test hooks for inspecting calls and\n * simulating SDK events.\n */\nexport interface MockAdaMessaging extends AdaMessagingInterface {\n\t/** Every method invocation, in call order. */\n\tcalls: RecordedAdaMessagingCall[];\n\t/**\n\t * Deliver an event to `subscribeEvent` and `subscribeAll` callbacks, as if\n\t * the SDK emitted it (e.g. `$emit(\"ada:ready\", { isRolledOut: true })`).\n\t * Routing matches the real SDK: keyed subscriptions match by prefix\n\t * (`\"ada:campaigns\"` catches `\"ada:campaigns:shown\"`), except the\n\t * transcript events `ada:message:sent`/`ada:message:received`, which reach\n\t * exact-key subscribers only and never `subscribeAll`.\n\t */\n\t$emit: (key: string, data?: unknown) => void;\n}\n\ninterface KeyedSubscription {\n\tkey: string;\n\tcallback: AdaSdkEventCallback;\n}\n\n// Mirrors EXACT_MATCH_EVENT_KEYS in client/client-host-runtime.ts: transcript\n// events reach exact-key subscribers only, never prefix or subscribeAll\n// subscribers. Duplicated (not imported) so the published testing entry stays\n// free of runtime modules; a drift test pins the mock against the real matcher.\nconst EXACT_MATCH_EVENT_KEYS = new Set([\n\t\"ada:message:sent\",\n\t\"ada:message:received\",\n]);\n\nfunction keyedSubscriptionMatches(\n\tsubscriptionKey: string,\n\teventKey: string,\n): boolean {\n\tif (EXACT_MATCH_EVENT_KEYS.has(eventKey)) {\n\t\treturn subscriptionKey === eventKey;\n\t}\n\t// Raw prefix match, same as the real SDK: \"ada:campaigns\" also catches\n\t// \"ada:campaigns:shown\" and underscore-suffixed keys.\n\treturn subscriptionKey === eventKey || eventKey.startsWith(subscriptionKey);\n}\n\nfunction toMetaFieldRecord(\n\tfields: Record<string, unknown>,\n): Record<string, MetaFieldValue> {\n\tconst record: Record<string, MetaFieldValue> = {};\n\tfor (const [key, value] of Object.entries(fields)) {\n\t\tif (\n\t\t\ttypeof value === \"string\" ||\n\t\t\ttypeof value === \"number\" ||\n\t\t\ttypeof value === \"boolean\" ||\n\t\t\tvalue === null\n\t\t) {\n\t\t\trecord[key] = value;\n\t\t}\n\t}\n\treturn record;\n}\n\n/**\n * Create an in-memory messaging-interface stand-in for unit-testing an\n * integration without loading the SDK from the CDN.\n *\n * Every method resolves, records itself in `calls`, and keeps just enough\n * state to behave like the real interface: `sendMessage` appends a\n * programmatic user message that `getMessages` returns, `open`/`close`/\n * `toggle` drive `isOpen` and `getInfo`, `setMetaFields` feeds\n * `getMetaFields` (sensitive fields are never returned, matching the real\n * SDK), and `$emit` fans out to `subscribeEvent`/`subscribeAll` callbacks.\n *\n * Pass `overrides` to replace any method (e.g. with `vi.fn()`); overridden\n * methods are still recorded in `calls`.\n */\nexport function createMockAdaMessaging(\n\toverrides: Partial<AdaMessagingInterface> = {},\n): MockAdaMessaging {\n\tconst calls: RecordedAdaMessagingCall[] = [];\n\tlet nextSubscriptionId = 1;\n\tlet nextMessageId = 1;\n\tlet chatOpen = false;\n\tlet conversationId: string | null = null;\n\tconst metaFields: Record<string, MetaFieldValue> = {};\n\tconst messages: PublicMessage[] = [];\n\tconst keyedSubscriptions = new Map<number, KeyedSubscription>();\n\tconst wildcardSubscriptions = new Map<\n\t\tnumber,\n\t\t(key: string, data: unknown) => void\n\t>();\n\n\tconst emit = (key: string, data?: unknown): void => {\n\t\tfor (const subscription of keyedSubscriptions.values()) {\n\t\t\tif (keyedSubscriptionMatches(subscription.key, key)) {\n\t\t\t\tsubscription.callback(data, { eventKey: key });\n\t\t\t}\n\t\t}\n\t\tif (EXACT_MATCH_EVENT_KEYS.has(key)) {\n\t\t\treturn;\n\t\t}\n\t\tfor (const callback of wildcardSubscriptions.values()) {\n\t\t\tcallback(key, data);\n\t\t}\n\t};\n\n\tconst clearTranscript = (): void => {\n\t\tmessages.length = 0;\n\t\tconversationId = null;\n\t};\n\n\tconst base: AdaMessagingInterface = {\n\t\tstart: async () => undefined,\n\t\tstop: async () => undefined,\n\t\ttoggle: async () => {\n\t\t\tchatOpen = !chatOpen;\n\t\t},\n\t\tdeleteHistory: async () => {\n\t\t\tclearTranscript();\n\t\t},\n\t\treset: async () => {\n\t\t\tclearTranscript();\n\t\t},\n\t\tsetMetaFields: async (fields) => {\n\t\t\tObject.assign(metaFields, toMetaFieldRecord(fields));\n\t\t},\n\t\t// Sensitive fields are accepted but never readable back, matching the\n\t\t// real SDK's getMetaFields contract.\n\t\tsetSensitiveMetaFields: async () => undefined,\n\t\tsetTestModeBanner: async () => undefined,\n\t\tgetInfo: async () => ({\n\t\t\tisChatOpen: chatOpen,\n\t\t\tisDrawerOpen: chatOpen,\n\t\t\thasActiveChatter: messages.length > 0,\n\t\t\thasClosedChat: false,\n\t\t}),\n\t\tgetMetaFields: async () => ({ ...metaFields }),\n\t\tisOpen: async () => chatOpen,\n\t\tgetConversation: async () => ({\n\t\t\tid: conversationId,\n\t\t\thandoff: { inLiveChat: false },\n\t\t}),\n\t\tgetMessages: async () => [...messages],\n\t\tsetComposerText: async () => undefined,\n\t\tsetDelegate: () => undefined,\n\t\ttrackEvent: async () => undefined,\n\t\tsubscribeEvent: async (key, callback) => {\n\t\t\tconst id = nextSubscriptionId++;\n\t\t\tkeyedSubscriptions.set(id, { key, callback });\n\t\t\treturn id;\n\t\t},\n\t\tunsubscribeEvent: async (id) => {\n\t\t\tkeyedSubscriptions.delete(id);\n\t\t\twildcardSubscriptions.delete(id);\n\t\t},\n\t\tsubscribeAll: async (callback) => {\n\t\t\tconst id = nextSubscriptionId++;\n\t\t\twildcardSubscriptions.set(id, callback);\n\t\t\treturn id;\n\t\t},\n\t\topen: async () => {\n\t\t\tchatOpen = true;\n\t\t},\n\t\tclose: async () => {\n\t\t\tchatOpen = false;\n\t\t},\n\t\tsetLanguage: async () => undefined,\n\t\tsetTheme: async () => undefined,\n\t\tsetDeviceToken: async () => undefined,\n\t\tsendMessage: async (body) => {\n\t\t\tconst id = `mock-message-${nextMessageId++}`;\n\t\t\tconversationId = conversationId ?? \"mock-conversation\";\n\t\t\tmessages.push({\n\t\t\t\tid,\n\t\t\t\trole: \"user\",\n\t\t\t\ttype: \"text\",\n\t\t\t\tbody,\n\t\t\t\tcreatedAt: Math.floor(Date.now() / 1000),\n\t\t\t\torigin: \"programmatic\",\n\t\t\t});\n\t\t\treturn { id };\n\t\t},\n\t\thandleNotification: async () => {\n\t\t\tchatOpen = true;\n\t\t},\n\t\trequestNotifications: async () => \"granted\",\n\t\tcreateProactive: async () => undefined,\n\t\ttriggerCampaign: async () => undefined,\n\t\tevaluateCampaignConditions: async () => undefined,\n\t\tcloseCampaign: async () => undefined,\n\t\ttriggerAnswer: async () => undefined,\n\t\ttriggerProactive: async () => undefined,\n\t\ttriggerGreeting: async () => undefined,\n\t\tdestroy: async () => {\n\t\t\tkeyedSubscriptions.clear();\n\t\t\twildcardSubscriptions.clear();\n\t\t\tchatOpen = false;\n\t\t},\n\t};\n\n\tconst definedOverrides = Object.fromEntries(\n\t\tObject.entries(overrides).filter(([, value]) => value !== undefined),\n\t);\n\tconst implementation = { ...base, ...definedOverrides };\n\tconst recorded = Object.fromEntries(\n\t\tObject.entries(implementation).map(([method, fn]) => [\n\t\t\tmethod,\n\t\t\t(...args: unknown[]) => {\n\t\t\t\tcalls.push({ method, args });\n\t\t\t\treturn (fn as (...callArgs: unknown[]) => unknown)(...args);\n\t\t\t},\n\t\t]),\n\t) as unknown as AdaMessagingInterface;\n\n\treturn { ...recorded, calls, $emit: emit };\n}\n"],"mappings":";;AAyCA,IAAM,yBAAyB,IAAI,IAAI,CACtC,oBACA,uBACA,CAAC;AAEF,SAAS,yBACR,iBACA,UACU;AACV,KAAI,uBAAuB,IAAI,SAAS,CACvC,QAAO,oBAAoB;AAI5B,QAAO,oBAAoB,YAAY,SAAS,WAAW,gBAAgB;;AAG5E,SAAS,kBACR,QACiC;CACjC,MAAM,SAAyC,EAAE;AACjD,MAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,OAAO,CAChD,KACC,OAAO,UAAU,YACjB,OAAO,UAAU,YACjB,OAAO,UAAU,aACjB,UAAU,KAEV,QAAO,OAAO;AAGhB,QAAO;;;;;;;;;;;;;;;;AAiBR,SAAgB,uBACf,YAA4C,EAAE,EAC3B;CACnB,MAAM,QAAoC,EAAE;CAC5C,IAAI,qBAAqB;CACzB,IAAI,gBAAgB;CACpB,IAAI,WAAW;CACf,IAAI,iBAAgC;CACpC,MAAM,aAA6C,EAAE;CACrD,MAAM,WAA4B,EAAE;CACpC,MAAM,qCAAqB,IAAI,KAAgC;CAC/D,MAAM,wCAAwB,IAAI,KAG/B;CAEH,MAAM,QAAQ,KAAa,SAAyB;AACnD,OAAK,MAAM,gBAAgB,mBAAmB,QAAQ,CACrD,KAAI,yBAAyB,aAAa,KAAK,IAAI,CAClD,cAAa,SAAS,MAAM,EAAE,UAAU,KAAK,CAAC;AAGhD,MAAI,uBAAuB,IAAI,IAAI,CAClC;AAED,OAAK,MAAM,YAAY,sBAAsB,QAAQ,CACpD,UAAS,KAAK,KAAK;;CAIrB,MAAM,wBAA8B;AACnC,WAAS,SAAS;AAClB,mBAAiB;;CAGlB,MAAM,OAA8B;EACnC,OAAO,YAAY,KAAA;EACnB,MAAM,YAAY,KAAA;EAClB,QAAQ,YAAY;AACnB,cAAW,CAAC;;EAEb,eAAe,YAAY;AAC1B,oBAAiB;;EAElB,OAAO,YAAY;AAClB,oBAAiB;;EAElB,eAAe,OAAO,WAAW;AAChC,UAAO,OAAO,YAAY,kBAAkB,OAAO,CAAC;;EAIrD,wBAAwB,YAAY,KAAA;EACpC,mBAAmB,YAAY,KAAA;EAC/B,SAAS,aAAa;GACrB,YAAY;GACZ,cAAc;GACd,kBAAkB,SAAS,SAAS;GACpC,eAAe;GACf;EACD,eAAe,aAAa,EAAE,GAAG,YAAY;EAC7C,QAAQ,YAAY;EACpB,iBAAiB,aAAa;GAC7B,IAAI;GACJ,SAAS,EAAE,YAAY,OAAO;GAC9B;EACD,aAAa,YAAY,CAAC,GAAG,SAAS;EACtC,iBAAiB,YAAY,KAAA;EAC7B,mBAAmB,KAAA;EACnB,YAAY,YAAY,KAAA;EACxB,gBAAgB,OAAO,KAAK,aAAa;GACxC,MAAM,KAAK;AACX,sBAAmB,IAAI,IAAI;IAAE;IAAK;IAAU,CAAC;AAC7C,UAAO;;EAER,kBAAkB,OAAO,OAAO;AAC/B,sBAAmB,OAAO,GAAG;AAC7B,yBAAsB,OAAO,GAAG;;EAEjC,cAAc,OAAO,aAAa;GACjC,MAAM,KAAK;AACX,yBAAsB,IAAI,IAAI,SAAS;AACvC,UAAO;;EAER,MAAM,YAAY;AACjB,cAAW;;EAEZ,OAAO,YAAY;AAClB,cAAW;;EAEZ,aAAa,YAAY,KAAA;EACzB,UAAU,YAAY,KAAA;EACtB,gBAAgB,YAAY,KAAA;EAC5B,aAAa,OAAO,SAAS;GAC5B,MAAM,KAAK,gBAAgB;AAC3B,oBAAiB,kBAAkB;AACnC,YAAS,KAAK;IACb;IACA,MAAM;IACN,MAAM;IACN;IACA,WAAW,KAAK,MAAM,KAAK,KAAK,GAAG,IAAK;IACxC,QAAQ;IACR,CAAC;AACF,UAAO,EAAE,IAAI;;EAEd,oBAAoB,YAAY;AAC/B,cAAW;;EAEZ,sBAAsB,YAAY;EAClC,iBAAiB,YAAY,KAAA;EAC7B,iBAAiB,YAAY,KAAA;EAC7B,4BAA4B,YAAY,KAAA;EACxC,eAAe,YAAY,KAAA;EAC3B,eAAe,YAAY,KAAA;EAC3B,kBAAkB,YAAY,KAAA;EAC9B,iBAAiB,YAAY,KAAA;EAC7B,SAAS,YAAY;AACpB,sBAAmB,OAAO;AAC1B,yBAAsB,OAAO;AAC7B,cAAW;;EAEZ;CAED,MAAM,mBAAmB,OAAO,YAC/B,OAAO,QAAQ,UAAU,CAAC,QAAQ,GAAG,WAAW,UAAU,KAAA,EAAU,CACpE;CACD,MAAM,iBAAiB;EAAE,GAAG;EAAM,GAAG;EAAkB;AAWvD,QAAO;EAAE,GAVQ,OAAO,YACvB,OAAO,QAAQ,eAAe,CAAC,KAAK,CAAC,QAAQ,QAAQ,CACpD,SACC,GAAG,SAAoB;AACvB,SAAM,KAAK;IAAE;IAAQ;IAAM,CAAC;AAC5B,UAAQ,GAA2C,GAAG,KAAK;IAE5D,CAAC,CACF;EAEqB;EAAO,OAAO;EAAM"}
|
|
1
|
+
{"version":3,"file":"testing.cjs","names":[],"sources":["../../src/npm/testing.ts"],"sourcesContent":["import type {\n\tAdaSdkEventCallback,\n\tMetaFieldValue,\n\tPublicMessage,\n} from \"../client/client-types\";\nimport type { AdaMessagingInterface } from \"./loader\";\n\n/** One recorded method invocation on a {@link MockAdaMessaging}. */\nexport interface RecordedAdaMessagingCall {\n\tmethod: string;\n\targs: unknown[];\n}\n\n/**\n * The mock returned by {@link createMockAdaMessaging}: the full\n * {@link AdaMessagingInterface} plus test hooks for inspecting calls and\n * simulating SDK events.\n */\nexport interface MockAdaMessaging extends AdaMessagingInterface {\n\t/** Every method invocation, in call order. */\n\tcalls: RecordedAdaMessagingCall[];\n\t/**\n\t * Deliver an event to `subscribeEvent` and `subscribeAll` callbacks, as if\n\t * the SDK emitted it (e.g. `$emit(\"ada:ready\", { isRolledOut: true })`).\n\t * Routing matches the real SDK: keyed subscriptions match by prefix\n\t * (`\"ada:campaigns\"` catches `\"ada:campaigns:shown\"`), except the\n\t * transcript events `ada:message:sent`/`ada:message:received`, which reach\n\t * exact-key subscribers only and never `subscribeAll`.\n\t */\n\t$emit: (key: string, data?: unknown) => void;\n}\n\ninterface KeyedSubscription {\n\tkey: string;\n\tcallback: AdaSdkEventCallback;\n}\n\n// Mirrors EXACT_MATCH_EVENT_KEYS in client/client-host-runtime.ts: transcript\n// events reach exact-key subscribers only, never prefix or subscribeAll\n// subscribers. Duplicated (not imported) so the published testing entry stays\n// free of runtime modules; a drift test pins the mock against the real matcher.\nconst EXACT_MATCH_EVENT_KEYS = new Set([\n\t\"ada:message:sent\",\n\t\"ada:message:received\",\n]);\n\nfunction keyedSubscriptionMatches(\n\tsubscriptionKey: string,\n\teventKey: string,\n): boolean {\n\tif (EXACT_MATCH_EVENT_KEYS.has(eventKey)) {\n\t\treturn subscriptionKey === eventKey;\n\t}\n\t// Raw prefix match, same as the real SDK: \"ada:campaigns\" also catches\n\t// \"ada:campaigns:shown\" and underscore-suffixed keys.\n\treturn subscriptionKey === eventKey || eventKey.startsWith(subscriptionKey);\n}\n\nfunction toMetaFieldRecord(\n\tfields: Record<string, unknown>,\n): Record<string, MetaFieldValue> {\n\tconst record: Record<string, MetaFieldValue> = {};\n\tfor (const [key, value] of Object.entries(fields)) {\n\t\tif (\n\t\t\ttypeof value === \"string\" ||\n\t\t\ttypeof value === \"number\" ||\n\t\t\ttypeof value === \"boolean\" ||\n\t\t\tvalue === null\n\t\t) {\n\t\t\trecord[key] = value;\n\t\t}\n\t}\n\treturn record;\n}\n\n/**\n * Create an in-memory messaging-interface stand-in for unit-testing an\n * integration without loading the SDK from the CDN.\n *\n * Every method resolves, records itself in `calls`, and keeps just enough\n * state to behave like the real interface: `sendMessage` appends a\n * programmatic user message that `getMessages` returns, `open`/`close`/\n * `toggle` drive `isOpen` and `getInfo`, `setMetaFields` feeds\n * `getMetaFields` (sensitive fields are never returned, matching the real\n * SDK), and `$emit` fans out to `subscribeEvent`/`subscribeAll` callbacks.\n *\n * Pass `overrides` to replace any method (e.g. with `vi.fn()`); overridden\n * methods are still recorded in `calls`.\n */\nexport function createMockAdaMessaging(\n\toverrides: Partial<AdaMessagingInterface> = {},\n): MockAdaMessaging {\n\tconst calls: RecordedAdaMessagingCall[] = [];\n\tlet nextSubscriptionId = 1;\n\tlet nextMessageId = 1;\n\tlet chatOpen = false;\n\tlet conversationId: string | null = null;\n\tconst metaFields: Record<string, MetaFieldValue> = {};\n\tconst messages: PublicMessage[] = [];\n\tconst keyedSubscriptions = new Map<number, KeyedSubscription>();\n\tconst wildcardSubscriptions = new Map<\n\t\tnumber,\n\t\t(key: string, data: unknown) => void\n\t>();\n\n\tconst emit = (key: string, data?: unknown): void => {\n\t\tfor (const subscription of keyedSubscriptions.values()) {\n\t\t\tif (keyedSubscriptionMatches(subscription.key, key)) {\n\t\t\t\tsubscription.callback(data, { eventKey: key });\n\t\t\t}\n\t\t}\n\t\tif (EXACT_MATCH_EVENT_KEYS.has(key)) {\n\t\t\treturn;\n\t\t}\n\t\tfor (const callback of wildcardSubscriptions.values()) {\n\t\t\tcallback(key, data);\n\t\t}\n\t};\n\n\tconst clearTranscript = (): void => {\n\t\tmessages.length = 0;\n\t\tconversationId = null;\n\t};\n\n\tconst base: AdaMessagingInterface = {\n\t\tstart: async () => undefined,\n\t\tstop: async () => undefined,\n\t\ttoggle: async () => {\n\t\t\tchatOpen = !chatOpen;\n\t\t},\n\t\tdeleteHistory: async () => {\n\t\t\tclearTranscript();\n\t\t},\n\t\treset: async () => {\n\t\t\tclearTranscript();\n\t\t},\n\t\tsetMetaFields: async (fields) => {\n\t\t\tObject.assign(metaFields, toMetaFieldRecord(fields));\n\t\t},\n\t\t// Sensitive fields are accepted but never readable back, matching the\n\t\t// real SDK's getMetaFields contract.\n\t\tsetSensitiveMetaFields: async () => undefined,\n\t\tsetTestModeBanner: async () => undefined,\n\t\tgetInfo: async () => ({\n\t\t\tisChatOpen: chatOpen,\n\t\t\tisDrawerOpen: chatOpen,\n\t\t\thasActiveChatter: messages.length > 0,\n\t\t\thasClosedChat: false,\n\t\t}),\n\t\tgetMetaFields: async () => ({ ...metaFields }),\n\t\tisOpen: async () => chatOpen,\n\t\tgetConversation: async () => ({\n\t\t\tid: conversationId,\n\t\t\thandoff: { inLiveChat: false },\n\t\t}),\n\t\tgetMessages: async () => [...messages],\n\t\tsetComposerText: async () => undefined,\n\t\tsetDelegate: () => undefined,\n\t\ttrackEvent: async () => undefined,\n\t\tsubscribeEvent: async (key, callback) => {\n\t\t\tconst id = nextSubscriptionId++;\n\t\t\tkeyedSubscriptions.set(id, { key, callback });\n\t\t\treturn id;\n\t\t},\n\t\tunsubscribeEvent: async (id) => {\n\t\t\tkeyedSubscriptions.delete(id);\n\t\t\twildcardSubscriptions.delete(id);\n\t\t},\n\t\tsubscribeAll: async (callback) => {\n\t\t\tconst id = nextSubscriptionId++;\n\t\t\twildcardSubscriptions.set(id, callback);\n\t\t\treturn id;\n\t\t},\n\t\topen: async () => {\n\t\t\tchatOpen = true;\n\t\t},\n\t\tclose: async () => {\n\t\t\tchatOpen = false;\n\t\t},\n\t\tsetLanguage: async () => undefined,\n\t\tsetTheme: async () => undefined,\n\t\tsetDeviceToken: async () => undefined,\n\t\t// Optional on AdaEmbedInterface (internal mobile-webview plumbing), but\n\t\t// Ada's own maintained test double still implements the full surface — the\n\t\t// types-drift guard requires the shipped mock to cover every interface\n\t\t// member so a customer's double never lags it.\n\t\tacknowledgeSessionMirrorClear: async () => undefined,\n\t\tdeliverSessionMirrorSeed: async () => undefined,\n\t\tsendMessage: async (body) => {\n\t\t\tconst id = `mock-message-${nextMessageId++}`;\n\t\t\tconversationId = conversationId ?? \"mock-conversation\";\n\t\t\tmessages.push({\n\t\t\t\tid,\n\t\t\t\trole: \"user\",\n\t\t\t\ttype: \"text\",\n\t\t\t\tbody,\n\t\t\t\tcreatedAt: Math.floor(Date.now() / 1000),\n\t\t\t\torigin: \"programmatic\",\n\t\t\t});\n\t\t\treturn { id };\n\t\t},\n\t\thandleNotification: async () => {\n\t\t\tchatOpen = true;\n\t\t},\n\t\trequestNotifications: async () => \"granted\",\n\t\tcreateProactive: async () => undefined,\n\t\ttriggerCampaign: async () => undefined,\n\t\tevaluateCampaignConditions: async () => undefined,\n\t\tcloseCampaign: async () => undefined,\n\t\ttriggerAnswer: async () => undefined,\n\t\ttriggerProactive: async () => undefined,\n\t\ttriggerGreeting: async () => undefined,\n\t\tdestroy: async () => {\n\t\t\tkeyedSubscriptions.clear();\n\t\t\twildcardSubscriptions.clear();\n\t\t\tchatOpen = false;\n\t\t},\n\t};\n\n\tconst definedOverrides = Object.fromEntries(\n\t\tObject.entries(overrides).filter(([, value]) => value !== undefined),\n\t);\n\tconst implementation = { ...base, ...definedOverrides };\n\tconst recorded = Object.fromEntries(\n\t\tObject.entries(implementation).map(([method, fn]) => [\n\t\t\tmethod,\n\t\t\t(...args: unknown[]) => {\n\t\t\t\tcalls.push({ method, args });\n\t\t\t\treturn (fn as (...callArgs: unknown[]) => unknown)(...args);\n\t\t\t},\n\t\t]),\n\t) as unknown as AdaMessagingInterface;\n\n\treturn { ...recorded, calls, $emit: emit };\n}\n"],"mappings":"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"}
|
package/dist/npm/testing.js
CHANGED
|
@@ -101,6 +101,8 @@ function createMockAdaMessaging(overrides = {}) {
|
|
|
101
101
|
setLanguage: async () => void 0,
|
|
102
102
|
setTheme: async () => void 0,
|
|
103
103
|
setDeviceToken: async () => void 0,
|
|
104
|
+
acknowledgeSessionMirrorClear: async () => void 0,
|
|
105
|
+
deliverSessionMirrorSeed: async () => void 0,
|
|
104
106
|
sendMessage: async (body) => {
|
|
105
107
|
const id = `mock-message-${nextMessageId++}`;
|
|
106
108
|
conversationId = conversationId ?? "mock-conversation";
|
package/dist/npm/testing.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"testing.js","names":[],"sources":["../../src/npm/testing.ts"],"sourcesContent":["import type {\n\tAdaSdkEventCallback,\n\tMetaFieldValue,\n\tPublicMessage,\n} from \"../client/client-types\";\nimport type { AdaMessagingInterface } from \"./loader\";\n\n/** One recorded method invocation on a {@link MockAdaMessaging}. */\nexport interface RecordedAdaMessagingCall {\n\tmethod: string;\n\targs: unknown[];\n}\n\n/**\n * The mock returned by {@link createMockAdaMessaging}: the full\n * {@link AdaMessagingInterface} plus test hooks for inspecting calls and\n * simulating SDK events.\n */\nexport interface MockAdaMessaging extends AdaMessagingInterface {\n\t/** Every method invocation, in call order. */\n\tcalls: RecordedAdaMessagingCall[];\n\t/**\n\t * Deliver an event to `subscribeEvent` and `subscribeAll` callbacks, as if\n\t * the SDK emitted it (e.g. `$emit(\"ada:ready\", { isRolledOut: true })`).\n\t * Routing matches the real SDK: keyed subscriptions match by prefix\n\t * (`\"ada:campaigns\"` catches `\"ada:campaigns:shown\"`), except the\n\t * transcript events `ada:message:sent`/`ada:message:received`, which reach\n\t * exact-key subscribers only and never `subscribeAll`.\n\t */\n\t$emit: (key: string, data?: unknown) => void;\n}\n\ninterface KeyedSubscription {\n\tkey: string;\n\tcallback: AdaSdkEventCallback;\n}\n\n// Mirrors EXACT_MATCH_EVENT_KEYS in client/client-host-runtime.ts: transcript\n// events reach exact-key subscribers only, never prefix or subscribeAll\n// subscribers. Duplicated (not imported) so the published testing entry stays\n// free of runtime modules; a drift test pins the mock against the real matcher.\nconst EXACT_MATCH_EVENT_KEYS = new Set([\n\t\"ada:message:sent\",\n\t\"ada:message:received\",\n]);\n\nfunction keyedSubscriptionMatches(\n\tsubscriptionKey: string,\n\teventKey: string,\n): boolean {\n\tif (EXACT_MATCH_EVENT_KEYS.has(eventKey)) {\n\t\treturn subscriptionKey === eventKey;\n\t}\n\t// Raw prefix match, same as the real SDK: \"ada:campaigns\" also catches\n\t// \"ada:campaigns:shown\" and underscore-suffixed keys.\n\treturn subscriptionKey === eventKey || eventKey.startsWith(subscriptionKey);\n}\n\nfunction toMetaFieldRecord(\n\tfields: Record<string, unknown>,\n): Record<string, MetaFieldValue> {\n\tconst record: Record<string, MetaFieldValue> = {};\n\tfor (const [key, value] of Object.entries(fields)) {\n\t\tif (\n\t\t\ttypeof value === \"string\" ||\n\t\t\ttypeof value === \"number\" ||\n\t\t\ttypeof value === \"boolean\" ||\n\t\t\tvalue === null\n\t\t) {\n\t\t\trecord[key] = value;\n\t\t}\n\t}\n\treturn record;\n}\n\n/**\n * Create an in-memory messaging-interface stand-in for unit-testing an\n * integration without loading the SDK from the CDN.\n *\n * Every method resolves, records itself in `calls`, and keeps just enough\n * state to behave like the real interface: `sendMessage` appends a\n * programmatic user message that `getMessages` returns, `open`/`close`/\n * `toggle` drive `isOpen` and `getInfo`, `setMetaFields` feeds\n * `getMetaFields` (sensitive fields are never returned, matching the real\n * SDK), and `$emit` fans out to `subscribeEvent`/`subscribeAll` callbacks.\n *\n * Pass `overrides` to replace any method (e.g. with `vi.fn()`); overridden\n * methods are still recorded in `calls`.\n */\nexport function createMockAdaMessaging(\n\toverrides: Partial<AdaMessagingInterface> = {},\n): MockAdaMessaging {\n\tconst calls: RecordedAdaMessagingCall[] = [];\n\tlet nextSubscriptionId = 1;\n\tlet nextMessageId = 1;\n\tlet chatOpen = false;\n\tlet conversationId: string | null = null;\n\tconst metaFields: Record<string, MetaFieldValue> = {};\n\tconst messages: PublicMessage[] = [];\n\tconst keyedSubscriptions = new Map<number, KeyedSubscription>();\n\tconst wildcardSubscriptions = new Map<\n\t\tnumber,\n\t\t(key: string, data: unknown) => void\n\t>();\n\n\tconst emit = (key: string, data?: unknown): void => {\n\t\tfor (const subscription of keyedSubscriptions.values()) {\n\t\t\tif (keyedSubscriptionMatches(subscription.key, key)) {\n\t\t\t\tsubscription.callback(data, { eventKey: key });\n\t\t\t}\n\t\t}\n\t\tif (EXACT_MATCH_EVENT_KEYS.has(key)) {\n\t\t\treturn;\n\t\t}\n\t\tfor (const callback of wildcardSubscriptions.values()) {\n\t\t\tcallback(key, data);\n\t\t}\n\t};\n\n\tconst clearTranscript = (): void => {\n\t\tmessages.length = 0;\n\t\tconversationId = null;\n\t};\n\n\tconst base: AdaMessagingInterface = {\n\t\tstart: async () => undefined,\n\t\tstop: async () => undefined,\n\t\ttoggle: async () => {\n\t\t\tchatOpen = !chatOpen;\n\t\t},\n\t\tdeleteHistory: async () => {\n\t\t\tclearTranscript();\n\t\t},\n\t\treset: async () => {\n\t\t\tclearTranscript();\n\t\t},\n\t\tsetMetaFields: async (fields) => {\n\t\t\tObject.assign(metaFields, toMetaFieldRecord(fields));\n\t\t},\n\t\t// Sensitive fields are accepted but never readable back, matching the\n\t\t// real SDK's getMetaFields contract.\n\t\tsetSensitiveMetaFields: async () => undefined,\n\t\tsetTestModeBanner: async () => undefined,\n\t\tgetInfo: async () => ({\n\t\t\tisChatOpen: chatOpen,\n\t\t\tisDrawerOpen: chatOpen,\n\t\t\thasActiveChatter: messages.length > 0,\n\t\t\thasClosedChat: false,\n\t\t}),\n\t\tgetMetaFields: async () => ({ ...metaFields }),\n\t\tisOpen: async () => chatOpen,\n\t\tgetConversation: async () => ({\n\t\t\tid: conversationId,\n\t\t\thandoff: { inLiveChat: false },\n\t\t}),\n\t\tgetMessages: async () => [...messages],\n\t\tsetComposerText: async () => undefined,\n\t\tsetDelegate: () => undefined,\n\t\ttrackEvent: async () => undefined,\n\t\tsubscribeEvent: async (key, callback) => {\n\t\t\tconst id = nextSubscriptionId++;\n\t\t\tkeyedSubscriptions.set(id, { key, callback });\n\t\t\treturn id;\n\t\t},\n\t\tunsubscribeEvent: async (id) => {\n\t\t\tkeyedSubscriptions.delete(id);\n\t\t\twildcardSubscriptions.delete(id);\n\t\t},\n\t\tsubscribeAll: async (callback) => {\n\t\t\tconst id = nextSubscriptionId++;\n\t\t\twildcardSubscriptions.set(id, callback);\n\t\t\treturn id;\n\t\t},\n\t\topen: async () => {\n\t\t\tchatOpen = true;\n\t\t},\n\t\tclose: async () => {\n\t\t\tchatOpen = false;\n\t\t},\n\t\tsetLanguage: async () => undefined,\n\t\tsetTheme: async () => undefined,\n\t\tsetDeviceToken: async () => undefined,\n\t\tsendMessage: async (body) => {\n\t\t\tconst id = `mock-message-${nextMessageId++}`;\n\t\t\tconversationId = conversationId ?? \"mock-conversation\";\n\t\t\tmessages.push({\n\t\t\t\tid,\n\t\t\t\trole: \"user\",\n\t\t\t\ttype: \"text\",\n\t\t\t\tbody,\n\t\t\t\tcreatedAt: Math.floor(Date.now() / 1000),\n\t\t\t\torigin: \"programmatic\",\n\t\t\t});\n\t\t\treturn { id };\n\t\t},\n\t\thandleNotification: async () => {\n\t\t\tchatOpen = true;\n\t\t},\n\t\trequestNotifications: async () => \"granted\",\n\t\tcreateProactive: async () => undefined,\n\t\ttriggerCampaign: async () => undefined,\n\t\tevaluateCampaignConditions: async () => undefined,\n\t\tcloseCampaign: async () => undefined,\n\t\ttriggerAnswer: async () => undefined,\n\t\ttriggerProactive: async () => undefined,\n\t\ttriggerGreeting: async () => undefined,\n\t\tdestroy: async () => {\n\t\t\tkeyedSubscriptions.clear();\n\t\t\twildcardSubscriptions.clear();\n\t\t\tchatOpen = false;\n\t\t},\n\t};\n\n\tconst definedOverrides = Object.fromEntries(\n\t\tObject.entries(overrides).filter(([, value]) => value !== undefined),\n\t);\n\tconst implementation = { ...base, ...definedOverrides };\n\tconst recorded = Object.fromEntries(\n\t\tObject.entries(implementation).map(([method, fn]) => [\n\t\t\tmethod,\n\t\t\t(...args: unknown[]) => {\n\t\t\t\tcalls.push({ method, args });\n\t\t\t\treturn (fn as (...callArgs: unknown[]) => unknown)(...args);\n\t\t\t},\n\t\t]),\n\t) as unknown as AdaMessagingInterface;\n\n\treturn { ...recorded, calls, $emit: emit };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"testing.js","names":[],"sources":["../../src/npm/testing.ts"],"sourcesContent":["import type {\n\tAdaSdkEventCallback,\n\tMetaFieldValue,\n\tPublicMessage,\n} from \"../client/client-types\";\nimport type { AdaMessagingInterface } from \"./loader\";\n\n/** One recorded method invocation on a {@link MockAdaMessaging}. */\nexport interface RecordedAdaMessagingCall {\n\tmethod: string;\n\targs: unknown[];\n}\n\n/**\n * The mock returned by {@link createMockAdaMessaging}: the full\n * {@link AdaMessagingInterface} plus test hooks for inspecting calls and\n * simulating SDK events.\n */\nexport interface MockAdaMessaging extends AdaMessagingInterface {\n\t/** Every method invocation, in call order. */\n\tcalls: RecordedAdaMessagingCall[];\n\t/**\n\t * Deliver an event to `subscribeEvent` and `subscribeAll` callbacks, as if\n\t * the SDK emitted it (e.g. `$emit(\"ada:ready\", { isRolledOut: true })`).\n\t * Routing matches the real SDK: keyed subscriptions match by prefix\n\t * (`\"ada:campaigns\"` catches `\"ada:campaigns:shown\"`), except the\n\t * transcript events `ada:message:sent`/`ada:message:received`, which reach\n\t * exact-key subscribers only and never `subscribeAll`.\n\t */\n\t$emit: (key: string, data?: unknown) => void;\n}\n\ninterface KeyedSubscription {\n\tkey: string;\n\tcallback: AdaSdkEventCallback;\n}\n\n// Mirrors EXACT_MATCH_EVENT_KEYS in client/client-host-runtime.ts: transcript\n// events reach exact-key subscribers only, never prefix or subscribeAll\n// subscribers. Duplicated (not imported) so the published testing entry stays\n// free of runtime modules; a drift test pins the mock against the real matcher.\nconst EXACT_MATCH_EVENT_KEYS = new Set([\n\t\"ada:message:sent\",\n\t\"ada:message:received\",\n]);\n\nfunction keyedSubscriptionMatches(\n\tsubscriptionKey: string,\n\teventKey: string,\n): boolean {\n\tif (EXACT_MATCH_EVENT_KEYS.has(eventKey)) {\n\t\treturn subscriptionKey === eventKey;\n\t}\n\t// Raw prefix match, same as the real SDK: \"ada:campaigns\" also catches\n\t// \"ada:campaigns:shown\" and underscore-suffixed keys.\n\treturn subscriptionKey === eventKey || eventKey.startsWith(subscriptionKey);\n}\n\nfunction toMetaFieldRecord(\n\tfields: Record<string, unknown>,\n): Record<string, MetaFieldValue> {\n\tconst record: Record<string, MetaFieldValue> = {};\n\tfor (const [key, value] of Object.entries(fields)) {\n\t\tif (\n\t\t\ttypeof value === \"string\" ||\n\t\t\ttypeof value === \"number\" ||\n\t\t\ttypeof value === \"boolean\" ||\n\t\t\tvalue === null\n\t\t) {\n\t\t\trecord[key] = value;\n\t\t}\n\t}\n\treturn record;\n}\n\n/**\n * Create an in-memory messaging-interface stand-in for unit-testing an\n * integration without loading the SDK from the CDN.\n *\n * Every method resolves, records itself in `calls`, and keeps just enough\n * state to behave like the real interface: `sendMessage` appends a\n * programmatic user message that `getMessages` returns, `open`/`close`/\n * `toggle` drive `isOpen` and `getInfo`, `setMetaFields` feeds\n * `getMetaFields` (sensitive fields are never returned, matching the real\n * SDK), and `$emit` fans out to `subscribeEvent`/`subscribeAll` callbacks.\n *\n * Pass `overrides` to replace any method (e.g. with `vi.fn()`); overridden\n * methods are still recorded in `calls`.\n */\nexport function createMockAdaMessaging(\n\toverrides: Partial<AdaMessagingInterface> = {},\n): MockAdaMessaging {\n\tconst calls: RecordedAdaMessagingCall[] = [];\n\tlet nextSubscriptionId = 1;\n\tlet nextMessageId = 1;\n\tlet chatOpen = false;\n\tlet conversationId: string | null = null;\n\tconst metaFields: Record<string, MetaFieldValue> = {};\n\tconst messages: PublicMessage[] = [];\n\tconst keyedSubscriptions = new Map<number, KeyedSubscription>();\n\tconst wildcardSubscriptions = new Map<\n\t\tnumber,\n\t\t(key: string, data: unknown) => void\n\t>();\n\n\tconst emit = (key: string, data?: unknown): void => {\n\t\tfor (const subscription of keyedSubscriptions.values()) {\n\t\t\tif (keyedSubscriptionMatches(subscription.key, key)) {\n\t\t\t\tsubscription.callback(data, { eventKey: key });\n\t\t\t}\n\t\t}\n\t\tif (EXACT_MATCH_EVENT_KEYS.has(key)) {\n\t\t\treturn;\n\t\t}\n\t\tfor (const callback of wildcardSubscriptions.values()) {\n\t\t\tcallback(key, data);\n\t\t}\n\t};\n\n\tconst clearTranscript = (): void => {\n\t\tmessages.length = 0;\n\t\tconversationId = null;\n\t};\n\n\tconst base: AdaMessagingInterface = {\n\t\tstart: async () => undefined,\n\t\tstop: async () => undefined,\n\t\ttoggle: async () => {\n\t\t\tchatOpen = !chatOpen;\n\t\t},\n\t\tdeleteHistory: async () => {\n\t\t\tclearTranscript();\n\t\t},\n\t\treset: async () => {\n\t\t\tclearTranscript();\n\t\t},\n\t\tsetMetaFields: async (fields) => {\n\t\t\tObject.assign(metaFields, toMetaFieldRecord(fields));\n\t\t},\n\t\t// Sensitive fields are accepted but never readable back, matching the\n\t\t// real SDK's getMetaFields contract.\n\t\tsetSensitiveMetaFields: async () => undefined,\n\t\tsetTestModeBanner: async () => undefined,\n\t\tgetInfo: async () => ({\n\t\t\tisChatOpen: chatOpen,\n\t\t\tisDrawerOpen: chatOpen,\n\t\t\thasActiveChatter: messages.length > 0,\n\t\t\thasClosedChat: false,\n\t\t}),\n\t\tgetMetaFields: async () => ({ ...metaFields }),\n\t\tisOpen: async () => chatOpen,\n\t\tgetConversation: async () => ({\n\t\t\tid: conversationId,\n\t\t\thandoff: { inLiveChat: false },\n\t\t}),\n\t\tgetMessages: async () => [...messages],\n\t\tsetComposerText: async () => undefined,\n\t\tsetDelegate: () => undefined,\n\t\ttrackEvent: async () => undefined,\n\t\tsubscribeEvent: async (key, callback) => {\n\t\t\tconst id = nextSubscriptionId++;\n\t\t\tkeyedSubscriptions.set(id, { key, callback });\n\t\t\treturn id;\n\t\t},\n\t\tunsubscribeEvent: async (id) => {\n\t\t\tkeyedSubscriptions.delete(id);\n\t\t\twildcardSubscriptions.delete(id);\n\t\t},\n\t\tsubscribeAll: async (callback) => {\n\t\t\tconst id = nextSubscriptionId++;\n\t\t\twildcardSubscriptions.set(id, callback);\n\t\t\treturn id;\n\t\t},\n\t\topen: async () => {\n\t\t\tchatOpen = true;\n\t\t},\n\t\tclose: async () => {\n\t\t\tchatOpen = false;\n\t\t},\n\t\tsetLanguage: async () => undefined,\n\t\tsetTheme: async () => undefined,\n\t\tsetDeviceToken: async () => undefined,\n\t\t// Optional on AdaEmbedInterface (internal mobile-webview plumbing), but\n\t\t// Ada's own maintained test double still implements the full surface — the\n\t\t// types-drift guard requires the shipped mock to cover every interface\n\t\t// member so a customer's double never lags it.\n\t\tacknowledgeSessionMirrorClear: async () => undefined,\n\t\tdeliverSessionMirrorSeed: async () => undefined,\n\t\tsendMessage: async (body) => {\n\t\t\tconst id = `mock-message-${nextMessageId++}`;\n\t\t\tconversationId = conversationId ?? \"mock-conversation\";\n\t\t\tmessages.push({\n\t\t\t\tid,\n\t\t\t\trole: \"user\",\n\t\t\t\ttype: \"text\",\n\t\t\t\tbody,\n\t\t\t\tcreatedAt: Math.floor(Date.now() / 1000),\n\t\t\t\torigin: \"programmatic\",\n\t\t\t});\n\t\t\treturn { id };\n\t\t},\n\t\thandleNotification: async () => {\n\t\t\tchatOpen = true;\n\t\t},\n\t\trequestNotifications: async () => \"granted\",\n\t\tcreateProactive: async () => undefined,\n\t\ttriggerCampaign: async () => undefined,\n\t\tevaluateCampaignConditions: async () => undefined,\n\t\tcloseCampaign: async () => undefined,\n\t\ttriggerAnswer: async () => undefined,\n\t\ttriggerProactive: async () => undefined,\n\t\ttriggerGreeting: async () => undefined,\n\t\tdestroy: async () => {\n\t\t\tkeyedSubscriptions.clear();\n\t\t\twildcardSubscriptions.clear();\n\t\t\tchatOpen = false;\n\t\t},\n\t};\n\n\tconst definedOverrides = Object.fromEntries(\n\t\tObject.entries(overrides).filter(([, value]) => value !== undefined),\n\t);\n\tconst implementation = { ...base, ...definedOverrides };\n\tconst recorded = Object.fromEntries(\n\t\tObject.entries(implementation).map(([method, fn]) => [\n\t\t\tmethod,\n\t\t\t(...args: unknown[]) => {\n\t\t\t\tcalls.push({ method, args });\n\t\t\t\treturn (fn as (...callArgs: unknown[]) => unknown)(...args);\n\t\t\t},\n\t\t]),\n\t) as unknown as AdaMessagingInterface;\n\n\treturn { ...recorded, calls, $emit: emit };\n}\n"],"mappings":"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"}
|
|
@@ -3,3 +3,43 @@
|
|
|
3
3
|
* Both sdk and core reference this set for cluster validation.
|
|
4
4
|
*/
|
|
5
5
|
export declare const VALID_ADA_CLUSTERS: Set<string>;
|
|
6
|
+
/**
|
|
7
|
+
* Cluster domains whose bots serve their web assets from the global preprod
|
|
8
|
+
* CDN (`messaging-assets.ada-dev2.support`) rather than the production one.
|
|
9
|
+
*
|
|
10
|
+
* Every TypeScript consumer imports this set rather than restating it. The
|
|
11
|
+
* native wrapper and the web runtime it loads must reach the SAME verdict for
|
|
12
|
+
* a given cluster: `sdk.js` derives its base from `import.meta.url`, so a
|
|
13
|
+
* production `webview.html` hosting a preprod `sdk.js` (or the reverse) mounts
|
|
14
|
+
* `core.html` cross-origin to its parent, core's `isNativeWebViewMount` check
|
|
15
|
+
* fails closed, and the session mirror silently never arms. Android's Kotlin
|
|
16
|
+
* copy cannot import this file; it is pinned against this set by
|
|
17
|
+
* `packages/sdk/src/preprod-cluster-contract.test.ts`.
|
|
18
|
+
*/
|
|
19
|
+
export declare const PREPROD_ADA_CLUSTER_DOMAINS: Set<string>;
|
|
20
|
+
/**
|
|
21
|
+
* Reduces a host-supplied cluster value to the canonical bot domain used by
|
|
22
|
+
* {@link VALID_ADA_CLUSTERS}, {@link PREPROD_ADA_CLUSTER_DOMAINS} and the core
|
|
23
|
+
* rollout/API host builders (e.g. `my-bot.maple.ada.support`).
|
|
24
|
+
*
|
|
25
|
+
* Accepts the shapes hosts actually pass: a scheme prefix, trailing slashes,
|
|
26
|
+
* mixed case, and the bare shorthand `maple` (expanded to `maple.ada.support`).
|
|
27
|
+
* `localhost` passes through. Returns undefined for anything that is not a
|
|
28
|
+
* valid domain-shaped identifier, so callers fail back to their safe default
|
|
29
|
+
* rather than propagating an unusable value.
|
|
30
|
+
*
|
|
31
|
+
* This is the single implementation: two normalizers that merely agree by
|
|
32
|
+
* inspection are how a cluster reaches opposite verdicts on the two sides of
|
|
33
|
+
* the same mount.
|
|
34
|
+
*/
|
|
35
|
+
export declare function normalizeAdaClusterDomain(
|
|
36
|
+
cluster: string | undefined,
|
|
37
|
+
): string | undefined;
|
|
38
|
+
/**
|
|
39
|
+
* Whether a host-supplied cluster value names a preprod cluster, normalized
|
|
40
|
+
* first. Prefer this over a bare set lookup so a caller cannot reach the
|
|
41
|
+
* opposite verdict by skipping normalization.
|
|
42
|
+
*/
|
|
43
|
+
export declare function isPreprodAdaCluster(
|
|
44
|
+
cluster: string | undefined,
|
|
45
|
+
): boolean;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ada-cx/messaging-sdk",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "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.",
|
|
5
5
|
"homepage": "https://docs.ada.cx",
|
|
6
6
|
"keywords": [
|