How can i monitor requests on WKWebview?
Asked Answered
S

4

20

How can i monitor requests on WKWebview?

I'v tried using NSURLprotocol (canInitWithRequest) but it won't monitor ajax requests (XHR), only navigation requests(document requests)

Serin answered 27/2, 2015 at 13:51 Comment(0)
S
35

Finally I solved it

Since I don't have control over the web view content, I injected to the WKWebview a java script that include a jQuery AJAX request listener.

When the listener catches a request it sends the native app the request body in the method:

webkit.messageHandlers.callbackHandler.postMessage(data);

The native app catches the message in a delegate called:

(void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message

and perform the corresponding actions

here is the relevant code:

ajaxHandler.js -

//Every time an Ajax call is being invoked the listener will recognize it and  will call the native app with the request details

$( document ).ajaxSend(function( event, request, settings )  {
    callNativeApp (settings.data);
});

function callNativeApp (data) {
    try {
        webkit.messageHandlers.callbackHandler.postMessage(data);
    }
    catch(err) {
        console.log('The native context does not exist yet');
    }
}

My ViewController delegate are:

@interface BrowserViewController : UIViewController <UIWebViewDelegate, WKUIDelegate, WKNavigationDelegate, WKScriptMessageHandler, UIWebViewDelegate>

And in my viewDidLoad(), I'm creating a WKWebView:

WKWebViewConfiguration *configuration = [[WKWebViewConfiguration alloc]init];
[self addUserScriptToUserContentController:configuration.userContentController];
appWebView = [[WKWebView alloc]initWithFrame:self.view.frame configuration:configuration];
appWebView.UIDelegate = self;
appWebView.navigationDelegate = self;
[appWebView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString: @"http://#############"]]];                                                     

Here is the addUserScriptToUserContentController:

- (void) addUserScriptToUserContentController:(WKUserContentController *) userContentController{
    NSString *jsHandler = [NSString stringWithContentsOfURL:[[NSBundle mainBundle]URLForResource:@"ajaxHandler" withExtension:@"js"] encoding:NSUTF8StringEncoding error:NULL];
    WKUserScript *ajaxHandler = [[WKUserScript alloc]initWithSource:jsHandler injectionTime:WKUserScriptInjectionTimeAtDocumentEnd forMainFrameOnly:NO];
    [userContentController addScriptMessageHandler:self name:@"callbackHandler"];
    [userContentController addUserScript:ajaxHandler];
}
Serin answered 3/3, 2015 at 9:40 Comment(4)
Is the jquery library already supported within WKWebView? or did you have to import that as well?Abdicate
This ajaxHandler is not working for me. I keep getting errors saying unknown variable $. Any ideas?Margeret
@Margeret You're probably not importing jQuery in the response of the original request ($ is a JQuery variable)Shulamite
@CharltonProvatas, given halil_g's answer looks like it's not supported by WKWebView and needs to be imported in the response web content.Vesta
C
25

@Benzi Heler answer is great, but it uses jQuery which seems like is not working in WKWebView anymore, so I have found solution without using jQuery.

Here is ViewController implementation that lets you be notified every AJAX request is completed in WKWebView:

import UIKit
import WebKit

class WebViewController: UIViewController {

    private var wkWebView: WKWebView!
    private let handler = "handler"

    override func viewDidLoad() {
        super.viewDidLoad()

        let config = WKWebViewConfiguration()
        let userScript = WKUserScript(source: getScript(), injectionTime: .atDocumentStart, forMainFrameOnly: false)
        config.userContentController.addUserScript(userScript)
        config.userContentController.add(self, name: handler)

        wkWebView = WKWebView(frame:  view.bounds, configuration: config)
        view.addSubview(wkWebView)

        if let url = URL(string: "YOUR AJAX WEBSITE") {
            wkWebView.load(URLRequest(url: url))
        } else {
            print("Wrong URL!")
        }
    }

    private func getScript() -> String {
        if let filepath = Bundle.main.path(forResource: "script", ofType: "js") {
            do {
                return try String(contentsOfFile: filepath)
            } catch {
                print(error)
            }
        } else {
            print("script.js not found!")
        }
        return ""
    }
}

extension WebViewController: WKScriptMessageHandler {
    func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
        if let dict = message.body as? Dictionary<String, AnyObject>, let status = dict["status"] as? Int, let responseUrl = dict["responseURL"] as? String {
            print(status)
            print(responseUrl)
        }
    }
}

Pretty standard implementation. There is a WKWebView created programmatically. There is injected script that is loaded from script.js file.

And the most important part is script.js file:

var open = XMLHttpRequest.prototype.open;
XMLHttpRequest.prototype.open = function() {
    this.addEventListener("load", function() {
        var message = {"status" : this.status, "responseURL" : this.responseURL}
        webkit.messageHandlers.handler.postMessage(message);
    });
    open.apply(this, arguments);
};

userContentController delegate method will be called every time there is AJAX request loaded. I'm passing there status and responseURL, because this was what I needed in my case, but you can also get more informations about request. Here is the list of all properties and methods available: https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest

My solution is inspired by this answer written by @John Culviner: https://mcmap.net/q/166992/-add-a-quot-hook-quot-to-all-ajax-requests-on-a-page

Consol answered 21/9, 2018 at 7:3 Comment(5)
is it possible to inject the configuration after WKWebView is loaded?Hydrogenate
No - you can only set a WKWebView's configuration in its init methodDetoxify
It's not working, it actually messes with the navigation flow.Ussery
@Consol I need to observe captcha change in Ajax. On didLoad I'm using "var c = document.createElement('canvas'); var ctx = c.getContext('2d'); const list = document.getElementsByTagName(\"img\"); ctx.drawImage(list[1], 100, 40); var value = c.toDataURL(); value.split(',')[1];". Can I do this in script.js If then now to add it in script.js ?Hydrogenate
Its great. I can catch json of network callsApraxia
K
5

If you have control of the content inside the WkWebView you can send messages to your native app using window.webkit.messageHandlers whenever you make an ajax request, which will be received as a WKScriptMessage that can be processed by whatever you've designated as your WKScriptMessageHandler. The messages can contain whatever information you wish, and will be automatically converted into native objects/values in your Objective-C or Swift code.

If you don't have control over the content you can still do this by injecting your own JavaScript via a WKUserScript to track ajax requests and send back messages using the method stated above.

Kokaras answered 2/3, 2015 at 6:25 Comment(2)
I don't have control of the content inside the WKWebview. Still will I be able to get the ajax request?Mittel
@BenziHeler, You mentioned that u don't have the control of the content inside the WKWebview but you are saying you have solved it the way Justin Michael suggested.... Can you please explain can it be possible it you don't have the control of the webview content?Mittel
B
-1

You can use this to respond to requests from the WKWebView. It works similar to UIWebView.

- (void)webView:(WKWebView *)webView2 decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler {
    
    if (navigationAction.navigationType == WKNavigationTypeLinkActivated) {
     
        NSString *url = [navigationAction.request.URL absoluteString];
        
       // Handle URL request internally

    }

    decisionHandler(WKNavigationActionPolicyAllow); // Will continue processing request

    decisionHandler(WKNavigationActionPolicyCancel); // Cancels request
}
Buckingham answered 21/9, 2020 at 20:5 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.