From 30fb4cb3f4cd94e2dcc3375fa45ee24f2a5631b0 Mon Sep 17 00:00:00 2001 From: Bruno Muniz Azevedo Filho <bruno@elixxir.io> Date: Mon, 8 Aug 2022 23:08:45 -0300 Subject: [PATCH] Remove files from integration feature --- Package.swift | 61 +- Sources/App/AppDelegate.swift | 20 +- Sources/App/DependencyRegistrator.swift | 34 +- Sources/App/PushRouter.swift | 13 +- .../ViewModels/GroupChatViewModel.swift | 134 +++- .../ViewModels/SingleChatViewModel.swift | 226 ++++++- .../ViewModel/ChatListViewModel.swift | 46 +- .../ViewModels/ContactViewModel.swift | 100 ++- .../ViewModels/ContactListViewModel.swift | 19 +- .../ViewModels/CreateGroupViewModel.swift | 73 ++- Sources/Defaults/KeyObject.swift | 1 - Sources/Integration/Callbacks.swift | 295 --------- Sources/Integration/Client.swift | 236 ------- Sources/Integration/Extensions.swift | 58 -- .../Implementations/Bindings.swift | 613 ------------------ .../Implementations/GroupManager.swift | 94 --- .../Implementations/TransferManager.swift | 63 -- .../Implementations/UserDiscovery.swift | 166 ----- .../Interfaces/BindingsInterface.swift | 174 ----- .../Interfaces/GroupManagerInterface.swift | 14 - .../Interfaces/TransferManagerInterface.swift | 27 - .../Interfaces/UserDiscoveryInterface.swift | 27 - Sources/Integration/Listeners.swift | 151 ----- Sources/Integration/Logging.swift | 85 --- .../Integration/Mocks/GroupManagerMock.swift | 21 - .../Mocks/TransferManagerMock.swift | 33 - .../Integration/Mocks/UserDiscoveryMock.swift | 45 -- .../Integration/Resources/cert_mainnet.txt | 1 - .../Integration/Session/Session+Chat.swift | 270 -------- .../Integration/Session/Session+Network.swift | 15 - .../Session/Session+Notifications.swift | 11 - Sources/Integration/Session/Session+UD.swift | 118 ---- Sources/Integration/Session/SessionType.swift | 73 --- Sources/Integration/XXNetwork.swift | 173 ----- Sources/LaunchFeature/LaunchController.swift | 4 +- Sources/LaunchFeature/LaunchCoordinator.swift | 10 +- Sources/LaunchFeature/LaunchViewModel.swift | 409 ++++++++++-- Sources/LaunchFeature/Resources/cmix.rip.crt | 21 + Sources/LaunchFeature/Resources/udContact.bin | 1 + .../ViewModels/MenuViewModel.swift | 12 +- .../OnboardingStartController.swift | 10 +- .../OnboardingUsernameController.swift | 13 +- .../Coordinator/OnboardingCoordinator.swift | 33 +- .../OnboardingFeature/Resources/cmix.rip.crt | 21 + .../OnboardingFeature/Resources/udContact.bin | 1 + ...OnboardingEmailConfirmationViewModel.swift | 14 +- .../ViewModels/OnboardingEmailViewModel.swift | 28 +- ...OnboardingPhoneConfirmationViewModel.swift | 14 +- .../ViewModels/OnboardingPhoneViewModel.swift | 30 +- .../OnboardingUsernameViewModel.swift | 276 +++++++- .../ViewModels/ProfileCodeViewModel.swift | 20 +- .../ViewModels/ProfileEmailViewModel.swift | 31 +- .../ViewModels/ProfilePhoneViewModel.swift | 30 +- .../ViewModels/ProfileViewModel.swift | 20 +- Sources/PushFeature/PushExtractor.swift | 44 +- Sources/PushFeature/PushHandler.swift | 5 +- .../ViewModels/RequestsFailedViewModel.swift | 21 +- .../RequestsReceivedViewModel.swift | 108 ++- .../ViewModels/RequestsSentViewModel.swift | 24 +- .../Controllers/RestoreController.swift | 4 +- .../Controllers/RestoreListController.swift | 10 +- .../Coordinator/RestoreCoordinator.swift | 9 +- Sources/RestoreFeature/Resources/cmix.rip.crt | 21 + .../RestoreFeature/Resources/udContact.bin | 1 + .../ViewModels/RestoreViewModel.swift | 129 +++- .../ViewModels/ScanDisplayViewModel.swift | 23 +- .../ViewModels/ScanViewModel.swift | 21 +- .../ViewModels/SearchContainerViewModel.swift | 8 +- .../ViewModels/SearchLeftViewModel.swift | 130 +++- .../ViewModels/SearchRightViewModel.swift | 27 +- .../ViewModels/AccountDeleteViewModel.swift | 6 - .../ViewModels/SettingsViewModel.swift | 15 +- .../FeedbackPlayer.swift | 0 .../Coordinator/SearchCoordinatorSpec.swift | 1 - .../xcshareddata/swiftpm/Package.resolved | 117 ++-- 75 files changed, 1865 insertions(+), 3347 deletions(-) delete mode 100644 Sources/Integration/Callbacks.swift delete mode 100644 Sources/Integration/Client.swift delete mode 100644 Sources/Integration/Extensions.swift delete mode 100644 Sources/Integration/Implementations/Bindings.swift delete mode 100644 Sources/Integration/Implementations/GroupManager.swift delete mode 100644 Sources/Integration/Implementations/TransferManager.swift delete mode 100644 Sources/Integration/Implementations/UserDiscovery.swift delete mode 100644 Sources/Integration/Interfaces/BindingsInterface.swift delete mode 100644 Sources/Integration/Interfaces/GroupManagerInterface.swift delete mode 100644 Sources/Integration/Interfaces/TransferManagerInterface.swift delete mode 100644 Sources/Integration/Interfaces/UserDiscoveryInterface.swift delete mode 100644 Sources/Integration/Listeners.swift delete mode 100644 Sources/Integration/Logging.swift delete mode 100644 Sources/Integration/Mocks/GroupManagerMock.swift delete mode 100644 Sources/Integration/Mocks/TransferManagerMock.swift delete mode 100644 Sources/Integration/Mocks/UserDiscoveryMock.swift delete mode 100644 Sources/Integration/Resources/cert_mainnet.txt delete mode 100644 Sources/Integration/Session/Session+Chat.swift delete mode 100644 Sources/Integration/Session/Session+Network.swift delete mode 100644 Sources/Integration/Session/Session+Notifications.swift delete mode 100644 Sources/Integration/Session/Session+UD.swift delete mode 100644 Sources/Integration/Session/SessionType.swift delete mode 100644 Sources/Integration/XXNetwork.swift create mode 100644 Sources/LaunchFeature/Resources/cmix.rip.crt create mode 100644 Sources/LaunchFeature/Resources/udContact.bin create mode 100644 Sources/OnboardingFeature/Resources/cmix.rip.crt create mode 100644 Sources/OnboardingFeature/Resources/udContact.bin create mode 100644 Sources/RestoreFeature/Resources/cmix.rip.crt create mode 100644 Sources/RestoreFeature/Resources/udContact.bin rename Sources/{Integration => Shared}/FeedbackPlayer.swift (100%) diff --git a/Package.swift b/Package.swift index f5678619..b74da346 100644 --- a/Package.swift +++ b/Package.swift @@ -23,7 +23,6 @@ let package = Package( .library(name: "ScanFeature", targets: ["ScanFeature"]), .library(name: "Permissions", targets: ["Permissions"]), .library(name: "MenuFeature", targets: ["MenuFeature"]), - .library(name: "Integration", targets: ["Integration"]), .library(name: "ChatFeature", targets: ["ChatFeature"]), .library(name: "PushFeature", targets: ["PushFeature"]), .library(name: "SFTPFeature", targets: ["SFTPFeature"]), @@ -115,6 +114,10 @@ let package = Package( url: "https://github.com/google/google-api-objectivec-client-for-rest", .upToNextMajor(from: "1.6.0") ), + .package( + url: "https://git.xx.network/elixxir/elixxir-dapps-sdk-swift", + branch: "development" + ), .package( url: "https://git.xx.network/elixxir/client-ios-db.git", .upToNextMajor(from: "1.1.0") @@ -207,10 +210,6 @@ let package = Package( .target(name: "Shared"), ] ), - .binaryTarget( - name: "Bindings", - path: "XCFrameworks/Bindings.xcframework" - ), .target( name: "Permissions", dependencies: [ @@ -224,8 +223,8 @@ let package = Package( dependencies: [ .target(name: "Models"), .target(name: "Defaults"), - .target(name: "Integration"), .target(name: "DependencyInjection"), + .product(name: "XXDatabase", package: "client-ios-db"), ] ), .target( @@ -373,26 +372,6 @@ let package = Package( .process("Resources"), ] ), - .target( - name: "Integration", - dependencies: [ - .target(name: "Shared"), - .target(name: "Bindings"), - .target(name: "XXLogger"), - .target(name: "Keychain"), - .target(name: "ToastFeature"), - .target(name: "BackupFeature"), - .target(name: "CrashReporting"), - .target(name: "NetworkMonitor"), - .target(name: "DependencyInjection"), - .product(name: "Retry", package: "Retry"), - .product(name: "XXDatabase", package: "client-ios-db"), - .product(name: "XXLegacyDatabaseMigrator", package: "client-ios-db"), - ], - resources: [ - .process("Resources"), - ] - ), .target( name: "Presentation", dependencies: [ @@ -422,12 +401,16 @@ let package = Package( .target(name: "HUD"), .target(name: "Shared"), .target(name: "SFTPFeature"), - .target(name: "Integration"), .target(name: "Presentation"), .target(name: "iCloudFeature"), + .target(name: "BackupFeature"), .target(name: "DropboxFeature"), .target(name: "GoogleDriveFeature"), .target(name: "DependencyInjection"), + .product(name: "XXClient", package: "elixxir-dapps-sdk-swift"), + ], + resources: [ + .process("Resources"), ] ), .target( @@ -459,7 +442,6 @@ let package = Package( .target(name: "Defaults"), .target(name: "Keychain"), .target(name: "Voxophone"), - .target(name: "Integration"), .target(name: "Permissions"), .target(name: "Presentation"), .target(name: "DrawerFeature"), @@ -486,10 +468,11 @@ let package = Package( .target(name: "HUD"), .target(name: "Shared"), .target(name: "Countries"), - .target(name: "Integration"), .target(name: "Presentation"), .target(name: "ContactFeature"), + .target(name: "NetworkMonitor"), .target(name: "DependencyInjection"), + .product(name: "XXDatabase", package: "client-ios-db"), ] ), .testTarget( @@ -509,12 +492,17 @@ let package = Package( .target(name: "Shared"), .target(name: "Defaults"), .target(name: "PushFeature"), - .target(name: "Integration"), .target(name: "Permissions"), .target(name: "DropboxFeature"), .target(name: "VersionChecking"), .target(name: "ReportingFeature"), .target(name: "DependencyInjection"), + .product(name: "XXClient", package: "elixxir-dapps-sdk-swift"), + .product(name: "CombineSchedulers", package: "combine-schedulers"), + .product(name: "XXLegacyDatabaseMigrator", package: "client-ios-db"), + ], + resources: [ + .process("Resources"), ] ), .target( @@ -531,7 +519,6 @@ let package = Package( dependencies: [ .target(name: "Theme"), .target(name: "Shared"), - .target(name: "Integration"), .target(name: "ToastFeature"), .target(name: "ContactFeature"), .target(name: "DependencyInjection"), @@ -559,7 +546,6 @@ let package = Package( .target(name: "InputField"), .target(name: "MenuFeature"), .target(name: "Permissions"), - .target(name: "Integration"), .target(name: "Presentation"), .target(name: "DrawerFeature"), .target(name: "DependencyInjection"), @@ -611,13 +597,15 @@ let package = Package( .target(name: "InputField"), .target(name: "Permissions"), .target(name: "PushFeature"), - .target(name: "Integration"), .target(name: "Presentation"), .target(name: "DrawerFeature"), .target(name: "VersionChecking"), .target(name: "DependencyInjection"), .product(name: "CombineSchedulers", package: "combine-schedulers"), .product(name: "ScrollViewController", package: "ScrollViewController"), + ], + resources: [ + .process("Resources"), ] ), .testTarget( @@ -635,8 +623,8 @@ let package = Package( .target(name: "Theme"), .target(name: "Shared"), .target(name: "Defaults"), - .target(name: "Integration"), .target(name: "Presentation"), + .target(name: "DrawerFeature"), .target(name: "DependencyInjection"), ] ), @@ -663,9 +651,9 @@ let package = Package( .target(name: "Shared"), .target(name: "Countries"), .target(name: "Permissions"), - .target(name: "Integration"), .target(name: "Presentation"), .target(name: "ContactFeature"), + .target(name: "NetworkMonitor"), .target(name: "DependencyInjection"), .product(name: "SnapKit", package: "SnapKit"), ] @@ -684,7 +672,6 @@ let package = Package( dependencies: [ .target(name: "Theme"), .target(name: "Shared"), - .target(name: "Integration"), .target(name: "Presentation"), .target(name: "ContactFeature"), .target(name: "DependencyInjection"), @@ -708,11 +695,11 @@ let package = Package( .target(name: "Shared"), .target(name: "Defaults"), .target(name: "Keychain"), + .target(name: "XXLogger"), .target(name: "InputField"), .target(name: "PushFeature"), .target(name: "Permissions"), .target(name: "MenuFeature"), - .target(name: "Integration"), .target(name: "Presentation"), .target(name: "DrawerFeature"), .target(name: "DependencyInjection"), diff --git a/Sources/App/AppDelegate.swift b/Sources/App/AppDelegate.swift index 4143a5f2..282e8572 100644 --- a/Sources/App/AppDelegate.swift +++ b/Sources/App/AppDelegate.swift @@ -5,7 +5,6 @@ import Theme import XXModels import XXLogger import Defaults -import Integration import PushFeature import ToastFeature import SwiftyDropbox @@ -14,6 +13,8 @@ import DropboxFeature import CrashReporting import DependencyInjection +import XXClient + public class AppDelegate: UIResponder, UIApplicationDelegate { @Dependency private var pushRouter: PushRouter @Dependency private var pushHandler: PushHandling @@ -69,7 +70,8 @@ public class AppDelegate: UIResponder, UIApplicationDelegate { } public func applicationDidEnterBackground(_ application: UIApplication) { - if let session = try? DependencyInjection.Container.shared.resolve() as SessionType { + if let cMix: CMix = try? DependencyInjection.Container.shared.resolve(), + let database: Database = try? DependencyInjection.Container.shared.resolve() { let backgroundTask = application.beginBackgroundTask(withName: "xx.stop.network") {} // An option here would be: create async completion closure @@ -78,9 +80,9 @@ public class AppDelegate: UIResponder, UIApplicationDelegate { guard UIApplication.shared.backgroundTimeRemaining > 8 else { if !self.calledStopNetwork { self.calledStopNetwork = true - session.stop() + try! cMix.stopNetworkFollower() } else { - if session.hasRunningTasks == false { + if cMix.hasRunningProcesses() == false { application.endBackgroundTask(backgroundTask) timer.invalidate() } @@ -95,7 +97,7 @@ public class AppDelegate: UIResponder, UIApplicationDelegate { let query = Message.Query(status: [.sending]) let assignment = Message.Assignments(status: .sendingFailed) - _ = try? session.dbManager.bulkUpdateMessages(query, assignment) + _ = try? database.bulkUpdateMessages(query, assignment) } return @@ -114,8 +116,8 @@ public class AppDelegate: UIResponder, UIApplicationDelegate { } public func applicationWillTerminate(_ application: UIApplication) { - if let session = try? DependencyInjection.Container.shared.resolve() as SessionType { - session.stop() + if let cMix: CMix = try? DependencyInjection.Container.shared.resolve() { + try? cMix.stopNetworkFollower() } } @@ -125,9 +127,9 @@ public class AppDelegate: UIResponder, UIApplicationDelegate { backgroundTimer = nil } - if let session = try? DependencyInjection.Container.shared.resolve() as SessionType { + if let cMix: CMix = try? DependencyInjection.Container.shared.resolve() { guard self.calledStopNetwork == true else { return } - session.start() + try? cMix.startNetworkFollower(timeoutMS: 10_000) self.calledStopNetwork = false } } diff --git a/Sources/App/DependencyRegistrator.swift b/Sources/App/DependencyRegistrator.swift index 182ac2f5..026bfd56 100644 --- a/Sources/App/DependencyRegistrator.swift +++ b/Sources/App/DependencyRegistrator.swift @@ -15,7 +15,6 @@ import Keychain import Defaults import Countries import Voxophone -import Integration import Permissions import PushFeature import SFTPFeature @@ -48,6 +47,9 @@ import RequestsFeature import OnboardingFeature import ContactListFeature +import KeychainAccess +import XXClient + struct DependencyRegistrator { static private let container = DependencyInjection.Container.shared @@ -59,7 +61,6 @@ struct DependencyRegistrator { container.register(VersionChecker.mock) container.register(ReportingStatus.mock()) container.register(SendReport.mock()) - container.register(XXNetwork<BindingsMock>() as XXNetworking) container.register(MockNetworkMonitor() as NetworkMonitoring) container.register(KeyObjectStore.userDefaults) container.register(MockPushHandler() as PushHandling) @@ -79,6 +80,12 @@ struct DependencyRegistrator { // MARK: LIVE static func registerForLive() { + let cMixManager = CMixManager.live(passwordStorage: .keychain) + container.register(cMixManager) + + container.register(GetIdFromContact.live) + container.register(GetFactsFromContact.live) + container.register(KeyObjectStore.userDefaults) container.register(XXLogger.live()) container.register(CrashReporter.live) @@ -86,7 +93,6 @@ struct DependencyRegistrator { container.register(ReportingStatus.live()) container.register(SendReport.live) - container.register(XXNetwork<BindingsClient>() as XXNetworking) container.register(NetworkMonitor() as NetworkMonitoring) container.register(PushHandler() as PushHandling) container.register(KeychainHandler() as KeychainHandling) @@ -134,7 +140,7 @@ struct DependencyRegistrator { searchFactory: SearchContainerController.init, requestsFactory: RequestsContainerController.init, chatListFactory: ChatListController.init, - onboardingFactory: OnboardingStartController.init(_:), + onboardingFactory: OnboardingStartController.init, singleChatFactory: SingleChatController.init(_:), groupChatFactory: GroupChatController.init(_:) ) as LaunchCoordinating) @@ -185,7 +191,7 @@ struct DependencyRegistrator { RestoreCoordinator( successFactory: RestoreSuccessController.init, chatListFactory: ChatListController.init, - restoreFactory: RestoreController.init(_:_:), + restoreFactory: RestoreController.init(_:), passphraseFactory: RestorePassphraseController.init(_:) ) as RestoreCoordinating) @@ -224,9 +230,9 @@ struct DependencyRegistrator { searchFactory: SearchContainerController.init, welcomeFactory: OnboardingWelcomeController.init, chatListFactory: ChatListController.init, - termsFactory: TermsConditionsController.init(_:), - usernameFactory: OnboardingUsernameController.init(_:), - restoreListFactory: RestoreListController.init(_:), + termsFactory: TermsConditionsController.init, + usernameFactory: OnboardingUsernameController.init, + restoreListFactory: RestoreListController.init, successFactory: OnboardingSuccessController.init(_:), countriesFactory: CountryListController.init(_:), phoneConfirmationFactory: OnboardingPhoneConfirmationController.init(_:_:), @@ -270,3 +276,15 @@ struct DependencyRegistrator { ) as ChatListCoordinating) } } + +extension PasswordStorage { + static let keychain: PasswordStorage = { + let keychain = KeychainAccess.Keychain( + service: "XXM" + ) + return PasswordStorage( + save: { password in keychain[data: "password"] = password}, + load: { try keychain[data: "password"] ?? { throw MissingPasswordError() }() } + ) + }() +} diff --git a/Sources/App/PushRouter.swift b/Sources/App/PushRouter.swift index a2d2d809..4e1e4323 100644 --- a/Sources/App/PushRouter.swift +++ b/Sources/App/PushRouter.swift @@ -1,12 +1,12 @@ import UIKit import PushFeature -import Integration import ChatFeature import SearchFeature import LaunchFeature import ChatListFeature import RequestsFeature import DependencyInjection +import XXModels extension PushRouter { static func live(navigationController: UINavigationController) -> PushRouter { @@ -20,24 +20,23 @@ extension PushRouter { navigationController.setViewControllers([RequestsContainerController()], animated: true) } case .search(username: let username): - if let _ = try? DependencyInjection.Container.shared.resolve() as SessionType, - !(navigationController.viewControllers.last is SearchContainerController) { + if !(navigationController.viewControllers.last is SearchContainerController) { navigationController.setViewControllers([ ChatListController(), SearchContainerController(username) ], animated: true) } case .contactChat(id: let id): - if let session = try? DependencyInjection.Container.shared.resolve() as SessionType, - let contact = try? session.dbManager.fetchContacts(.init(id: [id])).first { + if let database: Database = try? DependencyInjection.Container.shared.resolve(), + let contact = try? database.fetchContacts(.init(id: [id])).first { navigationController.setViewControllers([ ChatListController(), SingleChatController(contact) ], animated: true) } case .groupChat(id: let id): - if let session = try? DependencyInjection.Container.shared.resolve() as SessionType, - let info = try? session.dbManager.fetchGroupInfos(.init(groupId: id)).first { + if let database: Database = try? DependencyInjection.Container.shared.resolve(), + let info = try? database.fetchGroupInfos(.init(groupId: id)).first { navigationController.setViewControllers([ ChatListController(), GroupChatController(info) diff --git a/Sources/ChatFeature/ViewModels/GroupChatViewModel.swift b/Sources/ChatFeature/ViewModels/GroupChatViewModel.swift index 1dbce8a8..15754255 100644 --- a/Sources/ChatFeature/ViewModels/GroupChatViewModel.swift +++ b/Sources/ChatFeature/ViewModels/GroupChatViewModel.swift @@ -6,25 +6,34 @@ import Combine import XXModels import Defaults import Foundation -import Integration import ToastFeature import DifferenceKit import ReportingFeature import DependencyInjection +import struct XXModels.Message +import XXClient + enum GroupChatNavigationRoutes: Equatable { case waitingRound case webview(String) } final class GroupChatViewModel { - @Dependency private var session: SessionType - @Dependency private var sendReport: SendReport - @Dependency private var reportingStatus: ReportingStatus - @Dependency private var toastController: ToastController + @Dependency var cMix: CMix + @Dependency var database: Database + @Dependency var sendReport: SendReport + @Dependency var groupManager: GroupChat + @Dependency var userDiscovery: UserDiscovery + @Dependency var reportingStatus: ReportingStatus + @Dependency var toastController: ToastController @KeyObject(.username, defaultValue: nil) var username: String? + var myId: Data { + try! GetIdFromContact.live(userDiscovery.getContact()) + } + var hudPublisher: AnyPublisher<HUDStatus, Never> { hudSubject.eraseToAnyPublisher() } @@ -50,7 +59,7 @@ final class GroupChatViewModel { private let routesSubject = PassthroughSubject<GroupChatNavigationRoutes, Never>() var messages: AnyPublisher<[ArraySection<ChatSection, Message>], Never> { - session.dbManager.fetchMessagesPublisher(.init(chat: .group(info.group.id))) + database.fetchMessagesPublisher(.init(chat: .group(info.group.id))) .assertNoFailure() .map { messages -> [ArraySection<ChatSection, Message>] in let groupedByDate = Dictionary(grouping: messages) { domainModel -> Date in @@ -76,11 +85,11 @@ final class GroupChatViewModel { func readAll() { let assignment = Message.Assignments(isUnread: false) let query = Message.Query(chat: .group(info.group.id)) - _ = try? session.dbManager.bulkUpdateMessages(query, assignment) + _ = try? database.bulkUpdateMessages(query, assignment) } func didRequestDelete(_ messages: [Message]) { - _ = try? session.dbManager.deleteMessages(.init(id: Set(messages.map(\.id)))) + _ = try? database.deleteMessages(.init(id: Set(messages.map(\.id)))) } func didRequestReport(_ message: Message) { @@ -90,16 +99,109 @@ final class GroupChatViewModel { } func send(_ text: String) { - session.send(.init( + var message = Message( + senderId: myId, + recipientId: nil, + groupId: info.group.id, + date: Date(), + status: .sending, + isUnread: false, text: text.trimmingCharacters(in: .whitespacesAndNewlines), - reply: stagedReply - ), toGroup: info.group) + replyMessageId: stagedReply?.messageId + ) + + do { + try database.saveMessage(message) + + let report = try groupManager.send( + groupId: info.group.id, + message: Payload( + text: text.trimmingCharacters(in: .whitespacesAndNewlines), + reply: stagedReply + ).asData() + ) + + try cMix.waitForRoundResult( + roundList: try report.encode(), + timeoutMS: 5_000, + callback: .init(handle: { + switch $0 { + case .delivered: + message.status = .sent + _ = try? self.database.saveMessage(message) + + case .notDelivered(timedOut: let timedOut): + if timedOut { + message.status = .sendingTimedOut + } else { + message.status = .sendingFailed + } + + _ = try? self.database.saveMessage(message) + } + }) + ) + + message.networkId = report.messageId + message.date = Date.fromTimestamp(Int(report.timestamp)) + try database.saveMessage(message) + } catch { + message.status = .sendingFailed + _ = try? database.saveMessage(message) + } + stagedReply = nil } func retry(_ message: Message) { - guard let id = message.id else { return } - session.retryMessage(id) + var message = message + + do { + message.status = .sending + message = try database.saveMessage(message) + + var reply: Reply? + + if let replyId = message.replyMessageId { + reply = Reply(messageId: replyId, senderId: myId) + } + + let report = try groupManager.send( + groupId: message.groupId!, + message: Payload( + text: message.text, + reply: reply + ).asData() + ) + + try cMix.waitForRoundResult( + roundList: try report.encode(), + timeoutMS: 5_000, + callback: .init(handle: { + switch $0 { + case .delivered: + message.status = .sent + _ = try? self.database.saveMessage(message) + + case .notDelivered(timedOut: let timedOut): + if timedOut { + message.status = .sendingTimedOut + } else { + message.status = .sendingFailed + } + + _ = try? self.database.saveMessage(message) + } + }) + ) + + message.networkId = report.messageId + message.date = Date.fromTimestamp(Int(report.timestamp)) + message = try database.saveMessage(message) + } catch { + message.status = .sendingFailed + _ = try? database.saveMessage(message) + } } func showRoundFrom(_ roundURL: String?) { @@ -115,7 +217,7 @@ final class GroupChatViewModel { } func getReplyContent(for messageId: Data) -> (String, String) { - guard let message = try? session.dbManager.fetchMessages(.init(networkId: messageId)).first else { + guard let message = try? database.fetchMessages(.init(networkId: messageId)).first else { return ("[DELETED]", "[DELETED]") } @@ -123,9 +225,9 @@ final class GroupChatViewModel { } func getName(from senderId: Data) -> String { - guard senderId != session.myId else { return "You" } + guard senderId != myId else { return "You" } - guard let contact = try? session.dbManager.fetchContacts(.init(id: [senderId])).first else { + guard let contact = try? database.fetchContacts(.init(id: [senderId])).first else { return "[DELETED]" } diff --git a/Sources/ChatFeature/ViewModels/SingleChatViewModel.swift b/Sources/ChatFeature/ViewModels/SingleChatViewModel.swift index 8ba933ad..3593e3da 100644 --- a/Sources/ChatFeature/ViewModels/SingleChatViewModel.swift +++ b/Sources/ChatFeature/ViewModels/SingleChatViewModel.swift @@ -5,15 +5,17 @@ import Shared import Combine import XXLogger import XXModels +import XXClient import Foundation -import Integration -import Defaults import Permissions import ToastFeature import DifferenceKit import ReportingFeature import DependencyInjection +import struct XXModels.Message +import struct XXModels.FileTransfer + enum SingleChatNavigationRoutes: Equatable { case none case camera @@ -26,11 +28,15 @@ enum SingleChatNavigationRoutes: Equatable { } final class SingleChatViewModel: NSObject { - @Dependency private var logger: XXLogger - @Dependency private var session: SessionType - @Dependency private var permissions: PermissionHandling - @Dependency private var toastController: ToastController - @Dependency private var sendReport: SendReport + @Dependency var e2e: E2E + @Dependency var cMix: CMix + @Dependency var logger: XXLogger + @Dependency var database: Database + @Dependency var sendReport: SendReport + @Dependency var userDiscovery: UserDiscovery + @Dependency var permissions: PermissionHandling + @Dependency var toastController: ToastController + @Dependency var transferManager: XXClient.FileTransfer @KeyObject(.username, defaultValue: nil) var username: String? @@ -46,7 +52,15 @@ final class SingleChatViewModel: NSObject { var hud: AnyPublisher<HUDStatus, Never> { hudRelay.eraseToAnyPublisher() } private let hudRelay = CurrentValueSubject<HUDStatus, Never>(.none) - var isOnline: AnyPublisher<Bool, Never> { session.isOnline } + var isOnline: AnyPublisher<Bool, Never> { + // TO REFACTOR: + Just(.init(true)).eraseToAnyPublisher() + } + + var myId: Data { + try! GetIdFromContact.live(userDiscovery.getContact()) + } + var contactPublisher: AnyPublisher<Contact, Never> { contactSubject.eraseToAnyPublisher() } var replyPublisher: AnyPublisher<(String, String), Never> { replySubject.eraseToAnyPublisher() } var navigation: AnyPublisher<SingleChatNavigationRoutes, Never> { navigationRoutes.eraseToAnyPublisher() } @@ -68,7 +82,7 @@ final class SingleChatViewModel: NSObject { if contact.isRecent == true { var contact = contact contact.isRecent = false - _ = try? session.dbManager.saveContact(contact) + _ = try? database.saveContact(contact) } } @@ -82,13 +96,13 @@ final class SingleChatViewModel: NSObject { updateRecentState(contact) - session.dbManager.fetchContactsPublisher(Contact.Query(id: [contact.id])) + database.fetchContactsPublisher(Contact.Query(id: [contact.id])) .assertNoFailure() .compactMap { $0.first } .sink { [unowned self] in contactSubject.send($0) } .store(in: &cancellables) - session.dbManager.fetchMessagesPublisher(.init(chat: .direct(session.myId, contact.id))) + database.fetchMessagesPublisher(.init(chat: .direct(myId, contact.id))) .assertNoFailure() .map { let groupedByDate = Dictionary(grouping: $0) { domainModel -> Date in @@ -107,7 +121,7 @@ final class SingleChatViewModel: NSObject { // MARK: Public func getFileTransferWith(id: Data) -> FileTransfer { - guard let transfer = try? session.dbManager.fetchFileTransfers(.init(id: [id])).first else { + guard let transfer = try? database.fetchFileTransfers(.init(id: [id])).first else { fatalError() } @@ -115,36 +129,133 @@ final class SingleChatViewModel: NSObject { } func didSendAudio(url: URL) { - session.sendFile(url: url, to: contact) + do { + let transferId = try transferManager.send( + params: .init( + payload: .init( + name: "", + type: "", + preview: Data(), + contents: Data() + ), + recipientId: contact.id, + paramsJSON: Data(), + retry: 1, + period: "" + ), + callback: .init(handle: { + switch $0 { + case .success(let progressCallback): + print(progressCallback.progress.total) + case .failure(let error): + print(error.localizedDescription) + } + }) + ) + + // transferId + } catch { + + } } func didSend(image: UIImage) { guard let imageData = image.orientedUp().jpegData(compressionQuality: 1.0) else { return } hudRelay.send(.on) - session.send(imageData: imageData, to: contact) { [weak self] in - switch $0 { - case .success: - self?.hudRelay.send(.none) - case .failure(let error): - self?.hudRelay.send(.error(.init(with: error))) - } + do { + let transferId = try transferManager.send( + params: .init( + payload: .init( + name: "", + type: "", + preview: Data(), + contents: Data() + ), + recipientId: Data(), + paramsJSON: Data(), + retry: 1, + period: "" + ), + callback: .init(handle: { + switch $0 { + case .success(let progressCallback): + print(progressCallback.progress.total) + case .failure(let error): + print(error.localizedDescription) + } + }) + ) + } catch { + // self?.hudRelay.send(.error(.init(with: error))) } } func readAll() { let assignment = Message.Assignments(isUnread: false) - let query = Message.Query(chat: .direct(session.myId, contact.id)) - _ = try? session.dbManager.bulkUpdateMessages(query, assignment) + let query = Message.Query(chat: .direct(myId, contact.id)) + _ = try? database.bulkUpdateMessages(query, assignment) } func didRequestDeleteAll() { - _ = try? session.dbManager.deleteMessages(.init(chat: .direct(session.myId, contact.id))) + _ = try? database.deleteMessages(.init(chat: .direct(myId, contact.id))) } func didRequestRetry(_ message: Message) { - guard let id = message.id else { return } - session.retryMessage(id) + var message = message + + do { + message.status = .sending + message = try database.saveMessage(message) + + var reply: Reply? + + if let replyId = message.replyMessageId { + reply = Reply(messageId: replyId, senderId: myId) + } + + let report = try e2e.send( + messageType: 2, + recipientId: contact.id, + payload: Payload( + text: message.text, + reply: reply + ).asData(), + e2eParams: GetE2EParams.liveDefault() + ) + + try cMix.waitForRoundResult( + roundList: try report.encode(), + timeoutMS: 5_000, + callback: .init(handle: { + switch $0 { + case .delivered: + message.status = .sent + _ = try? self.database.saveMessage(message) + + case .notDelivered(timedOut: let timedOut): + if timedOut { + message.status = .sendingTimedOut + } else { + message.status = .sendingFailed + } + + _ = try? self.database.saveMessage(message) + } + }) + ) + + message.networkId = report.messageId + if let timestamp = report.timestamp { + message.date = Date.fromTimestamp(Int(timestamp)) + } + + message = try database.saveMessage(message) + } catch { + print(error.localizedDescription) + message.status = .sendingFailed + _ = try? database.saveMessage(message) + } } func didNavigateSomewhere() { @@ -194,9 +305,60 @@ final class SingleChatViewModel: NSObject { } func send(_ string: String) { - let text = string.trimmingCharacters(in: .whitespacesAndNewlines) - let payload = Payload(text: text, reply: stagedReply) - session.send(payload, toContact: contact) + var message: Message = .init( + senderId: myId, + recipientId: contact.id, + groupId: nil, + date: Date(), + status: .sending, + isUnread: false, + text: string.trimmingCharacters(in: .whitespacesAndNewlines), + replyMessageId: stagedReply?.messageId + ) + + do { + message = try database.saveMessage(message) + + let report = try e2e.send( + messageType: 2, + recipientId: contact.id, + payload: Payload(text: message.text, reply: stagedReply).asData(), + e2eParams: GetE2EParams.liveDefault() + ) + + try cMix.waitForRoundResult( + roundList: try report.encode(), + timeoutMS: 5_000, + callback: .init(handle: { + switch $0 { + case .delivered: + message.status = .sent + _ = try? self.database.saveMessage(message) + + case .notDelivered(timedOut: let timedOut): + if timedOut { + message.status = .sendingTimedOut + } else { + message.status = .sendingFailed + } + + _ = try? self.database.saveMessage(message) + } + }) + ) + + message.networkId = report.messageId + if let timestamp = report.timestamp { + message.date = Date.fromTimestamp(Int(timestamp)) + } + + message = try database.saveMessage(message) + } catch { + print(error.localizedDescription) + message.status = .sendingFailed + _ = try? database.saveMessage(message) + } + stagedReply = nil } @@ -204,7 +366,7 @@ final class SingleChatViewModel: NSObject { guard let networkId = message.networkId else { return } let senderTitle: String = { - if message.senderId == session.myId { + if message.senderId == myId { return "You" } else { return (contact.nickname ?? contact.username) ?? "Fetching username..." @@ -216,11 +378,11 @@ final class SingleChatViewModel: NSObject { } func getReplyContent(for messageId: Data) -> (String, String) { - guard let message = try? session.dbManager.fetchMessages(.init(networkId: messageId)).first else { + guard let message = try? database.fetchMessages(.init(networkId: messageId)).first else { return ("[DELETED]", "[DELETED]") } - guard let contact = try? session.dbManager.fetchContacts(.init(id: [message.senderId])).first else { + guard let contact = try? database.fetchContacts(.init(id: [message.senderId])).first else { fatalError() } @@ -237,7 +399,7 @@ final class SingleChatViewModel: NSObject { } func didRequestDelete(_ items: [Message]) { - _ = try? session.dbManager.deleteMessages(.init(id: Set(items.compactMap(\.id)))) + _ = try? database.deleteMessages(.init(id: Set(items.compactMap(\.id)))) } func itemWith(id: Int64) -> Message? { diff --git a/Sources/ChatListFeature/ViewModel/ChatListViewModel.swift b/Sources/ChatListFeature/ViewModel/ChatListViewModel.swift index af0a8401..ab527d84 100644 --- a/Sources/ChatListFeature/ViewModel/ChatListViewModel.swift +++ b/Sources/ChatListFeature/ViewModel/ChatListViewModel.swift @@ -5,10 +5,12 @@ import Models import Combine import XXModels import Defaults -import Integration import ReportingFeature import DependencyInjection +import struct XXModels.Group +import XXClient + enum SearchSection { case chats case connections @@ -23,11 +25,18 @@ typealias RecentsSnapshot = NSDiffableDataSourceSnapshot<SectionId, Contact> typealias SearchSnapshot = NSDiffableDataSourceSnapshot<SearchSection, SearchItem> final class ChatListViewModel { - @Dependency private var session: SessionType - @Dependency private var reportingStatus: ReportingStatus + @Dependency var database: Database + @Dependency var groupManager: GroupChat + @Dependency var userDiscovery: UserDiscovery + @Dependency var reportingStatus: ReportingStatus + // TO REFACTOR: var isOnline: AnyPublisher<Bool, Never> { - session.isOnline + Just(.init(true)).eraseToAnyPublisher() + } + + var myId: Data { + try! GetIdFromContact.live(userDiscovery.getContact()) } var chatsPublisher: AnyPublisher<[ChatInfo], Never> { @@ -45,7 +54,7 @@ final class ChatListViewModel { isBanned: reportingStatus.isEnabled() ? false : nil ) - return session.dbManager.fetchContactsPublisher(query) + return database.fetchContactsPublisher(query) .assertNoFailure() .map { let section = SectionId() @@ -62,13 +71,10 @@ final class ChatListViewModel { isBanned: reportingStatus.isEnabled() ? false : nil ) - let contactsStream = session.dbManager - .fetchContactsPublisher(contactsQuery) - .assertNoFailure() - .map { $0.filter { $0.id != self.session.myId }} - - return Publishers.CombineLatest3( - contactsStream, + Publishers.CombineLatest3( + database.fetchContactsPublisher(contactsQuery) + .assertNoFailure() + .map { $0.filter { $0.id != self.session.myId }}, chatsPublisher, searchSubject .removeDuplicates() @@ -132,8 +138,8 @@ final class ChatListViewModel { ) return Publishers.CombineLatest( - session.dbManager.fetchContactsPublisher(contactsQuery).assertNoFailure(), - session.dbManager.fetchGroupsPublisher(groupQuery).assertNoFailure() + database.fetchContactsPublisher(contactsQuery).assertNoFailure(), + database.fetchGroupsPublisher(groupQuery).assertNoFailure() ) .map { $0.0.count + $0.1.count } .eraseToAnyPublisher() @@ -145,10 +151,10 @@ final class ChatListViewModel { private let hudSubject = CurrentValueSubject<HUDStatus, Never>(.none) init() { - session.dbManager.fetchChatInfosPublisher( + database.fetchChatInfosPublisher( ChatInfo.Query( contactChatInfoQuery: .init( - userId: session.myId, + userId: myId, authStatus: [.friend], isBlocked: reportingStatus.isEnabled() ? false : nil, isBanned: reportingStatus.isEnabled() ? false : nil @@ -175,8 +181,8 @@ final class ChatListViewModel { hudSubject.send(.on) do { - try session.leave(group: group) - try session.dbManager.deleteMessages(.init(chat: .group(group.id))) + try groupManager.leaveGroup(groupId: group.id) + try database.deleteMessages(.init(chat: .group(group.id))) hudSubject.send(.none) } catch { hudSubject.send(.error(.init(with: error))) @@ -184,12 +190,12 @@ final class ChatListViewModel { } func clear(_ contact: Contact) { - _ = try? session.dbManager.deleteMessages(.init(chat: .direct(session.myId, contact.id))) + _ = try? database.deleteMessages(.init(chat: .direct(myId, contact.id))) } func groupInfo(from group: Group) -> GroupInfo? { let query = GroupInfo.Query(groupId: group.id) - guard let info = try? session.dbManager.fetchGroupInfos(query).first else { + guard let info = try? database.fetchGroupInfos(query).first else { return nil } diff --git a/Sources/ContactFeature/ViewModels/ContactViewModel.swift b/Sources/ContactFeature/ViewModels/ContactViewModel.swift index 5e6e0669..c5c5d085 100644 --- a/Sources/ContactFeature/ViewModels/ContactViewModel.swift +++ b/Sources/ContactFeature/ViewModels/ContactViewModel.swift @@ -3,10 +3,12 @@ import UIKit import Models import Combine import XXModels -import Integration +import Defaults import CombineSchedulers import DependencyInjection +import XXClient + struct ContactViewState: Equatable { var title: String? var email: String? @@ -17,7 +19,12 @@ struct ContactViewState: Equatable { } final class ContactViewModel { - @Dependency private var session: SessionType + @Dependency var e2e: E2E + @Dependency var database: Database + @Dependency var userDiscovery: UserDiscovery + @Dependency var getFactsFromContact: GetFactsFromContact + + @KeyObject(.username, defaultValue: nil) var username: String? var contact: Contact @@ -33,39 +40,42 @@ final class ContactViewModel { private let hudRelay = CurrentValueSubject<HUDStatus, Never>(.none) private let stateRelay = CurrentValueSubject<ContactViewState, Never>(.init()) + var myId: Data { + try! GetIdFromContact.live(userDiscovery.getContact()) + } + var backgroundScheduler: AnySchedulerOf<DispatchQueue> = DispatchQueue.global().eraseToAnyScheduler() init(_ contact: Contact) { self.contact = contact - do { - let email = try session.extract(fact: .email, from: contact.marshaled!) - let phone = try session.extract(fact: .phone, from: contact.marshaled!) - - stateRelay.value = .init( - title: contact.nickname ?? contact.username, - email: email, - phone: phone, - photo: contact.photo != nil ? UIImage(data: contact.photo!) : nil, - username: contact.username, - nickname: contact.nickname - ) - } catch { - print(error.localizedDescription) - } + let facts = try? getFactsFromContact(contact: contact.marshaled!) + let email = facts?.first(where: { $0.type == FactType.email.rawValue })?.fact + let phone = facts?.first(where: { $0.type == FactType.phone.rawValue })?.fact + + stateRelay.value = .init( + title: contact.nickname ?? contact.username, + email: email, + phone: phone, + photo: contact.photo != nil ? UIImage(data: contact.photo!) : nil, + username: contact.username, + nickname: contact.nickname + ) } func didChoosePhoto(_ photo: UIImage) { stateRelay.value.photo = photo contact.photo = photo.jpegData(compressionQuality: 0.0) - _ = try? session.dbManager.saveContact(contact) + _ = try? database.saveContact(contact) } func didTapDelete() { hudRelay.send(.on) do { - try session.deleteContact(contact) + try e2e.deleteRequest.partner(contact.id) + try database.deleteContact(contact) + hudRelay.send(.none) popToRootRelay.send() } catch { @@ -74,33 +84,49 @@ final class ContactViewModel { } func didTapReject() { - try? session.deleteContact(contact) + // TODO: Reject function on the API? + _ = try? database.deleteContact(contact) popRelay.send() } func didTapClear() { - _ = try? session.dbManager.deleteMessages(.init(chat: .direct(session.myId, contact.id))) + _ = try? database.deleteMessages(.init(chat: .direct(myId, contact.id))) } func didUpdateNickname(_ string: String) { contact.nickname = string.isEmpty ? nil : string stateRelay.value.title = string.isEmpty ? contact.username : string - _ = try? session.dbManager.saveContact(contact) + _ = try? database.saveContact(contact) stateRelay.value.nickname = contact.nickname } func didTapResend() { hudRelay.send(.on) + contact.authStatus = .requesting backgroundScheduler.schedule { [weak self] in guard let self = self else { return } do { - try self.session.add(self.contact) + try self.database.saveContact(self.contact) + + var myFacts = try self.userDiscovery.getFacts() + myFacts.append(Fact(fact: self.username!, type: FactType.username.rawValue)) + + let _ = try self.e2e.requestAuthenticatedChannel( + partnerContact: self.contact.id, + myFacts: myFacts + ) + + self.contact.authStatus = .requested + try self.database.saveContact(self.contact) + self.hudRelay.send(.none) self.popRelay.send() } catch { + self.contact.authStatus = .requestFailed + _ = try? self.database.saveContact(self.contact) self.hudRelay.send(.error(.init(with: error))) } } @@ -109,15 +135,30 @@ final class ContactViewModel { func didTapRequest(with nickname: String) { hudRelay.send(.on) contact.nickname = nickname + contact.authStatus = .requesting backgroundScheduler.schedule { [weak self] in guard let self = self else { return } do { - try self.session.add(self.contact) + try self.database.saveContact(self.contact) + + var myFacts = try self.userDiscovery.getFacts() + myFacts.append(Fact(fact: self.username!, type: FactType.username.rawValue)) + + let _ = try self.e2e.requestAuthenticatedChannel( + partnerContact: self.contact.marshaled!, + myFacts: myFacts + ) + + self.contact.authStatus = .requested + try self.database.saveContact(self.contact) + self.hudRelay.send(.none) self.successRelay.send() } catch { + self.contact.authStatus = .requestFailed + _ = try? self.database.saveContact(self.contact) self.hudRelay.send(.error(.init(with: error))) } } @@ -126,15 +167,24 @@ final class ContactViewModel { func didTapAccept(_ nickname: String) { hudRelay.send(.on) contact.nickname = nickname + contact.authStatus = .confirming backgroundScheduler.schedule { [weak self] in guard let self = self else { return } do { - try self.session.confirm(self.contact) + try self.database.saveContact(self.contact) + + let _ = try self.e2e.confirmReceivedRequest(partnerContact: self.contact.marshaled!) + + self.contact.authStatus = .friend + try self.database.saveContact(self.contact) + self.hudRelay.send(.none) self.popRelay.send() } catch { + self.contact.authStatus = .confirmationFailed + _ = try? self.database.saveContact(self.contact) self.hudRelay.send(.error(.init(with: error))) } } diff --git a/Sources/ContactListFeature/ViewModels/ContactListViewModel.swift b/Sources/ContactListFeature/ViewModels/ContactListViewModel.swift index 830172f3..f37561ac 100644 --- a/Sources/ContactListFeature/ViewModels/ContactListViewModel.swift +++ b/Sources/ContactListFeature/ViewModels/ContactListViewModel.swift @@ -2,14 +2,21 @@ import Models import Combine import XXModels import Defaults -import Integration import ReportingFeature import DependencyInjection +import Foundation +import XXClient + final class ContactListViewModel { - @Dependency private var session: SessionType + @Dependency var database: Database + @Dependency var userDiscovery: UserDiscovery @Dependency private var reportingStatus: ReportingStatus + var myId: Data { + try! GetIdFromContact.live(userDiscovery.getContact()) + } + var contacts: AnyPublisher<[Contact], Never> { let query = Contact.Query( authStatus: [.friend], @@ -17,9 +24,9 @@ final class ContactListViewModel { isBanned: reportingStatus.isEnabled() ? false: nil ) - return session.dbManager.fetchContactsPublisher(query) + return database.fetchContactsPublisher(query) .assertNoFailure() - .map { $0.filter { $0.id != self.session.myId }} + .map { $0.filter { $0.id != self.myId }} .eraseToAnyPublisher() } @@ -43,8 +50,8 @@ final class ContactListViewModel { ) return Publishers.CombineLatest( - session.dbManager.fetchContactsPublisher(contactsQuery).assertNoFailure(), - session.dbManager.fetchGroupsPublisher(groupQuery).assertNoFailure() + database.fetchContactsPublisher(contactsQuery).assertNoFailure(), + database.fetchGroupsPublisher(groupQuery).assertNoFailure() ) .map { $0.0.count + $0.1.count } .eraseToAnyPublisher() diff --git a/Sources/ContactListFeature/ViewModels/CreateGroupViewModel.swift b/Sources/ContactListFeature/ViewModels/CreateGroupViewModel.swift index 55540753..06dabcb0 100644 --- a/Sources/ContactListFeature/ViewModels/CreateGroupViewModel.swift +++ b/Sources/ContactListFeature/ViewModels/CreateGroupViewModel.swift @@ -4,8 +4,9 @@ import Models import Combine import XXModels import Defaults -import Integration +import XXClient import ReportingFeature +import CombineSchedulers import DependencyInjection final class CreateGroupViewModel { @@ -13,11 +14,17 @@ final class CreateGroupViewModel { // MARK: Injected - @Dependency private var session: SessionType - @Dependency private var reportingStatus: ReportingStatus + @Dependency var database: Database + @Dependency var groupManager: GroupChat + @Dependency var userDiscovery: UserDiscovery + @Dependency var reportingStatus: ReportingStatus // MARK: Properties + var myId: Data { + try! GetIdFromContact.live(userDiscovery.getContact()) + } + var selected: AnyPublisher<[Contact], Never> { selectedContactsRelay.eraseToAnyPublisher() } @@ -34,6 +41,9 @@ final class CreateGroupViewModel { infoRelay.eraseToAnyPublisher() } + var backgroundScheduler: AnySchedulerOf<DispatchQueue> + = DispatchQueue.global().eraseToAnyScheduler() + private var allContacts = [Contact]() private var cancellables = Set<AnyCancellable>() private let infoRelay = PassthroughSubject<GroupInfo, Never>() @@ -50,9 +60,9 @@ final class CreateGroupViewModel { isBanned: reportingStatus.isEnabled() ? false : nil ) - session.dbManager.fetchContactsPublisher(query) + database.fetchContactsPublisher(query) .assertNoFailure() - .map { $0.filter { $0.id != self.session.myId }} + .map { $0.filter { $0.id != self.myId }} .map { $0.sorted(by: { $0.username! < $1.username! })} .sink { [unowned self] in allContacts = $0 @@ -86,15 +96,54 @@ final class CreateGroupViewModel { func create(name: String, welcome: String?, members: [Contact]) { hudRelay.send(.on) - session.createGroup(name: name, welcome: welcome, members: members) { [weak self] in + backgroundScheduler.schedule { [weak self] in guard let self = self else { return } - self.hudRelay.send(.none) - - switch $0 { - case .success(let info): - self.infoRelay.send(info) - case .failure(let error): + do { + let report = try self.groupManager.makeGroup( + membership: members.map(\.id), + message: welcome?.data(using: .utf8), + name: name.data(using: .utf8) + ) + + let group = Group( + id: report.id, + name: name, + leaderId: self.myId, + createdAt: Date(), + authStatus: .participating, + serialized: try report.encode() // ? + ) + + _ = try self.database.saveGroup(group) + + if let welcomeMessage = welcome { + try self.database.saveMessage( + Message( + senderId: self.myId, + recipientId: nil, + groupId: group.id, + date: group.createdAt, + status: .sent, + isUnread: false, + text: welcomeMessage, + replyMessageId: nil, + roundURL: nil, + fileTransferId: nil + ) + ) + } + + try members + .map { GroupMember(groupId: group.id, contactId: $0.id) } + .forEach { try self.database.saveGroupMember($0) } + + let query = GroupInfo.Query(groupId: group.id) + let info = try self.database.fetchGroupInfos(query).first + + self.infoRelay.send(info!) + self.hudRelay.send(.none) + } catch { self.hudRelay.send(.error(.init(with: error))) } } diff --git a/Sources/Defaults/KeyObject.swift b/Sources/Defaults/KeyObject.swift index 0ade4e83..1effae9c 100644 --- a/Sources/Defaults/KeyObject.swift +++ b/Sources/Defaults/KeyObject.swift @@ -39,7 +39,6 @@ public enum Key: String { case crashReporting case icognitoKeyboard - case dummyTrafficOn case askedDummyTrafficOnce } diff --git a/Sources/Integration/Callbacks.swift b/Sources/Integration/Callbacks.swift deleted file mode 100644 index ed9d917c..00000000 --- a/Sources/Integration/Callbacks.swift +++ /dev/null @@ -1,295 +0,0 @@ -import Bindings - -final class TextListener: NSObject, BindingsListenerProtocol { - let callback: (BindingsMessage?) -> () - - init(_ callback: @escaping (BindingsMessage?) -> Void) { - self.callback = callback - super.init() - } - - func hear(_ message: BindingsMessage?) { - callback(message) - } - - func name() -> String { "TEXT_LISTENER" } -} - -final class ConfirmationCallback: NSObject, BindingsAuthConfirmCallbackProtocol { - let callback: (_ partner: BindingsContact) -> () - - init(_ callback: @escaping (_ partner: BindingsContact) -> ()) { - self.callback = callback - super.init() - } - - func callback(_ partner: BindingsContact?) { - guard let partner = partner else { return } - callback(partner) - } -} - -final class RequestCallback: NSObject, BindingsAuthRequestCallbackProtocol { - let callback: (_ requestor: BindingsContact) -> () - - init(_ callback: @escaping (_ requestor: BindingsContact) -> ()) { - self.callback = callback - super.init() - } - - func callback(_ requestor: BindingsContact?) { - guard let requestor = requestor else { return } - callback(requestor) - } -} - -final class HealthCallback: NSObject, BindingsNetworkHealthCallbackProtocol { - let callback: (Bool) -> Void - - init(_ callback: @escaping (Bool) -> Void) { - self.callback = callback - super.init() - } - - func callback(_ p0: Bool) { - callback(p0) - } -} - -final class LogCallback: NSObject, BindingsLogWriterProtocol { - let callback: (String?) -> Void - - init(_ callback: @escaping (String?) -> Void) { - self.callback = callback - super.init() - } - - func log(_ p0: String?) { - callback(p0) - } -} - -final class DeliveryCallback: NSObject, BindingsMessageDeliveryCallbackProtocol { - let callback: (DeliveryResult) -> Void - - init(_ callback: @escaping (DeliveryResult) -> Void) { - self.callback = callback - super.init() - } - - func eventCallback(_ msgID: Data?, delivered: Bool, timedOut: Bool, roundResults: Data?) { - - let content = - """ - "Delivery Callback: - - Timed out: \(timedOut) - - Delivered: \(delivered) - - Message ID in base64: \(String(describing: msgID?.base64EncodedString())) - - Round results in base64: \(String(describing: roundResults?.base64EncodedString()))" - """ - - log(string: content, type: .info) - callback((msgID, delivered, timedOut, roundResults)) - } -} - -final class RoundCallback: NSObject, BindingsRoundCompletionCallbackProtocol { - let callback: (Bool) -> Void - - init(_ callback: @escaping (Bool) -> Void) { - self.callback = callback - super.init() - } - - func eventCallback(_ rid: Int, success: Bool, timedOut: Bool) { - log(string: ">>> Add/Confirm RoundCallback:\nid: \(rid)\nSuccessfull: \(success)\nTimed out: \(timedOut)", type: .info) - callback(success && !timedOut) - } -} - -final class SearchCallback: NSObject, BindingsSingleSearchCallbackProtocol { - let callback: (Result<BindingsContact, Error>) -> Void - - init(_ callback: @escaping (Result<BindingsContact, Error>) -> Void) { - self.callback = callback - super.init() - } - - func callback(_ contact: BindingsContact?, error: String?) { - if let error = error, error.count > 0 { - callback(.failure(NSError.create(error).friendly())) - return - } - - if let contact = contact { - callback(.success(contact)) - } - } -} - -final class EventCallback: NSObject, BindingsEventCallbackFunctionObjectProtocol { - let callback: (BackendEvent) -> Void - - init(_ callback: @escaping (BackendEvent) -> Void) { - self.callback = callback - super.init() - } - - func reportEvent(_ priority: Int, category: String?, evtType: String?, details: String?) { - callback((priority, category, evtType, details)) - } -} - -final class GroupRequestCallback: NSObject, BindingsGroupRequestFuncProtocol { - let callback: (BindingsGroup) -> Void - - init(_ callback: @escaping (BindingsGroup) -> Void) { - self.callback = callback - super.init() - } - - func groupRequestCallback(_ g: BindingsGroup?) { - guard let group = g else { return } - callback(group) - } -} - -final class GroupMessageCallback: NSObject, BindingsGroupReceiveFuncProtocol { - let callback: (BindingsGroupMessageReceive) -> Void - - init(_ callback: @escaping (BindingsGroupMessageReceive) -> Void) { - self.callback = callback - super.init() - } - - func groupReceiveCallback(_ msg: BindingsGroupMessageReceive?) { - guard let message = msg else { return } - callback(message) - } -} - -final class MultiLookupCallback: NSObject, BindingsMultiLookupCallbackProtocol { - let thisCallback: (BindingsContactList?, BindingsIdList?, String?) -> Void - - init(_ callback: @escaping (BindingsContactList?, BindingsIdList?, String?) -> Void) { - self.thisCallback = callback - super.init() - } - - func callback(_ Succeeded: BindingsContactList?, failed: BindingsIdList?, errors: String?) { - thisCallback(Succeeded, failed, errors) - } -} - -final class PreImageCallback: NSObject, BindingsPreimageNotificationProtocol { - let callback: (Data?, Bool) -> Void - - init(_ callback: @escaping (Data?, Bool) -> Void) { - self.callback = callback - super.init() - } - - func notify(_ identity: Data?, deleted: Bool) { - callback(identity, deleted) - } -} - -final class LookupCallback: NSObject, BindingsLookupCallbackProtocol { - let callback: (Result<BindingsContact, Error>) -> Void - - init(_ callback: @escaping (Result<BindingsContact, Error>) -> Void) { - self.callback = callback - super.init() - } - - func callback(_ contact: BindingsContact?, error: String?) { - if let error = error, !error.isEmpty { - callback(.failure(NSError.create(error).friendly())) - return - } - - if let contact = contact { - callback(.success(contact)) - } - } -} - -final class IncomingTransferCallback: NSObject, BindingsFileTransferReceiveFuncProtocol { - let callback: (Data?, String?, String?, Data?, Int, Data?) -> Void - - init(_ callback: @escaping (Data?, String?, String?, Data?, Int, Data?) -> Void) { - self.callback = callback - super.init() - } - - func receiveCallback(_ tid: Data?, fileName: String?, fileType: String?, sender: Data?, size: Int, preview: Data?) { - callback(tid, fileName, fileType, sender, size, preview) - } -} - -final class IncomingTransferProgressCallback: NSObject, BindingsFileTransferReceivedProgressFuncProtocol { - let callback: (Bool, Int, Int, Error?) -> Void - - init(_ callback: @escaping (Bool, Int, Int, Error?) -> Void) { - self.callback = callback - super.init() - } - - func receivedProgressCallback(_ completed: Bool, received: Int, total: Int, t: BindingsFilePartTracker?, err: Error?) { - callback(completed, received, total, err) - } -} - -final class OutgoingTransferProgressCallback: NSObject, BindingsFileTransferSentProgressFuncProtocol { - let callback: (Bool, Int, Int, Int, Error?) -> Void - - init(_ callback: @escaping (Bool, Int, Int, Int, Error?) -> Void) { - self.callback = callback - super.init() - } - - func sentProgressCallback(_ completed: Bool, sent: Int, arrived: Int, total: Int, t: BindingsFilePartTracker?, err: Error?) { - callback(completed, sent, arrived, total, err) - } -} - -final class UpdateBackupCallback: NSObject, BindingsUpdateBackupFuncProtocol { - let callback: (Data) -> Void - - init(_ callback: @escaping (Data) -> Void) { - self.callback = callback - super.init() - } - - func updateBackup(_ encryptedBackup: Data?) { - guard let data = encryptedBackup else { return } - callback(data) - } -} - -final class ResetCallback: NSObject, BindingsAuthResetNotificationCallbackProtocol { - let callback: (BindingsContact) -> Void - - init(_ callback: @escaping (BindingsContact) -> Void) { - self.callback = callback - super.init() - } - - func callback(_ requestor: BindingsContact?) { - guard let requestor = requestor else { return } - callback(requestor) - } -} - -final class RestoreContactsCallback: NSObject, BindingsRestoreContactsUpdaterProtocol { - let callback: (Int, Int, Int, String?) -> Void - - init(_ callback: @escaping (Int, Int, Int, String?) -> Void) { - self.callback = callback - super.init() - } - - func restoreContactsCallback(_ numFound: Int, numRestored: Int, total: Int, err: String?) { - callback(numFound, numRestored, total, err) - } -} diff --git a/Sources/Integration/Client.swift b/Sources/Integration/Client.swift deleted file mode 100644 index 8fc201af..00000000 --- a/Sources/Integration/Client.swift +++ /dev/null @@ -1,236 +0,0 @@ -import Retry -import Models -import Combine -import Defaults -import Bindings -import XXModels -import Foundation - -public class Client { - @KeyObject(.inappnotifications, defaultValue: true) var inappnotifications: Bool - - let bindings: BindingsInterface - var backupManager: BackupInterface? - var dummyManager: DummyTrafficManaging? - var groupManager: GroupManagerInterface? - var userDiscovery: UserDiscoveryInterface? - var transferManager: TransferManagerInterface? - - var backup: AnyPublisher<Data, Never> { backupSubject.eraseToAnyPublisher() } - var network: AnyPublisher<Bool, Never> { networkSubject.eraseToAnyPublisher() } - var resets: AnyPublisher<Contact, Never> { resetsSubject.eraseToAnyPublisher() } - var messages: AnyPublisher<Message, Never> { messagesSubject.eraseToAnyPublisher() } - var requests: AnyPublisher<Contact, Never> { requestsSubject.eraseToAnyPublisher() } - var events: AnyPublisher<BackendEvent, Never> { eventsSubject.eraseToAnyPublisher() } - var transfers: AnyPublisher<FileTransfer, Never> { transfersSubject.eraseToAnyPublisher() } - var requestsSent: AnyPublisher<Contact, Never> { requestsSentSubject.eraseToAnyPublisher() } - var confirmations: AnyPublisher<Contact, Never> { confirmationsSubject.eraseToAnyPublisher() } - var groupRequests: AnyPublisher<(Group, [Data], String?), Never> { groupRequestsSubject.eraseToAnyPublisher() } - - private let backupSubject = PassthroughSubject<Data, Never>() - private let networkSubject = PassthroughSubject<Bool, Never>() - private let resetsSubject = PassthroughSubject<Contact, Never>() - private let requestsSubject = PassthroughSubject<Contact, Never>() - private let messagesSubject = PassthroughSubject<Message, Never>() - private let eventsSubject = PassthroughSubject<BackendEvent, Never>() - private let requestsSentSubject = PassthroughSubject<Contact, Never>() - private let confirmationsSubject = PassthroughSubject<Contact, Never>() - private let transfersSubject = PassthroughSubject<FileTransfer, Never>() - private let groupRequestsSubject = PassthroughSubject<(Group, [Data], String?), Never>() - - private var isBackupInitialization = false - private var isBackupInitializationCompleted = false - - // MARK: Lifecycle - - init( - _ bindings: BindingsInterface, - fromBackup: Bool, - email: String?, - phone: String? - ) { - self.bindings = bindings - self.isBackupInitialization = fromBackup - - do { - try registerListenersAndStart() - - if fromBackup { - try instantiateUserDiscoveryFromBackup(email: email, phone: phone) - } else { - try instantiateUserDiscovery() - } - - try instantiateTransferManager() - try instantiateDummyTrafficManager() - updatePreImage() - } catch { - log(string: error.localizedDescription, type: .error) - } - } - - public func initializeBackup(passphrase: String) { - backupManager = nil - backupManager = bindings.initializeBackup(passphrase: passphrase) { [weak backupSubject] in - backupSubject?.send($0) - } - } - - public func resumeBackup() { - backupManager = nil - backupManager = bindings.resumeBackup { [weak backupSubject] in - backupSubject?.send($0) - } - } - - // public func isBackupRunning() -> Bool { - // guard let backupManager = backupManager else { return false } - // return backupManager.isBackupRunning() - // } - - public func addJson(_ string: String) { - guard let backupManager = backupManager else { - fatalError("Trying to add json parameters to backup but no backup manager created yet") - } - - backupManager.addJson(string) - } - - public func stopListeningBackup() { - guard let backupManager = backupManager else { return } - try? backupManager.stop() - self.backupManager = nil - } - - public func restoreContacts(fromBackup backup: Data) { - var totalPendingRestoration: Int = 0 - - let report = bindings.restore( - ids: backup, - using: userDiscovery!) { [weak self] in - guard let self = self else { return } - - switch $0 { - case .success(var contact): - contact.authStatus = .requested - self.requestsSentSubject.send(contact) - print(">>> Restored \(contact.username). Setting status as requested") - case .failure(let error): - print(">>> \(error.localizedDescription)") - } - } restoreCallback: { numFound, numRestored, total, errorString in - totalPendingRestoration = total - let results = - """ - >>> Results from within closure of RestoreContacts: - - numFound: \(numFound) - - numRestored: \(numRestored) - - total: \(total) - - errorString: \(errorString) - """ - print(results) - } - - guard totalPendingRestoration > 0 else { fatalError("Total is zero, why called restore contacts?") } - - guard report.lenRestored() == totalPendingRestoration else { - print(">>> numRestored \(report.lenRestored()) is != than the total (\(totalPendingRestoration)). Going on recursion...\nnumFailed: \(report.lenFailed())\n\(report.getRestoreContactsError())") - restoreContacts(fromBackup: backup) - return - } - - isBackupInitializationCompleted = true - } - - private func registerListenersAndStart() throws { - bindings.listenNetworkUpdates { [weak networkSubject] in networkSubject?.send($0) } - - bindings.listenRequests { [weak self] in - guard let self = self else { return } - - if self.isBackupInitialization { - if self.isBackupInitializationCompleted { - self.requestsSubject.send($0) - } - } else { - self.requestsSubject.send($0) - } - } _: { [weak confirmationsSubject] in - confirmationsSubject?.send($0) - } _: { [weak resetsSubject] in - resetsSubject?.send($0) - } - - bindings.listenEvents { [weak eventsSubject] in - eventsSubject?.send($0) - } - - groupManager = try bindings.listenGroupRequests { [weak groupRequestsSubject] request, members, welcome in - groupRequestsSubject?.send((request, members, welcome)) - } groupMessages: { [weak messagesSubject] in - messagesSubject?.send($0) - } - - bindings.listenPreImageUpdates() - - try bindings.listenMessages { [weak messagesSubject] in - messagesSubject?.send($0) - } - - bindings.startNetwork() - } - - private func instantiateTransferManager() throws { - transferManager = try bindings.generateTransferManager { [weak transfersSubject] tid, name, type, sender in - - /// Someone transfered something to me - /// but I haven't received yet. I'll store an - /// IncomingTransfer object so later on I can - /// pull up whatever this contact has sent me. - /// - guard let name = name, - let type = type, - let contactId = sender else { - log(string: "Transfer of \(name ?? "nil").\(type ?? "nil") is being dismissed", type: .error) - return - } - - transfersSubject?.send( - FileTransfer( - id: tid, - contactId: contactId, - name: name, - type: type, - data: nil, - progress: 0.0, - isIncoming: true, - createdAt: Date() - ) - ) - } - } - - private func instantiateUserDiscovery() throws { - retry(max: 4, retryStrategy: .delay(seconds: 1)) { [weak self] in - guard let self = self else { return } - self.userDiscovery = try self.bindings.generateUD() - } - } - - private func instantiateUserDiscoveryFromBackup(email: String?, phone: String?) throws { - retry(max: 4, retryStrategy: .delay(seconds: 1)) { [weak self] in - guard let self = self else { return } - self.userDiscovery = try self.bindings.generateUDFromBackup(email: email, phone: phone) - } - } - - private func instantiateDummyTrafficManager() throws { - dummyManager = try bindings.generateDummyTraficManager() - } - - private func updatePreImage() { - if let defaults = UserDefaults(suiteName: "group.elixxir.messenger") { - defaults.set(bindings.getPreImages(), forKey: "preImage") - } - } -} diff --git a/Sources/Integration/Extensions.swift b/Sources/Integration/Extensions.swift deleted file mode 100644 index f2afdcb9..00000000 --- a/Sources/Integration/Extensions.swift +++ /dev/null @@ -1,58 +0,0 @@ -import Models -import XXModels -import Bindings - -extension Contact { - init(with contact: BindingsContact, status: Contact.AuthStatus) { - self.init( - id: contact.getID()!, - marshaled: try! contact.marshal(), - username: contact.retrieve(fact: .username) ?? "", - email: contact.retrieve(fact: .email), - phone: contact.retrieve(fact: .phone), - nickname: nil, - photo: nil, - authStatus: status, - isRecent: false, - createdAt: Date() - ) - } -} - -extension Message { - init(with message: BindingsMessage, myId: Data) { - guard let payload = try? Payload(with: message.getPayload()!) else { fatalError() } - - self.init( - networkId: message.getID()!, - senderId: message.getSender()!, - recipientId: myId, - groupId: nil, - date: Date.fromTimestamp(Int(message.getTimestampNano())), - status: .received, - isUnread: true, - text: payload.text, - replyMessageId: payload.reply?.messageId, - roundURL: message.getRoundURL(), - fileTransferId: nil - ) - } - - init(with message: BindingsGroupMessageReceive) { - guard let payload = try? Payload(with: message.getPayload()!) else { fatalError() } - - self.init( - networkId: message.getMessageID()!, - senderId: message.getSenderID()!, - recipientId: nil, - groupId: message.getGroupID()!, - date: Date.fromTimestamp(Int(message.getTimestampNano())), - status: .received, - isUnread: true, - text: payload.text, - replyMessageId: payload.reply?.messageId, - roundURL: message.getRoundURL(), - fileTransferId: nil - ) - } -} diff --git a/Sources/Integration/Implementations/Bindings.swift b/Sources/Integration/Implementations/Bindings.swift deleted file mode 100644 index a05387dd..00000000 --- a/Sources/Integration/Implementations/Bindings.swift +++ /dev/null @@ -1,613 +0,0 @@ -import Shared -import Models -import Bindings -import XXModels -import Foundation -import DependencyInjection - -public let evaluateNotification: NotificationEvaluation = BindingsNotificationsForMe - -public protocol NotificationReportProtocol { - func forMe() -> Bool - func type() -> String - func source() -> Data? -} - -public protocol NotificationManyReportProtocol { - func len() -> Int - func get(index: Int) throws -> NotificationReportProtocol -} - -extension BindingsNotificationForMeReport: NotificationReportProtocol {} - -extension BindingsManyNotificationForMeReport: NotificationManyReportProtocol { - public func get(index: Int) throws -> NotificationReportProtocol { - try get(index) - } -} - -extension BindingsClient: BindingsInterface { - public func removeContact(_ data: Data) throws { - do { - try deleteContact(data) - log(string: "Deleted a contact", type: .info) - } catch { - log(string: "Failed to delete a contact: \(error.localizedDescription)", type: .error) - throw error.friendly() - } - } - - func dumpThreads() { - log(type: .crumbs) - - var error: NSError? - let string = BindingsDumpStack(&error) - - if let error = error { - log(string: error.localizedDescription, type: .error) - return - } - - log(string: string, type: .bindings) - } - - public func resetSessionWith(_ recipient: Data) { - var int: Int = 0 - - do { - try resetSession(recipient, meMarshaled: meMarshalled, message: "", ret0_: &int) - } catch { - print(">>> \(error.localizedDescription)") - } - } - - public func verify(marshaled: Data, verifiedMarshaled: Data) throws -> Bool { - var bool: ObjCBool = false - try verifyOwnership(marshaled, verifiedMarshaled: verifiedMarshaled, ret0_: &bool) - log(string: "Onwership verification: \(bool.boolValue)", type: bool.boolValue ? .info : .error) - return bool.boolValue - } - - public func compress( - image: Data, - _ completion: @escaping(Result<Data, Error>) -> Void - ) { - var error: NSError? - let compressed = BindingsCompressJpeg(image, &error) - - guard error == nil else { - log(string: "Error when compressing jpeg: \(error!.localizedDescription)", type: .error) - completion(.failure(error!.friendly())) - return - } - - guard let compressed = compressed else { - completion(.failure(NSError.create("Image compression failed without error"))) - return - } - - let compressionRate = String(format: "%.4f", Float(compressed.count)/Float(image.count)) - log(string: "Compressed image x\(compressionRate) (\(image.count) -> \(compressed.count))", type: .info) - completion(.success(compressed)) - } - - public var hasRunningTasks: Bool { - hasRunningProcessies() - } - - public var myId: Data { - guard let user = getUser(), let contact = user.getContact(), let id = contact.getID() else { - fatalError("Couldn't get my ID") - } - - return id - } - - public var meMarshalled: Data { - guard let user = getUser(), let contact = user.getContact(), let marshal = try? contact.marshal() else { - fatalError("Couldn't get my own contact marshalled") - } - - return marshal - } - - public func getPreImages() -> String { - getPreimages(receptionId) - } - - public func meMarshalled(_ username: String, email: String?, phone: String?) -> Data { - guard let user = getUser(), - let contact = user.getContact(), - let factList = contact.getFactList() else { fatalError() } - - try! factList.add(username, factType: FactType.username.rawValue) - - if let email = email { - try! factList.add(email, factType: FactType.email.rawValue) - } - - if let phone = phone { - try! factList.add(phone, factType: FactType.phone.rawValue) - } - - return try! contact.marshal() - } - - public var receptionId: Data { - guard let user = getUser(), let recId = user.getReceptionID() else { fatalError() } - return recId - } - - public static let version: String = { - return BindingsGetVersion() - }() - - public static let new: ClientNew = BindingsNewClient - - public static let fromBackup: ClientFromBackup = BindingsNewClientFromBackup - - public static let secret: (Int) -> Data? = BindingsGenerateSecret - - public static let login: (String?, Data?, String?, NSErrorPointer) -> BindingsInterface? = BindingsLogin - - public static func updateNDF( - for env: NetworkEnvironment, - _ completion: @escaping (Result<Data?, Error>) -> Void - ) { - var error: NSError? - let ndf = BindingsDownloadAndVerifySignedNdfWithUrl(env.url, env.cert, &error) - - guard error == nil else { - Self.updateNDF(for: env, completion) - return - } - - completion(.success(ndf)) - } - - /// Fetches a JSON with up-to-date error descriptions - /// then passes it to the bindings that will emit cleaner - /// errors - /// - /// - ToDo: Request status codes for errors - /// - public static func updateErrors() { - log(type: .crumbs) - - var error: NSError? - if let dbErrors = BindingsDownloadErrorDB(&error) { - var otherError: NSError? - BindingsUpdateCommonErrors(String(data: dbErrors, encoding: .utf8), &otherError) - - if let otherError = otherError { - log(string: otherError.localizedDescription, type: .error) - } - } - - if let error = error { - log(string: error.localizedDescription, type: .error) - } - } - - /// Starts the network - /// - /// If network status is != 0 it means the network is - /// not ready yet or the device is not ready. A recursion was applied - /// as a temporary solution in order to retry indefinitely - /// - /// - ToDo: Split function into smaller functions - /// - public func startNetwork() { - log(type: .crumbs) - - var error: NSError? - let status = networkFollowerStatus() - - BindingsLogLevel(1, &error) - registerErrorCallback(BindingsError()) - - guard status == 0 else { - log(string: ">>> Network is not ready yet. Let's give it a second...", type: .error) - sleep(1) - startNetwork() - return - } - - try! startNetworkFollower(10000) - log(string: ">>> Starting the network...", type: .info) - } - - /// (Tries) to stop the network - /// - /// - Warning: This function tries to stop several - /// threads and it may take some time. - /// That's why we register a background - /// task on AppDelegate.swift - /// - public func stopNetwork() { - log(type: .crumbs) - - try! stopNetworkFollower() - log(string: "Stopping the network...", type: .info) - } - - /// Extracts *user id* from a contact - /// - /// - Parameters: - /// - from: Byte array containing contact object - /// - /// - Returns: Optional byte array, if *user id* could be retrieved - /// - public func getId(from marshaled: Data) -> Data? { - log(type: .crumbs) - - var error: NSError? - let contact = BindingsUnmarshalContact(marshaled, &error) - - if let error = error { - log(string: error.localizedDescription, type: .error) - return nil - } - - return contact?.getID() - } - - public func add(_ contact: Data, from me: Data, _ completion: @escaping (Result<Bool, Error>) -> Void) { - log(type: .crumbs) - - do { - var roundId = Int() - try requestAuthenticatedChannel(contact, meMarshaled: me, message: nil, ret0_: &roundId) - completion(.success(true)) - } catch { - log(string: error.localizedDescription, type: .error) - completion(.failure(error.friendly())) - } - } - - /// Confirms a contact request - /// - /// - Parameters: - /// - contact: Byte array containing *contact object* - /// - completion: Result callback with associated - /// values *boolean* = success && - /// !timedOut or *Error* upon throwing - /// - public func confirm(_ contact: Data, _ completion: @escaping (Result<Bool, Error>) -> Void) { - log(type: .crumbs) - - do { - var roundId = Int() - try confirmAuthenticatedChannel(contact, ret0_: &roundId) - completion(.success(true)) - } catch { - log(string: error.localizedDescription, type: .error) - completion(.failure(error.friendly())) - } - } - - /// Sends a message over CMIX - /// - /// - Parameters: - /// - recipient: Byte array containing *user id* - /// - payload: Byte array containing *message payload* - /// - /// - Returns: Result w/ associated values - /// byte array containing *SentReport* - /// or *Error* upon throwing - /// - public func send(_ payload: Data, to recipient: Data) -> Result<E2ESendReportType, Error> { - log(type: .crumbs) - - do { - let report = try sendE2E(recipient, payload: payload, messageType: 2, parameters: nil) - - var roundIds = [Int]() - - if let roundList = report.getRoundList(), let payloadUnwrapped = try? Payload(with: payload) { - let length = roundList.len() - for index in 0..<length { - var integer: Int = 0 - do { - try roundList.get(index, ret0_: &integer) - roundIds.append(integer) - } catch { - log(string: "Error trying to inspect round list: \(error.localizedDescription)", type: .error) - } - } - - log(string: "Round ids for \(payloadUnwrapped.text.prefix(5))... = \(roundIds)", type: .info) - } - - return .success(report) - } catch { - log(string: error.localizedDescription, type: .error) - return .failure(error) - } - } - - /// Listens to the delivery of a message through a report - /// - /// - Note: Delivery actually refers to the - /// gateway, not necessarily the other end - /// received/read this message yet. - /// - /// - Parameters: - /// - report: SentReport marshalled - /// - completion: Result callback w/ associated - /// values *completed* or *Error* - /// upon throwing - /// - public func listen(report: Data, _ completion: @escaping (Result<MessageDeliveryStatus, Error>) -> Void) { - do { - try listenDelivery(of: report) { msgId, delivered, timedOut, roundResults in - let status: MessageDeliveryStatus - - if delivered == false { - let extendedLogs = - """ - Round delivery callback from wait(forMessageDelivery:) - - timedOut = \(timedOut) - - delivered = \(delivered) - """ - log(string: extendedLogs, type: .error) - log(string: extendedLogs, type: .error) - - if timedOut == true { - status = .timedout - } else { - status = .failed - } - } else { - status = .sent - } - - completion(.success(status)) - } - } catch { - completion(.failure(error)) - } - } - - public func registerNotifications(_ token: Data) throws { - let tokenString = token.map { String(format: "%02hhx", $0) }.joined() - - do { - try register(forNotifications: tokenString) - } catch { - throw error.friendly() - } - } - - /// Unregisters device token on backend - /// - /// - Throws: If when trying to unregister - /// some exception come up such as - /// timing out or user is not registered - /// - public func unregisterNotifications() throws { - log(type: .crumbs) - - do { - try unregisterForNotifications() - log(string: "Unregistered notifications", type: .info) - } catch { - log(string: error.localizedDescription, type: .error) - throw error.friendly() - } - } - - /// Checks if number of nodes already registered is enough - /// - /// Whenever the user wants to do an operation that involves - /// *User Discovery*, the app should make sure that a minimum - /// amount of nodes already know about this user - /// - /// - Throws: `NodeRegistrationError.amountIsTooLow` if - /// the ratio is below minimum (currently 85%). - /// `NodeRegistrationError.networkIsNotHealthyYet` - /// when trying to fetch registration status and - /// network is not healthy yet - /// - public func nodeRegistrationStatus() throws { - log(type: .crumbs) - - enum NodeRegistrationError: Error { - case amountIsTooLow - } - - var shortRatio: String? - - do { - let status = try getNodeRegistrationStatus() - let registered = Float(status.getRegistered()) - let total = Float(status.getTotal()) - let ratio = Float(registered/total) - - let nf = NumberFormatter() - nf.roundingMode = .down - nf.maximumFractionDigits = 2 - nf.numberStyle = .percent - shortRatio = nf.string(from: NSNumber(value: ratio)) - - guard ratio >= 0.85 else { throw NodeRegistrationError.amountIsTooLow } - log(string: "Node registration rate: \(shortRatio ?? "")", type: .info) - } catch NodeRegistrationError.amountIsTooLow { - - let string = "Node registration rate is still below 85% (\(shortRatio ?? ""))" - log(string: string, type: .error) - - let userError = "We are still establishing a secure registration with the decentralized network. Please try again in a few seconds." - - throw NSError.create(userError) - } catch { - log(string: error.localizedDescription, type: .error) - throw error - } - } - - /// Instantiates a transfer manager - /// - /// - Returns: An instance of *BindingsFileTransfer (TransferManager)* - /// - /// - Throws: `FTError.noInstance` if no error was thrown - /// but also no instance was created - /// - public func generateTransferManager( - _ callback: @escaping (Data, String?, String?, Data?) -> Void - ) throws -> TransferManagerInterface { - log(type: .crumbs) - - let incomingTransferCallback = IncomingTransferCallback { tid, name, type, sender, size, preview in - guard let tid = tid else { fatalError("An incoming transfer has no TID?") } - - callback(tid, name, type, sender) - } - - var error: NSError? - let manager = BindingsNewFileTransferManager(self, incomingTransferCallback, "", &error) - - guard let error = error else { return manager! } - throw error.friendly() - } - - public func generateDummyTraficManager() throws -> DummyTrafficManaging { - var error: NSError? - let manager = BindingsNewDummyTrafficManager(self, 5, 30000, 25000, &error) - - guard let error = error else { return manager! } - throw error.friendly() - } - - public func generateUDFromBackup(email: String?, phone: String?) throws -> UserDiscoveryInterface { - var error: NSError? - - let paramEmail = email != nil ? "E\(email!)" : nil - let paramPhone = phone != nil ? "P\(phone!)" : nil - - let udb = BindingsNewUserDiscoveryFromBackup(self, paramEmail, paramPhone, &error) - - /// Alternate udb - - guard let certPath = Bundle.module.path(forResource: "ud.elixxir.io", ofType: "crt") else { - fatalError("Couldn't retrieve cert.") - } - - guard let contactFilePath = Bundle.module.path(forResource: "udContact-test", ofType: "bin") else { - fatalError("Couldn't retrieve cert.") - } - -// try! udb!.setAlternative( -// "18.198.117.203:11420".data(using: .utf8), -// cert: try! Data(contentsOf: URL(fileURLWithPath: certPath)), -// contactFile: try! Data(contentsOf: URL(fileURLWithPath: contactFilePath)) -// ) - - guard let error = error else { return udb! } - throw error.friendly() - } - - public func generateUD() throws -> UserDiscoveryInterface { - log(type: .crumbs) - - var error: NSError? - let udb = BindingsNewUserDiscovery(self, &error) - - /// Alternate udb - - guard let certPath = Bundle.module.path(forResource: "ud.elixxir.io", ofType: "crt") else { - fatalError("Couldn't retrieve cert.") - } - - guard let contactFilePath = Bundle.module.path(forResource: "udContact-test", ofType: "bin") else { - fatalError("Couldn't retrieve cert.") - } - -// try! udb!.setAlternative( -// "18.198.117.203:11420".data(using: .utf8), -// cert: try! Data(contentsOf: URL(fileURLWithPath: certPath)), -// contactFile: try! Data(contentsOf: URL(fileURLWithPath: contactFilePath)) -// ) - - guard let error = error else { return udb! } - throw error.friendly() - } - - public func restore( - ids: Data, - using ud: UserDiscoveryInterface, - lookupCallback: @escaping (Result<Contact, Error>) -> Void, - restoreCallback: @escaping (Int, Int, Int, String?) -> Void - ) -> RestoreReportType { - let restoreCb = RestoreContactsCallback(restoreCallback) - - let lookupCb = LookupCallback { - switch $0 { - case .success(let contact): - lookupCallback(.success(.init(with: contact, status: .stranger))) - case .failure(let error): - lookupCallback(.failure(error)) - } - } - - return BindingsRestoreContactsFromBackup(ids, self, ud as? BindingsUserDiscovery, lookupCb, restoreCb)! - } -} - -extension BindingsContact { - - /// Scans the contact instance for a specified fact - /// - /// - Parameters: - /// - fact: enum defined in ```FactType``` - /// that specifies the type we're - /// searching - /// - /// - Note: Since GoLang does not support collections - /// We need to do this workaround *length* and - /// *get* instead of subscripting as in Swift. - /// - /// - Returns: Optional string in case we find the the fact - /// - /// - ToDo: Return a struct that contains all possible facts (?) - /// - func retrieve(fact: FactType) -> String? { - log(type: .crumbs) - - guard let factList = getFactList() else { return nil } - for index in 0..<factList.num() { - if let actualFact = factList.get(index) { - if actualFact.type() == fact.rawValue { - return String(actualFact.stringify().dropFirst()) - } - } - } - return nil - } -} - -extension BindingsSendReport: E2ESendReportType { - public var marshalled: Data { try! marshal() } - public var timestamp: Int64 { getTimestampNano() } - public var uniqueId: Data? { getMessageID() } - public var roundURL: String { getRoundURL() } -} - -public protocol DummyTrafficManaging { - var status: Bool { get } - func setStatus(status: Bool) -} - -extension BindingsDummyTraffic: DummyTrafficManaging { - public var status: Bool { - getStatus() - } - - public func setStatus(status: Bool) { - try? setStatus(status) - } -} - -extension BindingsBackup: BackupInterface {} - -extension BindingsRestoreContactsReport: RestoreReportType {} diff --git a/Sources/Integration/Implementations/GroupManager.swift b/Sources/Integration/Implementations/GroupManager.swift deleted file mode 100644 index 4b3dc4ef..00000000 --- a/Sources/Integration/Implementations/GroupManager.swift +++ /dev/null @@ -1,94 +0,0 @@ -import Models -import XXModels -import Bindings - -extension BindingsGroupChat: GroupManagerInterface { - public func send(_ payload: Data, to group: Data) -> Result<(Int64, Data?, String), Error> { - log(type: .crumbs) - - do { - let report = try send(group, message: payload) - return .success(( - report.getRoundID(), - report.getMessageID(), - report.getRoundURL() - )) - } catch { - return .failure(error) - } - } - - public func create( - me: Data, - name: String, - welcome: String?, - with ids: [Data], - _ completion: @escaping (Result<Group, Error>) -> Void - ) { - log(type: .crumbs) - - let list = BindingsIdList() - ids.forEach { try? list.add($0) } - - var welcomeData: Data? - - DispatchQueue.global().async { [weak self] in - guard let self = self else { return } - - if let welcome = welcome { - welcomeData = welcome.data(using: .utf8) - } - - let report = self.makeGroup(list, name: name.data(using: .utf8), message: welcomeData) - - if let status = report?.getStatus() { - switch status { - case 0: - completion(.failure(NSError.create("An error occurred before any requests could be sent"))) - return - case 1, 2: - // 1. All requests failed to send - // 2. Some requests failed and some succeeded - - if let id = report?.getGroup()?.getID() { - do { - try self.resendRequest(id) - fallthrough - } catch { - completion(.failure(error)) - return - } - } - case 3: - // All good - guard let group = report?.getGroup() else { - let errorContent = "Couldn't get report from group, although status was 3." - completion(.failure(NSError.create(errorContent))) - log(string: errorContent, type: .error) - return - } - - completion(.success(.init( - id: group.getID()!, - name: name, - leaderId: me, - createdAt: Date(), - authStatus: .participating, - serialized: group.serialize()! - ))) - return - default: - break - } - } - } - } - - public func join(_ serializedGroup: Data) throws { - try joinGroup(serializedGroup) - } - - public func leave(_ groupId: Data) throws { - try leaveGroup(groupId) - } -} diff --git a/Sources/Integration/Implementations/TransferManager.swift b/Sources/Integration/Implementations/TransferManager.swift deleted file mode 100644 index 8c38a91f..00000000 --- a/Sources/Integration/Implementations/TransferManager.swift +++ /dev/null @@ -1,63 +0,0 @@ -import Models -import Bindings -import Foundation - -extension BindingsFileTransfer: TransferManagerInterface { - - public func endTransferUpload( - with TID: Data - ) throws { - try closeSend(TID) - } - - public func listenUploadFromTransfer( - with id: Data, - _ callback: @escaping (Bool, Int, Int, Int, Error?) -> Void - ) throws { - let cb = OutgoingTransferProgressCallback { completed, sent, arrived, total, error in - callback(completed, sent, arrived, total, error) - } - - try registerSendProgressCallback(id, progressFunc: cb, periodMS: 1000) - } - - public func listenDownloadFromTransfer( - with id: Data, - _ callback: @escaping (Bool, Int, Int, Error?) -> Void - ) throws { - let cb = IncomingTransferProgressCallback { completed, received, total, error in - callback(completed, received, total, error) - } - - try registerReceiveProgressCallback(id, progressFunc: cb, periodMS: 1000) - } - - public func downloadFileFromTransfer( - with id: Data - ) throws -> Data { - try receive(id) - } - - public func uploadFile( - url: URL, - to recipient: Data, - _ callback: @escaping (Bool, Int, Int, Int, Error?) -> Void - ) throws -> Data { - let cb = OutgoingTransferProgressCallback { completed, sent, arrived, total, error in - callback(completed, sent, arrived, total, error) - } - - guard let file = try? Data(contentsOf: url) else { fatalError() } - - return try send( - url.lastPathComponent, - fileType: url.pathExtension, - fileData: file, - recipientID: recipient, - retry: 1, - preview: nil, - progressFunc: cb, - periodMS: 1000 - ) - } -} diff --git a/Sources/Integration/Implementations/UserDiscovery.swift b/Sources/Integration/Implementations/UserDiscovery.swift deleted file mode 100644 index 56c9b4de..00000000 --- a/Sources/Integration/Implementations/UserDiscovery.swift +++ /dev/null @@ -1,166 +0,0 @@ -import Retry -import Models -import XXModels -import Bindings -import Foundation - -extension BindingsUserDiscovery: UserDiscoveryInterface { - public func lookup(forUserId: Data, _ completion: @escaping (Result<Contact, Error>) -> Void) { - let callback = LookupCallback { - switch $0 { - case .success(let contact): - completion(.success(.init(with: contact, status: .stranger))) - case .failure(let error): - completion(.failure(error)) - } - } - - retry(max: 10, retryStrategy: .delay(seconds: 1)) { [weak self] in - guard let self = self else { return } - try self.lookup(forUserId, callback: callback, timeoutMS: 20000) - }.finalCatch { error in - log(string: "UD.lookup 4E2E failed:\n\(error.localizedDescription)", type: .error) - completion(.failure(error.friendly())) - } - } - - public func lookup(idList: [Data], _ completion: @escaping (Result<[Contact], Error>) -> Void) { - let list = BindingsIdList() - idList.forEach { try? list.add($0) } - - let callback = MultiLookupCallback { [weak self] contactList, idList, error in - guard let self = self else { return } - - if let error = error, error.count > 2 { - log(string: "UD.lookup group failed: \(error)", type: .error) - completion(.failure(NSError.create(error).friendly())) - return - } - - guard let contacts = contactList else { return } - let count = contacts.len() - var results = [Contact]() - - for index in 0..<count { - guard let contact = try? contacts.get(index), - let marshal = try? contact.marshal(), - ((try? self.retrieve(from: marshal, fact: .username) != nil) != nil) else { - log(string: "Skipping", type: .error); continue - } - - results.append(Contact(with: contact, status: .stranger)) - } - - completion(.success(results)) - } - - DispatchQueue.global().async { [weak self] in - guard let self = self else { return } - - do { - try self.multiLookup(list, callback: callback, timeoutMS: 30000) - } catch { - log(string: "UD.lookup group failed: \(error.localizedDescription)", type: .error) - completion(.failure(error.friendly())) - } - } - } - - public func deleteMyself(_ username: String) throws { - log(type: .crumbs) - - do { - try removeUser("U\(username)") - } catch { - throw error.friendly() - } - } - - public func register(_ fact: FactType, value: String, _ completion: @escaping (Result<String?, Error>) -> Void) { - log(type: .crumbs) - - if fact == .username { - do { - try register(value) - completion(.success(value)) - return - } catch { - completion(.failure(error.friendly())) - return - } - } - - var error: NSError? - let bindingsFact = BindingsNewFact(fact.rawValue, value, &error) - - if let error = error { - completion(.failure(error.friendly())) - return - } - - var otherError: NSError? - let confirmationId = addFact(bindingsFact?.stringify(), error: &otherError) - - if let otherError = otherError { - completion(.failure(otherError)) - return - } - - completion(.success(confirmationId)) - } - - public func confirm(code: String, id: String) throws { - log(type: .crumbs) - - do { - try confirmFact(id, code: code) - } catch { - throw error.friendly() - } - } - - public func retrieve( - from marshaled: Data, - fact: FactType - ) throws -> String? { - - log(type: .crumbs) - - var error: NSError? - let contact = BindingsUnmarshalContact(marshaled, &error) - if let err = error { - throw err.friendly() - } - - return contact?.retrieve(fact: fact) - } - - public func remove(_ fact: String) throws { - log(type: .crumbs) - - do { - try removeFact(fact) - } catch { - throw error.friendly() - } - } - - public func search(fact: String, _ completion: @escaping (Result<Contact, Error>) -> Void) throws { - log(type: .crumbs) - - let callback = SearchCallback { - switch $0 { - case .success(let contact): - completion(.success(Contact(with: contact, status: .stranger))) - case .failure(let error): - completion(.failure(error)) - } - } - - do { - try searchSingle(fact, callback: callback, timeoutMS: 50000) - } catch { - throw error.friendly() - } - } -} diff --git a/Sources/Integration/Interfaces/BindingsInterface.swift b/Sources/Integration/Interfaces/BindingsInterface.swift deleted file mode 100644 index b2eef8f9..00000000 --- a/Sources/Integration/Interfaces/BindingsInterface.swift +++ /dev/null @@ -1,174 +0,0 @@ -import Models -import Combine -import XXModels -import Foundation - -public enum MessageDeliveryStatus { - case sent - case failed - case timedout -} - -public typealias DeliveryResult = (Data?, Bool, Bool, Data?) - -public typealias BackendEvent = (Int, String?, String?, String?) - -public typealias ClientNew = (String?, String?, Data?, String?, NSErrorPointer) -> Bool - -public typealias ClientFromBackup = (String?, String?, Data?, Data?, Data?, NSErrorPointer) -> Data? - -public typealias NotificationEvaluation = (String?, String?, NSErrorPointer) -> NotificationManyReportProtocol? - -public protocol E2ESendReportType { - var timestamp: Int64 { get } - var uniqueId: Data? { get } - var marshalled: Data { get } - var roundURL: String { get } -} - -public protocol BackupInterface { - func stop() throws - func addJson(_: String?) - func isBackupRunning() -> Bool -} - -public protocol RestoreReportType { - func lenFailed() -> Int - func lenRestored() -> Int - func getErrorAt(_: Int) -> String - func getFailedAt(_: Int) -> Data? - func getRestoreContactsError() -> String - func getRestoredAt(_: Int) -> Data? -} - -public protocol BindingsInterface { - - // MARK: Properties - - var myId: Data { get } - - var hasRunningTasks: Bool { get } - - var receptionId: Data { get } - - var meMarshalled: Data { get } - - func meMarshalled(_: String, email: String?, phone: String?) -> Data - - func verify(marshaled: Data, verifiedMarshaled: Data) throws -> Bool - - func nodeRegistrationStatus() throws - - // MARK: Static - - static func updateErrors() - - static var version: String { get } - - static var secret: (Int) -> Data? { get } - - static var login: (String?, Data?, String?, NSErrorPointer) -> BindingsInterface? { get } - - static var new: ClientNew { get } - - static var fromBackup: ClientFromBackup { get } - - static func updateNDF(for: NetworkEnvironment, _: @escaping (Result<Data?, Error>) -> Void) - - // MARK: Network - - func startNetwork() - - func stopNetwork() - - func replayRequests() - - // MARK: Contacts - - func getId(from: Data) -> Data? - - func confirm(_: Data, _: @escaping (Result<Bool, Error>) -> Void) - - func add(_: Data, from: Data, _: @escaping (Result<Bool, Error>) -> Void) - - // MARK: Messages - - func send(_ payload: Data, to recipient: Data) -> Result<E2ESendReportType, Error> - - func compress(image: Data, _: @escaping(Result<Data, Error>) -> Void) - - func resetSessionWith(_: Data) - - func listen( - report: Data, - _: @escaping (Result<MessageDeliveryStatus, Error>) -> Void - ) - - func listenRound( - id: Int, - _: @escaping (Result<Bool, Error>) -> Void - ) - - // MARK: Notifications - - func getPreImages() -> String - - func registerNotifications(_ token: Data) throws - - func unregisterNotifications() throws - - func generateDummyTraficManager() throws -> DummyTrafficManaging - - // MARK: UD - - func generateUD() throws -> UserDiscoveryInterface - - func generateUDFromBackup(email: String?, phone: String?) throws -> UserDiscoveryInterface - - // MARK: FileTransfer - - func generateTransferManager( - _: @escaping (Data, String?, String?, Data?) -> Void - ) throws -> TransferManagerInterface - - // MARK: Listeners - - static func listenLogs() - - func listenEvents(_: @escaping (BackendEvent) -> Void) - - func listenMessages(_: @escaping (Message) -> Void) throws - - func initializeBackup( - passphrase: String, - callback: @escaping (Data) -> Void - ) -> BackupInterface - - func resumeBackup( - callback: @escaping (Data) -> Void - ) -> BackupInterface - - func listenRequests( - _ requests: @escaping (Contact) -> Void, - _ confirmations: @escaping (Contact) -> Void, - _ resets: @escaping (Contact) -> Void - ) - - func listenPreImageUpdates() - - func listenGroupRequests( - _: @escaping (Group, [Data], String?) -> Void, - groupMessages: @escaping (Message) -> Void - ) throws -> GroupManagerInterface? - - func listenNetworkUpdates(_: @escaping (Bool) -> Void) - - func removeContact(_ data: Data) throws - - func restore( - ids: Data, - using: UserDiscoveryInterface, - lookupCallback: @escaping (Result<Contact, Error>) -> Void, - restoreCallback: @escaping (Int, Int, Int, String?) -> Void - ) -> RestoreReportType -} diff --git a/Sources/Integration/Interfaces/GroupManagerInterface.swift b/Sources/Integration/Interfaces/GroupManagerInterface.swift deleted file mode 100644 index dcddfa9e..00000000 --- a/Sources/Integration/Interfaces/GroupManagerInterface.swift +++ /dev/null @@ -1,14 +0,0 @@ -import Models -import XXModels -import Foundation - -public protocol GroupManagerInterface { - - func join(_: Data) throws - - func leave(_: Data) throws - - func send(_: Data, to: Data) -> Result<(Int64, Data?, String), Error> - - func create(me: Data, name: String, welcome: String?, with: [Data], _: @escaping (Result<Group, Error>) -> Void) -} diff --git a/Sources/Integration/Interfaces/TransferManagerInterface.swift b/Sources/Integration/Interfaces/TransferManagerInterface.swift deleted file mode 100644 index b95d1ee3..00000000 --- a/Sources/Integration/Interfaces/TransferManagerInterface.swift +++ /dev/null @@ -1,27 +0,0 @@ -import Foundation - -public protocol TransferManagerInterface { - func endTransferUpload( - with TID: Data - ) throws - - func listenUploadFromTransfer( - with: Data, - _: @escaping (Bool, Int, Int, Int, Error?) -> Void - ) throws - - func listenDownloadFromTransfer( - with: Data, - _: @escaping (Bool, Int, Int, Error?) -> Void - ) throws - - func downloadFileFromTransfer( - with: Data - ) throws -> Data - - func uploadFile( - url: URL, - to: Data, - _: @escaping (Bool, Int, Int, Int, Error?) -> Void - ) throws -> Data -} diff --git a/Sources/Integration/Interfaces/UserDiscoveryInterface.swift b/Sources/Integration/Interfaces/UserDiscoveryInterface.swift deleted file mode 100644 index ded311ec..00000000 --- a/Sources/Integration/Interfaces/UserDiscoveryInterface.swift +++ /dev/null @@ -1,27 +0,0 @@ -import Models -import XXModels -import Foundation - -public struct LookupResult { - public let id: Data - public let username: String -} - -public protocol UserDiscoveryInterface { - - func remove(_: String) throws - - func deleteMyself(_: String) throws - - func confirm(code: String, id: String) throws - - func retrieve(from: Data, fact: FactType) throws -> String? - - func lookup(forUserId: Data, _: @escaping (Result<Contact, Error>) -> Void) - - func search(fact: String, _: @escaping (Result<Contact, Error>) -> Void) throws - - func lookup(idList: [Data], _: @escaping (Result<[Contact], Error>) -> Void) - - func register(_: FactType, value: String, _: @escaping (Result<String?, Error>) -> Void) -} diff --git a/Sources/Integration/Listeners.swift b/Sources/Integration/Listeners.swift deleted file mode 100644 index 7ab87760..00000000 --- a/Sources/Integration/Listeners.swift +++ /dev/null @@ -1,151 +0,0 @@ -import Models -import Shared -import os.log -import Combine -import XXModels -import Bindings -import Foundation - -public extension BindingsClient { - static func listenLogs() { - let callback = LogCallback { log(string: $0 ?? "", type: .bindings) } - BindingsRegisterLogWriter(callback) - } - - func listenPreImageUpdates() { - let callback = PreImageCallback { [weak self] _, _ in - if let defaults = UserDefaults(suiteName: "group.elixxir.messenger") { - let preImage = self?.getPreImages() - defaults.set(preImage, forKey: "preImage") - } - } - - registerPreimageCallback(receptionId, pin: callback) - } - - func initializeBackup(passphrase: String, callback: @escaping (Data) -> Void) -> BackupInterface { - var error: NSError? - os_signpost(.begin, log: logHandler, name: "Encrypting", "Calling BindingsInitializeBackup") - let backup = BindingsInitializeBackup(passphrase, UpdateBackupCallback(callback), self, &error) - os_signpost(.end, log: logHandler, name: "Encrypting", "Finished BindingsInitializeBackup") - return backup! - } - - func resumeBackup(callback: @escaping (Data) -> Void) -> BackupInterface { - var error: NSError? - let backup = BindingsResumeBackup(UpdateBackupCallback(callback), self, &error) - return backup! - } - - func listenMessages(_ callback: @escaping (Message) -> Void) throws { - let zeroBytes = [UInt8](repeating: 0, count: 33) - - let listener = TextListener { bindingsMessage in - guard let message = bindingsMessage else { return } - let domainModel = Message(with: message, myId: self.myId) - callback(domainModel) - } - - _ = try! registerListener(Data(zeroBytes), msgType: 2, listener: listener) - } - - func listenRequests( - _ requests: @escaping (Contact) -> Void, - _ confirmations: @escaping (Contact) -> Void, - _ resets: @escaping (Contact) -> Void - ) { - let resetCallback = ResetCallback { resets(Contact(with: $0, status: .friend)) } - let confirmCallback = ConfirmationCallback { confirmations(Contact(with: $0, status: .friend)) } - let requestCallback = RequestCallback { requests(Contact(with: $0, status: .verificationInProgress)) } - registerAuthCallbacks(requestCallback, confirm: confirmCallback, reset: resetCallback) - } - - func listenNetworkUpdates(_ callback: @escaping (Bool) -> Void) { - registerNetworkHealthCB(HealthCallback(callback)) - } - - func listenEvents(_ completion: @escaping (BackendEvent) -> Void) { - do { - try registerEventCallback("EventListener", myObj: EventCallback(completion)) - } catch { - log(string: ">>> Event listener failed: \(error.localizedDescription)", type: .error) - } - } - - func listenRound(id: Int, _ completion: @escaping (Result<Bool, Error>) -> Void) { - let callback = RoundCallback { completion(.success($0)) } - - do { - try wait(forRoundCompletion: id, rec: callback, timeoutMS: 15000) - } catch { - completion(.failure(error)) - } - } - - func listenDelivery(of report: Data, _ completion: @escaping (DeliveryResult) -> Void) throws { - let callback = DeliveryCallback { completion($0) } - - var roundIds = [Int]() - - var unmarshalError: NSError? - - if let unmarshaled = BindingsUnmarshalSendReport(report, &unmarshalError), - let roundList = unmarshaled.getRoundList() { - let length = roundList.len() - for index in 0..<length { - var integer: Int = 0 - do { - try roundList.get(index, ret0_: &integer) - roundIds.append(integer) - } catch { - log(string: ">>> Error inspecting round list:\n\(error.localizedDescription)", type: .error) - } - } - } - - try! wait(forMessageDelivery: report, mdc: callback, timeoutMS: 30000) - } - - func listenGroupRequests( - _ groupRequests: @escaping (Group, [Data], String?) -> Void, - groupMessages: @escaping (Message) -> Void - ) throws -> GroupManagerInterface? { - var error: NSError? - - let requestCallback = GroupRequestCallback { - guard let id = $0.getID(), - let name = $0.getName(), - let serialize = $0.serialize(), - let memberList = $0.getMembership() else { return } - - var members = [Data]() - - var welcomeMessage: String? - - if let welcomeData = $0.getInitMessage() { - welcomeMessage = String(data: welcomeData, encoding: .utf8) - } - - for index in 0..<memberList.len() { - guard let member = try? memberList.get(index), - let memberId = member.getID() else { continue } - members.append(memberId) - } - - groupRequests(.init( - id: id, - name: String(data: name, encoding: .utf8)!, - leaderId: members.first!, - createdAt: Date(), - authStatus: .pending, - serialized: serialize - ), members, welcomeMessage) - } - - let messageCallback = GroupMessageCallback { groupMessages(Message(with: $0)) } - let groupManager = BindingsNewGroupManager(self, requestCallback, messageCallback, &error) - - guard let error = error else { return groupManager } - fatalError(error.localizedDescription) - } -} diff --git a/Sources/Integration/Logging.swift b/Sources/Integration/Logging.swift deleted file mode 100644 index c37e60b3..00000000 --- a/Sources/Integration/Logging.swift +++ /dev/null @@ -1,85 +0,0 @@ -import Bindings -import XXLogger -import CrashReporting -import DependencyInjection -import Foundation -import os - -let oslogger = Logger(subsystem: "logs_xxmessenger", category: "Logging.swift") - -final class BindingsError: NSObject, BindingsClientErrorProtocol { - func report(_ source: String?, message: String?, trace: String?) { - var content = "" - - content += String(describing: source) + "\n" - content += String(describing: message) + "\n" - content += String(describing: trace) - - log(string: content, type: .error) - } -} - -extension Error { - func friendly() -> NSError { - log(string: ">>> Switching to friendly error from: \(localizedDescription)", type: .error) - - let error = BindingsErrorStringToUserFriendlyMessage(localizedDescription) - if error.hasPrefix("UR") { - let crashReporter = try! DependencyInjection.Container.shared.resolve() as CrashReporter - crashReporter.sendError(self as NSError) - return NSError.create("Unexpected error. Please try again") - } else { - return NSError.create(error) - } - } -} - -enum LogType { - case info - case error - case crumbs - case bindings - - var icon: String { - switch self { - case .error: - return "🟥" - case .crumbs: - return "ðŸž" - case .bindings: - return "âš™ï¸" - case .info: - return "✅" - } - } -} - -func log( - string: String? = nil, - type: LogType, - function: String = #function, - file: String = #file, - line: Int = #line -) { - var trimmedFile = "" - if let index = file.lastIndex(of: "/") { - let afterEqualsTo = String(file.suffix(from: index).dropFirst()) - trimmedFile = afterEqualsTo - } - - let content = "\(type.icon) \(function) @\(trimmedFile):\(line) \(string ?? "")" - let logger = try! DependencyInjection.Container.shared.resolve() as XXLogger - - switch type { - case .info: - logger.info(content) - oslogger.info("\(content)") - case .error: - logger.error(content) - oslogger.error("\(content)") - case .crumbs: - logger.debug(content) - case .bindings: - logger.warning(content) - } -} diff --git a/Sources/Integration/Mocks/GroupManagerMock.swift b/Sources/Integration/Mocks/GroupManagerMock.swift deleted file mode 100644 index 137cfca6..00000000 --- a/Sources/Integration/Mocks/GroupManagerMock.swift +++ /dev/null @@ -1,21 +0,0 @@ -import Models -import XXModels -import Foundation - -final class GroupManagerMock: GroupManagerInterface { - func join(_: Data) throws {} - - func leave(_: Data) throws {} - - func send(_: Data, to: Data) -> Result<(Int64, Data?, String), Error> { - .success((1, nil, "https://www.google.com.br")) - } - - func create( - me: Data, - name: String, - welcome: String?, - with: [Data], - _: @escaping (Result<Group, Error>) -> Void - ) {} -} diff --git a/Sources/Integration/Mocks/TransferManagerMock.swift b/Sources/Integration/Mocks/TransferManagerMock.swift deleted file mode 100644 index 9b87da5d..00000000 --- a/Sources/Integration/Mocks/TransferManagerMock.swift +++ /dev/null @@ -1,33 +0,0 @@ -import Foundation - -final class TransferManagerMock: TransferManagerInterface { - func endTransferUpload( - with TID: Data - ) throws {} - - func listenDownloadFromTransfer( - with: Data, - _: @escaping (Bool, Int, Int, Error?) -> Void - ) throws { - fatalError() - } - - func listenUploadFromTransfer( - with: Data, - _: @escaping (Bool, Int, Int, Int, Error?) -> Void - ) throws {} - - func downloadFileFromTransfer( - with: Data - ) throws -> Data { - fatalError() - } - - func uploadFile( - url: URL, - to: Data, - _: @escaping (Bool, Int, Int, Int, Error?) -> Void - ) throws -> Data { - Data() - } -} diff --git a/Sources/Integration/Mocks/UserDiscoveryMock.swift b/Sources/Integration/Mocks/UserDiscoveryMock.swift deleted file mode 100644 index bb1a8f24..00000000 --- a/Sources/Integration/Mocks/UserDiscoveryMock.swift +++ /dev/null @@ -1,45 +0,0 @@ -import Models -import XXModels -import Foundation - -final class UserDiscoveryMock: UserDiscoveryInterface { - - func remove(_ fact: String) throws {} - - func deleteMyself(_ username: String) throws {} - - func confirm(code: String, id: String) throws {} - - func lookup(idList: [Data], _: @escaping (Result<[Contact], Error>) -> Void) {} - - func retrieve(from: Data, fact: FactType) throws -> String? { fact.description } - - func search(fact: String, _ completion: @escaping (Result<Contact, Error>) -> Void) throws { - completion(.success(.georgeDiscovered)) - } - - func register(_: FactType, value: String, _ completion: @escaping (Result<String?, Error>) -> Void) { - completion(.success("#CONFIRMATION_CODE_FOR \(value)")) - } - - func lookup( - forUserId: Data, - _ completion: @escaping (Result<Contact, Error>) -> Void - ) { - DispatchQueue.global().asyncAfter(deadline: .now() + 1) { - completion(.success( - .init( - id: "mock_username".data(using: .utf8)!, - marshaled: "mock_username".data(using: .utf8)!, - username: "mock_username", - email: nil, - phone: nil, - nickname: "mock_nickname", - photo: nil, - authStatus: .stranger, - isRecent: false, - createdAt: Date() - ))) - } - } -} diff --git a/Sources/Integration/Resources/cert_mainnet.txt b/Sources/Integration/Resources/cert_mainnet.txt deleted file mode 100644 index 40045d63..00000000 --- a/Sources/Integration/Resources/cert_mainnet.txt +++ /dev/null @@ -1 +0,0 @@ -[PLACE THE CERTIFICATE CONTENT HERE] diff --git a/Sources/Integration/Session/Session+Chat.swift b/Sources/Integration/Session/Session+Chat.swift deleted file mode 100644 index b8d6e02d..00000000 --- a/Sources/Integration/Session/Session+Chat.swift +++ /dev/null @@ -1,270 +0,0 @@ -import UIKit -import Models -import Shared -import XXModels -import Foundation - -extension Session { - public func send(imageData: Data, to contact: Contact, completion: @escaping (Result<Void, Error>) -> Void) { - client.bindings.compress(image: imageData) { [weak self] result in - guard let self = self else { - completion(.success(())) - return - } - - switch result { - case .success(let compressedImage): - do { - let url = try FileManager.store( - data: compressedImage, - name: "image_\(Date.asTimestamp)", - type: "jpeg" - ) - - self.sendFile(url: url, to: contact) - completion(.success(())) - } catch { - completion(.failure(error)) - } - - case .failure(let error): - completion(.failure(error)) - log(string: "Error when compressing image: \(error.localizedDescription)", type: .error) - } - } - } - - public func sendFile(url: URL, to contact: Contact) { - guard let manager = client.transferManager else { fatalError("A transfer manager was not created") } - - DispatchQueue.global().async { [weak self] in - guard let self = self else { return } - - var tid: Data? - - do { - tid = try manager.uploadFile(url: url, to: contact.id) { completed, send, arrived, total, error in - guard let tid = tid else { return } - - if completed { - self.endTransferWith(tid: tid) - } else { - if error != nil { - self.failTransferWith(tid: tid) - } else { - self.progressTransferWith(tid: tid, arrived: Float(arrived), total: Float(total)) - } - } - } - - guard let tid = tid else { return } - - let content = url.pathExtension == "m4a" ? "a voice message" : "an image" - - let transfer = FileTransfer( - id: tid, - contactId: contact.id, - name: url.deletingPathExtension().lastPathComponent, - type: url.pathExtension, - data: try? Data(contentsOf: url), - progress: 0.0, - isIncoming: false, - createdAt: Date() - ) - - _ = try? self.dbManager.saveFileTransfer(transfer) - - let message = Message( - networkId: nil, - senderId: self.client.bindings.myId, - recipientId: contact.id, - groupId: nil, - date: Date(), - status: .sending, - isUnread: false, - text: "You sent \(content)", - replyMessageId: nil, - roundURL: nil, - fileTransferId: tid - ) - - _ = try? self.dbManager.saveMessage(message) - } catch { - print(error.localizedDescription) - } - } - } - - public func send(_ payload: Payload, toContact contact: Contact) { - var message = Message( - networkId: nil, - senderId: client.bindings.myId, - recipientId: contact.id, - groupId: nil, - date: Date(), - status: .sending, - isUnread: false, - text: payload.text, - replyMessageId: payload.reply?.messageId, - roundURL: nil, - fileTransferId: nil - ) - - do { - message = try dbManager.saveMessage(message) - send(message: message) - } catch { - log(string: error.localizedDescription, type: .error) - } - } - - public func retryMessage(_ id: Int64) { - if var message = try? dbManager.fetchMessages(.init(id: [id])).first { - message.status = .sending - message.date = Date() - - if let message = try? dbManager.saveMessage(message) { - if let _ = message.recipientId { - send(message: message) - } else { - send(groupMessage: message) - } - } - } - } - - func send(message: Message) { - var message = message - - var reply: Reply? - if let replyId = message.replyMessageId, - let replyMessage = try? dbManager.fetchMessages(Message.Query(networkId: replyId)).first { - reply = Reply(messageId: replyId, senderId: replyMessage.senderId) - } - - let payloadData = Payload(text: message.text, reply: reply).asData() - - DispatchQueue.global().async { [weak self] in - guard let self = self else { return } - switch self.client.bindings.send(payloadData, to: message.recipientId!) { - case .success(let report): - message.roundURL = report.roundURL - - self.client.bindings.listen(report: report.marshalled) { result in - switch result { - case .success(let status): - switch status { - case .failed: - message.status = .sendingFailed - case .sent: - message.status = .sent - case .timedout: - message.status = .sendingTimedOut - } - case .failure: - message.status = .sendingFailed - } - - message.networkId = report.uniqueId - message.date = Date.fromTimestamp(Int(report.timestamp)) - DispatchQueue.main.async { - do { - _ = try self.dbManager.saveMessage(message) - } catch { - log(string: error.localizedDescription, type: .error) - } - } - } - case .failure(let error): - message.status = .sendingFailed - log(string: error.localizedDescription, type: .error) - } - - DispatchQueue.main.async { - do { - _ = try self.dbManager.saveMessage(message) - } catch { - log(string: error.localizedDescription, type: .error) - } - } - } - } - - private func endTransferWith(tid: Data) { - guard let manager = client.transferManager else { - fatalError("A transfer manager was not created") - } - - try? manager.endTransferUpload(with: tid) - - if var message = try? dbManager.fetchMessages(.init(fileTransferId: tid)).first { - message.status = .sent - _ = try? dbManager.saveMessage(message) - } - - if var transfer = try? dbManager.fetchFileTransfers(.init(id: [tid])).first { - transfer.progress = 1.0 - _ = try? dbManager.saveFileTransfer(transfer) - } - } - - private func failTransferWith(tid: Data) { - if var message = try? dbManager.fetchMessages(.init(fileTransferId: tid)).first { - message.status = .sendingFailed - _ = try? dbManager.saveMessage(message) - } - } - - private func progressTransferWith(tid: Data, arrived: Float, total: Float) { - if var transfer = try? dbManager.fetchFileTransfers(.init(id: [tid])).first { - transfer.progress = arrived/total - _ = try? dbManager.saveFileTransfer(transfer) - } - } - - func handle(incomingTransfer transfer: FileTransfer) { - guard let manager = client.transferManager else { - fatalError("A transfer manager was not created") - } - - let content = transfer.type == "m4a" ? "a voice message" : "an image" - - var message = try! dbManager.saveMessage( - Message( - networkId: nil, - senderId: transfer.contactId, - recipientId: myId, - groupId: nil, - date: transfer.createdAt, - status: .receiving, - isUnread: true, - text: "Sent you \(content)", - replyMessageId: nil, - roundURL: nil, - fileTransferId: transfer.id - ) - ) - - try! manager.listenDownloadFromTransfer(with: transfer.id) { completed, arrived, total, error in - if let error = error { - print(error.localizedDescription) - return - } - - if completed { - if let data = try? manager.downloadFileFromTransfer(with: transfer.id), - let _ = try? FileManager.store(data: data, name: transfer.name, type: transfer.type) { - var transfer = transfer - transfer.data = data - transfer.progress = 1.0 - message.status = .received - - _ = try? self.dbManager.saveFileTransfer(transfer) - _ = try? self.dbManager.saveMessage(message) - } - } else { - self.progressTransferWith(tid: transfer.id, arrived: Float(arrived), total: Float(total)) - } - } - } -} diff --git a/Sources/Integration/Session/Session+Network.swift b/Sources/Integration/Session/Session+Network.swift deleted file mode 100644 index 070ea2cd..00000000 --- a/Sources/Integration/Session/Session+Network.swift +++ /dev/null @@ -1,15 +0,0 @@ -import Foundation - -extension Session { - public func start() { - DispatchQueue.global().async { [weak client] in - client?.bindings.startNetwork() - } - } - - public func stop() { - DispatchQueue.global().async { [weak client] in - client?.bindings.stopNetwork() - } - } -} diff --git a/Sources/Integration/Session/Session+Notifications.swift b/Sources/Integration/Session/Session+Notifications.swift deleted file mode 100644 index b4e98e96..00000000 --- a/Sources/Integration/Session/Session+Notifications.swift +++ /dev/null @@ -1,11 +0,0 @@ -import Foundation - -extension Session { - public func registerNotifications(_ token: Data) throws { - try client.bindings.registerNotifications(token) - } - - public func unregisterNotifications() throws { - try client.bindings.unregisterNotifications() - } -} diff --git a/Sources/Integration/Session/Session+UD.swift b/Sources/Integration/Session/Session+UD.swift deleted file mode 100644 index 27add4b1..00000000 --- a/Sources/Integration/Session/Session+UD.swift +++ /dev/null @@ -1,118 +0,0 @@ -import Retry -import Models -import Combine -import XXModels -import Foundation - -extension Session { - public func waitForNodes(timeout: Int) -> AnyPublisher<Void, Error> { - Deferred { - Future { promise in - retry(max: timeout, retryStrategy: .delay(seconds: 1)) { [weak self] in - guard let self = self else { return } - try self.client.bindings.nodeRegistrationStatus() - promise(.success(())) - }.finalCatch { - promise(.failure($0)) - } - } - }.eraseToAnyPublisher() - } - - public func search(fact: String) -> AnyPublisher<Contact, Error> { - Deferred { - Future { promise in - guard let ud = self.client.userDiscovery else { - let error = NSError(domain: "", code: 0) - promise(.failure(error)) - return - } - - do { - try self.client.bindings.nodeRegistrationStatus() - try ud.search(fact: fact) { - switch $0 { - case .success(let contact): - promise(.success(contact)) - case .failure(let error): - promise(.failure(error)) - } - } - } catch { - promise(.failure(error)) - } - } - }.eraseToAnyPublisher() - } - - public func search(fact: String, _ completion: @escaping (Result<Contact, Error>) -> Void) throws { - guard let ud = client.userDiscovery else { return } - try client.bindings.nodeRegistrationStatus() - try ud.search(fact: fact, completion) - } - - public func extract(fact: FactType, from marshalled: Data) throws -> String? { - guard let ud = client.userDiscovery else { return nil } - return try ud.retrieve(from: marshalled, fact: fact) - } - - public func unregister(fact: FactType) throws { - guard let ud = client.userDiscovery else { return } - - switch fact { - case .phone: - try ud.remove("P" + phone!) - isSharingPhone = false - phone = nil - case .email: - try ud.remove("E" + email!) - isSharingEmail = false - email = nil - default: - break - } - } - - public func register(_ fact: FactType, value: String, _ completion: @escaping (Result<String?, Error>) -> Void) { - guard let ud = client.userDiscovery else { return } - - switch fact { - case .username: - ud.register(.username, value: value) { [weak self] in - guard let self = self else { return } - - switch $0 { - case .success(_): - self.username = value - - if var me = try? self.myContact() { - me.username = value - _ = try? self.dbManager.saveContact(me) - } - - completion(.success(nil)) - case .failure(let error): - completion(.failure(error)) - } - } - default: - ud.register(fact, value: value, completion) - } - } - - public func confirm(code: String, confirmation: AttributeConfirmation) throws { - guard let ud = client.userDiscovery else { return } - - try ud.confirm(code: code, id: confirmation.confirmationId!) - - if confirmation.isEmail { - email = confirmation.content - } else { - phone = confirmation.content - } - - if let _ = client.backupManager { - updateFactsOnBackup() - } - } -} diff --git a/Sources/Integration/Session/SessionType.swift b/Sources/Integration/Session/SessionType.swift deleted file mode 100644 index effd6c96..00000000 --- a/Sources/Integration/Session/SessionType.swift +++ /dev/null @@ -1,73 +0,0 @@ -import Models -import Combine -import XXModels -import Foundation - -public protocol SessionType { - var myId: Data { get } - var myQR: Data { get } - var version: String { get } - var hasRunningTasks: Bool { get } - var isOnline: AnyPublisher<Bool, Never> { get } - - var dbManager: Database { get } - - func deleteMyself() throws - func getId(from: Data) -> Data? - - func sendFile(url: URL, to: Contact) - func send(imageData: Data, to: Contact, completion: @escaping (Result<Void, Error>) -> Void) - - func verify(contact: Contact) - - func setDummyTraffic(status: Bool) - - // UserDiscovery - - func unregister(fact: FactType) throws - func extract(fact: FactType, from: Data) throws -> String? - func confirm(code: String, confirmation: AttributeConfirmation) throws - func search(fact: String, _: @escaping (Result<Contact, Error>) -> Void) throws - func register(_: FactType, value: String, _: @escaping (Result<String?, Error>) -> Void) - - // Notifications - - func unregisterNotifications() throws - func registerNotifications(_ token: Data) throws - - // Network - - func start() - func stop() - - // Messages - - func retryMessage(_: Int64) - func send(_: Payload, toContact: Contact) - - // Contacts - - func add(_: Contact) throws - func confirm(_: Contact) throws - func deleteContact(_: Contact) throws - - func retryRequest(_: Contact) throws - func scanStrangers(_: @escaping () -> Void) - - // Groups - - func join(group: Group) throws - func send(_: Payload, toGroup: Group) - func leave(group: Group) throws - - func createGroup( - name: String, - welcome: String?, - members: [Contact], - _ completion: @escaping (Result<GroupInfo, Error>) -> Void - ) - - func search(fact: String) -> AnyPublisher<Contact, Error> - - func waitForNodes(timeout: Int) -> AnyPublisher<Void, Error> -} diff --git a/Sources/Integration/XXNetwork.swift b/Sources/Integration/XXNetwork.swift deleted file mode 100644 index 1273a26a..00000000 --- a/Sources/Integration/XXNetwork.swift +++ /dev/null @@ -1,173 +0,0 @@ -import Shared -import XXLogger -import Keychain -import Foundation -import DependencyInjection - -public enum NetworkEnvironment { - case mainnet -} - -public protocol XXNetworking { - var hasClient: Bool { get } - - func writeLogs() - func purgeFiles() - func updateErrors() - func newClient(ndf: String) throws -> Client - - func updateNDF( - _: @escaping (Result<String, Error>) -> Void - ) - - func loadClient( - with: Data, - fromBackup: Bool, - email: String?, - phone: String? - ) throws -> Client - - func newClientFromBackup( - passphrase: String, - data: Data, - ndf: String - ) throws -> (Client, Data?) -} - -public struct XXNetwork<B: BindingsInterface> { - @Dependency private var logger: XXLogger - @Dependency private var keychain: KeychainHandling - - public init() {} -} - -extension XXNetwork: XXNetworking { - public var hasClient: Bool { - guard let files = FileManager.xxContents else { return false } - return files.count > 0 - } - - public func writeLogs() { - B.listenLogs() - } - - public func updateErrors() { - B.updateErrors() - } - - public func updateNDF(_ completion: @escaping (Result<String, Error>) -> Void) { - B.updateNDF(for: .mainnet) { - switch $0 { - case .success(let data): - guard let ndfData = data, let ndf = String(data: ndfData, encoding: .utf8) else { - completion(.failure(NSError.create("NDF is empty (?)"))) - return - } - - completion(.success(ndf)) - case .failure(let error): - completion(.failure(error)) - } - } - } - - public func purgeFiles() { - FileManager.xxCleanup() - } - - public func newClientFromBackup( - passphrase: String, - data: Data, - ndf: String - ) throws -> (Client, Data?) { - var error: NSError? - - let password = B.secret(32)! - try keychain.store(password: password) - - let backupData = B.fromBackup( - ndf, - FileManager.xxPath, - password, - "\(passphrase)".data(using: .utf8), - data, - &error - ) - - if let error = error { throw error } - - var email: String? - var phone: String? - - let report = try! JSONDecoder().decode(BackupReport.self, from: backupData!) - - if !report.parameters.isEmpty { - let params = try! JSONDecoder().decode(BackupParameters.self, from: Data(report.parameters.utf8)) - phone = params.phone - email = params.email - } - - let client = try loadClient(with: password, fromBackup: true, email: email, phone: phone) - return (client, backupData) - } - - public func newClient(ndf: String) throws -> Client { - var password: Data! - - if hasClient == false { - var error: NSError? - - password = B.secret(32) - try keychain.store(password: password) - - _ = B.new(ndf, FileManager.xxPath, password, nil, &error) - if let error = error { throw error } - } else { - guard let secret = try keychain.getPassword() else { - throw NSError.create("Empty stored secret") - } - - password = secret - } - - return try loadClient(with: password, fromBackup: false, email: nil, phone: nil) - } - - public func loadClient( - with secret: Data, - fromBackup: Bool, - email: String?, - phone: String? - ) throws -> Client { - var error: NSError? - let bindings = B.login(FileManager.xxPath, secret, "", &error) - if let error = error { throw error } - - if let defaults = UserDefaults(suiteName: "group.elixxir.messenger") { - defaults.set(bindings!.receptionId.base64EncodedString(), forKey: "receptionId") - } - - return Client(bindings!, fromBackup: fromBackup, email: email, phone: phone) - } -} - -extension NetworkEnvironment { - var url: String { - switch self { - case .mainnet: - return "https://elixxir-bins.s3.us-west-1.amazonaws.com/ndf/mainnet.json" - } - } - - var cert: String { - switch self { - case .mainnet: - guard let filepath = Bundle.module.path(forResource: "cert_mainnet", ofType: "txt"), - let certString = try? String(contentsOfFile: filepath) else { - fatalError("Couldn't retrieve network cert file.") - } - - return certString - } - } -} diff --git a/Sources/LaunchFeature/LaunchController.swift b/Sources/LaunchFeature/LaunchController.swift index cf8b093b..a17c9fc3 100644 --- a/Sources/LaunchFeature/LaunchController.swift +++ b/Sources/LaunchFeature/LaunchController.swift @@ -86,8 +86,8 @@ public final class LaunchController: UIViewController { coordinator.toChats(from: self) - case .onboarding(let ndf): - coordinator.toOnboarding(with: ndf, from: self) + case .onboarding: + coordinator.toOnboarding(from: self) case .update(let model): offerUpdate(model: model) diff --git a/Sources/LaunchFeature/LaunchCoordinator.swift b/Sources/LaunchFeature/LaunchCoordinator.swift index 37035773..acdc52aa 100644 --- a/Sources/LaunchFeature/LaunchCoordinator.swift +++ b/Sources/LaunchFeature/LaunchCoordinator.swift @@ -8,7 +8,7 @@ public protocol LaunchCoordinating { func toTerms(from: UIViewController) func toRequests(from: UIViewController) func toSearch(searching: String, from: UIViewController) - func toOnboarding(with: String, from: UIViewController) + func toOnboarding(from: UIViewController) func toSingleChat(with: Contact, from: UIViewController) func toGroupChat(with: GroupInfo, from: UIViewController) } @@ -20,7 +20,7 @@ public struct LaunchCoordinator: LaunchCoordinating { var searchFactory: (String) -> UIViewController var requestsFactory: () -> UIViewController var chatListFactory: () -> UIViewController - var onboardingFactory: (String) -> UIViewController + var onboardingFactory: () -> UIViewController var singleChatFactory: (Contact) -> UIViewController var groupChatFactory: (GroupInfo) -> UIViewController @@ -29,7 +29,7 @@ public struct LaunchCoordinator: LaunchCoordinating { searchFactory: @escaping (String) -> UIViewController, requestsFactory: @escaping () -> UIViewController, chatListFactory: @escaping () -> UIViewController, - onboardingFactory: @escaping (String) -> UIViewController, + onboardingFactory: @escaping () -> UIViewController, singleChatFactory: @escaping (Contact) -> UIViewController, groupChatFactory: @escaping (GroupInfo) -> UIViewController ) { @@ -65,8 +65,8 @@ public extension LaunchCoordinator { replacePresenter.present(screen, from: parent) } - func toOnboarding(with ndf: String, from parent: UIViewController) { - let screen = onboardingFactory(ndf) + func toOnboarding(from parent: UIViewController) { + let screen = onboardingFactory() replacePresenter.present(screen, from: parent) } diff --git a/Sources/LaunchFeature/LaunchViewModel.swift b/Sources/LaunchFeature/LaunchViewModel.swift index 73fa6722..ecfcbfb5 100644 --- a/Sources/LaunchFeature/LaunchViewModel.swift +++ b/Sources/LaunchFeature/LaunchViewModel.swift @@ -6,7 +6,6 @@ import Defaults import XXModels import Keychain import Foundation -import Integration import Permissions import ToastFeature import DropboxFeature @@ -15,6 +14,12 @@ import ReportingFeature import CombineSchedulers import DependencyInjection +import XXClient +import struct XXClient.FileTransfer + +import XXDatabase +import XXLegacyDatabaseMigrator + struct Update { let content: String let urlString: String @@ -26,20 +31,22 @@ struct Update { enum LaunchRoute { case chats case update(Update) - case onboarding(String) + case onboarding } final class LaunchViewModel { - @Dependency private var network: XXNetworking - @Dependency private var versionChecker: VersionChecker - @Dependency private var dropboxService: DropboxInterface - @Dependency private var keychainHandler: KeychainHandling - @Dependency private var permissionHandler: PermissionHandling - @Dependency private var fetchBannedList: FetchBannedList - @Dependency private var reportingStatus: ReportingStatus - @Dependency private var processBannedList: ProcessBannedList - @Dependency private var toastController: ToastController - @Dependency private var session: SessionType + @Dependency var database: Database + @Dependency var cMixManager: CMixManager + @Dependency var versionChecker: VersionChecker + @Dependency var dropboxService: DropboxInterface + @Dependency var fetchBannedList: FetchBannedList + @Dependency var reportingStatus: ReportingStatus + @Dependency var toastController: ToastController + @Dependency var keychainHandler: KeychainHandling + @Dependency var getIdFromContact: GetIdFromContact + @Dependency var processBannedList: ProcessBannedList + @Dependency var permissionHandler: PermissionHandling + @Dependency var getFactsFromContact: GetFactsFromContact @KeyObject(.username, defaultValue: nil) var username: String? @KeyObject(.biometrics, defaultValue: false) var isBiometricsOn: Bool @@ -86,56 +93,101 @@ final class LaunchViewModel { } func versionApproved() { - network.writeLogs() - - network.updateNDF { [weak self] in - guard let self = self else { return } + //self.updateBannedList { - switch $0 { - case .success(let ndf): - self.network.updateErrors() + _ = try? SetLogLevel.live(.trace) - guard self.network.hasClient else { - self.hudSubject.send(.none) - self.routeSubject.send(.onboarding(ndf)) - self.dropboxService.unlink() - try? self.keychainHandler.clear() - return - } - - guard self.username != nil else { - self.network.purgeFiles() - self.hudSubject.send(.none) - self.routeSubject.send(.onboarding(ndf)) - self.dropboxService.unlink() - try? self.keychainHandler.clear() - return - } + try! setupDatabase() - self.backgroundScheduler.schedule { [weak self] in - guard let self = self else { return } + if cMixManager.hasStorage(), username != nil { + checkBiometrics { [weak self] in + guard let self = self else { return } + switch $0 { + case .success(false): + break + case .success(true): do { - let session = try Session(ndf: ndf) - DependencyInjection.Container.shared.register(session as SessionType) - - self.updateBannedList { - DispatchQueue.main.async { - self.hudSubject.send(.none) - self.checkBiometrics() - } + //UpdateCommonErrors.live(jsonFile: ) DOWNLOAD THE JSON FROM THE REPO + + let cMix = try self.initCMix() + try cMix.startNetworkFollower(timeoutMS: 10_000) + guard cMix.waitForNetwork(timeoutMS: 10_000) else { + fatalError("^^^ cMix.waitForNetwork returned FALSE") } + + let e2e = try self.initE2E(cMix) + _ = try self.initUD(alternative: true, e2e: e2e, cMix: cMix) + _ = try self.initGroupManager(e2e) + _ = try self.initTransferManager(e2e) + _ = try self.initDummyTrafficManager(e2e) + + + self.hudSubject.send(.none) + self.routeSubject.send(.chats) } catch { - DispatchQueue.main.async { - self.hudSubject.send(.error(HUDError(with: error))) - } + self.hudSubject.send(.error(.init(with: error))) } + case .failure(let error): + self.hudSubject.send(.error(.init(with: error))) } - case .failure(let error): self.hudSubject.send(.error(HUDError(with: error))) } + } else { + cleanUp() + presentOnboardingFlow() + } + } + + private func cleanUp() { + try? cMixManager.remove() + try? keychainHandler.clear() + + dropboxService.unlink() + } + + private func presentOnboardingFlow() { + hudSubject.send(.none) + routeSubject.send(.onboarding) + } + + private func setupDatabase() throws { + let legacyOldPath = NSSearchPathForDirectoriesInDomains( + .documentDirectory, .userDomainMask, true + )[0].appending("/xxmessenger.sqlite") + + let legacyPath = FileManager.default + .containerURL(forSecurityApplicationGroupIdentifier: "group.elixxir.messenger")! + .appendingPathComponent("database") + .appendingPathExtension("sqlite").path + + let dbExistsInLegacyOldPath = FileManager.default.fileExists(atPath: legacyOldPath) + let dbExistsInLegacyPath = FileManager.default.fileExists(atPath: legacyPath) + + if dbExistsInLegacyOldPath && !dbExistsInLegacyPath { + try? FileManager.default.moveItem(atPath: legacyOldPath, toPath: legacyPath) + } + + let dbPath = FileManager.default + .containerURL(forSecurityApplicationGroupIdentifier: "group.elixxir.messenger")! + .appendingPathComponent("xxm_database") + .appendingPathExtension("sqlite").path + + let database = try Database.onDisk(path: dbPath) + + if dbExistsInLegacyPath { + try Migrator.live()( + try .init(path: legacyPath), + to: database, + myContactId: Data(), //client.bindings.myId, + meMarshaled: Data() //client.bindings.meMarshalled + ) + + try FileManager.default.moveItem(atPath: legacyPath, toPath: legacyPath.appending("-backup")) } + + DependencyInjection.Container.shared.register(database) } func getContactWith(userId: Data) -> Contact? { @@ -145,12 +197,23 @@ final class LaunchViewModel { isBanned: reportingStatus.isEnabled() ? false : nil ) - return try! session.dbManager.fetchContacts(query).first + guard let database: Database = try? DependencyInjection.Container.shared.resolve(), + let contact = try? database.fetchContacts(query).first else { + return nil + } + + return contact } func getGroupInfoWith(groupId: Data) -> GroupInfo? { let query = GroupInfo.Query(groupId: groupId) - return try! session.dbManager.fetchGroupInfos(query).first + + guard let database: Database = try? DependencyInjection.Container.shared.resolve(), + let info = try? database.fetchGroupInfos(query).first else { + return nil + } + + return info } private func versionFailed(error: Error) { @@ -189,25 +252,253 @@ final class LaunchViewModel { routeSubject.send(.update(model)) } - private func checkBiometrics() { + private func checkBiometrics(completion: @escaping (Result<Bool, Error>) -> Void) { if permissionHandler.isBiometricsAvailable && isBiometricsOn { - permissionHandler.requestBiometrics { [weak self] in - guard let self = self else { return } - + permissionHandler.requestBiometrics { switch $0 { case .success(let granted): - guard granted else { return } - self.routeSubject.send(.chats) + completion(.success(granted)) case .failure(let error): - self.hudSubject.send(.error(HUDError(with: error))) + completion(.failure(error)) } } } else { - self.routeSubject.send(.chats) + completion(.success(true)) + } + } + + private func initCMix() throws -> CMix { + if let cMix = try? DependencyInjection.Container.shared.resolve() as CMix { + return cMix + } + + let cMix = try cMixManager.load() + DependencyInjection.Container.shared.register(cMix) + return cMix + } + + private func initE2E(_ cMix: CMix) throws -> E2E { + if let e2e = try? DependencyInjection.Container.shared.resolve() as E2E { + return e2e + } + + let e2e = try Login.live( + cMixId: cMix.getId(), + authCallbacks: .init( + handle: { + switch $0 { + case .reset(contact: let contact, receptionId: _, ephemeralId: _, roundId: _): + self.handleReset(from: contact) + case .confirm(contact: let contact, receptionId: _, ephemeralId: _, roundId: _): + self.handleConfirm(from: contact) + case .request(contact: let contact, receptionId: _, ephemeralId: _, roundId: _): + self.handleRequest(from: contact) + } + } + ), + identity: cMix.makeLegacyReceptionIdentity() + ) + + try e2e.registerListener( + senderId: nil, + messageType: 2, + callback: .init(handle: { message in + print(message.timestamp) + }) + ) + + DependencyInjection.Container.shared.register(e2e) + return e2e + } + + private func initUD(alternative: Bool, e2e: E2E, cMix: CMix) throws -> UserDiscovery { + if let userDiscovery = try? DependencyInjection.Container.shared.resolve() as UserDiscovery { + return userDiscovery + } + + guard let certPath = Bundle.module.path(forResource: "cmix.rip", ofType: "crt"), + let contactFilePath = Bundle.module.path(forResource: "udContact", ofType: "bin") else { + fatalError("Couldn't retrieve alternative UD credentials") + } + + let address = alternative ? "46.101.98.49:18001" : e2e.getUdAddressFromNdf() + let cert = alternative ? try Data(contentsOf: URL(fileURLWithPath: certPath)) : e2e.getUdCertFromNdf() + let contactFile = alternative ? try Data(contentsOf: URL(fileURLWithPath: contactFilePath)) : try e2e.getUdContactFromNdf() + + let userDiscovery = try NewOrLoadUd.live(.init( + e2eId: e2e.getId(), + follower: .init(handle: { cMix.networkFollowerStatus().rawValue }), + username: username!, + registrationValidationSignature: cMix.getReceptionRegistrationValidationSignature(), + cert: cert, + contactFile: contactFile, + address: address + )) + + DependencyInjection.Container.shared.register(userDiscovery) + return userDiscovery + } + + private func initGroupManager(_ e2e: E2E) throws -> GroupChat { + if let groupManager = try? DependencyInjection.Container.shared.resolve() as GroupChat { + return groupManager + } + + let groupManager = try NewGroupChat.live( + e2eId: e2e.getId(), + groupRequest: .init(handle: { print($0) }), + groupChatProcessor: .init(handle: { print($0) }) + ) + + DependencyInjection.Container.shared.register(groupManager) + return groupManager + } + + private func initTransferManager(_ e2e: E2E) throws -> XXClient.FileTransfer { + if let transferManager = try? DependencyInjection.Container.shared.resolve() as FileTransfer { + return transferManager + } + + let transferManager = try InitFileTransfer.live( + e2eId: e2e.getId(), + callback: .init(handle: { + switch $0 { + case .success(let receivedFile): + print(receivedFile.name) + case .failure(let error): + print(error.localizedDescription) + } + }) + ) + + DependencyInjection.Container.shared.register(transferManager) + return transferManager + } + + private func initDummyTrafficManager(_ e2e: E2E) throws -> DummyTraffic { + if let dummyTrafficManager = try? DependencyInjection.Container.shared.resolve() as DummyTraffic { + return dummyTrafficManager + } + + let dummyTrafficManager = try NewDummyTrafficManager.live( + cMixId: e2e.getId(), + maxNumMessages: 1, + avgSendDeltaMS: 1, + randomRangeMS: 1 + ) + + DependencyInjection.Container.shared.register(dummyTrafficManager) + return dummyTrafficManager + } + + private func handleRequest(from contact: Data) { + guard isRepeatedRequest(from: contact) == false else { return } + + do { + let facts = try? getFactsFromContact(contact: contact) + + let model = try self.database.saveContact(.init( + id: try getIdFromContact(contact), + marshaled: contact, + username: facts?.first(where: { $0.type == FactType.username.rawValue })?.fact, + email: facts?.first(where: { $0.type == FactType.email.rawValue })?.fact, + phone: facts?.first(where: { $0.type == FactType.phone.rawValue })?.fact, + nickname: nil, + photo: nil, + authStatus: .verificationInProgress, + isRecent: true, + createdAt: Date() + )) + + if model.email == nil, model.phone == nil { + performLookup(on: model) + } else { + //performSearch() + } + } catch { + print("^^^ Request processing failed: \(error.localizedDescription)") } } + private func isRepeatedRequest(from contact: Data) -> Bool { + if let id = try? getIdFromContact(contact), + let _ = try? self.database.fetchContacts(Contact.Query(id: [id])).first { + return true + } + + return false + } + + private func performLookup(on contact: Contact) { + guard let e2e = try? DependencyInjection.Container.shared.resolve() as E2E, + let userDiscovery = try? DependencyInjection.Container.shared.resolve() as UserDiscovery else { + print("^^^ couldn't resolve UD/E2E to process lookup") + return + } + + do { + let _ = try LookupUD.live( + e2eId: e2e.getId(), + udContact: try userDiscovery.getContact(), + lookupId: contact.id, + callback: .init(handle: { [weak self] in + guard let self = self else { return } + + switch $0 { + case .success(let id): + self.performOwnershipVerification(contact: contact, idLookedUp: id) + case .failure(let error): + print("^^^ Lookup failed: \(error.localizedDescription)") + } + }) + ) + } catch { + print("^^^ Error when trying to run lookup: \(error.localizedDescription)") + } + } + + private func performOwnershipVerification(contact: Contact, idLookedUp: Data) { + guard let e2e = try? DependencyInjection.Container.shared.resolve() as E2E else { + print("^^^ couldn't resolve E2E to process verification") + return + } + + do { + let result = try e2e.verifyOwnership( + receivedContact: contact.marshaled!, + verifiedContact: idLookedUp, + e2eId: e2e.getId() + ) + + if result == true { + var contact = contact + contact.authStatus = .verified + try database.saveContact(contact) + } else { + try database.deleteContact(contact) + } + } catch { + print("^^^ Exception thrown at verify ownership") + } + } + + private func handleConfirm(from contact: Data) { + guard let id = try? getIdFromContact(contact) else { + print("^^^ Couldn't get id from contact. Confirmation failed") + return + } + + if var model = try? database.fetchContacts(.init(id: [id])).first { + model.authStatus = .friend + _ = try? database.saveContact(model) + } + } + + private func handleReset(from contact: Data) { + // TODO + } + private func updateBannedList(completion: @escaping () -> Void) { fetchBannedList { result in switch result { diff --git a/Sources/LaunchFeature/Resources/cmix.rip.crt b/Sources/LaunchFeature/Resources/cmix.rip.crt new file mode 100644 index 00000000..baa29e6a --- /dev/null +++ b/Sources/LaunchFeature/Resources/cmix.rip.crt @@ -0,0 +1,21 @@ +-----BEGIN CERTIFICATE----- +MIIDbDCCAlSgAwIBAgIJAOUNtZneIYECMA0GCSqGSIb3DQEBBQUAMGgxCzAJBgNV +BAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRIwEAYDVQQHDAlDbGFyZW1vbnQx +GzAZBgNVBAoMElByaXZhdGVncml0eSBDb3JwLjETMBEGA1UEAwwKKi5jbWl4LnJp +cDAeFw0xOTAzMDUxODM1NDNaFw0yOTAzMDIxODM1NDNaMGgxCzAJBgNVBAYTAlVT +MRMwEQYDVQQIDApDYWxpZm9ybmlhMRIwEAYDVQQHDAlDbGFyZW1vbnQxGzAZBgNV +BAoMElByaXZhdGVncml0eSBDb3JwLjETMBEGA1UEAwwKKi5jbWl4LnJpcDCCASIw +DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAPP0WyVkfZA/CEd2DgKpcudn0oDh +Dwsjmx8LBDWsUgQzyLrFiVigfUmUefknUH3dTJjmiJtGqLsayCnWdqWLHPJYvFfs +WYW0IGF93UG/4N5UAWO4okC3CYgKSi4ekpfw2zgZq0gmbzTnXcHF9gfmQ7jJUKSE +tJPSNzXq+PZeJTC9zJAb4Lj8QzH18rDM8DaL2y1ns0Y2Hu0edBFn/OqavBJKb/uA +m3AEjqeOhC7EQUjVamWlTBPt40+B/6aFJX5BYm2JFkRsGBIyBVL46MvC02MgzTT9 +bJIJfwqmBaTruwemNgzGu7Jk03hqqS1TUEvSI6/x8bVoba3orcKkf9HsDjECAwEA +AaMZMBcwFQYDVR0RBA4wDIIKKi5jbWl4LnJpcDANBgkqhkiG9w0BAQUFAAOCAQEA +neUocN4AbcQAC1+b3To8u5UGdaGxhcGyZBlAoenRVdjXK3lTjsMdMWb4QctgNfIf +U/zuUn2mxTmF/ekP0gCCgtleZr9+DYKU5hlXk8K10uKxGD6EvoiXZzlfeUuotgp2 +qvI3ysOm/hvCfyEkqhfHtbxjV7j7v7eQFPbvNaXbLa0yr4C4vMK/Z09Ui9JrZ/Z4 +cyIkxfC6/rOqAirSdIp09EGiw7GM8guHyggE4IiZrDslT8V3xIl985cbCxSxeW1R +tgH4rdEXuVe9+31oJhmXOE9ux2jCop9tEJMgWg7HStrJ5plPbb+HmjoX3nBO04E5 +6m52PyzMNV+2N21IPppKwA== +-----END CERTIFICATE----- diff --git a/Sources/LaunchFeature/Resources/udContact.bin b/Sources/LaunchFeature/Resources/udContact.bin new file mode 100644 index 00000000..b2611d41 --- /dev/null +++ b/Sources/LaunchFeature/Resources/udContact.bin @@ -0,0 +1 @@ +<xxc(2)7mbKFLE201WzH4SGxAOpHjjehwztIV+KGifi5L/PYPcDkAZiB9kZo+Dl3Vc7dD2SdZCFMOJVgwqGzfYRDkjc8RGEllBqNxq2sRRX09iQVef0kJQUgJCHNCOcvm6Ki0JJwvjLceyFh36iwK8oLbhLgqEZY86UScdACTyBCzBIab3ob5mBthYc3mheV88yq5PGF2DQ+dEvueUm+QhOSfwzppAJA/rpW9Wq9xzYcQzaqc3ztAGYfm2BBAHS7HVmkCbvZ/K07Xrl4EBPGHJYq12tWAN/C3mcbbBYUOQXyEzbSl/mO7sL3ORr0B4FMuqCi8EdlD6RO52pVhY+Cg6roRH1t5Ng1JxPt8Mv1yyjbifPhZ5fLKwxBz8UiFORfk0/jnhwgm25LRHqtNRRUlYXLvhv0HhqyYTUt17WNtCLATSVbqLrFGdy2EGadn8mP+kQNHp93f27d/uHgBNNe7LpuYCJMdWpoG6bOqmHEftxt0/MIQA8fTtTm3jJzv+7/QjZJDvQIv0SNdp8HFogpuwde+GuS4BcY7v5xz+ArGWcRR63ct2z83MqQEn9ODr1/gAAAgA7szRpDDQIdFUQo9mkWg8xBA==xxc> \ No newline at end of file diff --git a/Sources/MenuFeature/ViewModels/MenuViewModel.swift b/Sources/MenuFeature/ViewModels/MenuViewModel.swift index 72fbe071..ce8161fe 100644 --- a/Sources/MenuFeature/ViewModels/MenuViewModel.swift +++ b/Sources/MenuFeature/ViewModels/MenuViewModel.swift @@ -1,14 +1,14 @@ import Combine import XXModels +import XXClient import Defaults import Foundation -import Integration import ReportingFeature import DependencyInjection final class MenuViewModel { - @Dependency private var session: SessionType - @Dependency private var reportingStatus: ReportingStatus + @Dependency var database: Database + @Dependency var reportingStatus: ReportingStatus @KeyObject(.avatar, defaultValue: nil) var avatar: Data? @KeyObject(.username, defaultValue: "") var username: String @@ -33,15 +33,15 @@ final class MenuViewModel { ) return Publishers.CombineLatest( - session.dbManager.fetchContactsPublisher(contactsQuery).assertNoFailure(), - session.dbManager.fetchGroupsPublisher(groupQuery).assertNoFailure() + database.fetchContactsPublisher(contactsQuery).assertNoFailure(), + database.fetchGroupsPublisher(groupQuery).assertNoFailure() ) .map { $0.0.count + $0.1.count } .eraseToAnyPublisher() } var xxdk: String { - session.version + GetVersion.live() } var build: String { diff --git a/Sources/OnboardingFeature/Controllers/OnboardingStartController.swift b/Sources/OnboardingFeature/Controllers/OnboardingStartController.swift index d9b97032..3fce498d 100644 --- a/Sources/OnboardingFeature/Controllers/OnboardingStartController.swift +++ b/Sources/OnboardingFeature/Controllers/OnboardingStartController.swift @@ -11,20 +11,12 @@ public final class OnboardingStartController: UIViewController { lazy private var screenView = OnboardingStartView() - private let ndf: String private var cancellables = Set<AnyCancellable>() public override func loadView() { view = screenView } - public init(_ ndf: String) { - self.ndf = ndf - super.init(nibName: nil, bundle: nil) - } - - required init?(coder: NSCoder) { nil } - public override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) navigationItem.backButtonTitle = "" @@ -53,7 +45,7 @@ public final class OnboardingStartController: UIViewController { super.viewDidLoad() screenView.startButton.publisher(for: .touchUpInside) - .sink { [unowned self] in coordinator.toTerms(ndf: ndf, from: self) } + .sink { [unowned self] in coordinator.toTerms(from: self) } .store(in: &cancellables) } } diff --git a/Sources/OnboardingFeature/Controllers/OnboardingUsernameController.swift b/Sources/OnboardingFeature/Controllers/OnboardingUsernameController.swift index d42e1f81..fd349efb 100644 --- a/Sources/OnboardingFeature/Controllers/OnboardingUsernameController.swift +++ b/Sources/OnboardingFeature/Controllers/OnboardingUsernameController.swift @@ -15,9 +15,8 @@ public final class OnboardingUsernameController: UIViewController { lazy private var screenView = OnboardingUsernameView() lazy private var scrollViewController = ScrollViewController() - private let ndf: String private var cancellables = Set<AnyCancellable>() - private let viewModel: OnboardingUsernameViewModel! + private let viewModel = OnboardingUsernameViewModel() private var drawerCancellables = Set<AnyCancellable>() public override func viewWillAppear(_ animated: Bool) { @@ -27,14 +26,6 @@ public final class OnboardingUsernameController: UIViewController { navigationController?.navigationBar.customize(translucent: true) } - public init(_ ndf: String) { - self.ndf = ndf - self.viewModel = OnboardingUsernameViewModel(ndf: ndf) - super.init(nibName: nil, bundle: nil) - } - - required init?(coder: NSCoder) { nil } - public override func viewDidLoad() { super.viewDidLoad() setupScrollView() @@ -74,7 +65,7 @@ public final class OnboardingUsernameController: UIViewController { screenView.restoreView.restoreButton .publisher(for: .touchUpInside) .receive(on: DispatchQueue.main) - .sink { [unowned self] in coordinator.toRestoreList(with: ndf, from: self) } + .sink { [unowned self] in coordinator.toRestoreList(from: self) } .store(in: &cancellables) screenView.inputField.returnPublisher diff --git a/Sources/OnboardingFeature/Coordinator/OnboardingCoordinator.swift b/Sources/OnboardingFeature/Coordinator/OnboardingCoordinator.swift index 9ea57da2..dd26a3f6 100644 --- a/Sources/OnboardingFeature/Coordinator/OnboardingCoordinator.swift +++ b/Sources/OnboardingFeature/Coordinator/OnboardingCoordinator.swift @@ -10,10 +10,10 @@ public protocol OnboardingCoordinating { func toChats(from: UIViewController) func toEmail(from: UIViewController) func toPhone(from: UIViewController) + func toTerms(from: UIViewController) func toWelcome(from: UIViewController) - func toTerms(ndf: String, from: UIViewController) - func toUsername(with: String, from: UIViewController) - func toRestoreList(with: String, from: UIViewController) + func toUsername(from: UIViewController) + func toRestoreList(from: UIViewController) func toDrawer(_: UIViewController, from: UIViewController) func toSuccess(with: OnboardingSuccessModel, from: UIViewController) @@ -45,9 +45,9 @@ public struct OnboardingCoordinator: OnboardingCoordinating { var searchFactory: (String?) -> UIViewController var welcomeFactory: () -> UIViewController var chatListFactory: () -> UIViewController - var usernameFactory: (String) -> UIViewController - var restoreListFactory: (String) -> UIViewController - var termsFactory: (String?) -> UIViewController + var termsFactory: () -> UIViewController + var usernameFactory: () -> UIViewController + var restoreListFactory: () -> UIViewController var successFactory: (OnboardingSuccessModel) -> UIViewController var countriesFactory: (@escaping (Country) -> Void) -> UIViewController var phoneConfirmationFactory: (AttributeConfirmation, @escaping AttributeControllerClosure) -> UIViewController @@ -59,9 +59,9 @@ public struct OnboardingCoordinator: OnboardingCoordinating { searchFactory: @escaping (String?) -> UIViewController, welcomeFactory: @escaping () -> UIViewController, chatListFactory: @escaping () -> UIViewController, - termsFactory: @escaping (String?) -> UIViewController, - usernameFactory: @escaping (String) -> UIViewController, - restoreListFactory: @escaping (String) -> UIViewController, + termsFactory: @escaping () -> UIViewController, + usernameFactory: @escaping () -> UIViewController, + restoreListFactory: @escaping () -> UIViewController, successFactory: @escaping (OnboardingSuccessModel) -> UIViewController, countriesFactory: @escaping (@escaping (Country) -> Void) -> UIViewController, phoneConfirmationFactory: @escaping (AttributeConfirmation, @escaping AttributeControllerClosure) -> UIViewController, @@ -83,11 +83,8 @@ public struct OnboardingCoordinator: OnboardingCoordinating { } public extension OnboardingCoordinator { - func toTerms( - ndf: String, - from parent: UIViewController - ) { - let screen = termsFactory(ndf) + func toTerms(from parent: UIViewController) { + let screen = termsFactory() pushPresenter.present(screen, from: parent) } @@ -106,8 +103,8 @@ public extension OnboardingCoordinator { replacePresenter.present(screen, from: parent) } - func toRestoreList(with ndf: String, from parent: UIViewController) { - let screen = restoreListFactory(ndf) + func toRestoreList(from parent: UIViewController) { + let screen = restoreListFactory() pushPresenter.present(screen, from: parent) } @@ -116,8 +113,8 @@ public extension OnboardingCoordinator { replacePresenter.present(screen, from: parent) } - func toUsername(with ndf: String, from parent: UIViewController) { - let screen = usernameFactory(ndf) + func toUsername(from parent: UIViewController) { + let screen = usernameFactory() replacePresenter.present(screen, from: parent) } diff --git a/Sources/OnboardingFeature/Resources/cmix.rip.crt b/Sources/OnboardingFeature/Resources/cmix.rip.crt new file mode 100644 index 00000000..baa29e6a --- /dev/null +++ b/Sources/OnboardingFeature/Resources/cmix.rip.crt @@ -0,0 +1,21 @@ +-----BEGIN CERTIFICATE----- +MIIDbDCCAlSgAwIBAgIJAOUNtZneIYECMA0GCSqGSIb3DQEBBQUAMGgxCzAJBgNV +BAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRIwEAYDVQQHDAlDbGFyZW1vbnQx +GzAZBgNVBAoMElByaXZhdGVncml0eSBDb3JwLjETMBEGA1UEAwwKKi5jbWl4LnJp +cDAeFw0xOTAzMDUxODM1NDNaFw0yOTAzMDIxODM1NDNaMGgxCzAJBgNVBAYTAlVT +MRMwEQYDVQQIDApDYWxpZm9ybmlhMRIwEAYDVQQHDAlDbGFyZW1vbnQxGzAZBgNV +BAoMElByaXZhdGVncml0eSBDb3JwLjETMBEGA1UEAwwKKi5jbWl4LnJpcDCCASIw +DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAPP0WyVkfZA/CEd2DgKpcudn0oDh +Dwsjmx8LBDWsUgQzyLrFiVigfUmUefknUH3dTJjmiJtGqLsayCnWdqWLHPJYvFfs +WYW0IGF93UG/4N5UAWO4okC3CYgKSi4ekpfw2zgZq0gmbzTnXcHF9gfmQ7jJUKSE +tJPSNzXq+PZeJTC9zJAb4Lj8QzH18rDM8DaL2y1ns0Y2Hu0edBFn/OqavBJKb/uA +m3AEjqeOhC7EQUjVamWlTBPt40+B/6aFJX5BYm2JFkRsGBIyBVL46MvC02MgzTT9 +bJIJfwqmBaTruwemNgzGu7Jk03hqqS1TUEvSI6/x8bVoba3orcKkf9HsDjECAwEA +AaMZMBcwFQYDVR0RBA4wDIIKKi5jbWl4LnJpcDANBgkqhkiG9w0BAQUFAAOCAQEA +neUocN4AbcQAC1+b3To8u5UGdaGxhcGyZBlAoenRVdjXK3lTjsMdMWb4QctgNfIf +U/zuUn2mxTmF/ekP0gCCgtleZr9+DYKU5hlXk8K10uKxGD6EvoiXZzlfeUuotgp2 +qvI3ysOm/hvCfyEkqhfHtbxjV7j7v7eQFPbvNaXbLa0yr4C4vMK/Z09Ui9JrZ/Z4 +cyIkxfC6/rOqAirSdIp09EGiw7GM8guHyggE4IiZrDslT8V3xIl985cbCxSxeW1R +tgH4rdEXuVe9+31oJhmXOE9ux2jCop9tEJMgWg7HStrJ5plPbb+HmjoX3nBO04E5 +6m52PyzMNV+2N21IPppKwA== +-----END CERTIFICATE----- diff --git a/Sources/OnboardingFeature/Resources/udContact.bin b/Sources/OnboardingFeature/Resources/udContact.bin new file mode 100644 index 00000000..b2611d41 --- /dev/null +++ b/Sources/OnboardingFeature/Resources/udContact.bin @@ -0,0 +1 @@ +<xxc(2)7mbKFLE201WzH4SGxAOpHjjehwztIV+KGifi5L/PYPcDkAZiB9kZo+Dl3Vc7dD2SdZCFMOJVgwqGzfYRDkjc8RGEllBqNxq2sRRX09iQVef0kJQUgJCHNCOcvm6Ki0JJwvjLceyFh36iwK8oLbhLgqEZY86UScdACTyBCzBIab3ob5mBthYc3mheV88yq5PGF2DQ+dEvueUm+QhOSfwzppAJA/rpW9Wq9xzYcQzaqc3ztAGYfm2BBAHS7HVmkCbvZ/K07Xrl4EBPGHJYq12tWAN/C3mcbbBYUOQXyEzbSl/mO7sL3ORr0B4FMuqCi8EdlD6RO52pVhY+Cg6roRH1t5Ng1JxPt8Mv1yyjbifPhZ5fLKwxBz8UiFORfk0/jnhwgm25LRHqtNRRUlYXLvhv0HhqyYTUt17WNtCLATSVbqLrFGdy2EGadn8mP+kQNHp93f27d/uHgBNNe7LpuYCJMdWpoG6bOqmHEftxt0/MIQA8fTtTm3jJzv+7/QjZJDvQIv0SNdp8HFogpuwde+GuS4BcY7v5xz+ArGWcRR63ct2z83MqQEn9ODr1/gAAAgA7szRpDDQIdFUQo9mkWg8xBA==xxc> \ No newline at end of file diff --git a/Sources/OnboardingFeature/ViewModels/OnboardingEmailConfirmationViewModel.swift b/Sources/OnboardingFeature/ViewModels/OnboardingEmailConfirmationViewModel.swift index b3871d62..b22252cb 100644 --- a/Sources/OnboardingFeature/ViewModels/OnboardingEmailConfirmationViewModel.swift +++ b/Sources/OnboardingFeature/ViewModels/OnboardingEmailConfirmationViewModel.swift @@ -5,7 +5,7 @@ import Shared import Combine import Defaults import InputField -import Integration +import XXClient import CombineSchedulers import DependencyInjection @@ -16,7 +16,9 @@ struct OnboardingEmailConfirmationViewState: Equatable { } final class OnboardingEmailConfirmationViewModel { - @Dependency private var session: SessionType + @Dependency var userDiscovery: UserDiscovery + + @KeyObject(.email, defaultValue: nil) var email: String? var hud: AnyPublisher<HUDStatus, Never> { hudRelay.eraseToAnyPublisher() } private let hudRelay = CurrentValueSubject<HUDStatus, Never>(.none) @@ -64,11 +66,13 @@ final class OnboardingEmailConfirmationViewModel { guard let self = self else { return } do { - try self.session.confirm( - code: self.stateRelay.value.input, - confirmation: self.confirmation + try self.userDiscovery.confirmFact( + confirmationId: self.confirmation.confirmationId!, + code: self.stateRelay.value.input ) + self.email = self.confirmation.content + self.timer?.invalidate() self.hudRelay.send(.none) self.completionRelay.send(self.confirmation) diff --git a/Sources/OnboardingFeature/ViewModels/OnboardingEmailViewModel.swift b/Sources/OnboardingFeature/ViewModels/OnboardingEmailViewModel.swift index c3cbbb89..85776bd9 100644 --- a/Sources/OnboardingFeature/ViewModels/OnboardingEmailViewModel.swift +++ b/Sources/OnboardingFeature/ViewModels/OnboardingEmailViewModel.swift @@ -5,7 +5,7 @@ import Shared import Combine import Defaults import InputField -import Integration +import XXClient import CombineSchedulers import DependencyInjection @@ -16,9 +16,9 @@ struct OnboardingEmailViewState: Equatable { } final class OnboardingEmailViewModel { - @KeyObject(.pushNotifications, defaultValue: false) private var pushNotifications + @Dependency var userDiscovery: UserDiscovery - @Dependency private var session: SessionType + @KeyObject(.pushNotifications, defaultValue: false) private var pushNotifications var hud: AnyPublisher<HUDStatus, Never> { hudRelay.eraseToAnyPublisher() } private let hudRelay = CurrentValueSubject<HUDStatus, Never>(.none) @@ -43,17 +43,19 @@ final class OnboardingEmailViewModel { backgroundScheduler.schedule { [weak self] in guard let self = self else { return } - self.session.register(.email, value: self.stateRelay.value.input) { [weak self] in - guard let self = self else { return } + do { + let confirmationId = try self.userDiscovery.sendRegisterFact( + .init(fact: self.stateRelay.value.input, type: FactType.email.rawValue) + ) - switch $0 { - case .success(let confirmationId): - self.hudRelay.send(.none) - self.stateRelay.value.confirmation = - .init(content: self.stateRelay.value.input, isEmail: true, confirmationId: confirmationId) - case .failure(let error): - self.hudRelay.send(.error(.init(with: error))) - } + self.hudRelay.send(.none) + self.stateRelay.value.confirmation = .init( + content: self.stateRelay.value.input, + isEmail: true, + confirmationId: confirmationId + ) + } catch { + self.hudRelay.send(.error(.init(with: error))) } } } diff --git a/Sources/OnboardingFeature/ViewModels/OnboardingPhoneConfirmationViewModel.swift b/Sources/OnboardingFeature/ViewModels/OnboardingPhoneConfirmationViewModel.swift index 2bd5a7ae..b05a6b21 100644 --- a/Sources/OnboardingFeature/ViewModels/OnboardingPhoneConfirmationViewModel.swift +++ b/Sources/OnboardingFeature/ViewModels/OnboardingPhoneConfirmationViewModel.swift @@ -5,7 +5,7 @@ import Shared import Combine import Defaults import InputField -import Integration +import XXClient import CombineSchedulers import DependencyInjection @@ -16,7 +16,9 @@ struct OnboardingPhoneConfirmationViewState: Equatable { } final class OnboardingPhoneConfirmationViewModel { - @Dependency private var session: SessionType + @Dependency var userDiscovery: UserDiscovery + + @KeyObject(.phone, defaultValue: nil) var phone: String? var hud: AnyPublisher<HUDStatus, Never> { hudRelay.eraseToAnyPublisher() } private let hudRelay = CurrentValueSubject<HUDStatus, Never>(.none) @@ -64,11 +66,13 @@ final class OnboardingPhoneConfirmationViewModel { guard let self = self else { return } do { - try self.session.confirm( - code: self.stateRelay.value.input, - confirmation: self.confirmation + try self.userDiscovery.confirmFact( + confirmationId: self.confirmation.confirmationId!, + code: self.stateRelay.value.input ) + self.phone = self.confirmation.content + self.timer?.invalidate() self.hudRelay.send(.none) self.completionRelay.send(self.confirmation) diff --git a/Sources/OnboardingFeature/ViewModels/OnboardingPhoneViewModel.swift b/Sources/OnboardingFeature/ViewModels/OnboardingPhoneViewModel.swift index 0aff02f4..3469c45c 100644 --- a/Sources/OnboardingFeature/ViewModels/OnboardingPhoneViewModel.swift +++ b/Sources/OnboardingFeature/ViewModels/OnboardingPhoneViewModel.swift @@ -4,7 +4,7 @@ import Models import Combine import Countries import InputField -import Integration +import XXClient import CombineSchedulers import DependencyInjection @@ -16,7 +16,7 @@ struct OnboardingPhoneViewState: Equatable { } final class OnboardingPhoneViewModel { - @Dependency private var session: SessionType + @Dependency var userDiscovery: UserDiscovery var hud: AnyPublisher<HUDStatus, Never> { hudRelay.eraseToAnyPublisher() } private let hudRelay = CurrentValueSubject<HUDStatus, Never>(.none) @@ -53,19 +53,19 @@ final class OnboardingPhoneViewModel { guard let self = self else { return } let content = "\(self.stateRelay.value.input)\(self.stateRelay.value.country.code)" - self.session.register(.phone, value: content) { [weak self] in - guard let self = self else { return } - - switch $0 { - case .success(let confirmationId): - self.hudRelay.send(.none) - self.stateRelay.value.confirmation = .init( - content: content, - confirmationId: confirmationId - ) - case .failure(let error): - self.hudRelay.send(.error(.init(with: error))) - } + + do { + let confirmationId = try self.userDiscovery.sendRegisterFact( + .init(fact: content, type: FactType.phone.rawValue) + ) + + self.hudRelay.send(.none) + self.stateRelay.value.confirmation = .init( + content: content, + confirmationId: confirmationId + ) + } catch { + self.hudRelay.send(.error(.init(with: error))) } } } diff --git a/Sources/OnboardingFeature/ViewModels/OnboardingUsernameViewModel.swift b/Sources/OnboardingFeature/ViewModels/OnboardingUsernameViewModel.swift index ecb64035..8204662c 100644 --- a/Sources/OnboardingFeature/ViewModels/OnboardingUsernameViewModel.swift +++ b/Sources/OnboardingFeature/ViewModels/OnboardingUsernameViewModel.swift @@ -1,21 +1,31 @@ import HUD import Shared +import Models import Combine +import Defaults +import XXModels import InputField -import Integration +import XXClient import CombineSchedulers import DependencyInjection +import struct XXClient.FileTransfer + struct OnboardingUsernameViewState: Equatable { var input: String = "" var status: InputField.ValidationStatus = .unknown(nil) } final class OnboardingUsernameViewModel { + @Dependency var database: Database + @Dependency var cMixManager: CMixManager + @Dependency var getIdFromContact: GetIdFromContact + @Dependency var getFactsFromContact: GetFactsFromContact - let ndf: String + @KeyObject(.username, defaultValue: "") var username: String - var backgroundScheduler: AnySchedulerOf<DispatchQueue> = DispatchQueue.global().eraseToAnyScheduler() + var backgroundScheduler: AnySchedulerOf<DispatchQueue> + = DispatchQueue.global().eraseToAnyScheduler() var greenPublisher: AnyPublisher<Void, Never> { greenRelay.eraseToAnyPublisher() } private let greenRelay = PassthroughSubject<Void, Never>() @@ -26,10 +36,6 @@ final class OnboardingUsernameViewModel { var state: AnyPublisher<OnboardingUsernameViewState, Never> { stateRelay.eraseToAnyPublisher() } private let stateRelay = CurrentValueSubject<OnboardingUsernameViewState, Never>(.init()) - init(ndf: String) { - self.ndf = ndf - } - func didInput(_ string: String) { stateRelay.value.input = string.trimmingCharacters(in: .whitespacesAndNewlines) @@ -48,31 +54,255 @@ final class OnboardingUsernameViewModel { guard let self = self else { return } do { - var session: SessionType! + let cMix = try self.initCMix() + try cMix.startNetworkFollower(timeoutMS: 10_000) + guard cMix.waitForNetwork(timeoutMS: 10_000) else { + fatalError("^^^ cMix.waitForNetwork returned FALSE") + } + let e2e = try self.initE2E(cMix) + let ud = try self.initUD(alternative: true, e2e: e2e, cMix: cMix) + _ = try self.initGroupManager(e2e) + _ = try self.initTransferManager(e2e) + _ = try self.initDummyTrafficManager(e2e) + + self.hudRelay.send(.none) + self.greenRelay.send() + } catch { + self.hudRelay.send(.none) + self.stateRelay.value.status = .invalid(error.localizedDescription) + } + } + } + + private func initCMix() throws -> CMix { + if let cMix = try? DependencyInjection.Container.shared.resolve() as CMix { + return cMix + } + + let cMix = try cMixManager.create() + DependencyInjection.Container.shared.register(cMix) + return cMix + } + + private func initE2E(_ cMix: CMix) throws -> E2E { + if let e2e = try? DependencyInjection.Container.shared.resolve() as E2E { + return e2e + } - if let injectedSession = try? DependencyInjection.Container.shared.resolve() as SessionType { - session = injectedSession - } else { - session = try Session(ndf: self.ndf) - DependencyInjection.Container.shared.register(session as SessionType) + let e2e = try Login.live( + cMixId: cMix.getId(), + authCallbacks: .init( + handle: { + switch $0 { + case .reset(contact: let contact, receptionId: _, ephemeralId: _, roundId: _): + self.handleReset(from: contact) + case .confirm(contact: let contact, receptionId: _, ephemeralId: _, roundId: _): + self.handleConfirm(from: contact) + case .request(contact: let contact, receptionId: _, ephemeralId: _, roundId: _): + self.handleRequest(from: contact) + } } + ), + identity: cMix.makeLegacyReceptionIdentity() + ) - session.register(.username, value: self.stateRelay.value.input) { [weak self] in + try e2e.registerListener( + senderId: nil, + messageType: 2, + callback: .init(handle: { message in + print(message.timestamp) + }) + ) + + DependencyInjection.Container.shared.register(e2e) + return e2e + } + + private func initUD(alternative: Bool, e2e: E2E, cMix: CMix) throws -> UserDiscovery { + if let userDiscovery = try? DependencyInjection.Container.shared.resolve() as UserDiscovery { + return userDiscovery + } + + guard let certPath = Bundle.module.path(forResource: "cmix.rip", ofType: "crt"), + let contactFilePath = Bundle.module.path(forResource: "udContact", ofType: "bin") else { + fatalError("Couldn't retrieve alternative UD credentials") + } + + let address = alternative ? "46.101.98.49:18001" : e2e.getUdAddressFromNdf() + let cert = alternative ? try Data(contentsOf: URL(fileURLWithPath: certPath)) : e2e.getUdCertFromNdf() + let contactFile = alternative ? try Data(contentsOf: URL(fileURLWithPath: contactFilePath)) : try e2e.getUdContactFromNdf() + + let userDiscovery = try NewOrLoadUd.live(.init( + e2eId: e2e.getId(), + follower: .init(handle: { cMix.networkFollowerStatus().rawValue }), + username: self.stateRelay.value.input, + registrationValidationSignature: cMix.getReceptionRegistrationValidationSignature(), + cert: cert, + contactFile: contactFile, + address: address + )) + + username = self.stateRelay.value.input + DependencyInjection.Container.shared.register(userDiscovery) + return userDiscovery + } + + private func initGroupManager(_ e2e: E2E) throws -> GroupChat { + if let groupManager = try? DependencyInjection.Container.shared.resolve() as GroupChat { + return groupManager + } + + let groupManager = try NewGroupChat.live( + e2eId: e2e.getId(), + groupRequest: .init(handle: { print($0) }), + groupChatProcessor: .init(handle: { print($0) }) + ) + + DependencyInjection.Container.shared.register(groupManager) + return groupManager + } + + private func initTransferManager(_ e2e: E2E) throws -> XXClient.FileTransfer { + if let transferManager = try? DependencyInjection.Container.shared.resolve() as FileTransfer { + return transferManager + } + + let transferManager = try InitFileTransfer.live( + e2eId: e2e.getId(), + callback: .init(handle: { + switch $0 { + case .success(let receivedFile): + print(receivedFile.name) + case .failure(let error): + print(error.localizedDescription) + } + }) + ) + + DependencyInjection.Container.shared.register(transferManager) + return transferManager + } + + private func initDummyTrafficManager(_ e2e: E2E) throws -> DummyTraffic { + if let dummyTrafficManager = try? DependencyInjection.Container.shared.resolve() as DummyTraffic { + return dummyTrafficManager + } + + let dummyTrafficManager = try NewDummyTrafficManager.live( + cMixId: e2e.getId(), + maxNumMessages: 1, + avgSendDeltaMS: 1, + randomRangeMS: 1 + ) + + DependencyInjection.Container.shared.register(dummyTrafficManager) + return dummyTrafficManager + } + + private func handleRequest(from contact: Data) { + guard isRepeatedRequest(from: contact) == false else { return } + + do { + let facts = try? getFactsFromContact(contact: contact) + + let model = try self.database.saveContact(.init( + id: try getIdFromContact(contact), + marshaled: contact, + username: facts?.first(where: { $0.type == FactType.username.rawValue })?.fact, + email: facts?.first(where: { $0.type == FactType.email.rawValue })?.fact, + phone: facts?.first(where: { $0.type == FactType.phone.rawValue })?.fact, + nickname: nil, + photo: nil, + authStatus: .verificationInProgress, + isRecent: true, + createdAt: Date() + )) + + if model.email == nil, model.phone == nil { + performLookup(on: model) + } else { + //performSearch() + } + } catch { + print("^^^ Request processing failed: \(error.localizedDescription)") + } + } + + private func isRepeatedRequest(from contact: Data) -> Bool { + if let id = try? getIdFromContact(contact), + let _ = try? self.database.fetchContacts(Contact.Query(id: [id])).first { + return true + } + + return false + } + + private func performLookup(on contact: Contact) { + guard let e2e = try? DependencyInjection.Container.shared.resolve() as E2E, + let userDiscovery = try? DependencyInjection.Container.shared.resolve() as UserDiscovery else { + print("^^^ couldn't resolve UD/E2E to process lookup") + return + } + + do { + let _ = try LookupUD.live( + e2eId: e2e.getId(), + udContact: try userDiscovery.getContact(), + lookupId: contact.id, + callback: .init(handle: { [weak self] in guard let self = self else { return } switch $0 { - case .success(_): - self.hudRelay.send(.none) - self.greenRelay.send() + case .success(let id): + self.performOwnershipVerification(contact: contact, idLookedUp: id) case .failure(let error): - self.hudRelay.send(.none) - self.stateRelay.value.status = .invalid(error.localizedDescription) + print("^^^ Lookup failed: \(error.localizedDescription)") } - } - } catch { - self.hudRelay.send(.none) - self.stateRelay.value.status = .invalid(error.localizedDescription) + }) + ) + } catch { + print("^^^ Error when trying to run lookup: \(error.localizedDescription)") + } + } + + private func performOwnershipVerification(contact: Contact, idLookedUp: Data) { + guard let e2e = try? DependencyInjection.Container.shared.resolve() as E2E else { + print("^^^ couldn't resolve E2E to process verification") + return + } + + do { + let result = try e2e.verifyOwnership( + receivedContact: contact.marshaled!, + verifiedContact: idLookedUp, + e2eId: e2e.getId() + ) + + if result == true { + var contact = contact + contact.authStatus = .verified + try database.saveContact(contact) + } else { + try database.deleteContact(contact) } + } catch { + print("^^^ Exception thrown at verify ownership") + } + } + + private func handleConfirm(from contact: Data) { + guard let id = try? getIdFromContact(contact) else { + print("^^^ Couldn't get id from contact. Confirmation failed") + return } + + if var model = try? database.fetchContacts(.init(id: [id])).first { + model.authStatus = .friend + _ = try? database.saveContact(model) + } + } + + private func handleReset(from contact: Data) { + // TODO } } diff --git a/Sources/ProfileFeature/ViewModels/ProfileCodeViewModel.swift b/Sources/ProfileFeature/ViewModels/ProfileCodeViewModel.swift index d9763e98..49be0077 100644 --- a/Sources/ProfileFeature/ViewModels/ProfileCodeViewModel.swift +++ b/Sources/ProfileFeature/ViewModels/ProfileCodeViewModel.swift @@ -2,8 +2,9 @@ import HUD import Shared import Models import Combine +import Defaults import InputField -import Integration +import XXClient import CombineSchedulers import DependencyInjection @@ -14,7 +15,10 @@ struct ProfileCodeViewState: Equatable { } final class ProfileCodeViewModel { - @Dependency private var session: SessionType + @Dependency var userDiscovery: UserDiscovery + + @KeyObject(.email, defaultValue: nil) var email: String? + @KeyObject(.phone, defaultValue: nil) var phone: String? let confirmation: AttributeConfirmation @@ -63,11 +67,17 @@ final class ProfileCodeViewModel { guard let self = self else { return } do { - try self.session.confirm( - code: self.stateRelay.value.input, - confirmation: self.confirmation + try self.userDiscovery.confirmFact( + confirmationId: self.confirmation.confirmationId!, + code: self.stateRelay.value.input ) + if self.confirmation.isEmail { + self.email = self.confirmation.content + } else { + self.phone = self.confirmation.content + } + self.timer?.invalidate() self.hudRelay.send(.none) self.completionRelay.send(self.confirmation) diff --git a/Sources/ProfileFeature/ViewModels/ProfileEmailViewModel.swift b/Sources/ProfileFeature/ViewModels/ProfileEmailViewModel.swift index 6b57bc81..21ec9fa8 100644 --- a/Sources/ProfileFeature/ViewModels/ProfileEmailViewModel.swift +++ b/Sources/ProfileFeature/ViewModels/ProfileEmailViewModel.swift @@ -3,7 +3,7 @@ import Models import Shared import Combine import InputField -import Integration +import XXClient import CombineSchedulers import DependencyInjection @@ -16,7 +16,7 @@ struct ProfileEmailViewState: Equatable { final class ProfileEmailViewModel { // MARK: Injected - @Dependency private var session: SessionType + @Dependency var userDiscovery: UserDiscovery // MARK: Properties @@ -45,20 +45,19 @@ final class ProfileEmailViewModel { backgroundScheduler.schedule { [weak self] in guard let self = self else { return } - self.session.register(.email, value: self.stateRelay.value.input) { [weak self] in - guard let self = self else { return } - - switch $0 { - case .success(let confirmationId): - self.hudRelay.send(.none) - self.stateRelay.value.confirmation = .init( - content: self.stateRelay.value.input, - isEmail: true, - confirmationId: confirmationId - ) - case .failure(let error): - self.hudRelay.send(.error(.init(with: error))) - } + do { + let confirmationId = try self.userDiscovery.sendRegisterFact( + .init(fact: self.stateRelay.value.input, type: FactType.email.rawValue) + ) + + self.hudRelay.send(.none) + self.stateRelay.value.confirmation = .init( + content: self.stateRelay.value.input, + isEmail: true, + confirmationId: confirmationId + ) + } catch { + self.hudRelay.send(.error(.init(with: error))) } } } diff --git a/Sources/ProfileFeature/ViewModels/ProfilePhoneViewModel.swift b/Sources/ProfileFeature/ViewModels/ProfilePhoneViewModel.swift index 1013725b..37db504e 100644 --- a/Sources/ProfileFeature/ViewModels/ProfilePhoneViewModel.swift +++ b/Sources/ProfileFeature/ViewModels/ProfilePhoneViewModel.swift @@ -4,7 +4,7 @@ import Models import Combine import Countries import InputField -import Integration +import XXClient import CombineSchedulers import DependencyInjection @@ -18,7 +18,7 @@ struct ProfilePhoneViewState: Equatable { final class ProfilePhoneViewModel { // MARK: Injected - @Dependency private var session: SessionType + @Dependency var userDiscovery: UserDiscovery // MARK: Properties @@ -54,19 +54,19 @@ final class ProfilePhoneViewModel { let content = "\(self.stateRelay.value.input)\(self.stateRelay.value.country.code)" - self.session.register(.phone, value: content) { [weak self] in - guard let self = self else { return } - - switch $0 { - case .success(let confirmationId): - self.hudRelay.send(.none) - self.stateRelay.value.confirmation = .init( - content: content, - confirmationId: confirmationId - ) - case .failure(let error): - self.hudRelay.send(.error(.init(with: error))) - } + do { + let confirmationId = try self.userDiscovery.sendRegisterFact( + .init(fact: content, type: FactType.phone.rawValue) + ) + + self.hudRelay.send(.none) + self.stateRelay.value.confirmation = .init( + content: content, + confirmationId: confirmationId + ) + + } catch { + self.hudRelay.send(.error(.init(with: error))) } } } diff --git a/Sources/ProfileFeature/ViewModels/ProfileViewModel.swift b/Sources/ProfileFeature/ViewModels/ProfileViewModel.swift index a7066ecc..d6bc8bb3 100644 --- a/Sources/ProfileFeature/ViewModels/ProfileViewModel.swift +++ b/Sources/ProfileFeature/ViewModels/ProfileViewModel.swift @@ -1,12 +1,13 @@ import HUD import UIKit import Shared +import Models import Combine import Defaults import Countries import Foundation import Permissions -import Integration +import XXClient import CombineSchedulers import DependencyInjection @@ -30,7 +31,7 @@ final class ProfileViewModel { @KeyObject(.sharingEmail, defaultValue: false) var isEmailSharing: Bool @KeyObject(.sharingPhone, defaultValue: false) var isPhoneSharing: Bool - @Dependency private var session: SessionType + @Dependency var userDiscovery: UserDiscovery @Dependency private var permissions: PermissionHandling var name: String { username! } @@ -89,7 +90,20 @@ final class ProfileViewModel { guard let self = self else { return } do { - try self.session.unregister(fact: isEmail ? .email : .phone) + try self.userDiscovery.removeFact( + .init( + fact: isEmail ? "E\(self.emailStored!)" : "P\(self.phoneStored!)", + type: isEmail ? FactType.email.rawValue : FactType.phone.rawValue + ) + ) + + if isEmail { + self.emailStored = nil + self.isEmailSharing = false + } else { + self.phoneStored = nil + self.isPhoneSharing = false + } self.hudRelay.send(.none) self.refresh() diff --git a/Sources/PushFeature/PushExtractor.swift b/Sources/PushFeature/PushExtractor.swift index 045f0e89..6141650b 100644 --- a/Sources/PushFeature/PushExtractor.swift +++ b/Sources/PushFeature/PushExtractor.swift @@ -1,5 +1,4 @@ import Foundation -import Integration public struct PushExtractor { enum Constants { @@ -13,26 +12,27 @@ public struct PushExtractor { public extension PushExtractor { static let live = PushExtractor { dictionary in - var error: NSError? - - guard let data = dictionary[Constants.notificationData] as? String, - let defaults = UserDefaults(suiteName: Constants.appGroup), - let preImage = defaults.value(forKey: Constants.preImage) as? String, - let reports = evaluateNotification(data, preImage, &error) else { - return .success(nil) - } - - if let error = error { - return .failure(error) - } - - let pushes = (0..<reports.len()) - .compactMap { try? reports.get(index: $0) } - .filter { $0.forMe() } - .filter { $0.type() != PushType.silent.rawValue } - .filter { $0.type() != PushType.default.rawValue } - .compactMap { Push(type: $0.type(), source: $0.source()) } - - return .success(pushes) + fatalError(">>> Missing API for notifications") +// var error: NSError? +// +// guard let data = dictionary[Constants.notificationData] as? String, +// let defaults = UserDefaults(suiteName: Constants.appGroup), +// let preImage = defaults.value(forKey: Constants.preImage) as? String, +// let reports = evaluateNotification(data, preImage, &error) else { +// return .success(nil) +// } +// +// if let error = error { +// return .failure(error) +// } +// +// let pushes = (0..<reports.len()) +// .compactMap { try? reports.get(index: $0) } +// .filter { $0.forMe() } +// .filter { $0.type() != PushType.silent.rawValue } +// .filter { $0.type() != PushType.default.rawValue } +// .compactMap { Push(type: $0.type(), source: $0.source()) } +// +// return .success(pushes) } } diff --git a/Sources/PushFeature/PushHandler.swift b/Sources/PushFeature/PushHandler.swift index b090664f..acee5de0 100644 --- a/Sources/PushFeature/PushHandler.swift +++ b/Sources/PushFeature/PushHandler.swift @@ -2,7 +2,7 @@ import UIKit import Models import Defaults import XXModels -import Integration +import XXDatabase import ReportingFeature import DependencyInjection @@ -38,8 +38,7 @@ public final class PushHandler: PushHandling { public func registerToken(_ token: Data) { do { - let session = try DependencyInjection.Container.shared.resolve() as SessionType - try session.registerNotifications(token) + fatalError(">>> Missing API for notifications") } catch { isPushEnabled = false } diff --git a/Sources/RequestsFeature/ViewModels/RequestsFailedViewModel.swift b/Sources/RequestsFeature/ViewModels/RequestsFailedViewModel.swift index b7d81db3..6b3cf746 100644 --- a/Sources/RequestsFeature/ViewModels/RequestsFailedViewModel.swift +++ b/Sources/RequestsFeature/ViewModels/RequestsFailedViewModel.swift @@ -2,12 +2,18 @@ import HUD import UIKit import Models import Combine -import Integration +import XXModels +import Defaults +import XXClient import CombineSchedulers import DependencyInjection final class RequestsFailedViewModel { - @Dependency private var session: SessionType + @Dependency var e2e: E2E + @Dependency var database: Database + @Dependency var userDiscovery: UserDiscovery + + @KeyObject(.username, defaultValue: nil) var username: String? var hudPublisher: AnyPublisher<HUDStatus, Never> { hudSubject.eraseToAnyPublisher() @@ -24,7 +30,7 @@ final class RequestsFailedViewModel { var backgroundScheduler: AnySchedulerOf<DispatchQueue> = DispatchQueue.global().eraseToAnyScheduler() init() { - session.dbManager.fetchContactsPublisher(.init(authStatus: [.requestFailed])) + database.fetchContactsPublisher(.init(authStatus: [.requestFailed])) .assertNoFailure() .map { data -> NSDiffableDataSourceSnapshot<Section, Request> in var snapshot = NSDiffableDataSourceSnapshot<Section, Request>() @@ -43,7 +49,14 @@ final class RequestsFailedViewModel { guard let self = self else { return } do { - try self.session.retryRequest(contact) + var myFacts = try self.userDiscovery.getFacts() + myFacts.append(Fact(fact: self.username!, type: FactType.username.rawValue)) + + let _ = try self.e2e.requestAuthenticatedChannel( + partnerContact: contact.id, + myFacts: myFacts + ) + self.hudSubject.send(.none) } catch { self.hudSubject.send(.error(.init(with: error))) diff --git a/Sources/RequestsFeature/ViewModels/RequestsReceivedViewModel.swift b/Sources/RequestsFeature/ViewModels/RequestsReceivedViewModel.swift index 3f4be9cb..9ae5fc8e 100644 --- a/Sources/RequestsFeature/ViewModels/RequestsReceivedViewModel.swift +++ b/Sources/RequestsFeature/ViewModels/RequestsReceivedViewModel.swift @@ -5,12 +5,14 @@ import Shared import Combine import Defaults import XXModels -import Integration +import XXClient import DrawerFeature import ReportingFeature import CombineSchedulers import DependencyInjection +import struct XXModels.Group + struct RequestReceived: Hashable, Equatable { var request: Request? var isHidden: Bool @@ -18,8 +20,11 @@ struct RequestReceived: Hashable, Equatable { } final class RequestsReceivedViewModel { - @Dependency private var session: SessionType - @Dependency private var reportingStatus: ReportingStatus + @Dependency var e2e: E2E + @Dependency var database: Database + @Dependency var groupManager: GroupChat + @Dependency var userDiscovery: UserDiscovery + @Dependency var reportingStatus: ReportingStatus @KeyObject(.isShowingHiddenRequests, defaultValue: false) var isShowingHiddenRequests: Bool @@ -75,8 +80,8 @@ final class RequestsReceivedViewModel { isBanned: reportingStatus.isEnabled() ? false : nil ) - let groupStream = session.dbManager.fetchGroupsPublisher(groupsQuery).assertNoFailure() - let contactsStream = session.dbManager.fetchContactsPublisher(contactsQuery).assertNoFailure() + let groupStream = database.fetchGroupsPublisher(groupsQuery).assertNoFailure() + let contactsStream = database.fetchContactsPublisher(contactsQuery).assertNoFailure() Publishers.CombineLatest3( groupStream, @@ -139,11 +144,49 @@ final class RequestsReceivedViewModel { } func didTapStateButtonFor(request: Request) { - guard case let .contact(contact) = request else { return } + guard case var .contact(contact) = request else { return } if request.status == .failedToVerify { backgroundScheduler.schedule { [weak self] in - self?.session.verify(contact: contact) + guard let self = self else { return } + + do { + contact.authStatus = .verificationInProgress + try self.database.saveContact(contact) + + if contact.email == nil && contact.phone == nil { + let _ = try LookupUD.live( + e2eId: self.e2e.getId(), + udContact: self.userDiscovery.getContact(), + lookupId: contact.id, + callback: .init(handle: { + switch $0 { + case .success(let data): + let ownershipResult = try! self.e2e.verifyOwnership( + receivedContact: contact.marshaled!, + verifiedContact: data, + e2eId: self.e2e.getId() + ) + + if ownershipResult == true { + contact.authStatus = .verified + _ = try? self.database.saveContact(contact) + } else { + _ = try? self.database.deleteContact(contact) + } + case .failure(let error): + print("^^^ \(#file):\(#line) \(error.localizedDescription)") + contact.authStatus = .verificationFailed + _ = try? self.database.saveContact(contact) + } + }) + ) + } + } catch { + print("^^^ \(#file):\(#line) \(error.localizedDescription)") + contact.authStatus = .verificationFailed + _ = try? self.database.saveContact(contact) + } } } else if request.status == .verifying { verifyingSubject.send() @@ -151,9 +194,9 @@ final class RequestsReceivedViewModel { } func didRequestHide(group: Group) { - if var group = try? session.dbManager.fetchGroups(.init(id: [group.id])).first { + if var group = try? database.fetchGroups(.init(id: [group.id])).first { group.authStatus = .hidden - _ = try? session.dbManager.saveGroup(group) + _ = try? database.saveGroup(group) } } @@ -161,12 +204,23 @@ final class RequestsReceivedViewModel { hudSubject.send(.on) backgroundScheduler.schedule { [weak self] in + guard let self = self else { return } + do { - try self?.session.join(group: group) - self?.hudSubject.send(.none) - self?.groupConfirmationSubject.send(group) + let trackedId = try self.groupManager + .getGroup(groupId: group.id) + .getTrackedID() + + try self.groupManager.joinGroup(trackedGroupId: trackedId) + + var group = group + group.authStatus = .participating + try self.database.saveGroup(group) + + self.hudSubject.send(.none) + self.groupConfirmationSubject.send(group) } catch { - self?.hudSubject.send(.error(.init(with: error))) + self.hudSubject.send(.error(.init(with: error))) } } } @@ -175,8 +229,8 @@ final class RequestsReceivedViewModel { _ group: Group, _ completion: @escaping (Result<[DrawerTableCellModel], Error>) -> Void ) { - if let info = try? session.dbManager.fetchGroupInfos(.init(groupId: group.id)).first { - session.dbManager.fetchContactsPublisher(.init(id: Set(info.members.map(\.id)))) + if let info = try? database.fetchGroupInfos(.init(groupId: group.id)).first { + database.fetchContactsPublisher(.init(id: Set(info.members.map(\.id)))) .assertNoFailure() .sink { members in let withUsername = members @@ -209,9 +263,9 @@ final class RequestsReceivedViewModel { } func didRequestHide(contact: Contact) { - if var contact = try? session.dbManager.fetchContacts(.init(id: [contact.id])).first { + if var contact = try? database.fetchContacts(.init(id: [contact.id])).first { contact.authStatus = .hidden - _ = try? session.dbManager.saveContact(contact) + _ = try? database.saveContact(contact) } } @@ -219,21 +273,31 @@ final class RequestsReceivedViewModel { hudSubject.send(.on) var contact = contact + contact.authStatus = .confirming contact.nickname = nickname ?? contact.username backgroundScheduler.schedule { [weak self] in + guard let self = self else { return } + do { - try self?.session.confirm(contact) - self?.hudSubject.send(.none) - self?.contactConfirmationSubject.send(contact) + try self.database.saveContact(contact) + + let _ = try self.e2e.confirmReceivedRequest(partnerContact: contact.id) + contact.authStatus = .friend + try self.database.saveContact(contact) + + self.hudSubject.send(.none) + self.contactConfirmationSubject.send(contact) } catch { - self?.hudSubject.send(.error(.init(with: error))) + contact.authStatus = .confirmationFailed + _ = try? self.database.saveContact(contact) + self.hudSubject.send(.error(.init(with: error))) } } } func groupChatWith(group: Group) -> GroupInfo { - guard let info = try? session.dbManager.fetchGroupInfos(.init(groupId: group.id)).first else { + guard let info = try? database.fetchGroupInfos(.init(groupId: group.id)).first else { fatalError() } diff --git a/Sources/RequestsFeature/ViewModels/RequestsSentViewModel.swift b/Sources/RequestsFeature/ViewModels/RequestsSentViewModel.swift index 40964837..2463f011 100644 --- a/Sources/RequestsFeature/ViewModels/RequestsSentViewModel.swift +++ b/Sources/RequestsFeature/ViewModels/RequestsSentViewModel.swift @@ -5,7 +5,8 @@ import Shared import Combine import Defaults import XXModels -import Integration +import Defaults +import XXClient import ToastFeature import ReportingFeature import CombineSchedulers @@ -17,9 +18,13 @@ struct RequestSent: Hashable, Equatable { } final class RequestsSentViewModel { - @Dependency private var session: SessionType - @Dependency private var reportingStatus: ReportingStatus - @Dependency private var toastController: ToastController + @Dependency var e2e: E2E + @Dependency var database: Database + @Dependency var userDiscovery: UserDiscovery + @Dependency var reportingStatus: ReportingStatus + @Dependency var toastController: ToastController + + @KeyObject(.username, defaultValue: nil) var username: String? var hudPublisher: AnyPublisher<HUDStatus, Never> { hudSubject.eraseToAnyPublisher() @@ -45,7 +50,7 @@ final class RequestsSentViewModel { isBanned: reportingStatus.isEnabled() ? false : nil ) - session.dbManager.fetchContactsPublisher(query) + database.fetchContactsPublisher(query) .assertNoFailure() .removeDuplicates() .map { data -> NSDiffableDataSourceSnapshot<Section, RequestSent> in @@ -65,7 +70,14 @@ final class RequestsSentViewModel { guard let self = self else { return } do { - try self.session.retryRequest(contact) + var myFacts = try self.userDiscovery.getFacts() + myFacts.append(Fact(fact: self.username!, type: FactType.username.rawValue)) + + let _ = try self.e2e.requestAuthenticatedChannel( + partnerContact: contact.id, + myFacts: myFacts + ) + self.hudSubject.send(.none) var item = item diff --git a/Sources/RestoreFeature/Controllers/RestoreController.swift b/Sources/RestoreFeature/Controllers/RestoreController.swift index f2081aae..5ba058c6 100644 --- a/Sources/RestoreFeature/Controllers/RestoreController.swift +++ b/Sources/RestoreFeature/Controllers/RestoreController.swift @@ -14,8 +14,8 @@ public final class RestoreController: UIViewController { private var cancellables = Set<AnyCancellable>() private var drawerCancellables = Set<AnyCancellable>() - public init(_ ndf: String, _ settings: RestoreSettings) { - viewModel = .init(ndf: ndf, settings: settings) + public init(_ settings: RestoreSettings) { + viewModel = .init(settings: settings) super.init(nibName: nil, bundle: nil) } diff --git a/Sources/RestoreFeature/Controllers/RestoreListController.swift b/Sources/RestoreFeature/Controllers/RestoreListController.swift index 33470e44..a10fdfcf 100644 --- a/Sources/RestoreFeature/Controllers/RestoreListController.swift +++ b/Sources/RestoreFeature/Controllers/RestoreListController.swift @@ -11,7 +11,6 @@ public final class RestoreListController: UIViewController { lazy private var screenView = RestoreListView() - private let ndf: String private let viewModel = RestoreListViewModel() private var cancellables = Set<AnyCancellable>() private var drawerCancellables = Set<AnyCancellable>() @@ -21,13 +20,6 @@ public final class RestoreListController: UIViewController { presentWarning() } - public init(_ ndf: String) { - self.ndf = ndf - super.init(nibName: nil, bundle: nil) - } - - public required init?(coder: NSCoder) { nil } - public override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) navigationItem.backButtonTitle = "" @@ -45,7 +37,7 @@ public final class RestoreListController: UIViewController { viewModel.backupPublisher .receive(on: DispatchQueue.main) .sink { [unowned self] in - coordinator.toRestore(using: ndf, with: $0, from: self) + coordinator.toRestore(with: $0, from: self) }.store(in: &cancellables) screenView.cancelButton diff --git a/Sources/RestoreFeature/Coordinator/RestoreCoordinator.swift b/Sources/RestoreFeature/Coordinator/RestoreCoordinator.swift index 2183035b..b5f5d057 100644 --- a/Sources/RestoreFeature/Coordinator/RestoreCoordinator.swift +++ b/Sources/RestoreFeature/Coordinator/RestoreCoordinator.swift @@ -8,7 +8,7 @@ public protocol RestoreCoordinating { func toSuccess(from: UIViewController) func toDrawer(_: UIViewController, from: UIViewController) func toPassphrase(from: UIViewController, _: @escaping StringClosure) - func toRestore(using: String, with: RestoreSettings, from: UIViewController) + func toRestore(with: RestoreSettings, from: UIViewController) } public struct RestoreCoordinator: RestoreCoordinating { @@ -18,13 +18,13 @@ public struct RestoreCoordinator: RestoreCoordinating { var successFactory: () -> UIViewController var chatListFactory: () -> UIViewController - var restoreFactory: (String, RestoreSettings) -> UIViewController + var restoreFactory: (RestoreSettings) -> UIViewController var passphraseFactory: (@escaping StringClosure) -> UIViewController public init( successFactory: @escaping () -> UIViewController, chatListFactory: @escaping () -> UIViewController, - restoreFactory: @escaping (String, RestoreSettings) -> UIViewController, + restoreFactory: @escaping (RestoreSettings) -> UIViewController, passphraseFactory: @escaping (@escaping StringClosure) -> UIViewController ) { self.successFactory = successFactory @@ -36,11 +36,10 @@ public struct RestoreCoordinator: RestoreCoordinating { public extension RestoreCoordinator { func toRestore( - using ndf: String, with settings: RestoreSettings, from parent: UIViewController ) { - let screen = restoreFactory(ndf, settings) + let screen = restoreFactory(settings) pushPresenter.present(screen, from: parent) } diff --git a/Sources/RestoreFeature/Resources/cmix.rip.crt b/Sources/RestoreFeature/Resources/cmix.rip.crt new file mode 100644 index 00000000..baa29e6a --- /dev/null +++ b/Sources/RestoreFeature/Resources/cmix.rip.crt @@ -0,0 +1,21 @@ +-----BEGIN CERTIFICATE----- +MIIDbDCCAlSgAwIBAgIJAOUNtZneIYECMA0GCSqGSIb3DQEBBQUAMGgxCzAJBgNV +BAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRIwEAYDVQQHDAlDbGFyZW1vbnQx +GzAZBgNVBAoMElByaXZhdGVncml0eSBDb3JwLjETMBEGA1UEAwwKKi5jbWl4LnJp +cDAeFw0xOTAzMDUxODM1NDNaFw0yOTAzMDIxODM1NDNaMGgxCzAJBgNVBAYTAlVT +MRMwEQYDVQQIDApDYWxpZm9ybmlhMRIwEAYDVQQHDAlDbGFyZW1vbnQxGzAZBgNV +BAoMElByaXZhdGVncml0eSBDb3JwLjETMBEGA1UEAwwKKi5jbWl4LnJpcDCCASIw +DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAPP0WyVkfZA/CEd2DgKpcudn0oDh +Dwsjmx8LBDWsUgQzyLrFiVigfUmUefknUH3dTJjmiJtGqLsayCnWdqWLHPJYvFfs +WYW0IGF93UG/4N5UAWO4okC3CYgKSi4ekpfw2zgZq0gmbzTnXcHF9gfmQ7jJUKSE +tJPSNzXq+PZeJTC9zJAb4Lj8QzH18rDM8DaL2y1ns0Y2Hu0edBFn/OqavBJKb/uA +m3AEjqeOhC7EQUjVamWlTBPt40+B/6aFJX5BYm2JFkRsGBIyBVL46MvC02MgzTT9 +bJIJfwqmBaTruwemNgzGu7Jk03hqqS1TUEvSI6/x8bVoba3orcKkf9HsDjECAwEA +AaMZMBcwFQYDVR0RBA4wDIIKKi5jbWl4LnJpcDANBgkqhkiG9w0BAQUFAAOCAQEA +neUocN4AbcQAC1+b3To8u5UGdaGxhcGyZBlAoenRVdjXK3lTjsMdMWb4QctgNfIf +U/zuUn2mxTmF/ekP0gCCgtleZr9+DYKU5hlXk8K10uKxGD6EvoiXZzlfeUuotgp2 +qvI3ysOm/hvCfyEkqhfHtbxjV7j7v7eQFPbvNaXbLa0yr4C4vMK/Z09Ui9JrZ/Z4 +cyIkxfC6/rOqAirSdIp09EGiw7GM8guHyggE4IiZrDslT8V3xIl985cbCxSxeW1R +tgH4rdEXuVe9+31oJhmXOE9ux2jCop9tEJMgWg7HStrJ5plPbb+HmjoX3nBO04E5 +6m52PyzMNV+2N21IPppKwA== +-----END CERTIFICATE----- diff --git a/Sources/RestoreFeature/Resources/udContact.bin b/Sources/RestoreFeature/Resources/udContact.bin new file mode 100644 index 00000000..b2611d41 --- /dev/null +++ b/Sources/RestoreFeature/Resources/udContact.bin @@ -0,0 +1 @@ +<xxc(2)7mbKFLE201WzH4SGxAOpHjjehwztIV+KGifi5L/PYPcDkAZiB9kZo+Dl3Vc7dD2SdZCFMOJVgwqGzfYRDkjc8RGEllBqNxq2sRRX09iQVef0kJQUgJCHNCOcvm6Ki0JJwvjLceyFh36iwK8oLbhLgqEZY86UScdACTyBCzBIab3ob5mBthYc3mheV88yq5PGF2DQ+dEvueUm+QhOSfwzppAJA/rpW9Wq9xzYcQzaqc3ztAGYfm2BBAHS7HVmkCbvZ/K07Xrl4EBPGHJYq12tWAN/C3mcbbBYUOQXyEzbSl/mO7sL3ORr0B4FMuqCi8EdlD6RO52pVhY+Cg6roRH1t5Ng1JxPt8Mv1yyjbifPhZ5fLKwxBz8UiFORfk0/jnhwgm25LRHqtNRRUlYXLvhv0HhqyYTUt17WNtCLATSVbqLrFGdy2EGadn8mP+kQNHp93f27d/uHgBNNe7LpuYCJMdWpoG6bOqmHEftxt0/MIQA8fTtTm3jJzv+7/QjZJDvQIv0SNdp8HFogpuwde+GuS4BcY7v5xz+ArGWcRR63ct2z83MqQEn9ODr1/gAAAgA7szRpDDQIdFUQo9mkWg8xBA==xxc> \ No newline at end of file diff --git a/Sources/RestoreFeature/ViewModels/RestoreViewModel.swift b/Sources/RestoreFeature/ViewModels/RestoreViewModel.swift index bea37e51..4207eb1b 100644 --- a/Sources/RestoreFeature/ViewModels/RestoreViewModel.swift +++ b/Sources/RestoreFeature/ViewModels/RestoreViewModel.swift @@ -4,7 +4,6 @@ import Shared import Combine import Defaults import Foundation -import Integration import BackupFeature import DependencyInjection @@ -13,6 +12,9 @@ import iCloudFeature import DropboxFeature import GoogleDriveFeature +import XXClient +import struct Models.Backup + enum RestorationStep { case idle(CloudService, Backup?) case downloading(Float, Float) @@ -39,12 +41,16 @@ extension RestorationStep: Equatable { } final class RestoreViewModel { + @Dependency var cMixManager: CMixManager + @Dependency private var sftpService: SFTPService @Dependency private var iCloudService: iCloudInterface @Dependency private var dropboxService: DropboxInterface @Dependency private var googleService: GoogleDriveInterface @KeyObject(.username, defaultValue: nil) var username: String? + @KeyObject(.phone, defaultValue: nil) var phone: String? + @KeyObject(.email, defaultValue: nil) var email: String? var step: AnyPublisher<RestorationStep, Never> { stepRelay.eraseToAnyPublisher() @@ -54,13 +60,11 @@ final class RestoreViewModel { // private var pendingData: Data? - private let ndf: String private var passphrase: String! private let settings: RestoreSettings private let stepRelay: CurrentValueSubject<RestorationStep, Never> - init(ndf: String, settings: RestoreSettings) { - self.ndf = ndf + init(settings: RestoreSettings) { self.settings = settings self.stepRelay = .init(.idle(settings.cloudService, settings.backup)) } @@ -155,13 +159,120 @@ final class RestoreViewModel { guard let self = self else { return } do { - let session = try Session( - passphrase: self.passphrase, - backupFile: data, - ndf: self.ndf + let report = try self.cMixManager.restore( + backup: data, + passphrase: self.passphrase + ) + + struct BackupParameters: Codable { + var email: String? + var phone: String? + var username: String + } + + guard let paramsData = report.params.data(using: .utf8) else { + fatalError("Couldn't parse parameters from backup to byte array") + } + + let facts = try JSONDecoder().decode( + BackupParameters.self, + from: paramsData + ) + + var phoneFact: Fact? + + self.phone = facts.phone + self.email = facts.email + + let cMix = try self.cMixManager.load() + + DependencyInjection.Container.shared.register(cMix) + + let e2e = try Login.live( + cMixId: cMix.getId(), + authCallbacks: .init( + handle: { callbacks in + switch callbacks { + case .reset( + contact: _, + receptionId: _, + ephemeralId: _, + roundId: _ + ): + break + case .confirm( + contact: _, + receptionId: _, + ephemeralId: _, + roundId: _ + ): + break + case .request( + contact: _, + receptionId: _, + ephemeralId: _, + roundId: _ + ): + break + } + } + ), + identity: try cMix.makeLegacyReceptionIdentity() ) - DependencyInjection.Container.shared.register(session as SessionType) + guard let certPath = Bundle.module.path(forResource: "cmix.rip", ofType: "crt"), + let contactFilePath = Bundle.module.path(forResource: "udContact", ofType: "bin") else { + fatalError("Couldn't retrieve alternative UD credentials") + } + + let userDiscovery = try NewUdManagerFromBackup.live(.init( + e2eId: e2e.getId(), + follower: .init(handle: { cMix.networkFollowerStatus().rawValue }), + email: nil, + phone: nil, + cert: Data(contentsOf: URL(fileURLWithPath: certPath)), + contactFile: Data(contentsOf: URL(fileURLWithPath: contactFilePath)), + address: "46.101.98.49:18001" + )) + + DependencyInjection.Container.shared.register(userDiscovery) + + try e2e.registerListener( + senderId: nil, + messageType: 2, + callback: .init(handle: { message in + print(message.timestamp) + }) + ) + + DependencyInjection.Container.shared.register(e2e) + + let groupManager = try NewGroupChat.live( + e2eId: e2e.getId(), + groupRequest: .init(handle: { + print($0) + }), + groupChatProcessor: .init(handle: { + print($0) + }) + ) + + DependencyInjection.Container.shared.register(groupManager) + + let transferManager = try InitFileTransfer.live( + e2eId: e2e.getId(), + callback: .init(handle: { + switch $0 { + case .success(let receivedFile): + print(receivedFile.name) + case .failure(let error): + print(error.localizedDescription) + } + }) + ) + + DependencyInjection.Container.shared.register(transferManager) + self.stepRelay.send(.done) } catch { self.pendingData = data diff --git a/Sources/ScanFeature/ViewModels/ScanDisplayViewModel.swift b/Sources/ScanFeature/ViewModels/ScanDisplayViewModel.swift index df456065..1b7ed9b2 100644 --- a/Sources/ScanFeature/ViewModels/ScanDisplayViewModel.swift +++ b/Sources/ScanFeature/ViewModels/ScanDisplayViewModel.swift @@ -1,8 +1,9 @@ import UIKit +import Models import Combine import Defaults import Countries -import Integration +import XXClient import DependencyInjection struct ScanDisplayViewState: Equatable { @@ -14,7 +15,7 @@ struct ScanDisplayViewState: Equatable { } final class ScanDisplayViewModel { - @Dependency private var session: SessionType + @Dependency var userDiscovery: UserDiscovery @KeyObject(.email, defaultValue: nil) private var email: String? @KeyObject(.phone, defaultValue: nil) private var phone: String? @@ -58,7 +59,23 @@ final class ScanDisplayViewModel { func generateQR() { guard let filter = CIFilter(name: "CIQRCodeGenerator") else { return } - filter.setValue(session.myQR, forKey: "inputMessage") + var facts: [Fact] = [] + let myContact = try! userDiscovery.getContact() + + if sharingPhone { + facts.append(Fact(fact: phone!, type: FactType.phone.rawValue)) + } + + if sharingEmail { + facts.append(Fact(fact: email!, type: FactType.email.rawValue)) + } + + let myContactWithFacts = try! SetFactsOnContact.live( + contact: myContact, + facts: facts + ) + + filter.setValue(myContactWithFacts, forKey: "inputMessage") let transform = CGAffineTransform(scaleX: 5, y: 5) if let output = filter.outputImage?.transformed(by: transform) { diff --git a/Sources/ScanFeature/ViewModels/ScanViewModel.swift b/Sources/ScanFeature/ViewModels/ScanViewModel.swift index b940226d..c7c9b11a 100644 --- a/Sources/ScanFeature/ViewModels/ScanViewModel.swift +++ b/Sources/ScanFeature/ViewModels/ScanViewModel.swift @@ -3,7 +3,7 @@ import Models import Combine import XXModels import Foundation -import Integration +import XXClient import CombineSchedulers import DependencyInjection @@ -26,7 +26,8 @@ struct ScanViewState: Equatable { } final class ScanViewModel { - @Dependency private var session: SessionType + @Dependency var database: Database + @Dependency var getFactsFromContact: GetFactsFromContact var backgroundScheduler: AnySchedulerOf<DispatchQueue> = DispatchQueue.global().eraseToAnyScheduler() @@ -56,7 +57,7 @@ final class ScanViewModel { - if let previouslyAdded = try? self.session.dbManager.fetchContacts(.init(id: [usernameAndId.1])).first { + if let previouslyAdded = try? self.database.fetchContacts(.init(id: [usernameAndId.1])).first { var error = ScanError.unknown(Localized.Scan.Error.general) switch previouslyAdded.authStatus { @@ -72,12 +73,16 @@ final class ScanViewModel { return } + let facts = try? self.getFactsFromContact(contact: data) + let contactEmail = facts?.first(where: { $0.type == FactType.email.rawValue })?.fact + let contactPhone = facts?.first(where: { $0.type == FactType.phone.rawValue })?.fact + let contact = Contact( id: usernameAndId.1, marshaled: data, username: usernameAndId.0, - email: try? self.session.extract(fact: .email, from: data), - phone: try? self.session.extract(fact: .phone, from: data), + email: contactEmail, + phone: contactPhone, nickname: nil, photo: nil, authStatus: .stranger, @@ -93,9 +98,11 @@ final class ScanViewModel { } private func verifyScanned(_ data: Data) throws -> (String, Data)? { - guard let username = try session.extract(fact: .username, from: data), - let id = session.getId(from: data) else { return nil } + let id = try? GetIdFromContact.live(data) + let facts = try? getFactsFromContact(contact: data) + let username = facts?.first(where: { $0.type == FactType.username.rawValue })?.fact + guard let id = id, let username = username else { return nil } return (username, id) } diff --git a/Sources/SearchFeature/ViewModels/SearchContainerViewModel.swift b/Sources/SearchFeature/ViewModels/SearchContainerViewModel.swift index e308f6a5..9ca3794c 100644 --- a/Sources/SearchFeature/ViewModels/SearchContainerViewModel.swift +++ b/Sources/SearchFeature/ViewModels/SearchContainerViewModel.swift @@ -1,15 +1,14 @@ import UIKit import Combine import Defaults -import Integration import PushFeature +import XXClient import DependencyInjection final class SearchContainerViewModel { - @Dependency var session: SessionType @Dependency var pushHandler: PushHandling + @Dependency var dummyTrafficManager: DummyTraffic - @KeyObject(.dummyTrafficOn, defaultValue: false) var isCoverTrafficEnabled @KeyObject(.pushNotifications, defaultValue: false) var pushNotifications @KeyObject(.askedDummyTrafficOnce, defaultValue: false) var offeredCoverTraffic @@ -25,8 +24,7 @@ final class SearchContainerViewModel { } func didEnableCoverTraffic() { - isCoverTrafficEnabled = true - session.setDummyTraffic(status: true) + try! dummyTrafficManager.setStatus(true) } private func verifyCoverTraffic() { diff --git a/Sources/SearchFeature/ViewModels/SearchLeftViewModel.swift b/Sources/SearchFeature/ViewModels/SearchLeftViewModel.swift index 6f708401..d608dc01 100644 --- a/Sources/SearchFeature/ViewModels/SearchLeftViewModel.swift +++ b/Sources/SearchFeature/ViewModels/SearchLeftViewModel.swift @@ -3,11 +3,15 @@ import UIKit import Shared import Combine import XXModels +import XXClient import Defaults import Countries -import Integration +import Models +import Defaults +import CustomDump import NetworkMonitor import ReportingFeature +import CombineSchedulers import DependencyInjection typealias SearchSnapshot = NSDiffableDataSourceSnapshot<SearchSection, SearchItem> @@ -20,10 +24,18 @@ struct SearchLeftViewState { } final class SearchLeftViewModel { - @Dependency var session: SessionType + @Dependency var e2e: E2E + @Dependency var database: Database + @Dependency var userDiscovery: UserDiscovery @Dependency var reportingStatus: ReportingStatus @Dependency var networkMonitor: NetworkMonitoring + @KeyObject(.username, defaultValue: nil) var username: String? + + var myId: Data { + try! GetIdFromContact.live(userDiscovery.getContact()) + } + var hudPublisher: AnyPublisher<HUDStatus, Never> { hudSubject.eraseToAnyPublisher() } @@ -36,6 +48,8 @@ final class SearchLeftViewModel { stateSubject.eraseToAnyPublisher() } + var backgroundScheduler: AnySchedulerOf<DispatchQueue> = DispatchQueue.global().eraseToAnyScheduler() + private var invitation: String? private var searchCancellables = Set<AnyCancellable>() private let successSubject = PassthroughSubject<Contact, Never>() @@ -99,47 +113,107 @@ final class SearchLeftViewModel { content += stateSubject.value.country.code } - session.search(fact: "\(prefix)\(content)") - .sink { [unowned self] in - if case .failure(let error) = $0 { - self.appendToLocalSearch(nil) - self.hudSubject.send(.error(.init(with: error))) - } - } receiveValue: { contact in - self.hudSubject.send(.none) - self.appendToLocalSearch(contact) - }.store(in: &searchCancellables) +// backgroundScheduler.schedule { [weak self] in +// guard let self = self else { return } + + do { + let report = try SearchUD.live( + e2eId: self.e2e.getId(), + udContact: self.userDiscovery.getContact(), + facts: [Fact(fact: "teste", type: 0)], + callback: .init(handle: { + switch $0 { + case .success(let dataArray): + print("^^^ \(#file):\(#line) \(dataArray.map { $0.base64EncodedString() })") + + // self.hudSubject.send(.none) + // self.appendToLocalSearch(contact) + + case .failure(let error): + print("^^^ \(#file):\(#line) error: \(error.localizedDescription)") + self.appendToLocalSearch(nil) + self.hudSubject.send(.error(.init(with: error))) + } + }) + ) + + print(report) + } catch { + print("^^^ \(#file):\(#line) error: \(error.localizedDescription)") + } +// } } func didTapResend(contact: Contact) { hudSubject.send(.on) - do { - try self.session.retryRequest(contact) - hudSubject.send(.none) - } catch { - hudSubject.send(.error(.init(with: error))) + var contact = contact + contact.authStatus = .requesting + + backgroundScheduler.schedule { [weak self] in + guard let self = self else { return } + + do { + try self.database.saveContact(contact) + + var myFacts = try self.userDiscovery.getFacts() + myFacts.append(Fact(fact: self.username!, type: FactType.username.rawValue)) + + let _ = try self.e2e.requestAuthenticatedChannel( + partnerContact: contact.id, + myFacts: myFacts + ) + + contact.authStatus = .requested + contact = try self.database.saveContact(contact) + + self.hudSubject.send(.none) + } catch { + contact.authStatus = .requestFailed + _ = try? self.database.saveContact(contact) + self.hudSubject.send(.error(.init(with: error))) + } } } func didTapRequest(contact: Contact) { hudSubject.send(.on) + var contact = contact contact.nickname = contact.username + contact.authStatus = .requesting + + backgroundScheduler.schedule { [weak self] in + guard let self = self else { return } - do { - try self.session.add(contact) - hudSubject.send(.none) - successSubject.send(contact) - } catch { - hudSubject.send(.error(.init(with: error))) + do { + try self.database.saveContact(contact) + + var myFacts = try self.userDiscovery.getFacts() + myFacts.append(Fact(fact: self.username!, type: FactType.username.rawValue)) + + let _ = try self.e2e.requestAuthenticatedChannel( + partnerContact: contact.marshaled!, + myFacts: myFacts + ) + + contact.authStatus = .requested + contact = try self.database.saveContact(contact) + + self.hudSubject.send(.none) + self.successSubject.send(contact) + } catch { + contact.authStatus = .requestFailed + _ = try? self.database.saveContact(contact) + self.hudSubject.send(.error(.init(with: error))) + } } } func didSet(nickname: String, for contact: Contact) { - if var contact = try? session.dbManager.fetchContacts(.init(id: [contact.id])).first { + if var contact = try? database.fetchContacts(.init(id: [contact.id])).first { contact.nickname = nickname - _ = try? session.dbManager.saveContact(contact) + _ = try? database.saveContact(contact) } } @@ -147,7 +221,7 @@ final class SearchLeftViewModel { var snapshot = SearchSnapshot() if var user = user { - if let contact = try! session.dbManager.fetchContacts(.init(id: [user.id])).first { + if let contact = try? database.fetchContacts(.init(id: [user.id])).first { user.isBanned = contact.isBanned user.isBlocked = contact.isBlocked user.authStatus = contact.authStatus @@ -169,7 +243,7 @@ final class SearchLeftViewModel { isBanned: reportingStatus.isEnabled() ? false : nil ) - if let locals = try? session.dbManager.fetchContacts(localsQuery), + if let locals = try? database.fetchContacts(localsQuery), let localsWithoutMe = removeMyself(from: locals), localsWithoutMe.isEmpty == false { snapshot.appendSections([.connections]) @@ -183,6 +257,6 @@ final class SearchLeftViewModel { } private func removeMyself(from collection: [Contact]) -> [Contact]? { - collection.filter { $0.id != session.myId } + collection.filter { $0.id != myId } } } diff --git a/Sources/SearchFeature/ViewModels/SearchRightViewModel.swift b/Sources/SearchFeature/ViewModels/SearchRightViewModel.swift index db977528..671e340d 100644 --- a/Sources/SearchFeature/ViewModels/SearchRightViewModel.swift +++ b/Sources/SearchFeature/ViewModels/SearchRightViewModel.swift @@ -1,10 +1,11 @@ import Shared +import Models import Combine import XXModels import Defaults +import XXClient import Foundation import Permissions -import Integration import ReportingFeature import DependencyInjection @@ -23,9 +24,10 @@ enum ScanningError: Equatable { } final class SearchRightViewModel { - @Dependency var session: SessionType + @Dependency var database: Database @Dependency var permissions: PermissionHandling @Dependency var reportingStatus: ReportingStatus + @Dependency var getFactsFromContact: GetFactsFromContact var foundPublisher: AnyPublisher<Contact, Never> { foundSubject.eraseToAnyPublisher() @@ -70,8 +72,11 @@ final class SearchRightViewModel { /// Whatever got scanned, needs to have id and username /// otherwise is just noise or an unknown qr code /// - guard let userId = session.getId(from: data), - let username = try? session.extract(fact: .username, from: data) else { + let userId = try? GetIdFromContact.live(data) + let facts = try? getFactsFromContact(contact: data) + let username = facts?.first(where: { $0.type == FactType.username.rawValue })?.fact + + guard let userId = userId, let username = username else { let errorTitle = Localized.Scan.Error.invalid statusSubject.send(.failed(.unknown(errorTitle))) return @@ -80,7 +85,7 @@ final class SearchRightViewModel { /// Make sure we are not processing a contact /// that we already have /// - if let alreadyContact = try? session.dbManager.fetchContacts(.init(id: [userId])).first { + if let alreadyContact = try? database.fetchContacts(.init(id: [userId])).first { if alreadyContact.isBlocked, reportingStatus.isEnabled() { statusSubject.send(.failed(.unknown("You previously blocked this user."))) return @@ -108,12 +113,20 @@ final class SearchRightViewModel { statusSubject.send(.success) cameraSemaphoreSubject.send(false) + let email = try? GetFactsFromContact.live(contact: data) + .first(where: { $0.type == FactType.email.rawValue }) + .map(\.fact) + + let phone = try? GetFactsFromContact.live(contact: data) + .first(where: { $0.type == FactType.phone.rawValue }) + .map(\.fact) + foundSubject.send(.init( id: userId, marshaled: data, username: username, - email: try? session.extract(fact: .email, from: data), - phone: try? session.extract(fact: .phone, from: data), + email: email, + phone: phone, nickname: nil, photo: nil, authStatus: .stranger, diff --git a/Sources/SettingsFeature/ViewModels/AccountDeleteViewModel.swift b/Sources/SettingsFeature/ViewModels/AccountDeleteViewModel.swift index db7e6401..ea8dd26d 100644 --- a/Sources/SettingsFeature/ViewModels/AccountDeleteViewModel.swift +++ b/Sources/SettingsFeature/ViewModels/AccountDeleteViewModel.swift @@ -1,12 +1,9 @@ import HUD import Combine -import Integration import Foundation import DependencyInjection final class AccountDeleteViewModel { - @Dependency private var session: SessionType - var deleting = false var hud: AnyPublisher<HUDStatus, Never> { hudRelay.eraseToAnyPublisher() } @@ -21,9 +18,6 @@ final class AccountDeleteViewModel { } do { - try session.deleteMyself() - DependencyInjection.Container.shared.unregister(SessionType.self) - DispatchQueue.main.async { [weak self] in self?.hudRelay.send(.error(.init( content: "Now kill the app and re-open", diff --git a/Sources/SettingsFeature/ViewModels/SettingsViewModel.swift b/Sources/SettingsFeature/ViewModels/SettingsViewModel.swift index 9b985922..b44c37d8 100644 --- a/Sources/SettingsFeature/ViewModels/SettingsViewModel.swift +++ b/Sources/SettingsFeature/ViewModels/SettingsViewModel.swift @@ -4,8 +4,8 @@ import Shared import Combine import Defaults import Permissions -import Integration import PushFeature +import XXClient import UserNotifications import CombineSchedulers import DependencyInjection @@ -21,11 +21,10 @@ struct SettingsViewState: Equatable { } final class SettingsViewModel { - @Dependency private var session: SessionType + @Dependency var dummyTrafficManager: DummyTraffic @Dependency private var pushHandler: PushHandling @Dependency private var permissions: PermissionHandling - @KeyObject(.dummyTrafficOn, defaultValue: false) var isDummyTrafficOn: Bool @KeyObject(.biometrics, defaultValue: false) private var biometrics @KeyObject(.hideAppList, defaultValue: false) private var hideAppList @KeyObject(.icognitoKeyboard, defaultValue: false) private var icognitoKeyboard @@ -47,7 +46,7 @@ final class SettingsViewModel { stateRelay.value.isPushNotification = pushNotifications stateRelay.value.isInAppNotification = inAppNotifications stateRelay.value.isBiometricsPossible = permissions.isBiometricsAvailable - stateRelay.value.isDummyTrafficOn = isDummyTrafficOn + stateRelay.value.isDummyTrafficOn = dummyTrafficManager.getStatus() } func didToggleBiometrics() { @@ -64,9 +63,9 @@ final class SettingsViewModel { } func didToggleDummyTraffic() { - isDummyTrafficOn.toggle() - stateRelay.value.isDummyTrafficOn = isDummyTrafficOn - session.setDummyTraffic(status: isDummyTrafficOn) + let currently = dummyTrafficManager.getStatus() + try! dummyTrafficManager.setStatus(!currently) + stateRelay.value.isDummyTrafficOn = !currently } func didToggleHideActiveApps() { @@ -134,7 +133,7 @@ final class SettingsViewModel { guard let self = self else { return } do { - try self.session.unregisterNotifications() + fatalError(">>> Missing API for notifications") self.hudRelay.send(.none) } catch { self.hudRelay.send(.error(.init(with: error))) diff --git a/Sources/Integration/FeedbackPlayer.swift b/Sources/Shared/FeedbackPlayer.swift similarity index 100% rename from Sources/Integration/FeedbackPlayer.swift rename to Sources/Shared/FeedbackPlayer.swift diff --git a/Tests/SearchFeatureTests/Coordinator/SearchCoordinatorSpec.swift b/Tests/SearchFeatureTests/Coordinator/SearchCoordinatorSpec.swift index 7bb3736b..cb8056fc 100644 --- a/Tests/SearchFeatureTests/Coordinator/SearchCoordinatorSpec.swift +++ b/Tests/SearchFeatureTests/Coordinator/SearchCoordinatorSpec.swift @@ -1,7 +1,6 @@ import UIKit import Quick import Nimble -import Integration import TestHelpers @testable import SearchFeature diff --git a/client-ios.xcworkspace/xcshareddata/swiftpm/Package.resolved b/client-ios.xcworkspace/xcshareddata/swiftpm/Package.resolved index 2edfffa2..00c840d9 100644 --- a/client-ios.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/client-ios.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -5,8 +5,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/firebase/abseil-cpp-SwiftPM.git", "state" : { - "revision" : "fffc3c2729be5747390ad02d5100291a0d9ad26a", - "version" : "0.20200225.4" + "revision" : "583de9bd60f66b40e78d08599cc92036c2e7e4e1", + "version" : "0.20220203.2" } }, { @@ -14,8 +14,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/Alamofire/Alamofire.git", "state" : { - "revision" : "f82c23a8a7ef8dc1a49a8bfc6a96883e79121864", - "version" : "5.5.0" + "revision" : "8dd85aee02e39dd280c75eef88ffdb86eed4b07b", + "version" : "5.6.2" } }, { @@ -23,8 +23,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/openid/AppAuth-iOS.git", "state" : { - "revision" : "01131d68346c8ae552961c768d583c715fbe1410", - "version" : "1.4.0" + "revision" : "33660c271c961f8ce1084cc13f2ea8195e864f7d", + "version" : "1.5.0" } }, { @@ -41,8 +41,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/firebase/boringssl-SwiftPM.git", "state" : { - "revision" : "734a8247442fde37df4364c21f6a0085b6a36728", - "version" : "0.7.2" + "revision" : "dd3eda2b05a3f459fc3073695ad1b28659066eab", + "version" : "0.9.1" } }, { @@ -68,8 +68,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/pointfreeco/combine-schedulers", "state" : { - "revision" : "4cf088c29a20f52be0f2ca54992b492c54e0076b", - "version" : "0.5.3" + "revision" : "8fee20f993e64bbbf22bc3e3f444758ac2d05692", + "version" : "0.7.2" } }, { @@ -86,8 +86,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/mattgallagher/CwlPreconditionTesting.git", "state" : { - "revision" : "fb7a26374e8570ff5c68142e5c83406d6abae0d8", - "version" : "2.0.2" + "revision" : "c21f7bab5ca8eee0a9998bbd17ca1d0eb45d4688", + "version" : "2.1.0" } }, { @@ -95,8 +95,17 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/ra1028/DifferenceKit", "state" : { - "revision" : "62745d7780deef4a023a792a1f8f763ec7bf9705", - "version" : "1.2.0" + "revision" : "073b9671ce2b9b5b96398611427a1f929927e428", + "version" : "1.3.0" + } + }, + { + "identity" : "elixxir-dapps-sdk-swift", + "kind" : "remoteSourceControl", + "location" : "https://git.xx.network/elixxir/elixxir-dapps-sdk-swift", + "state" : { + "branch" : "development", + "revision" : "e5088ee5e3a3aeb6d6887d2402df829acdb4f8c2" } }, { @@ -113,8 +122,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/firebase/firebase-ios-sdk.git", "state" : { - "revision" : "08686f04881483d2bc098b2696e674c0ba135e47", - "version" : "8.10.0" + "revision" : "111d8d6ad1a1afd6c8e9561d26e55ab1e74fcb42", + "version" : "8.15.0" } }, { @@ -122,8 +131,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/google/google-api-objectivec-client-for-rest", "state" : { - "revision" : "22e0bb02729d60db396e8b90d8189313cd86ba53", - "version" : "1.6.0" + "revision" : "3228334d0584cb9174796fecbe628a723be70452", + "version" : "1.7.0" } }, { @@ -131,8 +140,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/google/GoogleAppMeasurement.git", "state" : { - "revision" : "9b2f6aca5b4685c45f9f5481f19bee8e7982c538", - "version" : "8.9.1" + "revision" : "ef819db8c58657a6ca367322e73f3b6322afe0a2", + "version" : "8.15.0" } }, { @@ -140,8 +149,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/google/GoogleDataTransport.git", "state" : { - "revision" : "15ccdfd25ac55b9239b82809531ff26605e7556e", - "version" : "9.1.2" + "revision" : "5056b15c5acbb90cd214fe4d6138bdf5a740e5a8", + "version" : "9.2.0" } }, { @@ -149,8 +158,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/google/GoogleSignIn-iOS", "state" : { - "revision" : "60ca2bfd218ccb194a746a79b41d9d50eb7e3af0", - "version" : "6.1.0" + "revision" : "5ce850448e89500aca5b095af7247eb46dc0ca18", + "version" : "6.2.3" } }, { @@ -158,8 +167,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/google/GoogleUtilities.git", "state" : { - "revision" : "b3bb0c5551fb3f80ca939829639ab5b093edd14f", - "version" : "7.7.0" + "revision" : "f4abe56ce62a779e64b525eb133c8fc2a84bbc1f", + "version" : "7.7.1" } }, { @@ -172,12 +181,12 @@ } }, { - "identity" : "grpc-swiftpm", + "identity" : "grpc-ios", "kind" : "remoteSourceControl", - "location" : "https://github.com/firebase/grpc-SwiftPM.git", + "location" : "https://github.com/grpc/grpc-ios.git", "state" : { - "revision" : "fb405dd2c7901485f7e158b24e3a0a47e4efd8b5", - "version" : "1.28.4" + "revision" : "8440b914756e0d26d4f4d054a1c1581daedfc5b6", + "version" : "1.44.3-grpc" } }, { @@ -185,8 +194,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/google/gtm-session-fetcher.git", "state" : { - "revision" : "bc6a19702ac76ac4e488b68148710eb815f9bc56", - "version" : "1.7.0" + "revision" : "4e9bbf2808b8fee444e84a48f5f3c12641987d3e", + "version" : "1.7.2" } }, { @@ -194,8 +203,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/google/GTMAppAuth.git", "state" : { - "revision" : "40f4103fb52109032c05599a0c39ad43edbdf80a", - "version" : "1.2.2" + "revision" : "b9d1683be336ba8c8d1c6867bafeb056a5399700", + "version" : "1.3.0" } }, { @@ -248,8 +257,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/google/promises.git", "state" : { - "revision" : "611337c330350c9c1823ad6d671e7f936af5ee13", - "version" : "2.0.0" + "revision" : "3e4e743631e86c8c70dbc6efdc7beaa6e90fd3bb", + "version" : "2.1.1" } }, { @@ -275,8 +284,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/darrarski/ScrollViewController", "state" : { - "revision" : "9a52bb056504bb4766ddb5ac518097dd48736303", - "version" : "1.2.0" + "revision" : "288999c7b9b0246aee0cfe3b7a066546038fd4b8", + "version" : "1.3.0" } }, { @@ -292,8 +301,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/SnapKit/SnapKit", "state" : { - "revision" : "d458564516e5676af9c70b4f4b2a9178294f1bc6", - "version" : "5.0.1" + "revision" : "f222cbdf325885926566172f6f5f06af95473158", + "version" : "5.6.0" } }, { @@ -301,8 +310,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/pointfreeco/swift-case-paths", "state" : { - "revision" : "241301b67d8551c26d8f09bd2c0e52cc49f18007", - "version" : "0.8.0" + "revision" : "a09839348486db8866f85a727b8550be1d671c50", + "version" : "0.9.1" } }, { @@ -319,8 +328,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/pointfreeco/swift-composable-architecture.git", "state" : { - "revision" : "313dd217dcd1d0478118ec5d15225fd473c1564a", - "version" : "0.32.0" + "revision" : "108e3a536fcebb16c4f247ef92c2d7326baf9fe3", + "version" : "0.39.0" } }, { @@ -337,8 +346,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/pointfreeco/swift-identified-collections", "state" : { - "revision" : "680bf440178a78a627b1c2c64c0855f6523ad5b9", - "version" : "0.3.2" + "revision" : "2d6b7ffcc67afd9077fac5e5a29bcd6d39b71076", + "version" : "0.4.0" } }, { @@ -346,8 +355,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-protobuf", "state" : { - "revision" : "7e2c5f3cbbeea68e004915e3a8961e20bd11d824", - "version" : "1.18.0" + "revision" : "fa0fcd43f272a260e7f734f23e6dc55e16fcae0a", + "version" : "1.19.1" } }, { @@ -364,8 +373,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/SwiftyBeaver/SwiftyBeaver.git", "state" : { - "revision" : "2c039501d6eeb4d4cd4aec4a8d884ad28862e044", - "version" : "1.9.5" + "revision" : "12b5acf96d98f91d50de447369bd18df74600f1a", + "version" : "1.9.6" } }, { @@ -373,8 +382,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/dropbox/SwiftyDropbox.git", "state" : { - "revision" : "7af87d903be1cf0af0e76e0394d992943055894e", - "version" : "8.2.1" + "revision" : "9df3d4997127f1f17ed295e5f373ab676419df08", + "version" : "8.3.0" } }, { @@ -382,8 +391,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/pointfreeco/xctest-dynamic-overlay", "state" : { - "revision" : "ef8e14e7ce1c0c304c644c6ba365d06c468ded6b", - "version" : "0.3.3" + "revision" : "38bc9242e4388b80bd23ddfdf3071428859e3260", + "version" : "0.4.0" } } ], -- GitLab