mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-18 06:47:02 +08:00
fix(core): fix ios blob upload (#10263)
This commit is contained in:
@@ -1,126 +0,0 @@
|
||||
//
|
||||
// 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 = 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!)" : ""
|
||||
let port = urlComponents.port != nil ? ":\(urlComponents.port!)" : ""
|
||||
guard let targetUrl = URL(string: "\(httpProtocol)://\(host)\(port)\(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
|
||||
DispatchQueue.main.async {
|
||||
if urlSchemeTask.stopped {
|
||||
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 = true
|
||||
urlSchemeTask.dataTask?.cancel()
|
||||
}
|
||||
}
|
||||
|
||||
private extension WKURLSchemeTask {
|
||||
var stopped: Bool {
|
||||
get {
|
||||
return objc_getAssociatedObject(self, &stoppedKey) as? Bool ?? false
|
||||
}
|
||||
set {
|
||||
objc_setAssociatedObject(self, &stoppedKey, newValue, .OBJC_ASSOCIATION_ASSIGN)
|
||||
}
|
||||
}
|
||||
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)
|
||||
@@ -20,13 +20,12 @@ class AFFiNEViewController: CAPBridgeViewController {
|
||||
}
|
||||
|
||||
override func webView(with frame: CGRect, configuration: WKWebViewConfiguration) -> WKWebView {
|
||||
configuration.setURLSchemeHandler(AffineHttpHandler(), forURLScheme: "affine-http")
|
||||
configuration.setURLSchemeHandler(AffineHttpHandler(), forURLScheme: "affine-https")
|
||||
return super.webView(with: frame, configuration: configuration)
|
||||
}
|
||||
|
||||
override func capacitorDidLoad() {
|
||||
let plugins: [CAPPlugin] = [
|
||||
AuthPlugin(),
|
||||
CookiePlugin(),
|
||||
HashcashPlugin(),
|
||||
NavigationGesturePlugin(),
|
||||
|
||||
170
packages/frontend/apps/ios/App/App/Plugins/Auth/AuthPlugin.swift
Normal file
170
packages/frontend/apps/ios/App/App/Plugins/Auth/AuthPlugin.swift
Normal file
@@ -0,0 +1,170 @@
|
||||
import Capacitor
|
||||
import Foundation
|
||||
|
||||
public class AuthPlugin: CAPPlugin, CAPBridgedPlugin {
|
||||
public let identifier = "AuthPlugin"
|
||||
public let jsName = "Auth"
|
||||
public let pluginMethods: [CAPPluginMethod] = [
|
||||
CAPPluginMethod(name: "signInMagicLink", returnType: CAPPluginReturnPromise),
|
||||
CAPPluginMethod(name: "signInOauth", returnType: CAPPluginReturnPromise),
|
||||
CAPPluginMethod(name: "signInPassword", returnType: CAPPluginReturnPromise),
|
||||
CAPPluginMethod(name: "signOut", returnType: CAPPluginReturnPromise),
|
||||
]
|
||||
|
||||
@objc public func signInMagicLink(_ call: CAPPluginCall) {
|
||||
Task {
|
||||
do {
|
||||
let endpoint = try call.getStringEnsure("endpoint")
|
||||
let email = try call.getStringEnsure("email")
|
||||
let token = try call.getStringEnsure("token")
|
||||
|
||||
let (data, response) = try await self.fetch(endpoint, method: "POST", action: "/api/auth/magic-link", headers: [:], body: ["email": email, "token": token])
|
||||
|
||||
if response.statusCode != 200 {
|
||||
if let textBody = String(data: data, encoding: .utf8) {
|
||||
call.reject(textBody)
|
||||
} else {
|
||||
call.reject("Failed to sign in")
|
||||
}
|
||||
}
|
||||
|
||||
guard let token = try self.tokenFromCookie(endpoint) else {
|
||||
call.reject("token not found")
|
||||
return
|
||||
}
|
||||
|
||||
call.resolve(["token": token])
|
||||
} catch {
|
||||
call.reject("Failed to sign in, \(error)", nil, error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@objc public func signInOauth(_ call: CAPPluginCall) {
|
||||
Task {
|
||||
do {
|
||||
let endpoint = try call.getStringEnsure("endpoint")
|
||||
let code = try call.getStringEnsure("code")
|
||||
let state = try call.getStringEnsure("state")
|
||||
|
||||
let (data, response) = try await self.fetch(endpoint, method: "POST", action: "/api/oauth/callback", headers: [:], body: ["code": code, "state": state])
|
||||
|
||||
if response.statusCode != 200 {
|
||||
if let textBody = String(data: data, encoding: .utf8) {
|
||||
call.reject(textBody)
|
||||
} else {
|
||||
call.reject("Failed to sign in")
|
||||
}
|
||||
}
|
||||
|
||||
guard let token = try self.tokenFromCookie(endpoint) else {
|
||||
call.reject("token not found")
|
||||
return
|
||||
}
|
||||
|
||||
call.resolve(["token": token])
|
||||
} catch {
|
||||
call.reject("Failed to sign in, \(error)", nil, error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@objc public func signInPassword(_ call: CAPPluginCall) {
|
||||
Task {
|
||||
do {
|
||||
let endpoint = try call.getStringEnsure("endpoint")
|
||||
let email = try call.getStringEnsure("email")
|
||||
let password = try call.getStringEnsure("password")
|
||||
let verifyToken = call.getString("verifyToken")
|
||||
let challenge = call.getString("challenge")
|
||||
|
||||
let (data, response) = try await self.fetch(endpoint, method: "POST", action: "/api/auth/sign-in", headers: [
|
||||
"x-captcha-token": verifyToken,
|
||||
"x-captcha-challenge": challenge,
|
||||
], body: ["email": email, "password": password])
|
||||
|
||||
if response.statusCode != 200 {
|
||||
if let textBody = String(data: data, encoding: .utf8) {
|
||||
call.reject(textBody)
|
||||
} else {
|
||||
call.reject("Failed to sign in")
|
||||
}
|
||||
}
|
||||
|
||||
guard let token = try self.tokenFromCookie(endpoint) else {
|
||||
call.reject("token not found")
|
||||
return
|
||||
}
|
||||
|
||||
call.resolve(["token": token])
|
||||
} catch {
|
||||
call.reject("Failed to sign in, \(error)", nil, error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@objc public func signOut(_ call: CAPPluginCall) {
|
||||
Task {
|
||||
do {
|
||||
let endpoint = try call.getStringEnsure("endpoint")
|
||||
|
||||
let (data, response) = try await self.fetch(endpoint, method: "GET", action: "/api/auth/sign-out", headers: [:], body: nil)
|
||||
|
||||
if response.statusCode != 200 {
|
||||
if let textBody = String(data: data, encoding: .utf8) {
|
||||
call.reject(textBody)
|
||||
} else {
|
||||
call.reject("Failed to sign in")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
call.resolve(["ok": true])
|
||||
} catch {
|
||||
call.reject("Failed to sign in, \(error)", nil, error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func tokenFromCookie(_ endpoint: String) throws -> String? {
|
||||
guard let endpointUrl = URL(string: endpoint) else {
|
||||
throw AuthError.invalidEndpoint
|
||||
}
|
||||
|
||||
if let cookie = HTTPCookieStorage.shared.cookies(for: endpointUrl)?.first(where: {
|
||||
$0.name == "affine_session"
|
||||
}) {
|
||||
return cookie.value
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
private func fetch(_ endpoint: String, method: String, action: String, headers: Dictionary<String, String?>, body: Encodable?) async throws -> (Data, HTTPURLResponse) {
|
||||
guard let targetUrl = URL(string: "\(endpoint)\(action)") else {
|
||||
throw AuthError.invalidEndpoint
|
||||
}
|
||||
|
||||
var request = URLRequest(url: targetUrl);
|
||||
request.httpMethod = method;
|
||||
request.httpShouldHandleCookies = true
|
||||
for (key, value) in headers {
|
||||
request.setValue(value, forHTTPHeaderField: key)
|
||||
}
|
||||
if body != nil {
|
||||
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
|
||||
request.httpBody = try JSONEncoder().encode(body!)
|
||||
}
|
||||
request.timeoutInterval = 10 // time out 10s
|
||||
|
||||
let (data, response) = try await URLSession.shared.data(for: request);
|
||||
guard let httpResponse = response as? HTTPURLResponse else {
|
||||
throw AuthError.internalError
|
||||
}
|
||||
return (data, httpResponse)
|
||||
}
|
||||
}
|
||||
|
||||
enum AuthError: Error {
|
||||
case invalidEndpoint, internalError
|
||||
}
|
||||
Reference in New Issue
Block a user