Signal-iOS/SignalMessaging/contacts/OWSSyncManager.swift
2021-03-25 09:24:27 -03:00

235 lines
10 KiB
Swift

//
// Copyright (c) 2021 Open Whisper Systems. All rights reserved.
//
import Foundation
import PromiseKit
extension OWSSyncManager: SyncManagerProtocolSwift {
// MARK: - Sync Requests
@objc
public func sendAllSyncRequestMessages() -> AnyPromise {
return AnyPromise(_sendAllSyncRequestMessages())
}
@objc
public func sendAllSyncRequestMessages(timeout: TimeInterval) -> AnyPromise {
return AnyPromise(_sendAllSyncRequestMessages()
.timeout(seconds: timeout, substituteValue: ()))
}
private func _sendAllSyncRequestMessages() -> Promise<Void> {
Logger.info("")
guard tsAccountManager.isRegisteredAndReady else {
return Promise(error: OWSAssertionError("Unexpectedly tried to send sync request before registration."))
}
databaseStorage.asyncWrite { transaction in
self.sendSyncRequestMessage(.blocked, transaction: transaction)
self.sendSyncRequestMessage(.configuration, transaction: transaction)
self.sendSyncRequestMessage(.groups, transaction: transaction)
self.sendSyncRequestMessage(.contacts, transaction: transaction)
self.sendSyncRequestMessage(.keys, transaction: transaction)
}
return when(fulfilled: [
NotificationCenter.default.observe(once: .IncomingContactSyncDidComplete).asVoid(),
NotificationCenter.default.observe(once: .IncomingGroupSyncDidComplete).asVoid(),
NotificationCenter.default.observe(once: .OWSSyncManagerConfigurationSyncDidComplete).asVoid(),
NotificationCenter.default.observe(once: .OWSBlockingManagerBlockedSyncDidComplete).asVoid(),
NotificationCenter.default.observe(once: .OWSSyncManagerKeysSyncDidComplete).asVoid()
])
}
@objc
public func sendKeysSyncMessage() {
Logger.info("")
guard tsAccountManager.isRegisteredAndReady else {
return owsFailDebug("Unexpectedly tried to send sync request before registration.")
}
guard tsAccountManager.isRegisteredPrimaryDevice else {
return owsFailDebug("Keys sync should only be initiated from the primary device")
}
databaseStorage.asyncWrite { [weak self] transaction in
guard let self = self else { return }
guard let thread = TSAccountManager.getOrCreateLocalThread(transaction: transaction) else {
return owsFailDebug("Missing thread")
}
let syncKeysMessage = OWSSyncKeysMessage(thread: thread, storageServiceKey: KeyBackupService.DerivedKey.storageService.data)
self.messageSenderJobQueue.add(message: syncKeysMessage.asPreparer, transaction: transaction)
}
}
@objc
public func processIncomingKeysSyncMessage(_ syncMessage: SSKProtoSyncMessageKeys, transaction: SDSAnyWriteTransaction) {
guard !tsAccountManager.isRegisteredPrimaryDevice else {
return owsFailDebug("Key sync messages should only be processed on linked devices")
}
KeyBackupService.storeSyncedKey(type: .storageService, data: syncMessage.storageService, transaction: transaction)
transaction.addAsyncCompletion {
NotificationCenter.default.postNotificationNameAsync(.OWSSyncManagerKeysSyncDidComplete, object: nil)
}
}
@objc
public func sendKeysSyncRequestMessage(transaction: SDSAnyWriteTransaction) {
sendSyncRequestMessage(.keys, transaction: transaction)
}
@objc
public func processIncomingMessageRequestResponseSyncMessage(
_ syncMessage: SSKProtoSyncMessageMessageRequestResponse,
transaction: SDSAnyWriteTransaction
) {
let thread: TSThread
if let groupId = syncMessage.groupID {
TSGroupThread.ensureGroupIdMapping(forGroupId: groupId, transaction: transaction)
guard let groupThread = TSGroupThread.fetch(groupId: groupId,
transaction: transaction) else {
return owsFailDebug("message request response for missing group thread")
}
thread = groupThread
} else if let threadAddress = syncMessage.threadAddress {
guard let contactThread = TSContactThread.getWithContactAddress(threadAddress, transaction: transaction) else {
return owsFailDebug("message request response for missing thread")
}
thread = contactThread
} else {
return owsFailDebug("message request response missing group or contact thread information")
}
guard let type = syncMessage.type else { return owsFailDebug("messasge request response missing type") }
switch type {
case .accept:
blockingManager.removeBlockedThread(thread, wasLocallyInitiated: false, transaction: transaction)
profileManager.addThread(toProfileWhitelist: thread, transaction: transaction)
case .delete:
thread.softDelete(with: transaction)
case .block:
blockingManager.addBlockedThread(thread, blockMode: .remote, transaction: transaction)
case .blockAndDelete:
thread.softDelete(with: transaction)
blockingManager.addBlockedThread(thread, blockMode: .remote, transaction: transaction)
case .unknown:
owsFailDebug("unexpected message request response type")
}
}
@objc
public func sendMessageRequestResponseSyncMessage(thread: TSThread, responseType: OWSSyncMessageRequestResponseType) {
Logger.info("")
guard tsAccountManager.isRegisteredAndReady else {
return owsFailDebug("Unexpectedly tried to send sync message before registration.")
}
databaseStorage.asyncWrite { [weak self] transaction in
self?.sendMessageRequestResponseSyncMessage(thread: thread, responseType: responseType, transaction: transaction)
}
}
@objc
public func sendMessageRequestResponseSyncMessage(
thread: TSThread,
responseType: OWSSyncMessageRequestResponseType,
transaction: SDSAnyWriteTransaction
) {
Logger.info("")
guard tsAccountManager.isRegisteredAndReady else {
return owsFailDebug("Unexpectedly tried to send sync message before registration.")
}
let syncMessageRequestResponse = OWSSyncMessageRequestResponseMessage(thread: thread, responseType: responseType)
messageSenderJobQueue.add(message: syncMessageRequestResponse.asPreparer, transaction: transaction)
}
}
// MARK: -
public extension OWSSyncManager {
func sendInitialSyncRequestsAwaitingCreatedThreadOrdering(timeoutSeconds: TimeInterval) -> Promise<[String]> {
Logger.info("")
guard tsAccountManager.isRegisteredAndReady else {
return Promise(error: OWSAssertionError("Unexpectedly tried to send sync request before registration."))
}
databaseStorage.asyncWrite { transaction in
self.sendSyncRequestMessage(.blocked, transaction: transaction)
self.sendSyncRequestMessage(.configuration, transaction: transaction)
self.sendSyncRequestMessage(.groups, transaction: transaction)
self.sendSyncRequestMessage(.contacts, transaction: transaction)
}
let notificationsPromise: Promise<([(threadId: String, sortOrder: UInt32)], [(threadId: String, sortOrder: UInt32)], Void, Void)> = when(fulfilled:
NotificationCenter.default.observe(once: .IncomingContactSyncDidComplete).map { $0.newThreads }.timeout(seconds: timeoutSeconds, substituteValue: []),
NotificationCenter.default.observe(once: .IncomingGroupSyncDidComplete).map { $0.newThreads }.timeout(seconds: timeoutSeconds, substituteValue: []),
NotificationCenter.default.observe(once: .OWSSyncManagerConfigurationSyncDidComplete).asVoid().timeout(seconds: timeoutSeconds),
NotificationCenter.default.observe(once: .OWSBlockingManagerBlockedSyncDidComplete).asVoid().timeout(seconds: timeoutSeconds)
)
return notificationsPromise.map { (newContactThreads, newGroupThreads, _, _) -> [String] in
var newThreads: [String: UInt32] = [:]
for newThread in newContactThreads {
assert(newThreads[newThread.threadId] == nil)
newThreads[newThread.threadId] = newThread.sortOrder
}
for newThread in newGroupThreads {
assert(newThreads[newThread.threadId] == nil)
newThreads[newThread.threadId] = newThread.sortOrder
}
return newThreads.sorted { (lhs: (key: String, value: UInt32), rhs: (key: String, value: UInt32)) -> Bool in
lhs.value < rhs.value
}.map { $0.key }
}
}
fileprivate func sendSyncRequestMessage(_ requestType: OWSSyncRequestType, transaction: SDSAnyWriteTransaction) {
Logger.info("")
guard tsAccountManager.isRegisteredAndReady else {
return owsFailDebug("Unexpectedly tried to send sync request before registration.")
}
guard !tsAccountManager.isRegisteredPrimaryDevice else {
return owsFailDebug("Sync request should only be sent from a linked device")
}
guard let thread = TSAccountManager.getOrCreateLocalThread(transaction: transaction) else {
return owsFailDebug("Missing thread")
}
let syncRequestMessage = OWSSyncRequestMessage(thread: thread, requestType: requestType)
messageSenderJobQueue.add(message: syncRequestMessage.asPreparer, transaction: transaction)
}
}
private extension Notification {
var newThreads: [(threadId: String, sortOrder: UInt32)] {
switch self.object {
case let groupSync as IncomingGroupSyncOperation:
return groupSync.newThreads
case let contactSync as IncomingContactSyncOperation:
return contactSync.newThreads
default:
owsFailDebug("unexpected object: \(String(describing: self.object))")
return []
}
}
}