@returningai/widget-sdk 1.7.0 → 1.7.1

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.
@@ -12542,6 +12542,80 @@ async function waitForBundleMountReadiness(mountResult, firstPaint, timeoutMs =
12542
12542
  );
12543
12543
  return "deep-ready";
12544
12544
  }
12545
+ function isWidgetBundleRuntime(value) {
12546
+ return (typeof value === "object" || typeof value === "function") && value !== null && typeof value.mount === "function";
12547
+ }
12548
+ function normalizeBundleUrl(url, baseUrl) {
12549
+ const normalized = new URL(url, baseUrl);
12550
+ normalized.hash = "";
12551
+ return normalized.href;
12552
+ }
12553
+ function createBundleRuntimeLoader({
12554
+ loadScript,
12555
+ readGlobal,
12556
+ baseUrl
12557
+ }) {
12558
+ const runtimePromises = /* @__PURE__ */ new Map();
12559
+ const globalLoadTails = /* @__PURE__ */ new Map();
12560
+ return async function getBundleRuntime(globalName, bundleUrl) {
12561
+ const normalizedUrl = normalizeBundleUrl(bundleUrl, baseUrl());
12562
+ const cacheKey = `${globalName}:${normalizedUrl}`;
12563
+ const cachedRuntime = runtimePromises.get(cacheKey);
12564
+ if (cachedRuntime) return cachedRuntime;
12565
+ const previousLoad = globalLoadTails.get(globalName) || Promise.resolve();
12566
+ const runtimePromise = previousLoad.then(async () => {
12567
+ await loadScript(normalizedUrl);
12568
+ const runtime = readGlobal(globalName);
12569
+ if (!isWidgetBundleRuntime(runtime)) {
12570
+ throw new Error(
12571
+ `window.${globalName}.mount not found after loading ${normalizedUrl}`
12572
+ );
12573
+ }
12574
+ const mount = runtime.mount;
12575
+ return {
12576
+ mount(container, config) {
12577
+ return mount.call(runtime, container, config);
12578
+ }
12579
+ };
12580
+ });
12581
+ runtimePromises.set(cacheKey, runtimePromise);
12582
+ const loadTail = runtimePromise.then(
12583
+ () => void 0,
12584
+ () => void 0
12585
+ );
12586
+ globalLoadTails.set(globalName, loadTail);
12587
+ void loadTail.then(() => {
12588
+ if (globalLoadTails.get(globalName) === loadTail) {
12589
+ globalLoadTails.delete(globalName);
12590
+ }
12591
+ });
12592
+ try {
12593
+ return await runtimePromise;
12594
+ } catch (error) {
12595
+ if (runtimePromises.get(cacheKey) === runtimePromise) {
12596
+ runtimePromises.delete(cacheKey);
12597
+ }
12598
+ throw error;
12599
+ }
12600
+ };
12601
+ }
12602
+ function appendScript(url) {
12603
+ return new Promise((resolve, reject) => {
12604
+ const script = document.createElement("script");
12605
+ script.src = url;
12606
+ script.onload = () => resolve();
12607
+ script.onerror = () => reject(new Error(`Failed to load ${url}`));
12608
+ document.head.appendChild(script);
12609
+ });
12610
+ }
12611
+ const loadBrowserBundleRuntime = createBundleRuntimeLoader({
12612
+ loadScript: appendScript,
12613
+ readGlobal: (globalName) => window[globalName],
12614
+ baseUrl: () => document.baseURI
12615
+ });
12616
+ function getOrLoadBundleRuntime(globalName, bundleUrl) {
12617
+ return loadBrowserBundleRuntime(globalName, bundleUrl);
12618
+ }
12545
12619
  function assertCallbackName(callbackName) {
12546
12620
  if (!callbackName || typeof callbackName !== "string") {
12547
12621
  throw new TypeError("ReturningAI callback name must be a non-empty string");
@@ -12860,15 +12934,6 @@ function resolveWidgetServerUrl(domainKey) {
12860
12934
  function resolveV2ApiUrl(domainKey) {
12861
12935
  return DOMAIN_KEY_TO_V2_API_URL[domainKey] || DOMAIN_KEY_TO_V2_API_URL.PROD;
12862
12936
  }
12863
- function loadScript(url) {
12864
- return new Promise((resolve, reject) => {
12865
- const s = document.createElement("script");
12866
- s.src = url;
12867
- s.onload = () => resolve();
12868
- s.onerror = () => reject(new Error(`Failed to load ${url}`));
12869
- document.head.appendChild(s);
12870
- });
12871
- }
12872
12937
  const loadedFonts = /* @__PURE__ */ new Set();
12873
12938
  function ensureGoogleFontLoaded(family) {
12874
12939
  if (!family || loadedFonts.has(family)) return;
@@ -13718,7 +13783,6 @@ class BaseWidget extends HTMLElement {
13718
13783
  }
13719
13784
  // ── Bundle mode (no-iframe) ──────────────────────────────────────────
13720
13785
  async mountWidget(initId) {
13721
- var _a;
13722
13786
  let container = null;
13723
13787
  try {
13724
13788
  this.teardownMountedWidget();
@@ -13742,16 +13806,11 @@ class BaseWidget extends HTMLElement {
13742
13806
  window.loadingAnimation = this.state.loadingAnimation;
13743
13807
  }
13744
13808
  const globalName = WIDGET_GLOBALS[this.config.widgetType];
13745
- if (!((_a = window[globalName]) == null ? void 0 : _a.mount)) {
13746
- await loadScript(this.config.bundleUrl);
13747
- }
13809
+ const widgetGlobal = await getOrLoadBundleRuntime(
13810
+ globalName,
13811
+ this.config.bundleUrl
13812
+ );
13748
13813
  if (!this.isCurrentInit(initId)) return;
13749
- const widgetGlobal = window[globalName];
13750
- if (!(widgetGlobal == null ? void 0 : widgetGlobal.mount)) {
13751
- throw new Error(
13752
- `window.${globalName}.mount not found after loading bundle`
13753
- );
13754
- }
13755
13814
  container = document.createElement("div");
13756
13815
  const hideNestedBundleUntilReady = this.config.widgetType === "custom";
13757
13816
  container.className = "rai-widget-root";
@@ -14007,7 +14066,7 @@ class CustomWidget extends BaseWidget {
14007
14066
  }
14008
14067
  }
14009
14068
  }
14010
- console.log(`[rai-widget] v${"1.7.0"}`);
14069
+ console.log(`[rai-widget] v${"1.7.1"}`);
14011
14070
  class StoreWidgetCompat extends StoreWidget {
14012
14071
  }
14013
14072
  const WIDGET_REGISTRY = [
@@ -14071,7 +14130,7 @@ function exposePublicApi() {
14071
14130
  })();
14072
14131
  const widget = container == null ? void 0 : container.querySelector(ALL_WIDGET_SELECTOR);
14073
14132
  window.ReturningAIWidget = {
14074
- version: "1.7.0",
14133
+ version: "1.7.1",
14075
14134
  reload: () => (widget == null ? void 0 : widget.reload()) ?? Promise.resolve(),
14076
14135
  logout: () => (widget == null ? void 0 : widget.logoutPublic()) ?? Promise.resolve(),
14077
14136
  isAuthenticated: () => (widget == null ? void 0 : widget.isAuthenticated()) ?? false,
@@ -0,0 +1,20 @@
1
+ export interface WidgetBundleRuntime {
2
+ mount(container: HTMLElement, config: unknown): unknown;
3
+ }
4
+ interface BundleRuntimeLoaderOptions {
5
+ loadScript: (url: string) => Promise<void>;
6
+ readGlobal: (globalName: string) => unknown;
7
+ baseUrl: () => string;
8
+ }
9
+ export declare function normalizeBundleUrl(url: string, baseUrl: string): string;
10
+ /**
11
+ * Cache widget runtimes by both their window global and normalized bundle URL.
12
+ *
13
+ * Different custom-widget bundles intentionally reuse the same global name, so
14
+ * the current value on window is not enough to identify the requested runtime.
15
+ * Capturing the runtime immediately after its script loads also lets A -> B -> A
16
+ * reuse A without re-evaluating its IIFE.
17
+ */
18
+ export declare function createBundleRuntimeLoader({ loadScript, readGlobal, baseUrl, }: BundleRuntimeLoaderOptions): (globalName: string, bundleUrl: string) => Promise<WidgetBundleRuntime>;
19
+ export declare function getOrLoadBundleRuntime(globalName: string, bundleUrl: string): Promise<WidgetBundleRuntime>;
20
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@returningai/widget-sdk",
3
- "version": "1.7.0",
3
+ "version": "1.7.1",
4
4
  "description": "Shadow DOM isolated widget SDK for ReturningAI",
5
5
  "main": "dist/rai-widget.iife.js",
6
6
  "module": "dist/rai-widget.js",
@@ -26,13 +26,18 @@
26
26
  "build:all": "npm run build && npm run build:banner",
27
27
  "build:all:min": "npm run build:min && npm run build:banner:min",
28
28
  "types": "tsc --emitDeclarationOnly --outDir dist/types && cp src/jsx.d.ts dist/types/jsx.d.ts",
29
- "test": "npm run test:host-callback && npm run test:bundle-readiness && npm run test:jsx-types",
29
+ "test": "npm run test:host-callback && npm run test:bundle-readiness && npm run test:bundle-runtime-cache && npm run test:jsx-types",
30
30
  "test:host-callback": "rm -rf test/.tmp && tsc -p test/tsconfig.json && node test/.tmp/test/postmessage-host-callback.test.js",
31
31
  "test:bundle-readiness": "rm -rf test/.tmp && tsc -p test/tsconfig.json && node test/.tmp/test/bundle-readiness.test.js",
32
+ "test:bundle-runtime-cache": "rm -rf test/.tmp && tsc -p test/tsconfig.json && node test/.tmp/test/bundle-runtime-cache.test.js",
32
33
  "test:jsx-types": "tsc --noEmit --strict --lib ES2020,DOM --skipLibCheck false src/jsx.d.ts",
34
+ "coverage:repowise": "rm -rf coverage && npm run coverage:host-callback && npm run coverage:bundle-readiness && npm run test:jsx-types",
35
+ "coverage:host-callback": "rm -rf test/.tmp && tsc -p test/tsconfig.json && c8 --reporter=lcov --reports-dir=coverage/host-callback --exclude-after-remap --exclude='**/*.test.ts' node test/.tmp/test/postmessage-host-callback.test.js && node scripts/name-lcov-test.mjs coverage/host-callback/lcov.info test/postmessage-host-callback.test.ts",
36
+ "coverage:bundle-readiness": "rm -rf test/.tmp && tsc -p test/tsconfig.json && c8 --reporter=lcov --reports-dir=coverage/bundle-readiness --exclude-after-remap --exclude='**/*.test.ts' node test/.tmp/test/bundle-readiness.test.js && node scripts/name-lcov-test.mjs coverage/bundle-readiness/lcov.info test/bundle-readiness.test.ts",
33
37
  "prepublishOnly": "npm run build:all:min && npm run types"
34
38
  },
35
39
  "devDependencies": {
40
+ "c8": "^11.0.0",
36
41
  "terser": "^5.46.0",
37
42
  "typescript": "^5.3.3",
38
43
  "vite": "^5.1.0"