forked from Ivasoft/mattermost-mobile
* Revert updated dependecies for SSO * Fix test setup Co-authored-by: Elias Nahum <nahumhbl@gmail.com>
This commit is contained in:
@@ -3,7 +3,7 @@
|
||||
|
||||
import {Alert, AppState, Dimensions, Linking, NativeModules, Platform} from 'react-native';
|
||||
import AsyncStorage from '@react-native-community/async-storage';
|
||||
import CookieManager from '@react-native-community/cookies';
|
||||
import CookieManager from 'react-native-cookies';
|
||||
import DeviceInfo from 'react-native-device-info';
|
||||
import {getLocales} from 'react-native-localize';
|
||||
import RNFetchBlob from 'rn-fetch-blob';
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
Platform,
|
||||
} from 'react-native';
|
||||
import {WebView} from 'react-native-webview';
|
||||
import CookieManager from '@react-native-community/cookies';
|
||||
import CookieManager from 'react-native-cookies';
|
||||
import urlParse from 'url-parse';
|
||||
|
||||
import {Client4} from '@mm-redux/client';
|
||||
@@ -27,7 +27,7 @@ const HEADERS = {
|
||||
'X-Mobile-App': 'mattermost',
|
||||
};
|
||||
|
||||
const postMessageJS = 'window.ReactNativeWebView.postMessage(document.body.innerText);';
|
||||
const postMessageJS = "window.postMessage(document.body.innerText, '*');";
|
||||
|
||||
// Used to make sure that OneLogin forms scale appropriately on both platforms.
|
||||
const oneLoginFormScalingJS = `
|
||||
@@ -240,17 +240,16 @@ class SSO extends PureComponent {
|
||||
<WebView
|
||||
ref={this.webViewRef}
|
||||
source={{uri: this.loginUrl, headers: HEADERS}}
|
||||
javaScriptEnabled={true}
|
||||
javaScriptEnabledAndroid={true}
|
||||
automaticallyAdjustContentInsets={false}
|
||||
startInLoadingState={true}
|
||||
onNavigationStateChange={this.onNavigationStateChange}
|
||||
onShouldStartLoadWithRequest={() => true}
|
||||
injectedJavaScript={jsCode}
|
||||
onLoadEnd={this.onLoadEnd}
|
||||
onMessage={(messagingEnabled || Platform.OS === 'android') ? this.onMessage : null}
|
||||
sharedCookiesEnabled={Platform.OS === 'android'}
|
||||
onMessage={messagingEnabled ? this.onMessage : null}
|
||||
useSharedProcessPool={true}
|
||||
cacheEnabled={false}
|
||||
useSharedProcessPool={false}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {Client4} from '@mm-redux/client';
|
||||
import CookieManager from '@react-native-community/cookies';
|
||||
import CookieManager from 'react-native-cookies';
|
||||
|
||||
export function setCSRFFromCookie(url) {
|
||||
return new Promise((resolve) => {
|
||||
|
||||
@@ -207,7 +207,7 @@ PODS:
|
||||
- React-jsinspector (0.62.2)
|
||||
- react-native-cameraroll (1.6.2):
|
||||
- React
|
||||
- react-native-cookies (3.0.0):
|
||||
- react-native-cookies (3.2.0):
|
||||
- React
|
||||
- react-native-document-picker (3.4.0):
|
||||
- React
|
||||
@@ -235,7 +235,7 @@ PODS:
|
||||
- react-native-video/Video (= 5.0.2)
|
||||
- react-native-video/Video (5.0.2):
|
||||
- React
|
||||
- react-native-webview (9.4.0):
|
||||
- react-native-webview (7.0.1):
|
||||
- React
|
||||
- React-RCTActionSheet (0.62.2):
|
||||
- React-Core/RCTActionSheetHeaders (= 0.62.2)
|
||||
@@ -320,7 +320,7 @@ PODS:
|
||||
- React
|
||||
- RNGestureHandler (1.6.1):
|
||||
- React
|
||||
- RNKeychain (6.0.0):
|
||||
- RNKeychain (4.0.5):
|
||||
- React
|
||||
- RNLocalize (1.4.0):
|
||||
- React
|
||||
@@ -380,7 +380,7 @@ DEPENDENCIES:
|
||||
- React-jsiexecutor (from `../node_modules/react-native/ReactCommon/jsiexecutor`)
|
||||
- React-jsinspector (from `../node_modules/react-native/ReactCommon/jsinspector`)
|
||||
- "react-native-cameraroll (from `../node_modules/@react-native-community/cameraroll`)"
|
||||
- "react-native-cookies (from `../node_modules/@react-native-community/cookies`)"
|
||||
- react-native-cookies (from `../node_modules/react-native-cookies/ios`)
|
||||
- react-native-document-picker (from `../node_modules/react-native-document-picker`)
|
||||
- react-native-hw-keyboard-event (from `../node_modules/react-native-hw-keyboard-event`)
|
||||
- react-native-image-picker (from `../node_modules/react-native-image-picker`)
|
||||
@@ -484,7 +484,7 @@ EXTERNAL SOURCES:
|
||||
react-native-cameraroll:
|
||||
:path: "../node_modules/@react-native-community/cameraroll"
|
||||
react-native-cookies:
|
||||
:path: "../node_modules/@react-native-community/cookies"
|
||||
:path: "../node_modules/react-native-cookies/ios"
|
||||
react-native-document-picker:
|
||||
:path: "../node_modules/react-native-document-picker"
|
||||
react-native-hw-keyboard-event:
|
||||
@@ -597,7 +597,7 @@ SPEC CHECKSUMS:
|
||||
React-jsiexecutor: 1540d1c01bb493ae3124ed83351b1b6a155db7da
|
||||
React-jsinspector: 512e560d0e985d0e8c479a54a4e5c147a9c83493
|
||||
react-native-cameraroll: 94bec91c68b94ac946c61b497b594bb38692c41b
|
||||
react-native-cookies: 6fe6a70d3b2fd8e81ddcd94d60a87727010d9b2e
|
||||
react-native-cookies: 854d59c4135c70b92a02ca4930e68e4e2eb58150
|
||||
react-native-document-picker: d694111879537cec2c258a1dcd2243d9df746824
|
||||
react-native-hw-keyboard-event: b517cefb8d5c659a38049c582de85ff43337dc53
|
||||
react-native-image-picker: 668e72d0277dc8c12ae90e835507c1eddd2e4f85
|
||||
@@ -609,7 +609,7 @@ SPEC CHECKSUMS:
|
||||
react-native-safe-area: e8230b0017d76c00de6b01e2412dcf86b127c6a3
|
||||
react-native-safe-area-context: 5f3e081f937ab7c44c179d551f332fa494cfcf8f
|
||||
react-native-video: 961749da457e73bf0b5565edfbaffc25abfb8974
|
||||
react-native-webview: 0e2881d2e3e72ad52a82445c1a116c43205fd471
|
||||
react-native-webview: 0d1c2b4e7ffb0543a74fa0512f2f8dc5fb0e49e2
|
||||
React-RCTActionSheet: f41ea8a811aac770e0cc6e0ad6b270c644ea8b7c
|
||||
React-RCTAnimation: 49ab98b1c1ff4445148b72a3d61554138565bad0
|
||||
React-RCTBlob: a332773f0ebc413a0ce85942a55b064471587a71
|
||||
@@ -630,7 +630,7 @@ SPEC CHECKSUMS:
|
||||
RNFastImage: 35ae972d6727c84ee3f5c6897e07f84d0a3445e9
|
||||
RNFileViewer: 3600e85d326dadfd0fd367eb38fcb4300b699b49
|
||||
RNGestureHandler: 8f09cd560f8d533eb36da5a6c5a843af9f056b38
|
||||
RNKeychain: bf2d7e9a0ae7a073c07770dd2aa6d11c67581733
|
||||
RNKeychain: 840f8e6f13be0576202aefcdffd26a4f54bfe7b5
|
||||
RNLocalize: fc27ee5878ce5a3af73873fb2d8e866e0d1e6d84
|
||||
RNPermissions: 8ca17fd6c822eea589fe84709d9426e05cc39c39
|
||||
RNReactNativeHapticFeedback: 22c5ecf474428766c6b148f96f2ff6155cd7225e
|
||||
|
||||
28
package-lock.json
generated
28
package-lock.json
generated
@@ -3219,14 +3219,6 @@
|
||||
"resolved": "https://registry.npmjs.org/@react-native-community/cli-types/-/cli-types-4.10.0.tgz",
|
||||
"integrity": "sha512-gU0Opspa/WYLQdmY0BKe0VLwD+SuNatypRvBP6nlyzS8/qmSaZ73047jHWYQavhfqn/WxHzBLQSwZK0a7ROfeg=="
|
||||
},
|
||||
"@react-native-community/cookies": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@react-native-community/cookies/-/cookies-3.0.0.tgz",
|
||||
"integrity": "sha512-ms/CsrBlHffTyTRhXsKSmSK6WlWScG8wklfSmkr4WeeA0j0AQ1sknTg8YNsK0bMr6Ij1uDuvX9t96vZrFGpYqQ==",
|
||||
"requires": {
|
||||
"invariant": "^2.2.4"
|
||||
}
|
||||
},
|
||||
"@react-native-community/masked-view": {
|
||||
"version": "0.1.10",
|
||||
"resolved": "https://registry.npmjs.org/@react-native-community/masked-view/-/masked-view-0.1.10.tgz",
|
||||
@@ -15816,6 +15808,13 @@
|
||||
"prop-types": "^15.7.2"
|
||||
}
|
||||
},
|
||||
"react-native-cookies": {
|
||||
"version": "github:mattermost/react-native-cookies#b35bafc388ae09c83bd875e887daf6a0755e0b40",
|
||||
"from": "github:mattermost/react-native-cookies#b35bafc388ae09c83bd875e887daf6a0755e0b40",
|
||||
"requires": {
|
||||
"invariant": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"react-native-device-info": {
|
||||
"version": "5.5.7",
|
||||
"resolved": "https://registry.npmjs.org/react-native-device-info/-/react-native-device-info-5.5.7.tgz",
|
||||
@@ -16188,18 +16187,17 @@
|
||||
}
|
||||
},
|
||||
"react-native-webview": {
|
||||
"version": "9.4.0",
|
||||
"resolved": "https://registry.npmjs.org/react-native-webview/-/react-native-webview-9.4.0.tgz",
|
||||
"integrity": "sha512-BBOFUuza0p04+7fNi7TJmB0arpDJzGxHYwTCgI4vj5n/fl7u4jbm7ETp88mf7lo9lP6C6HGLo38KnEy1aXCQkg==",
|
||||
"version": "github:mattermost/react-native-webview#b5e22940a613869d3999feac9451ee65352f4fbe",
|
||||
"from": "github:mattermost/react-native-webview#b5e22940a613869d3999feac9451ee65352f4fbe",
|
||||
"requires": {
|
||||
"escape-string-regexp": "2.0.0",
|
||||
"escape-string-regexp": "1.0.5",
|
||||
"invariant": "2.2.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"escape-string-regexp": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz",
|
||||
"integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w=="
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
|
||||
"integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ="
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
"@babel/runtime": "7.9.6",
|
||||
"@react-native-community/async-storage": "1.10.1",
|
||||
"@react-native-community/cameraroll": "1.6.2",
|
||||
"@react-native-community/cookies": "3.0.0",
|
||||
"@react-native-community/masked-view": "0.1.10",
|
||||
"@react-native-community/netinfo": "5.8.1",
|
||||
"@react-navigation/native": "5.3.2",
|
||||
@@ -39,6 +38,7 @@
|
||||
"react-native-button": "3.0.1",
|
||||
"react-native-calendars": "1.265.0",
|
||||
"react-native-circular-progress": "1.3.6",
|
||||
"react-native-cookies": "github:mattermost/react-native-cookies#b35bafc388ae09c83bd875e887daf6a0755e0b40",
|
||||
"react-native-device-info": "5.5.7",
|
||||
"react-native-document-picker": "3.4.0",
|
||||
"react-native-elements": "2.0.0",
|
||||
@@ -72,7 +72,7 @@
|
||||
"react-native-v8": "0.62.2-patch.1",
|
||||
"react-native-vector-icons": "6.6.0",
|
||||
"react-native-video": "5.0.2",
|
||||
"react-native-webview": "9.4.0",
|
||||
"react-native-webview": "github:mattermost/react-native-webview#b5e22940a613869d3999feac9451ee65352f4fbe",
|
||||
"react-native-youtube": "2.0.1",
|
||||
"react-redux": "7.2.0",
|
||||
"redux": "4.0.5",
|
||||
|
||||
@@ -1,188 +0,0 @@
|
||||
diff --git a/node_modules/react-native-webview/android/src/main/java/com/reactnativecommunity/webview/RNCWebViewManager.java b/node_modules/react-native-webview/android/src/main/java/com/reactnativecommunity/webview/RNCWebViewManager.java
|
||||
index 1995d87..33bd83c 100644
|
||||
--- a/node_modules/react-native-webview/android/src/main/java/com/reactnativecommunity/webview/RNCWebViewManager.java
|
||||
+++ b/node_modules/react-native-webview/android/src/main/java/com/reactnativecommunity/webview/RNCWebViewManager.java
|
||||
@@ -2,8 +2,12 @@ package com.reactnativecommunity.webview;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.annotation.TargetApi;
|
||||
+import android.app.Activity;
|
||||
+import android.app.AlertDialog;
|
||||
import android.app.DownloadManager;
|
||||
+import android.content.ActivityNotFoundException;
|
||||
import android.content.Context;
|
||||
+import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.ActivityInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
@@ -15,6 +19,7 @@ import android.os.Build;
|
||||
import android.os.Environment;
|
||||
import androidx.annotation.RequiresApi;
|
||||
import androidx.core.content.ContextCompat;
|
||||
+import android.text.InputType;
|
||||
import android.text.TextUtils;
|
||||
import android.view.Gravity;
|
||||
import android.view.View;
|
||||
@@ -25,7 +30,9 @@ import android.webkit.ConsoleMessage;
|
||||
import android.webkit.CookieManager;
|
||||
import android.webkit.DownloadListener;
|
||||
import android.webkit.GeolocationPermissions;
|
||||
+import android.webkit.HttpAuthHandler;
|
||||
import android.webkit.JavascriptInterface;
|
||||
+import android.widget.LinearLayout;
|
||||
import android.webkit.PermissionRequest;
|
||||
import android.webkit.URLUtil;
|
||||
import android.webkit.ValueCallback;
|
||||
@@ -35,6 +42,7 @@ import android.webkit.WebResourceResponse;
|
||||
import android.webkit.WebSettings;
|
||||
import android.webkit.WebView;
|
||||
import android.webkit.WebViewClient;
|
||||
+import android.widget.EditText;
|
||||
import android.widget.FrameLayout;
|
||||
|
||||
import com.facebook.react.views.scroll.ScrollEvent;
|
||||
@@ -720,6 +728,7 @@ public class RNCWebViewManager extends SimpleViewManager<WebView> {
|
||||
|
||||
protected static class RNCWebViewClient extends WebViewClient {
|
||||
|
||||
+ protected Activity mCurrentActivity;
|
||||
protected boolean mLastLoadFailed = false;
|
||||
protected @Nullable
|
||||
ReadableArray mUrlPrefixesForDefaultIntent;
|
||||
@@ -729,6 +738,10 @@ public class RNCWebViewManager extends SimpleViewManager<WebView> {
|
||||
ignoreErrFailedForThisURL = url;
|
||||
}
|
||||
|
||||
+ public void setCurrentActivity(Activity mCurrentActivity) {
|
||||
+ this.mCurrentActivity = mCurrentActivity;
|
||||
+ }
|
||||
+
|
||||
@Override
|
||||
public void onPageFinished(WebView webView, String url) {
|
||||
super.onPageFinished(webView, url);
|
||||
@@ -810,6 +823,49 @@ public class RNCWebViewManager extends SimpleViewManager<WebView> {
|
||||
new TopLoadingErrorEvent(webView.getId(), eventData));
|
||||
}
|
||||
|
||||
+ @Override
|
||||
+ public void onReceivedHttpAuthRequest(WebView view,
|
||||
+ final HttpAuthHandler handler, String host, String realm)
|
||||
+ {
|
||||
+ if (this.mCurrentActivity != null) {
|
||||
+ final EditText usernameInput = new EditText(this.mCurrentActivity);
|
||||
+ usernameInput.setHint("Username");
|
||||
+
|
||||
+ final EditText passwordInput = new EditText(this.mCurrentActivity);
|
||||
+ passwordInput.setHint("Password");
|
||||
+ passwordInput.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD);
|
||||
+
|
||||
+ LinearLayout layout = new LinearLayout(this.mCurrentActivity);
|
||||
+ layout.setOrientation(LinearLayout.VERTICAL);
|
||||
+ layout.addView(usernameInput);
|
||||
+ layout.addView(passwordInput);
|
||||
+
|
||||
+ AlertDialog.Builder authDialog = new AlertDialog.Builder(this.mCurrentActivity)
|
||||
+ .setTitle("Authentication Challenge")
|
||||
+ .setMessage(host + " requires user name and password ")
|
||||
+ .setView(layout)
|
||||
+ .setCancelable(false)
|
||||
+ .setPositiveButton("OK", new DialogInterface.OnClickListener() {
|
||||
+ public void onClick(DialogInterface dialogInterface, int i) {
|
||||
+ handler.proceed(usernameInput.getText().toString(), passwordInput.getText().toString());
|
||||
+ dialogInterface.dismiss();
|
||||
+ }
|
||||
+ })
|
||||
+ .setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
|
||||
+ public void onClick(DialogInterface dialogInterface, int i) {
|
||||
+ dialogInterface.dismiss();
|
||||
+ handler.cancel();
|
||||
+ }
|
||||
+ });
|
||||
+
|
||||
+ if (view != null) {
|
||||
+ authDialog.show();
|
||||
+ }
|
||||
+ } else {
|
||||
+ handler.cancel();
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
@RequiresApi(api = Build.VERSION_CODES.M)
|
||||
@Override
|
||||
public void onReceivedHttpError(
|
||||
@@ -1010,6 +1066,7 @@ public class RNCWebViewManager extends SimpleViewManager<WebView> {
|
||||
protected boolean sendContentSizeChangeEvents = false;
|
||||
private OnScrollDispatchHelper mOnScrollDispatchHelper;
|
||||
protected boolean hasScrollEvent = false;
|
||||
+ protected ReactContext reactContext;
|
||||
|
||||
/**
|
||||
* WebView must be created with an context of the current activity
|
||||
@@ -1019,6 +1076,7 @@ public class RNCWebViewManager extends SimpleViewManager<WebView> {
|
||||
*/
|
||||
public RNCWebView(ThemedReactContext reactContext) {
|
||||
super(reactContext);
|
||||
+ this.reactContext = reactContext;
|
||||
}
|
||||
|
||||
public void setIgnoreErrFailedForThisURL(String url) {
|
||||
@@ -1069,6 +1127,9 @@ public class RNCWebViewManager extends SimpleViewManager<WebView> {
|
||||
super.setWebViewClient(client);
|
||||
if (client instanceof RNCWebViewClient) {
|
||||
mRNCWebViewClient = (RNCWebViewClient) client;
|
||||
+ if (this.reactContext != null && this.reactContext.getCurrentActivity() != null && mRNCWebViewClient != null) {
|
||||
+ mRNCWebViewClient.setCurrentActivity(this.reactContext.getCurrentActivity());
|
||||
+ }
|
||||
}
|
||||
}
|
||||
|
||||
diff --git a/node_modules/react-native-webview/apple/RNCWebView.m b/node_modules/react-native-webview/apple/RNCWebView.m
|
||||
index d2f9956..4b8d0fe 100644
|
||||
--- a/node_modules/react-native-webview/apple/RNCWebView.m
|
||||
+++ b/node_modules/react-native-webview/apple/RNCWebView.m
|
||||
@@ -737,7 +737,44 @@ - (void) webView:(WKWebView *)webView
|
||||
if (webView.URL != nil) {
|
||||
host = webView.URL.host;
|
||||
}
|
||||
- if ([[challenge protectionSpace] authenticationMethod] == NSURLAuthenticationMethodClientCertificate) {
|
||||
+
|
||||
+ NSString *authenticationMethod = [[challenge protectionSpace] authenticationMethod];
|
||||
+
|
||||
+ if (authenticationMethod == NSURLAuthenticationMethodNTLM || authenticationMethod == NSURLAuthenticationMethodNegotiate) {
|
||||
+ NSString *title = @"Authentication Challenge";
|
||||
+ NSString *message = [NSString stringWithFormat:@"%@ requires user name and password", host];
|
||||
+ UIAlertController *alertController = [UIAlertController alertControllerWithTitle:title message:message preferredStyle:UIAlertControllerStyleAlert];
|
||||
+ [alertController addTextFieldWithConfigurationHandler:^(UITextField *textField) {
|
||||
+ textField.placeholder = @"User";
|
||||
+ }];
|
||||
+
|
||||
+ [alertController addTextFieldWithConfigurationHandler:^(UITextField *textField) {
|
||||
+ textField.placeholder = @"Password";
|
||||
+ textField.secureTextEntry = YES;
|
||||
+ }];
|
||||
+
|
||||
+ [alertController addAction:[UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
|
||||
+ NSString *userName = ((UITextField *)alertController.textFields[0]).text;
|
||||
+ NSString *password = ((UITextField *)alertController.textFields[1]).text;
|
||||
+ NSURLCredential *credential = [[NSURLCredential alloc] initWithUser:userName password:password persistence:NSURLCredentialPersistenceNone];
|
||||
+
|
||||
+ completionHandler(NSURLSessionAuthChallengeUseCredential, credential);
|
||||
+ }]];
|
||||
+
|
||||
+ [alertController addAction:[UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:^(UIAlertAction *action) {
|
||||
+ completionHandler(NSURLSessionAuthChallengeUseCredential, nil);
|
||||
+ }]];
|
||||
+
|
||||
+ dispatch_async(dispatch_get_main_queue(), ^{
|
||||
+ UIViewController *rootVC = UIApplication.sharedApplication.delegate.window.rootViewController;
|
||||
+
|
||||
+ while (rootVC.presentedViewController != nil) {
|
||||
+ rootVC = rootVC.presentedViewController;
|
||||
+ }
|
||||
+ [rootVC presentViewController:alertController animated:YES completion:^{}];
|
||||
+ });
|
||||
+ return;
|
||||
+ } else if (authenticationMethod == NSURLAuthenticationMethodClientCertificate) {
|
||||
completionHandler(NSURLSessionAuthChallengeUseCredential, clientAuthenticationCredential);
|
||||
return;
|
||||
}
|
||||
@@ -154,7 +154,7 @@ jest.mock('react-native-localize', () => ({
|
||||
]),
|
||||
}));
|
||||
|
||||
jest.mock('@react-native-community/cookies', () => ({
|
||||
jest.mock('react-native-cookies', () => ({
|
||||
addEventListener: jest.fn(),
|
||||
removeEventListener: jest.fn(),
|
||||
openURL: jest.fn(),
|
||||
|
||||
Reference in New Issue
Block a user