@ada-cx/messaging-sdk 1.0.0-setup.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.
Files changed (61) hide show
  1. package/README.md +211 -0
  2. package/dist/client/ada-settings-reader.d.ts +10 -0
  3. package/dist/client/client-channel-handlers.d.ts +92 -0
  4. package/dist/client/client-errors.d.ts +7 -0
  5. package/dist/client/client-frame-constants.d.ts +41 -0
  6. package/dist/client/client-frame-runtime.d.ts +54 -0
  7. package/dist/client/client-host-runtime.d.ts +73 -0
  8. package/dist/client/client-types.d.ts +231 -0
  9. package/dist/client/client.d.ts +843 -0
  10. package/dist/client/config-helpers.d.ts +157 -0
  11. package/dist/client/constants.d.ts +61 -0
  12. package/dist/client/core-channel-test-harness.d.ts +47 -0
  13. package/dist/client/embed-interface.d.ts +172 -0
  14. package/dist/client/embed-lifecycle.d.ts +12 -0
  15. package/dist/client/fallback-html.d.ts +1 -0
  16. package/dist/client/frame-manager.d.ts +64 -0
  17. package/dist/client/frame-styles.d.ts +164 -0
  18. package/dist/client/frame-visibility.d.ts +53 -0
  19. package/dist/client/integration-manager.d.ts +73 -0
  20. package/dist/client/legacy-callback-adapter.d.ts +22 -0
  21. package/dist/client/mask-element.d.ts +19 -0
  22. package/dist/client/proactive/index.d.ts +5 -0
  23. package/dist/client/proactive/proactive-channel.d.ts +33 -0
  24. package/dist/client/proactive/proactive-runtime.d.ts +65 -0
  25. package/dist/client/proactive/proactive-utils.d.ts +51 -0
  26. package/dist/client/sdk-error-reporter.d.ts +22 -0
  27. package/dist/client/sdk-event-bus.d.ts +116 -0
  28. package/dist/client/service-worker-notifications.d.ts +1 -0
  29. package/dist/client/url-business-events.d.ts +19 -0
  30. package/dist/frame-channel/frame-channel.d.ts +64 -0
  31. package/dist/frame-channel/frame-child.d.ts +45 -0
  32. package/dist/frame-channel/index.d.ts +3 -0
  33. package/dist/frame-channel/types.d.ts +736 -0
  34. package/dist/helpers/rollout.d.ts +18 -0
  35. package/dist/host-telemetry.d.ts +15 -0
  36. package/dist/index.d.ts +8 -0
  37. package/dist/logger.d.ts +59 -0
  38. package/dist/mobile-webview-runtime.d.ts +122 -0
  39. package/dist/npm/build-info.d.cts +17 -0
  40. package/dist/npm/build-info.d.ts +17 -0
  41. package/dist/npm/index.cjs +276 -0
  42. package/dist/npm/index.cjs.map +1 -0
  43. package/dist/npm/index.d.cts +15 -0
  44. package/dist/npm/index.d.ts +9 -0
  45. package/dist/npm/index.js +270 -0
  46. package/dist/npm/index.js.map +1 -0
  47. package/dist/npm/loader.d.cts +120 -0
  48. package/dist/npm/loader.d.ts +120 -0
  49. package/dist/npm/testing.cjs +155 -0
  50. package/dist/npm/testing.cjs.map +1 -0
  51. package/dist/npm/testing.d.cts +39 -0
  52. package/dist/npm/testing.d.ts +39 -0
  53. package/dist/npm/testing.js +154 -0
  54. package/dist/npm/testing.js.map +1 -0
  55. package/dist/sdk-module-loader.d.ts +1 -0
  56. package/dist/shared-utils/clusters.d.ts +5 -0
  57. package/dist/shared-utils/csat.d.ts +116 -0
  58. package/dist/shared-utils/host-telemetry.d.ts +35 -0
  59. package/dist/shared-utils/index.d.ts +3 -0
  60. package/dist/voice/voice-manager.d.ts +120 -0
  61. package/package.json +77 -0
@@ -0,0 +1,164 @@
1
+ /** CSS styles for hidden utility iframes (collapsed intro, etc.) */
2
+ export declare const HIDDEN_FRAME_STYLES = "position:absolute;width:0;height:0;border:0;overflow:hidden;";
3
+ /**
4
+ * Transparent halo (px) the button iframe reserves on every side so the unread
5
+ * badge, which overhangs the button's top-right corner, isn't clipped by the
6
+ * iframe bounds. Must match `BADGE_HALO` in `public/button.html`: button.html
7
+ * adds it to the reported frame size, and `getButtonFrameStyles` pulls the
8
+ * anchor in by the same amount so the 24px visual edge gap is unchanged.
9
+ */
10
+ export declare const BUTTON_BADGE_HALO = 8;
11
+ /**
12
+ * Transparent band (px) the proactive iframe reserves on every side so the
13
+ * bubble's drop shadow isn't clipped by the iframe bounds. `0 8px 24px` reaches
14
+ * 12px sideways and 20px below the box, and an iframe clips its own document
15
+ * regardless of the element's `overflow`, so anything short of that leaves the
16
+ * shadow ending on a hard line.
17
+ *
18
+ * Must match `.ada-intro-container`'s padding in
19
+ * `packages/integrations/src/intro-page.tsx`: the container paints the band, and
20
+ * {@link getProactiveFrameStyles} pulls the anchors out by the same amount so the
21
+ * content's visual position is unchanged. Same arrangement as
22
+ * {@link BUTTON_BADGE_HALO} and `public/button.html`.
23
+ */
24
+ export declare const PROACTIVE_SHADOW_HALO = 24;
25
+ /**
26
+ * The gap every floating surface keeps from the viewport edge. Shared so the
27
+ * launcher, the drawer and the proactive bubble cannot drift apart — the bubble
28
+ * lining up with the launcher is the whole point of threading it through.
29
+ */
30
+ export declare const EDGE_GAP = 24;
31
+ /** Default launcher diameter — `--btn-size` in `public/button.html`. */
32
+ export declare const DEFAULT_LAUNCHER_SIZE = 64;
33
+ /**
34
+ * The launcher frame's height before `button.resize` reports the measured one:
35
+ * the default button plus the badge halo it reserves on each side.
36
+ */
37
+ export declare const DEFAULT_LAUNCHER_FRAME_HEIGHT: number;
38
+ /**
39
+ * Read the launcher frame's current height off the element the `button.resize`
40
+ * handler sizes. Falls back to the default when the frame hasn't reported yet —
41
+ * a proactive can be shown before the launcher measures itself.
42
+ */
43
+ export declare function getLauncherFrameHeight(launcher: HTMLElement | null | undefined): number;
44
+ export declare const VIEWPORT_HEIGHT_CSS_VAR = "--ada-messaging-viewport-height";
45
+ /** Legacy embed-2's drawer durations (`ChatFrame/index.tsx` `:389` and `:178`). */
46
+ export declare const CHAT_OPEN_TRANSITION_MS = 300;
47
+ export declare const CHAT_CLOSE_TRANSITION_MS = 200;
48
+ /**
49
+ * The closed drawer's resting styles — used both to mount the core frame and to
50
+ * park it after a close. Collapsed to 0×0 *and* carrying the animation's start
51
+ * values: a transition needs a committed start value to interpolate from, so a
52
+ * frame resting at plain {@link HIDDEN_FRAME_STYLES} (`opacity` 1, no
53
+ * `transform`) would snap open however the open styles are written.
54
+ */
55
+ export declare const CHAT_FRAME_COLLAPSED_STYLES = "position:absolute;width:0;height:0;border:0;overflow:hidden;opacity:0;transform:translateY(100px);";
56
+ /**
57
+ * Ada's own stacking scale, named so the scrim's position is declared alongside
58
+ * the frames it has to sit under rather than inferred from them. The invariant
59
+ * has two halves — the scrim must stay below every frame, and above the band
60
+ * host chrome occupies — so it is pinned by `frame-styles.test.ts` rather than
61
+ * derived: a `Math.min` over the frames would satisfy the first half while a
62
+ * future low-stacked frame silently dragged the scrim under host content.
63
+ *
64
+ * `scrim` is embed-2's `.drawer-mask` value.
65
+ *
66
+ * `proactive` sits one below `launcher` deliberately. The proactive frame reserves
67
+ * a transparent shadow halo that reaches down over the launcher, and an iframe is
68
+ * atomic for hit-testing — a click anywhere in its box is consumed by its own
69
+ * document and never falls through to what is underneath, whatever
70
+ * `pointer-events` that document sets. At an equal z-index the winner would be DOM
71
+ * order, including a lazily mounted `intro` frame appended after `button`,
72
+ * so the halo would paint over the launcher and swallow clicks on its top edge
73
+ * while a bubble is showing — exactly when the user is most likely to reach for it.
74
+ */
75
+ export declare const FRAME_Z_INDEX: {
76
+ readonly scrim: 9998;
77
+ readonly proactive: 9999;
78
+ readonly launcher: 10000;
79
+ readonly chat: 10001;
80
+ readonly chatMobile: 10010;
81
+ readonly hostViewport: 10020;
82
+ };
83
+ /** Legacy Embed2's mobile signal: fixed for the page lifetime and UA-based. */
84
+ export declare function isMobileViewport(): boolean;
85
+ /**
86
+ * Legacy Generative Chat's content-layout signal. Unlike Embed2's outer drawer
87
+ * geometry above, Chat included iPad when choosing mobile quick-reply layouts.
88
+ */
89
+ export declare function isMobileContentLayout(): boolean;
90
+ /**
91
+ * A media query can't be expressed in an inline `style` attribute, and injecting
92
+ * a `<style>` element is not reliable on a host page whose CSP omits
93
+ * `'unsafe-inline'` — so the drawer's reduced-motion branch is resolved in JS.
94
+ * Degrades to "animate" when `matchMedia` is unavailable, matching
95
+ * {@link isMobileViewport}.
96
+ */
97
+ export declare function prefersReducedMotion(): boolean;
98
+ export declare function syncFrameViewportHeight(element: HTMLElement): () => void;
99
+ /**
100
+ * CSS styles for the button launcher iframe.
101
+ *
102
+ * The iframe starts invisible (opacity:0) and at 0×0 size. Once the
103
+ * button.html content renders and measures itself, it sends a
104
+ * "button.resize" postMessage with its dimensions. The SDK then
105
+ * resizes the iframe to fit and fades it in.
106
+ *
107
+ * The reported size is intentionally oversized by `BUTTON_BADGE_HALO` on each
108
+ * side so the overhanging unread badge isn't clipped by the iframe bounds, and
109
+ * the anchor is pulled in by the same amount to preserve the 24px visual edge
110
+ * gap. The halo is an inert band around the launcher (matching legacy chat,
111
+ * which reserves an even larger buffer) — it is not click-through.
112
+ */
113
+ export declare function getButtonFrameStyles(align: "left" | "right", _isText: boolean, isParentElement: boolean): string;
114
+ /**
115
+ * CSS styles for the open chat widget iframe.
116
+ *
117
+ * @param align - "left" | "right" (from client config)
118
+ * @param isParentElement - true when the SDK is mounted inside a host element
119
+ * @param isMobile - true for legacy's UA-detected mobile form factor;
120
+ * renders the drawer edge-to-edge full screen instead of the
121
+ * floating desktop card.
122
+ * @param animate - false reproduces the pre-animation styles exactly (reduced motion,
123
+ * and any restyle that must land instantly). Geometry is never
124
+ * transitioned — only `opacity`/`transform` — so the drawer's box
125
+ * still snaps to full size while the content slides and fades in.
126
+ */
127
+ export declare function getChatFrameStyles(align: "left" | "right", isParentElement: boolean, isMobile?: boolean, animate?: boolean): string;
128
+ /**
129
+ * Temporary host-viewport geometry for content that needs more room than the
130
+ * floating drawer (for example, an image lightbox). This is deliberately an
131
+ * SDK-owned iframe style, not a host-DOM portal: old SDKs ignore the request,
132
+ * and parent-element/headless clients never apply it.
133
+ *
134
+ * `100%` and the synchronized viewport-height custom property avoid the mobile
135
+ * browser chrome traps of `100vw`/`100vh`. Every declaration needed to restore
136
+ * an interactive frame is explicit because `applyFrameStyle` replaces the
137
+ * complete inline style block.
138
+ */
139
+ export declare function getHostViewportFrameStyles(): string;
140
+ /**
141
+ * The drawer's styles for the duration of the close animation: the *open*
142
+ * geometry, so there is a box to animate, plus the hidden end state.
143
+ *
144
+ * `visibility` flips only after the transition completes (`0ms linear` with a
145
+ * matching delay, legacy embed-2's shape) and `pointer-events:none` applies at
146
+ * once, so a frame still holding open geometry is inert over the customer's page
147
+ * even if the caller's collapse never lands.
148
+ */
149
+ export declare function getChatFrameClosingStyles(align: "left" | "right", isMobile?: boolean): string;
150
+ /**
151
+ * CSS styles for the proactive message intro iframe.
152
+ *
153
+ * Returns `HIDDEN_FRAME_STYLES` in parentElement mode because proactive
154
+ * popups are suppressed when the SDK is embedded in a host element.
155
+ *
156
+ * The anchors are the *visual* gaps minus {@link PROACTIVE_SHADOW_HALO}, since
157
+ * the container inside the frame insets the content by the halo. `EDGE_GAP`
158
+ * matches `getButtonFrameStyles` so the bubble and the launcher share a right
159
+ * edge.
160
+ *
161
+ * @param align - "left" | "right" (from client config)
162
+ * @param isParentElement - true when the SDK is mounted inside a host element
163
+ */
164
+ export declare function getProactiveFrameStyles(align: "left" | "right", isParentElement: boolean, launcherFrameHeight?: number): string;
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Whether a frame carrying this style block is invisible to a sighted user. The SDK
3
+ * expresses that three ways and only three — collapsing to 0×0, fading to
4
+ * `opacity: 0`, and the closing drawer's `visibility: hidden` — so the style block
5
+ * is the whole answer.
6
+ *
7
+ * Read from the declaration string rather than the element's parsed
8
+ * `style.width`/`style.opacity`, because jsdom drops every declaration after an
9
+ * `overflow` shorthand: the launcher's own mount style parses to an empty
10
+ * `CSSStyleDeclaration` there, so an element-based reading would be right in a
11
+ * browser and silently wrong in every unit test.
12
+ *
13
+ * A block that declares the *end* of a transition rather than the frame's current
14
+ * appearance is not something the string can distinguish — those callers write the
15
+ * style and set exposure themselves.
16
+ */
17
+ export declare function isHiddenFrameStyle(style: string): boolean;
18
+ /**
19
+ * Whether the frame has been sized by something other than its collapsed mount
20
+ * style. The launcher mounts at 0×0 and only becomes a real control once
21
+ * `button.resize` reports a measurement, so exposing it before that publishes an
22
+ * empty node the chatter cannot reach.
23
+ */
24
+ export declare function hasMeasuredSize(frame: HTMLElement | null | undefined): boolean;
25
+ /**
26
+ * Add or drop a frame's `aria-hidden`, keeping its focusability in step.
27
+ *
28
+ * `aria-hidden` alone leaves the frame's own controls in the tab order, so a
29
+ * keyboard user lands on something their screen reader has been told to ignore
30
+ * (axe `aria-hidden-focus`) — `inert` is what takes the subtree out of sequential
31
+ * focus, and it crosses into a frame's nested document where `tabindex` cannot.
32
+ *
33
+ * Where `inert` is unimplemented, `visibility: hidden` is the fallback: a 0×0 or
34
+ * faded frame still offers its nested controls to Tab, and `visibility` is the one
35
+ * property that removes a whole subtree — nested browsing context included — from
36
+ * both the tree and sequential focus. It costs the fade/slide on those browsers,
37
+ * which is the right trade against a reader-invisible control being reachable.
38
+ *
39
+ * Exposure drops the attribute rather than setting `aria-hidden="false"`, which
40
+ * cannot override a hidden ancestor and so reads as an opt-back-in it can never be.
41
+ */
42
+ export declare function setFrameHidden(frame: HTMLElement | null | undefined, hidden: boolean): void;
43
+ /**
44
+ * Write a frame's style block and bring its accessibility exposure with it.
45
+ *
46
+ * Derived from the style rather than passed alongside it because the two must not
47
+ * be able to disagree: a collapsed frame left in the tree offers a reader a control
48
+ * that cannot be operated, and a visible frame kept out of it is invisible to
49
+ * assistive technology while the chatter can see it. Frames whose visibility changes
50
+ * without a new style block — the launcher, which is faded by property writes —
51
+ * call {@link setFrameHidden} directly.
52
+ */
53
+ export declare function applyFrameStyle(frame: HTMLElement | null | undefined, style: string): void;
@@ -0,0 +1,73 @@
1
+ import { FrameChannel } from "../frame-channel/index.js";
2
+ import type { IntegrationCommand, IntegrationFrameId, TrackedIntegrationCommand } from "./config-helpers.js";
3
+ export interface IntegrationManagerDeps {
4
+ /** Build a URL pointing to an asset file within a given package directory. */
5
+ buildAssetUrl: (dir: "integrations", file: string) => string;
6
+ /** Container element into which integration iframes are appended. */
7
+ container: HTMLElement;
8
+ /** Forward a message to the core frame channel. */
9
+ sendToCore: (type: string, payload: unknown) => void;
10
+ /** Publish a named SDK event to host-page subscribers. */
11
+ routePublishedEvent: (key: string, data: unknown) => void;
12
+ /** Deliver the current SDK config to a newly-connected integration channel. */
13
+ deliverConfig: (channel: FrameChannel) => void;
14
+ /** Resolve the absolute origin for a mounted iframe (null if unavailable). */
15
+ getFrameOrigin: (iframe: HTMLIFrameElement) => string | null;
16
+ }
17
+ /**
18
+ * Manages the lifecycle of integration iframes (zendesk, zendeskMessaging,
19
+ * oauth, csat) and the command bus that routes commands to them.
20
+ *
21
+ * Designed with constructor dependency injection so it can be tested in
22
+ * isolation without requiring a full `AdaMessagingClient` instance.
23
+ */
24
+ export declare class IntegrationManager {
25
+ private readonly _deps;
26
+ private readonly _frames;
27
+ private readonly _channels;
28
+ private readonly _ready;
29
+ private readonly _loadTimers;
30
+ private readonly _pendingCommands;
31
+ private readonly _trackedCommands;
32
+ private readonly _trackedCommandTimers;
33
+ /** Terminal command ids whose eventual late receipts should be ignored. */
34
+ private readonly _settledCommandIds;
35
+ private _commandCounter;
36
+ private _activePlatform;
37
+ private _destroyed;
38
+ private readonly _loadHandlers;
39
+ constructor(deps: IntegrationManagerDeps);
40
+ /**
41
+ * Dispatch a raw integration command payload (as received from the core
42
+ * frame via `sdk.integration.command`). Validates, normalizes, routes to
43
+ * the appropriate iframe, and emits `ada:integration:command` for public
44
+ * integration work.
45
+ */
46
+ dispatch(rawPayload: unknown): void;
47
+ /**
48
+ * Wire the frame channel for a freshly-loaded integration iframe.
49
+ * Called by the SDK after the iframe's `load` event fires.
50
+ */
51
+ onFrameLoaded(frameId: IntegrationFrameId, iframe: HTMLIFrameElement): void;
52
+ /** Disconnect all channels, cancel all timers, and clear all state. */
53
+ teardown(): void;
54
+ /** Clear identity-bound integration state while keeping the manager reusable. */
55
+ resetForIdentityChange(): void;
56
+ private _clearRuntimeState;
57
+ private _ensureFrame;
58
+ private _mountFrame;
59
+ private _failPendingCommandsForFrame;
60
+ private _queueCommand;
61
+ private _removePending;
62
+ private _clearTracked;
63
+ private _rememberSettledCommandId;
64
+ private _trackCommand;
65
+ private _flush;
66
+ private _handleResult;
67
+ private _emitResult;
68
+ private _handleEvent;
69
+ /** @internal — used by client.test.ts integration buffering tests */
70
+ get _integrationPendingCommands(): Map<IntegrationFrameId, IntegrationCommand[]>;
71
+ /** @internal — used by client.test.ts integration buffering tests */
72
+ get _trackedIntegrationCommands(): Map<string, TrackedIntegrationCommand>;
73
+ }
@@ -0,0 +1,22 @@
1
+ import type { AdaSdkEventCallback } from "./client-types.js";
2
+ export interface AdaptLegacyCallbacksOptions {
3
+ /** Raw `window.adaSettings` object (may contain any shape). */
4
+ adaSettings: Record<string, unknown>;
5
+ /** The `eventCallbacks` map from the constructor config (if any). */
6
+ constructorEventCallbacks: Record<string, AdaSdkEventCallback> | undefined;
7
+ /** Bound reference to `AdaMessagingClient.subscribeEvent`. */
8
+ subscribeEvent: (key: string, callback: AdaSdkEventCallback) => number;
9
+ }
10
+ export interface AdaptLegacyCallbacksResult {
11
+ /**
12
+ * The `zdChatterAuthCallback` function extracted from adaSettings, or
13
+ * `null` if not present / not a function. The caller stores it separately
14
+ * and invokes it on demand (request-response pattern, not an event).
15
+ */
16
+ zdChatterAuthCallback: ((callback: (token: string) => void) => void) | null;
17
+ }
18
+ /**
19
+ * Registers applicable adaSettings callbacks as `subscribeEvent` subscribers
20
+ * and returns the extracted `zdChatterAuthCallback`.
21
+ */
22
+ export declare function adaptLegacyCallbacks(options: AdaptLegacyCallbacksOptions): AdaptLegacyCallbacksResult;
@@ -0,0 +1,19 @@
1
+ export interface MaskElementHandle {
2
+ /** Drawer open/closed. The scrim renders only while open and off mobile. */
3
+ setOpen(isOpen: boolean): void;
4
+ destroy(): void;
5
+ }
6
+ export interface CreateMaskElementOptions {
7
+ /** The SDK's frame container, which also scopes teardown. */
8
+ container: HTMLElement;
9
+ /** Invoked on a scrim click or an Escape press while the scrim is showing. */
10
+ onDismiss: () => void;
11
+ }
12
+ /**
13
+ * The host-page scrim behind an open chat drawer, for hosts that opt in with
14
+ * `hideMask: false`.
15
+ *
16
+ * Mobile form factor uses legacy's page-lifetime UA signal, so `setOpen()` can
17
+ * render synchronously without a resize/breakpoint subscription.
18
+ */
19
+ export declare function createMaskElement(options: CreateMaskElementOptions): MaskElementHandle;
@@ -0,0 +1,5 @@
1
+ export type { IntroChannelDependencies } from "./proactive-channel.js";
2
+ export { setupIntroChannelHandlers } from "./proactive-channel.js";
3
+ export type { IntroDisplayKind, IntroShowingState, ProactiveRuntimeDependencies, } from "./proactive-runtime.js";
4
+ export { evaluateProactiveConditionsRuntime, matchesIntroPathRuntime, onLocationChangeRuntime, openCtaUrlRuntime, scheduleIntroRuntime, } from "./proactive-runtime.js";
5
+ export { findMatchingProactives, isProactiveShown, markProactiveShown, markProactiveShownForUser, } from "./proactive-utils.js";
@@ -0,0 +1,33 @@
1
+ import type { FrameChannel, FrameRegistry, StartupConfig } from "../../frame-channel/index.js";
2
+ export interface IntroChannelDependencies {
3
+ channel: FrameChannel;
4
+ frames: FrameRegistry;
5
+ getStartupConfig: () => StartupConfig | null;
6
+ sendToCore: (type: string, payload: unknown) => void;
7
+ routePublishedEvent: (key: string, data: unknown) => void;
8
+ open: () => void;
9
+ /** Open after an exact proactive click without clearing its armed attribution. */
10
+ openFromProactiveEngagement?: () => void;
11
+ setIntroShowing: (state: "greeting" | "proactive" | null) => void;
12
+ getCurrentIntroSurface?: () => {
13
+ kind: "greeting";
14
+ displayId: number;
15
+ } | {
16
+ kind: "proactive";
17
+ campaignKey: string;
18
+ surfaceId: number;
19
+ intentId?: number;
20
+ } | null;
21
+ acknowledgeProactiveShown?: (campaignKey: string, surfaceId: number) => boolean;
22
+ /** Mark current-session intro metadata when an accepted proactive is removed. */
23
+ markIntroShown?: () => void;
24
+ clearCurrentIntroSurface?: () => void;
25
+ /** Retire an acknowledged bubble so a later open cannot publish it as opened. */
26
+ clearShownCampaign?: () => void;
27
+ /** Open a proactive bubble's branded CTA URL from the host-page context. The
28
+ * intro frame is sandboxed without allow-popups, so it cannot navigate itself;
29
+ * it delegates the open to the SDK running on the host page. */
30
+ openCtaUrl?: (url: string, openInNewTab: boolean) => void;
31
+ }
32
+ /** Wire all message handlers for the intro (proactive messages + greeting) frame. */
33
+ export declare function setupIntroChannelHandlers(deps: IntroChannelDependencies): void;
@@ -0,0 +1,65 @@
1
+ import type { FrameChannel, FrameName, FrameRegistry, StartupConfig } from "../../frame-channel/index.js";
2
+ export type IntroShowingState = "greeting" | "proactive" | null;
3
+ export type IntroDisplayKind = "greeting" | "proactive";
4
+ export interface ProactiveRuntimeDependencies {
5
+ handle: string;
6
+ language?: string;
7
+ parentElement: HTMLElement | null;
8
+ /** Whether the resolved host layout currently permits launcher/intro chrome. */
9
+ hasFloatingWidgetChrome: boolean;
10
+ channels: Map<FrameName, FrameChannel>;
11
+ frames: FrameRegistry;
12
+ getAlign: () => "left" | "right";
13
+ getIsChatOpen: () => boolean;
14
+ getHasChatOpened: () => boolean;
15
+ getHasIntroShown: () => boolean;
16
+ setHasIntroShown: (shown: boolean) => void;
17
+ /** Mark legacy's sticky `wasIntroShown` chatter metadata fact. */
18
+ markIntroShown: () => void;
19
+ resetIntroShownForNavigation: () => void;
20
+ sendToCore: (type: string, payload: unknown) => void;
21
+ routePublishedEvent: (key: string, data: unknown) => void;
22
+ getStartupConfig: () => StartupConfig | null;
23
+ getIntroTimer: () => ReturnType<typeof setTimeout> | null;
24
+ setIntroTimer: (timer: ReturnType<typeof setTimeout> | null) => void;
25
+ getProactiveTimers: () => Set<ReturnType<typeof setTimeout>>;
26
+ /** Retire all pre-chat content on a real navigation. */
27
+ retirePreChatSurface: () => void;
28
+ /** Allocate the ordering token carried through SDK -> core -> SDK. */
29
+ beginProactiveIntent: () => number;
30
+ /** True while no later SDK intent or navigation has superseded this one. */
31
+ isCurrentProactiveIntent: (intentId: number) => boolean;
32
+ setIntroShowing: (state: IntroShowingState) => void;
33
+ getLastEvaluatedUrl: () => string;
34
+ setLastEvaluatedUrl: (url: string) => void;
35
+ /** Deliver now when intro is ready, otherwise retain only the latest display. */
36
+ showOrQueueIntroDisplay: (kind: IntroDisplayKind, display: () => void, onDropped?: () => void) => void;
37
+ /** Deliver an emoji after the button channel is ready, retaining only latest. */
38
+ showOrQueueEmojiDisplay: (display: () => void) => void;
39
+ /** Mount the card-based intro runtime once a real surface has passed its gates. */
40
+ ensureIntroFrame: () => void;
41
+ beginGreetingDisplay: () => number;
42
+ }
43
+ /**
44
+ * Open a proactive bubble's branded CTA URL. The URL round-trips through the
45
+ * sandboxed intro frame before reaching the host page, so re-validate the
46
+ * destination with the shared credential-free navigation policy before opening —
47
+ * never open a `javascript:`/`data:`/relative URL. `open` is
48
+ * injected (`window.open`) so this stays pure and unit-testable.
49
+ */
50
+ export declare function openCtaUrlRuntime(url: string, openInNewTab: boolean, open: (url: string, target: string, features: string) => unknown): void;
51
+ /** Match an intro path pattern against the current pathname. Supports trailing `*` wildcard. */
52
+ export declare function matchesIntroPathRuntime(pattern: string, pathname: string): boolean;
53
+ /**
54
+ * Schedule the intro greeting popup to display after the configured delay.
55
+ * Skipped when: chat has already opened, this client instance already showed it,
56
+ * or paths don't match.
57
+ */
58
+ export declare function scheduleIntroRuntime(deps: ProactiveRuntimeDependencies, intro: NonNullable<StartupConfig["intro"]>): void;
59
+ /** Called on every SPA navigation. Re-evaluates intro and proactive conditions. */
60
+ export declare function onLocationChangeRuntime(deps: ProactiveRuntimeDependencies): void;
61
+ /**
62
+ * Evaluate URL-triggered proactive conversation conditions.
63
+ * Called on every SPA location change.
64
+ */
65
+ export declare function evaluateProactiveConditionsRuntime(deps: ProactiveRuntimeDependencies, ignoreFrequency?: boolean, surfaceAlreadyRetired?: boolean): boolean;
@@ -0,0 +1,51 @@
1
+ import type { StartupConfigProactiveConversation } from "../../frame-channel/types.js";
2
+ /**
3
+ * Compile an admin-authored URL regex within the existing defensive length ceiling.
4
+ * Successful and rejected compiles share a bounded cache across SPA evaluations.
5
+ * Native matching remains the legacy JavaScript contract for ordinary patterns.
6
+ * A conservative nested-repetition family is rejected because it can freeze the
7
+ * host page with only a few dozen URL characters (for example, `(a+)+`). Repeated
8
+ * alternation fails closed for the same reason. A small fixed repeat around
9
+ * flat, non-alternating optional tokens remains accepted because its optional-decision
10
+ * count is statically capped above. This heuristic is not a complete ReDoS proof.
11
+ */
12
+ export declare function compileCachedUrlRegex(source: string): RegExp | null;
13
+ /**
14
+ * Match one URL trigger condition, per-type, against the host page URL — mirroring
15
+ * legacy's `evalUrlMatchTriggerCondition`: `ends-with` and `equals` ignore query params,
16
+ * while `contains` and `regex` deliberately see the full URL including query params.
17
+ *
18
+ * The two differ on trailing slashes, and NOT symmetrically:
19
+ * - `equals` treats one as insignificant on either side (EXP-462 — docs.ada.cx documents
20
+ * `.../checkout` matching `.../checkout/`, so the matcher conforms to the docs).
21
+ * - `ends-with` tolerates one only on the PAGE url. A condition authored as `/checkout/`
22
+ * still will not match a page ending `/checkout`. That asymmetry is legacy's and is
23
+ * left alone deliberately: the docs promise nothing about slashes for `Ends With`, so
24
+ * there is no documented behaviour to conform to, and stripping the condition side
25
+ * needs a root guard (`"/"` would strip to `""`, which every URL ends with).
26
+ *
27
+ * An unknown type never matches, so a condition legacy cannot express (e.g.
28
+ * `starts-with`) cannot fire a proactive here either.
29
+ */
30
+ export declare function matchesUrlTriggerCondition(type: string, condValue: string, href: string): boolean;
31
+ interface ProactiveMatch {
32
+ key: string;
33
+ entry: StartupConfigProactiveConversation;
34
+ }
35
+ /** Select the final eligible unranked match, or the next ranked match. */
36
+ export declare function findMatchingProactives(proactives: Record<string, StartupConfigProactiveConversation> | null, href: string, handle: string): ProactiveMatch[];
37
+ /** Returns true if the proactive conversation has already been shown this session. */
38
+ export declare function isProactiveShown(key: string, handle: string): boolean;
39
+ /** Record that a proactive conversation has been shown this session. */
40
+ export declare function markProactiveShown(key: string, handle: string): void;
41
+ /**
42
+ * Returns true if the proactive conversation has already been shown to this user.
43
+ * Backed by localStorage so suppression survives tab close (once-per-user), under
44
+ * a key distinct from the once-per-session sessionStorage key.
45
+ */
46
+ export declare function isProactiveShownForUser(key: string, handle: string): boolean;
47
+ /** Record that a proactive conversation has been shown to this user (persists across sessions). */
48
+ export declare function markProactiveShownForUser(key: string, handle: string): void;
49
+ /** Record a ranked URL-triggered proactive so the next visit advances. */
50
+ export declare function markRankedProactiveShown(key: string, handle: string): void;
51
+ export {};
@@ -0,0 +1,22 @@
1
+ /**
2
+ * SDK error reporter.
3
+ *
4
+ * Captures errors that originate from Ada Messaging scripts running on the
5
+ * host page and forwards them to the core iframe via sendToCore so that core
6
+ * can route them to Sentry.
7
+ *
8
+ * Filtering strategy (host-page safety):
9
+ * - `error` events: when `event.filename` is non-empty, check it against our
10
+ * assets origin and skip errors from customer scripts. When filename is
11
+ * empty (cross-origin scripts or browser-sanitised events), we forward
12
+ * unconditionally as "opaque SDK context" — we can't attribute it but it
13
+ * may still reflect a crash in our code triggered by a 3rd-party call.
14
+ * Note: inline scripts on the host page also produce an empty filename;
15
+ * those will be forwarded but will not contain Ada stack frames and will
16
+ * appear as attributionless errors in Sentry.
17
+ * - `unhandledrejection` events: check the rejection's stack trace for our
18
+ * assets origin; skip rejections with no stack or a stack that doesn't
19
+ * reference our origin (can't attribute them to our code).
20
+ * - `ada:error` CustomEvents: always ours — forwarded with full detail.
21
+ */
22
+ export declare function setupSdkErrorReporter(sendToCore: (type: string, payload: unknown) => void): () => void;
@@ -0,0 +1,116 @@
1
+ import type { AdaSdkEventCallback, WindowInfo } from "./client-types.js";
2
+ /**
3
+ * SdkEventBus — owns the pub/sub subscriber registry and getInfo() promise tracking.
4
+ *
5
+ * Responsibilities:
6
+ * - subscribeEvent / unsubscribeEvent: namespace-matched event routing
7
+ * - routePublishedEvent: distribute published events to matching subscribers
8
+ * - requestInfo: issue sdk.info.get requests and resolve them asynchronously
9
+ * - teardown: flush pending info promises and clear all state
10
+ */
11
+ export declare class SdkEventBus {
12
+ private readonly _eventSubscribers;
13
+ private _nextSubscriberId;
14
+ private readonly _infoRequestCallbacks;
15
+ private readonly _infoRequestTimers;
16
+ private _infoRequestCounter;
17
+ private readonly _dataRequests;
18
+ /** Subscribe to an event key. Returns a stable subscription ID. */
19
+ subscribe(key: string, callback: AdaSdkEventCallback): number;
20
+ /**
21
+ * Subscribe to broad published events. Transcript-bearing events
22
+ * (`ada:message:sent` and `ada:message:received`) require exact
23
+ * subscribeEvent listeners and are not delivered to wildcard subscribers.
24
+ * Designed for bridge adapters (e.g. React Native, iOS, Android) that need to
25
+ * forward SDK events to native code.
26
+ *
27
+ * Returns a stable subscription ID that can be passed to unsubscribe().
28
+ */
29
+ subscribeAll(callback: (key: string, data: unknown) => void): number;
30
+ /** Cancel a subscription registered via subscribe() or subscribeAll(). */
31
+ unsubscribe(id: number): void;
32
+ /** Route a published event key to all matching subscribers. */
33
+ routePublishedEvent(key: string, data: unknown): void;
34
+ /** Whether a published event key would currently be observed by any subscriber. */
35
+ hasSubscribersFor(key: string): boolean;
36
+ /**
37
+ * Whether the exact event key has a direct subscribeEvent listener.
38
+ *
39
+ * Wildcard (`subscribeAll`) subscribers are intentionally excluded: the
40
+ * transcript-bearing events (`ada:message:sent`/`ada:message:received`)
41
+ * require an explicit per-key opt-in so conversation content is never
42
+ * delivered to generic event loggers that subscribe to everything.
43
+ */
44
+ hasExactSubscriberFor(key: string): boolean;
45
+ /**
46
+ * Request current window info from the core frame.
47
+ *
48
+ * `sendFn` dispatches the sdk.info.get message to core.
49
+ * `getFallbackInfo` provides local state if core does not respond within 5 s.
50
+ *
51
+ * The Maps exposed by `infoRequestCallbacks` and `infoRequestTimers` are
52
+ * passed directly to `setupCoreChannelHandlers` so the channel handler can
53
+ * resolve the promise without going through an additional layer.
54
+ */
55
+ requestInfo(sendFn: (type: string, payload: unknown) => void, getFallbackInfo: () => WindowInfo): Promise<WindowInfo>;
56
+ /**
57
+ * Exposed so CoreChannelDependencies can resolve and cancel info requests
58
+ * directly without additional indirection.
59
+ */
60
+ get infoRequestCallbacks(): Map<string, (info: WindowInfo) => void>;
61
+ get infoRequestTimers(): Map<string, ReturnType<typeof setTimeout>>;
62
+ /**
63
+ * Generic request/response round-trip to the core frame, modelled on
64
+ * {@link requestInfo}. Backs the read-only programmatic getters
65
+ * (getMetaFields/getConversation/getMessages). `sendFn` dispatches
66
+ * `messageType` with a `{ requestId }` payload; the matching `core.*.response`
67
+ * handler calls {@link resolveDataRequest}. If core does not respond within
68
+ * the timeout, the promise resolves with `fallback()` unless
69
+ * `timeoutErrorMessage` is set, in which case it rejects.
70
+ */
71
+ requestData<T>(sendFn: (type: string, payload: unknown) => void, messageType: string, fallback: () => T, options?: {
72
+ timeoutErrorMessage?: string;
73
+ payload?: Record<string, unknown>;
74
+ timeoutMs?: number;
75
+ reArmOnProgress?: boolean;
76
+ maxTotalMs?: number;
77
+ preserveForCoreReplacement?: boolean;
78
+ }): Promise<T>;
79
+ /** Register a bounded correlated response without sending the request. */
80
+ registerAckRequest<T>(fallback: () => T, options?: {
81
+ timeoutErrorMessage?: string;
82
+ timeoutMs?: number;
83
+ reArmOnProgress?: boolean;
84
+ maxTotalMs?: number;
85
+ preserveForCoreReplacement?: boolean;
86
+ }): {
87
+ requestId: string;
88
+ promise: Promise<T>;
89
+ };
90
+ /** Settle an abandoned acknowledgement through its compatibility fallback. */
91
+ settleUnconfirmed(requestId: string): void;
92
+ private _settleOnTimeout;
93
+ /**
94
+ * Re-arm requests whose response waits behind a multi-step activation. The
95
+ * absolute ceiling is measured from registration, so progress from another
96
+ * operation cannot keep a stale request alive indefinitely.
97
+ */
98
+ reArmPendingRequests(): void;
99
+ /** Resolve a pending requestData() promise with the core response payload. */
100
+ resolveDataRequest(requestId: string, data: unknown): void;
101
+ /** Reject a pending requestData() promise with a core-supplied error. */
102
+ rejectDataRequest(requestId: string, message: string): void;
103
+ /**
104
+ * Settle every pending requestData() promise. Ungated reads resolve with their
105
+ * fallback value; gated/programmatic reads reject with their timeout error.
106
+ * Used on core-frame crash (frame.error) and teardown.
107
+ */
108
+ flushDataRequests(options?: {
109
+ preserveForCoreReplacement?: boolean;
110
+ }): void;
111
+ /**
112
+ * Tear down: cancel pending info timers, resolve all pending getInfo()
113
+ * promises with `finalInfo` so callers don't hang, then clear all state.
114
+ */
115
+ teardown(finalInfo: WindowInfo): void;
116
+ }
@@ -0,0 +1 @@
1
+ export declare function setupServiceWorkerNotificationClickHandler(onNotificationClick: () => void): (() => void) | null;
@@ -0,0 +1,19 @@
1
+ interface UrlMatchCondition {
2
+ type: string;
3
+ value: string;
4
+ }
5
+ interface UrlTriggerCondition {
6
+ type: "url-match";
7
+ conditionsAny: UrlMatchCondition[];
8
+ }
9
+ export interface UrlBusinessEvent {
10
+ key: string;
11
+ value: number;
12
+ triggerConditions: UrlTriggerCondition[];
13
+ }
14
+ /** Parse the public `GET business_events/` response without trusting its shape. */
15
+ export declare function parseUrlBusinessEvents(payload: unknown): UrlBusinessEvent[];
16
+ /** Return every event with ANY matching URL trigger, matching legacy Embed2. */
17
+ export declare function getMatchingUrlBusinessEvents(events: UrlBusinessEvent[], href: string): UrlBusinessEvent[];
18
+ export declare function fetchUrlBusinessEvents(endpoint: string, fetcher?: typeof globalThis.fetch, signal?: AbortSignal): Promise<UrlBusinessEvent[]>;
19
+ export {};