TM-SGNL-iOS/SignalServiceKit/Contacts/OWSSyncManager.swift
TeleMessage developers dde0620daf initial commit
2025-05-03 12:28:28 -07:00

671 lines
30 KiB
Swift

//
// Copyright 2019 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
//
import Contacts
import Foundation
import LibSignalClient
extension Notification.Name {
public static let syncManagerConfigurationSyncDidComplete = Notification.Name("OWSSyncManagerConfigurationSyncDidCompleteNotification")
public static let syncManagerKeysSyncDidComplete = Notification.Name("OWSSyncManagerKeysSyncDidCompleteNotification")
}
@objc
public class OWSSyncManager: NSObject {
private static var keyValueStore: KeyValueStore {
KeyValueStore(collection: "kTSStorageManagerOWSSyncManagerCollection")
}
private let contactSyncQueue = SerialTaskQueue()
fileprivate let appReadiness: AppReadiness
public init(appReadiness: AppReadiness) {
self.appReadiness = appReadiness
super.init()
SwiftSingletons.register(self)
appReadiness.runNowOrWhenMainAppDidBecomeReadyAsync {
self.addObservers()
if TSAccountManagerObjcBridge.isRegisteredWithMaybeTransaction {
if TSAccountManagerObjcBridge.isPrimaryDeviceWithMaybeTransaction {
// syncAllContactsIfNecessary will skip if nothing has changed,
// so this won't yield redundant traffic.
self.syncAllContactsIfNecessary()
} else {
self.sendAllSyncRequestMessagesIfNecessary().catch { (_ error: Error) in
Logger.error("Error: \(error).")
}
}
}
}
}
private func addObservers() {
NotificationCenter.default.addObserver(self, selector: #selector(signalAccountsDidChange(_:)), name: .OWSContactsManagerSignalAccountsDidChange, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(registrationStateDidChange(_:)), name: RegistrationStateChangeNotifications.registrationStateDidChange, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(willEnterForeground(_:)), name: .OWSApplicationWillEnterForeground, object: nil)
}
// MARK: - Notifications
@objc
private func signalAccountsDidChange(_ notification: AnyObject) {
AssertIsOnMainThread()
syncAllContactsIfNecessary()
}
@objc
private func registrationStateDidChange(_ notification: AnyObject) {
AssertIsOnMainThread()
syncAllContactsIfNecessary()
}
@objc
private func willEnterForeground(_ notification: AnyObject) {
AssertIsOnMainThread()
_ = syncAllContactsIfFullSyncRequested()
}
}
extension OWSSyncManager: SyncManagerProtocolObjc {
// MARK: - Configuration Sync
private func _sendConfigurationSyncMessage(tx: SDSAnyWriteTransaction) {
let tsAccountManager = DependenciesBridge.shared.tsAccountManager
guard tsAccountManager.registrationState(tx: tx.asV2Read).isRegistered else {
return
}
guard let thread = TSContactThread.getOrCreateLocalThread(transaction: tx) else {
owsFailDebug("Missing thread.")
return
}
let linkPreviews = DependenciesBridge.shared.linkPreviewSettingStore.areLinkPreviewsEnabled(tx: tx.asV2Read)
let readReceipts = OWSReceiptManager.areReadReceiptsEnabled(transaction: tx)
let sealedSenderIndicators = SSKEnvironment.shared.preferencesRef.shouldShowUnidentifiedDeliveryIndicators(transaction: tx)
let typingIndicators = SSKEnvironment.shared.typingIndicatorsRef.areTypingIndicatorsEnabled()
let configurationSyncMessage = OWSSyncConfigurationMessage(
thread: thread,
readReceiptsEnabled: readReceipts,
showUnidentifiedDeliveryIndicators: sealedSenderIndicators,
showTypingIndicators: typingIndicators,
sendLinkPreviews: linkPreviews,
transaction: tx
)
let preparedMessage = PreparedOutgoingMessage.preprepared(
transientMessageWithoutAttachments: configurationSyncMessage
)
SSKEnvironment.shared.messageSenderJobQueueRef.add(message: preparedMessage, transaction: tx)
}
public func sendConfigurationSyncMessage() {
appReadiness.runNowOrWhenAppDidBecomeReadyAsync {
Task { await SSKEnvironment.shared.databaseStorageRef.awaitableWrite(block: self._sendConfigurationSyncMessage(tx:)) }
}
}
}
extension OWSSyncManager: SyncManagerProtocol, SyncManagerProtocolSwift {
// MARK: - Constants
private enum Constants {
static let lastContactSyncKey = "kTSStorageManagerOWSSyncManagerLastMessageKey"
static let fullSyncRequestIdKey = "FullSyncRequestId"
static let syncRequestedAppVersionKey = "SyncRequestedAppVersion"
}
// MARK: - Sync Requests
public func sendAllSyncRequestMessagesIfNecessary() -> Promise<Void> {
_sendAllSyncRequestMessages(onlyIfNecessary: true)
}
public func sendAllSyncRequestMessages(timeout: TimeInterval) -> Promise<Void> {
_sendAllSyncRequestMessages(onlyIfNecessary: false).timeout(seconds: timeout, substituteValue: ())
}
private func _sendAllSyncRequestMessages(onlyIfNecessary: Bool) -> Promise<Void> {
guard DependenciesBridge.shared.tsAccountManager.registrationStateWithMaybeSneakyTransaction.isRegistered else {
return Promise(error: OWSAssertionError("Unexpectedly tried to send sync request before registration."))
}
return SSKEnvironment.shared.databaseStorageRef.write(.promise) { (transaction) -> Promise<Void> in
let currentAppVersion = AppVersionImpl.shared.currentAppVersion
let syncRequestedAppVersion = {
Self.keyValueStore.getString(
Constants.syncRequestedAppVersionKey,
transaction: transaction.asV2Read
)
}
// If we don't need to send sync messages, don't send them.
if onlyIfNecessary, currentAppVersion == syncRequestedAppVersion() {
return .value(())
}
// Otherwise, send them & mark that we sent them for this app version.
self.sendSyncRequestMessage(.blocked, transaction: transaction)
self.sendSyncRequestMessage(.configuration, transaction: transaction)
self.sendSyncRequestMessage(.contacts, transaction: transaction)
self.sendSyncRequestMessage(.keys, transaction: transaction)
Self.keyValueStore.setString(
currentAppVersion,
key: Constants.syncRequestedAppVersionKey,
transaction: transaction.asV2Write
)
return Promise.when(fulfilled: [
NotificationCenter.default.observe(once: .incomingContactSyncDidComplete).asVoid(),
NotificationCenter.default.observe(once: .syncManagerConfigurationSyncDidComplete).asVoid(),
NotificationCenter.default.observe(once: BlockingManager.blockedSyncDidComplete).asVoid(),
NotificationCenter.default.observe(once: .syncManagerKeysSyncDidComplete).asVoid()
])
}.then(on: DependenciesBridge.shared.schedulers.sync) { $0 }
}
public func sendKeysSyncMessage() {
Logger.info("")
guard DependenciesBridge.shared.tsAccountManager.registrationStateWithMaybeSneakyTransaction.isRegistered else {
return owsFailDebug("Unexpectedly tried to send sync request before registration.")
}
guard DependenciesBridge.shared.tsAccountManager.registrationStateWithMaybeSneakyTransaction.isPrimaryDevice ?? false else {
return owsFailDebug("Keys sync should only be initiated from the primary device")
}
SSKEnvironment.shared.databaseStorageRef.asyncWrite { [weak self] transaction in
self?.sendKeysSyncMessage(tx: transaction)
}
}
public func sendKeysSyncMessage(tx: SDSAnyWriteTransaction) {
Logger.info("")
guard DependenciesBridge.shared.tsAccountManager.registrationState(tx: tx.asV2Write).isRegisteredPrimaryDevice else {
return owsFailDebug("Keys sync should only be initiated from the registered primary device")
}
guard let thread = TSContactThread.getOrCreateLocalThread(transaction: tx) else {
return owsFailDebug("Missing thread")
}
let storageServiceKey = DependenciesBridge.shared.svrKeyDeriver.data(for: .storageService, tx: tx.asV2Read)
let masterKey = DependenciesBridge.shared.svr.masterKeyDataForKeysSyncMessage(tx: tx.asV2Read)
let mrbk = DependenciesBridge.shared.mrbkStore.getOrGenerateMediaRootBackupKey(tx: tx.asV2Write)
let syncKeysMessage = OWSSyncKeysMessage(
thread: thread,
storageServiceKey: storageServiceKey?.rawData,
masterKey: masterKey,
mediaRootBackupKey: mrbk,
transaction: tx
)
let preparedMessage = PreparedOutgoingMessage.preprepared(
transientMessageWithoutAttachments: syncKeysMessage
)
SSKEnvironment.shared.messageSenderJobQueueRef.add(message: preparedMessage, transaction: tx)
}
public func processIncomingKeysSyncMessage(_ syncMessage: SSKProtoSyncMessageKeys, transaction: SDSAnyWriteTransaction) {
guard !DependenciesBridge.shared.tsAccountManager.registrationState(tx: transaction.asV2Read).isRegisteredPrimaryDevice else {
return owsFailDebug("Key sync messages should only be processed on linked devices")
}
if let masterKey = syncMessage.master {
DependenciesBridge.shared.svr.storeSyncedMasterKey(
data: masterKey,
authedDevice: .implicit,
updateStorageService: true,
transaction: transaction.asV2Write
)
}
try? DependenciesBridge.shared.mrbkStore.setMediaRootBackupKey(
fromKeysSyncMessage: syncMessage,
tx: transaction.asV2Write
)
transaction.addAsyncCompletionOffMain {
NotificationCenter.default.postNotificationNameAsync(.syncManagerKeysSyncDidComplete, object: nil)
}
}
public func sendKeysSyncRequestMessage(transaction: SDSAnyWriteTransaction) {
sendSyncRequestMessage(.keys, transaction: transaction)
}
public func processIncomingFetchLatestSyncMessage(
_ syncMessage: SSKProtoSyncMessageFetchLatest,
transaction: SDSAnyWriteTransaction
) {
switch syncMessage.unwrappedType {
case .unknown:
owsFailDebug("Unknown fetch latest type")
case .localProfile:
_ = SSKEnvironment.shared.profileManagerRef.fetchLocalUsersProfile(authedAccount: .implicit())
case .storageManifest:
SSKEnvironment.shared.storageServiceManagerRef.restoreOrCreateManifestIfNecessary(authedDevice: .implicit)
case .subscriptionStatus:
Logger.warn("Ignoring subscription status update fetch-latest sync message.")
}
}
public func processIncomingMessageRequestResponseSyncMessage(
_ syncMessage: SSKProtoSyncMessageMessageRequestResponse,
transaction: SDSAnyWriteTransaction
) {
guard let thread: TSThread = {
if let groupId = syncMessage.groupID {
return TSGroupThread.fetch(groupId: groupId, transaction: transaction)
}
if let threadAci = Aci.parseFrom(aciString: syncMessage.threadAci) {
return TSContactThread.getWithContactAddress(SignalServiceAddress(threadAci), transaction: transaction)
}
return nil
}() else {
return owsFailDebug("message request response couldn't find thread")
}
switch syncMessage.type {
case .accept:
SSKEnvironment.shared.blockingManagerRef.removeBlockedThread(thread, wasLocallyInitiated: false, transaction: transaction)
if let thread = thread as? TSContactThread {
/// When we accept a message request on a linked device,
/// we unhide the message sender. We will eventually also
/// learn about the unhide via a StorageService contact sync,
/// since the linked device should mark unhidden in
/// StorageService. But it doesn't hurt to get ahead of the
/// game and unhide here.
do {
try DependenciesBridge.shared.recipientHidingManager.removeHiddenRecipient(
thread.contactAddress,
wasLocallyInitiated: false,
tx: transaction.asV2Write
)
} catch {
owsFailDebug("Unable to unhide recipient after accept sync message")
}
}
SSKEnvironment.shared.profileManagerRef.addThread(
toProfileWhitelist: thread,
userProfileWriter: .localUser,
transaction: transaction
)
case .delete:
DependenciesBridge.shared.threadSoftDeleteManager.softDelete(
threads: [thread],
sendDeleteForMeSyncMessage: false,
tx: transaction.asV2Write
)
case .block:
SSKEnvironment.shared.blockingManagerRef.addBlockedThread(thread, blockMode: .remote, transaction: transaction)
case .blockAndDelete:
DependenciesBridge.shared.threadSoftDeleteManager.softDelete(
threads: [thread],
sendDeleteForMeSyncMessage: false,
tx: transaction.asV2Write
)
SSKEnvironment.shared.blockingManagerRef.addBlockedThread(thread, blockMode: .remote, transaction: transaction)
case .spam:
TSInfoMessage(thread: thread, messageType: .reportedSpam).anyInsert(transaction: transaction)
case .blockAndSpam:
SSKEnvironment.shared.blockingManagerRef.addBlockedThread(thread, blockMode: .remote, transaction: transaction)
TSInfoMessage(thread: thread, messageType: .reportedSpam).anyInsert(transaction: transaction)
case .unknown, .none:
owsFailDebug("unexpected message request response type")
}
}
public func sendMessageRequestResponseSyncMessage(thread: TSThread, responseType: OWSSyncMessageRequestResponseType) {
Logger.info("")
guard DependenciesBridge.shared.tsAccountManager.registrationStateWithMaybeSneakyTransaction.isRegistered else {
return owsFailDebug("Unexpectedly tried to send sync message before registration.")
}
SSKEnvironment.shared.databaseStorageRef.asyncWrite { [weak self] transaction in
self?.sendMessageRequestResponseSyncMessage(thread: thread, responseType: responseType, transaction: transaction)
}
}
public func sendMessageRequestResponseSyncMessage(
thread: TSThread,
responseType: OWSSyncMessageRequestResponseType,
transaction: SDSAnyWriteTransaction
) {
Logger.info("")
guard DependenciesBridge.shared.tsAccountManager.registrationState(tx: transaction.asV2Read).isRegistered else {
return owsFailDebug("Unexpectedly tried to send sync message before registration.")
}
let syncMessageRequestResponse = OWSSyncMessageRequestResponseMessage(thread: thread, responseType: responseType, transaction: transaction)
let preparedMessage = PreparedOutgoingMessage.preprepared(
transientMessageWithoutAttachments: syncMessageRequestResponse
)
SSKEnvironment.shared.messageSenderJobQueueRef.add(message: preparedMessage, transaction: transaction)
}
// MARK: - Contact Sync
public func syncAllContacts() -> Promise<Void> {
owsAssertDebug(canSendContactSyncMessage())
return syncContacts(mode: .allSignalAccounts)
}
func syncAllContactsIfNecessary() {
owsAssertDebug(CurrentAppContext().isMainApp)
_ = syncContacts(mode: .allSignalAccountsIfChanged)
}
public func syncAllContactsIfFullSyncRequested() -> Promise<Void> {
owsAssertDebug(CurrentAppContext().isMainApp)
return syncContacts(mode: .allSignalAccountsIfFullSyncRequested)
}
private enum ContactSyncMode {
case allSignalAccounts
case allSignalAccountsIfChanged
case allSignalAccountsIfFullSyncRequested
}
private func canSendContactSyncMessage() -> Bool {
guard appReadiness.isAppReady else {
return false
}
let tsAccountManager = DependenciesBridge.shared.tsAccountManager
guard tsAccountManager.registrationStateWithMaybeSneakyTransaction.isRegisteredPrimaryDevice else {
return false
}
return true
}
private func syncContacts(mode: ContactSyncMode) -> Promise<Void> {
if DebugFlags.dontSendContactOrGroupSyncMessages.get() {
Logger.info("Skipping contact sync message.")
return .value(())
}
guard canSendContactSyncMessage() else {
return Promise(error: OWSGenericError("Not ready to sync contacts."))
}
let syncTask = contactSyncQueue.enqueue {
return try await self._syncContacts(mode: mode)
}
return Promise.wrapAsync { try await syncTask.value }
}
private func _syncContacts(mode: ContactSyncMode) async throws {
// Don't bother sending sync messages with the same data as the last
// successfully sent contact sync message.
let opportunistic = mode == .allSignalAccountsIfChanged
if CurrentAppContext().isNSE {
// If a full sync is specifically requested in the NSE, mark it so that the
// main app can send that request the next time in runs.
if mode == .allSignalAccounts {
await SSKEnvironment.shared.databaseStorageRef.awaitableWrite { tx in
Self.keyValueStore.setString(UUID().uuidString, key: Constants.fullSyncRequestIdKey, transaction: tx.asV2Write)
}
}
// If a full sync sync is requested in NSE, ignore it. Opportunistic syncs
// shouldn't be requested, but this guards against cases where they are.
return
}
let recipientDatabaseTable = DependenciesBridge.shared.recipientDatabaseTable
let tsAccountManager = DependenciesBridge.shared.tsAccountManager
let (localIdentifiers, hasAnyLinkedDevice) = try SSKEnvironment.shared.databaseStorageRef.read { tx in
guard let localIdentifiers = tsAccountManager.localIdentifiers(tx: tx.asV2Read) else {
throw OWSError(error: .contactSyncFailed, description: "Not registered.", isRetryable: false)
}
let localRecipient = recipientDatabaseTable.fetchRecipient(serviceId: localIdentifiers.aci, transaction: tx.asV2Read)
return (localIdentifiers, (localRecipient?.deviceIds ?? []).count >= 2)
}
// Don't bother building the message if nobody will receive it. If a new
// device is linked, they will request a re-send.
guard hasAnyLinkedDevice else {
return
}
let thread = await SSKEnvironment.shared.databaseStorageRef.awaitableWrite { tx in
return TSContactThread.getOrCreateThread(withContactAddress: localIdentifiers.aciAddress, transaction: tx)
}
let result = try SSKEnvironment.shared.databaseStorageRef.read { tx in try buildContactSyncMessage(in: thread, mode: mode, tx: tx) }
guard let result else {
return
}
let messageHash: Data
do {
messageHash = try Cryptography.computeSHA256DigestOfFile(at: result.syncFileUrl)
} catch {
owsFailDebug("Error: \(error).")
throw OWSError(error: .contactSyncFailed, description: "Could not sync contacts.", isRetryable: false)
}
// If the NSE requested a sync and the main app does an opportunistic sync,
// we should send that request since we've been given a strong signal that
// someone is waiting to receive this message.
if opportunistic, result.fullSyncRequestId == nil, messageHash == result.previousMessageHash {
// Ignore redundant contacts sync message.
return
}
let dataSource = try DataSourcePath(fileUrl: result.syncFileUrl, shouldDeleteOnDeallocation: true)
try await SSKEnvironment.shared.messageSenderRef.sendTransientContactSyncAttachment(dataSource: dataSource, thread: thread)
await SSKEnvironment.shared.databaseStorageRef.awaitableWrite { tx in
Self.keyValueStore.setData(messageHash, key: Constants.lastContactSyncKey, transaction: tx.asV2Write)
self.clearFullSyncRequestId(ifMatches: result.fullSyncRequestId, tx: tx)
}
}
private struct BuildContactSyncMessageResult {
var syncFileUrl: URL
var fullSyncRequestId: String?
var previousMessageHash: Data?
}
private func buildContactSyncMessage(
in thread: TSThread,
mode: ContactSyncMode,
tx: SDSAnyReadTransaction
) throws -> BuildContactSyncMessageResult? {
// Check if there's a pending request from the NSE. Any full sync in the
// main app can clear this flag, even if it's not started in response to
// calling syncAllContactsIfFullSyncRequested.
let fullSyncRequestId = Self.keyValueStore.getString(Constants.fullSyncRequestIdKey, transaction: tx.asV2Read)
// However, only syncAllContactsIfFullSyncRequested-initiated requests
// should be skipped if there's no request.
if mode == .allSignalAccountsIfFullSyncRequested, fullSyncRequestId == nil {
return nil
}
guard let syncFileUrl = ContactSyncAttachmentBuilder.buildAttachmentFile(
contactsManager: SSKEnvironment.shared.contactManagerImplRef,
tx: tx
) else {
owsFailDebug("Failed to serialize contacts sync message.")
throw OWSError(error: .contactSyncFailed, description: "Could not sync contacts.", isRetryable: false)
}
return BuildContactSyncMessageResult(
syncFileUrl: syncFileUrl,
fullSyncRequestId: fullSyncRequestId,
previousMessageHash: Self.keyValueStore.getData(Constants.lastContactSyncKey, transaction: tx.asV2Read)
)
}
private func clearFullSyncRequestId(ifMatches requestId: String?, tx: SDSAnyWriteTransaction) {
guard let requestId else {
return
}
let storedRequestId = Self.keyValueStore.getString(Constants.fullSyncRequestIdKey, transaction: tx.asV2Read)
// If the requestId we just finished matches the one in the database, we've
// fulfilled the contract with the NSE. If the NSE triggers *another* sync
// while this is outstanding, the match will fail, and we'll kick off
// another sync at the next opportunity.
if storedRequestId == requestId {
Self.keyValueStore.removeValue(forKey: Constants.fullSyncRequestIdKey, transaction: tx.asV2Write)
}
}
// MARK: - Fetch Latest
public func sendFetchLatestProfileSyncMessage(tx: SDSAnyWriteTransaction) {
_sendFetchLatestSyncMessage(type: .localProfile, tx: tx)
}
public func sendFetchLatestStorageManifestSyncMessage() async {
await SSKEnvironment.shared.databaseStorageRef.awaitableWrite { tx in self._sendFetchLatestSyncMessage(type: .storageManifest, tx: tx) }
}
public func sendFetchLatestSubscriptionStatusSyncMessage() { sendFetchLatestSyncMessage(type: .subscriptionStatus) }
private func sendFetchLatestSyncMessage(type: OWSSyncFetchType) {
Task { await SSKEnvironment.shared.databaseStorageRef.awaitableWrite { tx in self._sendFetchLatestSyncMessage(type: type, tx: tx) } }
}
private func _sendFetchLatestSyncMessage(type: OWSSyncFetchType, tx: SDSAnyWriteTransaction) {
let tsAccountManager = DependenciesBridge.shared.tsAccountManager
guard tsAccountManager.registrationState(tx: tx.asV2Read).isRegistered else {
owsFailDebug("Tried to send sync message before registration.")
return
}
guard let thread = TSContactThread.getOrCreateLocalThread(transaction: tx) else {
owsFailDebug("Missing thread.")
return
}
let fetchLatestSyncMessage = OWSSyncFetchLatestMessage(thread: thread, fetchType: type, transaction: tx)
let preparedMessage = PreparedOutgoingMessage.preprepared(
transientMessageWithoutAttachments: fetchLatestSyncMessage
)
SSKEnvironment.shared.messageSenderJobQueueRef.add(message: preparedMessage, transaction: tx)
}
public func processIncomingConfigurationSyncMessage(_ syncMessage: SSKProtoSyncMessageConfiguration, transaction: SDSAnyWriteTransaction) {
if syncMessage.hasReadReceipts {
SSKEnvironment.shared.receiptManagerRef.setAreReadReceiptsEnabled(syncMessage.readReceipts, transaction: transaction)
}
if syncMessage.hasUnidentifiedDeliveryIndicators {
let updatedValue = syncMessage.unidentifiedDeliveryIndicators
SSKEnvironment.shared.preferencesRef.setShouldShowUnidentifiedDeliveryIndicators(updatedValue, transaction: transaction)
}
if syncMessage.hasTypingIndicators {
SSKEnvironment.shared.typingIndicatorsRef.setTypingIndicatorsEnabled(value: syncMessage.typingIndicators, transaction: transaction)
}
if syncMessage.hasLinkPreviews {
let linkPreviewSettingStore = DependenciesBridge.shared.linkPreviewSettingStore
linkPreviewSettingStore.setAreLinkPreviewsEnabled(syncMessage.linkPreviews, tx: transaction.asV2Write)
}
transaction.addAsyncCompletionOffMain {
NotificationCenter.default.postNotificationNameAsync(.syncManagerConfigurationSyncDidComplete, object: nil)
}
}
public func processIncomingContactsSyncMessage(_ syncMessage: SSKProtoSyncMessageContacts, transaction: SDSAnyWriteTransaction) {
guard
syncMessage.blob.hasCdnNumber,
let cdnKey = syncMessage.blob.cdnKey?.nilIfEmpty,
let encryptionKey = syncMessage.blob.key?.nilIfEmpty,
let digest = syncMessage.blob.digest?.nilIfEmpty,
syncMessage.blob.hasSize
else {
owsFailDebug("failed to create attachment download info from incoming contacts sync message")
return
}
SSKEnvironment.shared.smJobQueuesRef.incomingContactSyncJobQueue.add(
cdnNumber: syncMessage.blob.cdnNumber,
cdnKey: cdnKey,
encryptionKey: encryptionKey,
digest: digest,
plaintextLength: syncMessage.blob.size,
isComplete: syncMessage.isComplete,
tx: transaction
)
}
public func sendInitialSyncRequestsAwaitingCreatedThreadOrdering(timeoutSeconds: TimeInterval) -> Promise<[String]> {
Logger.info("")
guard DependenciesBridge.shared.tsAccountManager.registrationStateWithMaybeSneakyTransaction.isRegistered else {
return Promise(error: OWSAssertionError("Unexpectedly tried to send sync request before registration."))
}
SSKEnvironment.shared.databaseStorageRef.asyncWrite { transaction in
self.sendSyncRequestMessage(.blocked, transaction: transaction)
self.sendSyncRequestMessage(.configuration, transaction: transaction)
self.sendSyncRequestMessage(.contacts, transaction: transaction)
}
let notificationsPromise: Promise<([(threadUniqueId: String, sortOrder: UInt32)], Void, Void)> = Promise.when(fulfilled:
NotificationCenter.default.observe(once: .incomingContactSyncDidComplete).map { $0.insertedThreads }.timeout(seconds: timeoutSeconds, substituteValue: []),
NotificationCenter.default.observe(once: .syncManagerConfigurationSyncDidComplete).asVoid().timeout(seconds: timeoutSeconds),
NotificationCenter.default.observe(once: BlockingManager.blockedSyncDidComplete).asVoid().timeout(seconds: timeoutSeconds)
)
return notificationsPromise.map { (insertedThreads, _, _) -> [String] in
return insertedThreads.sorted(by: { $0.sortOrder < $1.sortOrder }).map({ $0.threadUniqueId })
}
}
private func sendSyncRequestMessage(
_ requestType: SSKProtoSyncMessageRequestType,
transaction: SDSAnyWriteTransaction
) {
switch requestType {
case .unknown:
owsFailDebug("should not request unknown")
case .contacts:
Logger.info("contacts")
case .blocked:
Logger.info("blocked")
case .configuration:
Logger.info("configuration")
case .keys:
Logger.info("keys")
}
guard DependenciesBridge.shared.tsAccountManager.registrationStateWithMaybeSneakyTransaction.isRegistered else {
return owsFailDebug("Unexpectedly tried to send sync request before registration.")
}
guard !DependenciesBridge.shared.tsAccountManager.registrationStateWithMaybeSneakyTransaction.isRegisteredPrimaryDevice else {
return owsFailDebug("Sync request should only be sent from a linked device")
}
guard let thread = TSContactThread.getOrCreateLocalThread(transaction: transaction) else {
return owsFailDebug("Missing thread")
}
let syncRequestMessage = OWSSyncRequestMessage(thread: thread, requestType: requestType.rawValue, transaction: transaction)
let preparedMessage = PreparedOutgoingMessage.preprepared(
transientMessageWithoutAttachments: syncRequestMessage
)
SSKEnvironment.shared.messageSenderJobQueueRef.add(message: preparedMessage, transaction: transaction)
}
}
// MARK: -
private extension Notification {
var insertedThreads: [(threadUniqueId: String, sortOrder: UInt32)] {
return userInfo?[IncomingContactSyncJobQueue.Constants.insertedThreads] as! [(threadUniqueId: String, sortOrder: UInt32)]
}
}