Skip to content
Snippets Groups Projects
Session+Contacts.swift 10 KiB
Newer Older
Bruno Muniz's avatar
Bruno Muniz committed
import Retry
import Models
import Shared
Bruno Muniz's avatar
Bruno Muniz committed
import Foundation

extension Session {
    public func getId(from data: Data) -> Data? {
        client.bindings.getId(from: data)
    }

    public func verify(contact: Contact) {
        log(string: "Requested verification of \(contact.username)", type: .crumbs)

        var contact = contact
        contact.status = .verificationInProgress

        do {
            contact = try dbManager.save(contact)
        } catch {
            log(string: "Failed to store contact request upon verification. Returning, request will be abandoned to not crash", type: .error)
        }

        retry(max: 4, retryStrategy: .delay(seconds: 1)) { [weak self] in
            if self?.networkMonitor.xxStatus != .available {
                log(string: "Network is not available yet for ownership. Retrying in 1 second...", type: .error)
                throw NSError.create("")
            }
        }.finalCatch { error in
            log(string: "Failed to verify contact cause network wasn't available at all", type: .crumbs)
            return
        }

        log(string: "Network is available. Verifying \(contact.username)", type: .crumbs)

        let resultClosure: (Result<Contact, Error>) -> Void = { result in
            switch result {
            case .success(let mightBe):
                guard try! self.client.bindings.verify(marshaled: contact.marshaled, verifiedMarshaled: mightBe.marshaled) else {
                    log(string: "\(contact.username) is fake. Deleted!", type: .info)

                    do {
                        try self.dbManager.delete(contact)
                    } catch {
                        log(string: error.localizedDescription, type: .error)
                    }

                    return
                }

                contact.status = .verified
                log(string: "Verified \(contact.username)", type: .info)

                do {
                    try self.dbManager.save(contact)
                } catch {
                    log(string: error.localizedDescription, type: .error)
                }

            case .failure(let error):
                log(string: "Verification of \(contact.username) failed: \(error.localizedDescription)", type: .error)
                contact.status = .verificationFailed

                do {
                    try self.dbManager.save(contact)
                } catch {
                    log(string: error.localizedDescription, type: .error)
                }
            }
        }

        let ud = client.userDiscovery!

        let hasEmail = contact.email != nil
        let hasPhone = contact.phone != nil

        guard hasEmail || hasPhone else {
Bruno Muniz's avatar
Bruno Muniz committed
            ud.lookup(forUserId: contact.userId, resultClosure)
            return
        }

        var fact: String

        if hasEmail {
            fact = "\(FactType.email.prefix)\(contact.email!)"
        } else {
            fact = "\(FactType.phone.prefix)\(contact.phone!)"
        }

Bruno Muniz's avatar
Bruno Muniz committed
        do {
            try ud.search(fact: fact, resultClosure)
        } catch {
            log(string: error.localizedDescription, type: .error)
            contact.status = .verificationFailed

            do {
                try self.dbManager.save(contact)
            } catch {
                log(string: error.localizedDescription, type: .error)
            }
        }
    }

    public func retryRequest(_ contact: Contact) throws {
        log(string: "Retrying to request a contact", type: .info)

        client.bindings.add(contact.marshaled, from: myQR) { [weak self, contact] in
            var contact = contact
            guard let self = self else { return }

            do {
                switch $0 {
                case .success:
                    log(string: "Retrying to request a contact -- Success", type: .info)
                    contact.status = .requested
                case .failure(let error):
                    log(string: "Retrying to request a contact -- Failed: \(error.localizedDescription)", type: .error)
                    contact.createdAt = Date()
                }

                _ = try self.dbManager.save(contact)
            } catch {
                log(string: error.localizedDescription, type: .error)
            }
        }
    }

