Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
signalapp
GitHub Repository: signalapp/Signal-iOS
Path: blob/main/SignalServiceKit/Storage/RecipientIdFinder.swift
1 views
//
// Copyright 2019 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
//

import Foundation
public import LibSignalClient

public typealias RecipientUniqueId = String

public enum RecipientIdError: Error, IsRetryableProvider {
    /// We can't use the Pni because it's been replaced by an Aci.
    case mustNotUsePniBecauseAciExists

    public var isRetryableProvider: Bool {
        // Allow retries so that we send to the Aci instead of the Pni.
        return true
    }
}

public final class RecipientIdFinder {
    private let recipientDatabaseTable: RecipientDatabaseTable
    private let recipientFetcher: RecipientFetcher

    public init(
        recipientDatabaseTable: RecipientDatabaseTable,
        recipientFetcher: RecipientFetcher,
    ) {
        self.recipientFetcher = recipientFetcher
        self.recipientDatabaseTable = recipientDatabaseTable
    }

    public func recipientUniqueId(for serviceId: ServiceId, tx: DBReadTransaction) -> Result<RecipientUniqueId, RecipientIdError>? {
        guard let recipient = recipientDatabaseTable.fetchRecipient(serviceId: serviceId, transaction: tx) else {
            return nil
        }
        return validateRecipient(recipient, for: serviceId).map(\.uniqueId)
    }

    public func recipientUniqueId(for address: SignalServiceAddress, tx: DBReadTransaction) -> Result<RecipientUniqueId, RecipientIdError>? {
        guard let recipient = recipientDatabaseTable.fetchRecipient(address: address, tx: tx) else {
            return nil
        }
        return validateRecipient(recipient, for: address.serviceId).map(\.uniqueId)
    }

    public func ensureRecipientUniqueId(for serviceId: ServiceId, tx: DBWriteTransaction) -> Result<RecipientUniqueId, RecipientIdError> {
        return ensureRecipient(for: serviceId, tx: tx).map(\.uniqueId)
    }

    public func recipientId(for serviceId: ServiceId, tx: DBReadTransaction) -> Result<SignalRecipient.RowId, RecipientIdError>? {
        guard let recipient = recipientDatabaseTable.fetchRecipient(serviceId: serviceId, transaction: tx) else {
            return nil
        }
        return validateRecipient(recipient, for: serviceId).map(\.id)
    }

    public func recipientId(for address: SignalServiceAddress, tx: DBReadTransaction) -> Result<SignalRecipient.RowId, RecipientIdError>? {
        guard let recipient = recipientDatabaseTable.fetchRecipient(address: address, tx: tx) else {
            return nil
        }
        return validateRecipient(recipient, for: address.serviceId).map(\.id)
    }

    public func ensureRecipientId(for serviceId: ServiceId, tx: DBWriteTransaction) -> Result<SignalRecipient.RowId, RecipientIdError> {
        return ensureRecipient(for: serviceId, tx: tx).map(\.id)
    }

    public func ensureRecipient(for serviceId: ServiceId, tx: DBWriteTransaction) -> Result<SignalRecipient, RecipientIdError> {
        let recipient = recipientFetcher.fetchOrCreate(serviceId: serviceId, tx: tx)
        return validateRecipient(recipient, for: serviceId)
    }

    private func validateRecipient(
        _ recipient: SignalRecipient,
        for serviceId: ServiceId?,
    ) -> Result<SignalRecipient, RecipientIdError> {
        if serviceId is Pni, recipient.aciString != nil {
            return .failure(.mustNotUsePniBecauseAciExists)
        }
        return .success(recipient)
    }
}