最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - Capture window.postMessage in Swift WKWebView - Stack Overflow

programmeradmin3浏览0评论

I am developing a swift ios app that uses WKWebView to load up an ecommerce site.
When a user purchases a product here, the checkout page allows the user to pay in cryptocurrency.

When the user clicks "Open in Wallet", the site shoots off a window.postMessage(paymentData) where payment data is a js object with a bitcoin url in it.

I am using a WKUserScript with WKWebConfiguration to inject a script that listens for a window message and then fires off data to my webkit.messageHandler.

let source = """
    window.addEventListener('message', function(e) { window.webkit.messageHandlers.iosListener.postMessage(JSON.stringify(e.data)) } )
    """

Unfortunately this code never triggers.

When I use chrome or safari devtools to inject the same javascript, it works just fine.

I have scoured stack overflow to see if there is a special condition for window.postMessage in WKWebView but have had no luck thus far.

Is it possible to capture a window.postMessage() event and pipe the event data back to my ios app?

Thanks in advance!!!! Here is my existing code.

  let webConfiguration = WKWebViewConfiguration()
    let source = """
    window.addEventListener('message', function(e) { window.webkit.messageHandlers.iosListener.postMessage(JSON.stringify(e.data)) } )
    """
    let script = WKUserScript(source: source, injectionTime: .atDocumentEnd, forMainFrameOnly: false)
    userContentController.addUserScript(script)

    userContentController.add(self, name: "iosListener")
    webConfiguration.userContentController = userContentController
    webView = WKWebView(frame: .zero, configuration: webConfiguration)
    webView.uiDelegate = self
    webView.navigationDelegate = self
    webView.translatesAutoresizingMaskIntoConstraints = false
    view.addSubview(webView)
 func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
    print("message body: \(message.body)")
    print("message frameInfo: \(message.frameInfo)")
  }

I am developing a swift ios app that uses WKWebView to load up an ecommerce site.
When a user purchases a product here, the checkout page allows the user to pay in cryptocurrency.

When the user clicks "Open in Wallet", the site shoots off a window.postMessage(paymentData) where payment data is a js object with a bitcoin url in it.

I am using a WKUserScript with WKWebConfiguration to inject a script that listens for a window message and then fires off data to my webkit.messageHandler.

let source = """
    window.addEventListener('message', function(e) { window.webkit.messageHandlers.iosListener.postMessage(JSON.stringify(e.data)) } )
    """

Unfortunately this code never triggers.

When I use chrome or safari devtools to inject the same javascript, it works just fine.

I have scoured stack overflow to see if there is a special condition for window.postMessage in WKWebView but have had no luck thus far.

Is it possible to capture a window.postMessage() event and pipe the event data back to my ios app?

Thanks in advance!!!! Here is my existing code.

  let webConfiguration = WKWebViewConfiguration()
    let source = """
    window.addEventListener('message', function(e) { window.webkit.messageHandlers.iosListener.postMessage(JSON.stringify(e.data)) } )
    """
    let script = WKUserScript(source: source, injectionTime: .atDocumentEnd, forMainFrameOnly: false)
    userContentController.addUserScript(script)

    userContentController.add(self, name: "iosListener")
    webConfiguration.userContentController = userContentController
    webView = WKWebView(frame: .zero, configuration: webConfiguration)
    webView.uiDelegate = self
    webView.navigationDelegate = self
    webView.translatesAutoresizingMaskIntoConstraints = false
    view.addSubview(webView)
 func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
    print("message body: \(message.body)")
    print("message frameInfo: \(message.frameInfo)")
  }
Share Improve this question edited Jun 21, 2019 at 7:56 Kaushik Makwana 1,3272 gold badges14 silver badges24 bronze badges asked Jun 20, 2019 at 19:03 MFD3000MFD3000 8741 gold badge13 silver badges26 bronze badges 7
  • 2 Did you solve it? Consider upvoting / accepting / interacting with answers to generate valuable knowledge to the website. It's not a good practice to ask a question and never return to it. – Gustavo Vollbrecht Commented Jul 9, 2019 at 7:48
  • 1 Hi @GustavoVollbrecht. I was on vacation and hoping I might get a few more answers. Thank you for your contribution but unfortunately we are still unable to catch the window.postMessage event. What is odd is we can capture any other event such as onClick. it is specifically window.postMessage(JSON.stringify(data), '*'); that we cannot capture. If you were interested, I could share the url of the site with you. Thanks again. I upvoted your answer. – MFD3000 Commented Jul 11, 2019 at 17:09
  • 2 @MFD3000, did you solve it? I ran into this as well. I got it working after changing to injectionTime: .atDocumentStart – Janno Teelem Commented Oct 3, 2019 at 15:34
  • 1 @JannoTeelem: My code now also works if I use atDocumentStart. It would be interesting if this answer would work for the OP. – testing Commented Nov 18, 2019 at 15:53
  • 1 @MFD3000 I am facing similar issue, did you resolve it? – subha Commented Aug 26, 2020 at 21:56
 |  Show 2 more comments

2 Answers 2

Reset to default 14

Yes, it is possible. You also need to set javascriptEnabled = true

self.webView.configuration.preferences.javaScriptEnabled = true

You can also configure the listener like this:

