import Foundation import UIKit import TelegramCore import SyncCore import WebKit import AsyncDisplayKit import Display import TelegramPresentationData private class WeakInstantPageWebEmbedNodeMessageHandler: NSObject, WKScriptMessageHandler { private let f: (WKScriptMessage) -> () init(_ f: @escaping (WKScriptMessage) -> ()) { self.f = f super.init() } func userContentController(_ controller: WKUserContentController, didReceive scriptMessage: WKScriptMessage) { self.f(scriptMessage) } } final class InstantPageWebEmbedNode: ASDisplayNode, InstantPageNode { let url: String? let html: String? let updateWebEmbedHeight: (CGFloat) -> Void private var webView: WKWebView? init(frame: CGRect, url: String?, html: String?, enableScrolling: Bool, updateWebEmbedHeight: @escaping (CGFloat) -> Void) { self.url = url self.html = html self.updateWebEmbedHeight = updateWebEmbedHeight super.init() let js = "var TelegramWebviewProxyProto = function() {}; " + "TelegramWebviewProxyProto.prototype.postEvent = function(eventName, eventData) { " + "window.webkit.messageHandlers.performAction.postMessage({'eventName': eventName, 'eventData': eventData}); " + "}; " + "var TelegramWebviewProxy = new TelegramWebviewProxyProto();" let configuration = WKWebViewConfiguration() let userController = WKUserContentController() let userScript = WKUserScript(source: js, injectionTime: .atDocumentStart, forMainFrameOnly: false) userController.addUserScript(userScript) userController.add(WeakInstantPageWebEmbedNodeMessageHandler { [weak self] message in if let strongSelf = self { strongSelf.handleScriptMessage(message) } }, name: "performAction") configuration.userContentController = userController let webView = WKWebView(frame: CGRect(origin: CGPoint(), size: frame.size), configuration: configuration) webView.allowsBackForwardNavigationGestures = false if #available(iOSApplicationExtension 9.0, iOS 9.0, *) { webView.allowsLinkPreview = false } if #available(iOSApplicationExtension 11.0, iOS 11.0, *) { webView.scrollView.contentInsetAdjustmentBehavior = .never } webView.scrollView.isScrollEnabled = enableScrolling if let html = html { webView.loadHTMLString(html, baseURL: nil) } else if let url = url, let parsedUrl = URL(string: url) { var request = URLRequest(url: parsedUrl) if let scheme = parsedUrl.scheme, let host = parsedUrl.host { let referrer = "\(scheme)://\(host)" request.setValue(referrer, forHTTPHeaderField: "Referer") } webView.load(request) } self.webView = webView } private func handleScriptMessage(_ message: WKScriptMessage) { guard let body = message.body as? [String: Any] else { return } guard let eventName = body["eventName"] as? String, let eventString = body["eventData"] as? String else { return } guard let eventData = eventString.data(using: .utf8) else { return } guard let dict = (try? JSONSerialization.jsonObject(with: eventData, options: [])) as? [String: Any] else { return } if eventName == "resize_frame", let height = dict["height"] as? Int { self.updateWebEmbedHeight(CGFloat(height)) } } override func didLoad() { super.didLoad() if let webView = self.webView { self.view.addSubview(webView) } } override func layout() { super.layout() self.webView?.frame = self.bounds } func transitionNode(media: InstantPageMedia) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { return nil } func updateHiddenMedia(media: InstantPageMedia?) { } func updateIsVisible(_ isVisible: Bool) { } func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) { } func update(strings: PresentationStrings, theme: InstantPageTheme) { } }