Sources/Instrumentation/WKWebView/Cookie/WebViewCookieManager.swift (126 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
class WebViewCookieManager {
static var shared: WebViewCookieManager {
let instance = WebViewCookieManager()
return instance
}
private static let dateFormatter: DateFormatter = DateFormatter(timeZone: TimeZone(abbreviation: "UTC")!, dateFormat: "EEE, d MMM yyyy HH:mm:ss zzz")
static func syncRequestCookie(request: inout URLRequest) {
guard let url = request.url else {
return
}
guard let cookies: [HTTPCookie] = HTTPCookieStorage.shared.cookies(for: url), cookies.count > 0 else {
return
}
let requestHeader: [String: String] = HTTPCookie.requestHeaderFields(with: cookies)
let cookie = requestHeader["Cookie"]
request.setValue(cookie, forHTTPHeaderField: "Cookie")
}
static func fixRequest(request: inout URLRequest) -> URLRequest {
var fixedRequest: URLRequest = request
if let _ = request.url, let cookies = HTTPCookieStorage.shared.cookies(for: request.url!), cookies.count > 0 {
var cookieArray = [String]()
for cookie in cookies {
cookieArray.append("\(cookie.name)=\(cookie.value)")
}
fixedRequest.setValue(cookieArray.joined(separator: ";"), forHTTPHeaderField: "Cookie")
}
return fixedRequest
}
}
// MARK: - Cookie sync between HTTPCookieStore and WKHTTPCookieStore
extension WebViewCookieManager {
static func copyWKHTTPCookieStoreToHTTPCookieStore(webView: WKWebView, completion: (() -> ())?) {
if #available(iOS 11.0, *) {
let cookieStore = webView.configuration.websiteDataStore.httpCookieStore
cookieStore.getAllCookies { cookies in
if cookies.count == 0 {
completion?()
return
}
for cookie in cookies {
HTTPCookieStorage.shared.setCookie(cookie)
if let last = cookies.last, last.isEqual(cookie) {
completion?()
return
}
}
}
}
}
static func copyHTTPCookieStorageToWKHTTPCookieStorageOniOS11(webView: WKWebView, completion: (()->Void)?) {
if #available(iOS 11.0, *) {
guard let cookies = HTTPCookieStorage.shared.cookies, cookies.count > 0 else {
completion?()
return
}
let cookieStore = webView.configuration.websiteDataStore.httpCookieStore
for cookie in cookies {
cookieStore.setCookie(cookie) {
if let c = cookies.last, c.isEqual(cookie) {
completion?()
return
}
}
}
}
}
}
// MARK: - Cookie setter & getter for WKScriptMessageHandler
extension WebViewCookieManager {
func setCookie(params: [String: Any], callback: ((_ response: [String: Any]?) -> ())?) {
guard let cookies = params["cookie"] as? String else {
return
}
var properties = [HTTPCookiePropertyKey: Any]()
let segments = cookies.components(separatedBy: ";")
for (idx, sgmt) in segments.enumerated() {
let trimmedSgmt = sgmt.trimmingCharacters(in: CharacterSet.whitespaces)
let keyValues = trimmedSgmt.components(separatedBy: "=")
if (keyValues.count == 2 && keyValues[0].count > 0){
let key = keyValues[0].trimmingCharacters(in: CharacterSet.whitespaces)
let value = keyValues[1].trimmingCharacters(in: CharacterSet.whitespaces)
if (0 == idx) {
properties[HTTPCookiePropertyKey.name] = key
properties[HTTPCookiePropertyKey.value] = value
} else if (key == "domain") {
properties[HTTPCookiePropertyKey.domain] = value
} else if (key == "path") {
properties[HTTPCookiePropertyKey.path] = value
} else if (key == "expires") {
properties[HTTPCookiePropertyKey.expires] = WebViewCookieManager.dateFormatter.date(from: value)
}
} else if (keyValues.count == 1 && keyValues[0].count > 0) {
let key = keyValues[0].trimmingCharacters(in: CharacterSet.whitespaces)
if (key == "Secure") {
properties[HTTPCookiePropertyKey.secure] = true
}
}
}
if (properties.count > 0) {
if let cookieObject: HTTPCookie = HTTPCookie.init(properties: properties) {
HTTPCookieStorage.shared.setCookie(cookieObject)
}
}
}
func getCookie(params: [String: Any], callback: ((_ response: [String: Any]?) -> ())?) {
guard let url = params["url"] as? String else {
callback?(["cookie": ""])
return
}
guard let cookies: [HTTPCookie] = HTTPCookieStorage.shared.cookies(for: URL.init(string: url)!), cookies.count > 0 else {
callback?(["cookie": ""])
return
}
let header = HTTPCookie.requestHeaderFields(with: cookies)
callback?([
"cookie": header["Cookie"] ?? ""
])
}
}
// MARK: - DateFormatter
fileprivate extension DateFormatter {
convenience init(timeZone: TimeZone, dateFormat: String) {
self.init()
self.timeZone = timeZone
self.dateFormat = dateFormat
}
}