fix(core): fix ios cloud sync (#10243)

This commit is contained in:
EYHN
2025-02-18 16:12:49 +08:00
committed by GitHub
parent 73f3226f58
commit 892fd16f52
6 changed files with 129 additions and 390 deletions

View File

@@ -25,7 +25,6 @@
9D90BE2D2CCB9876006677DB /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 9D90BE222CCB9876006677DB /* Main.storyboard */; }; 9D90BE2D2CCB9876006677DB /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 9D90BE222CCB9876006677DB /* Main.storyboard */; };
9D90BE2E2CCB9876006677DB /* public in Resources */ = {isa = PBXBuildFile; fileRef = 9D90BE232CCB9876006677DB /* public */; }; 9D90BE2E2CCB9876006677DB /* public in Resources */ = {isa = PBXBuildFile; fileRef = 9D90BE232CCB9876006677DB /* public */; };
9DEC593B2D3002E70027CEBD /* AffineHttpHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DEC593A2D3002C70027CEBD /* AffineHttpHandler.swift */; }; 9DEC593B2D3002E70027CEBD /* AffineHttpHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DEC593A2D3002C70027CEBD /* AffineHttpHandler.swift */; };
9DEC593F2D30EFA40027CEBD /* AffineWsHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DEC593E2D30EFA40027CEBD /* AffineWsHandler.swift */; };
9DEC59432D323EE40027CEBD /* Mutex.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DEC59422D323EE00027CEBD /* Mutex.swift */; }; 9DEC59432D323EE40027CEBD /* Mutex.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DEC59422D323EE00027CEBD /* Mutex.swift */; };
9DFCD1462D27D1D70028C92B /* libaffine_mobile_native.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 9DFCD1452D27D1D70028C92B /* libaffine_mobile_native.a */; }; 9DFCD1462D27D1D70028C92B /* libaffine_mobile_native.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 9DFCD1452D27D1D70028C92B /* libaffine_mobile_native.a */; };
C4C413792CBE705D00337889 /* Pods_App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AF277DCFFFF123FFC6DF26C7 /* Pods_App.framework */; }; C4C413792CBE705D00337889 /* Pods_App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AF277DCFFFF123FFC6DF26C7 /* Pods_App.framework */; };
@@ -56,7 +55,6 @@
9D90BE212CCB9876006677DB /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; }; 9D90BE212CCB9876006677DB /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
9D90BE232CCB9876006677DB /* public */ = {isa = PBXFileReference; lastKnownFileType = folder; path = public; sourceTree = "<group>"; }; 9D90BE232CCB9876006677DB /* public */ = {isa = PBXFileReference; lastKnownFileType = folder; path = public; sourceTree = "<group>"; };
9DEC593A2D3002C70027CEBD /* AffineHttpHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AffineHttpHandler.swift; sourceTree = "<group>"; }; 9DEC593A2D3002C70027CEBD /* AffineHttpHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AffineHttpHandler.swift; sourceTree = "<group>"; };
9DEC593E2D30EFA40027CEBD /* AffineWsHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AffineWsHandler.swift; sourceTree = "<group>"; };
9DEC59422D323EE00027CEBD /* Mutex.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Mutex.swift; sourceTree = "<group>"; }; 9DEC59422D323EE00027CEBD /* Mutex.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Mutex.swift; sourceTree = "<group>"; };
9DFCD1452D27D1D70028C92B /* libaffine_mobile_native.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libaffine_mobile_native.a; sourceTree = "<group>"; }; 9DFCD1452D27D1D70028C92B /* libaffine_mobile_native.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libaffine_mobile_native.a; sourceTree = "<group>"; };
AF277DCFFFF123FFC6DF26C7 /* Pods_App.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_App.framework; sourceTree = BUILT_PRODUCTS_DIR; }; AF277DCFFFF123FFC6DF26C7 /* Pods_App.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_App.framework; sourceTree = BUILT_PRODUCTS_DIR; };
@@ -164,7 +162,6 @@
children = ( children = (
9DEC59422D323EE00027CEBD /* Mutex.swift */, 9DEC59422D323EE00027CEBD /* Mutex.swift */,
9DEC593A2D3002C70027CEBD /* AffineHttpHandler.swift */, 9DEC593A2D3002C70027CEBD /* AffineHttpHandler.swift */,
9DEC593E2D30EFA40027CEBD /* AffineWsHandler.swift */,
9D52FC422D26CDB600105D0A /* JSValueContainerExt.swift */, 9D52FC422D26CDB600105D0A /* JSValueContainerExt.swift */,
9D90BE1A2CCB9876006677DB /* Plugins */, 9D90BE1A2CCB9876006677DB /* Plugins */,
9D90BE1C2CCB9876006677DB /* AppDelegate.swift */, 9D90BE1C2CCB9876006677DB /* AppDelegate.swift */,
@@ -349,7 +346,6 @@
9D90BE252CCB9876006677DB /* CookieManager.swift in Sources */, 9D90BE252CCB9876006677DB /* CookieManager.swift in Sources */,
9D90BE262CCB9876006677DB /* CookiePlugin.swift in Sources */, 9D90BE262CCB9876006677DB /* CookiePlugin.swift in Sources */,
9D6A85332CCF6DA700DAB35F /* HashcashPlugin.swift in Sources */, 9D6A85332CCF6DA700DAB35F /* HashcashPlugin.swift in Sources */,
9DEC593F2D30EFA40027CEBD /* AffineWsHandler.swift in Sources */,
9D90BE272CCB9876006677DB /* AffineViewController.swift in Sources */, 9D90BE272CCB9876006677DB /* AffineViewController.swift in Sources */,
9D90BE282CCB9876006677DB /* AppDelegate.swift in Sources */, 9D90BE282CCB9876006677DB /* AppDelegate.swift in Sources */,
); );

