Path: blob/main/Signal/AppLaunch/AppEnvironment.swift
1 views
//
// Copyright 2018 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
//
public import Foundation
import SignalServiceKit
public class AppEnvironment: NSObject {
private static var _shared: AppEnvironment?
static func setSharedEnvironment(_ appEnvironment: AppEnvironment) {
owsPrecondition(self._shared == nil)
self._shared = appEnvironment
}
@objc
public class var shared: AppEnvironment { _shared! }
/// Objects tied to this AppEnvironment that simply need to be retained.
@MainActor
var ownedObjects = [AnyObject]()
let deviceTransferServiceRef: DeviceTransferService
let pushRegistrationManagerRef: PushRegistrationManager
let cvAudioPlayerRef = CVAudioPlayer()
let speechManagerRef = SpeechManager()
let windowManagerRef = WindowManager()
private(set) var appIconBadgeUpdater: AppIconBadgeUpdater!
private(set) var avatarHistoryManager: AvatarHistoryManager!
private(set) var backupAttachmentDownloadTracker: BackupAttachmentDownloadTracker!
private(set) var backupAttachmentUploadTracker: BackupAttachmentUploadTracker!
private(set) var backupDisablingManager: BackupDisablingManager!
private(set) var backupEnablingManager: BackupEnablingManager!
private(set) var badgeManager: BadgeManager!
private(set) var callLinkProfileKeySharingManager: CallLinkProfileKeySharingManager!
private(set) var callService: CallService!
private(set) var outgoingDeviceRestorePresenter: OutgoingDeviceRestorePresenter!
private(set) var provisioningManager: ProvisioningManager!
private(set) var quickRestoreManager: QuickRestoreManager!
private var registrationIdMismatchManager: RegistrationIdMismatchManager!
init(appReadiness: AppReadiness, deviceTransferService: DeviceTransferService) {
self.deviceTransferServiceRef = deviceTransferService
self.pushRegistrationManagerRef = PushRegistrationManager(appReadiness: appReadiness)
super.init()
SwiftSingletons.register(self)
}
func setUp(appReadiness: AppReadiness, callService: CallService) {
let cron = DependenciesBridge.shared.cron
let authCredentialStore = AuthCredentialStore()
let backupAttachmentUploadEraStore = BackupAttachmentUploadEraStore()
let backupAttachmentDownloadStore = BackupAttachmentDownloadStore()
let backupCDNCredentialStore = BackupCDNCredentialStore()
let backupExportJobStore = BackupExportJobStore()
let backupSettingsStore = BackupSettingsStore()
let backupNonceStore = BackupNonceMetadataStore()
let backupSubscriptionIssueStore = BackupSubscriptionIssueStore()
let clvBackupExportProgressViewStore = CLVBackupExportProgressView.Store()
let badgeManager = BadgeManager(
badgeCountFetcher: DependenciesBridge.shared.badgeCountFetcher,
databaseStorage: SSKEnvironment.shared.databaseStorageRef,
)
let deviceProvisioningService = DeviceProvisioningServiceImpl(
networkManager: SSKEnvironment.shared.networkManagerRef,
)
self.appIconBadgeUpdater = AppIconBadgeUpdater(badgeManager: badgeManager)
self.avatarHistoryManager = AvatarHistoryManager(
appReadiness: appReadiness,
db: DependenciesBridge.shared.db,
)
self.backupAttachmentDownloadTracker = BackupAttachmentDownloadTracker(
backupAttachmentDownloadQueueStatusManager: DependenciesBridge.shared.backupAttachmentDownloadQueueStatusManager,
backupAttachmentDownloadProgress: DependenciesBridge.shared.backupAttachmentDownloadProgress,
)
self.backupAttachmentUploadTracker = BackupAttachmentUploadTracker(
backupAttachmentUploadQueueStatusManager: DependenciesBridge.shared.backupAttachmentUploadQueueStatusManager,
backupAttachmentUploadProgress: DependenciesBridge.shared.backupAttachmentUploadProgress,
)
self.badgeManager = badgeManager
self.backupDisablingManager = BackupDisablingManager(
accountEntropyPoolManager: DependenciesBridge.shared.accountEntropyPoolManager,
authCredentialStore: authCredentialStore,
backupAttachmentCoordinator: DependenciesBridge.shared.backupAttachmentCoordinator,
backupAttachmentDownloadQueueStatusManager: DependenciesBridge.shared.backupAttachmentDownloadQueueStatusManager,
backupAttachmentDownloadStore: backupAttachmentDownloadStore,
backupCDNCredentialStore: backupCDNCredentialStore,
backupExportJobStore: backupExportJobStore,
backupKeyService: DependenciesBridge.shared.backupKeyService,
backupListMediaManager: DependenciesBridge.shared.backupListMediaManager,
backupPlanManager: DependenciesBridge.shared.backupPlanManager,
backupSettingsStore: backupSettingsStore,
clvBackupExportProgressViewStore: clvBackupExportProgressViewStore,
db: DependenciesBridge.shared.db,
tsAccountManager: DependenciesBridge.shared.tsAccountManager,
)
self.backupEnablingManager = BackupEnablingManager(
backupAttachmentUploadEraStore: backupAttachmentUploadEraStore,
backupDisablingManager: self.backupDisablingManager,
backupKeyService: DependenciesBridge.shared.backupKeyService,
backupPlanManager: DependenciesBridge.shared.backupPlanManager,
backupSettingsStore: backupSettingsStore,
backupSubscriptionIssueStore: backupSubscriptionIssueStore,
backupSubscriptionManager: DependenciesBridge.shared.backupSubscriptionManager,
backupTestFlightEntitlementManager: DependenciesBridge.shared.backupTestFlightEntitlementManager,
db: DependenciesBridge.shared.db,
tsAccountManager: DependenciesBridge.shared.tsAccountManager,
notificationPresenter: SSKEnvironment.shared.notificationPresenterRef,
)
self.callService = callService
self.callLinkProfileKeySharingManager = CallLinkProfileKeySharingManager(
db: DependenciesBridge.shared.db,
accountManager: DependenciesBridge.shared.tsAccountManager,
)
self.provisioningManager = ProvisioningManager(
accountKeyStore: DependenciesBridge.shared.accountKeyStore,
db: DependenciesBridge.shared.db,
deviceManager: DependenciesBridge.shared.deviceManager,
deviceProvisioningService: deviceProvisioningService,
identityManager: DependenciesBridge.shared.identityManager,
linkAndSyncManager: DependenciesBridge.shared.linkAndSyncManager,
profileManager: SSKEnvironment.shared.profileManagerRef,
receiptManager: ProvisioningManager.Wrappers.ReceiptManager(SSKEnvironment.shared.receiptManagerRef),
tsAccountManager: DependenciesBridge.shared.tsAccountManager,
)
self.quickRestoreManager = QuickRestoreManager(
accountKeyStore: DependenciesBridge.shared.accountKeyStore,
backupNonceStore: backupNonceStore,
backupSettingsStore: backupSettingsStore,
db: DependenciesBridge.shared.db,
deviceProvisioningService: deviceProvisioningService,
identityManager: DependenciesBridge.shared.identityManager,
networkManager: SSKEnvironment.shared.networkManagerRef,
tsAccountManager: DependenciesBridge.shared.tsAccountManager,
)
let usernameValidationManager = DependenciesBridge.shared.usernameValidationManager
cron.schedulePeriodically(
uniqueKey: .checkUsername,
approximateInterval: .day,
mustBeRegistered: true,
mustBeConnected: true,
operation: { _ = try await usernameValidationManager.validateUsername() },
)
self.outgoingDeviceRestorePresenter = OutgoingDeviceRestorePresenter(
dateProvider: Date.provider,
db: DependenciesBridge.shared.db,
backupSettingsStore: BackupSettingsStore(),
deviceTransferService: deviceTransferServiceRef,
quickRestoreManager: quickRestoreManager,
)
self.registrationIdMismatchManager = RegistrationIdMismatchManagerImpl(
db: DependenciesBridge.shared.db,
tsAccountManager: DependenciesBridge.shared.tsAccountManager,
udManager: SSKEnvironment.shared.udManagerRef,
)
let inactiveLinkedDeviceFinder = DependenciesBridge.shared.inactiveLinkedDeviceFinder
cron.schedulePeriodically(
uniqueKey: .fetchDevices,
approximateInterval: .day,
mustBeRegistered: true,
mustBeConnected: true,
operation: { try await inactiveLinkedDeviceFinder.refreshLinkedDeviceStateIfNecessary() },
)
let subscriptionConfigManager = DependenciesBridge.shared.subscriptionConfigManager
cron.schedulePeriodically(
uniqueKey: .fetchSubscriptionConfig,
approximateInterval: .day,
mustBeRegistered: false,
mustBeConnected: true,
operation: { try await subscriptionConfigManager.refresh() },
)
let identityKeyMismatchManager = DependenciesBridge.shared.identityKeyMismatchManager
cron.scheduleFrequently(
mustBeRegistered: true,
mustBeDeviceType: .linked,
mustBeConnected: true,
operation: { try await identityKeyMismatchManager.validateLocalPniIdentityKeyIfNecessary() },
)
appReadiness.runNowOrWhenAppWillBecomeReady {
self.badgeManager.startObservingChanges(in: DependenciesBridge.shared.databaseChangeObserver)
self.appIconBadgeUpdater.startObserving()
}
appReadiness.runNowOrWhenAppDidBecomeReadyAsync {
let accountEntropyPoolManager = DependenciesBridge.shared.accountEntropyPoolManager
let attachmentBackfillManager = DependenciesBridge.shared.attachmentBackfillManager
let backupExportJobRunner = DependenciesBridge.shared.backupExportJobRunner
let backupIdService = DependenciesBridge.shared.backupIdService
let backupSubscriptionManager = DependenciesBridge.shared.backupSubscriptionManager
let backupTestFlightEntitlementManager = DependenciesBridge.shared.backupTestFlightEntitlementManager
let callRecordStore = DependenciesBridge.shared.callRecordStore
let callRecordQuerier = DependenciesBridge.shared.callRecordQuerier
let db = DependenciesBridge.shared.db
let groupCallPeekClient = SSKEnvironment.shared.groupCallManagerRef.groupCallPeekClient
let interactionStore = DependenciesBridge.shared.interactionStore
let masterKeySyncManager = DependenciesBridge.shared.masterKeySyncManager
let notificationPresenter = SSKEnvironment.shared.notificationPresenterRef
let recipientDatabaseTable = DependenciesBridge.shared.recipientDatabaseTable
let storageServiceManager = SSKEnvironment.shared.storageServiceManagerRef
let threadStore = DependenciesBridge.shared.threadStore
let tsAccountManager = DependenciesBridge.shared.tsAccountManager
let storageServiceRecordIkmMigrator = DependenciesBridge.shared.storageServiceRecordIkmMigrator
let avatarDefaultColorStorageServiceMigrator = AvatarDefaultColorStorageServiceMigrator(
db: db,
recipientDatabaseTable: recipientDatabaseTable,
storageServiceManager: storageServiceManager,
threadStore: threadStore,
)
let groupCallRecordRingingCleanupManager = GroupCallRecordRingingCleanupManager(
callRecordStore: callRecordStore,
callRecordQuerier: callRecordQuerier,
db: db,
interactionStore: interactionStore,
groupCallPeekClient: groupCallPeekClient,
notificationPresenter: notificationPresenter,
threadStore: threadStore,
)
let registeredState = db.read { tx in
return try? tsAccountManager.registeredState(tx: tx)
}
// Things that should run on either the primary or linked devices.
if let registeredState, registeredState.isPrimary {
Task {
do {
try await avatarDefaultColorStorageServiceMigrator.performMigrationIfNecessary()
} catch {
Logger.warn("Couldn't perform avatar default color migration: \(error)")
}
}
Task {
await storageServiceRecordIkmMigrator.migrateToManifestRecordIkmIfNecessary()
}
Task {
do {
try await backupIdService.registerBackupIDIfNecessary(
localAci: registeredState.localIdentifiers.aci,
auth: .implicit(),
logger: PrefixedLogger(prefix: "[Launch]"),
)
} catch {
// Do nothing, we'll try again on the next app launch.
owsFailDebug("Error registering backup ID \(error)")
}
}
// If we had an interrupted BackupExportJob, resume it.
backupExportJobRunner.resumeIfNecessary()
Task {
await accountEntropyPoolManager.generateIfMissing()
}
Task {
// Valide the local registration ID of the primary.
// There was a bug in re-registration flow that could lead to a discrepancy
// between client and server around the registrationID
await self.registrationIdMismatchManager.validateRegistrationIds()
}
// Start any incomplete enqueued attachment backfill requests.
attachmentBackfillManager.processEnqueuedInboundRequests(
registeredState: registeredState,
)
} else {
}
Task {
await db.awaitableWrite { tx in
masterKeySyncManager.runStartupJobs(tx: tx)
}
}
Task {
await db.awaitableWrite { tx in
groupCallRecordRingingCleanupManager.cleanupRingingCalls(tx: tx)
}
}
Task { () async -> Void in
await self.backupDisablingManager.disableRemotelyIfNecessary()
}
Task {
await self.avatarHistoryManager.cleanupOrphanedImages()
}
Task {
do {
try await backupSubscriptionManager.redeemSubscriptionIfNecessary()
} catch {
owsFailDebug("Failed to redeem Backup subscription in launch job: \(error)")
}
}
Task {
do {
try await backupTestFlightEntitlementManager.renewEntitlementIfNecessary()
} catch {
owsFailDebug("Failed to renew Backup entitlement for TestFlight in launch job: \(error)")
}
}
Task {
await DonationSubscriptionManager.performMigrationToStorageServiceIfNecessary()
do {
try await DonationSubscriptionManager.redeemSubscriptionIfNecessary()
} catch {
owsFailDebug("Failed to redeem subscription in launch job: \(error)")
}
}
}
}
}