@dynamic-labs-sdk/react-native-embedded-wallet-export 0.0.0 → 1.11.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 ADDED
@@ -0,0 +1,116 @@
1
+ # @dynamic-labs-sdk/react-native-embedded-wallet-export
2
+
3
+ Secure private-key export for Dynamic WaaS embedded wallets on **React Native**.
4
+
5
+ The export ceremony runs inside a visible, screenshot-protected native WebView
6
+ overlay. The private key is rendered entirely within the WebView's sandboxed
7
+ document — it is **never** sent across the bridge, so the host application's
8
+ JavaScript cannot read, log, or screenshot it. This is the intentional security
9
+ boundary: the developer integrating the SDK cannot access the raw key.
10
+
11
+ ## Installation
12
+
13
+ ```bash
14
+ pnpm add @dynamic-labs-sdk/react-native-embedded-wallet-export
15
+ ```
16
+
17
+ Requires `@dynamic-labs-sdk/client` (configured for React Native) and a React
18
+ Native host that links the `DynamicClientWebView` native module shipped with the
19
+ client package.
20
+
21
+ ## Usage
22
+
23
+ Mount `<ExportPrivateKey />` to present the export overlay; unmount it to
24
+ dismiss. The component owns the overlay's lifecycle.
25
+
26
+ ```tsx
27
+ import { ExportPrivateKey } from '@dynamic-labs-sdk/react-native-embedded-wallet-export';
28
+ import { getWalletAccounts } from '@dynamic-labs-sdk/client';
29
+ import { isWaasWalletAccount } from '@dynamic-labs-sdk/client/waas';
30
+ import { useState } from 'react';
31
+ import { Button, View } from 'react-native';
32
+
33
+ function ExportScreen() {
34
+ const [revealing, setRevealing] = useState(false);
35
+ const walletAccount = getWalletAccounts()[0];
36
+
37
+ if (!walletAccount || !isWaasWalletAccount({ walletAccount })) return null;
38
+
39
+ return (
40
+ <View>
41
+ <Button title="Reveal private key" onPress={() => setRevealing(true)} />
42
+ {revealing && (
43
+ <ExportPrivateKey
44
+ walletAccount={walletAccount}
45
+ onError={(error) => {
46
+ // Log a non-sensitive representation only — never the raw error,
47
+ // which could carry key material or stack traces.
48
+ const errorType = error instanceof Error ? error.name : typeof error;
49
+ console.warn('Private key export failed', { errorType });
50
+ setRevealing(false);
51
+ }}
52
+ />
53
+ )}
54
+ </View>
55
+ );
56
+ }
57
+ ```
58
+
59
+ ### Showing a loading state
60
+
61
+ `ExportPrivateKey` is a minimal primitive: it renders the secure box plus the
62
+ native overlay and signals readiness via `onComplete` (fired once the key is
63
+ displayed). The loading UI is yours to style — render your own spinner and hide
64
+ it on `onComplete`. The native overlay is transparent while it loads, so a
65
+ spinner placed behind it shows through during the wait.
66
+
67
+ ```tsx
68
+ import { ActivityIndicator, StyleSheet, View } from 'react-native';
69
+ import { useState } from 'react';
70
+
71
+ function ExportWithSpinner({ walletAccount }) {
72
+ const [loading, setLoading] = useState(true);
73
+
74
+ return (
75
+ <View style={{ position: 'relative' }}>
76
+ <ExportPrivateKey
77
+ walletAccount={walletAccount}
78
+ onComplete={() => setLoading(false)}
79
+ onError={() => setLoading(false)}
80
+ />
81
+ {loading && (
82
+ <View
83
+ pointerEvents="none"
84
+ style={[
85
+ StyleSheet.absoluteFillObject,
86
+ { alignItems: 'center', justifyContent: 'center' },
87
+ ]}
88
+ >
89
+ <ActivityIndicator size="large" />
90
+ </View>
91
+ )}
92
+ </View>
93
+ );
94
+ }
95
+ ```
96
+
97
+ `pointerEvents="none"` keeps the spinner from intercepting taps meant for the
98
+ native key WebView (e.g. its copy button).
99
+
100
+ ### Props
101
+
102
+ | Prop | Type | Description |
103
+ | --------------- | -------------------------- | ----------------------------------------------------------------- |
104
+ | `walletAccount` | `WalletAccount` | The WaaS wallet account whose private key is exported. Required. |
105
+ | `password` | `string` | Optional password to decrypt the private key. |
106
+ | `onComplete` | `() => void` | Called once the key is displayed in the overlay. No key material. |
107
+ | `onError` | `(error: unknown) => void` | Called on export failure. The error never contains key material. |
108
+
109
+ ## Security
110
+
111
+ - The key is displayed only inside the native WebView overlay and never crosses
112
+ the JS bridge.
113
+ - The overlay enables screenshot/recording protection and disables web
114
+ inspection.
115
+ - Unmounting the component tears the overlay down, so the displayed key cannot
116
+ outlive the screen that requested it.
@@ -0,0 +1,26 @@
1
+ import type { FC } from 'react';
2
+ import type { ExportPrivateKeyProps } from './ExportPrivateKey.types';
3
+ /**
4
+ * Renders a secure private-key export box for a WaaS wallet account on React
5
+ * Native. The key is rendered inside a `react-native-webview` that fills the
6
+ * box — the same split the web SDK uses (host renders the chrome, the
7
+ * cross-origin document renders only the key). Because it is a normal RN view,
8
+ * it scrolls, clips and positions like any view; no native overlay or manual
9
+ * window positioning is involved. The key is rendered inside the WebView and
10
+ * never crosses the bridge, so the host app's JS cannot read, log, or
11
+ * screenshot it. On Android, window-level screenshot/recording protection is
12
+ * enabled while the component is mounted.
13
+ *
14
+ * Place it inside your own modal/card and add your own close button — unmounting
15
+ * tears the WebView down. Size/position it with the `style` prop.
16
+ *
17
+ * @example
18
+ * ```tsx
19
+ * <Modal visible={revealing} onRequestClose={() => setRevealing(false)}>
20
+ * <ExportPrivateKey walletAccount={walletAccount} onError={setError} />
21
+ * <Button title="I'm Done" onPress={() => setRevealing(false)} />
22
+ * </Modal>
23
+ * ```
24
+ */
25
+ export declare const ExportPrivateKey: FC<ExportPrivateKeyProps>;
26
+ //# sourceMappingURL=ExportPrivateKey.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ExportPrivateKey.d.ts","sourceRoot":"","sources":["../../src/ExportPrivateKey/ExportPrivateKey.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC;AAUhC,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,0BAA0B,CAAC;AAEtE;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,eAAO,MAAM,gBAAgB,EAAE,EAAE,CAAC,qBAAqB,CA6GtD,CAAC"}
@@ -0,0 +1,33 @@
1
+ import type { WalletAccount } from '@dynamic-labs-sdk/client';
2
+ import type { StyleProp, ViewStyle } from 'react-native';
3
+ /**
4
+ * Props for the {@link ExportPrivateKey} component.
5
+ *
6
+ * The component owns the export view's lifecycle: the key is shown while the
7
+ * component is mounted and torn down on unmount. The private key is rendered
8
+ * entirely inside the `react-native-webview` and never reaches this (or any)
9
+ * host JS layer.
10
+ */
11
+ export type ExportPrivateKeyProps = {
12
+ /**
13
+ * Called once the export ceremony has completed and the key is displayed in
14
+ * the WebView. Receives no key material.
15
+ */
16
+ onComplete?: () => void;
17
+ /**
18
+ * Called if the export fails (load failure, step-up MFA failure, etc.). The
19
+ * error never contains key material.
20
+ */
21
+ onError?: (error: unknown) => void;
22
+ /** Optional password to decrypt the private key. */
23
+ password?: string;
24
+ /**
25
+ * Style for the box that hosts the key display (size, border, background).
26
+ * The WebView fills this box, so its dimensions control the display size.
27
+ * Defaults to a minimal white box with a min height; add your own chrome.
28
+ */
29
+ style?: StyleProp<ViewStyle>;
30
+ /** The WaaS wallet account whose private key is exported. */
31
+ walletAccount: WalletAccount;
32
+ };
33
+ //# sourceMappingURL=ExportPrivateKey.types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ExportPrivateKey.types.d.ts","sourceRoot":"","sources":["../../src/ExportPrivateKey/ExportPrivateKey.types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAC9D,OAAO,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAEzD;;;;;;;GAOG;AACH,MAAM,MAAM,qBAAqB,GAAG;IAClC;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,IAAI,CAAC;IACxB;;;OAGG;IACH,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;IACnC,oDAAoD;IACpD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;;OAIG;IACH,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC7B,6DAA6D;IAC7D,aAAa,EAAE,aAAa,CAAC;CAC9B,CAAC"}
@@ -0,0 +1,54 @@
1
+ import type { WaasSDKContainer } from '@dynamic-labs-sdk/client/waas/core';
2
+ import type { RefObject } from 'react';
3
+ import type WebView from 'react-native-webview';
4
+ /**
5
+ * JS injected into the hosted export page before its own scripts run. The page
6
+ * is written for the browser iframe transport: it reaches the SDK host via
7
+ * `window.parent.postMessage` and listens with `window.addEventListener
8
+ * ('message', ...)`. In a top-level WebView `window.parent === window`, so we
9
+ * override `window.postMessage` to forward the page's outbound payload to React
10
+ * Native through `window.ReactNativeWebView.postMessage`. The reverse direction
11
+ * (host -> page) is delivered by `sendMessage` injecting a synthetic
12
+ * `MessageEvent`, so no inbound wiring is needed here. Mirrors the native
13
+ * `BRIDGE_USER_SCRIPT` in the client package's DynamicWebViewHost.
14
+ */
15
+ export declare const WAAS_EXPORT_BRIDGE_SHIM = "\n(function () {\n if (window.__dynamicBridgeInstalled) { return; }\n window.__dynamicBridgeInstalled = true;\n var post = function (data) {\n try {\n var json = typeof data === 'string' ? data : JSON.stringify(data);\n window.ReactNativeWebView.postMessage(json);\n } catch (e) {}\n };\n try {\n Object.defineProperty(window, 'postMessage', {\n configurable: true,\n writable: true,\n value: function (data, targetOrigin, transfer) { post(data); }\n });\n } catch (e) {\n try { window.postMessage = function (d) { post(d); }; } catch (_) {}\n }\n})();\ntrue;\n";
16
+ /**
17
+ * Parameters for {@link createWebViewWaasContainer}.
18
+ */
19
+ export type CreateWebViewWaasContainerParams = {
20
+ /**
21
+ * Sets the URL the hosted `<WebView>` should load. An empty string unmounts
22
+ * it (used by `destroy`). The component renders the WebView from this state.
23
+ */
24
+ setUrl: (url: string) => void;
25
+ /** Ref to the rendered `react-native-webview` instance. */
26
+ webViewRef: RefObject<WebView | null>;
27
+ };
28
+ /**
29
+ * A {@link WaasSDKContainer} bound to a `react-native-webview`, plus the event
30
+ * sinks the host component wires into the WebView's `onMessage`/`onError`.
31
+ */
32
+ export type WebViewWaasContainer = {
33
+ /** The container handed to `exportWaasPrivateKeyToContainer`. */
34
+ container: WaasSDKContainer;
35
+ /** Feed a `<WebView>` load/HTTP error into the container's onError handlers. */
36
+ handleWebViewError: (error: unknown) => void;
37
+ /** Feed a raw `<WebView>` onMessage payload into the container's handlers. */
38
+ handleWebViewMessage: (raw: string) => void;
39
+ };
40
+ /**
41
+ * Creates a `WaasSDKContainer` backed by a `react-native-webview` rendered in
42
+ * the normal RN view tree (so it scrolls, clips, and positions like any view —
43
+ * no native overlay or manual window positioning). The private key is rendered
44
+ * inside the WebView's sandboxed document and never crosses the bridge; only
45
+ * the WaaS transport protocol messages do.
46
+ *
47
+ * The returned `container` is passed to `exportWaasPrivateKeyToContainer`; the
48
+ * `handleWebView*` sinks are wired into the WebView's `onMessage`/`onError` by
49
+ * the host component.
50
+ *
51
+ * @not-instrumented
52
+ */
53
+ export declare const createWebViewWaasContainer: ({ setUrl, webViewRef, }: CreateWebViewWaasContainerParams) => WebViewWaasContainer;
54
+ //# sourceMappingURL=createWebViewWaasContainer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"createWebViewWaasContainer.d.ts","sourceRoot":"","sources":["../../src/createWebViewWaasContainer/createWebViewWaasContainer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oCAAoC,CAAC;AAC3E,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AACvC,OAAO,KAAK,OAAO,MAAM,sBAAsB,CAAC;AAEhD;;;;;;;;;;GAUG;AACH,eAAO,MAAM,uBAAuB,qmBAqBnC,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,gCAAgC,GAAG;IAC7C;;;OAGG;IACH,MAAM,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IAC9B,2DAA2D;IAC3D,UAAU,EAAE,SAAS,CAAC,OAAO,GAAG,IAAI,CAAC,CAAC;CACvC,CAAC;AAEF;;;GAGG;AACH,MAAM,MAAM,oBAAoB,GAAG;IACjC,iEAAiE;IACjE,SAAS,EAAE,gBAAgB,CAAC;IAC5B,gFAAgF;IAChF,kBAAkB,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;IAC7C,8EAA8E;IAC9E,oBAAoB,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;CAC7C,CAAC;AAEF;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,0BAA0B,4BAGpC,gCAAgC,KAAG,oBAiFrC,CAAC"}
@@ -0,0 +1,3 @@
1
+ export { ExportPrivateKey } from '../ExportPrivateKey/ExportPrivateKey';
2
+ export type { ExportPrivateKeyProps } from '../ExportPrivateKey/ExportPrivateKey.types';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/exports/index.ts"],"names":[],"mappings":"AAQA,OAAO,EAAE,gBAAgB,EAAE,MAAM,sCAAsC,CAAC;AACxE,YAAY,EAAE,qBAAqB,EAAE,MAAM,4CAA4C,CAAC"}
package/dist/index.cjs ADDED
@@ -0,0 +1,252 @@
1
+ //#region rolldown:runtime
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __copyProps = (to, from, except, desc) => {
9
+ if (from && typeof from === "object" || typeof from === "function") {
10
+ for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
11
+ key = keys[i];
12
+ if (!__hasOwnProp.call(to, key) && key !== except) {
13
+ __defProp(to, key, {
14
+ get: ((k) => from[k]).bind(null, key),
15
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
16
+ });
17
+ }
18
+ }
19
+ }
20
+ return to;
21
+ };
22
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
23
+ value: mod,
24
+ enumerable: true
25
+ }) : target, mod));
26
+
27
+ //#endregion
28
+ let _dynamic_labs_sdk_assert_package_version = require("@dynamic-labs-sdk/assert-package-version");
29
+ let _dynamic_labs_sdk_client = require("@dynamic-labs-sdk/client");
30
+ let _dynamic_labs_sdk_client_core = require("@dynamic-labs-sdk/client/core");
31
+ let _dynamic_labs_sdk_client_waas_core = require("@dynamic-labs-sdk/client/waas/core");
32
+ let react = require("react");
33
+ let react_native = require("react-native");
34
+ let react_native_webview = require("react-native-webview");
35
+ react_native_webview = __toESM(react_native_webview);
36
+ let react_jsx_runtime = require("react/jsx-runtime");
37
+
38
+ //#region package.json
39
+ var name = "@dynamic-labs-sdk/react-native-embedded-wallet-export";
40
+ var version = "1.11.0";
41
+
42
+ //#endregion
43
+ //#region src/createWebViewWaasContainer/createWebViewWaasContainer.ts
44
+ /**
45
+ * JS injected into the hosted export page before its own scripts run. The page
46
+ * is written for the browser iframe transport: it reaches the SDK host via
47
+ * `window.parent.postMessage` and listens with `window.addEventListener
48
+ * ('message', ...)`. In a top-level WebView `window.parent === window`, so we
49
+ * override `window.postMessage` to forward the page's outbound payload to React
50
+ * Native through `window.ReactNativeWebView.postMessage`. The reverse direction
51
+ * (host -> page) is delivered by `sendMessage` injecting a synthetic
52
+ * `MessageEvent`, so no inbound wiring is needed here. Mirrors the native
53
+ * `BRIDGE_USER_SCRIPT` in the client package's DynamicWebViewHost.
54
+ */
55
+ const WAAS_EXPORT_BRIDGE_SHIM = `
56
+ (function () {
57
+ if (window.__dynamicBridgeInstalled) { return; }
58
+ window.__dynamicBridgeInstalled = true;
59
+ var post = function (data) {
60
+ try {
61
+ var json = typeof data === 'string' ? data : JSON.stringify(data);
62
+ window.ReactNativeWebView.postMessage(json);
63
+ } catch (e) {}
64
+ };
65
+ try {
66
+ Object.defineProperty(window, 'postMessage', {
67
+ configurable: true,
68
+ writable: true,
69
+ value: function (data, targetOrigin, transfer) { post(data); }
70
+ });
71
+ } catch (e) {
72
+ try { window.postMessage = function (d) { post(d); }; } catch (_) {}
73
+ }
74
+ })();
75
+ true;
76
+ `;
77
+ /**
78
+ * Creates a `WaasSDKContainer` backed by a `react-native-webview` rendered in
79
+ * the normal RN view tree (so it scrolls, clips, and positions like any view —
80
+ * no native overlay or manual window positioning). The private key is rendered
81
+ * inside the WebView's sandboxed document and never crosses the bridge; only
82
+ * the WaaS transport protocol messages do.
83
+ *
84
+ * The returned `container` is passed to `exportWaasPrivateKeyToContainer`; the
85
+ * `handleWebView*` sinks are wired into the WebView's `onMessage`/`onError` by
86
+ * the host component.
87
+ *
88
+ * @not-instrumented
89
+ */
90
+ const createWebViewWaasContainer = ({ setUrl, webViewRef }) => {
91
+ const messageHandlers = [];
92
+ const errorHandlers = [];
93
+ let destroyed = false;
94
+ return {
95
+ container: {
96
+ destroy() {
97
+ if (destroyed) return;
98
+ destroyed = true;
99
+ messageHandlers.length = 0;
100
+ errorHandlers.length = 0;
101
+ setUrl("");
102
+ },
103
+ isAlive: () => !destroyed,
104
+ onError(handler) {
105
+ errorHandlers.push(handler);
106
+ return () => {
107
+ const index = errorHandlers.indexOf(handler);
108
+ if (index !== -1) errorHandlers.splice(index, 1);
109
+ };
110
+ },
111
+ onMessage(handler) {
112
+ messageHandlers.push(handler);
113
+ return () => {
114
+ const index = messageHandlers.indexOf(handler);
115
+ if (index !== -1) messageHandlers.splice(index, 1);
116
+ };
117
+ },
118
+ sendMessage(data) {
119
+ const payload = JSON.stringify(data);
120
+ webViewRef.current?.injectJavaScript(`window.dispatchEvent(new MessageEvent('message',{data:${payload},origin:window.location.origin}));true;`);
121
+ },
122
+ async setUrl(url) {
123
+ setUrl(url);
124
+ }
125
+ },
126
+ handleWebViewError(error) {
127
+ if (destroyed) return;
128
+ for (const handler of errorHandlers) handler(error);
129
+ },
130
+ handleWebViewMessage(raw) {
131
+ if (destroyed) return;
132
+ let data;
133
+ try {
134
+ data = JSON.parse(raw);
135
+ } catch {
136
+ data = raw;
137
+ }
138
+ for (const handler of messageHandlers) handler(data);
139
+ }
140
+ };
141
+ };
142
+
143
+ //#endregion
144
+ //#region src/ExportPrivateKey/ExportPrivateKey.tsx
145
+ /**
146
+ * Renders a secure private-key export box for a WaaS wallet account on React
147
+ * Native. The key is rendered inside a `react-native-webview` that fills the
148
+ * box — the same split the web SDK uses (host renders the chrome, the
149
+ * cross-origin document renders only the key). Because it is a normal RN view,
150
+ * it scrolls, clips and positions like any view; no native overlay or manual
151
+ * window positioning is involved. The key is rendered inside the WebView and
152
+ * never crosses the bridge, so the host app's JS cannot read, log, or
153
+ * screenshot it. On Android, window-level screenshot/recording protection is
154
+ * enabled while the component is mounted.
155
+ *
156
+ * Place it inside your own modal/card and add your own close button — unmounting
157
+ * tears the WebView down. Size/position it with the `style` prop.
158
+ *
159
+ * @example
160
+ * ```tsx
161
+ * <Modal visible={revealing} onRequestClose={() => setRevealing(false)}>
162
+ * <ExportPrivateKey walletAccount={walletAccount} onError={setError} />
163
+ * <Button title="I'm Done" onPress={() => setRevealing(false)} />
164
+ * </Modal>
165
+ * ```
166
+ */
167
+ const ExportPrivateKey = ({ walletAccount, password, onComplete, onError, style }) => {
168
+ const webViewRef = (0, react.useRef)(null);
169
+ const [url, setUrl] = (0, react.useState)(null);
170
+ const onCompleteRef = (0, react.useRef)(onComplete);
171
+ onCompleteRef.current = onComplete;
172
+ const onErrorRef = (0, react.useRef)(onError);
173
+ onErrorRef.current = onError;
174
+ const passwordRef = (0, react.useRef)(password);
175
+ passwordRef.current = password;
176
+ const connectorRef = (0, react.useRef)(null);
177
+ if (connectorRef.current === null) connectorRef.current = createWebViewWaasContainer({
178
+ setUrl: (next) => setUrl(next || null),
179
+ webViewRef
180
+ });
181
+ const { container, handleWebViewMessage, handleWebViewError } = connectorRef.current;
182
+ const aliveRef = (0, react.useRef)(true);
183
+ const startedRef = (0, react.useRef)(false);
184
+ (0, react.useEffect)(() => {
185
+ if (startedRef.current) return;
186
+ startedRef.current = true;
187
+ (0, _dynamic_labs_sdk_client_core.setScreenshotProtection)({ enabled: true });
188
+ (0, _dynamic_labs_sdk_client_waas_core.exportWaasPrivateKeyToContainer)({
189
+ container,
190
+ password: passwordRef.current,
191
+ walletAccount
192
+ }, (0, _dynamic_labs_sdk_client.getDefaultClient)()).then(() => {
193
+ if (aliveRef.current) onCompleteRef.current?.();
194
+ }).catch((error) => {
195
+ if (aliveRef.current) onErrorRef.current?.(error);
196
+ });
197
+ return () => {
198
+ aliveRef.current = false;
199
+ container.destroy();
200
+ (0, _dynamic_labs_sdk_client_core.setScreenshotProtection)({ enabled: false });
201
+ };
202
+ }, []);
203
+ const expectedOrigin = (0, react.useMemo)(() => {
204
+ if (!url) return null;
205
+ try {
206
+ return new URL(url).origin;
207
+ } catch {
208
+ return null;
209
+ }
210
+ }, [url]);
211
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_native.View, {
212
+ style: [styles.box, style],
213
+ children: url && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_native_webview.default, {
214
+ ref: webViewRef,
215
+ source: { uri: url },
216
+ injectedJavaScriptBeforeContentLoaded: WAAS_EXPORT_BRIDGE_SHIM,
217
+ onMessage: (event) => handleWebViewMessage(event.nativeEvent.data),
218
+ onError: (event) => handleWebViewError(event.nativeEvent),
219
+ onHttpError: (event) => handleWebViewError(event.nativeEvent),
220
+ originWhitelist: expectedOrigin ? [expectedOrigin] : [],
221
+ onShouldStartLoadWithRequest: (request) => {
222
+ if (!expectedOrigin) return true;
223
+ try {
224
+ return new URL(request.url).origin === expectedOrigin;
225
+ } catch {
226
+ return false;
227
+ }
228
+ },
229
+ incognito: true,
230
+ style: styles.webView
231
+ })
232
+ });
233
+ };
234
+ const styles = react_native.StyleSheet.create({
235
+ box: {
236
+ backgroundColor: "#FFFFFF",
237
+ minHeight: 88,
238
+ overflow: "hidden"
239
+ },
240
+ webView: {
241
+ backgroundColor: "transparent",
242
+ flex: 1
243
+ }
244
+ });
245
+
246
+ //#endregion
247
+ //#region src/exports/index.ts
248
+ (0, _dynamic_labs_sdk_assert_package_version.assertPackageVersion)(name, version);
249
+
250
+ //#endregion
251
+ exports.ExportPrivateKey = ExportPrivateKey;
252
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.cjs","names":["messageHandlers: Array<(data: unknown) => void>","errorHandlers: Array<(error: unknown) => void>","data: unknown","ExportPrivateKey: FC<ExportPrivateKeyProps>","View","WebView","StyleSheet","packageName","packageVersion"],"sources":["../package.json","../src/createWebViewWaasContainer/createWebViewWaasContainer.ts","../src/ExportPrivateKey/ExportPrivateKey.tsx","../src/exports/index.ts"],"sourcesContent":["","import type { WaasSDKContainer } from '@dynamic-labs-sdk/client/waas/core';\nimport type { RefObject } from 'react';\nimport type WebView from 'react-native-webview';\n\n/**\n * JS injected into the hosted export page before its own scripts run. The page\n * is written for the browser iframe transport: it reaches the SDK host via\n * `window.parent.postMessage` and listens with `window.addEventListener\n * ('message', ...)`. In a top-level WebView `window.parent === window`, so we\n * override `window.postMessage` to forward the page's outbound payload to React\n * Native through `window.ReactNativeWebView.postMessage`. The reverse direction\n * (host -> page) is delivered by `sendMessage` injecting a synthetic\n * `MessageEvent`, so no inbound wiring is needed here. Mirrors the native\n * `BRIDGE_USER_SCRIPT` in the client package's DynamicWebViewHost.\n */\nexport const WAAS_EXPORT_BRIDGE_SHIM = `\n(function () {\n if (window.__dynamicBridgeInstalled) { return; }\n window.__dynamicBridgeInstalled = true;\n var post = function (data) {\n try {\n var json = typeof data === 'string' ? data : JSON.stringify(data);\n window.ReactNativeWebView.postMessage(json);\n } catch (e) {}\n };\n try {\n Object.defineProperty(window, 'postMessage', {\n configurable: true,\n writable: true,\n value: function (data, targetOrigin, transfer) { post(data); }\n });\n } catch (e) {\n try { window.postMessage = function (d) { post(d); }; } catch (_) {}\n }\n})();\ntrue;\n`;\n\n/**\n * Parameters for {@link createWebViewWaasContainer}.\n */\nexport type CreateWebViewWaasContainerParams = {\n /**\n * Sets the URL the hosted `<WebView>` should load. An empty string unmounts\n * it (used by `destroy`). The component renders the WebView from this state.\n */\n setUrl: (url: string) => void;\n /** Ref to the rendered `react-native-webview` instance. */\n webViewRef: RefObject<WebView | null>;\n};\n\n/**\n * A {@link WaasSDKContainer} bound to a `react-native-webview`, plus the event\n * sinks the host component wires into the WebView's `onMessage`/`onError`.\n */\nexport type WebViewWaasContainer = {\n /** The container handed to `exportWaasPrivateKeyToContainer`. */\n container: WaasSDKContainer;\n /** Feed a `<WebView>` load/HTTP error into the container's onError handlers. */\n handleWebViewError: (error: unknown) => void;\n /** Feed a raw `<WebView>` onMessage payload into the container's handlers. */\n handleWebViewMessage: (raw: string) => void;\n};\n\n/**\n * Creates a `WaasSDKContainer` backed by a `react-native-webview` rendered in\n * the normal RN view tree (so it scrolls, clips, and positions like any view —\n * no native overlay or manual window positioning). The private key is rendered\n * inside the WebView's sandboxed document and never crosses the bridge; only\n * the WaaS transport protocol messages do.\n *\n * The returned `container` is passed to `exportWaasPrivateKeyToContainer`; the\n * `handleWebView*` sinks are wired into the WebView's `onMessage`/`onError` by\n * the host component.\n *\n * @not-instrumented\n */\nexport const createWebViewWaasContainer = ({\n setUrl,\n webViewRef,\n}: CreateWebViewWaasContainerParams): WebViewWaasContainer => {\n // IframeManager registers more than one onMessage handler on the same\n // container (handshake wait + transport forwarding), so we fan out to all.\n const messageHandlers: Array<(data: unknown) => void> = [];\n const errorHandlers: Array<(error: unknown) => void> = [];\n let destroyed = false;\n\n const container: WaasSDKContainer = {\n destroy() {\n if (destroyed) return;\n destroyed = true;\n messageHandlers.length = 0;\n errorHandlers.length = 0;\n // Unmount the WebView so the displayed key cannot outlive the container.\n setUrl('');\n },\n\n isAlive: () => !destroyed,\n\n // Signature is dictated by the upstream WaasSDKContainer interface.\n // eslint-disable-next-line custom-rules/require-single-object-param\n onError(handler: (error: unknown) => void) {\n errorHandlers.push(handler);\n return () => {\n const index = errorHandlers.indexOf(handler);\n if (index !== -1) errorHandlers.splice(index, 1);\n };\n },\n\n // Signature is dictated by the upstream WaasSDKContainer interface.\n // eslint-disable-next-line custom-rules/require-single-object-param\n onMessage(handler: (data: unknown) => void) {\n messageHandlers.push(handler);\n return () => {\n const index = messageHandlers.indexOf(handler);\n if (index !== -1) messageHandlers.splice(index, 1);\n };\n },\n\n sendMessage(data: Record<string, unknown>) {\n // Deliver host -> page as a synthetic 'message' event — the shape the\n // page expects from window.parent.postMessage in a browser iframe.\n const payload = JSON.stringify(data);\n webViewRef.current?.injectJavaScript(\n `window.dispatchEvent(new MessageEvent('message',{data:${payload},origin:window.location.origin}));true;`\n );\n },\n\n // Signature is dictated by the upstream WaasSDKContainer interface.\n // eslint-disable-next-line custom-rules/require-single-object-param\n async setUrl(url: string) {\n setUrl(url);\n },\n };\n\n return {\n container,\n\n handleWebViewError(error: unknown) {\n if (destroyed) return;\n for (const handler of errorHandlers) handler(error);\n },\n\n // Single positional arg matches the WebView onMessage event sink shape.\n // eslint-disable-next-line custom-rules/require-single-object-param\n handleWebViewMessage(raw: string) {\n if (destroyed) return;\n // The WaaS transport posts BOTH objects (JSON) and plain strings (e.g. the\n // `iframe-ready-<instanceId>` handshake). Deliver parsed JSON when possible,\n // otherwise the raw string — mirroring browser postMessage semantics, where\n // a string stays a string and an object stays an object. Discarding non-JSON\n // here would drop the handshake and hang the export until timeout.\n let data: unknown;\n try {\n data = JSON.parse(raw);\n } catch {\n data = raw;\n }\n for (const handler of messageHandlers) handler(data);\n },\n };\n};\n","import { getDefaultClient } from '@dynamic-labs-sdk/client';\nimport { setScreenshotProtection } from '@dynamic-labs-sdk/client/core';\nimport { exportWaasPrivateKeyToContainer } from '@dynamic-labs-sdk/client/waas/core';\nimport type { FC } from 'react';\nimport { useEffect, useMemo, useRef, useState } from 'react';\nimport { StyleSheet, View } from 'react-native';\nimport WebView from 'react-native-webview';\n\nimport {\n WAAS_EXPORT_BRIDGE_SHIM,\n createWebViewWaasContainer,\n} from '../createWebViewWaasContainer/createWebViewWaasContainer';\nimport type { WebViewWaasContainer } from '../createWebViewWaasContainer/createWebViewWaasContainer';\nimport type { ExportPrivateKeyProps } from './ExportPrivateKey.types';\n\n/**\n * Renders a secure private-key export box for a WaaS wallet account on React\n * Native. The key is rendered inside a `react-native-webview` that fills the\n * box — the same split the web SDK uses (host renders the chrome, the\n * cross-origin document renders only the key). Because it is a normal RN view,\n * it scrolls, clips and positions like any view; no native overlay or manual\n * window positioning is involved. The key is rendered inside the WebView and\n * never crosses the bridge, so the host app's JS cannot read, log, or\n * screenshot it. On Android, window-level screenshot/recording protection is\n * enabled while the component is mounted.\n *\n * Place it inside your own modal/card and add your own close button — unmounting\n * tears the WebView down. Size/position it with the `style` prop.\n *\n * @example\n * ```tsx\n * <Modal visible={revealing} onRequestClose={() => setRevealing(false)}>\n * <ExportPrivateKey walletAccount={walletAccount} onError={setError} />\n * <Button title=\"I'm Done\" onPress={() => setRevealing(false)} />\n * </Modal>\n * ```\n */\nexport const ExportPrivateKey: FC<ExportPrivateKeyProps> = ({\n walletAccount,\n password,\n onComplete,\n onError,\n style,\n}) => {\n const webViewRef = useRef<WebView | null>(null);\n const [url, setUrl] = useState<string | null>(null);\n\n // Hold callbacks + options in refs so re-renders don't restart the export.\n const onCompleteRef = useRef(onComplete);\n onCompleteRef.current = onComplete;\n const onErrorRef = useRef(onError);\n onErrorRef.current = onError;\n const passwordRef = useRef(password);\n passwordRef.current = password;\n\n // The container handed to the SDK plus the sinks wired into the WebView. It\n // holds mutable state (handler arrays, destroyed flag), so it must be created\n // exactly once and stay stable for the component's lifetime — useRef, not\n // useMemo (which React may discard and recompute). setUrl drives whether (and\n // what) the WebView renders.\n const connectorRef = useRef<WebViewWaasContainer | null>(null);\n if (connectorRef.current === null) {\n connectorRef.current = createWebViewWaasContainer({\n setUrl: (next: string) => setUrl(next || null),\n webViewRef,\n });\n }\n const { container, handleWebViewMessage, handleWebViewError } =\n connectorRef.current;\n\n const aliveRef = useRef(true);\n const startedRef = useRef(false);\n\n useEffect(() => {\n // One-shot: the export ceremony runs once.\n if (startedRef.current) return;\n startedRef.current = true;\n\n // Window-level screenshot/recording protection while the key is visible\n // (Android FLAG_SECURE; no-op on iOS).\n setScreenshotProtection({ enabled: true });\n\n exportWaasPrivateKeyToContainer(\n {\n container,\n password: passwordRef.current,\n walletAccount,\n },\n // Internal API takes the client explicitly; the component defaults it.\n getDefaultClient()\n )\n .then(() => {\n if (aliveRef.current) onCompleteRef.current?.();\n })\n .catch((error: unknown) => {\n if (aliveRef.current) onErrorRef.current?.(error);\n });\n\n return () => {\n // Unmounting tears down the WebView + container and clears screenshot\n // protection so the displayed key cannot outlive the screen.\n aliveRef.current = false;\n container.destroy();\n setScreenshotProtection({ enabled: false });\n };\n // Effect intentionally runs once; inputs are captured in refs above.\n }, []);\n\n // Pin the WebView to the export URL's origin (navigation off it is blocked).\n const expectedOrigin = useMemo(() => {\n if (!url) return null;\n try {\n return new URL(url).origin;\n } catch {\n return null;\n }\n }, [url]);\n\n return (\n <View style={[styles.box, style]}>\n {url && (\n <WebView\n ref={webViewRef}\n source={{ uri: url }}\n injectedJavaScriptBeforeContentLoaded={WAAS_EXPORT_BRIDGE_SHIM}\n onMessage={(event) => handleWebViewMessage(event.nativeEvent.data)}\n onError={(event) => handleWebViewError(event.nativeEvent)}\n onHttpError={(event) => handleWebViewError(event.nativeEvent)}\n originWhitelist={expectedOrigin ? [expectedOrigin] : []}\n onShouldStartLoadWithRequest={(request) => {\n if (!expectedOrigin) return true;\n // Exact-origin match — a prefix check would let look-alike hosts\n // (e.g. https://export.example.com.evil.com) through.\n try {\n return new URL(request.url).origin === expectedOrigin;\n } catch {\n return false;\n }\n }}\n // A fresh, isolated, ephemeral store per export.\n incognito\n style={styles.webView}\n />\n )}\n </View>\n );\n};\n\nconst styles = StyleSheet.create({\n // Minimal default so the component stays \"headless\" — the host owns the\n // chrome (border, radius, etc.) via the `style` prop. We only keep an opaque\n // background, a min height so the box is measurable before the host sizes it,\n // and clipping for the WebView.\n box: {\n backgroundColor: '#FFFFFF',\n minHeight: 88,\n overflow: 'hidden',\n },\n webView: {\n backgroundColor: 'transparent',\n flex: 1,\n },\n});\n","import { assertPackageVersion } from '@dynamic-labs-sdk/assert-package-version';\n\nimport {\n name as packageName,\n version as packageVersion,\n} from '../../package.json';\nassertPackageVersion(packageName, packageVersion);\n\nexport { ExportPrivateKey } from '../ExportPrivateKey/ExportPrivateKey';\nexport type { ExportPrivateKeyProps } from '../ExportPrivateKey/ExportPrivateKey.types';\n"],"mappings":"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"}