@exodus/react-native-webview 11.26.1-exodus.12 → 11.26.1-exodus.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/.all-contributorsrc +185 -0
- package/.circleci/config.yml +66 -0
- package/.eslintignore +2 -0
- package/.eslintrc.js +94 -0
- package/.flowconfig +88 -0
- package/.flowconfig.android +88 -0
- package/.gitattributes +12 -0
- package/.github/CODEOWNERS +1 -0
- package/.github/ISSUE_TEMPLATE/bug-report.md +42 -0
- package/.github/ISSUE_TEMPLATE/feature_request.md +30 -0
- package/.github/workflows/android-ci.yml +35 -0
- package/.github/workflows/detox.yml +20 -0
- package/.github/workflows/ios-ci.yml +31 -0
- package/.github/workflows/scripts/install-vs-features.ps1 +108 -0
- package/.github/workflows/stale.yml +17 -0
- package/.gitignore +62 -0
- package/.prettierrc.js +10 -0
- package/.releaserc +15 -0
- package/.vscode/settings.json +9 -0
- package/android/src/main/java/com/reactnativecommunity/webview/RNCWebViewManager.java +5 -6
- package/apple/RNCWebView.h +1 -0
- package/apple/RNCWebView.m +17 -10
- package/apple/RNCWebViewManager.m +1 -0
- package/babel.config.js +11 -0
- package/bin/setup +26 -0
- package/docs/Contributing.md +102 -0
- package/docs/Custom-Android.md +222 -0
- package/docs/Custom-iOS.md +236 -0
- package/docs/Debugging.md +101 -0
- package/docs/Getting-Started.md +142 -0
- package/docs/Guide.md +613 -0
- package/docs/Reference.md +1639 -0
- package/example/.gitignore +14 -0
- package/example/.watchmanconfig +1 -0
- package/example/App.tsx +262 -0
- package/example/android/build.gradle +15 -0
- package/example/android/gradle/wrapper/gradle-wrapper.jar +0 -0
- package/example/android/gradle/wrapper/gradle-wrapper.properties +5 -0
- package/example/android/gradle.properties +34 -0
- package/example/android/gradlew +234 -0
- package/example/android/gradlew.bat +89 -0
- package/example/android/settings.gradle +12 -0
- package/example/app.json +20 -0
- package/example/assets/test.html +9 -0
- package/example/babel.config.js +3 -0
- package/example/examples/Alerts.tsx +72 -0
- package/example/examples/ApplePay.tsx +23 -0
- package/example/examples/Background.tsx +54 -0
- package/example/examples/Downloads.tsx +57 -0
- package/example/examples/Injection.tsx +161 -0
- package/example/examples/LocalPageLoad.tsx +16 -0
- package/example/examples/Messaging.tsx +63 -0
- package/example/examples/NativeWebpage.tsx +22 -0
- package/example/examples/Scrolling.tsx +68 -0
- package/example/examples/Uploads.tsx +69 -0
- package/example/index.js +9 -0
- package/example/ios/Podfile +8 -0
- package/example/ios/Podfile.lock +445 -0
- package/jest-setups/jest.setup.js +8 -0
- package/jest.config.js +184 -0
- package/lib/WebView.android.d.ts.map +1 -0
- package/lib/WebView.android.js +2 -3
- package/lib/WebView.d.ts.map +1 -0
- package/lib/WebView.ios.d.ts.map +1 -0
- package/lib/WebView.ios.js +2 -3
- package/lib/WebView.styles.d.ts.map +1 -0
- package/lib/WebViewNativeComponent.android.d.ts.map +1 -0
- package/lib/WebViewNativeComponent.ios.d.ts.map +1 -0
- package/lib/WebViewShared.d.ts.map +1 -0
- package/lib/WebViewTypes.d.ts +5 -0
- package/lib/WebViewTypes.d.ts.map +1 -0
- package/lib/index.d.ts.map +1 -0
- package/metro.config.js +57 -0
- package/package.json +1 -1
- package/src/WebView.android.tsx +246 -0
- package/src/WebView.ios.tsx +221 -0
- package/src/WebView.styles.ts +44 -0
- package/src/WebView.tsx +18 -0
- package/src/WebViewNativeComponent.android.ts +8 -0
- package/src/WebViewNativeComponent.ios.ts +8 -0
- package/src/WebViewShared.tsx +257 -0
- package/src/WebViewTypes.ts +920 -0
- package/src/__tests__/WebViewShared-test.js +208 -0
- package/src/__tests__/__snapshots__/WebViewShared-test.js.snap +8 -0
- package/src/index.ts +4 -0
- package/tsconfig.json +24 -0
- package/yarn.lock +13397 -0
package/src/WebView.tsx
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Text, View } from 'react-native';
|
|
3
|
+
import { IOSWebViewProps, AndroidWebViewProps } from './WebViewTypes';
|
|
4
|
+
|
|
5
|
+
export type WebViewProps = IOSWebViewProps & AndroidWebViewProps;
|
|
6
|
+
|
|
7
|
+
// This "dummy" WebView is to render something for unsupported platforms,
|
|
8
|
+
// like for example Expo SDK "web" platform.
|
|
9
|
+
const WebView: React.FunctionComponent<WebViewProps> = () => (
|
|
10
|
+
<View style={{ alignSelf: 'flex-start' }}>
|
|
11
|
+
<Text style={{ color: 'red' }}>
|
|
12
|
+
React Native WebView does not support this platform.
|
|
13
|
+
</Text>
|
|
14
|
+
</View>
|
|
15
|
+
);
|
|
16
|
+
|
|
17
|
+
export { WebView };
|
|
18
|
+
export default WebView;
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
import escapeStringRegexp from 'escape-string-regexp';
|
|
2
|
+
import React, { useCallback, useMemo, useRef, useState } from 'react';
|
|
3
|
+
import { Linking, View, ActivityIndicator, Text, Platform } from 'react-native';
|
|
4
|
+
import {
|
|
5
|
+
OnShouldStartLoadWithRequest,
|
|
6
|
+
ShouldStartLoadRequestEvent,
|
|
7
|
+
WebViewError,
|
|
8
|
+
WebViewErrorEvent,
|
|
9
|
+
WebViewMessageEvent,
|
|
10
|
+
WebViewMessage,
|
|
11
|
+
WebViewNavigationEvent,
|
|
12
|
+
WebViewProgressEvent,
|
|
13
|
+
WebViewNativeEvent,
|
|
14
|
+
} from './WebViewTypes';
|
|
15
|
+
import styles from './WebView.styles';
|
|
16
|
+
|
|
17
|
+
const defaultOriginWhitelist = ['https://*'] as const;
|
|
18
|
+
|
|
19
|
+
const extractOrigin = (url: string): string => {
|
|
20
|
+
const result = /^[A-Za-z][A-Za-z0-9+\-.]+:(\/\/)?[^/]*/.exec(url);
|
|
21
|
+
return result === null ? '' : result[0];
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const originWhitelistToRegex = (originWhitelist: string): string =>
|
|
25
|
+
`^${escapeStringRegexp(originWhitelist).replace(/\\\*/g, '.*')}$`;
|
|
26
|
+
|
|
27
|
+
const _passesWhitelist = (
|
|
28
|
+
compiledWhitelist: readonly string[],
|
|
29
|
+
url: string,
|
|
30
|
+
) => {
|
|
31
|
+
const origin = extractOrigin(url);
|
|
32
|
+
if (!origin) return false;
|
|
33
|
+
if (origin !== new URL(url).origin) return null;
|
|
34
|
+
return compiledWhitelist.some(x => new RegExp(x).test(origin));
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const compileWhitelist = (
|
|
38
|
+
originWhitelist: readonly string[],
|
|
39
|
+
): readonly string[] =>
|
|
40
|
+
['about:blank', ...(originWhitelist || [])].map(originWhitelistToRegex);
|
|
41
|
+
|
|
42
|
+
const createOnShouldStartLoadWithRequest = (
|
|
43
|
+
loadRequest: (
|
|
44
|
+
shouldStart: boolean,
|
|
45
|
+
url: string,
|
|
46
|
+
lockIdentifier: number,
|
|
47
|
+
) => void,
|
|
48
|
+
originWhitelist: readonly string[],
|
|
49
|
+
onShouldStartLoadWithRequest?: OnShouldStartLoadWithRequest,
|
|
50
|
+
) => {
|
|
51
|
+
return ({ nativeEvent }: ShouldStartLoadRequestEvent) => {
|
|
52
|
+
let shouldStart = true;
|
|
53
|
+
const { url, lockIdentifier, isTopFrame } = nativeEvent;
|
|
54
|
+
|
|
55
|
+
if (!_passesWhitelist(compileWhitelist(originWhitelist), url)) {
|
|
56
|
+
Linking.canOpenURL(url).then((supported) => {
|
|
57
|
+
if (supported && isTopFrame && /^https:\/\//.test(url)) {
|
|
58
|
+
return Linking.openURL(url);
|
|
59
|
+
}
|
|
60
|
+
console.warn(`Can't open url: ${url}`);
|
|
61
|
+
return undefined;
|
|
62
|
+
}).catch(e => {
|
|
63
|
+
console.warn('Error opening URL: ', e);
|
|
64
|
+
});
|
|
65
|
+
shouldStart = false;
|
|
66
|
+
} else if (onShouldStartLoadWithRequest) {
|
|
67
|
+
shouldStart = onShouldStartLoadWithRequest(nativeEvent);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
loadRequest(shouldStart, url, lockIdentifier);
|
|
71
|
+
};
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const defaultRenderLoading = () => (
|
|
75
|
+
<View style={styles.loadingOrErrorView}>
|
|
76
|
+
<ActivityIndicator />
|
|
77
|
+
</View>
|
|
78
|
+
);
|
|
79
|
+
const defaultRenderError = (
|
|
80
|
+
errorDomain: string | undefined,
|
|
81
|
+
errorCode: number,
|
|
82
|
+
errorDesc: string,
|
|
83
|
+
) => (
|
|
84
|
+
<View style={styles.loadingOrErrorView}>
|
|
85
|
+
<Text style={styles.errorTextTitle}>Error loading page</Text>
|
|
86
|
+
<Text style={styles.errorText}>{`Domain: ${errorDomain}`}</Text>
|
|
87
|
+
<Text style={styles.errorText}>{`Error Code: ${errorCode}`}</Text>
|
|
88
|
+
<Text style={styles.errorText}>{`Description: ${errorDesc}`}</Text>
|
|
89
|
+
</View>
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
export {
|
|
93
|
+
defaultOriginWhitelist,
|
|
94
|
+
createOnShouldStartLoadWithRequest,
|
|
95
|
+
defaultRenderLoading,
|
|
96
|
+
defaultRenderError,
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
export const useWebWiewLogic = ({
|
|
101
|
+
startInLoadingState,
|
|
102
|
+
onLoadStart,
|
|
103
|
+
onLoad,
|
|
104
|
+
onLoadEnd,
|
|
105
|
+
onError,
|
|
106
|
+
onMessageProp,
|
|
107
|
+
originWhitelist,
|
|
108
|
+
onShouldStartLoadWithRequestProp,
|
|
109
|
+
onShouldStartLoadWithRequestCallback,
|
|
110
|
+
validateMeta,
|
|
111
|
+
validateData,
|
|
112
|
+
}: {
|
|
113
|
+
startInLoadingState?: boolean
|
|
114
|
+
onLoadStart?: (event: WebViewNavigationEvent) => void;
|
|
115
|
+
onLoad?: (event: WebViewNavigationEvent) => void;
|
|
116
|
+
onLoadEnd?: (event: WebViewNavigationEvent | WebViewErrorEvent) => void;
|
|
117
|
+
onError?: (event: WebViewErrorEvent) => void;
|
|
118
|
+
onMessageProp?: (event: WebViewMessage) => void;
|
|
119
|
+
originWhitelist: readonly string[];
|
|
120
|
+
onShouldStartLoadWithRequestProp?: OnShouldStartLoadWithRequest;
|
|
121
|
+
onShouldStartLoadWithRequestCallback: (shouldStart: boolean, url: string, lockIdentifier?: number | undefined) => void;
|
|
122
|
+
validateMeta: (event: WebViewNativeEvent) => WebViewNativeEvent;
|
|
123
|
+
validateData: (data: object) => object;
|
|
124
|
+
}) => {
|
|
125
|
+
|
|
126
|
+
const [viewState, setViewState] = useState<'IDLE' | 'LOADING' | 'ERROR'>(startInLoadingState ? "LOADING" : "IDLE");
|
|
127
|
+
const [lastErrorEvent, setLastErrorEvent] = useState<WebViewError | null>(null);
|
|
128
|
+
const startUrl = useRef<string | null>(null)
|
|
129
|
+
|
|
130
|
+
const passesWhitelist = (url: string) => {
|
|
131
|
+
if (!url || typeof url !== 'string') return false;
|
|
132
|
+
return _passesWhitelist(compileWhitelist(originWhitelist), url);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const passesWhitelistUse = useCallback(passesWhitelist, [originWhitelist])
|
|
136
|
+
|
|
137
|
+
const extractMeta = (nativeEvent: WebViewNativeEvent): WebViewNativeEvent => ({
|
|
138
|
+
url: String(nativeEvent.url),
|
|
139
|
+
loading: Boolean(nativeEvent.loading),
|
|
140
|
+
title: String(nativeEvent.title),
|
|
141
|
+
canGoBack: Boolean(nativeEvent.canGoBack),
|
|
142
|
+
canGoForward: Boolean(nativeEvent.canGoForward),
|
|
143
|
+
lockIdentifier: Number(nativeEvent.lockIdentifier),
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
const onLoadingStart = useCallback((event: WebViewNavigationEvent) => {
|
|
147
|
+
// Needed for android
|
|
148
|
+
startUrl.current = event.nativeEvent.url;
|
|
149
|
+
// !Needed for android
|
|
150
|
+
|
|
151
|
+
onLoadStart?.(event);
|
|
152
|
+
}, [onLoadStart]);
|
|
153
|
+
|
|
154
|
+
const onLoadingError = useCallback((event: WebViewErrorEvent) => {
|
|
155
|
+
event.persist();
|
|
156
|
+
if (onError) {
|
|
157
|
+
onError(event);
|
|
158
|
+
} else {
|
|
159
|
+
console.warn('Encountered an error loading page', event.nativeEvent);
|
|
160
|
+
}
|
|
161
|
+
onLoadEnd?.(event);
|
|
162
|
+
if (event.isDefaultPrevented()) { return };
|
|
163
|
+
setViewState('ERROR');
|
|
164
|
+
setLastErrorEvent(event.nativeEvent);
|
|
165
|
+
}, [onError, onLoadEnd]);
|
|
166
|
+
|
|
167
|
+
const onLoadingFinish = useCallback((event: WebViewNavigationEvent) => {
|
|
168
|
+
onLoad?.(event);
|
|
169
|
+
onLoadEnd?.(event);
|
|
170
|
+
const { nativeEvent: { url } } = event;
|
|
171
|
+
if (!passesWhitelistUse(url)) return;
|
|
172
|
+
|
|
173
|
+
// on Android, only if url === startUrl
|
|
174
|
+
if (Platform.OS !== "android" || url === startUrl.current) {
|
|
175
|
+
setViewState('IDLE');
|
|
176
|
+
}
|
|
177
|
+
// !on Android, only if url === startUrl
|
|
178
|
+
// REMOVED: updateNavigationState(event);
|
|
179
|
+
}, [onLoad, onLoadEnd, passesWhitelistUse]);
|
|
180
|
+
|
|
181
|
+
const onMessage = useCallback((event: WebViewMessageEvent) => {
|
|
182
|
+
const { nativeEvent } = event;
|
|
183
|
+
if (!passesWhitelistUse(nativeEvent.url)) return;
|
|
184
|
+
|
|
185
|
+
// TODO: can/should we perform any other validation?
|
|
186
|
+
|
|
187
|
+
const data = JSON.stringify(validateData(JSON.parse(nativeEvent.data)));
|
|
188
|
+
const meta = validateMeta(extractMeta(nativeEvent));
|
|
189
|
+
|
|
190
|
+
onMessageProp?.({ ...meta, data });
|
|
191
|
+
}, [onMessageProp, passesWhitelistUse, validateData, validateMeta]);
|
|
192
|
+
|
|
193
|
+
const onLoadingProgress = useCallback((event: WebViewProgressEvent) => {
|
|
194
|
+
const { nativeEvent: { progress } } = event;
|
|
195
|
+
if (!passesWhitelistUse(event.nativeEvent.url)) return;
|
|
196
|
+
|
|
197
|
+
// patch for Android only
|
|
198
|
+
if (Platform.OS === "android" && progress === 1) {
|
|
199
|
+
setViewState(prevViewState => prevViewState === 'LOADING' ? 'IDLE' : prevViewState);
|
|
200
|
+
}
|
|
201
|
+
// !patch for Android only
|
|
202
|
+
// REMOVED: onLoadProgress?.(event);
|
|
203
|
+
}, [passesWhitelistUse]);
|
|
204
|
+
|
|
205
|
+
const onShouldStartLoadWithRequest = useMemo(() => createOnShouldStartLoadWithRequest(
|
|
206
|
+
onShouldStartLoadWithRequestCallback,
|
|
207
|
+
originWhitelist,
|
|
208
|
+
onShouldStartLoadWithRequestProp,
|
|
209
|
+
)
|
|
210
|
+
, [originWhitelist, onShouldStartLoadWithRequestProp, onShouldStartLoadWithRequestCallback])
|
|
211
|
+
|
|
212
|
+
return {
|
|
213
|
+
onShouldStartLoadWithRequest,
|
|
214
|
+
onLoadingStart,
|
|
215
|
+
onLoadingProgress,
|
|
216
|
+
onLoadingError,
|
|
217
|
+
onLoadingFinish,
|
|
218
|
+
onMessage,
|
|
219
|
+
passesWhitelist,
|
|
220
|
+
viewState,
|
|
221
|
+
setViewState,
|
|
222
|
+
lastErrorEvent,
|
|
223
|
+
}
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
export const versionPasses = (version: string | undefined, minimum: string | undefined): boolean => {
|
|
227
|
+
if (!version || !minimum) return false
|
|
228
|
+
if (typeof version !== 'string' || typeof minimum !== 'string') return false
|
|
229
|
+
|
|
230
|
+
if (minimum.includes(', ')) {
|
|
231
|
+
// We have a set of possible versions
|
|
232
|
+
const variants = minimum.split(', ')
|
|
233
|
+
// Every entry but the last one should be with an upper bound
|
|
234
|
+
if (!variants.slice(0, -1).every(x => x.includes(' <'))) return false
|
|
235
|
+
return variants.some(x => versionPasses(version, x)) // Any match passes
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
if (minimum.includes(' <')) {
|
|
239
|
+
const [min, max, ...rest] = minimum.split(' <')
|
|
240
|
+
if (rest.length > 0) return false
|
|
241
|
+
// Last check is required for correctness/formatting validation
|
|
242
|
+
return versionPasses(version, min) && !versionPasses(version, max) && versionPasses(max, version)
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
const versionRegex = /^[0-9]+(\.[0-9]+)*$/
|
|
246
|
+
if (!versionRegex.test(version) || !versionRegex.test(minimum)) return false
|
|
247
|
+
const versionParts = version.split('.').map(Number)
|
|
248
|
+
const minimumParts = minimum.split('.').map(Number)
|
|
249
|
+
const len = Math.max(versionParts.length, minimumParts.length)
|
|
250
|
+
for (let i = 0; i < len; i += 1) {
|
|
251
|
+
const ver = versionParts[i] || 0
|
|
252
|
+
const min = minimumParts[i] || 0
|
|
253
|
+
if (ver > min) return true
|
|
254
|
+
if (ver < min) return false
|
|
255
|
+
}
|
|
256
|
+
return true // equals
|
|
257
|
+
}
|