View File

@@ -22,8 +22,6 @@ class AFFiNEViewController: CAPBridgeViewController {
override func webView(with frame: CGRect, configuration: WKWebViewConfiguration) -> WKWebView { override func webView(with frame: CGRect, configuration: WKWebViewConfiguration) -> WKWebView {
configuration.setURLSchemeHandler(AffineHttpHandler(), forURLScheme: "affine-http") configuration.setURLSchemeHandler(AffineHttpHandler(), forURLScheme: "affine-http")
configuration.setURLSchemeHandler(AffineHttpHandler(), forURLScheme: "affine-https") configuration.setURLSchemeHandler(AffineHttpHandler(), forURLScheme: "affine-https")
configuration.setURLSchemeHandler(AffineWsHandler(), forURLScheme: "affine-ws")
configuration.setURLSchemeHandler(AffineWsHandler(), forURLScheme: "affine-wss")
return super.webView(with: frame, configuration: configuration) return super.webView(with: frame, configuration: configuration)
} }

View File

@@ -1,194 +0,0 @@
//
// RequestUrlSchemeHandler.swift
// App
//
// Created by EYHN on 2025/1/9.
//
import WebKit
enum AffineWsError: Error {
case invalidOperation(reason: String), invalidState(reason: String)
}
/**
this custom url scheme handler simulates websocket connection through an http request.
frontend open websocket connections and send messages by sending requests to affine-ws:// or affine-wss://
the handler has two endpoints:
`affine-ws:///open?uuid={uuid}&url={wsUrl}`: open a websocket connection and return received data through the SSE protocol. If the front-end closes the http connection, the websocket connection will also be closed.
`affine-ws:///send?uuid={uuid}`: send the request body data to the websocket connection with the specified uuid.
*/
class AffineWsHandler: NSObject, WKURLSchemeHandler {
var wsTasks: [UUID: URLSessionWebSocketTask] = [:]
func webView(_ webView: WKWebView, start urlSchemeTask: any WKURLSchemeTask) {
urlSchemeTask.stopped = false
guard let rawUrl = urlSchemeTask.request.url else {
urlSchemeTask.didFailWithError(AffineWsError.invalidOperation(reason: "bad request"))
return
}
guard let urlComponents = URLComponents(url: rawUrl, resolvingAgainstBaseURL: true) else {
urlSchemeTask.didFailWithError(AffineWsError.invalidOperation(reason: "bad request"))
return
}
let path = urlComponents.path
if path == "/open" {
guard let targetUrlStr = urlComponents.queryItems?.first(where: { $0.name == "url" })?.value else {
urlSchemeTask.didFailWithError(AffineWsError.invalidOperation(reason: "url is request"))
return
}
guard let targetUrl = URL(string: targetUrlStr) else {
urlSchemeTask.didFailWithError(AffineWsError.invalidOperation(reason: "failed to parse url"))
return
}
guard let uuidStr = urlComponents.queryItems?.first(where: { $0.name == "uuid" })?.value else {
urlSchemeTask.didFailWithError(AffineWsError.invalidOperation(reason: "url is request"))
return
}
guard let uuid = UUID(uuidString: uuidStr) else {
urlSchemeTask.didFailWithError(AffineWsError.invalidOperation(reason: "invalid uuid"))
return
}
guard let response = HTTPURLResponse.init(url: rawUrl, statusCode: 200, httpVersion: nil, headerFields: [
"X-Accel-Buffering": "no",
"Content-Type": "text/event-stream",
"Cache-Control": "no-cache",
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "*"
]) else {
urlSchemeTask.didFailWithError(AffineHttpError.invalidState(reason: "failed to create response"))
return
}
urlSchemeTask.didReceive(response)
let jsonEncoder = JSONEncoder()
let json = String(data: try! jsonEncoder.encode(["type": "start"]), encoding: .utf8)!
urlSchemeTask.didReceive("data: \(json)\n\n".data(using: .utf8)!)
var request = URLRequest(url: targetUrl);
request.httpShouldHandleCookies = true
let webSocketTask = URLSession.shared.webSocketTask(with: targetUrl)
self.wsTasks[uuid] = webSocketTask
webSocketTask.resume()
urlSchemeTask.wsTask = webSocketTask
var completionHandler: ((Result<URLSessionWebSocketTask.Message, any Error>) -> Void)!
completionHandler = {
let result = $0
DispatchQueue.main.async {
if urlSchemeTask.stopped {
return
}
let jsonEncoder = JSONEncoder()
switch result {
case .success(let message):
if case .string(let string) = message {
let json = String(data: try! jsonEncoder.encode(["type": "message", "data": string]), encoding: .utf8)!
urlSchemeTask.didReceive("data: \(json)\n\n".data(using: .utf8)!)
}
case .failure(let error):
let json = String(data: try! jsonEncoder.encode(["type": "error", "error": error.localizedDescription]), encoding: .utf8)!
urlSchemeTask.didReceive("data: \(json)\n\n".data(using: .utf8)!)
urlSchemeTask.didFinish()
}
}
// recursive calls
webSocketTask.receive(completionHandler: completionHandler)
}
webSocketTask.receive(completionHandler: completionHandler)
} else if path == "/send" {
if urlSchemeTask.request.httpMethod != "POST" {
urlSchemeTask.didFailWithError(AffineWsError.invalidOperation(reason: "Method should be POST"))
return
}
guard let uuidStr = urlComponents.queryItems?.first(where: { $0.name == "uuid" })?.value else {
urlSchemeTask.didFailWithError(AffineWsError.invalidOperation(reason: "url is request"))
return
}
guard let uuid = UUID(uuidString: uuidStr) else {
urlSchemeTask.didFailWithError(AffineWsError.invalidOperation(reason: "invalid uuid"))
return
}
guard let ContentType = urlSchemeTask.request.allHTTPHeaderFields?.first(where: {$0.key.lowercased() == "content-type"})?.value else {
urlSchemeTask.didFailWithError(AffineWsError.invalidOperation(reason: "content-type is request"))
return
}
if ContentType != "text/plain" {
urlSchemeTask.didFailWithError(AffineWsError.invalidOperation(reason: "content-type not support"))
return
}
guard let body = urlSchemeTask.request.httpBody else {
urlSchemeTask.didFailWithError(AffineWsError.invalidOperation(reason: "no body"))
return
}
let stringBody = String(decoding: body, as: UTF8.self)
guard let webSocketTask = self.wsTasks[uuid] else {
urlSchemeTask.didFailWithError(AffineWsError.invalidOperation(reason: "connection not found"))
return
}
guard let response = HTTPURLResponse.init(url: rawUrl, statusCode: 200, httpVersion: nil, headerFields: [
"Content-Type": "application/json",
"Cache-Control": "no-cache",
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "*"
]) else {
urlSchemeTask.didFailWithError(AffineHttpError.invalidState(reason: "failed to create response"))
return
}
let jsonEncoder = JSONEncoder()
webSocketTask.send(.string(stringBody), completionHandler: {
error in
DispatchQueue.main.async {
if urlSchemeTask.stopped {
return
}
if error != nil {
let json = try! jsonEncoder.encode(["error": error!.localizedDescription])
urlSchemeTask.didReceive(response)
urlSchemeTask.didReceive(json)
} else {
urlSchemeTask.didReceive(response)
urlSchemeTask.didReceive(try! jsonEncoder.encode(["uuid": uuid.uuidString.data(using: .utf8)!]))
urlSchemeTask.didFinish()
}
}
})
}
}
func webView(_ webView: WKWebView, stop urlSchemeTask: WKURLSchemeTask) {
urlSchemeTask.stopped = true
urlSchemeTask.wsTask?.cancel(with: .abnormalClosure, reason: "Closed".data(using: .utf8))
}
}
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 wsTask: URLSessionWebSocketTask? {
get {
return objc_getAssociatedObject(self, &wsTaskKey) as? URLSessionWebSocketTask
}
set {
objc_setAssociatedObject(self, &wsTaskKey, newValue, .OBJC_ASSOCIATION_RETAIN)
}
}
}
private var stoppedKey = malloc(1)
private var wsTaskKey = malloc(1)

