Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

When used with firebase/facebook/onesignal plugins JS code injection not working #189

Closed
platomaniac opened this issue Nov 10, 2019 · 8 comments

Comments

@platomaniac
Copy link

platomaniac commented Nov 10, 2019

The plugin is super awesome but here is an inexplicable problem that is bugging us.

We are using this flutter webview plugin in Android. We inject the following JavaScript code to intercept AJAX network call. It is working fine with most of the plugins we are using in our app and intercepting network call as expected. But as soon as we integrate/build our app with certian other plugins (any of: firebase/facebook/onesignal plugins) it stops intercepting the AJAX call and we get the following error.

Failed to execute 'write' on 'Document': It isn't possible to write into a document from an asynchronously-loaded external script unless it is explicitly opened.

We load the JS code like this:

InAppWebView(
  initialUrl: hitUrl,
  initialOptions: {
    'javaScriptEnabled': true,
    'supportZoom': true,
    "domStorageEnabled": true,
    "useOnLoadResource": true,
  },
  onLoadStart: (InAppWebViewController controller, String url) {
    print("started $url");
  },
  onWebViewCreated:
  (InAppWebViewController controller) async {
    webView = controller;
    webView.setOptions({'javaScriptEnabled': true});
    /// injecting the javascript code here.
    await webView.injectScriptCode(widget.jsCode);
    webView.addJavaScriptHandler('zaboHandler', onZaboHandlerCallback);
  },
  onConsoleMessage: (InAppWebViewController controller,
                     ConsoleMessage consoleMessage) {
    print(consoleMessage.message);
  },
)

The example JS code that was injected above:

var open = window.XMLHttpRequest.prototype.open;
var send = window.XMLHttpRequest.prototype.send;
var onReadyStateChange;
var index = 0;
function openReplacement(method, url, async, user, password) {
    var syncMode = async !== false ? 'async' : 'sync';
    return open.apply(this, arguments);
}

function sendReplacement(data) {
    if (this.onreadystatechange) {
        this._onreadystatechange = this.onreadystatechange;
    }
    this.onreadystatechange = onReadyStateChangeReplacement;
    return send.apply(this, arguments);
}

