@ada-cx/messaging-bridge 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.
- package/README.md +328 -0
- package/dist/bridge-client.d.ts +69 -0
- package/dist/bridge-react.d.ts +63 -0
- package/dist/build-info.d.ts +17 -0
- package/dist/derive.d.ts +115 -0
- package/dist/loader-CVynp73o.js +160 -0
- package/dist/loader-CVynp73o.js.map +1 -0
- package/dist/loader.d.ts +125 -0
- package/dist/npm-index.d.ts +17 -0
- package/dist/npm-index.js +28 -0
- package/dist/npm-index.js.map +1 -0
- package/dist/npm-react.d.ts +13 -0
- package/dist/npm-react.js +113 -0
- package/dist/npm-react.js.map +1 -0
- package/dist/operations.d.ts +323 -0
- package/dist/shared-utils/csat-settings.d.ts +61 -0
- package/dist/shared-utils/file-upload.d.ts +36 -0
- package/dist/shared-utils/index.d.ts +2 -0
- package/dist/state-keys.d.ts +105 -0
- package/dist/testing.d.ts +53 -0
- package/dist/testing.js +471 -0
- package/dist/testing.js.map +1 -0
- package/dist/types.d.ts +849 -0
- package/package.json +72 -0
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
//#region \0@oxc-project+runtime@0.122.0/helpers/typeof.js
|
|
2
|
+
function _typeof(o) {
|
|
3
|
+
"@babel/helpers - typeof";
|
|
4
|
+
return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function(o) {
|
|
5
|
+
return typeof o;
|
|
6
|
+
} : function(o) {
|
|
7
|
+
return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o;
|
|
8
|
+
}, _typeof(o);
|
|
9
|
+
}
|
|
10
|
+
//#endregion
|
|
11
|
+
//#region \0@oxc-project+runtime@0.122.0/helpers/toPrimitive.js
|
|
12
|
+
function toPrimitive(t, r) {
|
|
13
|
+
if ("object" != _typeof(t) || !t) return t;
|
|
14
|
+
var e = t[Symbol.toPrimitive];
|
|
15
|
+
if (void 0 !== e) {
|
|
16
|
+
var i = e.call(t, r || "default");
|
|
17
|
+
if ("object" != _typeof(i)) return i;
|
|
18
|
+
throw new TypeError("@@toPrimitive must return a primitive value.");
|
|
19
|
+
}
|
|
20
|
+
return ("string" === r ? String : Number)(t);
|
|
21
|
+
}
|
|
22
|
+
//#endregion
|
|
23
|
+
//#region \0@oxc-project+runtime@0.122.0/helpers/toPropertyKey.js
|
|
24
|
+
function toPropertyKey(t) {
|
|
25
|
+
var i = toPrimitive(t, "string");
|
|
26
|
+
return "symbol" == _typeof(i) ? i : i + "";
|
|
27
|
+
}
|
|
28
|
+
//#endregion
|
|
29
|
+
//#region \0@oxc-project+runtime@0.122.0/helpers/defineProperty.js
|
|
30
|
+
function _defineProperty(e, r, t) {
|
|
31
|
+
return (r = toPropertyKey(r)) in e ? Object.defineProperty(e, r, {
|
|
32
|
+
value: t,
|
|
33
|
+
enumerable: !0,
|
|
34
|
+
configurable: !0,
|
|
35
|
+
writable: !0
|
|
36
|
+
}) : e[r] = t, e;
|
|
37
|
+
}
|
|
38
|
+
//#endregion
|
|
39
|
+
//#region src/loader.ts
|
|
40
|
+
var DEFAULT_CDN_BASE = "https://messaging-assets.ada.support";
|
|
41
|
+
var BRIDGE_ASSET_PATH = "/bridge.js";
|
|
42
|
+
var PINNED_BRIDGE_ENTRY_PATH = "bridge/bridge.js";
|
|
43
|
+
var PINNED_BUILD_SHA_PATTERN = /^[0-9a-f]{40}$/;
|
|
44
|
+
var UNSTAMPED_BUILD_SHA_PATTERN = /^0{40}$/;
|
|
45
|
+
/**
|
|
46
|
+
* Typed failure raised by {@link loadMessagingBridge} and
|
|
47
|
+
* {@link resolveBridgeCdnUrl}. Mirrors the sdk loader's
|
|
48
|
+
* `MessagingSdkLoadError` so both loaders share one error contract.
|
|
49
|
+
*/
|
|
50
|
+
var MessagingBridgeLoadError = class extends Error {
|
|
51
|
+
constructor(message, code, cause) {
|
|
52
|
+
super(message);
|
|
53
|
+
_defineProperty(this, "code", void 0);
|
|
54
|
+
this.name = "MessagingBridgeLoadError";
|
|
55
|
+
this.code = code;
|
|
56
|
+
if (cause !== void 0) this.cause = cause;
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
var defaultImportModule = (url) => import(
|
|
60
|
+
/* webpackIgnore: true */
|
|
61
|
+
/* @vite-ignore */
|
|
62
|
+
url
|
|
63
|
+
);
|
|
64
|
+
var inFlightByImporter = /* @__PURE__ */ new WeakMap();
|
|
65
|
+
var LOOPBACK_HOSTS = new Set([
|
|
66
|
+
"localhost",
|
|
67
|
+
"127.0.0.1",
|
|
68
|
+
"[::1]",
|
|
69
|
+
"::1"
|
|
70
|
+
]);
|
|
71
|
+
function isLoopbackHostname(hostname) {
|
|
72
|
+
const normalized = hostname.trim().toLowerCase();
|
|
73
|
+
return LOOPBACK_HOSTS.has(normalized) || normalized.endsWith(".localhost");
|
|
74
|
+
}
|
|
75
|
+
function resolvePinnedBuildSha(pinBuildSha) {
|
|
76
|
+
const sha = pinBuildSha.trim().toLowerCase();
|
|
77
|
+
if (!PINNED_BUILD_SHA_PATTERN.test(sha)) throw new MessagingBridgeLoadError(`The pinBuildSha option must be a full 40-character hex git SHA: "${pinBuildSha}".`, "invalid_build_sha");
|
|
78
|
+
if (UNSTAMPED_BUILD_SHA_PATTERN.test(sha)) throw new MessagingBridgeLoadError("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.", "unstamped_build_sha");
|
|
79
|
+
return sha;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Resolve the full bridge asset URL for the given options: trailing slashes
|
|
83
|
+
* are stripped from `cdnBase` and `/bridge.js` is appended — or, with
|
|
84
|
+
* `pinBuildSha`, `/<sha>/bridge/bridge.js` for that build's immutable copy.
|
|
85
|
+
*
|
|
86
|
+
* The runtime this URL serves carries the bridge's security logic (origin
|
|
87
|
+
* derivation, the TOFU origin lock, the hydration TTL), so a cleartext
|
|
88
|
+
* transport would let an on-path attacker replace it wholesale: `https:` is
|
|
89
|
+
* required, with a loopback-only `http:` exception for local development.
|
|
90
|
+
*
|
|
91
|
+
* @throws {MessagingBridgeLoadError} with code `invalid_cdn_base` when
|
|
92
|
+
* `cdnBase` does not form a valid URL under that policy, `invalid_build_sha`
|
|
93
|
+
* when `pinBuildSha` is not a full 40-hex git SHA, or `unstamped_build_sha`
|
|
94
|
+
* when it is the unstamped placeholder.
|
|
95
|
+
*/
|
|
96
|
+
function resolveBridgeCdnUrl(options) {
|
|
97
|
+
const base = (options?.cdnBase ?? DEFAULT_CDN_BASE).trim().replace(/\/+$/, "");
|
|
98
|
+
const assetPath = options?.pinBuildSha === void 0 ? BRIDGE_ASSET_PATH : `/${resolvePinnedBuildSha(options.pinBuildSha)}/${PINNED_BRIDGE_ENTRY_PATH}`;
|
|
99
|
+
let url;
|
|
100
|
+
try {
|
|
101
|
+
url = new URL(`${base}${assetPath}`);
|
|
102
|
+
} catch (cause) {
|
|
103
|
+
throw new MessagingBridgeLoadError(`The cdnBase option is not a valid URL: "${base}".`, "invalid_cdn_base", cause);
|
|
104
|
+
}
|
|
105
|
+
const isHttps = url.protocol === "https:";
|
|
106
|
+
const isLoopbackHttp = url.protocol === "http:" && isLoopbackHostname(url.hostname);
|
|
107
|
+
if (!isHttps && !isLoopbackHttp) throw new MessagingBridgeLoadError(`The cdnBase option must use https (plain http is allowed only for loopback hosts): "${base}".`, "invalid_cdn_base");
|
|
108
|
+
return url.toString();
|
|
109
|
+
}
|
|
110
|
+
function isMessagingBridgeModule(value) {
|
|
111
|
+
return typeof value === "object" && value !== null && typeof value.createBridgeClient === "function";
|
|
112
|
+
}
|
|
113
|
+
async function importBridgeModule(url, importModule) {
|
|
114
|
+
let loaded;
|
|
115
|
+
try {
|
|
116
|
+
loaded = await importModule(url);
|
|
117
|
+
} catch (cause) {
|
|
118
|
+
throw new MessagingBridgeLoadError(`The Ada bridge runtime failed to load from ${url}.`, "bridge_import_failed", cause);
|
|
119
|
+
}
|
|
120
|
+
if (!isMessagingBridgeModule(loaded)) throw new MessagingBridgeLoadError(`The module at ${url} does not export createBridgeClient; it is not an Ada bridge build.`, "bridge_module_invalid");
|
|
121
|
+
return loaded;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Load the Ada bridge runtime from the CDN.
|
|
125
|
+
*
|
|
126
|
+
* The returned promise is memoized per resolved URL: concurrent and repeated
|
|
127
|
+
* calls with the same `cdnBase` share one in-flight import. A failed load is
|
|
128
|
+
* evicted from the memo so a later call can retry. Rejections are always
|
|
129
|
+
* {@link MessagingBridgeLoadError}.
|
|
130
|
+
*
|
|
131
|
+
* @example
|
|
132
|
+
* const { createBridgeClient, STATE } = await loadMessagingBridge();
|
|
133
|
+
*/
|
|
134
|
+
function loadMessagingBridge(options = {}) {
|
|
135
|
+
const importModule = options.importModule ?? defaultImportModule;
|
|
136
|
+
let url;
|
|
137
|
+
try {
|
|
138
|
+
url = resolveBridgeCdnUrl(options);
|
|
139
|
+
} catch (error) {
|
|
140
|
+
return Promise.reject(error);
|
|
141
|
+
}
|
|
142
|
+
let inFlight = inFlightByImporter.get(importModule);
|
|
143
|
+
if (!inFlight) {
|
|
144
|
+
inFlight = /* @__PURE__ */ new Map();
|
|
145
|
+
inFlightByImporter.set(importModule, inFlight);
|
|
146
|
+
}
|
|
147
|
+
const existing = inFlight.get(url);
|
|
148
|
+
if (existing) return existing;
|
|
149
|
+
const cache = inFlight;
|
|
150
|
+
const pending = importBridgeModule(url, importModule);
|
|
151
|
+
cache.set(url, pending);
|
|
152
|
+
pending.catch(() => {
|
|
153
|
+
if (cache.get(url) === pending) cache.delete(url);
|
|
154
|
+
});
|
|
155
|
+
return pending;
|
|
156
|
+
}
|
|
157
|
+
//#endregion
|
|
158
|
+
export { loadMessagingBridge as n, resolveBridgeCdnUrl as r, MessagingBridgeLoadError as t };
|
|
159
|
+
|
|
160
|
+
//# sourceMappingURL=loader-CVynp73o.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"loader-CVynp73o.js","names":[],"sources":["../src/loader.ts"],"sourcesContent":["import type { BridgeClient, createBridgeClient } from \"./bridge-client\";\nimport type {\n\tfilterDisplayable,\n\tfindFirstUnread,\n\tgroupMessages,\n\tisAgentTyping,\n\tisConnectivityLost,\n\tisHistoricalRow,\n\tmessageKey,\n\tresolveBotName,\n\tselectUnread,\n} from \"./derive\";\nimport type { STATE } from \"./state-keys\";\n\nconst DEFAULT_CDN_BASE = \"https://messaging-assets.ada.support\";\nconst BRIDGE_ASSET_PATH = \"/bridge.js\";\n\n// The CDN pipeline uploads each main commit's bridge runtime as an immutable\n// SHA-rooted copy (`<cdnBase>/<sha>/bridge/bridge.js`); the mutable root\n// `bridge.js` is the loader fence's copy of one of those. A pin bypasses the\n// root asset and imports one immutable copy directly.\nconst PINNED_BRIDGE_ENTRY_PATH = \"bridge/bridge.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 the sdk loader's boundary).\nconst PINNED_BUILD_SHA_PATTERN = /^[0-9a-f]{40}$/;\nconst UNSTAMPED_BUILD_SHA_PATTERN = /^0{40}$/;\n\n/** Why a {@link MessagingBridgeLoadError} was raised. */\nexport type MessagingBridgeLoadErrorCode =\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 asset failed (network, CSP, 404). */\n\t| \"bridge_import_failed\"\n\t/** The imported module does not expose the Ada bridge surface. */\n\t| \"bridge_module_invalid\";\n\n/**\n * Typed failure raised by {@link loadMessagingBridge} and\n * {@link resolveBridgeCdnUrl}. Mirrors the sdk loader's\n * `MessagingSdkLoadError` so both loaders share one error contract.\n */\nexport class MessagingBridgeLoadError extends Error {\n\treadonly code: MessagingBridgeLoadErrorCode;\n\n\tconstructor(\n\t\tmessage: string,\n\t\tcode: MessagingBridgeLoadErrorCode,\n\t\tcause?: unknown,\n\t) {\n\t\tsuper(message);\n\t\tthis.name = \"MessagingBridgeLoadError\";\n\t\tthis.code = code;\n\t\tif (cause !== undefined) {\n\t\t\tthis.cause = cause;\n\t\t}\n\t}\n}\n\n/**\n * The module shape served at `<cdnBase>/bridge.js`.\n *\n * The npm package never carries this runtime — it always resolves from Ada's\n * CDN so security-bearing logic (origin derivation, the TOFU origin lock, the\n * hydration TTL) stays patchable by Ada without a customer release.\n */\nexport interface MessagingBridgeModule {\n\t/**\n\t * Create a {@link BridgeClient} connected to the parent core frame.\n\t * Declared as `typeof` the runtime symbol so the published type cannot\n\t * drift from the CDN implementation (pinned by types-drift.test.ts).\n\t */\n\tcreateBridgeClient: typeof createBridgeClient;\n\t/** Typed constants for all bridge state keys. */\n\tSTATE: typeof STATE;\n\t// Pure derivation helpers (src/derive.ts) — logic mined from Ada's\n\t// reference app, distributed with the runtime so it stays Ada-patchable.\n\t// Older cached bridge.js builds may predate them: feature-detect before\n\t// depending on one in code that must tolerate a stale CDN cache.\n\t/** Stable message identity across the stream-final and optimistic-durable id swaps. */\n\tmessageKey: typeof messageKey;\n\t/** Whether a row belongs to a conversation the chatter has left. */\n\tisHistoricalRow: typeof isHistoricalRow;\n\t/** First unread bot/agent message past the persisted read watermark. */\n\tfindFirstUnread: typeof findFirstUnread;\n\t/** Unread divider anchor + count derived from state. */\n\tselectUnread: typeof selectUnread;\n\t/** Per-index date-divider and sender-run grouping decisions. */\n\tgroupMessages: typeof groupMessages;\n\t/** The messages a transcript should actually render. */\n\tfilterDisplayable: typeof filterDisplayable;\n\t/** Bot display name: `config.botName`, else `config.handle`, else \"Ada\". */\n\tresolveBotName: typeof resolveBotName;\n\t/** Someone is composing a reply (core's typing verdict + active agent). */\n\tisAgentTyping: typeof isAgentTyping;\n\t/** Core's connectivity verdict — never substitute `navigator.onLine`. */\n\tisConnectivityLost: typeof isConnectivityLost;\n}\n\ntype ImportBridgeModule = (url: string) => Promise<unknown>;\n\n/**\n * Options for {@link loadMessagingBridge}.\n */\nexport interface LoadMessagingBridgeOptions {\n\t/**\n\t * Origin the bridge runtime is loaded from. Must be an `https:` URL;\n\t * plain `http:` is accepted only for loopback hosts (`localhost`,\n\t * `127.0.0.1`, `[::1]`, `*.localhost`) during local development.\n\t * Defaults to Ada's production asset host\n\t * (`https://messaging-assets.ada.support`). Override only for staging\n\t * validation against a non-production asset host.\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 mutable root `bridge.js` and imports that build's\n\t * immutable copy (`<cdnBase>/<sha>/bridge/bridge.js`) directly. Pass the\n\t * package's `CDN_BUILD_SHA` export to pin the build associated with this\n\t * npm version. NOT recommended for production: a pinned runtime misses\n\t * Ada's fixes and the loader-fence rollout, and may predate core/server\n\t * contract changes. Composes 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 asset URL.\n\t */\n\timportModule?: ImportBridgeModule;\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: ImportBridgeModule = (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\tImportBridgeModule,\n\tMap<string, Promise<MessagingBridgeModule>>\n>();\n\n// Mirrors the loopback semantics of the shared `isLoopbackHost` /\n// `validateMessagingEndpoint` (shared/utils/src/url.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 MessagingBridgeLoadError(\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 MessagingBridgeLoadError(\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 * Resolve the full bridge asset URL for the given options: trailing slashes\n * are stripped from `cdnBase` and `/bridge.js` is appended — or, with\n * `pinBuildSha`, `/<sha>/bridge/bridge.js` for that build's immutable copy.\n *\n * The runtime this URL serves carries the bridge's security logic (origin\n * derivation, the TOFU origin lock, the hydration TTL), so a cleartext\n * transport would let an on-path attacker replace it wholesale: `https:` is\n * required, with a loopback-only `http:` exception for local development.\n *\n * @throws {MessagingBridgeLoadError} with code `invalid_cdn_base` when\n * `cdnBase` does not form a valid URL under that policy, `invalid_build_sha`\n * when `pinBuildSha` is not a full 40-hex git SHA, or `unstamped_build_sha`\n * when it is the unstamped placeholder.\n */\nexport function resolveBridgeCdnUrl(\n\toptions?: Pick<LoadMessagingBridgeOptions, \"cdnBase\" | \"pinBuildSha\">,\n): string {\n\tconst base = (options?.cdnBase ?? DEFAULT_CDN_BASE)\n\t\t.trim()\n\t\t.replace(/\\/+$/, \"\");\n\tconst assetPath =\n\t\toptions?.pinBuildSha === undefined\n\t\t\t? BRIDGE_ASSET_PATH\n\t\t\t: `/${resolvePinnedBuildSha(options.pinBuildSha)}/${PINNED_BRIDGE_ENTRY_PATH}`;\n\tlet url: URL;\n\ttry {\n\t\turl = new URL(`${base}${assetPath}`);\n\t} catch (cause) {\n\t\tthrow new MessagingBridgeLoadError(\n\t\t\t`The cdnBase option is not a valid URL: \"${base}\".`,\n\t\t\t\"invalid_cdn_base\",\n\t\t\tcause,\n\t\t);\n\t}\n\tconst isHttps = url.protocol === \"https:\";\n\tconst isLoopbackHttp =\n\t\turl.protocol === \"http:\" && isLoopbackHostname(url.hostname);\n\tif (!isHttps && !isLoopbackHttp) {\n\t\tthrow new MessagingBridgeLoadError(\n\t\t\t`The cdnBase option must use https (plain http is allowed only for loopback hosts): \"${base}\".`,\n\t\t\t\"invalid_cdn_base\",\n\t\t);\n\t}\n\treturn url.toString();\n}\n\nfunction isMessagingBridgeModule(\n\tvalue: unknown,\n): value is MessagingBridgeModule {\n\treturn (\n\t\ttypeof value === \"object\" &&\n\t\tvalue !== null &&\n\t\ttypeof (value as Record<string, unknown>).createBridgeClient === \"function\"\n\t);\n}\n\nasync function importBridgeModule(\n\turl: string,\n\timportModule: ImportBridgeModule,\n): Promise<MessagingBridgeModule> {\n\tlet loaded: unknown;\n\ttry {\n\t\tloaded = await importModule(url);\n\t} catch (cause) {\n\t\tthrow new MessagingBridgeLoadError(\n\t\t\t`The Ada bridge runtime failed to load from ${url}.`,\n\t\t\t\"bridge_import_failed\",\n\t\t\tcause,\n\t\t);\n\t}\n\tif (!isMessagingBridgeModule(loaded)) {\n\t\tthrow new MessagingBridgeLoadError(\n\t\t\t`The module at ${url} does not export createBridgeClient; it is not an Ada bridge build.`,\n\t\t\t\"bridge_module_invalid\",\n\t\t);\n\t}\n\treturn loaded;\n}\n\n/**\n * Load the Ada bridge runtime from the CDN.\n *\n * The returned promise is memoized per resolved URL: concurrent and repeated\n * calls with the same `cdnBase` share one in-flight import. A failed load is\n * evicted from the memo so a later call can retry. Rejections are always\n * {@link MessagingBridgeLoadError}.\n *\n * @example\n * const { createBridgeClient, STATE } = await loadMessagingBridge();\n */\nexport function loadMessagingBridge(\n\toptions: LoadMessagingBridgeOptions = {},\n): Promise<MessagingBridgeModule> {\n\tconst importModule = options.importModule ?? defaultImportModule;\n\tlet url: string;\n\ttry {\n\t\turl = resolveBridgeCdnUrl(options);\n\t} catch (error) {\n\t\treturn Promise.reject(error);\n\t}\n\n\tlet inFlight = inFlightByImporter.get(importModule);\n\tif (!inFlight) {\n\t\tinFlight = new Map();\n\t\tinFlightByImporter.set(importModule, inFlight);\n\t}\n\tconst existing = inFlight.get(url);\n\tif (existing) {\n\t\treturn existing;\n\t}\n\n\tconst cache = inFlight;\n\tconst pending = importBridgeModule(url, importModule);\n\tcache.set(url, pending);\n\tpending.catch(() => {\n\t\tif (cache.get(url) === pending) {\n\t\t\tcache.delete(url);\n\t\t}\n\t});\n\treturn pending;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAcA,IAAM,mBAAmB;AACzB,IAAM,oBAAoB;AAM1B,IAAM,2BAA2B;AAIjC,IAAM,2BAA2B;AACjC,IAAM,8BAA8B;;;;;;AA2BpC,IAAa,2BAAb,cAA8C,MAAM;CAGnD,YACC,SACA,MACA,OACC;AACD,QAAM,QAAQ;wBAPf,QAAA,KAAA,EAAS;AAQR,OAAK,OAAO;AACZ,OAAK,OAAO;AACZ,MAAI,UAAU,KAAA,EACb,MAAK,QAAQ;;;AAiFhB,IAAM,uBAA2C,QAChD;;;CAAoD;;AAIrD,IAAM,qCAAqB,IAAI,SAG5B;AAKH,IAAM,iBAAiB,IAAI,IAAI;CAAC;CAAa;CAAa;CAAS;CAAM,CAAC;AAE1E,SAAS,mBAAmB,UAA2B;CACtD,MAAM,aAAa,SAAS,MAAM,CAAC,aAAa;AAIhD,QAAO,eAAe,IAAI,WAAW,IAAI,WAAW,SAAS,aAAa;;AAG3E,SAAS,sBAAsB,aAA6B;CAC3D,MAAM,MAAM,YAAY,MAAM,CAAC,aAAa;AAC5C,KAAI,CAAC,yBAAyB,KAAK,IAAI,CACtC,OAAM,IAAI,yBACT,oEAAoE,YAAY,KAChF,oBACA;AAEF,KAAI,4BAA4B,KAAK,IAAI,CACxC,OAAM,IAAI,yBACT,gLACA,sBACA;AAEF,QAAO;;;;;;;;;;;;;;;;;AAkBR,SAAgB,oBACf,SACS;CACT,MAAM,QAAQ,SAAS,WAAW,kBAChC,MAAM,CACN,QAAQ,QAAQ,GAAG;CACrB,MAAM,YACL,SAAS,gBAAgB,KAAA,IACtB,oBACA,IAAI,sBAAsB,QAAQ,YAAY,CAAC,GAAG;CACtD,IAAI;AACJ,KAAI;AACH,QAAM,IAAI,IAAI,GAAG,OAAO,YAAY;UAC5B,OAAO;AACf,QAAM,IAAI,yBACT,2CAA2C,KAAK,KAChD,oBACA,MACA;;CAEF,MAAM,UAAU,IAAI,aAAa;CACjC,MAAM,iBACL,IAAI,aAAa,WAAW,mBAAmB,IAAI,SAAS;AAC7D,KAAI,CAAC,WAAW,CAAC,eAChB,OAAM,IAAI,yBACT,uFAAuF,KAAK,KAC5F,mBACA;AAEF,QAAO,IAAI,UAAU;;AAGtB,SAAS,wBACR,OACiC;AACjC,QACC,OAAO,UAAU,YACjB,UAAU,QACV,OAAQ,MAAkC,uBAAuB;;AAInE,eAAe,mBACd,KACA,cACiC;CACjC,IAAI;AACJ,KAAI;AACH,WAAS,MAAM,aAAa,IAAI;UACxB,OAAO;AACf,QAAM,IAAI,yBACT,8CAA8C,IAAI,IAClD,wBACA,MACA;;AAEF,KAAI,CAAC,wBAAwB,OAAO,CACnC,OAAM,IAAI,yBACT,iBAAiB,IAAI,sEACrB,wBACA;AAEF,QAAO;;;;;;;;;;;;;AAcR,SAAgB,oBACf,UAAsC,EAAE,EACP;CACjC,MAAM,eAAe,QAAQ,gBAAgB;CAC7C,IAAI;AACJ,KAAI;AACH,QAAM,oBAAoB,QAAQ;UAC1B,OAAO;AACf,SAAO,QAAQ,OAAO,MAAM;;CAG7B,IAAI,WAAW,mBAAmB,IAAI,aAAa;AACnD,KAAI,CAAC,UAAU;AACd,6BAAW,IAAI,KAAK;AACpB,qBAAmB,IAAI,cAAc,SAAS;;CAE/C,MAAM,WAAW,SAAS,IAAI,IAAI;AAClC,KAAI,SACH,QAAO;CAGR,MAAM,QAAQ;CACd,MAAM,UAAU,mBAAmB,KAAK,aAAa;AACrD,OAAM,IAAI,KAAK,QAAQ;AACvB,SAAQ,YAAY;AACnB,MAAI,MAAM,IAAI,IAAI,KAAK,QACtB,OAAM,OAAO,IAAI;GAEjB;AACF,QAAO"}
|
package/dist/loader.d.ts
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import type { createBridgeClient } from "./bridge-client.js";
|
|
2
|
+
import type { filterDisplayable, findFirstUnread, groupMessages, isAgentTyping, isConnectivityLost, isHistoricalRow, messageKey, resolveBotName, selectUnread } from "./derive.js";
|
|
3
|
+
import type { STATE } from "./state-keys.js";
|
|
4
|
+
/** Why a {@link MessagingBridgeLoadError} was raised. */
|
|
5
|
+
export type MessagingBridgeLoadErrorCode =
|
|
6
|
+
/**
|
|
7
|
+
* The `cdnBase` option is not a valid `https:` URL (plain `http:` is
|
|
8
|
+
* allowed only for loopback hosts).
|
|
9
|
+
*/
|
|
10
|
+
"invalid_cdn_base"
|
|
11
|
+
/** The `pinBuildSha` option is not a full 40-character hex git SHA. */
|
|
12
|
+
| "invalid_build_sha"
|
|
13
|
+
/**
|
|
14
|
+
* The `pinBuildSha` option is the unstamped 40-zero placeholder: this
|
|
15
|
+
* copy of the package was built from the repository, not published, so
|
|
16
|
+
* its `CDN_BUILD_SHA` names no CDN build.
|
|
17
|
+
*/
|
|
18
|
+
| "unstamped_build_sha"
|
|
19
|
+
/** The dynamic import of the CDN asset failed (network, CSP, 404). */
|
|
20
|
+
| "bridge_import_failed"
|
|
21
|
+
/** The imported module does not expose the Ada bridge surface. */
|
|
22
|
+
| "bridge_module_invalid";
|
|
23
|
+
/**
|
|
24
|
+
* Typed failure raised by {@link loadMessagingBridge} and
|
|
25
|
+
* {@link resolveBridgeCdnUrl}. Mirrors the sdk loader's
|
|
26
|
+
* `MessagingSdkLoadError` so both loaders share one error contract.
|
|
27
|
+
*/
|
|
28
|
+
export declare class MessagingBridgeLoadError extends Error {
|
|
29
|
+
readonly code: MessagingBridgeLoadErrorCode;
|
|
30
|
+
constructor(message: string, code: MessagingBridgeLoadErrorCode, cause?: unknown);
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* The module shape served at `<cdnBase>/bridge.js`.
|
|
34
|
+
*
|
|
35
|
+
* The npm package never carries this runtime — it always resolves from Ada's
|
|
36
|
+
* CDN so security-bearing logic (origin derivation, the TOFU origin lock, the
|
|
37
|
+
* hydration TTL) stays patchable by Ada without a customer release.
|
|
38
|
+
*/
|
|
39
|
+
export interface MessagingBridgeModule {
|
|
40
|
+
/**
|
|
41
|
+
* Create a {@link BridgeClient} connected to the parent core frame.
|
|
42
|
+
* Declared as `typeof` the runtime symbol so the published type cannot
|
|
43
|
+
* drift from the CDN implementation (pinned by types-drift.test.ts).
|
|
44
|
+
*/
|
|
45
|
+
createBridgeClient: typeof createBridgeClient;
|
|
46
|
+
/** Typed constants for all bridge state keys. */
|
|
47
|
+
STATE: typeof STATE;
|
|
48
|
+
/** Stable message identity across the stream-final and optimistic-durable id swaps. */
|
|
49
|
+
messageKey: typeof messageKey;
|
|
50
|
+
/** Whether a row belongs to a conversation the chatter has left. */
|
|
51
|
+
isHistoricalRow: typeof isHistoricalRow;
|
|
52
|
+
/** First unread bot/agent message past the persisted read watermark. */
|
|
53
|
+
findFirstUnread: typeof findFirstUnread;
|
|
54
|
+
/** Unread divider anchor + count derived from state. */
|
|
55
|
+
selectUnread: typeof selectUnread;
|
|
56
|
+
/** Per-index date-divider and sender-run grouping decisions. */
|
|
57
|
+
groupMessages: typeof groupMessages;
|
|
58
|
+
/** The messages a transcript should actually render. */
|
|
59
|
+
filterDisplayable: typeof filterDisplayable;
|
|
60
|
+
/** Bot display name: `config.botName`, else `config.handle`, else "Ada". */
|
|
61
|
+
resolveBotName: typeof resolveBotName;
|
|
62
|
+
/** Someone is composing a reply (core's typing verdict + active agent). */
|
|
63
|
+
isAgentTyping: typeof isAgentTyping;
|
|
64
|
+
/** Core's connectivity verdict — never substitute `navigator.onLine`. */
|
|
65
|
+
isConnectivityLost: typeof isConnectivityLost;
|
|
66
|
+
}
|
|
67
|
+
type ImportBridgeModule = (url: string) => Promise<unknown>;
|
|
68
|
+
/**
|
|
69
|
+
* Options for {@link loadMessagingBridge}.
|
|
70
|
+
*/
|
|
71
|
+
export interface LoadMessagingBridgeOptions {
|
|
72
|
+
/**
|
|
73
|
+
* Origin the bridge runtime is loaded from. Must be an `https:` URL;
|
|
74
|
+
* plain `http:` is accepted only for loopback hosts (`localhost`,
|
|
75
|
+
* `127.0.0.1`, `[::1]`, `*.localhost`) during local development.
|
|
76
|
+
* Defaults to Ada's production asset host
|
|
77
|
+
* (`https://messaging-assets.ada.support`). Override only for staging
|
|
78
|
+
* validation against a non-production asset host.
|
|
79
|
+
*/
|
|
80
|
+
cdnBase?: string;
|
|
81
|
+
/**
|
|
82
|
+
* Full 40-character git commit SHA of a deployed CDN build. When set, the
|
|
83
|
+
* loader skips the mutable root `bridge.js` and imports that build's
|
|
84
|
+
* immutable copy (`<cdnBase>/<sha>/bridge/bridge.js`) directly. Pass the
|
|
85
|
+
* package's `CDN_BUILD_SHA` export to pin the build associated with this
|
|
86
|
+
* npm version. NOT recommended for production: a pinned runtime misses
|
|
87
|
+
* Ada's fixes and the loader-fence rollout, and may predate core/server
|
|
88
|
+
* contract changes. Composes with `cdnBase` for staged validation.
|
|
89
|
+
*/
|
|
90
|
+
pinBuildSha?: string;
|
|
91
|
+
/**
|
|
92
|
+
* Import seam for unit tests. Defaults to a native dynamic `import()` of
|
|
93
|
+
* the resolved asset URL.
|
|
94
|
+
*/
|
|
95
|
+
importModule?: ImportBridgeModule;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Resolve the full bridge asset URL for the given options: trailing slashes
|
|
99
|
+
* are stripped from `cdnBase` and `/bridge.js` is appended — or, with
|
|
100
|
+
* `pinBuildSha`, `/<sha>/bridge/bridge.js` for that build's immutable copy.
|
|
101
|
+
*
|
|
102
|
+
* The runtime this URL serves carries the bridge's security logic (origin
|
|
103
|
+
* derivation, the TOFU origin lock, the hydration TTL), so a cleartext
|
|
104
|
+
* transport would let an on-path attacker replace it wholesale: `https:` is
|
|
105
|
+
* required, with a loopback-only `http:` exception for local development.
|
|
106
|
+
*
|
|
107
|
+
* @throws {MessagingBridgeLoadError} with code `invalid_cdn_base` when
|
|
108
|
+
* `cdnBase` does not form a valid URL under that policy, `invalid_build_sha`
|
|
109
|
+
* when `pinBuildSha` is not a full 40-hex git SHA, or `unstamped_build_sha`
|
|
110
|
+
* when it is the unstamped placeholder.
|
|
111
|
+
*/
|
|
112
|
+
export declare function resolveBridgeCdnUrl(options?: Pick<LoadMessagingBridgeOptions, "cdnBase" | "pinBuildSha">): string;
|
|
113
|
+
/**
|
|
114
|
+
* Load the Ada bridge runtime from the CDN.
|
|
115
|
+
*
|
|
116
|
+
* The returned promise is memoized per resolved URL: concurrent and repeated
|
|
117
|
+
* calls with the same `cdnBase` share one in-flight import. A failed load is
|
|
118
|
+
* evicted from the memo so a later call can retry. Rejections are always
|
|
119
|
+
* {@link MessagingBridgeLoadError}.
|
|
120
|
+
*
|
|
121
|
+
* @example
|
|
122
|
+
* const { createBridgeClient, STATE } = await loadMessagingBridge();
|
|
123
|
+
*/
|
|
124
|
+
export declare function loadMessagingBridge(options?: LoadMessagingBridgeOptions): Promise<MessagingBridgeModule>;
|
|
125
|
+
export {};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@ada-cx/messaging-bridge` — published npm entry.
|
|
3
|
+
*
|
|
4
|
+
* Carries only TypeScript types and the thin CDN loader. The bridge runtime
|
|
5
|
+
* (`createBridgeClient`, `STATE`) always resolves from Ada's CDN via
|
|
6
|
+
* `loadMessagingBridge()` so Ada can patch security-bearing logic without a
|
|
7
|
+
* customer release. Never add runtime implementation to this entry.
|
|
8
|
+
*/
|
|
9
|
+
export type { FileUploadErrorData, FileUploadErrorReason, } from "./shared-utils/index.js";
|
|
10
|
+
export type { BridgeClient } from "./bridge-client.js";
|
|
11
|
+
export { CDN_BUILD_SHA, isCdnBuildShaStamped } from "./build-info.js";
|
|
12
|
+
export type { MessageGroupingEntry, UnreadSelection } from "./derive.js";
|
|
13
|
+
export type { LoadMessagingBridgeOptions, MessagingBridgeLoadErrorCode, MessagingBridgeModule, } from "./loader.js";
|
|
14
|
+
export { loadMessagingBridge, MessagingBridgeLoadError, resolveBridgeCdnUrl, } from "./loader.js";
|
|
15
|
+
export type { BridgeClientDestroyedError, BridgeOperations, BridgeOperationsHost, CaptureHandle, CsatSubmitHandle, EndChatEligibilityResult, FileUploadHandle, ObserveOptions, OperationResult, SendHandle, SettleOptions, } from "./operations.js";
|
|
16
|
+
export type { StateKey } from "./state-keys.js";
|
|
17
|
+
export type { AppDisplayState, AppEvents, CaptureData, CsatMessage, CsatScaleType, DownloadableMessage, FallbackUiConfig, LinkMessage, ListSelectionData, LiveChatStateData, Message, NotificationPermissionMessage, OptionItem, OptionsMessage, PictureMessage, PresenceMessage, QueueBotBlockMessage, QuickRepliesMessage, QuickReply, SdkPlatform, SeamlessOAuthMessage, SelectableListItem, SelectableListMessage, SignInMessage, TextMessage, VideoMessage, WidgetMessage, } from "./types.js";
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { n as loadMessagingBridge, r as resolveBridgeCdnUrl, t as MessagingBridgeLoadError } from "./loader-CVynp73o.js";
|
|
2
|
+
//#region src/build-info.ts
|
|
3
|
+
var GIT_SHA_PATTERN = /^[0-9a-f]{40}$/;
|
|
4
|
+
var UNSTAMPED_SHA_PATTERN = /^0{40}$/;
|
|
5
|
+
/**
|
|
6
|
+
* Git commit SHA of the monorepo commit this npm version was published from.
|
|
7
|
+
* The CDN pipeline deploys each main commit's bridge runtime under an
|
|
8
|
+
* immutable SHA root, so this value names the CDN build associated with this
|
|
9
|
+
* npm version. Pass it as the `pinBuildSha` loader option to load exactly
|
|
10
|
+
* that runtime build — not recommended for production (see the README).
|
|
11
|
+
*
|
|
12
|
+
* In the repository, and in any locally built copy, the value is the
|
|
13
|
+
* unstamped 40-zero placeholder ({@link isCdnBuildShaStamped} returns
|
|
14
|
+
* `false`); only published tarballs carry a real SHA.
|
|
15
|
+
*/
|
|
16
|
+
var CDN_BUILD_SHA = "0000000000000000000000000000000000000000";
|
|
17
|
+
/**
|
|
18
|
+
* Whether `sha` (default {@link CDN_BUILD_SHA}) is a stamped full git commit
|
|
19
|
+
* SHA rather than the unstamped repo placeholder.
|
|
20
|
+
*/
|
|
21
|
+
function isCdnBuildShaStamped(sha = CDN_BUILD_SHA) {
|
|
22
|
+
const normalized = sha.trim().toLowerCase();
|
|
23
|
+
return GIT_SHA_PATTERN.test(normalized) && !UNSTAMPED_SHA_PATTERN.test(normalized);
|
|
24
|
+
}
|
|
25
|
+
//#endregion
|
|
26
|
+
export { CDN_BUILD_SHA, MessagingBridgeLoadError, isCdnBuildShaStamped, loadMessagingBridge, resolveBridgeCdnUrl };
|
|
27
|
+
|
|
28
|
+
//# sourceMappingURL=npm-index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"npm-index.js","names":[],"sources":["../src/build-info.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 packages/bridge/dist output via\n// scripts/stamp-cdn-build-sha.mjs and verifies the packed tarball before\n// `npm publish`. Keep the placeholder as a single 40-zero string literal:\n// the stamp is a same-length text 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 bridge runtime under an\n * immutable SHA root, so this value names the CDN build associated with this\n * npm version. Pass it as the `pinBuildSha` loader option to load exactly\n * that 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 = \"0000000000000000000000000000000000000000\";\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"],"mappings":";;AAMA,IAAM,kBAAkB;AACxB,IAAM,wBAAwB;;;;;;;;;;;;AAa9B,IAAa,gBAAwB;;;;;AAMrC,SAAgB,qBAAqB,MAAc,eAAwB;CAC1E,MAAM,aAAa,IAAI,MAAM,CAAC,aAAa;AAC3C,QACC,gBAAgB,KAAK,WAAW,IAAI,CAAC,sBAAsB,KAAK,WAAW"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@ada-cx/messaging-bridge/react` — published npm entry.
|
|
3
|
+
*
|
|
4
|
+
* Trivial delegation only: the provider and hooks accept a `BridgeClient`
|
|
5
|
+
* created by the CDN-loaded runtime. Never import `createBridgeClient` here —
|
|
6
|
+
* the security-bearing runtime must not reach the npm tarball.
|
|
7
|
+
*/
|
|
8
|
+
export type { BridgeClient } from "./bridge-client.js";
|
|
9
|
+
export type { LoadedBridgeProviderProps } from "./bridge-react.js";
|
|
10
|
+
export { BridgeProvider, createBridgeProvider, useBridgeClient, useBridgeState, useBridgeStateKey, } from "./bridge-react.js";
|
|
11
|
+
export type { LoadMessagingBridgeOptions, MessagingBridgeLoadErrorCode, MessagingBridgeModule, } from "./loader.js";
|
|
12
|
+
export { loadMessagingBridge, MessagingBridgeLoadError, resolveBridgeCdnUrl, } from "./loader.js";
|
|
13
|
+
export type { AppDisplayState, AppEvents } from "./types.js";
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { n as loadMessagingBridge, r as resolveBridgeCdnUrl, t as MessagingBridgeLoadError } from "./loader-CVynp73o.js";
|
|
2
|
+
import { createContext, useCallback, useContext, useEffect, useRef, useState, useSyncExternalStore } from "react";
|
|
3
|
+
import { Fragment, jsx } from "react/jsx-runtime";
|
|
4
|
+
//#region src/bridge-react.tsx
|
|
5
|
+
var BridgeContext = createContext(null);
|
|
6
|
+
/**
|
|
7
|
+
* Provide a {@link BridgeClient} to the React tree.
|
|
8
|
+
*
|
|
9
|
+
* Pure delegation: the caller owns the client lifecycle. The provider does not
|
|
10
|
+
* create, initialize, or destroy the client — obtain one from the CDN module
|
|
11
|
+
* returned by `loadMessagingBridge()` (or use {@link createBridgeProvider} to
|
|
12
|
+
* have loading, `app.initialize`, and teardown handled for you).
|
|
13
|
+
*/
|
|
14
|
+
function BridgeProvider({ client, children }) {
|
|
15
|
+
return /* @__PURE__ */ jsx(BridgeContext.Provider, {
|
|
16
|
+
value: client,
|
|
17
|
+
children
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
/** The active {@link BridgeClient}. Throws outside a `BridgeProvider`. */
|
|
21
|
+
function useBridgeClient() {
|
|
22
|
+
const client = useContext(BridgeContext);
|
|
23
|
+
if (!client) throw new Error("useBridgeClient must be used within a BridgeProvider");
|
|
24
|
+
return client;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* The full display-state snapshot, or `null` before the first
|
|
28
|
+
* `core.state.update`. Re-renders on every state change.
|
|
29
|
+
*/
|
|
30
|
+
function useBridgeState() {
|
|
31
|
+
const client = useBridgeClient();
|
|
32
|
+
const subscribe = useCallback((callback) => client.subscribe(callback), [client]);
|
|
33
|
+
const getSnapshot = useCallback(() => client.getState(), [client]);
|
|
34
|
+
return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Returns a single typed value from the bridge state, or `null` when the state
|
|
38
|
+
* is not yet available. Provides full TypeScript autocomplete for the key names
|
|
39
|
+
* and returns the exact type declared in AppDisplayState.
|
|
40
|
+
*
|
|
41
|
+
* Re-renders only when THIS key's value changes (delegating to the client's
|
|
42
|
+
* `subscribeKey`), not on every state update the way `useBridgeState` does.
|
|
43
|
+
*/
|
|
44
|
+
function useBridgeStateKey(key) {
|
|
45
|
+
const client = useBridgeClient();
|
|
46
|
+
const subscribe = useCallback((onStoreChange) => typeof client.subscribeKey === "function" ? client.subscribeKey(key, onStoreChange) : client.subscribe(onStoreChange), [client, key]);
|
|
47
|
+
const getSnapshot = useCallback(() => {
|
|
48
|
+
const state = client.getState();
|
|
49
|
+
if (state === null) return null;
|
|
50
|
+
const value = state[key];
|
|
51
|
+
return value !== void 0 ? value : null;
|
|
52
|
+
}, [client, key]);
|
|
53
|
+
return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Build a provider component that loads the bridge runtime from the CDN,
|
|
57
|
+
* creates a client, sends the required `app.initialize` handshake, and
|
|
58
|
+
* destroys the client on unmount.
|
|
59
|
+
*
|
|
60
|
+
* The returned component renders `fallback` (default: nothing) until the
|
|
61
|
+
* runtime has loaded, then renders `children` inside a {@link BridgeProvider}.
|
|
62
|
+
* When the load fails it renders `errorFallback` (default: `fallback`) and
|
|
63
|
+
* reports the error through `onError` (default: `console.error`).
|
|
64
|
+
*
|
|
65
|
+
* @example
|
|
66
|
+
* const AdaBridgeProvider = createBridgeProvider();
|
|
67
|
+
* root.render(<AdaBridgeProvider><MyChatUi /></AdaBridgeProvider>);
|
|
68
|
+
*/
|
|
69
|
+
function createBridgeProvider(load = loadMessagingBridge) {
|
|
70
|
+
return function LoadedBridgeProvider({ children, fallback, errorFallback, onError }) {
|
|
71
|
+
const [client, setClient] = useState(null);
|
|
72
|
+
const [failed, setFailed] = useState(false);
|
|
73
|
+
const onErrorRef = useRef(onError);
|
|
74
|
+
useEffect(() => {
|
|
75
|
+
onErrorRef.current = onError;
|
|
76
|
+
});
|
|
77
|
+
useEffect(() => {
|
|
78
|
+
let disposed = false;
|
|
79
|
+
let created = null;
|
|
80
|
+
load().then((mod) => {
|
|
81
|
+
if (disposed) return;
|
|
82
|
+
created = mod.createBridgeClient();
|
|
83
|
+
created.sendEvent("app.initialize");
|
|
84
|
+
setClient(created);
|
|
85
|
+
}).catch((error) => {
|
|
86
|
+
if (disposed) return;
|
|
87
|
+
setFailed(true);
|
|
88
|
+
const handler = onErrorRef.current;
|
|
89
|
+
if (handler) {
|
|
90
|
+
handler(error);
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
console.error("[AdaMessaging] Failed to load the bridge runtime:", error);
|
|
94
|
+
});
|
|
95
|
+
return () => {
|
|
96
|
+
disposed = true;
|
|
97
|
+
created?.destroy();
|
|
98
|
+
};
|
|
99
|
+
}, []);
|
|
100
|
+
if (!client) {
|
|
101
|
+
if (failed) return /* @__PURE__ */ jsx(Fragment, { children: errorFallback ?? fallback ?? null });
|
|
102
|
+
return /* @__PURE__ */ jsx(Fragment, { children: fallback ?? null });
|
|
103
|
+
}
|
|
104
|
+
return /* @__PURE__ */ jsx(BridgeProvider, {
|
|
105
|
+
client,
|
|
106
|
+
children
|
|
107
|
+
});
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
//#endregion
|
|
111
|
+
export { BridgeProvider, MessagingBridgeLoadError, createBridgeProvider, loadMessagingBridge, resolveBridgeCdnUrl, useBridgeClient, useBridgeState, useBridgeStateKey };
|
|
112
|
+
|
|
113
|
+
//# sourceMappingURL=npm-react.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"npm-react.js","names":[],"sources":["../src/bridge-react.tsx"],"sourcesContent":["import {\n\tcreateContext,\n\tuseCallback,\n\tuseContext,\n\tuseEffect,\n\tuseRef,\n\tuseState,\n\tuseSyncExternalStore,\n} from \"react\";\nimport type { BridgeClient } from \"./bridge-client\";\nimport type { MessagingBridgeModule } from \"./loader\";\nimport { loadMessagingBridge } from \"./loader\";\nimport type { AppDisplayState } from \"./types\";\n\nexport const BridgeContext = createContext<BridgeClient | null>(null);\n\n/**\n * Provide a {@link BridgeClient} to the React tree.\n *\n * Pure delegation: the caller owns the client lifecycle. The provider does not\n * create, initialize, or destroy the client — obtain one from the CDN module\n * returned by `loadMessagingBridge()` (or use {@link createBridgeProvider} to\n * have loading, `app.initialize`, and teardown handled for you).\n */\nexport function BridgeProvider({\n\tclient,\n\tchildren,\n}: {\n\tclient: BridgeClient;\n\tchildren: React.ReactNode;\n}): React.JSX.Element {\n\treturn (\n\t\t<BridgeContext.Provider value={client}>{children}</BridgeContext.Provider>\n\t);\n}\n\n/** The active {@link BridgeClient}. Throws outside a `BridgeProvider`. */\nexport function useBridgeClient(): BridgeClient {\n\tconst client = useContext(BridgeContext);\n\tif (!client) {\n\t\tthrow new Error(\"useBridgeClient must be used within a BridgeProvider\");\n\t}\n\treturn client;\n}\n\n/**\n * The full display-state snapshot, or `null` before the first\n * `core.state.update`. Re-renders on every state change.\n */\nexport function useBridgeState(): AppDisplayState | null {\n\tconst client = useBridgeClient();\n\tconst subscribe = useCallback(\n\t\t(callback: () => void) => client.subscribe(callback),\n\t\t[client],\n\t);\n\tconst getSnapshot = useCallback(() => client.getState(), [client]);\n\n\treturn useSyncExternalStore(subscribe, getSnapshot, getSnapshot);\n}\n\n/**\n * Returns a single typed value from the bridge state, or `null` when the state\n * is not yet available. Provides full TypeScript autocomplete for the key names\n * and returns the exact type declared in AppDisplayState.\n *\n * Re-renders only when THIS key's value changes (delegating to the client's\n * `subscribeKey`), not on every state update the way `useBridgeState` does.\n */\nexport function useBridgeStateKey<K extends keyof AppDisplayState>(\n\tkey: K,\n): AppDisplayState[K] | null {\n\tconst client = useBridgeClient();\n\tconst subscribe = useCallback(\n\t\t(onStoreChange: () => void) =>\n\t\t\t// A stale-cached CDN runtime (or a hand-rolled client) may predate\n\t\t\t// subscribeKey; fall back to the full-state subscription — the\n\t\t\t// snapshot comparison below still skips the re-render.\n\t\t\ttypeof client.subscribeKey === \"function\"\n\t\t\t\t? client.subscribeKey(key, onStoreChange)\n\t\t\t\t: client.subscribe(onStoreChange),\n\t\t[client, key],\n\t);\n\tconst getSnapshot = useCallback((): AppDisplayState[K] | null => {\n\t\tconst state = client.getState();\n\t\tif (state === null) {\n\t\t\treturn null;\n\t\t}\n\t\tconst value = state[key];\n\t\treturn value !== undefined ? value : null;\n\t}, [client, key]);\n\n\treturn useSyncExternalStore(subscribe, getSnapshot, getSnapshot);\n}\n\nexport interface LoadedBridgeProviderProps {\n\tchildren: React.ReactNode;\n\t/** Rendered while the bridge runtime is loading. Defaults to nothing. */\n\tfallback?: React.ReactNode;\n\t/**\n\t * Rendered when the runtime fails to load (network, CSP, 404). Defaults\n\t * to `fallback`, so without it a failure looks like loading forever.\n\t */\n\terrorFallback?: React.ReactNode;\n\t/**\n\t * Called when the runtime fails to load, with the rejection — a\n\t * {@link MessagingBridgeLoadError} for every loader-detected failure.\n\t * When omitted, the failure is logged with `console.error`.\n\t */\n\tonError?: (error: unknown) => void;\n}\n\n/**\n * Build a provider component that loads the bridge runtime from the CDN,\n * creates a client, sends the required `app.initialize` handshake, and\n * destroys the client on unmount.\n *\n * The returned component renders `fallback` (default: nothing) until the\n * runtime has loaded, then renders `children` inside a {@link BridgeProvider}.\n * When the load fails it renders `errorFallback` (default: `fallback`) and\n * reports the error through `onError` (default: `console.error`).\n *\n * @example\n * const AdaBridgeProvider = createBridgeProvider();\n * root.render(<AdaBridgeProvider><MyChatUi /></AdaBridgeProvider>);\n */\nexport function createBridgeProvider(\n\tload: () => Promise<MessagingBridgeModule> = loadMessagingBridge,\n): (props: LoadedBridgeProviderProps) => React.JSX.Element {\n\treturn function LoadedBridgeProvider({\n\t\tchildren,\n\t\tfallback,\n\t\terrorFallback,\n\t\tonError,\n\t}: LoadedBridgeProviderProps): React.JSX.Element {\n\t\tconst [client, setClient] = useState<BridgeClient | null>(null);\n\t\tconst [failed, setFailed] = useState(false);\n\t\t// Ref so a new onError identity does not re-trigger the load effect.\n\t\tconst onErrorRef = useRef(onError);\n\t\tuseEffect(() => {\n\t\t\tonErrorRef.current = onError;\n\t\t});\n\n\t\tuseEffect(() => {\n\t\t\tlet disposed = false;\n\t\t\tlet created: BridgeClient | null = null;\n\t\t\tload()\n\t\t\t\t.then((mod) => {\n\t\t\t\t\tif (disposed) {\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tcreated = mod.createBridgeClient();\n\t\t\t\t\tcreated.sendEvent(\"app.initialize\");\n\t\t\t\t\tsetClient(created);\n\t\t\t\t})\n\t\t\t\t.catch((error) => {\n\t\t\t\t\tif (disposed) {\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tsetFailed(true);\n\t\t\t\t\tconst handler = onErrorRef.current;\n\t\t\t\t\tif (handler) {\n\t\t\t\t\t\thandler(error);\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tconsole.error(\n\t\t\t\t\t\t\"[AdaMessaging] Failed to load the bridge runtime:\",\n\t\t\t\t\t\terror,\n\t\t\t\t\t);\n\t\t\t\t});\n\t\t\treturn () => {\n\t\t\t\tdisposed = true;\n\t\t\t\tcreated?.destroy();\n\t\t\t};\n\t\t}, []);\n\n\t\tif (!client) {\n\t\t\tif (failed) {\n\t\t\t\treturn <>{errorFallback ?? fallback ?? null}</>;\n\t\t\t}\n\t\t\treturn <>{fallback ?? null}</>;\n\t\t}\n\t\treturn <BridgeProvider client={client}>{children}</BridgeProvider>;\n\t};\n}\n"],"mappings":";;;;AAcA,IAAa,gBAAgB,cAAmC,KAAK;;;;;;;;;AAUrE,SAAgB,eAAe,EAC9B,QACA,YAIqB;AACrB,QACC,oBAAC,cAAc,UAAf;EAAwB,OAAO;EAAS;EAAkC,CAAA;;;AAK5E,SAAgB,kBAAgC;CAC/C,MAAM,SAAS,WAAW,cAAc;AACxC,KAAI,CAAC,OACJ,OAAM,IAAI,MAAM,uDAAuD;AAExE,QAAO;;;;;;AAOR,SAAgB,iBAAyC;CACxD,MAAM,SAAS,iBAAiB;CAChC,MAAM,YAAY,aAChB,aAAyB,OAAO,UAAU,SAAS,EACpD,CAAC,OAAO,CACR;CACD,MAAM,cAAc,kBAAkB,OAAO,UAAU,EAAE,CAAC,OAAO,CAAC;AAElE,QAAO,qBAAqB,WAAW,aAAa,YAAY;;;;;;;;;;AAWjE,SAAgB,kBACf,KAC4B;CAC5B,MAAM,SAAS,iBAAiB;CAChC,MAAM,YAAY,aAChB,kBAIA,OAAO,OAAO,iBAAiB,aAC5B,OAAO,aAAa,KAAK,cAAc,GACvC,OAAO,UAAU,cAAc,EACnC,CAAC,QAAQ,IAAI,CACb;CACD,MAAM,cAAc,kBAA6C;EAChE,MAAM,QAAQ,OAAO,UAAU;AAC/B,MAAI,UAAU,KACb,QAAO;EAER,MAAM,QAAQ,MAAM;AACpB,SAAO,UAAU,KAAA,IAAY,QAAQ;IACnC,CAAC,QAAQ,IAAI,CAAC;AAEjB,QAAO,qBAAqB,WAAW,aAAa,YAAY;;;;;;;;;;;;;;;;AAkCjE,SAAgB,qBACf,OAA6C,qBACa;AAC1D,QAAO,SAAS,qBAAqB,EACpC,UACA,UACA,eACA,WACgD;EAChD,MAAM,CAAC,QAAQ,aAAa,SAA8B,KAAK;EAC/D,MAAM,CAAC,QAAQ,aAAa,SAAS,MAAM;EAE3C,MAAM,aAAa,OAAO,QAAQ;AAClC,kBAAgB;AACf,cAAW,UAAU;IACpB;AAEF,kBAAgB;GACf,IAAI,WAAW;GACf,IAAI,UAA+B;AACnC,SAAM,CACJ,MAAM,QAAQ;AACd,QAAI,SACH;AAED,cAAU,IAAI,oBAAoB;AAClC,YAAQ,UAAU,iBAAiB;AACnC,cAAU,QAAQ;KACjB,CACD,OAAO,UAAU;AACjB,QAAI,SACH;AAED,cAAU,KAAK;IACf,MAAM,UAAU,WAAW;AAC3B,QAAI,SAAS;AACZ,aAAQ,MAAM;AACd;;AAED,YAAQ,MACP,qDACA,MACA;KACA;AACH,gBAAa;AACZ,eAAW;AACX,aAAS,SAAS;;KAEjB,EAAE,CAAC;AAEN,MAAI,CAAC,QAAQ;AACZ,OAAI,OACH,QAAO,oBAAA,UAAA,EAAA,UAAG,iBAAiB,YAAY,MAAQ,CAAA;AAEhD,UAAO,oBAAA,UAAA,EAAA,UAAG,YAAY,MAAQ,CAAA;;AAE/B,SAAO,oBAAC,gBAAD;GAAwB;GAAS;GAA0B,CAAA"}
|