View File

@@ -24,6 +24,30 @@
} }
} }
} }
},
"NSCameraUsageDescription" : {
"comment" : "Privacy - Camera Usage Description",
"extractionState" : "extracted_with_value",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "new",
"value" : "AFFiNE requires access to the camera to capture images and insert them into your documents"
}
}
}
},
"NSPhotoLibraryUsageDescription" : {
"comment" : "Privacy - Photo Library Usage Description",
"extractionState" : "extracted_with_value",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "new",
"value" : "AFFiNE requires access to select photos from your photo library and insert them into your documents"
}
}
}
} }
}, },
"version" : "1.0" "version" : "1.0"

View File

@@ -281,7 +281,7 @@ private func makeRustCall<T, E: Swift.Error>(
_ callback: (UnsafeMutablePointer<RustCallStatus>) -> T, _ callback: (UnsafeMutablePointer<RustCallStatus>) -> T,
errorHandler: ((RustBuffer) throws -> E)? errorHandler: ((RustBuffer) throws -> E)?
) throws -> T { ) throws -> T {
uniffiEnsureInitialized() uniffiEnsureAffineMobileNativeInitialized()
var callStatus = RustCallStatus.init() var callStatus = RustCallStatus.init()
let returnedVal = callback(&callStatus) let returnedVal = callback(&callStatus)
try uniffiCheckCallStatus(callStatus: callStatus, errorHandler: errorHandler) try uniffiCheckCallStatus(callStatus: callStatus, errorHandler: errorHandler)
@@ -352,9 +352,10 @@ private func uniffiTraitInterfaceCallWithError<T, E>(
callStatus.pointee.errorBuf = FfiConverterString.lower(String(describing: error)) callStatus.pointee.errorBuf = FfiConverterString.lower(String(describing: error))
} }
} }
fileprivate class UniffiHandleMap<T> { fileprivate final class UniffiHandleMap<T>: @unchecked Sendable {
private var map: [UInt64: T] = [:] // All mutation happens with this lock held, which is why we implement @unchecked Sendable.
private let lock = NSLock() private let lock = NSLock()
private var map: [UInt64: T] = [:]
private var currentHandle: UInt64 = 1 private var currentHandle: UInt64 = 1
func insert(obj: T) -> UInt64 { func insert(obj: T) -> UInt64 {
@@ -496,7 +497,7 @@ fileprivate struct FfiConverterString: FfiConverter {
public protocol DocStoragePoolProtocol : AnyObject { public protocol DocStoragePoolProtocol: AnyObject {
func clearClocks(universalId: String) async throws func clearClocks(universalId: String) async throws
@@ -554,9 +555,7 @@ public protocol DocStoragePoolProtocol : AnyObject {
func setSpaceId(universalId: String, spaceId: String) async throws func setSpaceId(universalId: String, spaceId: String) async throws
} }
open class DocStoragePool: DocStoragePoolProtocol, @unchecked Sendable {
open class DocStoragePool:
DocStoragePoolProtocol {
fileprivate let pointer: UnsafeMutableRawPointer! fileprivate let pointer: UnsafeMutableRawPointer!
/// Used to instantiate a [FFIObject] without an actual pointer, for fakes in tests, mostly. /// Used to instantiate a [FFIObject] without an actual pointer, for fakes in tests, mostly.
@@ -954,7 +953,7 @@ open func setBlob(universalId: String, blob: SetBlob)async throws {
rustFutureFunc: { rustFutureFunc: {
uniffi_affine_mobile_native_fn_method_docstoragepool_set_blob( uniffi_affine_mobile_native_fn_method_docstoragepool_set_blob(
self.uniffiClonePointer(), self.uniffiClonePointer(),
FfiConverterString.lower(universalId),FfiConverterTypeSetBlob.lower(blob) FfiConverterString.lower(universalId),FfiConverterTypeSetBlob_lower(blob)
) )
}, },
pollFunc: ffi_affine_mobile_native_rust_future_poll_void, pollFunc: ffi_affine_mobile_native_rust_future_poll_void,
@@ -971,7 +970,7 @@ open func setDocSnapshot(universalId: String, snapshot: DocRecord)async throws
rustFutureFunc: { rustFutureFunc: {
uniffi_affine_mobile_native_fn_method_docstoragepool_set_doc_snapshot( uniffi_affine_mobile_native_fn_method_docstoragepool_set_doc_snapshot(
self.uniffiClonePointer(), self.uniffiClonePointer(),
FfiConverterString.lower(universalId),FfiConverterTypeDocRecord.lower(snapshot) FfiConverterString.lower(universalId),FfiConverterTypeDocRecord_lower(snapshot)
) )
}, },
pollFunc: ffi_affine_mobile_native_rust_future_poll_i8, pollFunc: ffi_affine_mobile_native_rust_future_poll_i8,
@@ -1053,6 +1052,7 @@ open func setSpaceId(universalId: String, spaceId: String)async throws {
} }
#if swift(>=5.8) #if swift(>=5.8)
@_documentation(visibility: private) @_documentation(visibility: private)
#endif #endif
@@ -1088,8 +1088,6 @@ public struct FfiConverterTypeDocStoragePool: FfiConverter {
} }
#if swift(>=5.8) #if swift(>=5.8)
@_documentation(visibility: private) @_documentation(visibility: private)
#endif #endif
@@ -1105,6 +1103,8 @@ public func FfiConverterTypeDocStoragePool_lower(_ value: DocStoragePool) -> Uns
} }
public struct Blob { public struct Blob {
public var key: String public var key: String
public var data: String public var data: String
@@ -1123,6 +1123,9 @@ public struct Blob {
} }
} }
#if compiler(>=6)
extension Blob: Sendable {}
#endif
extension Blob: Equatable, Hashable { extension Blob: Equatable, Hashable {
@@ -1155,6 +1158,7 @@ extension Blob: Equatable, Hashable {
} }
#if swift(>=5.8) #if swift(>=5.8)
@_documentation(visibility: private) @_documentation(visibility: private)
#endif #endif
@@ -1207,6 +1211,9 @@ public struct DocClock {
} }
} }
#if compiler(>=6)
extension DocClock: Sendable {}
#endif
extension DocClock: Equatable, Hashable { extension DocClock: Equatable, Hashable {
@@ -1227,6 +1234,7 @@ extension DocClock: Equatable, Hashable {
} }
#if swift(>=5.8) #if swift(>=5.8)
@_documentation(visibility: private) @_documentation(visibility: private)
#endif #endif
@@ -1275,6 +1283,9 @@ public struct DocRecord {
} }
} }
#if compiler(>=6)
extension DocRecord: Sendable {}
#endif
extension DocRecord: Equatable, Hashable { extension DocRecord: Equatable, Hashable {
@@ -1299,6 +1310,7 @@ extension DocRecord: Equatable, Hashable {
} }
#if swift(>=5.8) #if swift(>=5.8)
@_documentation(visibility: private) @_documentation(visibility: private)
#endif #endif
@@ -1349,6 +1361,9 @@ public struct DocUpdate {
} }
} }
#if compiler(>=6)
extension DocUpdate: Sendable {}
#endif
extension DocUpdate: Equatable, Hashable { extension DocUpdate: Equatable, Hashable {
@@ -1373,6 +1388,7 @@ extension DocUpdate: Equatable, Hashable {
} }
#if swift(>=5.8) #if swift(>=5.8)
@_documentation(visibility: private) @_documentation(visibility: private)
#endif #endif
@@ -1425,6 +1441,9 @@ public struct ListedBlob {
} }
} }
#if compiler(>=6)
extension ListedBlob: Sendable {}
#endif
extension ListedBlob: Equatable, Hashable { extension ListedBlob: Equatable, Hashable {
@@ -1453,6 +1472,7 @@ extension ListedBlob: Equatable, Hashable {
} }
#if swift(>=5.8) #if swift(>=5.8)
@_documentation(visibility: private) @_documentation(visibility: private)
#endif #endif
@@ -1505,6 +1525,9 @@ public struct SetBlob {
} }
} }
#if compiler(>=6)
extension SetBlob: Sendable {}
#endif
extension SetBlob: Equatable, Hashable { extension SetBlob: Equatable, Hashable {
@@ -1529,6 +1552,7 @@ extension SetBlob: Equatable, Hashable {
} }
#if swift(>=5.8) #if swift(>=5.8)
@_documentation(visibility: private) @_documentation(visibility: private)
#endif #endif
@@ -1627,14 +1651,32 @@ public struct FfiConverterTypeUniffiError: FfiConverterRustBuffer {
} }
#if swift(>=5.8)
@_documentation(visibility: private)
#endif
public func FfiConverterTypeUniffiError_lift(_ buf: RustBuffer) throws -> UniffiError {
return try FfiConverterTypeUniffiError.lift(buf)
}
#if swift(>=5.8)
@_documentation(visibility: private)
#endif
public func FfiConverterTypeUniffiError_lower(_ value: UniffiError) -> RustBuffer {
return FfiConverterTypeUniffiError.lower(value)
}
extension UniffiError: Equatable, Hashable {} extension UniffiError: Equatable, Hashable {}
extension UniffiError: Foundation.LocalizedError { extension UniffiError: Foundation.LocalizedError {
public var errorDescription: String? { public var errorDescription: String? {
String(reflecting: self) String(reflecting: self)
} }
} }
#if swift(>=5.8) #if swift(>=5.8)
@_documentation(visibility: private) @_documentation(visibility: private)
#endif #endif
@@ -1843,9 +1885,9 @@ fileprivate func uniffiRustCallAsync<F, T>(
liftFunc: (F) throws -> T, liftFunc: (F) throws -> T,
errorHandler: ((RustBuffer) throws -> Swift.Error)? errorHandler: ((RustBuffer) throws -> Swift.Error)?
) async throws -> T { ) async throws -> T {
// Make sure to call uniffiEnsureInitialized() since future creation doesn't have a // Make sure to call the ensure init function since future creation doesn't have a
// RustCallStatus param, so doesn't use makeRustCall() // RustCallStatus param, so doesn't use makeRustCall()
uniffiEnsureInitialized() uniffiEnsureAffineMobileNativeInitialized()
let rustFuture = rustFutureFunc() let rustFuture = rustFutureFunc()
defer { defer {
freeFunc(rustFuture) freeFunc(rustFuture)
@@ -1885,7 +1927,7 @@ public func hashcashMint(resource: String, bits: UInt32) -> String {
}) })
} }
public func newDocStoragePool() -> DocStoragePool { public func newDocStoragePool() -> DocStoragePool {
return try! FfiConverterTypeDocStoragePool.lift(try! rustCall() { return try! FfiConverterTypeDocStoragePool_lift(try! rustCall() {
uniffi_affine_mobile_native_fn_func_new_doc_storage_pool($0 uniffi_affine_mobile_native_fn_func_new_doc_storage_pool($0
) )
}) })
@@ -1898,9 +1940,9 @@ private enum InitializationResult {
} }
// Use a global variable to perform the versioning checks. Swift ensures that // Use a global variable to perform the versioning checks. Swift ensures that
// the code inside is only computed once. // the code inside is only computed once.
private var initializationResult: InitializationResult = { private let initializationResult: InitializationResult = {
// Get the bindings contract version from our ComponentInterface // Get the bindings contract version from our ComponentInterface
let bindings_contract_version = 26 let bindings_contract_version = 29
// Get the scaffolding contract version by calling the into the dylib // Get the scaffolding contract version by calling the into the dylib
let scaffolding_contract_version = ffi_affine_mobile_native_uniffi_contract_version() let scaffolding_contract_version = ffi_affine_mobile_native_uniffi_contract_version()
if bindings_contract_version != scaffolding_contract_version { if bindings_contract_version != scaffolding_contract_version {
@@ -1994,7 +2036,9 @@ private var initializationResult: InitializationResult = {
return InitializationResult.ok return InitializationResult.ok
}() }()
private func uniffiEnsureInitialized() { // Make the ensure init function public so that other modules which have external type references to
// our types can call it.
public func uniffiEnsureAffineMobileNativeInitialized() {
switch initializationResult { switch initializationResult {
case .ok: case .ok:
break break

View File

@@ -1,7 +1,7 @@
import '@affine/core/bootstrap/browser'; import '@affine/core/bootstrap/browser';
/** /**
* the below code includes the custom fetch and websocket implementation for ios webview. * the below code includes the custom fetch and xmlhttprequest implementation for ios webview.
* should be included in the entry file of the app or webworker. * should be included in the entry file of the app or webworker.
*/ */
@@ -38,154 +38,25 @@ globalThis.fetch = async (input: RequestInfo | URL, init?: RequestInit) => {
return rawFetch(url, input instanceof Request ? input : init); return rawFetch(url, input instanceof Request ? input : init);
}; };
/** const rawXMLHttpRequest = globalThis.XMLHttpRequest;
* we create a custom websocket class to simulate the browser's websocket connection globalThis.XMLHttpRequest = class extends rawXMLHttpRequest {
* through the custom url scheme handler. override open(
* method: string,
* to overcome the restrictions of cross-domain and third-party cookies in ios webview, url: string,
* the front-end opens a websocket connection and sends a message by sending a request async?: boolean,
* to `affine-ws://` or `affine-wss://`. user?: string,
* password?: string
* the scheme has two endpoints:
*
* `affine-ws:///open?uuid={uuid}&url={wsUrl}`: opens a websocket connection and returns
* the received data via the SSE protocol.
* If the front-end closes the http connection, the websocket connection will also be closed.
*
* `affine-ws:///send?uuid={uuid}`: sends the request body data to the websocket connection
* with the specified uuid.
*/
class WrappedWebSocket {
static CLOSED = WebSocket.CLOSED;
static CLOSING = WebSocket.CLOSING;
static CONNECTING = WebSocket.CONNECTING;
static OPEN = WebSocket.OPEN;
readonly isWss: boolean;
readonly uuid = crypto.randomUUID();
readyState: number = WebSocket.CONNECTING;
events: Record<string, ((event: any) => void)[]> = {};
onopen: ((event: any) => void) | undefined = undefined;
onclose: ((event: any) => void) | undefined = undefined;
onerror: ((event: any) => void) | undefined = undefined;
onmessage: ((event: any) => void) | undefined = undefined;
eventSource: EventSource;
constructor(
readonly url: string,
_protocols?: string | string[] // not supported yet
) { ) {
const parsedUrl = new URL(url); let normalizedUrl = new URL(url, globalThis.location.origin).href;
this.isWss = parsedUrl.protocol === 'wss:';
this.eventSource = new EventSource( if (normalizedUrl.startsWith('http:')) {
`${this.isWss ? 'affine-wss' : 'affine-ws'}:///open?uuid=${this.uuid}&url=${encodeURIComponent(this.url)}` url = 'affine-http:' + url.slice(5);
);
this.eventSource.addEventListener('open', () => {
this.emitOpen(new Event('open'));
});
this.eventSource.addEventListener('error', () => {
this.eventSource.close();
this.emitError(new Event('error'));
this.emitClose(new CloseEvent('close'));
});
this.eventSource.addEventListener('message', data => {
const decodedData = JSON.parse(data.data);
if (decodedData.type === 'message') {
this.emitMessage(
new MessageEvent('message', { data: decodedData.data })
);
}
});
} }
send(data: string) { if (normalizedUrl.startsWith('https:')) {
rawFetch( url = 'affine-https:' + url.slice(6);
`${this.isWss ? 'affine-wss' : 'affine-ws'}:///send?uuid=${this.uuid}`,
{
method: 'POST',
headers: {
'Content-Type': 'text/plain',
},
body: data,
}
).catch(e => {
console.error('Failed to send message', e);
});
} }
close() { (super.open as any)(method, url, async, user, password);
this.eventSource.close();
this.emitClose(new CloseEvent('close'));
} }
};
addEventListener(type: string, listener: (event: any) => void) {
this.events[type] = this.events[type] || [];
this.events[type].push(listener);
}
removeEventListener(type: string, listener: (event: any) => void) {
this.events[type] = this.events[type] || [];
this.events[type] = this.events[type].filter(l => l !== listener);
}
private emitOpen(event: Event) {
this.readyState = WebSocket.OPEN;
this.events['open']?.forEach(listener => {
try {
listener(event);
} catch (e) {
console.error(e);
}
});
try {
this.onopen?.(event);
} catch (e) {
console.error(e);
}
}
private emitClose(event: CloseEvent) {
this.readyState = WebSocket.CLOSED;
this.events['close']?.forEach(listener => {
try {
listener(event);
} catch (e) {
console.error(e);
}
});
try {
this.onclose?.(event);
} catch (e) {
console.error(e);
}
}
private emitMessage(event: MessageEvent) {
this.events['message']?.forEach(listener => {
try {
listener(event);
} catch (e) {
console.error(e);
}
});
try {
this.onmessage?.(event);
} catch (e) {
console.error(e);
}
}
private emitError(event: Event) {
this.events['error']?.forEach(listener => {
try {
listener(event);
} catch (e) {
console.error(e);
}
});
try {
this.onerror?.(event);
} catch (e) {
console.error(e);
}
}
}
globalThis.WebSocket = WrappedWebSocket as any;