@exodus/react-native-webview 11.26.1-exodus.26 → 11.26.1-exodus.28

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.
Files changed (116) hide show
  1. package/.all-contributorsrc +185 -0
  2. package/.circleci/config.yml +66 -0
  3. package/.eslintignore +2 -0
  4. package/.eslintrc.js +94 -0
  5. package/.flowconfig +88 -0
  6. package/.flowconfig.android +88 -0
  7. package/.gitattributes +12 -0
  8. package/.github/CODEOWNERS +1 -0
  9. package/.github/ISSUE_TEMPLATE/bug-report.md +42 -0
  10. package/.github/ISSUE_TEMPLATE/feature_request.md +30 -0
  11. package/.github/workflows/android-ci.yml +35 -0
  12. package/.github/workflows/detox.yml +20 -0
  13. package/.github/workflows/ios-ci.yml +31 -0
  14. package/.github/workflows/scripts/install-vs-features.ps1 +108 -0
  15. package/.github/workflows/stale.yml +17 -0
  16. package/.gitignore +62 -0
  17. package/.prettierrc.js +10 -0
  18. package/.releaserc +15 -0
  19. package/.vscode/settings.json +9 -0
  20. package/android/.gradle/8.9/checksums/checksums.lock +0 -0
  21. package/android/.gradle/8.9/checksums/md5-checksums.bin +0 -0
  22. package/android/.gradle/8.9/checksums/sha1-checksums.bin +0 -0
  23. package/android/.gradle/8.9/dependencies-accessors/gc.properties +0 -0
  24. package/android/.gradle/8.9/fileChanges/last-build.bin +0 -0
  25. package/android/.gradle/8.9/fileHashes/fileHashes.lock +0 -0
  26. package/android/.gradle/8.9/gc.properties +0 -0
  27. package/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock +0 -0
  28. package/android/.gradle/buildOutputCleanup/cache.properties +2 -0
  29. package/android/.gradle/vcs-1/gc.properties +0 -0
  30. package/android/src/main/java/com/reactnativecommunity/webview/RNCWebViewManager.java +1 -0
  31. package/apple/RNCWebView.m +10 -2
  32. package/babel.config.js +11 -0
  33. package/bin/setup +26 -0
  34. package/docs/Contributing.md +102 -0
  35. package/docs/Custom-Android.md +222 -0
  36. package/docs/Custom-iOS.md +236 -0
  37. package/docs/Debugging.md +101 -0
  38. package/docs/Getting-Started.md +142 -0
  39. package/docs/Guide.md +613 -0
  40. package/docs/Reference.md +1639 -0
  41. package/example/.gitignore +14 -0
  42. package/example/.watchmanconfig +1 -0
  43. package/example/App.tsx +262 -0
  44. package/example/android/.gradle/7.3.3/checksums/checksums.lock +0 -0
  45. package/example/android/.gradle/7.3.3/checksums/md5-checksums.bin +0 -0
  46. package/example/android/.gradle/7.3.3/checksums/sha1-checksums.bin +0 -0
  47. package/example/android/.gradle/7.3.3/dependencies-accessors/dependencies-accessors.lock +0 -0
  48. package/example/android/.gradle/7.3.3/dependencies-accessors/gc.properties +0 -0
  49. package/example/android/.gradle/7.3.3/executionHistory/executionHistory.lock +0 -0
  50. package/example/android/.gradle/7.3.3/fileChanges/last-build.bin +0 -0
  51. package/example/android/.gradle/7.3.3/fileHashes/fileHashes.lock +0 -0
  52. package/example/android/.gradle/7.3.3/gc.properties +0 -0
  53. package/example/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock +0 -0
  54. package/example/android/.gradle/buildOutputCleanup/cache.properties +2 -0
  55. package/example/android/.gradle/vcs-1/gc.properties +0 -0
  56. package/example/android/app/build/intermediates/cxx/abi_configuration_1a4zb4f4.json +14 -0
  57. package/example/android/app/build/intermediates/cxx/abi_configuration_1a4zb4f4.log +1 -0
  58. package/example/android/app/build/intermediates/cxx/abi_configuration_1a4zb4f4_key.json +18 -0
  59. package/example/android/app/build/intermediates/cxx/create_cxx_tasks_21_timing.txt +62 -0
  60. package/example/android/app/build/intermediates/cxx/ndk_locator_record_5i2h5t59.json +11 -0
  61. package/example/android/app/build/intermediates/cxx/ndk_locator_record_5i2h5t59.log +128 -0
  62. package/example/android/app/build/intermediates/cxx/ndk_locator_record_5i2h5t59_key.json +7 -0
  63. package/example/android/app/build/intermediates/cxx/ndk_locator_record_6n5y632q.json +11 -0
  64. package/example/android/app/build/intermediates/cxx/ndk_locator_record_6n5y632q.log +72 -0
  65. package/example/android/app/build/intermediates/cxx/ndk_locator_record_6n5y632q_key.json +8 -0
  66. package/example/android/build.gradle +15 -0
  67. package/example/android/gradle/wrapper/gradle-wrapper.jar +0 -0
  68. package/example/android/gradle/wrapper/gradle-wrapper.properties +5 -0
  69. package/example/android/gradle.properties +34 -0
  70. package/example/android/gradlew +234 -0
  71. package/example/android/gradlew.bat +89 -0
  72. package/example/android/settings.gradle +12 -0
  73. package/example/app.json +20 -0
  74. package/example/assets/test.html +9 -0
  75. package/example/babel.config.js +3 -0
  76. package/example/examples/Alerts.tsx +72 -0
  77. package/example/examples/ApplePay.tsx +23 -0
  78. package/example/examples/Background.tsx +54 -0
  79. package/example/examples/Downloads.tsx +57 -0
  80. package/example/examples/Injection.tsx +161 -0
  81. package/example/examples/LocalPageLoad.tsx +16 -0
  82. package/example/examples/Messaging.tsx +63 -0
  83. package/example/examples/NativeWebpage.tsx +22 -0
  84. package/example/examples/Scrolling.tsx +68 -0
  85. package/example/examples/Uploads.tsx +69 -0
  86. package/example/index.js +9 -0
  87. package/example/ios/Podfile +8 -0
  88. package/example/ios/Podfile.lock +445 -0
  89. package/jest-setups/jest.setup.js +8 -0
  90. package/jest.config.js +184 -0
  91. package/lib/WebView.android.d.ts.map +1 -0
  92. package/lib/WebView.d.ts.map +1 -0
  93. package/lib/WebView.ios.d.ts.map +1 -0
  94. package/lib/WebView.styles.d.ts.map +1 -0
  95. package/lib/WebViewNativeComponent.android.d.ts.map +1 -0
  96. package/lib/WebViewNativeComponent.ios.d.ts.map +1 -0
  97. package/lib/WebViewShared.d.ts +1 -1
  98. package/lib/WebViewShared.d.ts.map +1 -0
  99. package/lib/WebViewShared.js +1 -3
  100. package/lib/WebViewTypes.d.ts.map +1 -0
  101. package/lib/index.d.ts.map +1 -0
  102. package/metro.config.js +57 -0
  103. package/package.json +2 -2
  104. package/src/WebView.android.tsx +255 -0
  105. package/src/WebView.ios.tsx +227 -0
  106. package/src/WebView.styles.ts +44 -0
  107. package/src/WebView.tsx +18 -0
  108. package/src/WebViewNativeComponent.android.ts +8 -0
  109. package/src/WebViewNativeComponent.ios.ts +8 -0
  110. package/src/WebViewShared.tsx +318 -0
  111. package/src/WebViewTypes.ts +941 -0
  112. package/src/__tests__/WebViewShared-test.js +292 -0
  113. package/src/__tests__/__snapshots__/WebViewShared-test.js.snap +13 -0
  114. package/src/index.ts +4 -0
  115. package/tsconfig.json +24 -0
  116. package/yarn.lock +13397 -0
