();
-+ try {
-+
-+ if (cookieManager.hasCookies()) {
-+ String response = cookieManager.getCookie(httpUrl.toString());
-+
-+ if (response != null) {
-+ String[] browserCookies = response.split(";");
-+
-+ for (String cookieStr : browserCookies) {
-+ Cookie cookie = Cookie.parse(httpUrl, cookieStr);
-+ if (cookie == null) {
-+ continue;
-+ }
-+ cookieList.add(cookie);
-+ }
-+ }
-+
-+ }
-+ return cookieList;
-+ } catch (Exception e) {
-+ e.printStackTrace();
-+ return cookieList;
-+ }
-+ }
-+}
-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 f743bbc..b520532 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
-@@ -5,6 +5,7 @@ import android.annotation.TargetApi;
- import android.app.Activity;
- import android.app.DownloadManager;
- import android.content.Context;
-+import android.content.Intent;
- import android.content.pm.ActivityInfo;
- import android.content.pm.PackageManager;
- import android.graphics.Bitmap;
-@@ -14,6 +15,7 @@ import android.net.http.SslError;
- import android.net.Uri;
- import android.os.Build;
- import android.os.Environment;
-+import android.os.Handler;
- import android.os.Message;
- import android.os.SystemClock;
- import android.text.TextUtils;
-@@ -24,12 +26,17 @@ import android.view.View;
- import android.view.ViewGroup;
- import android.view.ViewGroup.LayoutParams;
- import android.view.WindowManager;
-+import android.view.inputmethod.InputMethodManager;
- import android.webkit.ConsoleMessage;
- import android.webkit.CookieManager;
- import android.webkit.DownloadListener;
- import android.webkit.GeolocationPermissions;
- import android.webkit.JavascriptInterface;
-+import android.webkit.JsPromptResult;
-+import android.webkit.JsResult;
- import android.webkit.RenderProcessGoneDetail;
-+import android.webkit.ServiceWorkerClient;
-+import android.webkit.ServiceWorkerController;
- import android.webkit.SslErrorHandler;
- import android.webkit.PermissionRequest;
- import android.webkit.URLUtil;
-@@ -40,6 +47,7 @@ import android.webkit.WebResourceResponse;
- import android.webkit.WebSettings;
- import android.webkit.WebView;
- import android.webkit.WebViewClient;
-+import android.widget.Button;
- import android.widget.FrameLayout;
-
- import androidx.annotation.Nullable;
-@@ -88,18 +96,54 @@ import com.reactnativecommunity.webview.events.TopRenderProcessGoneEvent;
- import org.json.JSONException;
- import org.json.JSONObject;
-
-+import java.io.ByteArrayInputStream;
-+import java.io.IOException;
-+import java.io.InputStream;
- import java.io.UnsupportedEncodingException;
-+import java.lang.reflect.Field;
-+import java.net.CookieStore;
-+import java.net.HttpCookie;
-+import java.net.HttpURLConnection;
- import java.net.MalformedURLException;
-+import java.net.URI;
-+import java.net.URISyntaxException;
- import java.net.URL;
- import java.net.URLEncoder;
-+import java.nio.charset.Charset;
-+import java.nio.charset.StandardCharsets;
-+import java.nio.charset.UnsupportedCharsetException;
-+import java.text.Bidi;
- import java.util.ArrayList;
-+import java.util.Arrays;
-+import java.util.Collection;
- import java.util.Collections;
- import java.util.HashMap;
-+import java.util.HashSet;
- import java.util.List;
- import java.util.Locale;
- import java.util.Map;
-+import java.util.Objects;
-+import java.util.Set;
- import java.util.concurrent.atomic.AtomicReference;
-
-+import okhttp3.MediaType;
-+import okhttp3.OkHttpClient;
-+import okhttp3.Request;
-+import okhttp3.RequestBody;
-+import okhttp3.Response;
-+
-+import android.view.inputmethod.BaseInputConnection;
-+import android.view.inputmethod.EditorInfo;
-+import android.view.inputmethod.InputConnection;
-+
-+
-+import android.content.DialogInterface;
-+import android.os.Bundle;
-+import android.widget.Toast;
-+
-+import androidx.appcompat.app.AlertDialog;
-+import androidx.appcompat.app.AppCompatActivity;
-+
- /**
- * Manages instances of {@link WebView}
- *
-@@ -137,13 +181,19 @@ public class RNCWebViewManager extends SimpleViewManager {
- public static final int COMMAND_LOAD_URL = 7;
- public static final int COMMAND_FOCUS = 8;
-
-+ protected static final String MIME_UNKNOWN = "application/octet-stream";
-+ protected static final String HTML_ENCODING = "UTF-8";
-+ protected static final long BYTES_IN_MEGABYTE = 1000000;
-+
- // android commands
- public static final int COMMAND_CLEAR_FORM_DATA = 1000;
- public static final int COMMAND_CLEAR_CACHE = 1001;
- public static final int COMMAND_CLEAR_HISTORY = 1002;
-
- protected static final String REACT_CLASS = "RNCWebView";
-- protected static final String HTML_ENCODING = "UTF-8";
-+
-+ protected static final String HEADER_CONTENT_TYPE = "content-type";
-+
- protected static final String HTML_MIME_TYPE = "text/html";
- protected static final String JAVASCRIPT_INTERFACE = "ReactNativeWebView";
- protected static final String HTTP_METHOD_POST = "POST";
-@@ -155,13 +205,19 @@ public class RNCWebViewManager extends SimpleViewManager {
-
- protected RNCWebChromeClient mWebChromeClient = null;
- protected boolean mAllowsFullscreenVideo = false;
-- protected @Nullable String mUserAgent = null;
-- protected @Nullable String mUserAgentWithApplicationName = null;
-+ protected @Nullable String RNUserAgent = null;
-+ protected @Nullable String RNUserAgentWithApplicationName = null;
-+ protected static String deviceUserAgent;
-+
-+ protected static OkHttpClient httpClient;
-
- public RNCWebViewManager() {
-- mWebViewConfig = new WebViewConfig() {
-- public void configWebView(WebView webView) {
-- }
-+ mWebViewConfig = webView -> {
-+ httpClient = new OkHttpClient.Builder()
-+ .cookieJar(new CustomCookieJar())
-+ .followRedirects(false)
-+ .followSslRedirects(false)
-+ .build();
- };
- }
-
-@@ -182,6 +238,7 @@ public class RNCWebViewManager extends SimpleViewManager {
- @TargetApi(Build.VERSION_CODES.LOLLIPOP)
- protected WebView createViewInstance(ThemedReactContext reactContext) {
- RNCWebView webView = createRNCWebViewInstance(reactContext);
-+ deviceUserAgent = webView.getSettings().getUserAgentString();
- setupWebChromeClient(reactContext, webView);
- reactContext.addLifecycleEventListener(webView);
- mWebViewConfig.configWebView(webView);
-@@ -209,47 +266,161 @@ public class RNCWebViewManager extends SimpleViewManager {
- }
-
- webView.setDownloadListener(new DownloadListener() {
-+ protected ReactContext mReactContext;
-+
- public void onDownloadStart(String url, String userAgent, String contentDisposition, String mimetype, long contentLength) {
- webView.setIgnoreErrFailedForThisURL(url);
-+ this.mReactContext = reactContext;
-
- RNCWebViewModule module = getModule(reactContext);
--
- DownloadManager.Request request = new DownloadManager.Request(Uri.parse(url));
--
- String fileName = URLUtil.guessFileName(url, contentDisposition, mimetype);
-- String downloadMessage = "Downloading " + fileName;
-+ //Filename validation checking for files that use RTL characters and do not allow those types
-+ if(fileName == null || (fileName != null && (new Bidi(fileName, Bidi.DIRECTION_DEFAULT_LEFT_TO_RIGHT).isMixed()))) {
-+ Toast.makeText(mReactContext, "Invalid filename or type", Toast.LENGTH_SHORT).show();
-+ } else {
-+ AlertDialog.Builder builder = new AlertDialog.Builder(mReactContext);
-+ builder.setMessage("Do you want to download \n" + fileName + "?");
-+ builder.setCancelable(false);
-+ builder.setPositiveButton("Download", new DialogInterface.OnClickListener() {
-+ public void onClick(DialogInterface dialog, int which) {
-+ String downloadMessage = "Downloading " + fileName;
-+
-+ //Attempt to add cookie, if it exists
-+ URL urlObj = null;
-+ try {
-+ urlObj = new URL(url);
-+ String baseUrl = urlObj.getProtocol() + "://" + urlObj.getHost();
-+ String cookie = CookieManager.getInstance().getCookie(baseUrl);
-+ request.addRequestHeader("Cookie", cookie);
-+ } catch (MalformedURLException e) {
-+ System.out.println("Error getting cookie for DownloadManager: " + e.toString());
-+ e.printStackTrace();
-+ }
-
-- //Attempt to add cookie, if it exists
-- URL urlObj = null;
-- try {
-- urlObj = new URL(url);
-- String baseUrl = urlObj.getProtocol() + "://" + urlObj.getHost();
-- String cookie = CookieManager.getInstance().getCookie(baseUrl);
-- request.addRequestHeader("Cookie", cookie);
-- } catch (MalformedURLException e) {
-- System.out.println("Error getting cookie for DownloadManager: " + e.toString());
-- e.printStackTrace();
-+ //Finish setting up request
-+ request.addRequestHeader("User-Agent", userAgent);
-+ request.setTitle(fileName);
-+ request.setDescription(downloadMessage);
-+ request.allowScanningByMediaScanner();
-+ request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
-+ request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, fileName);
-+ module.setDownloadRequest(request);
-+ if (module.grantFileDownloaderPermissions()) {
-+ module.downloadFile();
-+ }
-+ }
-+ });
-+ builder.setNegativeButton("Cancel", (DialogInterface.OnClickListener) (dialog, which) -> {
-+ return;
-+ });
-+ AlertDialog alertDialog = builder.create();
-+ alertDialog.show();
- }
-+ }
-+ });
-
-- //Finish setting up request
-- request.addRequestHeader("User-Agent", userAgent);
-- request.setTitle(fileName);
-- request.setDescription(downloadMessage);
-- request.allowScanningByMediaScanner();
-- request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
-- request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, fileName);
-+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
-+ ServiceWorkerController swController = ServiceWorkerController.getInstance();
-+ swController.setServiceWorkerClient(new ServiceWorkerClient() {
-+ @Override
-+ public WebResourceResponse shouldInterceptRequest(WebResourceRequest request) {
-+ String method = request.getMethod();
-
-- module.setDownloadRequest(request);
-+ if (method.equals("GET")) {
-+ WebResourceResponse response = RNCWebViewManager.this.shouldInterceptRequest(request, false, webView);
-+ if (response != null) {
-+ return response;
-+ }
-+ }
-
-- if (module.grantFileDownloaderPermissions()) {
-- module.downloadFile();
-+ return super.shouldInterceptRequest(request);
- }
-- }
-- });
-+ });
-+ }
-
- return webView;
- }
-
-+ private Boolean urlStringLooksInvalid(String urlString) {
-+ return urlString == null ||
-+ urlString.trim().equals("") ||
-+ !(urlString.startsWith("http") && !urlString.startsWith("www")) ||
-+ urlString.contains("|");
-+ }
-+
-+ public static Boolean responseRequiresJSInjection(Response response) {
-+ if (response.isRedirect()) {
-+ return false;
-+ }
-+ final String contentTypeAndCharset = response.header(HEADER_CONTENT_TYPE, MIME_UNKNOWN);
-+ final int responseCode = response.code();
-+
-+ boolean contentTypeIsHtml = contentTypeAndCharset.startsWith(HTML_MIME_TYPE);
-+ boolean responseCodeIsInjectible = responseCode == 200;
-+ String responseBody = "";
-+
-+ if (contentTypeIsHtml && responseCodeIsInjectible) {
-+ try {
-+ assert response.body() != null;
-+ responseBody = response.peekBody(BYTES_IN_MEGABYTE).string();
-+ } catch (IOException e) {
-+ e.printStackTrace();
-+ return false;
-+ }
-+
-+
-+ boolean responseBodyContainsHTMLLikeString = responseBody.matches("[\\S\\s]*<[a-z]+[\\S\\s]*>[\\S\\s]*");
-+ return responseBodyContainsHTMLLikeString;
-+ } else {
-+ return false;
-+ }
-+ }
-+
-+ public WebResourceResponse shouldInterceptRequest(WebResourceRequest request, Boolean onlyMainFrame, RNCWebView webView) {
-+ Uri url = request.getUrl();
-+ String urlStr = url.toString();
-+
-+ if (onlyMainFrame && !request.isForMainFrame() ||
-+ urlStringLooksInvalid(urlStr)) {
-+ return null;
-+ }
-+
-+ String _userAgent;
-+
-+ if (RNUserAgent != null) {
-+ _userAgent = RNUserAgent;
-+ } else {
-+ _userAgent = deviceUserAgent;
-+ }
-+
-+ try {
-+ Request req = new Request.Builder()
-+ .url(urlStr)
-+ .header("User-Agent", _userAgent)
-+ .build();
-+
-+ Response response = httpClient.newCall(req).execute();
-+
-+ if (!responseRequiresJSInjection(response)) {
-+ return null;
-+ }
-+
-+ InputStream is = response.body().byteStream();
-+ MediaType contentType = response.body().contentType();
-+ Charset charset = contentType != null ? contentType.charset(StandardCharsets.UTF_8) : StandardCharsets.UTF_8;
-+
-+ RNCWebView reactWebView = (RNCWebView) webView;
-+ if (response.code() == HttpURLConnection.HTTP_OK) {
-+ is = new InputStreamWithInjectedJS(is, reactWebView.injectedJSBeforeContentLoaded, charset);
-+ }
-+
-+ return new WebResourceResponse("text/html", charset.name(), is);
-+ } catch (IOException e) {
-+ return null;
-+ }
-+ }
-+
- @ReactProp(name = "javaScriptEnabled")
- public void setJavaScriptEnabled(WebView view, boolean enabled) {
- view.getSettings().setJavaScriptEnabled(enabled);
-@@ -285,13 +456,10 @@ public class RNCWebViewManager extends SimpleViewManager {
- if (enabled) {
- Context ctx = view.getContext();
- if (ctx != null) {
-- view.getSettings().setAppCachePath(ctx.getCacheDir().getAbsolutePath());
- view.getSettings().setCacheMode(WebSettings.LOAD_DEFAULT);
-- view.getSettings().setAppCacheEnabled(true);
- }
- } else {
- view.getSettings().setCacheMode(WebSettings.LOAD_NO_CACHE);
-- view.getSettings().setAppCacheEnabled(false);
- }
- }
-
-@@ -327,12 +495,12 @@ public class RNCWebViewManager extends SimpleViewManager {
- public void setLayerType(WebView view, String layerTypeString) {
- int layerType = View.LAYER_TYPE_NONE;
- switch (layerTypeString) {
-- case "hardware":
-- layerType = View.LAYER_TYPE_HARDWARE;
-- break;
-- case "software":
-- layerType = View.LAYER_TYPE_SOFTWARE;
-- break;
-+ case "hardware":
-+ layerType = View.LAYER_TYPE_HARDWARE;
-+ break;
-+ case "software":
-+ layerType = View.LAYER_TYPE_SOFTWARE;
-+ break;
- }
- view.setLayerType(layerType, null);
- }
-@@ -387,9 +555,9 @@ public class RNCWebViewManager extends SimpleViewManager {
- @ReactProp(name = "userAgent")
- public void setUserAgent(WebView view, @Nullable String userAgent) {
- if (userAgent != null) {
-- mUserAgent = userAgent;
-+ RNUserAgent = userAgent;
- } else {
-- mUserAgent = null;
-+ RNUserAgent = null;
- }
- this.setUserAgentString(view);
- }
-@@ -399,19 +567,19 @@ public class RNCWebViewManager extends SimpleViewManager {
- if(applicationName != null) {
- if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
- String defaultUserAgent = WebSettings.getDefaultUserAgent(view.getContext());
-- mUserAgentWithApplicationName = defaultUserAgent + " " + applicationName;
-+ RNUserAgentWithApplicationName = defaultUserAgent + " " + applicationName;
- }
- } else {
-- mUserAgentWithApplicationName = null;
-+ RNUserAgentWithApplicationName = null;
- }
- this.setUserAgentString(view);
- }
-
- protected void setUserAgentString(WebView view) {
-- if(mUserAgent != null) {
-- view.getSettings().setUserAgentString(mUserAgent);
-- } else if(mUserAgentWithApplicationName != null) {
-- view.getSettings().setUserAgentString(mUserAgentWithApplicationName);
-+ if(RNUserAgent != null) {
-+ view.getSettings().setUserAgentString(RNUserAgent);
-+ } else if(RNUserAgentWithApplicationName != null) {
-+ view.getSettings().setUserAgentString(RNUserAgentWithApplicationName);
- } else if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
- // handle unsets of `userAgent` prop as long as device is >= API 17
- view.getSettings().setUserAgentString(WebSettings.getDefaultUserAgent(view.getContext()));
-@@ -490,7 +658,6 @@ public class RNCWebViewManager extends SimpleViewManager {
-
- // Disable caching
- view.getSettings().setCacheMode(WebSettings.LOAD_NO_CACHE);
-- view.getSettings().setAppCacheEnabled(false);
- view.clearHistory();
- view.clearCache(true);
-
-@@ -842,13 +1009,116 @@ public class RNCWebViewManager extends SimpleViewManager {
- }
- }
-
-- protected static class RNCWebViewClient extends WebViewClient {
-+ public static class InputStreamWithInjectedJS extends InputStream {
-+ private InputStream pageIS;
-+ private InputStream scriptIS;
-+ private Charset charset;
-+ private static final String REACT_CLASS = "InputStreamWithInjectedJS";
-+ private static Map script = new HashMap<>();
-+
-+ private boolean hasJS = false;
-+ private boolean headWasFound = false;
-+ private boolean scriptWasInjected = false;
-+
-+ private int lowercaseD = 100;
-+ private int closingTag = 62;
-+ private boolean hasClosingHead = false;
-+
-+ private StringBuffer contentBuffer = new StringBuffer();
-+
-+ @SuppressLint("LongLogTag")
-+ private static Charset getCharset(String charsetName) {
-+ Charset cs = StandardCharsets.UTF_8;
-+ try {
-+ if (charsetName != null) {
-+ cs = Charset.forName(charsetName);
-+ }
-+ } catch (UnsupportedCharsetException e) {
-+ Log.d(REACT_CLASS, "wrong charset: " + charsetName);
-+ }
-+
-+ return cs;
-+ }
-+
-+ private static InputStream getScript(Charset charset) {
-+ String js = script.get(charset);
-+ if (js == null) {
-+ String defaultJs = script.get(StandardCharsets.UTF_8);
-+ js = new String(defaultJs.getBytes(StandardCharsets.UTF_8), charset);
-+ script.put(charset, js);
-+ }
-+
-+ return new ByteArrayInputStream(js.getBytes(charset));
-+ }
-+
-+ InputStreamWithInjectedJS(InputStream is, String js, Charset charset) {
-+ if (js == null) {
-+ this.pageIS = is;
-+ } else {
-+ this.hasJS = true;
-+ this.charset = charset;
-+ Charset cs = StandardCharsets.UTF_8;
-+ String jsScript = "";
-+ script.put(cs, jsScript);
-+ this.pageIS = is;
-+ }
-+ }
-+
-+ @Override
-+ public int read() throws IOException {
-+ if (scriptWasInjected || !hasJS) {
-+ return pageIS.read();
-+ }
-+
-+ if (!scriptWasInjected && headWasFound) {
-+ int nextByte;
-+ if (!hasClosingHead) {
-+ nextByte = pageIS.read();
-+ if (nextByte != closingTag) {
-+ return nextByte;
-+ }
-+ hasClosingHead = true;
-+ return nextByte;
-+ }
-+ nextByte = scriptIS.read();
-+ if (nextByte == -1) {
-+ scriptIS.close();
-+ scriptWasInjected = true;
-+ return pageIS.read();
-+ } else {
-+ return nextByte;
-+ }
-+ }
-+
-+ if (!headWasFound) {
-+ int nextByte = pageIS.read();
-+ contentBuffer.append((char) nextByte);
-+ int bufferLength = contentBuffer.length();
-+ if (nextByte == lowercaseD && bufferLength >= 5) {
-+ if (contentBuffer.substring(bufferLength - 5).equals(" {
- @Override
- public void onPageFinished(WebView webView, String url) {
- super.onPageFinished(webView, url);
-+ // Only return the URL that the web view is currently showing.
-+ String visibleUrl = webView.getUrl();
-+ Boolean isFinishedLoading = url.equals(visibleUrl);
-
-- if (!mLastLoadFailed) {
-+ if (!mLastLoadFailed && isFinishedLoading) {
-+ if(Objects.nonNull(mWebChromeClient)) mWebChromeClient.blockJsDuringLoading = false;
- RNCWebView reactWebView = (RNCWebView) webView;
-
- reactWebView.callInjectedJavaScript();
-
-- emitFinishEvent(webView, url);
-+ emitFinishEvent(webView, visibleUrl);
- }
- }
-
- @Override
- public void onPageStarted(WebView webView, String url, Bitmap favicon) {
- super.onPageStarted(webView, url, favicon);
-+ if(Objects.nonNull(mWebChromeClient)) mWebChromeClient.blockJsDuringLoading = true;
- mLastLoadFailed = false;
-
-- RNCWebView reactWebView = (RNCWebView) webView;
-- reactWebView.callInjectedJavaScriptBeforeContentLoaded();
--
- ((RNCWebView) webView).dispatchEvent(
- webView,
- new TopLoadingStartEvent(
-@@ -882,6 +1154,20 @@ public class RNCWebViewManager extends SimpleViewManager {
- createWebViewEvent(webView, url)));
- }
-
-+ @Override
-+ public WebResourceResponse shouldInterceptRequest(WebView webView, WebResourceRequest request) {
-+ String method = request.getMethod();
-+
-+ if (method.equals("GET")) {
-+ WebResourceResponse response = RNCWebViewManager.this.shouldInterceptRequest(request, true, (RNCWebView)webView);
-+ if (response != null) {
-+ return response;
-+ }
-+ }
-+
-+ return super.shouldInterceptRequest(webView, request);
-+ }
-+
- @Override
- public boolean shouldOverrideUrlLoading(WebView view, String url) {
- final RNCWebView rncWebView = (RNCWebView) view;
-@@ -891,7 +1177,6 @@ public class RNCWebViewManager extends SimpleViewManager {
- final Pair> lock = RNCWebViewModule.shouldOverrideUrlLoadingLock.getNewLock();
- final int lockIdentifier = lock.first;
- final AtomicReference lockObject = lock.second;
--
- final WritableMap event = createWebViewEvent(view, url);
- event.putInt("lockIdentifier", lockIdentifier);
- rncWebView.sendDirectMessage("onShouldStartLoadWithRequest", event);
-@@ -919,6 +1204,17 @@ public class RNCWebViewManager extends SimpleViewManager {
- RNCWebViewModule.shouldOverrideUrlLoadingLock.removeLock(lockIdentifier);
-
- return shouldOverride;
-+ } else if (url != null && Arrays.asList(DEEPLINK_ALLOW_LIST).contains(url)) {
-+ // This case is used to support deeplinking within the webview. We are limiting this but
-+ // if more links are to be supported we should consider a more scaleable solution. That is
-+ // secure and scaleable.
-+ Intent intent = new Intent(Intent.ACTION_VIEW);
-+ intent.setData(Uri.parse(url));
-+ if(intent.resolveActivity(view.getContext().getPackageManager()) != null) {
-+ view.getContext().startActivity(intent);
-+ return true;
-+ } else
-+ return false;
- } else {
- FLog.w(TAG, "Couldn't use blocking synchronous call for onShouldStartLoadWithRequest due to debugging or missing Catalyst instance, falling back to old event-and-load.");
- progressChangedFilter.setWaitingForCommandLoadUrl(true);
-@@ -934,67 +1230,86 @@ public class RNCWebViewManager extends SimpleViewManager {
- @TargetApi(Build.VERSION_CODES.N)
- @Override
- public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
-+ if(Objects.nonNull(mWebChromeClient)) mWebChromeClient.blockJsDuringLoading = true;
-+
-+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
-+
-+ /*
-+ * In order to follow redirects properly, we return null in interceptRequest().
-+ * Doing this breaks the web3 injection on the resulting page, so we have to reload to
-+ * make sure web3 is available.
-+ * */
-+
-+ if (request.isForMainFrame() && request.isRedirect()) {
-+ view.loadUrl(request.getUrl().toString());
-+ return true;
-+ }
-+ }
-+
- final String url = request.getUrl().toString();
-+
- return this.shouldOverrideUrlLoading(view, url);
- }
-
-+
-+
- @Override
- public void onReceivedSslError(final WebView webView, final SslErrorHandler handler, final SslError error) {
-- // onReceivedSslError is called for most requests, per Android docs: https://developer.android.com/reference/android/webkit/WebViewClient#onReceivedSslError(android.webkit.WebView,%2520android.webkit.SslErrorHandler,%2520android.net.http.SslError)
-- // WebView.getUrl() will return the top-level window URL.
-- // If a top-level navigation triggers this error handler, the top-level URL will be the failing URL (not the URL of the currently-rendered page).
-- // This is desired behavior. We later use these values to determine whether the request is a top-level navigation or a subresource request.
-- String topWindowUrl = webView.getUrl();
-- String failingUrl = error.getUrl();
--
-- // Cancel request after obtaining top-level URL.
-- // If request is cancelled before obtaining top-level URL, undesired behavior may occur.
-- // Undesired behavior: Return value of WebView.getUrl() may be the current URL instead of the failing URL.
-- handler.cancel();
--
-- if (!topWindowUrl.equalsIgnoreCase(failingUrl)) {
-- // If error is not due to top-level navigation, then do not call onReceivedError()
-- Log.w("RNCWebViewManager", "Resource blocked from loading due to SSL error. Blocked URL: "+failingUrl);
-- return;
-- }
-+ // onReceivedSslError is called for most requests, per Android docs: https://developer.android.com/reference/android/webkit/WebViewClient#onReceivedSslError(android.webkit.WebView,%2520android.webkit.SslErrorHandler,%2520android.net.http.SslError)
-+ // WebView.getUrl() will return the top-level window URL.
-+ // If a top-level navigation triggers this error handler, the top-level URL will be the failing URL (not the URL of the currently-rendered page).
-+ // This is desired behavior. We later use these values to determine whether the request is a top-level navigation or a subresource request.
-+ String topWindowUrl = webView.getUrl();
-+ String failingUrl = error.getUrl();
-+
-+ // Cancel request after obtaining top-level URL.
-+ // If request is cancelled before obtaining top-level URL, undesired behavior may occur.
-+ // Undesired behavior: Return value of WebView.getUrl() may be the current URL instead of the failing URL.
-+ handler.cancel();
-+
-+ if (!topWindowUrl.equalsIgnoreCase(failingUrl)) {
-+ // If error is not due to top-level navigation, then do not call onReceivedError()
-+ Log.w("RNCWebViewManager", "Resource blocked from loading due to SSL error. Blocked URL: "+failingUrl);
-+ return;
-+ }
-
-- int code = error.getPrimaryError();
-- String description = "";
-- String descriptionPrefix = "SSL error: ";
--
-- // https://developer.android.com/reference/android/net/http/SslError.html
-- switch (code) {
-- case SslError.SSL_DATE_INVALID:
-- description = "The date of the certificate is invalid";
-- break;
-- case SslError.SSL_EXPIRED:
-- description = "The certificate has expired";
-- break;
-- case SslError.SSL_IDMISMATCH:
-- description = "Hostname mismatch";
-- break;
-- case SslError.SSL_INVALID:
-- description = "A generic error occurred";
-- break;
-- case SslError.SSL_NOTYETVALID:
-- description = "The certificate is not yet valid";
-- break;
-- case SslError.SSL_UNTRUSTED:
-- description = "The certificate authority is not trusted";
-- break;
-- default:
-- description = "Unknown SSL Error";
-- break;
-- }
-+ int code = error.getPrimaryError();
-+ String description = "";
-+ String descriptionPrefix = "SSL error: ";
-
-- description = descriptionPrefix + description;
-+ // https://developer.android.com/reference/android/net/http/SslError.html
-+ switch (code) {
-+ case SslError.SSL_DATE_INVALID:
-+ description = "The date of the certificate is invalid";
-+ break;
-+ case SslError.SSL_EXPIRED:
-+ description = "The certificate has expired";
-+ break;
-+ case SslError.SSL_IDMISMATCH:
-+ description = "Hostname mismatch";
-+ break;
-+ case SslError.SSL_INVALID:
-+ description = "A generic error occurred";
-+ break;
-+ case SslError.SSL_NOTYETVALID:
-+ description = "The certificate is not yet valid";
-+ break;
-+ case SslError.SSL_UNTRUSTED:
-+ description = "The certificate authority is not trusted";
-+ break;
-+ default:
-+ description = "Unknown SSL Error";
-+ break;
-+ }
-
-- this.onReceivedError(
-- webView,
-- code,
-- description,
-- failingUrl
-- );
-+ description = descriptionPrefix + description;
-+
-+ this.onReceivedError(
-+ webView,
-+ code,
-+ description,
-+ failingUrl
-+ );
- }
-
- @Override
-@@ -1005,9 +1320,9 @@ public class RNCWebViewManager extends SimpleViewManager {
- String failingUrl) {
-
- if (ignoreErrFailedForThisURL != null
-- && failingUrl.equals(ignoreErrFailedForThisURL)
-- && errorCode == -1
-- && description.equals("net::ERR_FAILED")) {
-+ && failingUrl.equals(ignoreErrFailedForThisURL)
-+ && errorCode == -1
-+ && description.equals("net::ERR_FAILED")) {
-
- // This is a workaround for a bug in the WebView.
- // See these chromium issues for more context:
-@@ -1056,36 +1371,36 @@ public class RNCWebViewManager extends SimpleViewManager {
- @TargetApi(Build.VERSION_CODES.O)
- @Override
- public boolean onRenderProcessGone(WebView webView, RenderProcessGoneDetail detail) {
-- // WebViewClient.onRenderProcessGone was added in O.
-- if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
-- return false;
-- }
-- super.onRenderProcessGone(webView, detail);
-+ // WebViewClient.onRenderProcessGone was added in O.
-+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
-+ return false;
-+ }
-+ super.onRenderProcessGone(webView, detail);
-
-- if(detail.didCrash()){
-- Log.e("RNCWebViewManager", "The WebView rendering process crashed.");
-- }
-- else{
-- Log.w("RNCWebViewManager", "The WebView rendering process was killed by the system.");
-- }
-+ if(detail.didCrash()){
-+ Log.e("RNCWebViewManager", "The WebView rendering process crashed.");
-+ }
-+ else{
-+ Log.w("RNCWebViewManager", "The WebView rendering process was killed by the system.");
-+ }
-
-- // if webView is null, we cannot return any event
-- // since the view is already dead/disposed
-- // still prevent the app crash by returning true.
-- if(webView == null){
-- return true;
-- }
-+ // if webView is null, we cannot return any event
-+ // since the view is already dead/disposed
-+ // still prevent the app crash by returning true.
-+ if(webView == null){
-+ return true;
-+ }
-
-- WritableMap event = createWebViewEvent(webView, webView.getUrl());
-- event.putBoolean("didCrash", detail.didCrash());
-+ WritableMap event = createWebViewEvent(webView, webView.getUrl());
-+ event.putBoolean("didCrash", detail.didCrash());
-
- ((RNCWebView) webView).dispatchEvent(
-- webView,
-- new TopRenderProcessGoneEvent(webView.getId(), event)
-- );
-+ webView,
-+ new TopRenderProcessGoneEvent(webView.getId(), event)
-+ );
-
-- // returning false would crash the app.
-- return true;
-+ // returning false would crash the app.
-+ return true;
- }
-
- protected void emitFinishEvent(WebView webView, String url) {
-@@ -1138,6 +1453,7 @@ public class RNCWebViewManager extends SimpleViewManager {
-
- protected View mVideoView;
- protected WebChromeClient.CustomViewCallback mCustomViewCallback;
-+ protected boolean blockJsDuringLoading = true; //This boolean block JS prompts and alerts from displaying during loading
-
- /*
- * - Permissions -
-@@ -1211,8 +1527,8 @@ public class RNCWebViewManager extends SimpleViewManager {
- @TargetApi(Build.VERSION_CODES.LOLLIPOP)
- @Override
- public void onPermissionRequest(final PermissionRequest request) {
--
- grantedPermissions = new ArrayList<>();
-+ ArrayList requestPermissionIdentifiers = new ArrayList<>();
-
- ArrayList requestedAndroidPermissions = new ArrayList<>();
- for (String requestedResource : request.getResources()) {
-@@ -1220,36 +1536,74 @@ public class RNCWebViewManager extends SimpleViewManager {
-
- if (requestedResource.equals(PermissionRequest.RESOURCE_AUDIO_CAPTURE)) {
- androidPermission = Manifest.permission.RECORD_AUDIO;
-+ requestPermissionIdentifiers.add("microphone");
- } else if (requestedResource.equals(PermissionRequest.RESOURCE_VIDEO_CAPTURE)) {
- androidPermission = Manifest.permission.CAMERA;
-+ requestPermissionIdentifiers.add("camera");
- } else if(requestedResource.equals(PermissionRequest.RESOURCE_PROTECTED_MEDIA_ID)) {
- androidPermission = PermissionRequest.RESOURCE_PROTECTED_MEDIA_ID;
- }
- // TODO: RESOURCE_MIDI_SYSEX, RESOURCE_PROTECTED_MEDIA_ID.
--
- if (androidPermission != null) {
-- if (ContextCompat.checkSelfPermission(mReactContext, androidPermission) == PackageManager.PERMISSION_GRANTED) {
-- grantedPermissions.add(requestedResource);
-- } else {
-- requestedAndroidPermissions.add(androidPermission);
-- }
-+ if (ContextCompat.checkSelfPermission(mReactContext, androidPermission) == PackageManager.PERMISSION_GRANTED) {
-+ grantedPermissions.add(requestedResource);
-+ } else {
-+ requestedAndroidPermissions.add(androidPermission);
-+ }
- }
- }
-
-- // If all the permissions are already granted, send the response to the WebView synchronously
-- if (requestedAndroidPermissions.isEmpty()) {
-- request.grant(grantedPermissions.toArray(new String[0]));
-- grantedPermissions = null;
-- return;
-- }
--
-- // Otherwise, ask to Android System for native permissions asynchronously
-+ if (!requestedAndroidPermissions.isEmpty()) {
-+ // Show the dialog and request the permissions
-+ AlertDialog.Builder builder = new AlertDialog.Builder(mReactContext);
-+ String permissionsIdentifiers = TextUtils.join(" and ", requestPermissionIdentifiers);
-+ builder.setMessage("The app needs access to your " + permissionsIdentifiers + ". Allow?");
-+ builder.setCancelable(false);
-+ builder.setPositiveButton("Allow", new DialogInterface.OnClickListener() {
-+ public void onClick(DialogInterface dialog, int which) {
-+ permissionRequest = request;
-+ requestPermissions(requestedAndroidPermissions);
-+ }
-+ });
-+ builder.setNegativeButton("Don't allow", (DialogInterface.OnClickListener) (dialog, which) -> {
-+ request.deny();
-+ });
-
-- this.permissionRequest = request;
-+ AlertDialog alertDialog = builder.create();
-+ alertDialog.show();
-+ // Delay making `allow` clickable for 500ms
-+ Button posButton = alertDialog.getButton(AlertDialog.BUTTON_POSITIVE);
-+ posButton.setEnabled(false);
-+ this.runDelayed(() -> posButton.setEnabled(true), 500);
-+ } else if (!grantedPermissions.isEmpty()) {
-+ // You need to show to the user that the website is requesting permissions
-+ // If that happens and the permissions are already granted you need to ask again
-+ AlertDialog.Builder builder = new AlertDialog.Builder(mReactContext);
-+ String permissionsIdentifiers = TextUtils.join(" and ", requestPermissionIdentifiers);
-+ builder.setMessage("The app needs access to your " + permissionsIdentifiers + ". Allow?");
-+ builder.setCancelable(false);
-+ builder.setPositiveButton("Allow", new DialogInterface.OnClickListener() {
-+ public void onClick(DialogInterface dialog, int which) {
-+ request.grant(grantedPermissions.toArray(new String[0]));
-+ }
-+ });
-+ builder.setNegativeButton("Don't allow", (DialogInterface.OnClickListener) (dialog, which) -> {
-+ request.deny();
-+ });
-
-- requestPermissions(requestedAndroidPermissions);
-+ AlertDialog alertDialog = builder.create();
-+ alertDialog.show();
-+ // Delay making `allow` clickable for 500ms
-+ Button posButton = alertDialog.getButton(AlertDialog.BUTTON_POSITIVE);
-+ posButton.setEnabled(false);
-+ this.runDelayed(() -> posButton.setEnabled(true), 500);
-+ }
- }
-
-+ private void runDelayed(Runnable function, long delayMillis) {
-+ Handler handler = new Handler();
-+ handler.postDelayed(function, delayMillis);
-+ }
-
- @Override
- public void onGeolocationPermissionsShowPrompt(String origin, GeolocationPermissions.Callback callback) {
-@@ -1266,7 +1620,22 @@ public class RNCWebViewManager extends SimpleViewManager {
- requestPermissions(Collections.singletonList(Manifest.permission.ACCESS_FINE_LOCATION));
-
- } else {
-- callback.invoke(origin, true, false);
-+ String alertMessage = String.format("Allow this app to use your location?");
-+ AlertDialog.Builder builder = new AlertDialog.Builder(this.mWebView.getContext());
-+ builder.setMessage(alertMessage);
-+ builder.setCancelable(false);
-+ builder.setPositiveButton("Allow", (dialog, which) -> {
-+ callback.invoke(origin, true, false);
-+ });
-+ builder.setNegativeButton("Don't allow", (dialog, which) -> {
-+ callback.invoke(origin, false, false);
-+ });
-+ AlertDialog alertDialog = builder.create();
-+ alertDialog.show();
-+ //Delay making `allow` clickable for 500ms to avoid unwanted presses.
-+ Button posButton = alertDialog.getButton(AlertDialog.BUTTON_POSITIVE);
-+ posButton.setEnabled(false);
-+ this.runDelayed(() -> posButton.setEnabled(true), 500);
- }
- }
-
-@@ -1402,6 +1771,15 @@ public class RNCWebViewManager extends SimpleViewManager {
- }
- }
-
-+ @Override
-+ public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) {
-+ if(blockJsDuringLoading) {
-+ result.cancel();
-+ return true;
-+ } else
-+ return super.onJsPrompt(view, url, message, defaultValue, result);
-+ }
-+
- @Override
- public void onHostPause() { }
-
-@@ -1447,6 +1825,13 @@ public class RNCWebViewManager extends SimpleViewManager {
- protected boolean nestedScrollEnabled = false;
- protected ProgressChangedFilter progressChangedFilter;
-
-+ /**
-+ * Taken from EditorInfo.IME_FLAG_NO_PERSONALIZED_LEARNING We can't use that
-+ * value directly as it was only added on Oreo, but we can apply the value
-+ * anyway.
-+ */
-+ private static final int IME_FLAG_NO_PERSONALIZED_LEARNING = 0x1000000;
-+
- /**
- * WebView must be created with an context of the current activity
- *
-@@ -1475,6 +1860,42 @@ public class RNCWebViewManager extends SimpleViewManager {
- this.nestedScrollEnabled = nestedScrollEnabled;
- }
-
-+ @Override
-+ public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
-+ InputConnection inputConnection;
-+ if (!usingGoogleKeyboard()) {
-+ inputConnection = super.onCreateInputConnection(outAttrs);
-+ } else {
-+ inputConnection = new BaseInputConnection(this, false);
-+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
-+ outAttrs.imeOptions = EditorInfo.IME_FLAG_NO_PERSONALIZED_LEARNING;
-+ } else {
-+ // Cover OS versions below Oreo
-+ outAttrs.imeOptions = IME_FLAG_NO_PERSONALIZED_LEARNING;
-+ }
-+ }
-+
-+ return inputConnection;
-+ }
-+
-+ public boolean usingGoogleKeyboard() {
-+ final InputMethodManager richImm =
-+ (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
-+
-+ boolean isKeyboard = false;
-+
-+ final Field field;
-+ try {
-+ field = richImm.getClass().getDeclaredField("mCurId");
-+ field.setAccessible(true);
-+ Object value = field.get(richImm);
-+ isKeyboard = Objects.equals(value, "com.google.android.inputmethod.latin/com.android.inputmethod.latin.LatinIME");
-+ } catch (IllegalAccessException | NoSuchFieldException e) {
-+ return false;
-+ }
-+ return isKeyboard;
-+ }
-+
- @Override
- public void onHostResume() {
- // do nothing
-@@ -1533,6 +1954,8 @@ public class RNCWebViewManager extends SimpleViewManager {
- }
- }
-
-+
-+
- public @Nullable
- RNCWebViewClient getRNCWebViewClient() {
- return mRNCWebViewClient;
-@@ -1609,8 +2032,8 @@ public class RNCWebViewManager extends SimpleViewManager {
-
- public void callInjectedJavaScriptBeforeContentLoaded() {
- if (getSettings().getJavaScriptEnabled() &&
-- injectedJSBeforeContentLoaded != null &&
-- !TextUtils.isEmpty(injectedJSBeforeContentLoaded)) {
-+ injectedJSBeforeContentLoaded != null &&
-+ !TextUtils.isEmpty(injectedJSBeforeContentLoaded)) {
- evaluateJavascriptWithFallback("(function() {\n" + injectedJSBeforeContentLoaded + ";\n})();");
- }
- }
-@@ -1672,16 +2095,16 @@ public class RNCWebViewManager extends SimpleViewManager {
-
- if (mOnScrollDispatchHelper.onScrollChanged(x, y)) {
- ScrollEvent event = ScrollEvent.obtain(
-- this.getId(),
-- ScrollEventType.SCROLL,
-- x,
-- y,
-- mOnScrollDispatchHelper.getXFlingVelocity(),
-- mOnScrollDispatchHelper.getYFlingVelocity(),
-- this.computeHorizontalScrollRange(),
-- this.computeVerticalScrollRange(),
-- this.getWidth(),
-- this.getHeight());
-+ this.getId(),
-+ ScrollEventType.SCROLL,
-+ x,
-+ y,
-+ mOnScrollDispatchHelper.getXFlingVelocity(),
-+ mOnScrollDispatchHelper.getYFlingVelocity(),
-+ this.computeHorizontalScrollRange(),
-+ this.computeVerticalScrollRange(),
-+ this.getWidth(),
-+ this.getHeight());
-
- dispatchEvent(this, event);
- }
-diff --git a/node_modules/react-native-webview/android/src/main/java/com/reactnativecommunity/webview/Worker.java b/node_modules/react-native-webview/android/src/main/java/com/reactnativecommunity/webview/Worker.java
-new file mode 100644
-index 0000000..b9581ac
---- /dev/null
-+++ b/node_modules/react-native-webview/android/src/main/java/com/reactnativecommunity/webview/Worker.java
-@@ -0,0 +1,21 @@
-+package com.reactnativecommunity.webview;
-+
-+import android.os.Handler;
-+import android.os.HandlerThread;
-+
-+class Worker extends HandlerThread {
-+ private Handler handler;
-+
-+ private static final String TAG = "WORKER";
-+
-+ public Worker() {
-+ super(TAG);
-+ start();
-+ handler = new Handler(getLooper());
-+ }
-+
-+ public Worker execute(Runnable task) {
-+ handler.post(task);
-+ return this;
-+ }
-+}
-\ No newline at end of file
-diff --git a/node_modules/react-native-webview/apple/RNCWebView.m b/node_modules/react-native-webview/apple/RNCWebView.m
-index 28c078a..9bb5368 100644
---- a/node_modules/react-native-webview/apple/RNCWebView.m
-+++ b/node_modules/react-native-webview/apple/RNCWebView.m
-@@ -105,6 +105,7 @@ static NSDictionary* customCertificatesForHost;
- UIStatusBarStyle _savedStatusBarStyle;
- #endif // !TARGET_OS_OSX
- BOOL _savedStatusBarHidden;
-+ BOOL _disablePromptDuringLoading; //Disables the display of prompts during site navigation/loading
-
- #if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 110000 /* __IPHONE_11_0 */
- UIScrollViewContentInsetAdjustmentBehavior _savedContentInsetAdjustmentBehavior;
-@@ -139,6 +140,7 @@ static NSDictionary* customCertificatesForHost;
- _injectedJavaScriptForMainFrameOnly = YES;
- _injectedJavaScriptBeforeContentLoaded = nil;
- _injectedJavaScriptBeforeContentLoadedForMainFrameOnly = YES;
-+ _disablePromptDuringLoading = YES;
-
- #if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 110000 /* __IPHONE_11_0 */
- _savedContentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
-@@ -417,6 +419,7 @@ static NSDictionary* customCertificatesForHost;
- - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context{
- if ([keyPath isEqual:@"estimatedProgress"] && object == self.webView) {
- if(_onLoadingProgress){
-+ _disablePromptDuringLoading = YES;
- NSMutableDictionary *event = [self baseEvent];
- [event addEntriesFromDictionary:@{@"progress":[NSNumber numberWithDouble:self.webView.estimatedProgress]}];
- _onLoadingProgress(event);
-@@ -492,6 +495,7 @@ static NSDictionary* customCertificatesForHost;
- NSMutableDictionary *event = [self baseEvent];
- [event addEntriesFromDictionary: @{@"navigationType": message.body}];
- _onLoadingFinish(event);
-+ _disablePromptDuringLoading = NO;
- }
- } else if ([message.name isEqualToString:MessageHandlerName]) {
- if (_onMessage) {
-@@ -851,11 +855,13 @@ static NSDictionary* customCertificatesForHost;
- - (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler
- {
- #if !TARGET_OS_OSX
-- UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"" message:message preferredStyle:UIAlertControllerStyleAlert];
-- [alert addAction:[UIAlertAction actionWithTitle:@"Ok" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
-- completionHandler();
-- }]];
-- [[self topViewController] presentViewController:alert animated:YES completion:NULL];
-+ dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.7 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
-+ UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"" message:message preferredStyle:UIAlertControllerStyleAlert];
-+ [alert addAction:[UIAlertAction actionWithTitle:@"Ok" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
-+ completionHandler();
-+ }]];
-+ [[self topViewController] presentViewController:alert animated:YES completion:NULL];
-+ });
- #else
- NSAlert *alert = [[NSAlert alloc] init];
- [alert setMessageText:message];
-@@ -868,6 +874,51 @@ static NSDictionary* customCertificatesForHost;
- /**
- * confirm
- */
-+// This patch made to overridde the restrictions that webView is imposing to the native Alert, by restricting its size.
-+- (void)webView:(WKWebView *)webView requestMediaCapturePermissionForOrigin:(WKSecurityOrigin *)origin initiatedByFrame:(WKFrameInfo *)frame type:(WKMediaCaptureType)type decisionHandler:(void (^)(WKPermissionDecision decision))decisionHandler API_AVAILABLE(ios(15.0)){
-+
-+ NSString *deviceType;
-+
-+ switch (type) {
-+ case WKMediaCaptureTypeCamera:
-+ deviceType = @"camera";
-+ break;
-+ case WKMediaCaptureTypeMicrophone:
-+ deviceType = @"microphone";
-+ break;
-+ case WKMediaCaptureTypeCameraAndMicrophone:
-+ deviceType = @"camera and microphone";
-+ break;
-+ default:
-+ deviceType = @"unknown device";
-+ }
-+
-+ NSString *message = [NSString stringWithFormat:@"The webpage %@ is requesting access to your %@. Do you want to allow this?", origin.host, deviceType];
-+
-+ UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"Permission Request"
-+ message:message
-+ preferredStyle:UIAlertControllerStyleAlert];
-+
-+ UIAlertAction *allowAction = [UIAlertAction actionWithTitle:@"Allow"
-+ style:UIAlertActionStyleDefault
-+ handler:^(UIAlertAction * _Nonnull action) {
-+ decisionHandler(WKPermissionDecisionGrant);
-+ }
-+ ];
-+
-+ UIAlertAction *denyAction = [UIAlertAction actionWithTitle:@"Deny"
-+ style:UIAlertActionStyleCancel
-+ handler:^(UIAlertAction * _Nonnull action) {
-+ decisionHandler(WKPermissionDecisionDeny);
-+ }
-+ ];
-+
-+ [alertController addAction:allowAction];
-+ [alertController addAction:denyAction];
-+
-+ [[self topViewController] presentViewController:alertController animated:YES completion:NULL];
-+}
-+
- - (void)webView:(WKWebView *)webView runJavaScriptConfirmPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(BOOL))completionHandler{
- #if !TARGET_OS_OSX
- UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"" message:message preferredStyle:UIAlertControllerStyleAlert];
-@@ -894,44 +945,49 @@ static NSDictionary* customCertificatesForHost;
- * prompt
- */
- - (void)webView:(WKWebView *)webView runJavaScriptTextInputPanelWithPrompt:(NSString *)prompt defaultText:(NSString *)defaultText initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(NSString *))completionHandler{
--#if !TARGET_OS_OSX
-- UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"" message:prompt preferredStyle:UIAlertControllerStyleAlert];
-- [alert addTextFieldWithConfigurationHandler:^(UITextField *textField) {
-- textField.text = defaultText;
-- }];
-- UIAlertAction *okAction = [UIAlertAction actionWithTitle:@"Ok" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
-- completionHandler([[alert.textFields lastObject] text]);
-- }];
-- [alert addAction:okAction];
-- UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:^(UIAlertAction *action) {
-- completionHandler(nil);
-- }];
-- [alert addAction:cancelAction];
-- alert.preferredAction = okAction;
-- [[self topViewController] presentViewController:alert animated:YES completion:NULL];
--#else
-- NSAlert *alert = [[NSAlert alloc] init];
-- [alert setMessageText:prompt];
--
-- const NSRect RCTSingleTextFieldFrame = NSMakeRect(0.0, 0.0, 275.0, 22.0);
-- NSTextField *textField = [[NSTextField alloc] initWithFrame:RCTSingleTextFieldFrame];
-- textField.cell.scrollable = YES;
-- if (@available(macOS 10.11, *)) {
-- textField.maximumNumberOfLines = 1;
-- }
-- textField.stringValue = defaultText;
-- [alert setAccessoryView:textField];
-
-- [alert addButtonWithTitle:NSLocalizedString(@"OK", @"OK button")];
-- [alert addButtonWithTitle:NSLocalizedString(@"Cancel", @"Cancel button")];
-- [alert beginSheetModalForWindow:[NSApp keyWindow] completionHandler:^(NSModalResponse response) {
-- if (response == NSAlertFirstButtonReturn) {
-- completionHandler([textField stringValue]);
-+ if(!_disablePromptDuringLoading) {
-+ #if !TARGET_OS_OSX
-+ UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"" message:prompt preferredStyle:UIAlertControllerStyleAlert];
-+ [alert addTextFieldWithConfigurationHandler:^(UITextField *textField) {
-+ textField.text = defaultText;
-+ }];
-+ UIAlertAction *okAction = [UIAlertAction actionWithTitle:@"Ok" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
-+ completionHandler([[alert.textFields lastObject] text]);
-+ }];
-+ [alert addAction:okAction];
-+ UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:^(UIAlertAction *action) {
-+ completionHandler(nil);
-+ }];
-+ [alert addAction:cancelAction];
-+ alert.preferredAction = okAction;
-+ [[self topViewController] presentViewController:alert animated:YES completion:NULL];
-+ #else
-+ NSAlert *alert = [[NSAlert alloc] init];
-+ [alert setMessageText:prompt];
-+
-+ const NSRect RCTSingleTextFieldFrame = NSMakeRect(0.0, 0.0, 275.0, 22.0);
-+ NSTextField *textField = [[NSTextField alloc] initWithFrame:RCTSingleTextFieldFrame];
-+ textField.cell.scrollable = YES;
-+ if (@available(macOS 10.11, *)) {
-+ textField.maximumNumberOfLines = 1;
-+ }
-+ textField.stringValue = defaultText;
-+ [alert setAccessoryView:textField];
-+
-+ [alert addButtonWithTitle:NSLocalizedString(@"OK", @"OK button")];
-+ [alert addButtonWithTitle:NSLocalizedString(@"Cancel", @"Cancel button")];
-+ [alert beginSheetModalForWindow:[NSApp keyWindow] completionHandler:^(NSModalResponse response) {
-+ if (response == NSAlertFirstButtonReturn) {
-+ completionHandler([textField stringValue]);
-+ } else {
-+ completionHandler(nil);
-+ }
-+ }];
-+ #endif // !TARGET_OS_OSX
- } else {
-- completionHandler(nil);
-+ completionHandler(nil);
- }
-- }];
--#endif // !TARGET_OS_OSX
- }
-
- #if !TARGET_OS_OSX
-@@ -1157,6 +1213,7 @@ static NSDictionary* customCertificatesForHost;
- }
-
- if (_onLoadingFinish) {
-+ _disablePromptDuringLoading = NO;
- _onLoadingFinish([self baseEvent]);
- }
- }
-@@ -1446,3 +1503,4 @@ static NSDictionary* customCertificatesForHost;
- }
-
- @end
-+
diff --git a/scripts/build-inpage-bridge.sh b/scripts/build-inpage-bridge.sh
index 7823e49dd80..52753a1589a 100755
--- a/scripts/build-inpage-bridge.sh
+++ b/scripts/build-inpage-bridge.sh
@@ -3,11 +3,7 @@ set -euo pipefail
rm -f app/core/InpageBridgeWeb3.js
mkdir -p scripts/inpage-bridge/dist && rm -rf scripts/inpage-bridge/dist/*
-cd scripts/inpage-bridge/inpage
-../../../node_modules/.bin/webpack --config webpack.config.js
-cd ..
-node content-script/build.js
-cat dist/inpage-bundle.js content-script/index.js > dist/index-raw.js
+cd scripts/inpage-bridge/
../../node_modules/.bin/webpack --config webpack.config.js
cd ../..
cp scripts/inpage-bridge/dist/index.js app/core/InpageBridgeWeb3.js
diff --git a/scripts/inpage-bridge/content-script/build.js b/scripts/inpage-bridge/content-script/build.js
deleted file mode 100644
index cd0ca95605c..00000000000
--- a/scripts/inpage-bridge/content-script/build.js
+++ /dev/null
@@ -1,14 +0,0 @@
-const fs = require('fs');
-const path = require('path');
-
-const distPath = path.join(__dirname, '..', '/', 'dist');
-
-const inpageContent = fs
- .readFileSync(path.join(distPath, 'inpage-content.js'))
- .toString();
-
-// wrap the inpage content in a variable declaration
-const code = `const inpageBundle = ${JSON.stringify(inpageContent)}`;
-
-fs.writeFileSync(path.join(distPath, 'inpage-bundle.js'), code, 'ascii');
-console.log('content-script.js generated succesfully');
diff --git a/scripts/inpage-bridge/inpage/webpack.config.js b/scripts/inpage-bridge/inpage/webpack.config.js
deleted file mode 100644
index f1d28be96f1..00000000000
--- a/scripts/inpage-bridge/inpage/webpack.config.js
+++ /dev/null
@@ -1,63 +0,0 @@
-const webpack = require('webpack');
-const path = require('path');
-const { readFileSync } = require('fs');
-
-const SVG_LOGO_PATH =
- '../../../app/images/fox.svg';
-function getBuildIcon() {
- const svg = readFileSync(SVG_LOGO_PATH, 'utf8');
- return `data:image/svg+xml,${encodeURIComponent(svg)}`;
-}
-
-const config = {
- entry: './index.js',
-
- output: {
- path: path.resolve(__dirname, '..', 'dist'),
- filename: 'inpage-content.js',
- },
-
- mode: 'production',
- module: {
- rules: [
- {
- test: /\.(js|jsx|mjs)$/u,
- use: {
- loader: 'babel-loader',
- options: {
- presets: ['@babel/preset-env'],
- },
- },
- },
- ],
- },
- resolve: {
- fallback: {
- buffer: require.resolve('buffer'),
- stream: require.resolve('stream-browserify'),
- _stream_transform: require.resolve('readable-stream/transform'),
- _stream_readable: require.resolve('readable-stream/readable'),
- _stream_writable: require.resolve('readable-stream/writable'),
- _stream_duplex: require.resolve('readable-stream/duplex'),
- _stream_passthrough: require.resolve('readable-stream/passthrough'),
- },
- },
- plugins: [
- new webpack.ProvidePlugin({
- Buffer: ['buffer', 'Buffer'],
- process: 'process/browser',
- }),
- new webpack.DefinePlugin({
- 'process.env.METAMASK_BUILD_NAME': JSON.stringify('MetaMask'),
- 'process.env.METAMASK_BUILD_ICON': JSON.stringify(getBuildIcon()),
- 'process.env.METAMASK_BUILD_APP_ID': JSON.stringify('io.metamask.mobile'),
- }),
- ],
-};
-
-module.exports = (_env, argv) => {
- if (argv.mode === 'development') {
- config.mode = 'development';
- }
- return config;
-};
diff --git a/scripts/inpage-bridge/inpage/MobilePortStream.js b/scripts/inpage-bridge/src/MobilePortStream.js
similarity index 100%
rename from scripts/inpage-bridge/inpage/MobilePortStream.js
rename to scripts/inpage-bridge/src/MobilePortStream.js
diff --git a/scripts/inpage-bridge/inpage/ReactNativePostMessageStream.js b/scripts/inpage-bridge/src/ReactNativePostMessageStream.js
similarity index 100%
rename from scripts/inpage-bridge/inpage/ReactNativePostMessageStream.js
rename to scripts/inpage-bridge/src/ReactNativePostMessageStream.js
diff --git a/scripts/inpage-bridge/content-script/index.js b/scripts/inpage-bridge/src/index.js
similarity index 73%
rename from scripts/inpage-bridge/content-script/index.js
rename to scripts/inpage-bridge/src/index.js
index 16052682c1f..2a4787822fc 100644
--- a/scripts/inpage-bridge/content-script/index.js
+++ b/scripts/inpage-bridge/src/index.js
@@ -1,7 +1,8 @@
-/* global inpageBundle */
+
+import injectInpageProvider from './provider';
if (shouldInject()) {
- injectScript(inpageBundle);
+ injectInpageProvider();
start();
}
@@ -16,28 +17,6 @@ async function start() {
window._metamaskSetupProvider();
}
-/**
- * Injects a script tag into the current document
- *
- * @param {string} content - Code to be executed in the current document
- */
-function injectScript(content) {
- try {
- const container = document.head || document.documentElement;
-
- // synchronously execute script in page context
- const scriptTag = document.createElement('script');
- scriptTag.setAttribute('async', false);
- scriptTag.textContent = content;
- container.insertBefore(scriptTag, container.children[0]);
-
- // script executed; remove script element from DOM
- container.removeChild(scriptTag);
- } catch (err) {
- console.error('MetaMask script injection failed', err);
- }
-}
-
/**
* Determines if the provider should be injected.
*
@@ -47,7 +26,6 @@ function shouldInject() {
return (
doctypeCheck() &&
suffixCheck() &&
- documentElementCheck() &&
!blockedDomainCheck()
);
}
@@ -86,19 +64,6 @@ function suffixCheck() {
return true;
}
-/**
- * Checks the documentElement of the current document
- *
- * @returns {boolean} {@code true} if the documentElement is an html node or if none exists
- */
-function documentElementCheck() {
- const documentElement = document.documentElement.nodeName;
- if (documentElement) {
- return documentElement.toLowerCase() === 'html';
- }
- return true;
-}
-
/**
* Checks if the current domain is blocked
*
diff --git a/scripts/inpage-bridge/inpage/index.js b/scripts/inpage-bridge/src/provider.js
similarity index 83%
rename from scripts/inpage-bridge/inpage/index.js
rename to scripts/inpage-bridge/src/provider.js
index b76b2ae006a..f3cdca54008 100644
--- a/scripts/inpage-bridge/inpage/index.js
+++ b/scripts/inpage-bridge/src/provider.js
@@ -15,28 +15,31 @@ const metamaskStream = new ReactNativePostMessageStream({
target: CONTENT_SCRIPT,
});
-// Initialize provider object (window.ethereum)
-initializeProvider({
- connectionStream: metamaskStream,
- shouldSendMetadata: false,
- providerInfo: {
- uuid: uuid(),
- name: process.env.METAMASK_BUILD_NAME,
- icon: process.env.METAMASK_BUILD_ICON,
- rdns: process.env.METAMASK_BUILD_APP_ID,
- },
-});
+const init = () => {
+ // Initialize provider object (window.ethereum)
+ initializeProvider({
+ connectionStream: metamaskStream,
+ shouldSendMetadata: false,
+ providerInfo: {
+ uuid: uuid(),
+ name: process.env.METAMASK_BUILD_NAME,
+ icon: process.env.METAMASK_BUILD_ICON,
+ rdns: process.env.METAMASK_BUILD_APP_ID,
+ },
+ });
-// Set content script post-setup function
-Object.defineProperty(window, '_metamaskSetupProvider', {
- value: () => {
- setupProviderStreams();
- delete window._metamaskSetupProvider;
- },
- configurable: true,
- enumerable: false,
- writable: false,
-});
+ // Set content script post-setup function
+ Object.defineProperty(window, '_metamaskSetupProvider', {
+ value: () => {
+ setupProviderStreams();
+ delete window._metamaskSetupProvider;
+ },
+ configurable: true,
+ enumerable: false,
+ writable: false,
+ });
+
+}
// Functions
@@ -130,3 +133,5 @@ function notifyProviderOfStreamFailure() {
window.location.origin,
);
}
+
+export default init;
diff --git a/scripts/inpage-bridge/webpack.config.js b/scripts/inpage-bridge/webpack.config.js
index 8c0dff54856..b8fa14e20d1 100644
--- a/scripts/inpage-bridge/webpack.config.js
+++ b/scripts/inpage-bridge/webpack.config.js
@@ -1,7 +1,16 @@
+const webpack = require('webpack');
const path = require('path');
+const { readFileSync } = require('fs');
+
+const SVG_LOGO_PATH =
+ '../../app/images/fox.svg';
+function getBuildIcon() {
+ const svg = readFileSync(SVG_LOGO_PATH, 'utf8');
+ return `data:image/svg+xml,${encodeURIComponent(svg)}`;
+}
const config = {
- entry: './dist/index-raw.js',
+ entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'dist'),
@@ -9,6 +18,41 @@ const config = {
},
mode: 'production',
+ module: {
+ rules: [
+ {
+ test: /\.(js|jsx|mjs)$/u,
+ use: {
+ loader: 'babel-loader',
+ options: {
+ presets: ['@babel/preset-env'],
+ },
+ },
+ },
+ ],
+ },
+ resolve: {
+ fallback: {
+ buffer: require.resolve('buffer'),
+ stream: require.resolve('stream-browserify'),
+ _stream_transform: require.resolve('readable-stream/transform'),
+ _stream_readable: require.resolve('readable-stream/readable'),
+ _stream_writable: require.resolve('readable-stream/writable'),
+ _stream_duplex: require.resolve('readable-stream/duplex'),
+ _stream_passthrough: require.resolve('readable-stream/passthrough'),
+ },
+ },
+ plugins: [
+ new webpack.ProvidePlugin({
+ Buffer: ['buffer', 'Buffer'],
+ process: 'process/browser',
+ }),
+ new webpack.DefinePlugin({
+ 'process.env.METAMASK_BUILD_NAME': JSON.stringify('MetaMask'),
+ 'process.env.METAMASK_BUILD_ICON': JSON.stringify(getBuildIcon()),
+ 'process.env.METAMASK_BUILD_APP_ID': JSON.stringify('io.metamask.mobile'),
+ }),
+ ],
};
module.exports = (_env, argv) => {
diff --git a/yarn.lock b/yarn.lock
index 3e03fa82aef..30a497d0534 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -4569,13 +4569,6 @@
resolved "https://registry.yarnpkg.com/@metamask/react-native-actionsheet/-/react-native-actionsheet-2.4.2.tgz#9f956fe9e784d92c8e33656877fcfaabe4a482f1"
integrity sha512-oibRXUzF+7DB0Nyzp2cMGN7ztB9Sl21W1NFq1IMa00mB4/X43JY+u+LCkx625WvQUeq0GO2ZQ6hG1L5XjMumSA==
-"@metamask/react-native-animated-fox@^2.1.0":
- version "2.1.0"
- resolved "https://registry.yarnpkg.com/@metamask/react-native-animated-fox/-/react-native-animated-fox-2.1.0.tgz#504e1f68e13ad273fb193c6f6a3832f3a5242518"
- integrity sha512-Hc+DyaEIXYa7NjzqXfgh01bsoP9WbE/ENNKZ4A65YwSBmJk5ZDvhMgTMFz+qybkUllx4kn4ENkmr0SXERZ2wmg==
- dependencies:
- prop-types "^15.5.10"
-
"@metamask/react-native-button@^3.0.0":
version "3.0.0"
resolved "https://registry.yarnpkg.com/@metamask/react-native-button/-/react-native-button-3.0.0.tgz#4af8affd11e2b285cfc1b1752280797e1b33e62b"
@@ -4602,6 +4595,14 @@
resolved "https://registry.yarnpkg.com/@metamask/react-native-splash-screen/-/react-native-splash-screen-3.2.0.tgz#06a6547c143b088e47af40eacea9ac6657ac937f"
integrity sha512-V8Cn0MXe9jdaUli/DK3PoJ71tx7k3IW2v2slqflvNstvHiO3MpCtdylsYIyu+tiPwI2JiyLRzLK8s02/3jxk6g==
+"@metamask/react-native-webview@^14.0.1":
+ version "14.0.1"
+ resolved "https://registry.yarnpkg.com/@metamask/react-native-webview/-/react-native-webview-14.0.1.tgz#db1a1f4abea077b17fcc163538db4fcd7b2890bd"
+ integrity sha512-euctbS170XwxCCOIP36pr3N2bW8hQdZIKzpoEpWFK+5aumQ7UrTmwswcD3OCqnlk5JvpsRvlixIje8AA+6Gmtg==
+ dependencies:
+ escape-string-regexp "2.0.0"
+ invariant "2.2.4"
+
"@metamask/rpc-errors@^6.0.0", "@metamask/rpc-errors@^6.2.1":
version "6.2.1"
resolved "https://registry.yarnpkg.com/@metamask/rpc-errors/-/rpc-errors-6.2.1.tgz#f5daf429ededa7cb83069dc621bd5738fe2a1d80"
@@ -24850,14 +24851,6 @@ react-native-webview-invoke@^0.6.2:
resolved "https://registry.yarnpkg.com/react-native-webview-invoke/-/react-native-webview-invoke-0.6.2.tgz#75cc27ef98ea1cbc9386269347d3aafe90d33aa3"
integrity sha512-PCzP7Zl3XwHU10JYS8nR0gwuR8XiOO0MhC8y9ZuPPI+HeISn95GvNYhOXxeLgfbdbUcpNWh1HqxPDySlfCIqxg==
-react-native-webview@11.13.0:
- version "11.13.0"
- resolved "https://registry.yarnpkg.com/react-native-webview/-/react-native-webview-11.13.0.tgz#a2eca0f87b2ae9bba0dd8144594aeff9947cc5d6"
- integrity sha512-jjQAKWv8JzRmcn76fMe4lXD84AAeR7kn43kAmUe1GX312BMLaP+RbKlgpYAlNuOBXL0YirItGKDrpaD0bNROOA==
- dependencies:
- escape-string-regexp "2.0.0"
- invariant "2.2.4"
-
react-native@0.71.15:
version "0.71.15"
resolved "https://registry.yarnpkg.com/react-native/-/react-native-0.71.15.tgz#7d99f478238c559b8b3fdaad2514f11d53ef135a"