    public func add(_ contact: Contact) throws {
        guard contact.username != username else {
            throw NSError.create("You can't add yourself")
        }

        var contactToOperate: Contact!

        if contact.status == .requestFailed || contact.status == .confirmationFailed {
            contactToOperate = contact
        } else {
            guard (try? dbManager.fetch(.withUsername(contact.username)).first as Contact?) == nil else {
                throw NSError.create("This user has already been requested")
            }

            contactToOperate = try dbManager.save(contact)
        }

        guard contactToOperate.status != .confirmationFailed else {
            contactToOperate.createdAt = Date()
            try confirm(contact)
            return
        }

        contactToOperate.status = .requesting

        let myself = client.bindings.meMarshalled(
            username!,
            email: isSharingEmail ? email : nil,
            phone: isSharingPhone ? phone : nil
        )

        client.bindings.add(contactToOperate.marshaled, from: myself) { [weak self, contactToOperate] in
Bruno Muniz's avatar
Bruno Muniz committed
            guard let self = self, var contactToOperate = contactToOperate else { return }
            let safeName = contactToOperate.nickname ?? contactToOperate.username
            let title = "\(safeName.prefix(2))...\(safeName.suffix(3))"

            do {
                switch $0 {
                case .success(let success):
                    contactToOperate.status = success ? .requested : .requestFailed
                    contactToOperate = try self.dbManager.save(contactToOperate)

                    log(string: "Successfully added \(title)", type: .info)
                case .failure(let error):
                    contactToOperate.status = .requestFailed
                    contactToOperate.createdAt = Date()
                    contactToOperate = try self.dbManager.save(contactToOperate)

                    log(string: "Failed when adding \(title):\n\(error.localizedDescription)", type: .error)

                    self.toastController.enqueueToast(model: .init(
                        title: Localized.Requests.Failed.toast(contactToOperate.nickname ?? contact.username),
                        color: Asset.accentDanger.color,
                        leftImage: Asset.requestFailedToaster.image
                    ))
Bruno Muniz's avatar
Bruno Muniz committed
                }
            } catch {
                log(string: "Error adding \(title):\n\(error.localizedDescription)", type: .error)
            }
        }
    }

    public func confirm(_ contact: Contact) throws {
        var contact = contact
        contact.status = .confirming
        contact = try dbManager.save(contact)

        client.bindings.confirm(contact.marshaled) { [weak self] in
            let safeName = contact.nickname ?? contact.username
            let title = "\(safeName.prefix(2))...\(safeName.suffix(3))"

            switch $0 {
            case .success(let confirmed):
Ahmed Shehata's avatar
Ahmed Shehata committed
                contact.isRecent = true
                contact.createdAt = Date()
Bruno Muniz's avatar
Bruno Muniz committed
                contact.status = confirmed ? .friend : .confirmationFailed
                log(string: "Confirming request from \(title) = \(confirmed)", type: confirmed ? .info : .error)
            case .failure(let error):
                contact.status = .confirmationFailed
                log(string: "Error confirming request from \(title):\n\(error.localizedDescription)", type: .error)
            }

            _ = try? self?.dbManager.save(contact)
        }
    }

    public func update(_ contact: Contact) {
        do {
            if var stored = try dbManager.fetch(.withUsername(contact.username)).first as Contact? {
                stored.email = contact.email
                stored.photo = contact.photo
                stored.phone = contact.phone
                stored.nickname = contact.nickname
Ahmed Shehata's avatar
Ahmed Shehata committed
                stored.isRecent = contact.isRecent
                stored.createdAt = contact.createdAt
Bruno Muniz's avatar
Bruno Muniz committed
                try dbManager.save(stored)

                try dbManager.updateAll(
                    GroupMember.self,
                    GroupMember.Request.withUserId(stored.userId),
                    with: [GroupMember.Column.photo.set(to: stored.photo)]
                )
            }
        } catch {
            log(string: "Error updating a contact: \(error.localizedDescription)", type: .error)
        }
    }

    public func delete<T: Persistable>(_ model: T, isRequest: Bool = false) {
        log(string: "Deleting a model...", type: .info)

        do {
            try dbManager.delete(model)
        } catch {
            log(string: "Error deleting a model: \(error.localizedDescription)", type: .error)
        }
    }

    public func find(by username: String) -> Contact? {
        log(string: "Trying to find contact with username: \(username)", type: .info)

        do {
            if let contact: Contact = try dbManager.fetch(.withUsername(username)).first {
                log(string: "Found \(username)!", type: .info)
                return contact
            } else {
                log(string: "No such contact with username: \(username)", type: .info)
                return nil
            }
        } catch {
            log(string: "Error trying to find a contact: \(error.localizedDescription)", type: .error)
        }

        return nil
    }

    public func deleteContact(_ contact: Contact) throws {
        if let _: FileTransfer = try? dbManager.fetch(.withContactId(contact.userId)).first {
            throw NSError.create("There is an ongoing file transfer with this contact as you are receiving or sending a file, please try again later once it’s done")
        } else {
            print("No pending transfer with this contact. Free to delete")
        }

        try client.bindings.removeContact(contact.marshaled)
        try dbManager.delete(contact)
    }
}