self.webView.configuration.userContentController.add(self, name: "iosListener")

And make sure you're applying both commands before

self.webView.load(/*some request*/)

You can make a simple test after the page didFinish loading with:

self.webView.evaluateJavaScript("window.webkit.messageHandlers.iosListener.postMessage('test');", completionHandler: { (result, err) in
    if (err != nil) {
        // show error feedback to user.
    }
})

Another advice is to always have ; at the end of commands on javascript code when interacting with webView as some can rely on standard javascript.

let source = """
    window.addEventListener('message', function(e) {
        window.webkit.messageHandlers.iosListener.postMessage(JSON.stringify(e.data));
    });
    """

Note: I'd also suggest to have the webView as a class variable instead of a method variable, you're probably creating it on viewDidLoad(), I'd suggest that you move the variable to your class.

var webView: WKWebView!

Here is what I did to get this working for me. I needed to display a registration page to the user in my app and that page calls window.opener.postMessage() to send data back to the original web page, but in this case I wanted it to go my view controller instead.

The key thing for me was to open the registration URL with javascript via window.open so that the WKWebView would call my delegate method webView(..., createWebViewWith ...) which returns a new WKWebView. The WKWebView will correctly set the window.opener so that the registration page can post messages back to my webpage which then sends the data to swift.

class GPRegisterDeviceWebViewController: UIViewController, WKScriptMessageHandler, WKUIDelegate, WKNavigationDelegate {
    
    let webView = WKWebView(forAutoLayout: ())!
    let registrationURL = "https://registrationURL"
    
    
    init() {
        super.init(nibName: nil, bundle: nil)
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        self.navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .cancel, target: self, action: #selector(onCancel))
        
        view.addSubview(webView)
        webView.translatesAutoresizingMaskIntoConstraints = false
        
        //We need to set the uiDelegate so that we can respond to webView(..., createWebViewWith ...)
        //We can't just open the registration URL becase that page calls the javascript
        //window.opener.postMessage("SOME MSG");
        //WKWebView will set the window.opener if you go through the delegate method createWebViewWith
        webView.uiDelegate = self
        webView.configuration.defaultWebpagePreferences.allowsContentJavaScript = true
        
        //For debugging, allow safari to inspect the page
        if #available(iOS 16.4, *) {
            webView.isInspectable = true
        }
        
        //This uses PureLayout to set up the views layout constraints
        webView.autoPinEdgesToSuperviewEdges(with: UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0))
        
        //We want to let this webview call back into our swift code.  To do this we need to
        //add this message handler.  So now in our javascript code we can call
        //window.webkit.messageHandlers.myEvent.postMessage("SOME MSG");
        //and it will call
        //userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage)
        //on our view controller
        webView.configuration.userContentController.add(self, name: "myEvent")
        
        
        //This is the base web page that just receives the message from the registration page.
        let html = """
        <html>
        <head><script type="text/javascript">
        function receiveMessage(event) {
          window.webkit.messageHandlers.myEvent.postMessage("Send data to swift");
        }

        function onPageLoad() {
            //We need to set up the message event listener.  
            //The registration page will call window.opener.postMessage() which
            //will call receiveMessage on our page.
            window.addEventListener("message", receiveMessage, false);
        }
        
        </script></head>
        <body onload="onPageLoad();">
        </body>
        </html>
        """

        //It is VERY important that this has the baseURL set.  We get an error saying that it
        //can't post to any other orgin if we don't do this.
        webView.loadHTMLString(html, baseURL: URL(string: "https://myBaseURL"))
                                        
        //Now we need to open the registration URL. It is VERY important that we go through the javascript window.open
        //to do this and that we don't just create the view here.
        //We need to do this so that WKWebView will set the window.opener by going through the delegate method createWebViewWith
        self.webView.evaluateJavaScript("window.open('\(registrationURL)');")
    }
    
    func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
        decisionHandler(.allow)
    }
    
    
    func webView(_ webView: WKWebView, createWebViewWith configuration: WKWebViewConfiguration, for navigationAction: WKNavigationAction, windowFeatures: WKWindowFeatures) -> WKWebView? {
        let popupWebView = WKWebView(frame: .zero, configuration: configuration)
        popupWebView.translatesAutoresizingMaskIntoConstraints = false
        popupWebView.navigationDelegate = self
        popupWebView.uiDelegate = self
        popupWebView.configuration.defaultWebpagePreferences.allowsContentJavaScript = true
        
        if #available(iOS 16.4, *) {
            //For debugging
            popupWebView.isInspectable = true
        }
        
        self.view.addSubview(popupWebView)
        
        //This uses PureLayout to set up the views layout constraints
        popupWebView.autoPinEdgesToSuperviewEdges(with: UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0))
        
        //VERY IMPORTANT!  We must return the view to set window.opener
        return popupWebView
    }
    
    func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
        if message.name == "myEvent" {
            if let body = message.body as? String {
                //body contains the data posted from our javascript function receiveMessage()
            }
        }
    }
    
    func webViewDidClose(_ webView: WKWebView) {
        self.navigationController?.dismiss(animated: true, completion: nil)
    }
    
    @objc func onCancel() {
        self.navigationController?.dismiss(animated: true, completion: nil)
    }

}
发布评论

评论列表(0)

  1. 暂无评论