function onReadyStateChangeReplacement() {
    if (this.readyState === XMLHttpRequest.DONE) {
        if (this.responseType === 'text') {
            if (this.responseText !== '' && this.responseText !== null) {
              if (window.Android && window.Android.postMessage) {
                Android.postMessage(this.responseText);
              }
            }
        }
        if (this.responseType === 'json') {
               var res = JSON.stringify(this.response);
               if (res !== '' && res !== null) {
                 
                   if (window.Android && window.Android.postMessage) {
                        Android.postMessage(res);
                   }
               }
     }
    if (this._onreadystatechange) {
        return this._onreadystatechange.apply(this, arguments);
    }
}


window.XMLHttpRequest.prototype.open = openReplacement;
window.XMLHttpRequest.prototype.send = sendReplacement;

When we build with any of the following flutter plugins the above failure/error happens.

What can go wrong with the above plugins that inappbrowser fails to work as expected?

@pichillilorenzo
Copy link
Owner

The error

Failed to execute 'write' on 'Document': It isn't possible to write into a document from an asynchronously-loaded external script unless it is explicitly opened.

is about JavaScript and not coming from Dart/Flutter or any of flutter plugins because plugins such as flutterfire or flutter_facebook_login, don't interact in any way with the InAppWebView widget (and with flutter_inappbrowser in general), so it is impossible that is caused by these plugins.

Probably you have some bugged javascript code in some scripts (not this one because there isn't a document.write() call as I can see). You need to debug your javascript code (see https://stackoverflow.com/a/24297863/4637638 about the Failed to execute 'write' on 'Document' error).

Also, in the onConsoleMessage event handler, you can print also the sourceURL and the lineNumber of the error to have more details about where the error is coming from (maybe some external script??):

onConsoleMessage: (InAppWebViewController controller, ConsoleMessage consoleMessage) {
  print("""
  console output:
    sourceURL: ${consoleMessage.sourceURL}
    lineNumber: ${consoleMessage.lineNumber}
    message: ${consoleMessage.message}
  """);
},

If you want, you can use the 2.0 version of this plugin because it has shouldInterceptAjaxRequest event handler to manage automatically XMLHttpRequest requests:

shouldInterceptAjaxRequest: (InAppWebViewController controller, AjaxRequest ajaxRequest) async {
  print("AJAX REQUEST: ${ajaxRequest.method} - ${ajaxRequest.url}, DATA: ${ajaxRequest.data}");
  // Manipulates the ajax request
  // ...
  return ajaxRequest;
},
onAjaxReadyStateChange: (InAppWebViewController controller, AjaxRequest ajaxRequest) async {
  print("AJAX READY STATE CHANGE: ${ajaxRequest.method} - ${ajaxRequest.url}, ${ajaxRequest.status}, ${ajaxRequest.readyState}, ${ajaxRequest.responseType}, ${ajaxRequest.responseText}, ${ajaxRequest.responseHeaders}");
  return AjaxRequestAction.PROCEED;
},
onAjaxProgress: (InAppWebViewController controller, AjaxRequest ajaxRequest) async {
  print("AJAX EVENT: ${ajaxRequest.method} - ${ajaxRequest.url}, ${ajaxRequest.event.type}, LOADED: ${ajaxRequest.event.loaded}, ${ajaxRequest.responseHeaders}");
  return AjaxRequestAction.PROCEED;
},

To use this event, you need to set useShouldInterceptAjaxRequest option to true (see the code in https://github.com/pichillilorenzo/flutter_inappbrowser/blob/master/example/lib/inline_example.screen.dart)

NOTE that version 2.0 it is not published officially yet and the syntax changes a little bit! However, you can point the dependency on the master branch of this repository (see https://flutter.dev/docs/development/packages-and-plugins/using-packages#dependencies-on-unpublished-packages).

@vijayawoshe
Copy link

vijayawoshe commented Nov 10, 2019

Thank for you quick response @pichillilorenzo

getting following error if I use the ajaxinterceptor with v2.0.

I know this is an error because some ajax requests are using a responseType json.
```Uncaught InvalidStateError: Failed to read the 'responseText' property from 'XMLHttpRequest': The value is only accessible if the object's 'responseType' is '' or 'text' (was 'json').````

Can you please help?

@pichillilorenzo
Copy link
Owner

Ok! I will fix it as soon as possible.

@vijayawoshe
Copy link

Thanks a lot for your quick response @pichillilorenzo.

For one website I am getting the following error as well. I need an AJAX interceptor for that website which I need for our application (we want to move that app to production as early as possible. we are only stucked with AJAX interceptor).

Uncaught EvalError: Refused to evaluate a string as JavaScript because 'unsafe-eval' is not an allowed source of script in the following Content Security Policy directive: "script-src 'self' 'unsafe-inline' https:

I would really appreciate your help if you will solve this issue as well,

Regards
Vijay

pichillilorenzo added a commit that referenced this issue Nov 10, 2019
… No need to listen to window.addEventListener(flutterInAppBrowserPlatformReady, fuction(){ }) javascript event anymore to use JaaScript message handlers
@pichillilorenzo
Copy link
Owner

pichillilorenzo commented Nov 10, 2019

InvalidStateError  error fixed with last commit!

Instead, about the error of the Content Security Policy, I think the unique available solution could be something like this: https://stackoverflow.com/a/34777722/4637638.

If you need to call specific javascript code but you get the Content Security Policy error, the only way is something like this:

onLoadStop: (InAppWebViewController controller, String url) async {
  controller.loadUrl(url: "javascript:(function() { console.log('this code can run'); })();");
},

So, you must put all the javascript code that you need to run in one line.

About the last commit, in case you are using window.addEventListener("flutterInAppBrowserPlatformReady") javascript event:

No need to listen to window.addEventListener("flutterInAppBrowserPlatformReady", fuction(){ }) javascript event anymore to call window.flutter_inappbrowser.callHandler(handlerName <String>, ...args) to use the JavaScript message handlers

@vijayawoshe
Copy link

vijayawoshe commented Nov 11, 2019

Hello @pichillilorenzo
Thanks a lot for your quick solution.

Fortunately, it is solving the issue with responseType json, text etc.
But the still security issue coming. I tried to apply your suggestion but did not help.

onLoadStop: (InAppWebViewController controller, String url) async {
                    var givenJS = await rootBundle.loadString('assets/zabo_flutter_webview.js');
                    String s = "javascript:(" + givenJS + ")();";
                    controller.loadUrl(url: s);
                    String path = "assets/zabo_flutter_webview.js";
//                    await Future.delayed(Duration(seconds: 2));
//                    await webView.injectJavascriptFileFromAsset(assetFilePath: path);
                  },

If I use your ajaxInterceptor then some part of the page not even loading so I am trying to apply my simple AjaxInterceptor with the above code after resource is loaded.
The same thing is working with flutter's official webview plugin without getting security issue if i load the code with timeout. But with your plugin I am trying the same approach and its not working.

@pichillilorenzo
Copy link
Owner

@vijayawoshe my javascript ajax interceptor should be fixed in the latest commit now! Also I re-added the window.addEventListener("flutterInAppBrowserPlatformReady", fuction(){ }) because of some problems in Android. Can you try the latest commit now?

@vijayawoshe
Copy link

@pichillilorenzo Thanks for your quick response. I will check and let you know. I really appreciate.

This was referenced Jul 6, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants