Sources/Instrumentation/WKWebView/WKWebViewDelegate.swift (96 lines of code) (raw):
//
// Copyright 2023 aliyun-sls Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import Foundation
import WebKit
// MARK: WKNavigationDelegate & WKUIDelegate
class WKWebViewDelegate: NSObject, WKNavigationDelegate {
// MARK: - WKNavigationDelegate
// before send request
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
if var request = navigationAction.request as? URLRequest {
WebViewCookieManager.syncRequestCookie(request: &request)
}
decisionHandler(.allow)
}
// after receive response
func webView(_ webView: WKWebView, decidePolicyFor navigationResponse: WKNavigationResponse, decisionHandler: @escaping (WKNavigationResponsePolicy) -> Void) {
if #available(iOS 11.0, *) {
// sync WKWebView cookie to HTTPCookieStore
WebViewCookieManager.copyWKHTTPCookieStoreToHTTPCookieStore(webView: webView, completion: nil)
} else {
// sync response 'Set-Cookie' from WKWebView to HTTPCookieStorage
if let response = navigationResponse.response as? HTTPURLResponse,
let url = response.url,
let allHeaderFields = response.allHeaderFields as? [String: String] {
let cookies = HTTPCookie.cookies(withResponseHeaderFields: allHeaderFields, for: url)
for cookie in cookies {
HTTPCookieStorage.shared.setCookie(cookie)
}
}
}
decisionHandler(.allow)
}
// after page jump
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
// ignored
}
// for authentication
func webView(_ webView: WKWebView, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
// ignored
completionHandler(.performDefaultHandling, nil)
}
func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) {
print("WKWebViewNavigationDelegate: \(error)")
}
func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error) {
print("WKWebViewNavigationDelegate: \(error)")
}
}
// MARK: - WKUIDelegate
extension WKWebViewDelegate: WKUIDelegate {
// create a new webview
func webView(_ webView: WKWebView, createWebViewWith configuration: WKWebViewConfiguration, for navigationAction: WKNavigationAction, windowFeatures: WKWindowFeatures) -> WKWebView? {
guard let bool = navigationAction.targetFrame?.isMainFrame, !bool else {
return nil
}
// sync cookie with '<a target="_blank" href="">' tag
var request = navigationAction.request
webView.load(WebViewCookieManager.fixRequest(request: &request))
return nil
}
// // alert panel
// func webView(_ webView: WKWebView, runJavaScriptAlertPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping () -> Void) {
//
// }
//
// // confirm panel
// func webView(_ webView: WKWebView, runJavaScriptConfirmPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping (Bool) -> Void) {
//
// }
// input panel
func webView(_ webView: WKWebView, runJavaScriptTextInputPanelWithPrompt prompt: String, defaultText: String?, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping (String?) -> Void) {
if self.handleSyncCallWithPrompt(webView: webView, prompt: prompt, defaultText: defaultText, completionHandler: completionHandler) {
return
}
}
// fileprivate func canShowPanelWithWebView(webView: WKWebView) -> Bool {
// if let delegate = webView.holderObject as? WKWebViewNavigationDelegate {
//
// }
// }
}
// MARK: - handle sync call with
extension WKWebViewDelegate {
func handleSyncCallWithPrompt(webView: WKWebView, prompt: String, defaultText: String?, completionHandler: @escaping (String?) -> Void) -> Bool {
if prompt != "OTelJSBridge" {
return false
}
guard let defaultText = defaultText else {
completionHandler(nil)
return false
}
guard let jsonData = defaultText.data(using: .utf8), let body = try? JSONSerialization.jsonObject(with: jsonData) as? [String: Any] else {
completionHandler(nil)
return false
}
let bridgeMessage = JSBridgeMessageModule(dictionary: body) { response in
guard let response = response, response.count > 0 else {
completionHandler(nil)
return
}
guard let data = try? JSONSerialization.data(withJSONObject: response) else {
completionHandler(nil)
return
}
guard let jsonString = String.init(data: data, encoding: .utf8) else {
completionHandler(nil)
return
}
completionHandler(jsonString)
}
WKWebViewInstrumentation.dispatchCallbackMessage(webView: webView, message: bridgeMessage)
return true
}
}
fileprivate extension WKWebView {
private struct AssociateKeys {
static var canShowPanelWithWebView = "canShowPanelWithWebView"
}
var holderObject: AnyObject? {
get {
return objc_getAssociatedObject(self, &AssociateKeys.canShowPanelWithWebView) as AnyObject
}
set {
objc_setAssociatedObject(self, &AssociateKeys.canShowPanelWithWebView, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
}
}