443 lines
18 KiB
Swift
443 lines
18 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: NotificationsProtocolSwift
|
|
) -> 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()
|
|
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))
|
|
|
|
let keyValueStoreFactory = SDSKeyValueStoreFactory()
|
|
|
|
// MARK: DependenciesBridge
|
|
|
|
let recipientDatabaseTable = RecipientDatabaseTableImpl()
|
|
let recipientFetcher = RecipientFetcherImpl(recipientDatabaseTable: recipientDatabaseTable)
|
|
let recipientIdFinder = RecipientIdFinder(recipientDatabaseTable: recipientDatabaseTable, recipientFetcher: recipientFetcher)
|
|
|
|
let accountServiceClient = AccountServiceClient()
|
|
let aciSignalProtocolStore = SignalProtocolStoreImpl(
|
|
for: .aci,
|
|
keyValueStoreFactory: keyValueStoreFactory,
|
|
recipientIdFinder: recipientIdFinder
|
|
)
|
|
let attachmentDownloads = OWSAttachmentDownloads()
|
|
let blockingManager = BlockingManager()
|
|
let dateProvider = Date.provider
|
|
let earlyMessageManager = EarlyMessageManager()
|
|
let groupsV2 = GroupsV2Impl()
|
|
let messageProcessor = MessageProcessor()
|
|
let messageSender = MessageSender()
|
|
let messageSenderJobQueue = MessageSenderJobQueue()
|
|
let modelReadCaches = ModelReadCaches(factory: ModelReadCacheFactory())
|
|
let networkManager = NetworkManager()
|
|
let ows2FAManager = OWS2FAManager()
|
|
let paymentsHelper = PaymentsHelperImpl()
|
|
let pniSignalProtocolStore = SignalProtocolStoreImpl(
|
|
for: .pni,
|
|
keyValueStoreFactory: keyValueStoreFactory,
|
|
recipientIdFinder: recipientIdFinder
|
|
)
|
|
let profileManager = OWSProfileManager(
|
|
databaseStorage: databaseStorage,
|
|
swiftValues: OWSProfileManagerSwiftValues()
|
|
)
|
|
let reachabilityManager = SSKReachabilityManagerImpl()
|
|
let receiptManager = OWSReceiptManager()
|
|
let senderKeyStore = SenderKeyStore()
|
|
let signalProtocolStoreManager = SignalProtocolStoreManagerImpl(
|
|
aciProtocolStore: aciSignalProtocolStore,
|
|
pniProtocolStore: pniSignalProtocolStore
|
|
)
|
|
let signalService = OWSSignalService()
|
|
let signalServiceAddressCache = SignalServiceAddressCache()
|
|
let storageServiceManager = StorageServiceManagerImpl.shared
|
|
let syncManager = OWSSyncManager(default: ())
|
|
let udManager = OWSUDManagerImpl()
|
|
let versionedProfiles = VersionedProfilesImpl()
|
|
|
|
let usernameLookupManager = UsernameLookupManagerImpl()
|
|
let contactsManager = OWSContactsManager(swiftValues: OWSContactsManagerSwiftValues(
|
|
usernameLookupManager: usernameLookupManager
|
|
))
|
|
|
|
let dependenciesBridge = DependenciesBridge.setUpSingleton(
|
|
accountServiceClient: accountServiceClient,
|
|
appContext: appContext,
|
|
appVersion: appVersion,
|
|
attachmentDownloads: attachmentDownloads,
|
|
blockingManager: blockingManager,
|
|
contactsManager: contactsManager,
|
|
databaseStorage: databaseStorage,
|
|
dateProvider: dateProvider,
|
|
earlyMessageManager: earlyMessageManager,
|
|
groupsV2: groupsV2,
|
|
keyValueStoreFactory: keyValueStoreFactory,
|
|
messageProcessor: messageProcessor,
|
|
messageSender: messageSender,
|
|
messageSenderJobQueue: messageSenderJobQueue,
|
|
modelReadCaches: modelReadCaches,
|
|
networkManager: networkManager,
|
|
notificationsManager: notificationPresenter,
|
|
ows2FAManager: ows2FAManager,
|
|
paymentsEvents: paymentsEvents,
|
|
paymentsHelper: paymentsHelper,
|
|
profileManager: profileManager,
|
|
reachabilityManager: reachabilityManager,
|
|
receiptManager: receiptManager,
|
|
recipientDatabaseTable: recipientDatabaseTable,
|
|
recipientFetcher: recipientFetcher,
|
|
recipientIdFinder: recipientIdFinder,
|
|
senderKeyStore: senderKeyStore,
|
|
signalProtocolStoreManager: signalProtocolStoreManager,
|
|
signalService: signalService,
|
|
signalServiceAddressCache: signalServiceAddressCache,
|
|
storageServiceManager: storageServiceManager,
|
|
syncManager: syncManager,
|
|
udManager: udManager,
|
|
usernameLookupManager: usernameLookupManager,
|
|
versionedProfiles: versionedProfiles,
|
|
websocketFactory: webSocketFactory
|
|
)
|
|
|
|
// MARK: SignalMessaging environment properties
|
|
|
|
let preferences = Preferences()
|
|
let proximityMonitoringManager = OWSProximityMonitoringManagerImpl()
|
|
let avatarBuilder = AvatarBuilder()
|
|
let smJobQueues = SignalMessagingJobQueues(
|
|
db: dependenciesBridge.db,
|
|
reachabilityManager: reachabilityManager
|
|
)
|
|
|
|
// MARK: SSK environment properties
|
|
|
|
let appExpiry = dependenciesBridge.appExpiry
|
|
let linkPreviewManager = OWSLinkPreviewManager()
|
|
let pendingReceiptRecorder = MessageRequestPendingReceipts()
|
|
let messageReceiver = MessageReceiver()
|
|
let remoteConfigManager = RemoteConfigManagerImpl(
|
|
appExpiry: appExpiry,
|
|
db: dependenciesBridge.db,
|
|
keyValueStoreFactory: dependenciesBridge.keyValueStoreFactory,
|
|
tsAccountManager: dependenciesBridge.tsAccountManager,
|
|
serviceClient: SignalServiceRestClient.shared
|
|
)
|
|
let messageDecrypter = OWSMessageDecrypter()
|
|
let groupsV2MessageProcessor = GroupsV2MessageProcessor()
|
|
let disappearingMessagesJob = OWSDisappearingMessagesJob()
|
|
let receiptSender = ReceiptSender(
|
|
kvStoreFactory: dependenciesBridge.keyValueStoreFactory,
|
|
signalServiceAddressCache: signalServiceAddressCache
|
|
)
|
|
let typingIndicators = TypingIndicatorsImpl()
|
|
let stickerManager = StickerManager()
|
|
let sskPreferences = SSKPreferences()
|
|
let groupV2Updates = GroupV2UpdatesImpl()
|
|
let messageFetcherJob = MessageFetcherJob()
|
|
let bulkProfileFetch = BulkProfileFetch(
|
|
databaseStorage: databaseStorage,
|
|
reachabilityManager: reachabilityManager,
|
|
tsAccountManager: dependenciesBridge.tsAccountManager
|
|
)
|
|
let messagePipelineSupervisor = MessagePipelineSupervisor()
|
|
let paymentsCurrencies = PaymentsCurrenciesImpl()
|
|
let spamChallengeResolver = SpamChallengeResolver()
|
|
let phoneNumberUtil = PhoneNumberUtil()
|
|
let legacyChangePhoneNumber = LegacyChangePhoneNumber()
|
|
let subscriptionManager = SubscriptionManagerImpl()
|
|
let systemStoryManager = SystemStoryManager()
|
|
let remoteMegaphoneFetcher = RemoteMegaphoneFetcher()
|
|
let contactDiscoveryManager = ContactDiscoveryManagerImpl(
|
|
db: dependenciesBridge.db,
|
|
recipientDatabaseTable: dependenciesBridge.recipientDatabaseTable,
|
|
recipientFetcher: dependenciesBridge.recipientFetcher,
|
|
recipientMerger: dependenciesBridge.recipientMerger,
|
|
tsAccountManager: dependenciesBridge.tsAccountManager,
|
|
udManager: udManager,
|
|
websocketFactory: webSocketFactory
|
|
)
|
|
let messageSendLog = MessageSendLog(
|
|
db: dependenciesBridge.db,
|
|
dateProvider: { Date() }
|
|
)
|
|
let localUserLeaveGroupJobQueue = LocalUserLeaveGroupJobQueue(
|
|
db: dependenciesBridge.db,
|
|
reachabilityManager: reachabilityManager
|
|
)
|
|
|
|
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,
|
|
messageReceiver: messageReceiver,
|
|
blockingManager: blockingManager,
|
|
remoteConfigManager: remoteConfigManager,
|
|
aciSignalProtocolStore: aciSignalProtocolStore,
|
|
pniSignalProtocolStore: pniSignalProtocolStore,
|
|
udManager: udManager,
|
|
messageDecrypter: messageDecrypter,
|
|
groupsV2MessageProcessor: groupsV2MessageProcessor,
|
|
ows2FAManager: ows2FAManager,
|
|
disappearingMessagesJob: disappearingMessagesJob,
|
|
receiptManager: receiptManager,
|
|
receiptSender: receiptSender,
|
|
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,
|
|
contactDiscoveryManager: contactDiscoveryManager,
|
|
callMessageHandler: callMessageHandler,
|
|
notificationsManager: notificationPresenter,
|
|
messageSendLog: messageSendLog,
|
|
messageSenderJobQueue: messageSenderJobQueue,
|
|
localUserLeaveGroupJobQueue: localUserLeaveGroupJobQueue
|
|
)
|
|
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,
|
|
dependenciesBridge: dependenciesBridge,
|
|
smEnvironment: smEnvironment,
|
|
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 dependenciesBridge: DependenciesBridge
|
|
private let smEnvironment: SMEnvironment
|
|
private let sskEnvironment: SSKEnvironment
|
|
private let backgroundTask: OWSBackgroundTask
|
|
|
|
fileprivate init(
|
|
appContext: AppContext,
|
|
dependenciesBridge: DependenciesBridge,
|
|
smEnvironment: SMEnvironment,
|
|
sskEnvironment: SSKEnvironment,
|
|
backgroundTask: OWSBackgroundTask
|
|
) {
|
|
self.appContext = appContext
|
|
self.dependenciesBridge = dependenciesBridge
|
|
self.smEnvironment = smEnvironment
|
|
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.smEnvironment.didLoadDatabase()
|
|
self.backgroundTask.end()
|
|
future.resolve(AppSetup.FinalContinuation(
|
|
dependenciesBridge: self.dependenciesBridge,
|
|
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 dependenciesBridge: DependenciesBridge
|
|
private let sskEnvironment: SSKEnvironment
|
|
|
|
fileprivate init(dependenciesBridge: DependenciesBridge, sskEnvironment: SSKEnvironment) {
|
|
self.dependenciesBridge = dependenciesBridge
|
|
self.sskEnvironment = sskEnvironment
|
|
}
|
|
}
|
|
}
|
|
|
|
extension AppSetup.FinalContinuation {
|
|
public enum SetupError: Error {
|
|
case corruptRegistrationState
|
|
}
|
|
|
|
public func finish(willResumeInProgressRegistration: Bool) -> SetupError? {
|
|
AssertIsOnMainThread()
|
|
|
|
ZkParamsMigrator(
|
|
db: dependenciesBridge.db,
|
|
keyValueStoreFactory: dependenciesBridge.keyValueStoreFactory,
|
|
groupsV2: sskEnvironment.groupsV2Ref,
|
|
profileManager: sskEnvironment.profileManagerRef,
|
|
tsAccountManager: dependenciesBridge.tsAccountManager,
|
|
versionedProfiles: sskEnvironment.versionedProfilesRef
|
|
).migrateIfNeeded()
|
|
|
|
guard setUpLocalIdentifiers(willResumeInProgressRegistration: willResumeInProgressRegistration) else {
|
|
return .corruptRegistrationState
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
private func setUpLocalIdentifiers(willResumeInProgressRegistration: Bool) -> Bool {
|
|
let databaseStorage = sskEnvironment.databaseStorageRef
|
|
let storageServiceManager = sskEnvironment.storageServiceManagerRef
|
|
let tsAccountManager = DependenciesBridge.shared.tsAccountManager
|
|
|
|
let updateLocalIdentifiers: (LocalIdentifiersObjC) -> Void = { [weak storageServiceManager] localIdentifiers in
|
|
storageServiceManager?.setLocalIdentifiers(localIdentifiers)
|
|
}
|
|
|
|
if
|
|
tsAccountManager.registrationStateWithMaybeSneakyTransaction.isRegistered
|
|
&& !willResumeInProgressRegistration
|
|
{
|
|
let localIdentifiers = databaseStorage.read { tsAccountManager.localIdentifiers(tx: $0.asV2Read) }
|
|
guard let localIdentifiers else {
|
|
return false
|
|
}
|
|
updateLocalIdentifiers(LocalIdentifiersObjC(localIdentifiers))
|
|
// We are fully registered, and we're not in the middle of registration, so
|
|
// ensure discoverability is configured.
|
|
setUpDefaultDiscoverability()
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
private func setUpDefaultDiscoverability() {
|
|
let databaseStorage = sskEnvironment.databaseStorageRef
|
|
let phoneNumberDiscoverabilityManager = DependenciesBridge.shared.phoneNumberDiscoverabilityManager
|
|
let tsAccountManager = DependenciesBridge.shared.tsAccountManager
|
|
|
|
if databaseStorage.read(block: { tsAccountManager.phoneNumberDiscoverability(tx: $0.asV2Read) }) != nil {
|
|
return
|
|
}
|
|
|
|
databaseStorage.write { tx in
|
|
phoneNumberDiscoverabilityManager.setPhoneNumberDiscoverability(
|
|
PhoneNumberDiscoverabilityManager.Constants.discoverabilityDefault,
|
|
updateAccountAttributes: true,
|
|
updateStorageService: true,
|
|
authedAccount: .implicit(),
|
|
tx: tx.asV2Write
|
|
)
|
|
}
|
|
}
|
|
}
|