Signal-iOS/SignalMessaging/environment/AppSetup.swift

356 lines
15 KiB
Swift

//
// Copyright 2023 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
//
import Foundation
import SignalCoreKit
import SignalServiceKit
public class AppSetup {
public init() {}
public func start(
appContext: AppContext,
appVersion: AppVersion,
paymentsEvents: PaymentsEvents,
mobileCoinHelper: MobileCoinHelper,
webSocketFactory: WebSocketFactory,
callMessageHandler: OWSCallMessageHandler,
notificationPresenter: NotificationsProtocol
) -> AppSetup.DatabaseContinuation {
configureUnsatisfiableConstraintLogging()
let sleepBlockObject = NSObject()
DeviceSleepManager.shared.addBlock(blockObject: sleepBlockObject)
let backgroundTask = OWSBackgroundTask(label: #function)
// Order matters here.
//
// All of these "singletons" should have any dependencies used in their
// initializers injected.
OWSBackgroundTaskManager.shared().observeNotifications()
let storageCoordinator = StorageCoordinator()
Logger.info("hasGrdbFile: \(StorageCoordinator.hasGrdbFile)")
let databaseStorage = storageCoordinator.nonGlobalDatabaseStorage
// AFNetworking (via CFNetworking) spools its attachments in
// NSTemporaryDirectory(). If you receive a media message while the device
// is locked, the download will fail if the temporary directory is
// NSFileProtectionComplete.
let temporaryDirectory = NSTemporaryDirectory()
owsAssert(OWSFileSystem.ensureDirectoryExists(temporaryDirectory))
owsAssert(OWSFileSystem.protectFileOrFolder(atPath: temporaryDirectory, fileProtectionType: .completeUntilFirstUserAuthentication))
// MARK: DependenciesBridge
let accountServiceClient = AccountServiceClient()
let aciSignalProtocolStore = SignalProtocolStoreImpl(for: .aci)
let dateProvider = Date.provider
let groupsV2 = GroupsV2Impl()
let identityManager = OWSIdentityManager(databaseStorage: databaseStorage)
let messageProcessor = MessageProcessor()
let messageSender = MessageSender()
let modelReadCaches = ModelReadCaches(factory: ModelReadCacheFactory())
let networkManager = NetworkManager()
let ows2FAManager = OWS2FAManager()
let pniSignalProtocolStore = SignalProtocolStoreImpl(for: .pni)
let profileManager = OWSProfileManager(databaseStorage: databaseStorage)
let signalProtocolStoreManager = SignalProtocolStoreManagerImpl(
aciProtocolStore: aciSignalProtocolStore,
pniProtocolStore: pniSignalProtocolStore
)
let signalService = OWSSignalService()
let signalServiceAddressCache = SignalServiceAddressCache()
let storageServiceManager = StorageServiceManagerImpl.shared
let syncManager = OWSSyncManager(default: ())
let tsAccountManager = TSAccountManager()
let dependenciesBridge = DependenciesBridge.setupSingleton(
accountServiceClient: accountServiceClient,
appVersion: appVersion,
databaseStorage: databaseStorage,
dateProvider: dateProvider,
groupsV2: groupsV2,
identityManager: identityManager,
messageProcessor: messageProcessor,
messageSender: messageSender,
modelReadCaches: modelReadCaches,
networkManager: networkManager,
notificationsManager: notificationPresenter,
ows2FAManager: ows2FAManager,
profileManager: profileManager,
signalProtocolStoreManager: signalProtocolStoreManager,
signalService: signalService,
signalServiceAddressCache: signalServiceAddressCache,
storageServiceManager: storageServiceManager,
syncManager: syncManager,
tsAccountManager: tsAccountManager,
websocketFactory: webSocketFactory
)
// MARK: SignalMessaging environment properties
let preferences = Preferences()
let proximityMonitoringManager = OWSProximityMonitoringManagerImpl()
let avatarBuilder = AvatarBuilder()
let smJobQueues = SignalMessagingJobQueues()
// MARK: SSK environment properties
let appExpiry = DependenciesBridge.shared.appExpiry
let contactsManager = OWSContactsManager(swiftValues: .makeWithValuesFromDependenciesBridge())
let linkPreviewManager = OWSLinkPreviewManager()
let pendingReceiptRecorder = MessageRequestPendingReceipts()
let messageManager = OWSMessageManager()
let blockingManager = BlockingManager()
let remoteConfigManager = ServiceRemoteConfigManager(
appExpiry: appExpiry,
db: DependenciesBridge.shared.db,
keyValueStoreFactory: DependenciesBridge.shared.keyValueStoreFactory,
tsAccountManager: tsAccountManager,
serviceClient: SignalServiceRestClient.shared
)
let udManager = OWSUDManagerImpl()
let messageDecrypter = OWSMessageDecrypter()
let groupsV2MessageProcessor = GroupsV2MessageProcessor()
let socketManager = SocketManager(appExpiry: appExpiry, db: DependenciesBridge.shared.db)
let disappearingMessagesJob = OWSDisappearingMessagesJob()
let receiptManager = OWSReceiptManager()
let outgoingReceiptManager = OWSOutgoingReceiptManager()
let reachabilityManager = SSKReachabilityManagerImpl()
let typingIndicators = TypingIndicatorsImpl()
let attachmentDownloads = OWSAttachmentDownloads()
let stickerManager = StickerManager()
let sskPreferences = SSKPreferences()
let groupV2Updates = GroupV2UpdatesImpl()
let messageFetcherJob = MessageFetcherJob()
let bulkProfileFetch = BulkProfileFetch()
let versionedProfiles = VersionedProfilesImpl()
let earlyMessageManager = EarlyMessageManager()
let messagePipelineSupervisor = MessagePipelineSupervisor()
let paymentsHelper = PaymentsHelperImpl()
let paymentsCurrencies = PaymentsCurrenciesImpl()
let spamChallengeResolver = SpamChallengeResolver()
let senderKeyStore = SenderKeyStore()
let phoneNumberUtil = PhoneNumberUtil()
let legacyChangePhoneNumber = LegacyChangePhoneNumber()
let subscriptionManager = SubscriptionManagerImpl()
let systemStoryManager = SystemStoryManager()
let remoteMegaphoneFetcher = RemoteMegaphoneFetcher()
let sskJobQueues = SSKJobQueues()
let contactDiscoveryManager = ContactDiscoveryManagerImpl(
db: dependenciesBridge.db,
recipientFetcher: dependenciesBridge.recipientFetcher,
recipientMerger: dependenciesBridge.recipientMerger,
tsAccountManager: tsAccountManager,
websocketFactory: webSocketFactory
)
let messageSendLog = MessageSendLog(
databaseStorage: databaseStorage,
dateProvider: { Date() }
)
let smEnvironment = SMEnvironment(
preferences: preferences,
proximityMonitoringManager: proximityMonitoringManager,
avatarBuilder: avatarBuilder,
smJobQueues: smJobQueues
)
SMEnvironment.setShared(smEnvironment)
let sskEnvironment = SSKEnvironment(
contactsManager: contactsManager,
linkPreviewManager: linkPreviewManager,
messageSender: messageSender,
pendingReceiptRecorder: pendingReceiptRecorder,
profileManager: profileManager,
networkManager: networkManager,
messageManager: messageManager,
blockingManager: blockingManager,
identityManager: identityManager,
remoteConfigManager: remoteConfigManager,
aciSignalProtocolStore: aciSignalProtocolStore,
pniSignalProtocolStore: pniSignalProtocolStore,
udManager: udManager,
messageDecrypter: messageDecrypter,
groupsV2MessageProcessor: groupsV2MessageProcessor,
socketManager: socketManager,
tsAccountManager: tsAccountManager,
ows2FAManager: ows2FAManager,
disappearingMessagesJob: disappearingMessagesJob,
receiptManager: receiptManager,
outgoingReceiptManager: outgoingReceiptManager,
reachabilityManager: reachabilityManager,
syncManager: syncManager,
typingIndicators: typingIndicators,
attachmentDownloads: attachmentDownloads,
stickerManager: stickerManager,
databaseStorage: databaseStorage,
signalServiceAddressCache: signalServiceAddressCache,
signalService: signalService,
accountServiceClient: accountServiceClient,
storageServiceManager: storageServiceManager,
storageCoordinator: storageCoordinator,
sskPreferences: sskPreferences,
groupsV2: groupsV2,
groupV2Updates: groupV2Updates,
messageFetcherJob: messageFetcherJob,
bulkProfileFetch: bulkProfileFetch,
versionedProfiles: versionedProfiles,
modelReadCaches: modelReadCaches,
earlyMessageManager: earlyMessageManager,
messagePipelineSupervisor: messagePipelineSupervisor,
appExpiry: appExpiry,
messageProcessor: messageProcessor,
paymentsHelper: paymentsHelper,
paymentsCurrencies: paymentsCurrencies,
paymentsEvents: paymentsEvents,
mobileCoinHelper: mobileCoinHelper,
spamChallengeResolver: spamChallengeResolver,
senderKeyStore: senderKeyStore,
phoneNumberUtil: phoneNumberUtil,
webSocketFactory: webSocketFactory,
legacyChangePhoneNumber: legacyChangePhoneNumber,
subscriptionManager: subscriptionManager,
systemStoryManager: systemStoryManager,
remoteMegaphoneFetcher: remoteMegaphoneFetcher,
sskJobQueues: sskJobQueues,
contactDiscoveryManager: contactDiscoveryManager,
callMessageHandler: callMessageHandler,
notificationsManager: notificationPresenter,
messageSendLog: messageSendLog
)
SSKEnvironment.setShared(sskEnvironment, isRunningTests: appContext.isRunningTests)
// Register renamed classes.
NSKeyedUnarchiver.setClass(OWSUserProfile.self, forClassName: OWSUserProfile.collection())
NSKeyedUnarchiver.setClass(TSGroupModelV2.self, forClassName: "TSGroupModelV2")
Sounds.performStartupTasks()
return AppSetup.DatabaseContinuation(
appContext: appContext,
sskEnvironment: sskEnvironment,
backgroundTask: backgroundTask
)
}
private func configureUnsatisfiableConstraintLogging() {
UserDefaults.standard.setValue(DebugFlags.internalLogging, forKey: "_UIConstraintBasedLayoutLogUnsatisfiable")
}
}
// MARK: - DatabaseContinuation
extension AppSetup {
public class DatabaseContinuation {
private let appContext: AppContext
private let sskEnvironment: SSKEnvironment
private let backgroundTask: OWSBackgroundTask
fileprivate init(
appContext: AppContext,
sskEnvironment: SSKEnvironment,
backgroundTask: OWSBackgroundTask
) {
self.appContext = appContext
self.sskEnvironment = sskEnvironment
self.backgroundTask = backgroundTask
}
}
}
extension AppSetup.DatabaseContinuation {
public func prepareDatabase() -> Guarantee<AppSetup.FinalContinuation> {
let databaseStorage = sskEnvironment.databaseStorageRef
let (guarantee, future) = Guarantee<AppSetup.FinalContinuation>.pending()
DispatchQueue.global().async {
if self.shouldTruncateGrdbWal() {
// Try to truncate GRDB WAL before any readers or writers are active.
do {
try databaseStorage.grdbStorage.syncTruncatingCheckpoint()
} catch {
owsFailDebug("Failed to truncate database: \(error)")
}
}
databaseStorage.runGrdbSchemaMigrationsOnMainDatabase {
self.sskEnvironment.warmCaches()
self.backgroundTask.end()
future.resolve(AppSetup.FinalContinuation(sskEnvironment: self.sskEnvironment))
}
}
return guarantee
}
private func shouldTruncateGrdbWal() -> Bool {
guard appContext.isMainApp else {
return false
}
guard appContext.mainApplicationStateOnLaunch() != .background else {
return false
}
return true
}
}
// MARK: - FinalContinuation
extension AppSetup {
public class FinalContinuation {
private let sskEnvironment: SSKEnvironment
fileprivate init(sskEnvironment: SSKEnvironment) {
self.sskEnvironment = sskEnvironment
}
}
}
extension AppSetup.FinalContinuation {
public enum SetupError: Error {
case corruptRegistrationState
}
public func finish(willResumeInProgressRegistration: Bool) -> SetupError? {
AssertIsOnMainThread()
guard setUpLocalIdentifiers(willResumeInProgressRegistration: willResumeInProgressRegistration) else {
return .corruptRegistrationState
}
// Do this after we've finished running database migrations.
if DebugFlags.internalLogging {
DispatchQueue.global().async { SDSKeyValueStore.logCollectionStatistics() }
}
return nil
}
private func setUpLocalIdentifiers(willResumeInProgressRegistration: Bool) -> Bool {
let databaseStorage = sskEnvironment.databaseStorageRef
let storageServiceManager = sskEnvironment.storageServiceManagerRef
let tsAccountManager = sskEnvironment.tsAccountManagerRef
let updateLocalIdentifiers: (LocalIdentifiersObjC) -> Void = { [weak storageServiceManager] localIdentifiers in
storageServiceManager?.setLocalIdentifiers(localIdentifiers)
}
// If we're not registered, listen for when we become registered. If we are
// registered, listen for when we learn about our PNI or change our number.
tsAccountManager.didStoreLocalNumber = updateLocalIdentifiers
if tsAccountManager.isOnboarded && !willResumeInProgressRegistration {
let localIdentifiers = databaseStorage.read { tsAccountManager.localIdentifiers(transaction: $0) }
guard let localIdentifiers else {
return false
}
updateLocalIdentifiers(LocalIdentifiersObjC(localIdentifiers))
}
return true
}
}