Replace onPageStarted with shouldInterceptRequest

This commit is contained in:
Vitaliy Vlasov 2020-04-04 16:54:36 +03:00
parent eabeafbec6
commit 4368633479
No known key found for this signature in database
GPG Key ID: A7D57C347F2B2964
1 changed files with 264 additions and 11 deletions

View File

@ -1,5 +1,27 @@
package com.reactnativecommunity.webview;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.charset.UnsupportedCharsetException;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.OkHttpClient.Builder;
import okhttp3.Request;
import okhttp3.Response;
import org.json.JSONException;
import org.json.JSONObject;
import java.net.HttpURLConnection;
import static okhttp3.internal.Util.UTF_8;
import android.util.Log;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.app.DownloadManager;
@ -30,6 +52,8 @@ import android.webkit.JavascriptInterface;
import android.webkit.SslErrorHandler;
import android.webkit.PermissionRequest;
import android.webkit.URLUtil;
import android.webkit.ServiceWorkerController;
import android.webkit.ServiceWorkerClient;
import android.webkit.ValueCallback;
import android.webkit.WebChromeClient;
import android.webkit.WebResourceRequest;
@ -126,8 +150,10 @@ public class RNCWebViewManager extends SimpleViewManager<WebView> {
public static final int COMMAND_CLEAR_HISTORY = 1002;
protected static final String REACT_CLASS = "RNCWebView";
protected static final String HEADER_CONTENT_TYPE = "content-type";
protected static final String HTML_ENCODING = "UTF-8";
protected static final String HTML_MIME_TYPE = "text/html";
protected static final String UNKNOWN_MIME_TYPE = "application/octet-stream";
protected static final String JAVASCRIPT_INTERFACE = "ReactNativeWebView";
protected static final String HTTP_METHOD_POST = "POST";
// Use `webView.loadUrl("about:blank")` to reliably reset the view
@ -139,12 +165,22 @@ public class RNCWebViewManager extends SimpleViewManager<WebView> {
protected boolean mAllowsFullscreenVideo = false;
protected @Nullable String mUserAgent = null;
protected @Nullable String mUserAgentWithApplicationName = null;
protected static String userAgent;
protected static OkHttpClient httpClient;
public RNCWebViewManager() {
mWebViewConfig = new WebViewConfig() {
public void configWebView(WebView webView) {
}
};
httpClient = new Builder()
.followRedirects(false)
.followSslRedirects(false)
.build();
}
public RNCWebViewManager(WebViewConfig webViewConfig) {
@ -171,6 +207,7 @@ public class RNCWebViewManager extends SimpleViewManager<WebView> {
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
protected WebView createViewInstance(ThemedReactContext reactContext) {
RNCWebView webView = createRNCWebViewInstance(reactContext);
userAgent = webView.getSettings().getUserAgentString();
setupWebChromeClient(reactContext, webView);
reactContext.addLifecycleEventListener(webView);
mWebViewConfig.configWebView(webView);
@ -235,9 +272,102 @@ public class RNCWebViewManager extends SimpleViewManager<WebView> {
}
});
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
ServiceWorkerController swController = ServiceWorkerController.getInstance();
swController.setServiceWorkerClient(new ServiceWorkerClient() {
@Override
public WebResourceResponse shouldInterceptRequest(WebResourceRequest request) {
Log.d(REACT_CLASS, "shouldInterceptRequest / ServiceWorkerClient");
WebResourceResponse response = RNCWebViewManager.this.shouldInterceptRequest(request, false, webView);
if (response != null) {
Log.d(REACT_CLASS, "shouldInterceptRequest / ServiceWorkerClient -> return intersept response");
return response;
}
Log.d(REACT_CLASS, "shouldInterceptRequest / ServiceWorkerClient -> intercept response is nil, delegating up");
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("|");
}
private Boolean responseRequiresJSInjection(Response response) {
// we don't want to inject JS into redirects
if (response.isRedirect()) {
return false;
}
// ...okhttp appends charset to content type sometimes, like "text/html; charset=UTF8"
final String contentTypeAndCharset = response.header(HEADER_CONTENT_TYPE, UNKNOWN_MIME_TYPE);
// ...and we only want to inject it in to HTML, really
return contentTypeAndCharset.startsWith(HTML_MIME_TYPE);
}
public WebResourceResponse shouldInterceptRequest(WebResourceRequest request, Boolean onlyMainFrame, RNCWebView webView) {
Uri url = request.getUrl();
String urlStr = url.toString();
Log.i("StatusNativeLogs", "###shouldInterceptRequest 1");
Log.d(REACT_CLASS, "new request ");
Log.d(REACT_CLASS, "url " + urlStr);
Log.d(REACT_CLASS, "host " + request.getUrl().getHost());
Log.d(REACT_CLASS, "path " + request.getUrl().getPath());
Log.d(REACT_CLASS, "main " + request.isForMainFrame());
Log.d(REACT_CLASS, "headers " + request.getRequestHeaders().toString());
Log.d(REACT_CLASS, "method " + request.getMethod());
Log.i("StatusNativeLogs", "###shouldInterceptRequest 2");
if (onlyMainFrame && !request.isForMainFrame() ||
urlStringLooksInvalid(urlStr)) {
return null;//super.shouldInterceptRequest(webView, request);
}
Log.i("StatusNativeLogs", "###shouldInterceptRequest 3");
try {
Log.i("StatusNativeLogs", "###shouldInterceptRequest 4");
Request req = new Request.Builder()
.url(urlStr)
.header("User-Agent", userAgent)
.build();
Log.i("StatusNativeLogs", "### httpCall " + new Boolean(httpClient != null).toString());
Response response = httpClient.newCall(req).execute();
Log.d(REACT_CLASS, "response headers " + response.headers().toString());
Log.d(REACT_CLASS, "response code " + response.code());
Log.d(REACT_CLASS, "response suc " + response.isSuccessful());
if (!responseRequiresJSInjection(response)) {
return null;
}
InputStream is = response.body().byteStream();
MediaType contentType = response.body().contentType();
Charset charset = contentType != null ? contentType.charset(UTF_8) : UTF_8;
RNCWebView reactWebView = (RNCWebView) webView;
if (response.code() == HttpURLConnection.HTTP_OK) {
is = new InputStreamWithInjectedJS(is, reactWebView.injectedJSBeforeContentLoaded, charset);
}
Log.d(REACT_CLASS, "inject our custom JS to this request");
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);
@ -740,8 +870,95 @@ public class RNCWebViewManager extends SimpleViewManager<WebView> {
}
}
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<Charset, String> script = new HashMap<>();
private boolean hasJS = false;
private boolean headWasFound = false;
private boolean scriptWasInjected = false;
private StringBuffer contentBuffer = new StringBuffer();
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>" + js + "</script>";
script.put(cs, jsScript);
this.pageIS = is;
}
}
@Override
public int read() throws IOException {
if (scriptWasInjected || !hasJS) {
return pageIS.read();
}
if (!scriptWasInjected && headWasFound) {
int 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 == 62 && bufferLength >= 6) {
if (contentBuffer.substring(bufferLength - 6).equals("<head>")) {
this.scriptIS = getScript(this.charset);
headWasFound = true;
}
}
return nextByte;
}
return pageIS.read();
}
}
protected class RNCWebViewClient extends WebViewClient {
protected static final String REACT_CLASS = "RNCWebViewClient";
protected boolean mLastLoadFailed = false;
protected @Nullable
ReadableArray mUrlPrefixesForDefaultIntent;
@ -752,6 +969,7 @@ public class RNCWebViewManager extends SimpleViewManager<WebView> {
ignoreErrFailedForThisURL = url;
}
@Override
public void onPageFinished(WebView webView, String url) {
super.onPageFinished(webView, url);
@ -770,9 +988,6 @@ public class RNCWebViewManager extends SimpleViewManager<WebView> {
super.onPageStarted(webView, url, favicon);
mLastLoadFailed = false;
RNCWebView reactWebView = (RNCWebView) webView;
reactWebView.callInjectedJavaScriptBeforeContentLoaded();
dispatchEvent(
webView,
new TopLoadingStartEvent(
@ -780,6 +995,48 @@ public class RNCWebViewManager extends SimpleViewManager<WebView> {
createWebViewEvent(webView, url)));
}
@Override
public WebResourceResponse shouldInterceptRequest(WebView webView, WebResourceRequest request) {
Log.d(REACT_CLASS, "shouldInterceptRequest / WebViewClient");
WebResourceResponse response = RNCWebViewManager.this.shouldInterceptRequest(request, true, (RNCWebView)webView);
if (response != null) {
Log.d(REACT_CLASS, "shouldInterceptRequest / WebViewClient -> return intercept response");
return response;
}
Log.d(REACT_CLASS, "shouldInterceptRequest / WebViewClient -> intercept response is nil, delegating up");
return super.shouldInterceptRequest(webView, request);
}
@Override
public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
if (request == null || view == null) {
return false;
}
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;
}
}
/*
* API < 24: TODO: implement based on https://github.com/toshiapp/toshi-android-client/blob/f4840d3d24ff60223662eddddceca8586a1be8bb/app/src/main/java/com/toshi/view/activity/webView/ToshiWebClient.kt#L99
* */
final String url = request.getUrl().toString();
return this.shouldOverrideUrlLoading(view, url);
}
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
progressChangedFilter.setWaitingForCommandLoadUrl(true);
@ -792,13 +1049,6 @@ public class RNCWebViewManager extends SimpleViewManager<WebView> {
}
@TargetApi(Build.VERSION_CODES.N)
@Override
public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
final String url = request.getUrl().toString();
return this.shouldOverrideUrlLoading(view, url);
}
@Override
public void onReceivedSslError(final WebView webView, final SslErrorHandler handler, final SslError error) {
handler.cancel();
@ -957,6 +1207,9 @@ public class RNCWebViewManager extends SimpleViewManager<WebView> {
@Override
public boolean onConsoleMessage(ConsoleMessage message) {
Log.i("StatusNativeLogs", "###js " + message.message() + " -- From line "
+ message.lineNumber() + " of "
+ message.sourceId());
if (ReactBuildConfig.DEBUG) {
return super.onConsoleMessage(message);
}