Files
AFFiNE-Mirror/packages/frontend/apps/ios/App/App/AffineHttpHandler.swift
T
2025-01-21 00:16:21 +00:00

128 lines
4.3 KiB
Swift

//
// RequestUrlSchemeHandler.swift
// App
//
// Created by EYHN on 2025/1/9.
//
import WebKit
enum AffineHttpError: Error {
case invalidOperation(reason: String), invalidState(reason: String)
}
class AffineHttpHandler: NSObject, WKURLSchemeHandler {
func webView(_ webView: WKWebView, start urlSchemeTask: any WKURLSchemeTask) {
urlSchemeTask.stopped = Mutex.init(false)
guard let rawUrl = urlSchemeTask.request.url else {
urlSchemeTask.didFailWithError(AffineHttpError.invalidOperation(reason: "bad request"))
return
}
guard let scheme = rawUrl.scheme else {
urlSchemeTask.didFailWithError(AffineHttpError.invalidOperation(reason: "bad request"))
return
}
let httpProtocol = scheme == "affine-http" ? "http" : "https"
guard let urlComponents = URLComponents(url: rawUrl, resolvingAgainstBaseURL: true) else {
urlSchemeTask.didFailWithError(AffineHttpError.invalidOperation(reason: "bad request"))
return
}
guard let host = urlComponents.host else {
urlSchemeTask.didFailWithError(AffineHttpError.invalidOperation(reason: "bad url"))
return
}
let path = urlComponents.path
let query = urlComponents.query != nil ? "?\(urlComponents.query!)" : ""
guard let targetUrl = URL(string: "\(httpProtocol)://\(host)\(path)\(query)") else {
urlSchemeTask.didFailWithError(AffineHttpError.invalidOperation(reason: "bad url"))
return
}
var request = URLRequest(url: targetUrl);
request.httpMethod = urlSchemeTask.request.httpMethod;
request.httpShouldHandleCookies = true
request.httpBody = urlSchemeTask.request.httpBody
urlSchemeTask.request.allHTTPHeaderFields?.filter({
key, value in
let normalizedKey = key.lowercased()
return normalizedKey == "content-type" ||
normalizedKey == "content-length" ||
normalizedKey == "accept"
}).forEach {
key, value in
request.setValue(value, forHTTPHeaderField: key)
}
let task = URLSession.shared.dataTask(with: request) {
(rawData, rawResponse, error) in
urlSchemeTask.stopped?.withLock({
if $0 {
return
}
if error != nil {
urlSchemeTask.didFailWithError(error!)
} else {
guard let httpResponse = rawResponse as? HTTPURLResponse else {
urlSchemeTask.didFailWithError(AffineHttpError.invalidState(reason: "bad response"))
return
}
let inheritedHeaders = httpResponse.allHeaderFields.filter({
key, value in
let normalizedKey = (key as? String)?.lowercased()
return normalizedKey == "content-type" ||
normalizedKey == "content-length"
}) as? [String: String] ?? [:]
let newHeaders: [String: String] = [
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "*"
]
guard let response = HTTPURLResponse.init(url: rawUrl, statusCode: httpResponse.statusCode, httpVersion: nil, headerFields: inheritedHeaders.merging(newHeaders, uniquingKeysWith: { (_, newHeaders) in newHeaders })) else {
urlSchemeTask.didFailWithError(AffineHttpError.invalidState(reason: "failed to create response"))
return
}
urlSchemeTask.didReceive(response)
if rawData != nil {
urlSchemeTask.didReceive(rawData!)
}
urlSchemeTask.didFinish()
}
})
}
task.resume()
urlSchemeTask.dataTask = task
}
func webView(_ webView: WKWebView, stop urlSchemeTask: WKURLSchemeTask) {
urlSchemeTask.stopped?.withLock({
$0 = true
})
urlSchemeTask.dataTask?.cancel()
}
}
private extension WKURLSchemeTask {
var stopped: Mutex<Bool>? {
get {
return objc_getAssociatedObject(self, &stoppedKey) as? Mutex<Bool> ?? nil
}
set {
objc_setAssociatedObject(self, &stoppedKey, newValue, .OBJC_ASSOCIATION_RETAIN)
}
}
var dataTask: URLSessionDataTask? {
get {
return objc_getAssociatedObject(self, &dataTaskKey) as? URLSessionDataTask
}
set {
objc_setAssociatedObject(self, &dataTaskKey, newValue, .OBJC_ASSOCIATION_RETAIN)
}
}
}
private var stoppedKey = malloc(1)
private var dataTaskKey = malloc(1)