Skip to content
Snippets Groups Projects
Client.swift 8.71 KiB
Newer Older
Bruno Muniz's avatar
Bruno Muniz committed
import Retry
import Models
import Combine
import Defaults
import Bindings
import XXModels
import Foundation
Bruno Muniz's avatar
Bruno Muniz committed

public class Client {
    @KeyObject(.inappnotifications, defaultValue: true) var inappnotifications: Bool

    let bindings: BindingsInterface
    var backupManager: BackupInterface?
Bruno Muniz's avatar
Bruno Muniz committed
    var dummyManager: DummyTrafficManaging?
    var groupManager: GroupManagerInterface?
    var userDiscovery: UserDiscoveryInterface?
    var transferManager: TransferManagerInterface?

    var backup: AnyPublisher<Data, Never> { backupSubject.eraseToAnyPublisher() }
Bruno Muniz's avatar
Bruno Muniz committed
    var network: AnyPublisher<Bool, Never> { networkSubject.eraseToAnyPublisher() }
    var resets: AnyPublisher<Contact, Never> { resetsSubject.eraseToAnyPublisher() }
Bruno Muniz's avatar
Bruno Muniz committed
    var messages: AnyPublisher<Message, Never> { messagesSubject.eraseToAnyPublisher() }
    var requests: AnyPublisher<Contact, Never> { requestsSubject.eraseToAnyPublisher() }
    var events: AnyPublisher<BackendEvent, Never> { eventsSubject.eraseToAnyPublisher() }
Bruno Muniz's avatar
Bruno Muniz committed
    var transfers: AnyPublisher<FileTransfer, Never> { transfersSubject.eraseToAnyPublisher() }
    var requestsSent: AnyPublisher<Contact, Never> { requestsSentSubject.eraseToAnyPublisher() }
Bruno Muniz's avatar
Bruno Muniz committed
    var confirmations: AnyPublisher<Contact, Never> { confirmationsSubject.eraseToAnyPublisher() }
    var groupRequests: AnyPublisher<(Group, [Data], String?), Never> { groupRequestsSubject.eraseToAnyPublisher() }

    private let backupSubject = PassthroughSubject<Data, Never>()
Bruno Muniz's avatar
Bruno Muniz committed
    private let networkSubject = PassthroughSubject<Bool, Never>()
    private let resetsSubject = PassthroughSubject<Contact, Never>()
Bruno Muniz's avatar
Bruno Muniz committed
    private let requestsSubject = PassthroughSubject<Contact, Never>()
    private let messagesSubject = PassthroughSubject<Message, Never>()
    private let eventsSubject = PassthroughSubject<BackendEvent, Never>()
    private let requestsSentSubject = PassthroughSubject<Contact, Never>()
Bruno Muniz's avatar
Bruno Muniz committed
    private let confirmationsSubject = PassthroughSubject<Contact, Never>()
    private let transfersSubject = PassthroughSubject<FileTransfer, Never>()
Bruno Muniz's avatar
Bruno Muniz committed
    private let groupRequestsSubject = PassthroughSubject<(Group, [Data], String?), Never>()

    private var isBackupInitialization = false
    private var isBackupInitializationCompleted = false

Bruno Muniz's avatar
Bruno Muniz committed
    // MARK: Lifecycle

    init(
        _ bindings: BindingsInterface,
        fromBackup: Bool,
        email: String?,
        phone: String?
    ) {
Bruno Muniz's avatar
Bruno Muniz committed
        self.bindings = bindings
        self.isBackupInitialization = fromBackup
Bruno Muniz's avatar
Bruno Muniz committed

        do {
            try registerListenersAndStart()

            if fromBackup {
                try instantiateUserDiscoveryFromBackup(email: email, phone: phone)
            } else {
                try instantiateUserDiscovery()
            }

Bruno Muniz's avatar
Bruno Muniz committed
            try instantiateTransferManager()
            try instantiateDummyTrafficManager()
            updatePreImage()
        } catch {
            log(string: error.localizedDescription, type: .error)
        }
    }

Ahmed Shehata's avatar
Ahmed Shehata committed
    public func initializeBackup(passphrase: String) {
        backupManager = nil
Ahmed Shehata's avatar
Ahmed Shehata committed
        backupManager = bindings.initializeBackup(passphrase: passphrase) { [weak backupSubject] in
            backupSubject?.send($0)
        }
    }

Ahmed Shehata's avatar
Ahmed Shehata committed
    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 {
Bruno Muniz's avatar
Bruno Muniz committed
            fatalError("Trying to add json parameters to backup but no backup manager created yet")
        backupManager.addJson(string)
    }
Bruno Muniz's avatar
Bruno Muniz committed

    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) }

Bruno Muniz's avatar
Bruno Muniz committed
        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
Bruno Muniz's avatar
Bruno Muniz committed
            confirmationsSubject?.send($0)
        } _: { [weak resetsSubject] in
            resetsSubject?.send($0)
Bruno Muniz's avatar
Bruno Muniz committed
        }

        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)
Bruno Muniz's avatar
Bruno Muniz committed
        }

        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()
                )
            )
        }
Bruno Muniz's avatar
Bruno Muniz committed
    }

    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)
        }
    }

Bruno Muniz's avatar
Bruno Muniz committed
    private func instantiateDummyTrafficManager() throws {
        dummyManager = try bindings.generateDummyTraficManager()
    }

    private func updatePreImage() {
Ahmed Shehata's avatar
Ahmed Shehata committed
        if let defaults = UserDefaults(suiteName: "group.elixxir.messenger") {
Bruno Muniz's avatar
Bruno Muniz committed
            defaults.set(bindings.getPreImages(), forKey: "preImage")
        }
    }
}