Replace onPageStarted with shouldInterceptRequest
This commit is contained in:
parent
eabeafbec6
commit
4368633479
|
@ -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);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue