@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,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bug-report widget.
|
|
3
|
+
*
|
|
4
|
+
* TestFlight beta users hit "Report a bug" → small modal → text + optional
|
|
5
|
+
* screenshot → POST to a consumer-configured endpoint (typically a thin
|
|
6
|
+
* Slack-relay route on the consumer's backend). Sentry user feedback works
|
|
7
|
+
* too but the social UX of a Slack channel + screenshot is what beta cohorts
|
|
8
|
+
* actually respond to.
|
|
9
|
+
*
|
|
10
|
+
* This module owns the submission contract + a tiny snapshot helper. The
|
|
11
|
+
* React widget itself ships in src/react/BugReportButton.tsx (next step) —
|
|
12
|
+
* consumer apps that don't use React can call submitBugReport() directly.
|
|
13
|
+
*/
|
|
14
|
+
interface BugReportPayload {
|
|
15
|
+
message: string;
|
|
16
|
+
/** Optional screenshot — data URL or remote URL — uploaded as part of report. */
|
|
17
|
+
screenshot?: string;
|
|
18
|
+
/** Consumer-supplied user identifier (subscriber_id, email — whatever's stable). */
|
|
19
|
+
userIdentifier?: string;
|
|
20
|
+
/** Free-form metadata; merged with platform info before send. */
|
|
21
|
+
extra?: Record<string, unknown>;
|
|
22
|
+
}
|
|
23
|
+
interface BugReportEnvironment {
|
|
24
|
+
platform: string;
|
|
25
|
+
userAgent: string;
|
|
26
|
+
appVersion?: string;
|
|
27
|
+
buildNumber?: string;
|
|
28
|
+
currentUrl: string;
|
|
29
|
+
timestamp: string;
|
|
30
|
+
}
|
|
31
|
+
interface BugReportSubmission extends BugReportPayload {
|
|
32
|
+
environment: BugReportEnvironment;
|
|
33
|
+
}
|
|
34
|
+
interface BugReportConfig {
|
|
35
|
+
endpoint: string;
|
|
36
|
+
/** Headers to attach (auth token, host-app key, etc.). */
|
|
37
|
+
headers?: () => Record<string, string> | Promise<Record<string, string>>;
|
|
38
|
+
/** Consumer-supplied version metadata. */
|
|
39
|
+
appVersion?: string;
|
|
40
|
+
buildNumber?: string;
|
|
41
|
+
/**
|
|
42
|
+
* fetch credentials mode. Defaults to `"omit"` because the Capacitor
|
|
43
|
+
* WebView origin (`capacitor://localhost`) differs from the consumer's API
|
|
44
|
+
* origin, so cookie-based auth wouldn't flow anyway, and `omit` avoids
|
|
45
|
+
* accidentally turning the bug-report endpoint into a CSRF target if the
|
|
46
|
+
* consumer enables permissive CORS. Set to `"include"` only when you
|
|
47
|
+
* understand the consequences for the consumer's endpoint.
|
|
48
|
+
*/
|
|
49
|
+
credentials?: "omit" | "same-origin" | "include";
|
|
50
|
+
}
|
|
51
|
+
declare function configureBugReport(c: BugReportConfig): void;
|
|
52
|
+
/** Internal — for tests only. */
|
|
53
|
+
declare function __resetBugReportConfigForTests(): void;
|
|
54
|
+
/** @deprecated use __resetBugReportConfigForTests; prod calls now warn. */
|
|
55
|
+
declare function resetBugReportConfig(): void;
|
|
56
|
+
declare function getBugReportEnvironment(): BugReportEnvironment;
|
|
57
|
+
/**
|
|
58
|
+
* Submit a bug report to the configured endpoint. Throws if not configured.
|
|
59
|
+
*
|
|
60
|
+
* The post body always includes a top-level `environment` object — consumer
|
|
61
|
+
* backends can route on `environment.platform === "ios"` to tag with a
|
|
62
|
+
* Slack emoji, etc.
|
|
63
|
+
*/
|
|
64
|
+
declare function submitBugReport(payload: BugReportPayload): Promise<void>;
|
|
65
|
+
/**
|
|
66
|
+
* Capture the current screen as a data URL using the Capacitor Screenshot
|
|
67
|
+
* plugin (if present) or a DOM-canvas fallback. Returns null if no capture
|
|
68
|
+
* method is available — the bug-report modal then submits without an image.
|
|
69
|
+
*
|
|
70
|
+
* Note: many WebView screenshot plugins can't capture native chrome (push
|
|
71
|
+
* banners, system alerts). For TestFlight QA that's fine — the bug message
|
|
72
|
+
* almost always describes the chrome anyway.
|
|
73
|
+
*/
|
|
74
|
+
declare function captureScreenshot(): Promise<string | null>;
|
|
75
|
+
|
|
76
|
+
export { type BugReportEnvironment, type BugReportPayload, type BugReportSubmission, __resetBugReportConfigForTests, captureScreenshot, configureBugReport, getBugReportEnvironment, resetBugReportConfig, submitBugReport };
|
|
@@ -0,0 +1,111 @@
|
|
|
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
|
+
|
|
15
|
+
// src/bugreport/index.ts
|
|
16
|
+
var GLOBAL_KEY = /* @__PURE__ */ Symbol.for("@meetreeve/capacitor-bridge/bugreport@1");
|
|
17
|
+
function getGlobalState() {
|
|
18
|
+
const g = globalThis;
|
|
19
|
+
if (!g[GLOBAL_KEY]) g[GLOBAL_KEY] = { config: null };
|
|
20
|
+
return g[GLOBAL_KEY];
|
|
21
|
+
}
|
|
22
|
+
function configureBugReport(c) {
|
|
23
|
+
const state = getGlobalState();
|
|
24
|
+
if (state.config && state.config.endpoint !== c.endpoint) {
|
|
25
|
+
throw new Error(
|
|
26
|
+
"[@meetreeve/capacitor-bridge/bugreport] configureBugReport has already been called with a different endpoint. Re-configuration is refused so a later-loaded script cannot redirect bug-report submissions. Call __resetBugReportConfigForTests() in tests."
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
state.config = Object.freeze({ ...c });
|
|
30
|
+
}
|
|
31
|
+
function __resetBugReportConfigForTests() {
|
|
32
|
+
getGlobalState().config = null;
|
|
33
|
+
}
|
|
34
|
+
function resetBugReportConfig() {
|
|
35
|
+
if (typeof console !== "undefined" && typeof console.warn === "function") {
|
|
36
|
+
console.warn(
|
|
37
|
+
"[@meetreeve/capacitor-bridge/bugreport] resetBugReportConfig is deprecated; use __resetBugReportConfigForTests."
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
__resetBugReportConfigForTests();
|
|
41
|
+
}
|
|
42
|
+
function getBugReportEnvironment() {
|
|
43
|
+
const config = getGlobalState().config;
|
|
44
|
+
if (typeof window === "undefined") {
|
|
45
|
+
return {
|
|
46
|
+
platform: getPlatform(),
|
|
47
|
+
userAgent: "",
|
|
48
|
+
currentUrl: "",
|
|
49
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
return {
|
|
53
|
+
platform: getPlatform(),
|
|
54
|
+
userAgent: window.navigator?.userAgent ?? "",
|
|
55
|
+
appVersion: config?.appVersion,
|
|
56
|
+
buildNumber: config?.buildNumber,
|
|
57
|
+
currentUrl: window.location?.href ?? "",
|
|
58
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
async function submitBugReport(payload) {
|
|
62
|
+
const config = getGlobalState().config;
|
|
63
|
+
if (!config) {
|
|
64
|
+
throw new Error(
|
|
65
|
+
"[@meetreeve/capacitor-bridge/bugreport] submitBugReport called without configureBugReport({endpoint})."
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
const submission = {
|
|
69
|
+
...payload,
|
|
70
|
+
environment: getBugReportEnvironment()
|
|
71
|
+
};
|
|
72
|
+
const headers = {
|
|
73
|
+
"Content-Type": "application/json"
|
|
74
|
+
};
|
|
75
|
+
if (config.headers) {
|
|
76
|
+
const extraHeaders = await config.headers();
|
|
77
|
+
Object.assign(headers, extraHeaders);
|
|
78
|
+
}
|
|
79
|
+
const response = await fetch(config.endpoint, {
|
|
80
|
+
method: "POST",
|
|
81
|
+
headers,
|
|
82
|
+
body: JSON.stringify(submission),
|
|
83
|
+
credentials: config.credentials ?? "omit"
|
|
84
|
+
});
|
|
85
|
+
if (!response.ok) {
|
|
86
|
+
throw new Error(
|
|
87
|
+
`[@meetreeve/capacitor-bridge/bugreport] bug-report submission failed: ${response.status} ${response.statusText}`
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
async function captureScreenshot() {
|
|
92
|
+
if (typeof window === "undefined") return null;
|
|
93
|
+
const cap = window.Capacitor;
|
|
94
|
+
const plugin = cap?.Plugins?.Screenshot;
|
|
95
|
+
if (plugin) {
|
|
96
|
+
try {
|
|
97
|
+
const { base64 } = await plugin.take();
|
|
98
|
+
return `data:image/png;base64,${base64}`;
|
|
99
|
+
} catch {
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
export {
|
|
105
|
+
__resetBugReportConfigForTests,
|
|
106
|
+
captureScreenshot,
|
|
107
|
+
configureBugReport,
|
|
108
|
+
getBugReportEnvironment,
|
|
109
|
+
resetBugReportConfig,
|
|
110
|
+
submitBugReport
|
|
111
|
+
};
|
|
@@ -0,0 +1,160 @@
|
|
|
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/core/index.ts
|
|
21
|
+
var core_exports = {};
|
|
22
|
+
__export(core_exports, {
|
|
23
|
+
__resetSplashStateForTests: () => __resetSplashStateForTests,
|
|
24
|
+
dismissSplash: () => dismissSplash,
|
|
25
|
+
getPlatform: () => getPlatform,
|
|
26
|
+
getSafeAreaInsets: () => getSafeAreaInsets,
|
|
27
|
+
hasSafeAreaInsets: () => hasSafeAreaInsets,
|
|
28
|
+
installSafeAreaVars: () => installSafeAreaVars,
|
|
29
|
+
isAndroid: () => isAndroid,
|
|
30
|
+
isIOS: () => isIOS,
|
|
31
|
+
isNativePlatform: () => isNativePlatform,
|
|
32
|
+
resetSplashState: () => resetSplashState
|
|
33
|
+
});
|
|
34
|
+
module.exports = __toCommonJS(core_exports);
|
|
35
|
+
|
|
36
|
+
// src/core/platform.ts
|
|
37
|
+
function getCapacitor() {
|
|
38
|
+
if (typeof window === "undefined") return null;
|
|
39
|
+
const cap = window.Capacitor;
|
|
40
|
+
if (!cap || typeof cap.isNativePlatform !== "function") return null;
|
|
41
|
+
return cap;
|
|
42
|
+
}
|
|
43
|
+
function getPlatform() {
|
|
44
|
+
const cap = getCapacitor();
|
|
45
|
+
if (!cap) return "web";
|
|
46
|
+
const p = cap.getPlatform();
|
|
47
|
+
return p === "ios" ? "ios" : p === "android" ? "android" : "web";
|
|
48
|
+
}
|
|
49
|
+
function isNativePlatform() {
|
|
50
|
+
return getPlatform() !== "web";
|
|
51
|
+
}
|
|
52
|
+
function isIOS() {
|
|
53
|
+
return getPlatform() === "ios";
|
|
54
|
+
}
|
|
55
|
+
function isAndroid() {
|
|
56
|
+
return getPlatform() === "android";
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// src/core/safe-area.ts
|
|
60
|
+
var ZERO = { top: 0, right: 0, bottom: 0, left: 0 };
|
|
61
|
+
function getSafeAreaInsets() {
|
|
62
|
+
if (typeof window === "undefined" || typeof document === "undefined") {
|
|
63
|
+
return ZERO;
|
|
64
|
+
}
|
|
65
|
+
const root = document.documentElement;
|
|
66
|
+
const styles = window.getComputedStyle(root);
|
|
67
|
+
const read = (side) => {
|
|
68
|
+
const v = styles.getPropertyValue(`--safe-area-inset-${side}`).trim();
|
|
69
|
+
const n = parseFloat(v);
|
|
70
|
+
return Number.isFinite(n) ? n : 0;
|
|
71
|
+
};
|
|
72
|
+
return {
|
|
73
|
+
top: read("top"),
|
|
74
|
+
right: read("right"),
|
|
75
|
+
bottom: read("bottom"),
|
|
76
|
+
left: read("left")
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
function installSafeAreaVars() {
|
|
80
|
+
if (typeof document === "undefined") return;
|
|
81
|
+
const TAG = "reeve-capacitor-bridge-safe-area";
|
|
82
|
+
if (document.querySelector(`style[data-${TAG}]`)) return;
|
|
83
|
+
const style = document.createElement("style");
|
|
84
|
+
style.setAttribute(`data-${TAG}`, "1");
|
|
85
|
+
style.textContent = `
|
|
86
|
+
:root {
|
|
87
|
+
--safe-area-inset-top: env(safe-area-inset-top, 0px);
|
|
88
|
+
--safe-area-inset-right: env(safe-area-inset-right, 0px);
|
|
89
|
+
--safe-area-inset-bottom: env(safe-area-inset-bottom, 0px);
|
|
90
|
+
--safe-area-inset-left: env(safe-area-inset-left, 0px);
|
|
91
|
+
}
|
|
92
|
+
`;
|
|
93
|
+
document.head.appendChild(style);
|
|
94
|
+
}
|
|
95
|
+
function hasSafeAreaInsets() {
|
|
96
|
+
if (!isNativePlatform()) return false;
|
|
97
|
+
const insets = getSafeAreaInsets();
|
|
98
|
+
return insets.top > 0 || insets.bottom > 0;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// src/core/splash.ts
|
|
102
|
+
function getSplashPlugin() {
|
|
103
|
+
if (typeof window === "undefined") return null;
|
|
104
|
+
const cap = window.Capacitor;
|
|
105
|
+
return cap?.Plugins?.SplashScreen ?? null;
|
|
106
|
+
}
|
|
107
|
+
var GLOBAL_KEY = /* @__PURE__ */ Symbol.for("@meetreeve/capacitor-bridge/splash@1");
|
|
108
|
+
function getGlobalState() {
|
|
109
|
+
const g = globalThis;
|
|
110
|
+
if (!g[GLOBAL_KEY]) g[GLOBAL_KEY] = { done: null, pending: null };
|
|
111
|
+
return g[GLOBAL_KEY];
|
|
112
|
+
}
|
|
113
|
+
async function dismissSplash(fadeOutDuration = 250) {
|
|
114
|
+
if (!isNativePlatform()) return;
|
|
115
|
+
const state = getGlobalState();
|
|
116
|
+
if (state.done) return state.done;
|
|
117
|
+
if (state.pending) return state.pending;
|
|
118
|
+
const plugin = getSplashPlugin();
|
|
119
|
+
if (!plugin) return;
|
|
120
|
+
const attempt = (async () => {
|
|
121
|
+
try {
|
|
122
|
+
await plugin.hide({ fadeOutDuration });
|
|
123
|
+
state.done = Promise.resolve();
|
|
124
|
+
} catch (err) {
|
|
125
|
+
state.pending = null;
|
|
126
|
+
throw err;
|
|
127
|
+
} finally {
|
|
128
|
+
if (state.done) state.pending = null;
|
|
129
|
+
}
|
|
130
|
+
})();
|
|
131
|
+
state.pending = attempt;
|
|
132
|
+
return attempt.catch(() => {
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
function __resetSplashStateForTests() {
|
|
136
|
+
const state = getGlobalState();
|
|
137
|
+
state.done = null;
|
|
138
|
+
state.pending = null;
|
|
139
|
+
}
|
|
140
|
+
function resetSplashState() {
|
|
141
|
+
if (typeof console !== "undefined" && typeof console.warn === "function") {
|
|
142
|
+
console.warn(
|
|
143
|
+
"[@meetreeve/capacitor-bridge/splash] resetSplashState is deprecated; use __resetSplashStateForTests."
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
__resetSplashStateForTests();
|
|
147
|
+
}
|
|
148
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
149
|
+
0 && (module.exports = {
|
|
150
|
+
__resetSplashStateForTests,
|
|
151
|
+
dismissSplash,
|
|
152
|
+
getPlatform,
|
|
153
|
+
getSafeAreaInsets,
|
|
154
|
+
hasSafeAreaInsets,
|
|
155
|
+
installSafeAreaVars,
|
|
156
|
+
isAndroid,
|
|
157
|
+
isIOS,
|
|
158
|
+
isNativePlatform,
|
|
159
|
+
resetSplashState
|
|
160
|
+
});
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export { N as NativePlatform, P as Platform, S as SafeAreaInsets, g as getPlatform, a as getSafeAreaInsets, h as hasSafeAreaInsets, i as installSafeAreaVars, b as isAndroid, c as isIOS, d as isNativePlatform } from '../safe-area-B67yGQOn.cjs';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Splash bridge. Capacitor's SplashScreen plugin holds the native launch
|
|
5
|
+
* screen visible until JS explicitly hides it. Without this bridge, every
|
|
6
|
+
* consumer app would have to figure out the right moment to call hide().
|
|
7
|
+
*
|
|
8
|
+
* The pattern we standardize: hide on first paint of authenticated content
|
|
9
|
+
* (or first paint of the public landing if no auth). Consumer apps call
|
|
10
|
+
* dismissSplash() once their root component has mounted.
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* Dismiss the native splash screen. No-op on web or if already successfully
|
|
14
|
+
* dismissed once. Concurrent callers coalesce onto a single in-flight
|
|
15
|
+
* dismiss promise so the plugin only sees one `hide()` call per attempt,
|
|
16
|
+
* and a plugin error releases the latch so a later attempt can retry.
|
|
17
|
+
*
|
|
18
|
+
* Fade-out defaults to 250ms for a perceptibly smooth handoff to JS-rendered
|
|
19
|
+
* content (vs the plugin's default which can feel abrupt).
|
|
20
|
+
*/
|
|
21
|
+
declare function dismissSplash(fadeOutDuration?: number): Promise<void>;
|
|
22
|
+
/**
|
|
23
|
+
* Internal — clears the dismissed latch. For tests only.
|
|
24
|
+
*/
|
|
25
|
+
declare function __resetSplashStateForTests(): void;
|
|
26
|
+
/** @deprecated use __resetSplashStateForTests; prod calls now warn. */
|
|
27
|
+
declare function resetSplashState(): void;
|
|
28
|
+
|
|
29
|
+
export { __resetSplashStateForTests, dismissSplash, resetSplashState };
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export { N as NativePlatform, P as Platform, S as SafeAreaInsets, g as getPlatform, a as getSafeAreaInsets, h as hasSafeAreaInsets, i as installSafeAreaVars, b as isAndroid, c as isIOS, d as isNativePlatform } from '../safe-area-B67yGQOn.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Splash bridge. Capacitor's SplashScreen plugin holds the native launch
|
|
5
|
+
* screen visible until JS explicitly hides it. Without this bridge, every
|
|
6
|
+
* consumer app would have to figure out the right moment to call hide().
|
|
7
|
+
*
|
|
8
|
+
* The pattern we standardize: hide on first paint of authenticated content
|
|
9
|
+
* (or first paint of the public landing if no auth). Consumer apps call
|
|
10
|
+
* dismissSplash() once their root component has mounted.
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* Dismiss the native splash screen. No-op on web or if already successfully
|
|
14
|
+
* dismissed once. Concurrent callers coalesce onto a single in-flight
|
|
15
|
+
* dismiss promise so the plugin only sees one `hide()` call per attempt,
|
|
16
|
+
* and a plugin error releases the latch so a later attempt can retry.
|
|
17
|
+
*
|
|
18
|
+
* Fade-out defaults to 250ms for a perceptibly smooth handoff to JS-rendered
|
|
19
|
+
* content (vs the plugin's default which can feel abrupt).
|
|
20
|
+
*/
|
|
21
|
+
declare function dismissSplash(fadeOutDuration?: number): Promise<void>;
|
|
22
|
+
/**
|
|
23
|
+
* Internal — clears the dismissed latch. For tests only.
|
|
24
|
+
*/
|
|
25
|
+
declare function __resetSplashStateForTests(): void;
|
|
26
|
+
/** @deprecated use __resetSplashStateForTests; prod calls now warn. */
|
|
27
|
+
declare function resetSplashState(): void;
|
|
28
|
+
|
|
29
|
+
export { __resetSplashStateForTests, dismissSplash, resetSplashState };
|
|
@@ -0,0 +1,124 @@
|
|
|
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
|
+
function isIOS() {
|
|
18
|
+
return getPlatform() === "ios";
|
|
19
|
+
}
|
|
20
|
+
function isAndroid() {
|
|
21
|
+
return getPlatform() === "android";
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// src/core/safe-area.ts
|
|
25
|
+
var ZERO = { top: 0, right: 0, bottom: 0, left: 0 };
|
|
26
|
+
function getSafeAreaInsets() {
|
|
27
|
+
if (typeof window === "undefined" || typeof document === "undefined") {
|
|
28
|
+
return ZERO;
|
|
29
|
+
}
|
|
30
|
+
const root = document.documentElement;
|
|
31
|
+
const styles = window.getComputedStyle(root);
|
|
32
|
+
const read = (side) => {
|
|
33
|
+
const v = styles.getPropertyValue(`--safe-area-inset-${side}`).trim();
|
|
34
|
+
const n = parseFloat(v);
|
|
35
|
+
return Number.isFinite(n) ? n : 0;
|
|
36
|
+
};
|
|
37
|
+
return {
|
|
38
|
+
top: read("top"),
|
|
39
|
+
right: read("right"),
|
|
40
|
+
bottom: read("bottom"),
|
|
41
|
+
left: read("left")
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
function installSafeAreaVars() {
|
|
45
|
+
if (typeof document === "undefined") return;
|
|
46
|
+
const TAG = "reeve-capacitor-bridge-safe-area";
|
|
47
|
+
if (document.querySelector(`style[data-${TAG}]`)) return;
|
|
48
|
+
const style = document.createElement("style");
|
|
49
|
+
style.setAttribute(`data-${TAG}`, "1");
|
|
50
|
+
style.textContent = `
|
|
51
|
+
:root {
|
|
52
|
+
--safe-area-inset-top: env(safe-area-inset-top, 0px);
|
|
53
|
+
--safe-area-inset-right: env(safe-area-inset-right, 0px);
|
|
54
|
+
--safe-area-inset-bottom: env(safe-area-inset-bottom, 0px);
|
|
55
|
+
--safe-area-inset-left: env(safe-area-inset-left, 0px);
|
|
56
|
+
}
|
|
57
|
+
`;
|
|
58
|
+
document.head.appendChild(style);
|
|
59
|
+
}
|
|
60
|
+
function hasSafeAreaInsets() {
|
|
61
|
+
if (!isNativePlatform()) return false;
|
|
62
|
+
const insets = getSafeAreaInsets();
|
|
63
|
+
return insets.top > 0 || insets.bottom > 0;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// src/core/splash.ts
|
|
67
|
+
function getSplashPlugin() {
|
|
68
|
+
if (typeof window === "undefined") return null;
|
|
69
|
+
const cap = window.Capacitor;
|
|
70
|
+
return cap?.Plugins?.SplashScreen ?? null;
|
|
71
|
+
}
|
|
72
|
+
var GLOBAL_KEY = /* @__PURE__ */ Symbol.for("@meetreeve/capacitor-bridge/splash@1");
|
|
73
|
+
function getGlobalState() {
|
|
74
|
+
const g = globalThis;
|
|
75
|
+
if (!g[GLOBAL_KEY]) g[GLOBAL_KEY] = { done: null, pending: null };
|
|
76
|
+
return g[GLOBAL_KEY];
|
|
77
|
+
}
|
|
78
|
+
async function dismissSplash(fadeOutDuration = 250) {
|
|
79
|
+
if (!isNativePlatform()) return;
|
|
80
|
+
const state = getGlobalState();
|
|
81
|
+
if (state.done) return state.done;
|
|
82
|
+
if (state.pending) return state.pending;
|
|
83
|
+
const plugin = getSplashPlugin();
|
|
84
|
+
if (!plugin) return;
|
|
85
|
+
const attempt = (async () => {
|
|
86
|
+
try {
|
|
87
|
+
await plugin.hide({ fadeOutDuration });
|
|
88
|
+
state.done = Promise.resolve();
|
|
89
|
+
} catch (err) {
|
|
90
|
+
state.pending = null;
|
|
91
|
+
throw err;
|
|
92
|
+
} finally {
|
|
93
|
+
if (state.done) state.pending = null;
|
|
94
|
+
}
|
|
95
|
+
})();
|
|
96
|
+
state.pending = attempt;
|
|
97
|
+
return attempt.catch(() => {
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
function __resetSplashStateForTests() {
|
|
101
|
+
const state = getGlobalState();
|
|
102
|
+
state.done = null;
|
|
103
|
+
state.pending = null;
|
|
104
|
+
}
|
|
105
|
+
function resetSplashState() {
|
|
106
|
+
if (typeof console !== "undefined" && typeof console.warn === "function") {
|
|
107
|
+
console.warn(
|
|
108
|
+
"[@meetreeve/capacitor-bridge/splash] resetSplashState is deprecated; use __resetSplashStateForTests."
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
__resetSplashStateForTests();
|
|
112
|
+
}
|
|
113
|
+
export {
|
|
114
|
+
__resetSplashStateForTests,
|
|
115
|
+
dismissSplash,
|
|
116
|
+
getPlatform,
|
|
117
|
+
getSafeAreaInsets,
|
|
118
|
+
hasSafeAreaInsets,
|
|
119
|
+
installSafeAreaVars,
|
|
120
|
+
isAndroid,
|
|
121
|
+
isIOS,
|
|
122
|
+
isNativePlatform,
|
|
123
|
+
resetSplashState
|
|
124
|
+
};
|
|
@@ -0,0 +1,166 @@
|
|
|
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/deeplink/index.ts
|
|
21
|
+
var deeplink_exports = {};
|
|
22
|
+
__export(deeplink_exports, {
|
|
23
|
+
__resetDeepLinkHandlersForTests: () => __resetDeepLinkHandlersForTests,
|
|
24
|
+
dispatchDeepLink: () => dispatchDeepLink,
|
|
25
|
+
parseDeepLink: () => parseDeepLink,
|
|
26
|
+
registerDeepLinkHandler: () => registerDeepLinkHandler,
|
|
27
|
+
resetDeepLinkHandlers: () => resetDeepLinkHandlers,
|
|
28
|
+
startDeepLinkListener: () => startDeepLinkListener,
|
|
29
|
+
stopDeepLinkListener: () => stopDeepLinkListener
|
|
30
|
+
});
|
|
31
|
+
module.exports = __toCommonJS(deeplink_exports);
|
|
32
|
+
|
|
33
|
+
// src/core/platform.ts
|
|
34
|
+
function getCapacitor() {
|
|
35
|
+
if (typeof window === "undefined") return null;
|
|
36
|
+
const cap = window.Capacitor;
|
|
37
|
+
if (!cap || typeof cap.isNativePlatform !== "function") return null;
|
|
38
|
+
return cap;
|
|
39
|
+
}
|
|
40
|
+
function getPlatform() {
|
|
41
|
+
const cap = getCapacitor();
|
|
42
|
+
if (!cap) return "web";
|
|
43
|
+
const p = cap.getPlatform();
|
|
44
|
+
return p === "ios" ? "ios" : p === "android" ? "android" : "web";
|
|
45
|
+
}
|
|
46
|
+
function isNativePlatform() {
|
|
47
|
+
return getPlatform() !== "web";
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// src/deeplink/index.ts
|
|
51
|
+
function getAppPlugin() {
|
|
52
|
+
if (typeof window === "undefined") return null;
|
|
53
|
+
const cap = window.Capacitor;
|
|
54
|
+
return cap?.Plugins?.App ?? null;
|
|
55
|
+
}
|
|
56
|
+
var WEB_SCHEMES = /* @__PURE__ */ new Set(["http", "https", "ws", "wss", "file"]);
|
|
57
|
+
function normalizePath(raw) {
|
|
58
|
+
if (!raw || raw === "/") return "/";
|
|
59
|
+
const collapsed = raw.replace(/\/+/g, "/");
|
|
60
|
+
if (collapsed === "/") return "/";
|
|
61
|
+
return collapsed.endsWith("/") ? collapsed.slice(0, -1) : collapsed;
|
|
62
|
+
}
|
|
63
|
+
function parseDeepLink(url) {
|
|
64
|
+
const parsed = new URL(url);
|
|
65
|
+
const scheme = parsed.protocol.replace(/:$/, "");
|
|
66
|
+
const query = {};
|
|
67
|
+
parsed.searchParams.forEach((value, key) => {
|
|
68
|
+
query[key] = value;
|
|
69
|
+
});
|
|
70
|
+
const isWeb = WEB_SCHEMES.has(scheme);
|
|
71
|
+
const rawHost = parsed.host;
|
|
72
|
+
const rawPath = parsed.pathname || "";
|
|
73
|
+
const host = isWeb ? rawHost : "";
|
|
74
|
+
const path = isWeb ? normalizePath(rawPath || "/") : normalizePath(`/${rawHost}${rawPath}`);
|
|
75
|
+
return {
|
|
76
|
+
url,
|
|
77
|
+
scheme,
|
|
78
|
+
host,
|
|
79
|
+
path,
|
|
80
|
+
query,
|
|
81
|
+
hash: parsed.hash.replace(/^#/, "")
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
var GLOBAL_KEY = /* @__PURE__ */ Symbol.for("@meetreeve/capacitor-bridge/deeplink@1");
|
|
85
|
+
function getGlobalState() {
|
|
86
|
+
const g = globalThis;
|
|
87
|
+
if (!g[GLOBAL_KEY]) {
|
|
88
|
+
g[GLOBAL_KEY] = { handlers: [], listenerPromise: null, allowedSchemes: null };
|
|
89
|
+
}
|
|
90
|
+
return g[GLOBAL_KEY];
|
|
91
|
+
}
|
|
92
|
+
function registerDeepLinkHandler(handler) {
|
|
93
|
+
const state = getGlobalState();
|
|
94
|
+
state.handlers.push(handler);
|
|
95
|
+
return () => {
|
|
96
|
+
const idx = state.handlers.indexOf(handler);
|
|
97
|
+
if (idx >= 0) state.handlers.splice(idx, 1);
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
function __resetDeepLinkHandlersForTests() {
|
|
101
|
+
const state = getGlobalState();
|
|
102
|
+
state.handlers.length = 0;
|
|
103
|
+
state.listenerPromise = null;
|
|
104
|
+
state.allowedSchemes = null;
|
|
105
|
+
}
|
|
106
|
+
function resetDeepLinkHandlers() {
|
|
107
|
+
if (typeof console !== "undefined" && typeof console.warn === "function") {
|
|
108
|
+
console.warn(
|
|
109
|
+
"[@meetreeve/capacitor-bridge/deeplink] resetDeepLinkHandlers is deprecated; use __resetDeepLinkHandlersForTests."
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
__resetDeepLinkHandlersForTests();
|
|
113
|
+
}
|
|
114
|
+
async function dispatchDeepLink(link) {
|
|
115
|
+
const state = getGlobalState();
|
|
116
|
+
for (const h of state.handlers) {
|
|
117
|
+
try {
|
|
118
|
+
const result = await h(link);
|
|
119
|
+
if (result === true) return true;
|
|
120
|
+
} catch {
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return false;
|
|
124
|
+
}
|
|
125
|
+
function startDeepLinkListener(opts = {}) {
|
|
126
|
+
if (!isNativePlatform()) return null;
|
|
127
|
+
const state = getGlobalState();
|
|
128
|
+
if (opts.allowedSchemes !== void 0) {
|
|
129
|
+
state.allowedSchemes = new Set(opts.allowedSchemes);
|
|
130
|
+
}
|
|
131
|
+
if (state.listenerPromise) return state.listenerPromise;
|
|
132
|
+
const plugin = getAppPlugin();
|
|
133
|
+
if (!plugin) return null;
|
|
134
|
+
state.listenerPromise = plugin.addListener("appUrlOpen", (event) => {
|
|
135
|
+
let link;
|
|
136
|
+
try {
|
|
137
|
+
link = parseDeepLink(event.url);
|
|
138
|
+
} catch {
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
if (state.allowedSchemes && !state.allowedSchemes.has(link.scheme)) {
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
void dispatchDeepLink(link).then((handled) => {
|
|
145
|
+
if (!handled && opts.onUnhandled) opts.onUnhandled(link);
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
return state.listenerPromise;
|
|
149
|
+
}
|
|
150
|
+
async function stopDeepLinkListener() {
|
|
151
|
+
const state = getGlobalState();
|
|
152
|
+
if (!state.listenerPromise) return;
|
|
153
|
+
const listener = await state.listenerPromise;
|
|
154
|
+
state.listenerPromise = null;
|
|
155
|
+
await listener.remove();
|
|
156
|
+
}
|
|
157
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
158
|
+
0 && (module.exports = {
|
|
159
|
+
__resetDeepLinkHandlersForTests,
|
|
160
|
+
dispatchDeepLink,
|
|
161
|
+
parseDeepLink,
|
|
162
|
+
registerDeepLinkHandler,
|
|
163
|
+
resetDeepLinkHandlers,
|
|
164
|
+
startDeepLinkListener,
|
|
165
|
+
stopDeepLinkListener
|
|
166
|
+
});
|