@portal-hq/webview 2.0.13

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,49 @@
1
+ # Portal React Native WebView
2
+
3
+ The `@portal-hq/webview` package exports a single `WebView` component for loading dApps within your app with built-in Portal Provider injection.
4
+
5
+ ## Dependency linking
6
+
7
+ Because this package uses the `react-native-webview` package (which contain native modules) there is some additional linking required to make it work with your React Native project.
8
+
9
+ Explicitly install the `react-native-webview` package in your project.
10
+
11
+ ```bash
12
+ yarn add react-native-webview
13
+
14
+ # OR #
15
+
16
+ npm install --save react-native-webview
17
+ ```
18
+
19
+ ## Using the WebView Component
20
+
21
+ To use the WebView component, import it from the `@portal-hq/webview` package and use it as you would the `react-native-webview` package and render the component within your app.
22
+
23
+ ```jsx
24
+ import WebView from '@portal-hq/webview'
25
+
26
+ const App = () => {
27
+ return (
28
+ <WebView
29
+ url={'https://app.uniswap.com/'}
30
+ />
31
+ )
32
+ }
33
+ ```
34
+
35
+ ### Optional Props
36
+
37
+ The `WebView` component two optional props that can be used to customize the behavior of the WebView.
38
+
39
+ #### `onNavigationStateChange`
40
+
41
+ This allows you to provide a callback for when the navigation state changes. This allows you to manage loading states and other navigation events.
42
+
43
+ #### `onSigningRequested`
44
+
45
+ This allows you to provide a callback for when signing is requested. For the most part, this prop is not needed if you have already set up the `PortalProvider` in your app.
46
+
47
+
48
+
49
+
@@ -0,0 +1,153 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || function (mod) {
19
+ if (mod && mod.__esModule) return mod;
20
+ var result = {};
21
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
+ __setModuleDefault(result, mod);
23
+ return result;
24
+ };
25
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
26
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
27
+ return new (P || (P = Promise))(function (resolve, reject) {
28
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
29
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
30
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
31
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
32
+ });
33
+ };
34
+ var __importDefault = (this && this.__importDefault) || function (mod) {
35
+ return (mod && mod.__esModule) ? mod : { "default": mod };
36
+ };
37
+ Object.defineProperty(exports, "__esModule", { value: true });
38
+ // Node modules.
39
+ const react_1 = __importStar(require("react"));
40
+ const react_native_webview_1 = __importDefault(require("react-native-webview"));
41
+ const react_native_1 = require("react-native");
42
+ const injected_provider_1 = require("@portal-hq/injected-provider");
43
+ const core_1 = require("@portal-hq/core");
44
+ // Relative modules.
45
+ const loading_1 = __importDefault(require("./loading"));
46
+ const Webview = (0, react_1.memo)((0, react_1.forwardRef)(({ onNavigationStateChange, onSigningRequested, url }, webViewRef) => {
47
+ const Webview = () => {
48
+ // Derive context.
49
+ const portal = (0, core_1.usePortal)();
50
+ // Derive refs.
51
+ const canGoBack = (0, react_1.useRef)(false);
52
+ const initialized = (0, react_1.useRef)(false);
53
+ const webView = (0, react_1.useRef)();
54
+ // Derive state.
55
+ const [currentUrl, setCurrentUrl] = (0, react_1.useState)(url);
56
+ const [walletAddress, setWalletAddress] = (0, react_1.useState)();
57
+ const source = (0, react_1.useMemo)(() => ({
58
+ uri: currentUrl,
59
+ }), [currentUrl]);
60
+ const scriptToInject = (0, injected_provider_1.injectionScript)({
61
+ address: walletAddress,
62
+ apiKey: portal.apiKey,
63
+ chainId: portal.chainId,
64
+ gatewayConfig: portal.gatewayConfig,
65
+ });
66
+ const handleBackPress = () => {
67
+ var _a;
68
+ if (react_native_1.Platform.OS === 'android' && canGoBack) {
69
+ (_a = webView.current) === null || _a === void 0 ? void 0 : _a.goBack();
70
+ }
71
+ else if (react_native_1.Platform.OS === 'ios') {
72
+ // Don't need to handle this on IOS I don't think
73
+ }
74
+ return true;
75
+ };
76
+ const handleNavigationStateChange = (event) => {
77
+ const { canGoBack: _canGoBack, loading, url } = event;
78
+ if (loading) {
79
+ if (onNavigationStateChange) {
80
+ onNavigationStateChange(event);
81
+ }
82
+ // Set the source
83
+ setCurrentUrl(url);
84
+ canGoBack.current = _canGoBack;
85
+ }
86
+ };
87
+ const handlePostMessage = (event) => __awaiter(void 0, void 0, void 0, function* () {
88
+ const { type, data } = JSON.parse(event.nativeEvent.data);
89
+ const { method, params } = data;
90
+ switch (type) {
91
+ case 'portal_sign':
92
+ yield portal.request(method, params);
93
+ break;
94
+ }
95
+ });
96
+ // Initialize Portal Bindings and set the wallet address
97
+ (0, react_1.useEffect)(() => {
98
+ if (portal && !initialized.current) {
99
+ // Prevent this from running again
100
+ initialized.current = true;
101
+ // Bind to signature messages after signing
102
+ portal.on('portal_signatureReceived', (data) => {
103
+ var _a;
104
+ (_a = webView.current) === null || _a === void 0 ? void 0 : _a.postMessage(JSON.stringify({
105
+ type: 'portal_signatureReceived',
106
+ data,
107
+ }));
108
+ });
109
+ // Bind to signing requests
110
+ if (onSigningRequested) {
111
+ portal.on('portal_signingRequested', onSigningRequested);
112
+ }
113
+ // Store the wallet address in the state
114
+ const storeWalletAddress = () => __awaiter(void 0, void 0, void 0, function* () {
115
+ const address = yield portal.address;
116
+ setWalletAddress(address);
117
+ });
118
+ void storeWalletAddress();
119
+ }
120
+ }, [portal]);
121
+ // Clean up Portal Provider bindings
122
+ (0, react_1.useEffect)(() => {
123
+ return () => {
124
+ portal.provider.removeEventListener('portal_signatureReceived');
125
+ portal.provider.removeEventListener('portal_signingRequested', onSigningRequested);
126
+ };
127
+ }, []);
128
+ // Handle Android stuff
129
+ (0, react_1.useEffect)(() => {
130
+ react_native_1.BackHandler.addEventListener('hardwareBackPress', handleBackPress);
131
+ return () => {
132
+ react_native_1.BackHandler.removeEventListener('hardwareBackPress', handleBackPress);
133
+ };
134
+ }, []);
135
+ // Ensure that the webView ref gets forwarded to the parent component
136
+ (0, react_1.useImperativeHandle)(webViewRef, () => webView.current);
137
+ return (<react_native_1.View style={styles.container}>
138
+ {walletAddress && source && (<react_native_webview_1.default allowsBackForwardNavigationGestures allowsFullscreenVideo={false} allowsInlineMediaPlayback={true} incognito={true} injectedJavaScriptBeforeContentLoaded={scriptToInject} injectedJavaScriptBeforeContentLoadedForMainFrameOnly={true} mediaPlaybackRequiresUserAction={true} onMessage={handlePostMessage} onNavigationStateChange={handleNavigationStateChange} ref={webView} renderLoading={() => <loading_1.default />} source={source} startInLoadingState={true} webviewDebuggingEnabled={true}/>)}
139
+ </react_native_1.View>);
140
+ };
141
+ return <Webview />;
142
+ }), (prevProps, nextProps) => {
143
+ console.log(`prevProps:`, prevProps);
144
+ console.log(`nextProps:`, nextProps);
145
+ // return true
146
+ return prevProps.url === nextProps.url;
147
+ });
148
+ const styles = react_native_1.StyleSheet.create({
149
+ container: {
150
+ flex: 1,
151
+ },
152
+ });
153
+ exports.default = Webview;
@@ -0,0 +1,25 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ // Node modules.
7
+ const react_1 = __importDefault(require("react"));
8
+ const react_native_1 = require("react-native");
9
+ // Relative modules.
10
+ const loader_1 = __importDefault(require("./loader"));
11
+ const Loading = () => (<react_native_1.View style={styles.container}>
12
+ <loader_1.default />
13
+ </react_native_1.View>);
14
+ const styles = react_native_1.StyleSheet.create({
15
+ container: {
16
+ alignContent: 'center',
17
+ alignItems: 'center',
18
+ backgroundColor: 'rgba(0, 0, 0, 0.1)',
19
+ height: '100%',
20
+ justifyContent: 'center',
21
+ justifyItems: 'center',
22
+ width: '100%',
23
+ },
24
+ });
25
+ exports.default = Loading;
@@ -0,0 +1,11 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const react_1 = __importDefault(require("react"));
7
+ const react_native_1 = require("react-native");
8
+ const Loader = ({ color = '#3E71F8', size = 'small' }) => {
9
+ return <react_native_1.ActivityIndicator color={color} size={size}/>;
10
+ };
11
+ exports.default = Loader;
@@ -0,0 +1,125 @@
1
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
+ return new (P || (P = Promise))(function (resolve, reject) {
4
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
8
+ });
9
+ };
10
+ // Node modules.
11
+ import React, { forwardRef, memo, useEffect, useImperativeHandle, useMemo, useRef, useState, } from 'react';
12
+ import WebView from 'react-native-webview';
13
+ import { BackHandler, Platform, StyleSheet, View } from 'react-native';
14
+ import { injectionScript, } from '@portal-hq/injected-provider';
15
+ import { usePortal } from '@portal-hq/core';
16
+ // Relative modules.
17
+ import Loading from './loading';
18
+ const Webview = memo(forwardRef(({ onNavigationStateChange, onSigningRequested, url }, webViewRef) => {
19
+ const Webview = () => {
20
+ // Derive context.
21
+ const portal = usePortal();
22
+ // Derive refs.
23
+ const canGoBack = useRef(false);
24
+ const initialized = useRef(false);
25
+ const webView = useRef();
26
+ // Derive state.
27
+ const [currentUrl, setCurrentUrl] = useState(url);
28
+ const [walletAddress, setWalletAddress] = useState();
29
+ const source = useMemo(() => ({
30
+ uri: currentUrl,
31
+ }), [currentUrl]);
32
+ const scriptToInject = injectionScript({
33
+ address: walletAddress,
34
+ apiKey: portal.apiKey,
35
+ chainId: portal.chainId,
36
+ gatewayConfig: portal.gatewayConfig,
37
+ });
38
+ const handleBackPress = () => {
39
+ var _a;
40
+ if (Platform.OS === 'android' && canGoBack) {
41
+ (_a = webView.current) === null || _a === void 0 ? void 0 : _a.goBack();
42
+ }
43
+ else if (Platform.OS === 'ios') {
44
+ // Don't need to handle this on IOS I don't think
45
+ }
46
+ return true;
47
+ };
48
+ const handleNavigationStateChange = (event) => {
49
+ const { canGoBack: _canGoBack, loading, url } = event;
50
+ if (loading) {
51
+ if (onNavigationStateChange) {
52
+ onNavigationStateChange(event);
53
+ }
54
+ // Set the source
55
+ setCurrentUrl(url);
56
+ canGoBack.current = _canGoBack;
57
+ }
58
+ };
59
+ const handlePostMessage = (event) => __awaiter(void 0, void 0, void 0, function* () {
60
+ const { type, data } = JSON.parse(event.nativeEvent.data);
61
+ const { method, params } = data;
62
+ switch (type) {
63
+ case 'portal_sign':
64
+ yield portal.request(method, params);
65
+ break;
66
+ }
67
+ });
68
+ // Initialize Portal Bindings and set the wallet address
69
+ useEffect(() => {
70
+ if (portal && !initialized.current) {
71
+ // Prevent this from running again
72
+ initialized.current = true;
73
+ // Bind to signature messages after signing
74
+ portal.on('portal_signatureReceived', (data) => {
75
+ var _a;
76
+ (_a = webView.current) === null || _a === void 0 ? void 0 : _a.postMessage(JSON.stringify({
77
+ type: 'portal_signatureReceived',
78
+ data,
79
+ }));
80
+ });
81
+ // Bind to signing requests
82
+ if (onSigningRequested) {
83
+ portal.on('portal_signingRequested', onSigningRequested);
84
+ }
85
+ // Store the wallet address in the state
86
+ const storeWalletAddress = () => __awaiter(void 0, void 0, void 0, function* () {
87
+ const address = yield portal.address;
88
+ setWalletAddress(address);
89
+ });
90
+ void storeWalletAddress();
91
+ }
92
+ }, [portal]);
93
+ // Clean up Portal Provider bindings
94
+ useEffect(() => {
95
+ return () => {
96
+ portal.provider.removeEventListener('portal_signatureReceived');
97
+ portal.provider.removeEventListener('portal_signingRequested', onSigningRequested);
98
+ };
99
+ }, []);
100
+ // Handle Android stuff
101
+ useEffect(() => {
102
+ BackHandler.addEventListener('hardwareBackPress', handleBackPress);
103
+ return () => {
104
+ BackHandler.removeEventListener('hardwareBackPress', handleBackPress);
105
+ };
106
+ }, []);
107
+ // Ensure that the webView ref gets forwarded to the parent component
108
+ useImperativeHandle(webViewRef, () => webView.current);
109
+ return (<View style={styles.container}>
110
+ {walletAddress && source && (<WebView allowsBackForwardNavigationGestures allowsFullscreenVideo={false} allowsInlineMediaPlayback={true} incognito={true} injectedJavaScriptBeforeContentLoaded={scriptToInject} injectedJavaScriptBeforeContentLoadedForMainFrameOnly={true} mediaPlaybackRequiresUserAction={true} onMessage={handlePostMessage} onNavigationStateChange={handleNavigationStateChange} ref={webView} renderLoading={() => <Loading />} source={source} startInLoadingState={true} webviewDebuggingEnabled={true}/>)}
111
+ </View>);
112
+ };
113
+ return <Webview />;
114
+ }), (prevProps, nextProps) => {
115
+ console.log(`prevProps:`, prevProps);
116
+ console.log(`nextProps:`, nextProps);
117
+ // return true
118
+ return prevProps.url === nextProps.url;
119
+ });
120
+ const styles = StyleSheet.create({
121
+ container: {
122
+ flex: 1,
123
+ },
124
+ });
125
+ export default Webview;
@@ -0,0 +1,20 @@
1
+ // Node modules.
2
+ import React from 'react';
3
+ import { StyleSheet, View } from 'react-native';
4
+ // Relative modules.
5
+ import Loader from './loader';
6
+ const Loading = () => (<View style={styles.container}>
7
+ <Loader />
8
+ </View>);
9
+ const styles = StyleSheet.create({
10
+ container: {
11
+ alignContent: 'center',
12
+ alignItems: 'center',
13
+ backgroundColor: 'rgba(0, 0, 0, 0.1)',
14
+ height: '100%',
15
+ justifyContent: 'center',
16
+ justifyItems: 'center',
17
+ width: '100%',
18
+ },
19
+ });
20
+ export default Loading;
@@ -0,0 +1,6 @@
1
+ import React from 'react';
2
+ import { ActivityIndicator } from 'react-native';
3
+ const Loader = ({ color = '#3E71F8', size = 'small' }) => {
4
+ return <ActivityIndicator color={color} size={size}/>;
5
+ };
6
+ export default Loader;
package/package.json ADDED
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "@portal-hq/webview",
3
+ "version": "2.0.13",
4
+ "license": "MIT",
5
+ "main": "lib/commonjs/index",
6
+ "module": "lib/esm/index",
7
+ "source": "src/index",
8
+ "types": "src/index",
9
+ "files": [
10
+ "src",
11
+ "lib"
12
+ ],
13
+ "scripts": {
14
+ "coverage": "jest --passWithNoTests --coverage",
15
+ "prepare": "yarn prepare:cjs && yarn prepare:esm",
16
+ "prepare:cjs": "tsc --outDir lib/commonjs --module commonjs",
17
+ "prepare:esm": "tsc --outDir lib/esm --module es2015 --target es2015",
18
+ "test": "jest --passWithNoTests"
19
+ },
20
+ "dependencies": {
21
+ "@portal-hq/core": "^2.0.13",
22
+ "@portal-hq/injected-provider": "^2.0.13",
23
+ "@portal-hq/utils": "^2.0.13"
24
+ },
25
+ "devDependencies": {
26
+ "@types/jest": "^29.2.0",
27
+ "@types/react": "*",
28
+ "@types/react-native": "*",
29
+ "jest": "^29.2.1",
30
+ "jest-environment-jsdom": "^29.2.2",
31
+ "ts-jest": "^29.0.3",
32
+ "typescript": "*"
33
+ },
34
+ "peerDependencies": {
35
+ "react": "*",
36
+ "react-native": "*",
37
+ "react-native-webview": "*"
38
+ },
39
+ "gitHead": "bd4ff2f92e936f51b1611083d42804980e96bf27"
40
+ }
package/src/index.tsx ADDED
@@ -0,0 +1,196 @@
1
+ // Node modules.
2
+ import React, {
3
+ FC,
4
+ MutableRefObject,
5
+ forwardRef,
6
+ memo,
7
+ useEffect,
8
+ useImperativeHandle,
9
+ useMemo,
10
+ useRef,
11
+ useState,
12
+ } from 'react'
13
+ import WebView, {
14
+ WebViewMessageEvent,
15
+ WebViewNavigation,
16
+ } from 'react-native-webview'
17
+ import { BackHandler, Platform, StyleSheet, View } from 'react-native'
18
+ import {
19
+ injectionScript,
20
+ type RequestArguments,
21
+ } from '@portal-hq/injected-provider'
22
+ import { type WebViewSourceUri } from 'react-native-webview/lib/WebViewTypes'
23
+ import { usePortal } from '@portal-hq/core'
24
+ // Relative modules.
25
+ import Loading from './loading'
26
+
27
+ interface WebviewProps {
28
+ onNavigationStateChange?: (event: WebViewNavigation) => void
29
+ onSigningRequested?: (approvalRequest: any) => void
30
+ url: string
31
+ }
32
+
33
+ const Webview = memo(
34
+ forwardRef<WebView, WebviewProps>(
35
+ ({ onNavigationStateChange, onSigningRequested, url }, webViewRef) => {
36
+ const Webview: FC = () => {
37
+ // Derive context.
38
+ const portal = usePortal()
39
+
40
+ // Derive refs.
41
+ const canGoBack = useRef<boolean>(false)
42
+ const initialized = useRef<boolean>(false)
43
+ const webView = useRef<WebView>()
44
+
45
+ // Derive state.
46
+ const [currentUrl, setCurrentUrl] = useState<string>(url)
47
+ const [walletAddress, setWalletAddress] = useState<string>()
48
+
49
+ const source = useMemo(
50
+ (): WebViewSourceUri => ({
51
+ uri: currentUrl,
52
+ }),
53
+ [currentUrl],
54
+ )
55
+
56
+ const scriptToInject = injectionScript({
57
+ address: walletAddress as string,
58
+ apiKey: portal.apiKey,
59
+ chainId: portal.chainId,
60
+ gatewayConfig: portal.gatewayConfig,
61
+ })
62
+
63
+ const handleBackPress = (): boolean => {
64
+ if (Platform.OS === 'android' && canGoBack) {
65
+ webView.current?.goBack()
66
+ } else if (Platform.OS === 'ios') {
67
+ // Don't need to handle this on IOS I don't think
68
+ }
69
+ return true
70
+ }
71
+
72
+ const handleNavigationStateChange = (event: WebViewNavigation) => {
73
+ const { canGoBack: _canGoBack, loading, url } = event
74
+
75
+ if (loading) {
76
+ if (onNavigationStateChange) {
77
+ onNavigationStateChange(event)
78
+ }
79
+
80
+ // Set the source
81
+ setCurrentUrl(url)
82
+
83
+ canGoBack.current = _canGoBack
84
+ }
85
+ }
86
+
87
+ const handlePostMessage = async (event: WebViewMessageEvent) => {
88
+ const { type, data } = JSON.parse(event.nativeEvent.data)
89
+ const { method, params } = data as RequestArguments
90
+
91
+ switch (type) {
92
+ case 'portal_sign':
93
+ await portal.request(method, params as unknown[])
94
+ break
95
+ }
96
+ }
97
+
98
+ // Initialize Portal Bindings and set the wallet address
99
+ useEffect(() => {
100
+ if (portal && !initialized.current) {
101
+ // Prevent this from running again
102
+ initialized.current = true
103
+
104
+ // Bind to signature messages after signing
105
+ portal.on('portal_signatureReceived', (data: any) => {
106
+ webView.current?.postMessage(
107
+ JSON.stringify({
108
+ type: 'portal_signatureReceived',
109
+ data,
110
+ }),
111
+ )
112
+ })
113
+
114
+ // Bind to signing requests
115
+ if (onSigningRequested) {
116
+ portal.on('portal_signingRequested', onSigningRequested)
117
+ }
118
+
119
+ // Store the wallet address in the state
120
+ const storeWalletAddress = async () => {
121
+ const address = await portal.address
122
+ setWalletAddress(address)
123
+ }
124
+
125
+ void storeWalletAddress()
126
+ }
127
+ }, [portal])
128
+
129
+ // Clean up Portal Provider bindings
130
+ useEffect(() => {
131
+ return () => {
132
+ portal.provider.removeEventListener('portal_signatureReceived')
133
+ portal.provider.removeEventListener(
134
+ 'portal_signingRequested',
135
+ onSigningRequested,
136
+ )
137
+ }
138
+ }, [])
139
+
140
+ // Handle Android stuff
141
+ useEffect(() => {
142
+ BackHandler.addEventListener('hardwareBackPress', handleBackPress)
143
+
144
+ return () => {
145
+ BackHandler.removeEventListener(
146
+ 'hardwareBackPress',
147
+ handleBackPress,
148
+ )
149
+ }
150
+ }, [])
151
+
152
+ // Ensure that the webView ref gets forwarded to the parent component
153
+ useImperativeHandle(webViewRef, () => webView.current as WebView)
154
+
155
+ return (
156
+ <View style={styles.container}>
157
+ {walletAddress && source && (
158
+ <WebView
159
+ allowsBackForwardNavigationGestures
160
+ allowsFullscreenVideo={false}
161
+ allowsInlineMediaPlayback={true}
162
+ incognito={true}
163
+ injectedJavaScriptBeforeContentLoaded={scriptToInject}
164
+ injectedJavaScriptBeforeContentLoadedForMainFrameOnly={true}
165
+ mediaPlaybackRequiresUserAction={true}
166
+ onMessage={handlePostMessage}
167
+ onNavigationStateChange={handleNavigationStateChange}
168
+ ref={webView as MutableRefObject<WebView>}
169
+ renderLoading={() => <Loading />}
170
+ source={source}
171
+ startInLoadingState={true}
172
+ webviewDebuggingEnabled={true}
173
+ />
174
+ )}
175
+ </View>
176
+ )
177
+ }
178
+
179
+ return <Webview />
180
+ },
181
+ ),
182
+ (prevProps, nextProps) => {
183
+ console.log(`prevProps:`, prevProps)
184
+ console.log(`nextProps:`, nextProps)
185
+ // return true
186
+ return prevProps.url === nextProps.url
187
+ },
188
+ )
189
+
190
+ const styles = StyleSheet.create({
191
+ container: {
192
+ flex: 1,
193
+ },
194
+ })
195
+
196
+ export default Webview
@@ -0,0 +1,25 @@
1
+ // Node modules.
2
+ import React, { FC } from 'react'
3
+ import { StyleSheet, View } from 'react-native'
4
+ // Relative modules.
5
+ import Loader from './loader'
6
+
7
+ const Loading: FC = () => (
8
+ <View style={styles.container}>
9
+ <Loader />
10
+ </View>
11
+ )
12
+
13
+ const styles = StyleSheet.create({
14
+ container: {
15
+ alignContent: 'center',
16
+ alignItems: 'center',
17
+ backgroundColor: 'rgba(0, 0, 0, 0.1)',
18
+ height: '100%',
19
+ justifyContent: 'center',
20
+ justifyItems: 'center',
21
+ width: '100%',
22
+ },
23
+ })
24
+
25
+ export default Loading
@@ -0,0 +1,13 @@
1
+ import React, { FC } from 'react'
2
+ import { ActivityIndicator } from 'react-native'
3
+
4
+ interface LoaderProps {
5
+ color?: string
6
+ size?: 'large' | 'small'
7
+ }
8
+
9
+ const Loader: FC<LoaderProps> = ({ color = '#3E71F8', size = 'small' }) => {
10
+ return <ActivityIndicator color={color} size={size} />
11
+ }
12
+
13
+ export default Loader