diff --git a/ios/Internxt/FileProviderDomainManager.swift b/ios/Internxt/FileProviderDomainManager.swift index 2d714704e..60ae0360c 100644 --- a/ios/Internxt/FileProviderDomainManager.swift +++ b/ios/Internxt/FileProviderDomainManager.swift @@ -22,6 +22,20 @@ enum FileProviderDomainManager { } } + static func signalEnumeration(completion: @escaping (Error?) -> Void) { + guard let manager = NSFileProviderManager(for: domain) else { + completion(NSError( + domain: "FileProviderDomainManager", + code: -1, + userInfo: [NSLocalizedDescriptionKey: "No NSFileProviderManager for domain \(domainIdentifier.rawValue)"] + )) + return + } + manager.signalEnumerator(for: .workingSet) { error in + completion(error) + } + } + private static func isAlreadyExists(_ error: Error?) -> Bool { guard let error = error as NSError? else { return false } return error.domain == NSCocoaErrorDomain && error.code == NSFileWriteFileExistsError diff --git a/ios/Internxt/InternxtAuthCredentialsModule.swift b/ios/Internxt/InternxtAuthCredentialsModule.swift index d5f7613f7..2f8391722 100644 --- a/ios/Internxt/InternxtAuthCredentialsModule.swift +++ b/ios/Internxt/InternxtAuthCredentialsModule.swift @@ -26,7 +26,12 @@ class InternxtAuthCredentialsModule: NSObject { rejecter("E_REGISTER_DOMAIN", error.localizedDescription, error as NSError) return } - resolver(nil) + FileProviderDomainManager.signalEnumeration { signalError in + if let signalError = signalError { + NSLog("InternxtAuthCredentialsModule: signalEnumeration failed (ignored): \(signalError.localizedDescription)") + } + resolver(nil) + } } } @@ -56,6 +61,7 @@ class InternxtAuthCredentialsModule: NSObject { writeIfPresent(creds["rootFolderUuid"], to: SharedAuthKeychain.rootFolderIdKey) writeIfPresent(creds["bridgeUser"], to: SharedAuthKeychain.bridgeUserKey) writeIfPresent(creds["userId"], to: SharedAuthKeychain.userIdKey) + writeIfPresent(creds["driveBaseUrl"], to: SharedAuthKeychain.driveBaseUrlKey) } private func writeIfPresent(_ value: Any?, to sharedKey: String) { diff --git a/ios/InternxtFileProvider/FileProviderExtension.swift b/ios/InternxtFileProvider/FileProviderExtension.swift index c1802b584..f59fba9c9 100644 --- a/ios/InternxtFileProvider/FileProviderExtension.swift +++ b/ios/InternxtFileProvider/FileProviderExtension.swift @@ -9,31 +9,70 @@ import FileProvider import InternxtSwiftCore class FileProviderExtension: NSObject, NSFileProviderReplicatedExtension { + private let rootDisplayName: String + required init(domain: NSFileProviderDomain) { + self.rootDisplayName = domain.displayName super.init() } - private func currentAuthToken() -> String? { - guard let data = SharedAuthKeychain.read(SharedAuthKeychain.photosTokenKey) else { return nil } - return String(data: data, encoding: .utf8) - } - func invalidate() { // TODO: cleanup any resources } func item(for identifier: NSFileProviderItemIdentifier, request: NSFileProviderRequest, completionHandler: @escaping (NSFileProviderItem?, Error?) -> Void) -> Progress { - _ = currentAuthToken() + if identifier == .rootContainer { + let rootItem = FileProviderItem.root(displayName: rootDisplayName) + completionHandler(rootItem, nil) + return Progress() + } - // TODO: implement the actual lookup using the auth token + guard let decoded = FileProviderItemID.decode(identifier) else { + completionHandler(nil, NSFileProviderError(.noSuchItem)) + return Progress() + } - completionHandler(FileProviderItem(identifier: identifier), nil) - return Progress() + guard let driveAPI = DriveAPIFactory.make() else { + completionHandler(nil, NSFileProviderError(.notAuthenticated)) + return Progress() + } + + let progress = Progress(totalUnitCount: 1) + Task { + do { + let item = try await resolveItem(decoded, identifier: identifier, driveAPI: driveAPI) + progress.completedUnitCount = 1 + completionHandler(item, nil) + } catch { + completionHandler(nil, lookupError(from: error)) + } + } + return progress } - func fetchContents(for itemIdentifier: NSFileProviderItemIdentifier, version requestedVersion: NSFileProviderItemVersion?, request: NSFileProviderRequest, completionHandler: @escaping (URL?, NSFileProviderItem?, Error?) -> Void) -> Progress { - _ = currentAuthToken() + private func resolveItem( + _ decoded: (kind: DriveItemKind, uuid: String), + identifier: NSFileProviderItemIdentifier, + driveAPI: DriveAPI + ) async throws -> FileProviderItem { + switch decoded.kind { + case .folder: + let meta = try await driveAPI.getFolderMetaByUuid(uuid: decoded.uuid) + return FileProviderItem(folderMeta: meta, identifier: identifier) + case .file: + let meta = try await driveAPI.getFileMetaByUuid(uuid: decoded.uuid) + return FileProviderItem(fileMeta: meta, identifier: identifier) + } + } + private func lookupError(from error: Error) -> Error { + if let apiError = error as? APIClientError, apiError.statusCode == 401 { + return NSFileProviderError(.notAuthenticated) + } + return error + } + + func fetchContents(for itemIdentifier: NSFileProviderItemIdentifier, version requestedVersion: NSFileProviderItemVersion?, request: NSFileProviderRequest, completionHandler: @escaping (URL?, NSFileProviderItem?, Error?) -> Void) -> Progress { // TODO: implement fetching of the contents for the itemIdentifier at the specified version completionHandler(nil, nil, NSError(domain: NSCocoaErrorDomain, code: NSFeatureUnsupportedError, userInfo:[:])) diff --git a/src/services/native/InternxtAuthCredentialsModule.spec.ts b/src/services/native/InternxtAuthCredentialsModule.spec.ts index f815ba694..7f5193263 100644 --- a/src/services/native/InternxtAuthCredentialsModule.spec.ts +++ b/src/services/native/InternxtAuthCredentialsModule.spec.ts @@ -45,6 +45,14 @@ describe('InternxtAuthCredentialsModule wrapper', () => { expect(setCredentials).toHaveBeenCalledWith(credentials); }); + it('when on iOS with the module present, then setCredentials forwards driveBaseUrl to the native module', async () => { + const { wrapper, setCredentials } = arrangePresentNative('ios'); + + await wrapper.setCredentials(credentials); + + expect(setCredentials.mock.calls[0][0]).toMatchObject({ driveBaseUrl: 'https://drive.example' }); + }); + it('when on iOS with the module present, then clearCredentials delegates to the native module', async () => { const { wrapper, clearCredentials } = arrangePresentNative('ios');