@revenuecat/purchases-ui-js 4.8.13 → 4.8.14

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.
@@ -83,9 +83,10 @@
83
83
  // WebView has no visibility overrides; it shows unless visible is explicitly false.
84
84
  const isVisible = $derived(props.visible !== false);
85
85
 
86
- // Host<->content bridge (client-only): createHostBridge comes from the bundled
87
- // npm SDK and is attached to the live iframe. The content reports its size
88
- // (`resize`), driving `fit` axes; a resolve failure degrades to the plain iframe.
86
+ // Host<->content bridge (client-only): the host IIFE is loaded from the CDN by
87
+ // protocol version via <script> and attached to the live iframe. The content
88
+ // reports its size (`resize`), driving `fit` axes; a resolve failure degrades
89
+ // to the plain iframe.
89
90
  let iframeEl = $state<HTMLIFrameElement | undefined>(undefined);
90
91
  let previousSafeUrl: string | null | undefined;
91
92
 
@@ -1,13 +1,16 @@
1
1
  /**
2
- * Resolves `createHostBridge` for the web_view host bridge.
2
+ * Loads `createHostBridge` for the web_view host bridge from the CDN IIFE:
3
+ * `https://sdk.revenuecat-static.com/v<major>/rc-host.iife.js` → `window.RCHost`.
3
4
  *
4
- * Imported statically from `@revenuecat/workflow-web-components-sdk` so this
5
- * module stays Metro-safe (no non-literal dynamic `import()`). Host version is
6
- * pinned to the npm dependency until a script-tag CDN loader lands.
5
+ * Classic `<script src>` is Metro-safe (no non-literal dynamic `import()`). The
6
+ * ESM sibling at `/v<major>/rc-host.js` remains for other consumers. Types and
7
+ * message constants come from `@revenuecat/workflow-web-components-sdk`.
7
8
  */
8
- import { type Bridge, type HostBridgeConfig } from "@revenuecat/workflow-web-components-sdk";
9
+ import type { Bridge, HostBridgeConfig } from "@revenuecat/workflow-web-components-sdk";
9
10
  export { FIT_MESSAGE, RESIZE_MESSAGE, } from "@revenuecat/workflow-web-components-sdk";
10
11
  export type { ResizePayload } from "@revenuecat/workflow-web-components-sdk";
12
+ /** Base origin the versioned SDK builds are served from. */
13
+ export declare const SDK_BASE_URL = "https://sdk.revenuecat-static.com";
11
14
  /**
12
15
  * The subset of the SDK bridge purchases-ui-js uses: subscribe to a message, run
13
16
  * a callback once the channel opens, fire-and-forget, and tear down.
@@ -16,20 +19,32 @@ export type WebViewHostBridge = Pick<Bridge, "on" | "onReady" | "send" | "destro
16
19
  export type CreateHostBridge = (config: HostBridgeConfig) => Bridge;
17
20
  /** Test factories may return only the bridge surface this package uses. */
18
21
  export type CreateHostBridgeForTests = (config: HostBridgeConfig) => WebViewHostBridge;
22
+ declare global {
23
+ interface Window {
24
+ RCHost?: {
25
+ createHostBridge?: CreateHostBridge;
26
+ };
27
+ }
28
+ }
29
+ type ScriptLoader = (url: string) => Promise<CreateHostBridge>;
30
+ /** Stable, rolling per-major URL of the host bridge IIFE build. */
31
+ export declare function hostSdkUrl(protocolVersion: number): string;
19
32
  /**
20
- * Resolve `createHostBridge` for a protocol major. Rejects on an invalid
21
- * `protocol_version` so callers can degrade to a render-only iframe.
22
- *
23
- * Today only the bundled npm host implementation is returned (protocol majors
24
- * share that build). Callers still pass `protocol_version` so the bridge is
25
- * configured with the component's declared major.
33
+ * Resolve `createHostBridge` for a protocol major from the CDN. Rejects on an
34
+ * invalid `protocol_version` or a failed script load so callers can degrade to
35
+ * a render-only iframe.
26
36
  */
27
37
  export declare function loadCreateHostBridge(protocolVersion: number): Promise<CreateHostBridge>;
28
38
  /**
29
39
  * Override the host-bridge factory. Intended for tests so they never exercise
30
- * the real postMessage wiring. Pass `null` to restore the bundled implementation.
40
+ * the real postMessage wiring. Pass `null` to restore CDN loading.
31
41
  */
32
42
  export declare function __setCreateHostBridgeForTests(factory: CreateHostBridgeForTests | null): void;
43
+ /**
44
+ * Override the script loader (and clear the cache). Intended for tests that
45
+ * assert the CDN URL without hitting the network.
46
+ */
47
+ export declare function __setHostScriptLoaderForTests(fn: ScriptLoader | null): void;
33
48
  /**
34
49
  * Make `loadCreateHostBridge` reject so callers (e.g. WebView) can assert they
35
50
  * degrade to a render-only iframe. Pass `null` to clear.
@@ -1,42 +1,121 @@
1
- /**
2
- * Resolves `createHostBridge` for the web_view host bridge.
3
- *
4
- * Imported statically from `@revenuecat/workflow-web-components-sdk` so this
5
- * module stays Metro-safe (no non-literal dynamic `import()`). Host version is
6
- * pinned to the npm dependency until a script-tag CDN loader lands.
7
- */
8
- import { createHostBridge as bundledCreateHostBridge, } from "@revenuecat/workflow-web-components-sdk";
9
1
  export { FIT_MESSAGE, RESIZE_MESSAGE, } from "@revenuecat/workflow-web-components-sdk";
2
+ /** Base origin the versioned SDK builds are served from. */
3
+ export const SDK_BASE_URL = "https://sdk.revenuecat-static.com";
4
+ let scriptLoader = loadHostScript;
10
5
  let createHostBridgeTestingOverride = null;
6
+ // Memoize per protocol major: the script is stable for the page's lifetime and
7
+ // multiple web views sharing a `protocol_version` should load it once.
8
+ const moduleCache = new Map();
9
+ /** Stable, rolling per-major URL of the host bridge IIFE build. */
10
+ export function hostSdkUrl(protocolVersion) {
11
+ return `${SDK_BASE_URL}/v${protocolVersion}/rc-host.iife.js`;
12
+ }
11
13
  function assertValidProtocolVersion(protocolVersion) {
12
14
  if (!Number.isInteger(protocolVersion) || protocolVersion < 1) {
13
15
  throw new Error(`invalid web_view protocol_version: ${protocolVersion}`);
14
16
  }
15
17
  }
18
+ function readCreateHostBridge(url) {
19
+ const factory = window.RCHost?.createHostBridge;
20
+ if (typeof factory !== "function") {
21
+ throw new Error(`rc-host.iife.js at ${url} did not register window.RCHost.createHostBridge`);
22
+ }
23
+ return factory;
24
+ }
25
+ /**
26
+ * Load the CDN IIFE via a classic `<script>` tag and resolve
27
+ * `window.RCHost.createHostBridge`.
28
+ */
29
+ function loadHostScript(url) {
30
+ if (typeof document === "undefined") {
31
+ return Promise.reject(new Error("web_view host bridge requires a document to load the CDN script"));
32
+ }
33
+ if (typeof window.RCHost?.createHostBridge === "function") {
34
+ return Promise.resolve(readCreateHostBridge(url));
35
+ }
36
+ return new Promise((resolve, reject) => {
37
+ const existing = document.querySelector(`script[data-rc-host-sdk="${url.replace(/"/g, '\\"')}"]`);
38
+ if (existing) {
39
+ const settle = () => {
40
+ try {
41
+ resolve(readCreateHostBridge(url));
42
+ }
43
+ catch (error) {
44
+ reject(error);
45
+ }
46
+ };
47
+ if (typeof window.RCHost?.createHostBridge === "function") {
48
+ settle();
49
+ return;
50
+ }
51
+ existing.addEventListener("load", settle);
52
+ existing.addEventListener("error", () => {
53
+ reject(new Error(`failed to load ${url}`));
54
+ });
55
+ return;
56
+ }
57
+ const script = document.createElement("script");
58
+ script.src = url;
59
+ script.async = true;
60
+ script.dataset.rcHostSdk = url;
61
+ script.onload = () => {
62
+ try {
63
+ resolve(readCreateHostBridge(url));
64
+ }
65
+ catch (error) {
66
+ reject(error);
67
+ }
68
+ };
69
+ script.onerror = () => {
70
+ reject(new Error(`failed to load ${url}`));
71
+ };
72
+ document.head.appendChild(script);
73
+ });
74
+ }
75
+ function resolveHostFactory(protocolVersion) {
76
+ const cached = moduleCache.get(protocolVersion);
77
+ if (cached)
78
+ return cached;
79
+ const url = hostSdkUrl(protocolVersion);
80
+ const promise = scriptLoader(url);
81
+ promise.catch(() => {
82
+ if (moduleCache.get(protocolVersion) === promise) {
83
+ moduleCache.delete(protocolVersion);
84
+ }
85
+ });
86
+ moduleCache.set(protocolVersion, promise);
87
+ return promise;
88
+ }
16
89
  /**
17
- * Resolve `createHostBridge` for a protocol major. Rejects on an invalid
18
- * `protocol_version` so callers can degrade to a render-only iframe.
19
- *
20
- * Today only the bundled npm host implementation is returned (protocol majors
21
- * share that build). Callers still pass `protocol_version` so the bridge is
22
- * configured with the component's declared major.
90
+ * Resolve `createHostBridge` for a protocol major from the CDN. Rejects on an
91
+ * invalid `protocol_version` or a failed script load so callers can degrade to
92
+ * a render-only iframe.
23
93
  */
24
94
  export async function loadCreateHostBridge(protocolVersion) {
25
95
  assertValidProtocolVersion(protocolVersion);
26
96
  if (createHostBridgeTestingOverride) {
27
97
  return createHostBridgeTestingOverride();
28
98
  }
29
- return bundledCreateHostBridge;
99
+ return resolveHostFactory(protocolVersion);
30
100
  }
31
101
  /**
32
102
  * Override the host-bridge factory. Intended for tests so they never exercise
33
- * the real postMessage wiring. Pass `null` to restore the bundled implementation.
103
+ * the real postMessage wiring. Pass `null` to restore CDN loading.
34
104
  */
35
105
  export function __setCreateHostBridgeForTests(factory) {
36
106
  createHostBridgeTestingOverride = factory
37
107
  ? // Test stubs only implement the WebViewHostBridge surface.
38
108
  async () => factory
39
109
  : null;
110
+ moduleCache.clear();
111
+ }
112
+ /**
113
+ * Override the script loader (and clear the cache). Intended for tests that
114
+ * assert the CDN URL without hitting the network.
115
+ */
116
+ export function __setHostScriptLoaderForTests(fn) {
117
+ scriptLoader = fn ?? loadHostScript;
118
+ moduleCache.clear();
40
119
  }
41
120
  /**
42
121
  * Make `loadCreateHostBridge` reject so callers (e.g. WebView) can assert they
@@ -48,4 +127,5 @@ export function __failCreateHostBridgeForTests(error) {
48
127
  throw error;
49
128
  }
50
129
  : null;
130
+ moduleCache.clear();
51
131
  }
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@revenuecat/purchases-ui-js",
3
3
  "description": "Web components for Paywalls. Powered by RevenueCat",
4
4
  "private": false,
5
- "version": "4.8.13",
5
+ "version": "4.8.14",
6
6
  "author": {
7
7
  "name": "RevenueCat, Inc."
8
8
  },