@@ -0,0 +1,318 @@
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
+ WebViewOpenWindowEvent,
13
+ WebViewProgressEvent,
14
+ WebViewNativeEvent,
15
+ } from './WebViewTypes';
16
+ import styles from './WebView.styles';
17
+
18
+ const defaultOriginWhitelist = ['https://*'] as const;
19
+ const defaultDeeplinkWhitelist = ['https:'] as const;
20
+ const defaultDeeplinkBlocklist = [`http:`, `file:`, `javascript:`] as const;
21
+
22
+ const stringWhitelistToRegex = (originWhitelist: string): RegExp =>
23
+ new RegExp(`^${escapeStringRegexp(originWhitelist).replace(/\\\*/g, '.*')}$`);
24
+
25
+ const matchWithRegexList = (
26
+ compiledRegexList: readonly RegExp[],
27
+ value: string,
28
+ ) => {
29
+ return compiledRegexList.some(x => x.test(value));
30
+ };
31
+
32
+ const matchWithStringList = (
33
+ prefixes: readonly string[],
34
+ value: string,
35
+ ) => {
36
+ if (typeof value !== 'string') throw new Error(`value was not a string`)
37
+ return Array.prototype.includes.call(prefixes, value)
38
+ };
39
+
40
+ const _passesWhitelist = (
41
+ compiledWhitelist: readonly RegExp[],
42
+ url: string,
43
+ ) => {
44
+ try {
45
+ const { origin } = new URL(url)
46
+ return origin && matchWithRegexList(compiledWhitelist, origin)
47
+ } catch {
48
+ return false
49
+ }
50
+ };
51
+
52
+ const compileWhitelist = (
53
+ originWhitelist: readonly string[],
54
+ ): readonly RegExp[] =>
55
+ ['about:blank', ...(originWhitelist || [])].map(stringWhitelistToRegex);
56
+
57
+ const urlToProtocolScheme = (url: string): string | null => {
58
+ try {
59
+ return new URL(url).protocol
60
+ } catch {
61
+ // Protocol schemes must start with a letter and cannot start with digits, underscores etc.
62
+ // e.g 0invalid, _invalid, +invalid, -invalid, .invalid will all become null
63
+ return null
64
+ }
65
+ }
66
+
67
+ const createOnShouldStartLoadWithRequest = (
68
+ loadRequest: (
69
+ shouldStart: boolean,
70
+ url: string,
71
+ lockIdentifier: number,
72
+ ) => void,
73
+ originWhitelist: readonly string[],
74
+ deepLinkWhitelist: readonly string[],
75
+ onShouldStartLoadWithRequest?: OnShouldStartLoadWithRequest,
76
+ ) => {
77
+ const compiledWhiteList = compileWhitelist(originWhitelist)
78
+
79
+ return ({ nativeEvent }: ShouldStartLoadRequestEvent) => {
80
+ let shouldStart = true;
81
+ const { url, lockIdentifier, isTopFrame } = nativeEvent;
82
+
83
+ /** Check if the url passes the origin whitelist */
84
+ if (!_passesWhitelist(compiledWhiteList, url)) {
85
+ const protocol = urlToProtocolScheme(url)
86
+
87
+ /* Check that the protocol was properly parsed */
88
+ if (protocol !== null) {
89
+ /** Check if the protocol passes the hardcoded deeplink blocklist */
90
+ const foundMatchInBlocklist = matchWithStringList(defaultDeeplinkBlocklist, protocol)
91
+ if (!foundMatchInBlocklist) {
92
+ /** Check if the protocol passes the dynamic deeplink allow list */
93
+ const foundMatchInAllowlist = matchWithStringList(deepLinkWhitelist, protocol)
94
+
95
+ if (foundMatchInAllowlist) {
96
+ Linking.canOpenURL(url).then((supported) => {
97
+ if (supported && isTopFrame) {
98
+ return Linking.openURL(url);
99
+ }
100
+ console.warn(`Can't open url: ${url}`);
101
+ return undefined;
102
+ }).catch(e => {
103
+ console.warn('Error opening URL: ', e);
104
+ });
105
+ } else {
106
+ console.warn(`Failed to pass whitelist for deep link url: ${url}`);
107
+ }
108
+ } else {
109
+ console.warn(`Failed to pass default block list for deep link url: ${url}`);
110
+ }
111
+ }
112
+
113
+ shouldStart = false;
114
+ } else if (onShouldStartLoadWithRequest) {
115
+ shouldStart = onShouldStartLoadWithRequest(nativeEvent);
116
+ }
117
+
118
+ loadRequest(shouldStart, url, lockIdentifier);
119
+ };
120
+ };
121
+
122
+ const defaultRenderLoading = () => (
123
+ <View style={styles.loadingOrErrorView}>
124
+ <ActivityIndicator />
125
+ </View>
126
+ );
127
+ const defaultRenderError = (
128
+ errorDomain: string | undefined,
129
+ errorCode: number,
130
+ errorDesc: string,
131
+ ) => (
132
+ <View style={styles.loadingOrErrorView}>
133
+ <Text style={styles.errorTextTitle}>Error loading page</Text>
134
+ <Text style={styles.errorText}>{`Domain: ${errorDomain}`}</Text>
135
+ <Text style={styles.errorText}>{`Error Code: ${errorCode}`}</Text>
136
+ <Text style={styles.errorText}>{`Description: ${errorDesc}`}</Text>
137
+ </View>
138
+ );
139
+
140
+ export {
141
+ defaultOriginWhitelist,
142
+ defaultDeeplinkWhitelist,
143
+ createOnShouldStartLoadWithRequest,
144
+ defaultRenderLoading,
145
+ defaultRenderError,
146
+ };
147
+
148
+
149
+ export const useWebWiewLogic = ({
150
+ startInLoadingState,
151
+ onLoadStart,
152
+ onLoad,
153
+ onLoadEnd,
154
+ onError,
155
+ onMessageProp,
156
+ onOpenWindowProp,
157
+ originWhitelist,
158
+ deeplinkWhitelist,
159
+ onShouldStartLoadWithRequestProp,
160
+ onShouldStartLoadWithRequestCallback,
161
+ validateMeta,
162
+ validateData,
163
+ }: {
164
+ startInLoadingState?: boolean
165
+ onLoadStart?: (event: WebViewNavigationEvent) => void;
166
+ onLoad?: (event: WebViewNavigationEvent) => void;
167
+ onLoadEnd?: (event: WebViewNavigationEvent | WebViewErrorEvent) => void;
168
+ onError?: (event: WebViewErrorEvent) => void;
169
+ onMessageProp?: (event: WebViewMessage) => void;
170
+ onOpenWindowProp?: (event: WebViewOpenWindowEvent) => void;
171
+ originWhitelist: readonly string[];
172
+ deeplinkWhitelist: readonly string[];
173
+ onShouldStartLoadWithRequestProp?: OnShouldStartLoadWithRequest;
174
+ onShouldStartLoadWithRequestCallback: (shouldStart: boolean, url: string, lockIdentifier?: number | undefined) => void;
175
+ validateMeta: (event: WebViewNativeEvent) => WebViewNativeEvent;
176
+ validateData: (data: object) => object;
177
+ }) => {
178
+
179
+ const [viewState, setViewState] = useState<'IDLE' | 'LOADING' | 'ERROR'>(startInLoadingState ? "LOADING" : "IDLE");
180
+ const [lastErrorEvent, setLastErrorEvent] = useState<WebViewError | null>(null);
181
+ const startUrl = useRef<string | null>(null)
182
+
183
+ const passesWhitelist = (url: string) => {
184
+ if (!url || typeof url !== 'string') return false;
185
+ return _passesWhitelist(compileWhitelist(originWhitelist), url);
186
+ }
187
+
188
+ const passesWhitelistUse = useCallback(passesWhitelist, [originWhitelist])
189
+
190
+ const extractMeta = (nativeEvent: WebViewNativeEvent): WebViewNativeEvent => ({
191
+ url: String(nativeEvent.url),
192
+ loading: Boolean(nativeEvent.loading),
193
+ title: String(nativeEvent.title),
194
+ canGoBack: Boolean(nativeEvent.canGoBack),
195
+ canGoForward: Boolean(nativeEvent.canGoForward),
196
+ lockIdentifier: Number(nativeEvent.lockIdentifier),
197
+ });
198
+
199
+ const onLoadingStart = useCallback((event: WebViewNavigationEvent) => {
200
+ // Needed for android
201
+ startUrl.current = event.nativeEvent.url;
202
+ // !Needed for android
203
+
204
+ onLoadStart?.(event);
205
+ }, [onLoadStart]);
206
+
207
+ const onLoadingError = useCallback((event: WebViewErrorEvent) => {
208
+ event.persist();
209
+ if (onError) {
210
+ onError(event);
211
+ } else {
212
+ console.warn('Encountered an error loading page', event.nativeEvent);
213
+ }
214
+ onLoadEnd?.(event);
215
+ if (event.isDefaultPrevented()) { return };
216
+ setViewState('ERROR');
217
+ setLastErrorEvent(event.nativeEvent);
218
+ }, [onError, onLoadEnd]);
219
+
220
+ const onLoadingFinish = useCallback((event: WebViewNavigationEvent) => {
221
+ onLoad?.(event);
222
+ onLoadEnd?.(event);
223
+ const { nativeEvent: { url } } = event;
224
+ if (!passesWhitelistUse(url)) return;
225
+
226
+ // on Android, only if url === startUrl
227
+ if (Platform.OS !== "android" || url === startUrl.current) {
228
+ setViewState('IDLE');
229
+ }
230
+ // !on Android, only if url === startUrl
231
+ // REMOVED: updateNavigationState(event);
232
+ }, [onLoad, onLoadEnd, passesWhitelistUse]);
233
+
234
+ const onMessage = useCallback((event: WebViewMessageEvent) => {
235
+ const { nativeEvent } = event;
236
+ if (!passesWhitelistUse(nativeEvent.url)) return;
237
+
238
+ // TODO: can/should we perform any other validation?
239
+
240
+ const data = JSON.stringify(validateData(JSON.parse(nativeEvent.data)));
241
+ const meta = validateMeta(extractMeta(nativeEvent));
242
+
243
+ onMessageProp?.({ ...meta, data });
244
+ }, [onMessageProp, passesWhitelistUse, validateData, validateMeta]);
245
+
246
+ const onLoadingProgress = useCallback((event: WebViewProgressEvent) => {
247
+ const { nativeEvent: { progress } } = event;
248
+ if (!passesWhitelistUse(event.nativeEvent.url)) return;
249
+
250
+ // patch for Android only
251
+ if (Platform.OS === "android" && progress === 1) {
252
+ setViewState(prevViewState => prevViewState === 'LOADING' ? 'IDLE' : prevViewState);
253
+ }
254
+ // !patch for Android only
255
+ // REMOVED: onLoadProgress?.(event);
256
+ }, [passesWhitelistUse]);
257
+
258
+ const onShouldStartLoadWithRequest = useMemo(() => createOnShouldStartLoadWithRequest(
259
+ onShouldStartLoadWithRequestCallback,
260
+ originWhitelist,
261
+ deeplinkWhitelist,
262
+ onShouldStartLoadWithRequestProp,
263
+ )
264
+ , [originWhitelist, deeplinkWhitelist, onShouldStartLoadWithRequestProp, onShouldStartLoadWithRequestCallback])
265
+
266
+ // Android Only
267
+ const onOpenWindow = useCallback((event: WebViewOpenWindowEvent) => {
268
+ onOpenWindowProp?.(event);
269
+ }, [onOpenWindowProp]);
270
+ // !Android Only
271
+
272
+ return {
273
+ onShouldStartLoadWithRequest,
274
+ onLoadingStart,
275
+ onLoadingProgress,
276
+ onLoadingError,
277
+ onLoadingFinish,
278
+ onMessage,
279
+ onOpenWindow,
280
+ passesWhitelist,
281
+ viewState,
282
+ setViewState,
283
+ lastErrorEvent,
284
+ }
285
+ };
286
+
287
+ export const versionPasses = (version: string | undefined, minimum: string | undefined): boolean => {
288
+ if (!version || !minimum) return false
289
+ if (typeof version !== 'string' || typeof minimum !== 'string') return false
290
+
291
+ if (minimum.includes(', ')) {
292
+ // We have a set of possible versions
293
+ const variants = minimum.split(', ')
294
+ // Every entry but the last one should be with an upper bound
295
+ if (!variants.slice(0, -1).every(x => x.includes(' <'))) return false
296
+ return variants.some(x => versionPasses(version, x)) // Any match passes
297
+ }
298
+
299
+ if (minimum.includes(' <')) {
300
+ const [min, max, ...rest] = minimum.split(' <')
301
+ if (rest.length > 0) return false
302
+ // Last check is required for correctness/formatting validation
303
+ return versionPasses(version, min) && !versionPasses(version, max) && versionPasses(max, version)
304
+ }
305
+
306
+ const versionRegex = /^[0-9]+(\.[0-9]+)*$/
307
+ if (!versionRegex.test(version) || !versionRegex.test(minimum)) return false
308
+ const versionParts = version.split('.').map(Number)
309
+ const minimumParts = minimum.split('.').map(Number)
310
+ const len = Math.max(versionParts.length, minimumParts.length)
311
+ for (let i = 0; i < len; i += 1) {
312
+ const ver = versionParts[i] || 0
313
+ const min = minimumParts[i] || 0
314
+ if (ver > min) return true
315
+ if (ver < min) return false
316
+ }
317
+ return true // equals
318
+ }