How can i monitor requests on WKWebview?

无人久伴 提交于 2019-11-27 06:21:19
Benzi Heler

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];
}

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.

@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://stackoverflow.com/a/27363569/3448282

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!