@meetreeve/capacitor-bridge 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +134 -0
- package/compliance/PrivacyInfo.xcprivacy.template +155 -0
- package/compliance/README.md +42 -0
- package/compliance/index.cjs +60 -0
- package/compliance/permission-strings.json +14 -0
- package/dist/bugreport/index.cjs +143 -0
- package/dist/bugreport/index.d.cts +76 -0
- package/dist/bugreport/index.d.ts +76 -0
- package/dist/bugreport/index.js +111 -0
- package/dist/core/index.cjs +160 -0
- package/dist/core/index.d.cts +29 -0
- package/dist/core/index.d.ts +29 -0
- package/dist/core/index.js +124 -0
- package/dist/deeplink/index.cjs +166 -0
- package/dist/deeplink/index.d.cts +81 -0
- package/dist/deeplink/index.d.ts +81 -0
- package/dist/deeplink/index.js +133 -0
- package/dist/paywall/index.cjs +103 -0
- package/dist/paywall/index.d.cts +75 -0
- package/dist/paywall/index.d.ts +75 -0
- package/dist/paywall/index.js +71 -0
- package/dist/react/index.cjs +523 -0
- package/dist/react/index.d.cts +68 -0
- package/dist/react/index.d.ts +68 -0
- package/dist/react/index.js +483 -0
- package/dist/safe-area-B67yGQOn.d.cts +51 -0
- package/dist/safe-area-B67yGQOn.d.ts +51 -0
- package/package.json +78 -0
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deep-link router.
|
|
3
|
+
*
|
|
4
|
+
* Two flavors of inbound URL we have to route in a Capacitor app:
|
|
5
|
+
* 1. Custom URL scheme — `com.<product>.ios://callback?...` — used by
|
|
6
|
+
* Auth0 mobile flow (Reeve.Auth Capacitor) and other native handoffs.
|
|
7
|
+
* 2. Universal Links — `https://app.<product>.com/...` — used by emails,
|
|
8
|
+
* iMessage bubbles, and push notification deep links.
|
|
9
|
+
*
|
|
10
|
+
* Capacitor's App plugin emits an `appUrlOpen` event for both; this module
|
|
11
|
+
* parses the URL, runs registered handlers, and falls back to navigating
|
|
12
|
+
* the WebView to the path if nothing else claims it.
|
|
13
|
+
*/
|
|
14
|
+
interface DeepLink {
|
|
15
|
+
/** Full URL as delivered by the OS. */
|
|
16
|
+
url: string;
|
|
17
|
+
/** Scheme — `https` for universal links, `com.x.ios` etc. for custom schemes. */
|
|
18
|
+
scheme: string;
|
|
19
|
+
/** Host portion (or empty for custom schemes that don't use one). */
|
|
20
|
+
host: string;
|
|
21
|
+
/** Path portion, always starting with `/`. */
|
|
22
|
+
path: string;
|
|
23
|
+
/** Parsed query string. */
|
|
24
|
+
query: Record<string, string>;
|
|
25
|
+
/** Fragment without the leading `#`, if any. */
|
|
26
|
+
hash: string;
|
|
27
|
+
}
|
|
28
|
+
type DeepLinkHandler = (link: DeepLink) => boolean | Promise<boolean>;
|
|
29
|
+
interface AppPluginListener {
|
|
30
|
+
remove: () => Promise<void>;
|
|
31
|
+
}
|
|
32
|
+
declare function parseDeepLink(url: string): DeepLink;
|
|
33
|
+
/**
|
|
34
|
+
* Register a handler. Handlers run in registration order; the FIRST handler
|
|
35
|
+
* that returns true (sync or async) consumes the link and downstream handlers
|
|
36
|
+
* are skipped. Return false (or throw) to defer.
|
|
37
|
+
*
|
|
38
|
+
* Returns an unregister function.
|
|
39
|
+
*/
|
|
40
|
+
declare function registerDeepLinkHandler(handler: DeepLinkHandler): () => void;
|
|
41
|
+
/**
|
|
42
|
+
* Internal — wipes registered handlers and resets the OS listener. Exposed
|
|
43
|
+
* for tests + hot reload only. Production code MUST NOT call this; doing so
|
|
44
|
+
* silently destroys the deep-link routing your app booted with.
|
|
45
|
+
*/
|
|
46
|
+
declare function __resetDeepLinkHandlersForTests(): void;
|
|
47
|
+
/** @deprecated use __resetDeepLinkHandlersForTests; prod calls now warn. */
|
|
48
|
+
declare function resetDeepLinkHandlers(): void;
|
|
49
|
+
/**
|
|
50
|
+
* Dispatch a parsed link through the handler chain. Returns true if
|
|
51
|
+
* something consumed it, false if nothing did.
|
|
52
|
+
*/
|
|
53
|
+
declare function dispatchDeepLink(link: DeepLink): Promise<boolean>;
|
|
54
|
+
/**
|
|
55
|
+
* Wire up the Capacitor App plugin listener so OS-delivered URLs flow into
|
|
56
|
+
* the handler chain. Idempotent: subsequent calls return the same listener
|
|
57
|
+
* promise.
|
|
58
|
+
*
|
|
59
|
+
* SECURITY: `onUnhandled(link)` is what your app does with a link no handler
|
|
60
|
+
* claimed. A common pattern is to navigate the WebView to `link.path` — DO
|
|
61
|
+
* NOT do this without re-validating that the path is safe for the link's
|
|
62
|
+
* scheme. iOS will deliver any URL the user opened (push, iMessage, NFC,
|
|
63
|
+
* QR), and a crafted `app://...` URL can otherwise drive the WebView to a
|
|
64
|
+
* post-auth route the user didn't intend. The `allowedSchemes` option is the
|
|
65
|
+
* cheap defense — set it to the list of URL schemes you actually own.
|
|
66
|
+
* Anything else is dropped before reaching handlers or `onUnhandled`.
|
|
67
|
+
*/
|
|
68
|
+
declare function startDeepLinkListener(opts?: {
|
|
69
|
+
onUnhandled?: (link: DeepLink) => void;
|
|
70
|
+
/** URL schemes (without trailing colon) that this app handles. Default:
|
|
71
|
+
* every link is allowed — equivalent to the v0 behavior — but consumers
|
|
72
|
+
* SHOULD set this to e.g. `["https", "com.meetfreya.ios"]`. */
|
|
73
|
+
allowedSchemes?: string[];
|
|
74
|
+
}): Promise<AppPluginListener> | null;
|
|
75
|
+
/**
|
|
76
|
+
* Tear down the listener. Returns a promise that resolves once the OS
|
|
77
|
+
* subscription is gone.
|
|
78
|
+
*/
|
|
79
|
+
declare function stopDeepLinkListener(): Promise<void>;
|
|
80
|
+
|
|
81
|
+
export { type DeepLink, type DeepLinkHandler, __resetDeepLinkHandlersForTests, dispatchDeepLink, parseDeepLink, registerDeepLinkHandler, resetDeepLinkHandlers, startDeepLinkListener, stopDeepLinkListener };
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deep-link router.
|
|
3
|
+
*
|
|
4
|
+
* Two flavors of inbound URL we have to route in a Capacitor app:
|
|
5
|
+
* 1. Custom URL scheme — `com.<product>.ios://callback?...` — used by
|
|
6
|
+
* Auth0 mobile flow (Reeve.Auth Capacitor) and other native handoffs.
|
|
7
|
+
* 2. Universal Links — `https://app.<product>.com/...` — used by emails,
|
|
8
|
+
* iMessage bubbles, and push notification deep links.
|
|
9
|
+
*
|
|
10
|
+
* Capacitor's App plugin emits an `appUrlOpen` event for both; this module
|
|
11
|
+
* parses the URL, runs registered handlers, and falls back to navigating
|
|
12
|
+
* the WebView to the path if nothing else claims it.
|
|
13
|
+
*/
|
|
14
|
+
interface DeepLink {
|
|
15
|
+
/** Full URL as delivered by the OS. */
|
|
16
|
+
url: string;
|
|
17
|
+
/** Scheme — `https` for universal links, `com.x.ios` etc. for custom schemes. */
|
|
18
|
+
scheme: string;
|
|
19
|
+
/** Host portion (or empty for custom schemes that don't use one). */
|
|
20
|
+
host: string;
|
|
21
|
+
/** Path portion, always starting with `/`. */
|
|
22
|
+
path: string;
|
|
23
|
+
/** Parsed query string. */
|
|
24
|
+
query: Record<string, string>;
|
|
25
|
+
/** Fragment without the leading `#`, if any. */
|
|
26
|
+
hash: string;
|
|
27
|
+
}
|
|
28
|
+
type DeepLinkHandler = (link: DeepLink) => boolean | Promise<boolean>;
|
|
29
|
+
interface AppPluginListener {
|
|
30
|
+
remove: () => Promise<void>;
|
|
31
|
+
}
|
|
32
|
+
declare function parseDeepLink(url: string): DeepLink;
|
|
33
|
+
/**
|
|
34
|
+
* Register a handler. Handlers run in registration order; the FIRST handler
|
|
35
|
+
* that returns true (sync or async) consumes the link and downstream handlers
|
|
36
|
+
* are skipped. Return false (or throw) to defer.
|
|
37
|
+
*
|
|
38
|
+
* Returns an unregister function.
|
|
39
|
+
*/
|
|
40
|
+
declare function registerDeepLinkHandler(handler: DeepLinkHandler): () => void;
|
|
41
|
+
/**
|
|
42
|
+
* Internal — wipes registered handlers and resets the OS listener. Exposed
|
|
43
|
+
* for tests + hot reload only. Production code MUST NOT call this; doing so
|
|
44
|
+
* silently destroys the deep-link routing your app booted with.
|
|
45
|
+
*/
|
|
46
|
+
declare function __resetDeepLinkHandlersForTests(): void;
|
|
47
|
+
/** @deprecated use __resetDeepLinkHandlersForTests; prod calls now warn. */
|
|
48
|
+
declare function resetDeepLinkHandlers(): void;
|
|
49
|
+
/**
|
|
50
|
+
* Dispatch a parsed link through the handler chain. Returns true if
|
|
51
|
+
* something consumed it, false if nothing did.
|
|
52
|
+
*/
|
|
53
|
+
declare function dispatchDeepLink(link: DeepLink): Promise<boolean>;
|
|
54
|
+
/**
|
|
55
|
+
* Wire up the Capacitor App plugin listener so OS-delivered URLs flow into
|
|
56
|
+
* the handler chain. Idempotent: subsequent calls return the same listener
|
|
57
|
+
* promise.
|
|
58
|
+
*
|
|
59
|
+
* SECURITY: `onUnhandled(link)` is what your app does with a link no handler
|
|
60
|
+
* claimed. A common pattern is to navigate the WebView to `link.path` — DO
|
|
61
|
+
* NOT do this without re-validating that the path is safe for the link's
|
|
62
|
+
* scheme. iOS will deliver any URL the user opened (push, iMessage, NFC,
|
|
63
|
+
* QR), and a crafted `app://...` URL can otherwise drive the WebView to a
|
|
64
|
+
* post-auth route the user didn't intend. The `allowedSchemes` option is the
|
|
65
|
+
* cheap defense — set it to the list of URL schemes you actually own.
|
|
66
|
+
* Anything else is dropped before reaching handlers or `onUnhandled`.
|
|
67
|
+
*/
|
|
68
|
+
declare function startDeepLinkListener(opts?: {
|
|
69
|
+
onUnhandled?: (link: DeepLink) => void;
|
|
70
|
+
/** URL schemes (without trailing colon) that this app handles. Default:
|
|
71
|
+
* every link is allowed — equivalent to the v0 behavior — but consumers
|
|
72
|
+
* SHOULD set this to e.g. `["https", "com.meetfreya.ios"]`. */
|
|
73
|
+
allowedSchemes?: string[];
|
|
74
|
+
}): Promise<AppPluginListener> | null;
|
|
75
|
+
/**
|
|
76
|
+
* Tear down the listener. Returns a promise that resolves once the OS
|
|
77
|
+
* subscription is gone.
|
|
78
|
+
*/
|
|
79
|
+
declare function stopDeepLinkListener(): Promise<void>;
|
|
80
|
+
|
|
81
|
+
export { type DeepLink, type DeepLinkHandler, __resetDeepLinkHandlersForTests, dispatchDeepLink, parseDeepLink, registerDeepLinkHandler, resetDeepLinkHandlers, startDeepLinkListener, stopDeepLinkListener };
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
// src/core/platform.ts
|
|
2
|
+
function getCapacitor() {
|
|
3
|
+
if (typeof window === "undefined") return null;
|
|
4
|
+
const cap = window.Capacitor;
|
|
5
|
+
if (!cap || typeof cap.isNativePlatform !== "function") return null;
|
|
6
|
+
return cap;
|
|
7
|
+
}
|
|
8
|
+
function getPlatform() {
|
|
9
|
+
const cap = getCapacitor();
|
|
10
|
+
if (!cap) return "web";
|
|
11
|
+
const p = cap.getPlatform();
|
|
12
|
+
return p === "ios" ? "ios" : p === "android" ? "android" : "web";
|
|
13
|
+
}
|
|
14
|
+
function isNativePlatform() {
|
|
15
|
+
return getPlatform() !== "web";
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// src/deeplink/index.ts
|
|
19
|
+
function getAppPlugin() {
|
|
20
|
+
if (typeof window === "undefined") return null;
|
|
21
|
+
const cap = window.Capacitor;
|
|
22
|
+
return cap?.Plugins?.App ?? null;
|
|
23
|
+
}
|
|
24
|
+
var WEB_SCHEMES = /* @__PURE__ */ new Set(["http", "https", "ws", "wss", "file"]);
|
|
25
|
+
function normalizePath(raw) {
|
|
26
|
+
if (!raw || raw === "/") return "/";
|
|
27
|
+
const collapsed = raw.replace(/\/+/g, "/");
|
|
28
|
+
if (collapsed === "/") return "/";
|
|
29
|
+
return collapsed.endsWith("/") ? collapsed.slice(0, -1) : collapsed;
|
|
30
|
+
}
|
|
31
|
+
function parseDeepLink(url) {
|
|
32
|
+
const parsed = new URL(url);
|
|
33
|
+
const scheme = parsed.protocol.replace(/:$/, "");
|
|
34
|
+
const query = {};
|
|
35
|
+
parsed.searchParams.forEach((value, key) => {
|
|
36
|
+
query[key] = value;
|
|
37
|
+
});
|
|
38
|
+
const isWeb = WEB_SCHEMES.has(scheme);
|
|
39
|
+
const rawHost = parsed.host;
|
|
40
|
+
const rawPath = parsed.pathname || "";
|
|
41
|
+
const host = isWeb ? rawHost : "";
|
|
42
|
+
const path = isWeb ? normalizePath(rawPath || "/") : normalizePath(`/${rawHost}${rawPath}`);
|
|
43
|
+
return {
|
|
44
|
+
url,
|
|
45
|
+
scheme,
|
|
46
|
+
host,
|
|
47
|
+
path,
|
|
48
|
+
query,
|
|
49
|
+
hash: parsed.hash.replace(/^#/, "")
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
var GLOBAL_KEY = /* @__PURE__ */ Symbol.for("@meetreeve/capacitor-bridge/deeplink@1");
|
|
53
|
+
function getGlobalState() {
|
|
54
|
+
const g = globalThis;
|
|
55
|
+
if (!g[GLOBAL_KEY]) {
|
|
56
|
+
g[GLOBAL_KEY] = { handlers: [], listenerPromise: null, allowedSchemes: null };
|
|
57
|
+
}
|
|
58
|
+
return g[GLOBAL_KEY];
|
|
59
|
+
}
|
|
60
|
+
function registerDeepLinkHandler(handler) {
|
|
61
|
+
const state = getGlobalState();
|
|
62
|
+
state.handlers.push(handler);
|
|
63
|
+
return () => {
|
|
64
|
+
const idx = state.handlers.indexOf(handler);
|
|
65
|
+
if (idx >= 0) state.handlers.splice(idx, 1);
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
function __resetDeepLinkHandlersForTests() {
|
|
69
|
+
const state = getGlobalState();
|
|
70
|
+
state.handlers.length = 0;
|
|
71
|
+
state.listenerPromise = null;
|
|
72
|
+
state.allowedSchemes = null;
|
|
73
|
+
}
|
|
74
|
+
function resetDeepLinkHandlers() {
|
|
75
|
+
if (typeof console !== "undefined" && typeof console.warn === "function") {
|
|
76
|
+
console.warn(
|
|
77
|
+
"[@meetreeve/capacitor-bridge/deeplink] resetDeepLinkHandlers is deprecated; use __resetDeepLinkHandlersForTests."
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
__resetDeepLinkHandlersForTests();
|
|
81
|
+
}
|
|
82
|
+
async function dispatchDeepLink(link) {
|
|
83
|
+
const state = getGlobalState();
|
|
84
|
+
for (const h of state.handlers) {
|
|
85
|
+
try {
|
|
86
|
+
const result = await h(link);
|
|
87
|
+
if (result === true) return true;
|
|
88
|
+
} catch {
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return false;
|
|
92
|
+
}
|
|
93
|
+
function startDeepLinkListener(opts = {}) {
|
|
94
|
+
if (!isNativePlatform()) return null;
|
|
95
|
+
const state = getGlobalState();
|
|
96
|
+
if (opts.allowedSchemes !== void 0) {
|
|
97
|
+
state.allowedSchemes = new Set(opts.allowedSchemes);
|
|
98
|
+
}
|
|
99
|
+
if (state.listenerPromise) return state.listenerPromise;
|
|
100
|
+
const plugin = getAppPlugin();
|
|
101
|
+
if (!plugin) return null;
|
|
102
|
+
state.listenerPromise = plugin.addListener("appUrlOpen", (event) => {
|
|
103
|
+
let link;
|
|
104
|
+
try {
|
|
105
|
+
link = parseDeepLink(event.url);
|
|
106
|
+
} catch {
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
if (state.allowedSchemes && !state.allowedSchemes.has(link.scheme)) {
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
void dispatchDeepLink(link).then((handled) => {
|
|
113
|
+
if (!handled && opts.onUnhandled) opts.onUnhandled(link);
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
return state.listenerPromise;
|
|
117
|
+
}
|
|
118
|
+
async function stopDeepLinkListener() {
|
|
119
|
+
const state = getGlobalState();
|
|
120
|
+
if (!state.listenerPromise) return;
|
|
121
|
+
const listener = await state.listenerPromise;
|
|
122
|
+
state.listenerPromise = null;
|
|
123
|
+
await listener.remove();
|
|
124
|
+
}
|
|
125
|
+
export {
|
|
126
|
+
__resetDeepLinkHandlersForTests,
|
|
127
|
+
dispatchDeepLink,
|
|
128
|
+
parseDeepLink,
|
|
129
|
+
registerDeepLinkHandler,
|
|
130
|
+
resetDeepLinkHandlers,
|
|
131
|
+
startDeepLinkListener,
|
|
132
|
+
stopDeepLinkListener
|
|
133
|
+
};
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/paywall/index.ts
|
|
21
|
+
var paywall_exports = {};
|
|
22
|
+
__export(paywall_exports, {
|
|
23
|
+
__resetPaywallRegistryForTests: () => __resetPaywallRegistryForTests,
|
|
24
|
+
registerNativePaywall: () => registerNativePaywall,
|
|
25
|
+
registerWebPaywall: () => registerWebPaywall,
|
|
26
|
+
resetPaywallRegistry: () => resetPaywallRegistry,
|
|
27
|
+
runPaywall: () => runPaywall,
|
|
28
|
+
shouldShowWebPaywall: () => shouldShowWebPaywall
|
|
29
|
+
});
|
|
30
|
+
module.exports = __toCommonJS(paywall_exports);
|
|
31
|
+
|
|
32
|
+
// src/core/platform.ts
|
|
33
|
+
function getCapacitor() {
|
|
34
|
+
if (typeof window === "undefined") return null;
|
|
35
|
+
const cap = window.Capacitor;
|
|
36
|
+
if (!cap || typeof cap.isNativePlatform !== "function") return null;
|
|
37
|
+
return cap;
|
|
38
|
+
}
|
|
39
|
+
function getPlatform() {
|
|
40
|
+
const cap = getCapacitor();
|
|
41
|
+
if (!cap) return "web";
|
|
42
|
+
const p = cap.getPlatform();
|
|
43
|
+
return p === "ios" ? "ios" : p === "android" ? "android" : "web";
|
|
44
|
+
}
|
|
45
|
+
function isNativePlatform() {
|
|
46
|
+
return getPlatform() !== "web";
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// src/paywall/index.ts
|
|
50
|
+
var GLOBAL_KEY = /* @__PURE__ */ Symbol.for("@meetreeve/capacitor-bridge/paywall@1");
|
|
51
|
+
function getRegistry() {
|
|
52
|
+
const g = globalThis;
|
|
53
|
+
if (!g[GLOBAL_KEY]) g[GLOBAL_KEY] = { native: null, web: null };
|
|
54
|
+
return g[GLOBAL_KEY];
|
|
55
|
+
}
|
|
56
|
+
function registerNativePaywall(handler) {
|
|
57
|
+
getRegistry().native = handler;
|
|
58
|
+
}
|
|
59
|
+
function registerWebPaywall(handler) {
|
|
60
|
+
getRegistry().web = handler;
|
|
61
|
+
}
|
|
62
|
+
function __resetPaywallRegistryForTests() {
|
|
63
|
+
const r = getRegistry();
|
|
64
|
+
r.native = null;
|
|
65
|
+
r.web = null;
|
|
66
|
+
}
|
|
67
|
+
function resetPaywallRegistry() {
|
|
68
|
+
if (typeof console !== "undefined" && typeof console.warn === "function") {
|
|
69
|
+
console.warn(
|
|
70
|
+
"[@meetreeve/capacitor-bridge/paywall] resetPaywallRegistry is deprecated; use __resetPaywallRegistryForTests."
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
__resetPaywallRegistryForTests();
|
|
74
|
+
}
|
|
75
|
+
async function runPaywall(product) {
|
|
76
|
+
const r = getRegistry();
|
|
77
|
+
if (isNativePlatform()) {
|
|
78
|
+
if (!r.native) {
|
|
79
|
+
throw new Error(
|
|
80
|
+
"[@meetreeve/capacitor-bridge/paywall] runPaywall called on a native platform but no native handler is registered. Call registerNativePaywall() at app boot (DEV-1388 Reeve.Commerce wiring)."
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
return r.native(product);
|
|
84
|
+
}
|
|
85
|
+
if (!r.web) {
|
|
86
|
+
throw new Error(
|
|
87
|
+
"[@meetreeve/capacitor-bridge/paywall] runPaywall called on web but no web handler is registered. Call registerWebPaywall() at app boot."
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
return r.web(product);
|
|
91
|
+
}
|
|
92
|
+
function shouldShowWebPaywall() {
|
|
93
|
+
return !isNativePlatform();
|
|
94
|
+
}
|
|
95
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
96
|
+
0 && (module.exports = {
|
|
97
|
+
__resetPaywallRegistryForTests,
|
|
98
|
+
registerNativePaywall,
|
|
99
|
+
registerWebPaywall,
|
|
100
|
+
resetPaywallRegistry,
|
|
101
|
+
runPaywall,
|
|
102
|
+
shouldShowWebPaywall
|
|
103
|
+
});
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Native-aware paywall gate.
|
|
3
|
+
*
|
|
4
|
+
* Apple App Store guideline 3.1.1: digital subscriptions sold inside an iOS
|
|
5
|
+
* app MUST use Apple IAP. Linking out to a Stripe Checkout URL from within
|
|
6
|
+
* an iOS Capacitor build will get the app rejected. This module is the one
|
|
7
|
+
* shared gate that every consumer app's paywall flow runs through.
|
|
8
|
+
*
|
|
9
|
+
* Behavior:
|
|
10
|
+
* - On web: allow → run the consumer's web checkout (Stripe etc.).
|
|
11
|
+
* - On iOS/Android: defer to the registered native checkout handler
|
|
12
|
+
* (RevenueCat in v1 — wired by Reeve.Commerce DEV-1388).
|
|
13
|
+
*
|
|
14
|
+
* Consumer apps register a native handler once at boot (typically in a
|
|
15
|
+
* CapacitorProvider sibling). The same shared gate function then routes
|
|
16
|
+
* the actual purchase intent based on the current platform.
|
|
17
|
+
*/
|
|
18
|
+
interface PaywallProduct {
|
|
19
|
+
/** Substrate-side product identifier — matches `commerce_entitlements.tier`. */
|
|
20
|
+
productId: string;
|
|
21
|
+
/** Optional override of the App Store / Play Store product ID, if different. */
|
|
22
|
+
nativeProductId?: string;
|
|
23
|
+
/** Stripe price ID — used on web only. */
|
|
24
|
+
webPriceId?: string;
|
|
25
|
+
/** Display info — passed through to the native sheet for QA. */
|
|
26
|
+
displayName?: string;
|
|
27
|
+
}
|
|
28
|
+
type PaywallResult = {
|
|
29
|
+
status: "purchased";
|
|
30
|
+
sourceChannel: "web_stripe" | "ios_iap" | "android_iap";
|
|
31
|
+
receipt?: unknown;
|
|
32
|
+
} | {
|
|
33
|
+
status: "cancelled";
|
|
34
|
+
sourceChannel: "web_stripe" | "ios_iap" | "android_iap";
|
|
35
|
+
} | {
|
|
36
|
+
status: "error";
|
|
37
|
+
sourceChannel: "web_stripe" | "ios_iap" | "android_iap";
|
|
38
|
+
message: string;
|
|
39
|
+
/** Underlying error / native receipt-error code, when available. */
|
|
40
|
+
cause?: unknown;
|
|
41
|
+
};
|
|
42
|
+
type NativePaywallHandler = (product: PaywallProduct) => Promise<PaywallResult>;
|
|
43
|
+
type WebPaywallHandler = (product: PaywallProduct) => Promise<PaywallResult>;
|
|
44
|
+
/**
|
|
45
|
+
* Register the native (iOS/Android) checkout handler. Reeve.Commerce DEV-1388
|
|
46
|
+
* supplies a RevenueCat-backed implementation; consumer apps call this once.
|
|
47
|
+
*/
|
|
48
|
+
declare function registerNativePaywall(handler: NativePaywallHandler): void;
|
|
49
|
+
/**
|
|
50
|
+
* Register the web checkout handler. Consumer apps wrap their existing
|
|
51
|
+
* Stripe Checkout redirect in this signature.
|
|
52
|
+
*/
|
|
53
|
+
declare function registerWebPaywall(handler: WebPaywallHandler): void;
|
|
54
|
+
/** Internal — for tests only. */
|
|
55
|
+
declare function __resetPaywallRegistryForTests(): void;
|
|
56
|
+
/** @deprecated use __resetPaywallRegistryForTests; prod calls now warn. */
|
|
57
|
+
declare function resetPaywallRegistry(): void;
|
|
58
|
+
/**
|
|
59
|
+
* Run a paywall checkout, routing automatically to the right handler for
|
|
60
|
+
* the current platform.
|
|
61
|
+
*
|
|
62
|
+
* Throws if no handler has been registered for the current platform —
|
|
63
|
+
* "fail loudly" is preferable to "silently link to Stripe on iOS" which
|
|
64
|
+
* is the App Store rejection footgun this whole gate exists to prevent.
|
|
65
|
+
*/
|
|
66
|
+
declare function runPaywall(product: PaywallProduct): Promise<PaywallResult>;
|
|
67
|
+
/**
|
|
68
|
+
* Boolean gate for "should the paywall UI render its Stripe Checkout
|
|
69
|
+
* button?" — false on iOS/Android, true on web. Consumer wp-frontend code
|
|
70
|
+
* uses this to skip rendering the Stripe link entirely when in a Capacitor
|
|
71
|
+
* build (the IAP flow has its own UI sheet).
|
|
72
|
+
*/
|
|
73
|
+
declare function shouldShowWebPaywall(): boolean;
|
|
74
|
+
|
|
75
|
+
export { type NativePaywallHandler, type PaywallProduct, type PaywallResult, type WebPaywallHandler, __resetPaywallRegistryForTests, registerNativePaywall, registerWebPaywall, resetPaywallRegistry, runPaywall, shouldShowWebPaywall };
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Native-aware paywall gate.
|
|
3
|
+
*
|
|
4
|
+
* Apple App Store guideline 3.1.1: digital subscriptions sold inside an iOS
|
|
5
|
+
* app MUST use Apple IAP. Linking out to a Stripe Checkout URL from within
|
|
6
|
+
* an iOS Capacitor build will get the app rejected. This module is the one
|
|
7
|
+
* shared gate that every consumer app's paywall flow runs through.
|
|
8
|
+
*
|
|
9
|
+
* Behavior:
|
|
10
|
+
* - On web: allow → run the consumer's web checkout (Stripe etc.).
|
|
11
|
+
* - On iOS/Android: defer to the registered native checkout handler
|
|
12
|
+
* (RevenueCat in v1 — wired by Reeve.Commerce DEV-1388).
|
|
13
|
+
*
|
|
14
|
+
* Consumer apps register a native handler once at boot (typically in a
|
|
15
|
+
* CapacitorProvider sibling). The same shared gate function then routes
|
|
16
|
+
* the actual purchase intent based on the current platform.
|
|
17
|
+
*/
|
|
18
|
+
interface PaywallProduct {
|
|
19
|
+
/** Substrate-side product identifier — matches `commerce_entitlements.tier`. */
|
|
20
|
+
productId: string;
|
|
21
|
+
/** Optional override of the App Store / Play Store product ID, if different. */
|
|
22
|
+
nativeProductId?: string;
|
|
23
|
+
/** Stripe price ID — used on web only. */
|
|
24
|
+
webPriceId?: string;
|
|
25
|
+
/** Display info — passed through to the native sheet for QA. */
|
|
26
|
+
displayName?: string;
|
|
27
|
+
}
|
|
28
|
+
type PaywallResult = {
|
|
29
|
+
status: "purchased";
|
|
30
|
+
sourceChannel: "web_stripe" | "ios_iap" | "android_iap";
|
|
31
|
+
receipt?: unknown;
|
|
32
|
+
} | {
|
|
33
|
+
status: "cancelled";
|
|
34
|
+
sourceChannel: "web_stripe" | "ios_iap" | "android_iap";
|
|
35
|
+
} | {
|
|
36
|
+
status: "error";
|
|
37
|
+
sourceChannel: "web_stripe" | "ios_iap" | "android_iap";
|
|
38
|
+
message: string;
|
|
39
|
+
/** Underlying error / native receipt-error code, when available. */
|
|
40
|
+
cause?: unknown;
|
|
41
|
+
};
|
|
42
|
+
type NativePaywallHandler = (product: PaywallProduct) => Promise<PaywallResult>;
|
|
43
|
+
type WebPaywallHandler = (product: PaywallProduct) => Promise<PaywallResult>;
|
|
44
|
+
/**
|
|
45
|
+
* Register the native (iOS/Android) checkout handler. Reeve.Commerce DEV-1388
|
|
46
|
+
* supplies a RevenueCat-backed implementation; consumer apps call this once.
|
|
47
|
+
*/
|
|
48
|
+
declare function registerNativePaywall(handler: NativePaywallHandler): void;
|
|
49
|
+
/**
|
|
50
|
+
* Register the web checkout handler. Consumer apps wrap their existing
|
|
51
|
+
* Stripe Checkout redirect in this signature.
|
|
52
|
+
*/
|
|
53
|
+
declare function registerWebPaywall(handler: WebPaywallHandler): void;
|
|
54
|
+
/** Internal — for tests only. */
|
|
55
|
+
declare function __resetPaywallRegistryForTests(): void;
|
|
56
|
+
/** @deprecated use __resetPaywallRegistryForTests; prod calls now warn. */
|
|
57
|
+
declare function resetPaywallRegistry(): void;
|
|
58
|
+
/**
|
|
59
|
+
* Run a paywall checkout, routing automatically to the right handler for
|
|
60
|
+
* the current platform.
|
|
61
|
+
*
|
|
62
|
+
* Throws if no handler has been registered for the current platform —
|
|
63
|
+
* "fail loudly" is preferable to "silently link to Stripe on iOS" which
|
|
64
|
+
* is the App Store rejection footgun this whole gate exists to prevent.
|
|
65
|
+
*/
|
|
66
|
+
declare function runPaywall(product: PaywallProduct): Promise<PaywallResult>;
|
|
67
|
+
/**
|
|
68
|
+
* Boolean gate for "should the paywall UI render its Stripe Checkout
|
|
69
|
+
* button?" — false on iOS/Android, true on web. Consumer wp-frontend code
|
|
70
|
+
* uses this to skip rendering the Stripe link entirely when in a Capacitor
|
|
71
|
+
* build (the IAP flow has its own UI sheet).
|
|
72
|
+
*/
|
|
73
|
+
declare function shouldShowWebPaywall(): boolean;
|
|
74
|
+
|
|
75
|
+
export { type NativePaywallHandler, type PaywallProduct, type PaywallResult, type WebPaywallHandler, __resetPaywallRegistryForTests, registerNativePaywall, registerWebPaywall, resetPaywallRegistry, runPaywall, shouldShowWebPaywall };
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
// src/core/platform.ts
|
|
2
|
+
function getCapacitor() {
|
|
3
|
+
if (typeof window === "undefined") return null;
|
|
4
|
+
const cap = window.Capacitor;
|
|
5
|
+
if (!cap || typeof cap.isNativePlatform !== "function") return null;
|
|
6
|
+
return cap;
|
|
7
|
+
}
|
|
8
|
+
function getPlatform() {
|
|
9
|
+
const cap = getCapacitor();
|
|
10
|
+
if (!cap) return "web";
|
|
11
|
+
const p = cap.getPlatform();
|
|
12
|
+
return p === "ios" ? "ios" : p === "android" ? "android" : "web";
|
|
13
|
+
}
|
|
14
|
+
function isNativePlatform() {
|
|
15
|
+
return getPlatform() !== "web";
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// src/paywall/index.ts
|
|
19
|
+
var GLOBAL_KEY = /* @__PURE__ */ Symbol.for("@meetreeve/capacitor-bridge/paywall@1");
|
|
20
|
+
function getRegistry() {
|
|
21
|
+
const g = globalThis;
|
|
22
|
+
if (!g[GLOBAL_KEY]) g[GLOBAL_KEY] = { native: null, web: null };
|
|
23
|
+
return g[GLOBAL_KEY];
|
|
24
|
+
}
|
|
25
|
+
function registerNativePaywall(handler) {
|
|
26
|
+
getRegistry().native = handler;
|
|
27
|
+
}
|
|
28
|
+
function registerWebPaywall(handler) {
|
|
29
|
+
getRegistry().web = handler;
|
|
30
|
+
}
|
|
31
|
+
function __resetPaywallRegistryForTests() {
|
|
32
|
+
const r = getRegistry();
|
|
33
|
+
r.native = null;
|
|
34
|
+
r.web = null;
|
|
35
|
+
}
|
|
36
|
+
function resetPaywallRegistry() {
|
|
37
|
+
if (typeof console !== "undefined" && typeof console.warn === "function") {
|
|
38
|
+
console.warn(
|
|
39
|
+
"[@meetreeve/capacitor-bridge/paywall] resetPaywallRegistry is deprecated; use __resetPaywallRegistryForTests."
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
__resetPaywallRegistryForTests();
|
|
43
|
+
}
|
|
44
|
+
async function runPaywall(product) {
|
|
45
|
+
const r = getRegistry();
|
|
46
|
+
if (isNativePlatform()) {
|
|
47
|
+
if (!r.native) {
|
|
48
|
+
throw new Error(
|
|
49
|
+
"[@meetreeve/capacitor-bridge/paywall] runPaywall called on a native platform but no native handler is registered. Call registerNativePaywall() at app boot (DEV-1388 Reeve.Commerce wiring)."
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
return r.native(product);
|
|
53
|
+
}
|
|
54
|
+
if (!r.web) {
|
|
55
|
+
throw new Error(
|
|
56
|
+
"[@meetreeve/capacitor-bridge/paywall] runPaywall called on web but no web handler is registered. Call registerWebPaywall() at app boot."
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
return r.web(product);
|
|
60
|
+
}
|
|
61
|
+
function shouldShowWebPaywall() {
|
|
62
|
+
return !isNativePlatform();
|
|
63
|
+
}
|
|
64
|
+
export {
|
|
65
|
+
__resetPaywallRegistryForTests,
|
|
66
|
+
registerNativePaywall,
|
|
67
|
+
registerWebPaywall,
|
|
68
|
+
resetPaywallRegistry,
|
|
69
|
+
runPaywall,
|
|
70
|
+
shouldShowWebPaywall
|
|
71
|
+
};
|