diff --git a/NotificationServiceExtension/NotificationServiceExtensionContext.swift b/NotificationServiceExtension/NSEContext.swift similarity index 98% rename from NotificationServiceExtension/NotificationServiceExtensionContext.swift rename to NotificationServiceExtension/NSEContext.swift index 675a2bd917..60de662cad 100644 --- a/NotificationServiceExtension/NotificationServiceExtensionContext.swift +++ b/NotificationServiceExtension/NSEContext.swift @@ -6,7 +6,7 @@ import Foundation import SignalServiceKit import SignalMessaging -class NotificationServiceExtensionContext: NSObject, AppContext { +class NSEContext: NSObject, AppContext { let isMainApp = false let isMainAppAndActive = false diff --git a/NotificationServiceExtension/NSEEnvironment.swift b/NotificationServiceExtension/NSEEnvironment.swift new file mode 100644 index 0000000000..24efc449da --- /dev/null +++ b/NotificationServiceExtension/NSEEnvironment.swift @@ -0,0 +1,172 @@ +// +// Copyright (c) 2021 Open Whisper Systems. All rights reserved. +// + +import Foundation +import SignalServiceKit +import SignalMessaging + +class NSEEnvironment: Dependencies { + var isProcessingMessages = AtomicBool(false) + + // MARK: - Main App Comms + + func askMainAppToHandleReceipt(handledCallback: @escaping (_ mainAppHandledReceipt: Bool) -> Void) { + DispatchQueue.main.async { + // We track whether we've ever handled the call back to ensure + // we only notify the caller once and avoid any races that may + // occur between the notification observer and the dispatch + // after block. + var hasCalledBack = false + + // Listen for an indication that the main app is going to handle + // this notification. If the main app is active we don't want to + // process any messages here. + let token = DarwinNotificationCenter.addObserver(for: .mainAppHandledNotification, queue: .main) { token in + guard !hasCalledBack else { return } + + hasCalledBack = true + + handledCallback(true) + + if DarwinNotificationCenter.isValidObserver(token) { + DarwinNotificationCenter.removeObserver(token) + } + } + + // Notify the main app that we received new content to process. + // If it's running, it will notify us so we can bail out. + DarwinNotificationCenter.post(.nseDidReceiveNotification) + + // The main app should notify us nearly instantaneously if it's + // going to process this notification so we only wait a fraction + // of a second to hear back from it. + DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.001) { + if DarwinNotificationCenter.isValidObserver(token) { + DarwinNotificationCenter.removeObserver(token) + } + + guard !hasCalledBack else { return } + + hasCalledBack = true + + // If we haven't called back yet and removed the observer token, + // the main app is not running and will not handle receipt of this + // notification. + handledCallback(false) + } + } + } + + private var mainAppLaunchObserverToken = DarwinNotificationInvalidObserver + func listenForMainAppLaunch() { + guard !DarwinNotificationCenter.isValidObserver(mainAppLaunchObserverToken) else { return } + mainAppLaunchObserverToken = DarwinNotificationCenter.addObserver(for: .mainAppLaunched, queue: .global(), using: { _ in + // If we're currently processing messages we want to commit + // suicide to ensure that we don't try and process messages + // while the main app is running. If we're not processing + // messages we keep alive since future notifications will + // be passed off gracefully to the main app. We only kill + // ourselves as a last resort. + // TODO: We could eventually make the message fetch process + // cancellable to never have to exit here. + guard self.isProcessingMessages.get() else { return } + Logger.info("Exiting because main app launched while we were processing messages.") + Logger.flush() + exit(0) + }) + } + + // MARK: - Setup + + private var isSetup = AtomicBool(false) + func setupIfNecessary() { + guard isSetup.tryToSetFlag() else { return } + DispatchQueue.main.sync { setup() } + } + + private var areVersionMigrationsComplete = false + private func setup() { + AssertIsOnMainThread() + + // This should be the first thing we do. + SetCurrentAppContext(NSEContext()) + + DebugLogger.shared().enableTTYLogging() + if _isDebugAssertConfiguration() { + DebugLogger.shared().enableFileLogging() + } else if OWSPreferences.isLoggingEnabled() { + DebugLogger.shared().enableFileLogging() + } + + Logger.info("") + + _ = AppVersion.shared() + + Cryptography.seedRandom() + + AppSetup.setupEnvironment( + appSpecificSingletonBlock: { + SSKEnvironment.shared.callMessageHandlerRef = NSECallMessageHandler() + SSKEnvironment.shared.notificationsManagerRef = NotificationPresenter() + }, + migrationCompletion: { [weak self] error in + if let error = error { + // TODO: Maybe notify that you should open the main app. + owsFailDebug("Error \(error)") + return + } + self?.versionMigrationsDidComplete() + } + ) + + NotificationCenter.default.addObserver( + self, + selector: #selector(storageIsReady), + name: .StorageIsReady, + object: nil + ) + + Logger.info("completed.") + + OWSAnalytics.appLaunchDidBegin() + + listenForMainAppLaunch() + } + + @objc + private func versionMigrationsDidComplete() { + AssertIsOnMainThread() + + Logger.debug("") + + areVersionMigrationsComplete = true + + checkIsAppReady() + } + + @objc + private func storageIsReady() { + AssertIsOnMainThread() + + Logger.debug("") + + checkIsAppReady() + } + + @objc + private func checkIsAppReady() { + AssertIsOnMainThread() + + // Only mark the app as ready once. + guard !AppReadiness.isAppReady else { return } + + // App isn't ready until storage is ready AND all version migrations are complete. + guard storageCoordinator.isStorageReady && areVersionMigrationsComplete else { return } + + // Note that this does much more than set a flag; it will also run all deferred blocks. + AppReadiness.setAppIsReady() + + AppVersion.shared().nseLaunchDidComplete() + } +} diff --git a/NotificationServiceExtension/NotificationService.swift b/NotificationServiceExtension/NotificationService.swift index 5ffd6d8fba..0b0bc7bd20 100644 --- a/NotificationServiceExtension/NotificationService.swift +++ b/NotificationServiceExtension/NotificationService.swift @@ -7,6 +7,29 @@ import SignalMessaging import SignalServiceKit import PromiseKit +// The lifecycle of the NSE looks something like the following: +// 1) App receives notification +// 2) System creates an instance of the extension class +// and calls `didReceive` in the background +// 3) Extension processes messages / displays whatever +// notifications it needs to +// 4) Extension notifies its work is complete by calling +// the contentHandler +// 5) If the extension takes too long to perform its work +// (more than 30s), it will be notified and immediately +// terminated +// +// Note that the NSE does *not* always spawn a new process to +// handle a new notification and will also try and process notifications +// in parallel. `didReceive` could be called twice for the same process, +// but it will always be called on different threads. It may or may not be +// called on the same instance of `NotificationService` as a previous +// notification. +// +// We keep a global `environment` singleton to ensure that our app context, +// database, logging, etc. are only ever setup once per *process* +let environment = NSEEnvironment() + class NotificationService: UNNotificationServiceExtension { var contentHandler: ((UNNotificationContent) -> Void)? @@ -18,43 +41,25 @@ class NotificationService: UNNotificationServiceExtension { contentHandler?(content) } - // The lifecycle of the NSE looks something like the following: - // 1) App receives notification - // 2) System creates an instance of the extension class - // and calls this method in the background - // 3) Extension processes messages / displays whatever - // notifications it needs to - // 4) Extension notifies its work is complete by calling - // the contentHandler - // 5) If the extension takes too long to perform its work - // (more than 30s), it will be notified and immediately - // terminated - // - // Note that the NSE does *not* always spawn a new process to - // handle a new notification and will also try and process notifications - // in parallel. `didReceive` could be called twice for the same process, - // but will always be called on different threads. To deal with this we - // ensure that we only do setup *once* per process and we dispatch to - // the main queue to make sure the calls to the message fetcher job - // run serially. override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) { self.contentHandler = contentHandler - DispatchQueue.main.sync { self.setupIfNecessary() } + environment.setupIfNecessary() owsAssertDebug(FeatureFlags.notificationServiceExtension) - listenForMainAppLaunch() + Logger.info("Received notification in class: \(self), thread: \(Thread.current), pid: \(ProcessInfo.processInfo.processIdentifier)") - askMainAppToHandleReceipt { mainAppHandledReceipt in + environment.askMainAppToHandleReceipt { [weak self] mainAppHandledReceipt in guard !mainAppHandledReceipt else { Logger.info("Received notification handled by main application.") - return self.completeSilenty() + self?.completeSilenty() + return } Logger.info("Processing received notification.") - AppReadiness.runNowOrWhenAppDidBecomeReadySync { self.fetchAndProcessMessages() } + AppReadiness.runNowOrWhenAppDidBecomeReadySync { self?.fetchAndProcessMessages() } } } @@ -68,161 +73,6 @@ class NotificationService: UNNotificationServiceExtension { completeSilenty() } - private var hasSetup = false - func setupIfNecessary() { - AssertIsOnMainThread() - - // The NSE will often re-use the same process, so if we're - // already setup we want to do nothing. We're already ready - // to process new messages. - guard !hasSetup else { return } - - hasSetup = true - - // This should be the first thing we do. - SetCurrentAppContext(NotificationServiceExtensionContext()) - - DebugLogger.shared().enableTTYLogging() - if _isDebugAssertConfiguration() { - DebugLogger.shared().enableFileLogging() - } else if OWSPreferences.isLoggingEnabled() { - DebugLogger.shared().enableFileLogging() - } - - Logger.info("") - - _ = AppVersion.shared() - - Cryptography.seedRandom() - - AppSetup.setupEnvironment( - appSpecificSingletonBlock: { - SSKEnvironment.shared.callMessageHandlerRef = NSECallMessageHandler() - SSKEnvironment.shared.notificationsManagerRef = NotificationPresenter() - }, - migrationCompletion: { [weak self] error in - if let error = error { - // TODO: Maybe notify that you should open the main app. - owsFailDebug("Error \(error)") - return - } - self?.versionMigrationsDidComplete() - } - ) - - NotificationCenter.default.addObserver(self, - selector: #selector(storageIsReady), - name: .StorageIsReady, - object: nil) - - Logger.info("completed.") - - OWSAnalytics.appLaunchDidBegin() - } - - @objc - func versionMigrationsDidComplete() { - AssertIsOnMainThread() - - Logger.debug("") - - areVersionMigrationsComplete = true - - checkIsAppReady() - } - - @objc - func storageIsReady() { - AssertIsOnMainThread() - - Logger.debug("") - - checkIsAppReady() - } - - @objc - func checkIsAppReady() { - AssertIsOnMainThread() - - // Only mark the app as ready once. - guard !AppReadiness.isAppReady else { return } - - // App isn't ready until storage is ready AND all version migrations are complete. - guard storageCoordinator.isStorageReady && areVersionMigrationsComplete else { return } - - // Note that this does much more than set a flag; it will also run all deferred blocks. - AppReadiness.setAppIsReady() - - AppVersion.shared().nseLaunchDidComplete() - } - - func askMainAppToHandleReceipt(handledCallback: @escaping (_ mainAppHandledReceipt: Bool) -> Void) { - DispatchQueue.main.async { - // We track whether we've ever handled the call back to ensure - // we only notify the caller once and avoid any races that may - // occur between the notification observer and the dispatch - // after block. - var hasCalledBack = false - - // Listen for an indication that the main app is going to handle - // this notification. If the main app is active we don't want to - // process any messages here. - let token = DarwinNotificationCenter.addObserver(for: .mainAppHandledNotification, queue: .main) { token in - guard !hasCalledBack else { return } - - hasCalledBack = true - - handledCallback(true) - - if DarwinNotificationCenter.isValidObserver(token) { - DarwinNotificationCenter.removeObserver(token) - } - } - - // Notify the main app that we received new content to process. - // If it's running, it will notify us so we can bail out. - DarwinNotificationCenter.post(.nseDidReceiveNotification) - - // The main app should notify us nearly instantaneously if it's - // going to process this notification so we only wait a fraction - // of a second to hear back from it. - DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.001) { - if DarwinNotificationCenter.isValidObserver(token) { - DarwinNotificationCenter.removeObserver(token) - } - - guard !hasCalledBack else { return } - - hasCalledBack = true - - // If we haven't called back yet and removed the observer token, - // the main app is not running and will not handle receipt of this - // notification. - handledCallback(false) - } - } - } - - private var mainAppLaunchObserverToken = DarwinNotificationInvalidObserver - func listenForMainAppLaunch() { - guard !DarwinNotificationCenter.isValidObserver(mainAppLaunchObserverToken) else { return } - mainAppLaunchObserverToken = DarwinNotificationCenter.addObserver(for: .mainAppLaunched, queue: .global(), using: { _ in - // If we're currently processing messages we want to commit - // suicide to ensure that we don't try and process messages - // while the main app is running. If we're not processing - // messages we keep alive since future notifications will - // be passed off gracefully to the main app. We only kill - // ourselves as a last resort. - // TODO: We could eventually make the message fetch process - // cancellable to never have to exit here. - guard self.isProcessingMessages.get() else { return } - Logger.info("Exiting because main app launched while we were processing messages.") - exit(0) - }) - } - - private let isProcessingMessages = AtomicBool(false) - func fetchAndProcessMessages() { AssertIsOnMainThread() @@ -231,16 +81,18 @@ class NotificationService: UNNotificationServiceExtension { return completeSilenty() } - isProcessingMessages.set(true) + environment.isProcessingMessages.set(true) Logger.info("Beginning message fetch.") - messageFetcherJob.run().promise.then { + messageFetcherJob.run().promise.then { [weak self] () -> Promise in + Logger.info("Waiting for processing to complete.") + guard let self = self else { return Promise.value(()) } return self.messageProcessor.processingCompletePromise() - }.ensure { + }.ensure { [weak self] in Logger.info("Message fetch completed.") - self.isProcessingMessages.set(false) - self.completeSilenty() + environment.isProcessingMessages.set(false) + self?.completeSilenty() }.catch { error in Logger.warn("Error: \(error)") } diff --git a/Signal.xcodeproj/project.pbxproj b/Signal.xcodeproj/project.pbxproj index 5c33ce3ff4..573a638dcb 100644 --- a/Signal.xcodeproj/project.pbxproj +++ b/Signal.xcodeproj/project.pbxproj @@ -722,6 +722,7 @@ 88411B60249B0759005D10AA /* ConversationViewController+LastVisibleSortId.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88411B5F249B0757005D10AA /* ConversationViewController+LastVisibleSortId.swift */; }; 8841584C252F9F1C0078903D /* SignalCall.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8841584B252F9F1C0078903D /* SignalCall.swift */; }; 8845B0C9264F12F800FA694C /* GroupDescriptionPreviewView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8845B0C8264F12F800FA694C /* GroupDescriptionPreviewView.swift */; }; + 88489AFB266AB59800EE1166 /* NSEEnvironment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88489AFA266AB59800EE1166 /* NSEEnvironment.swift */; }; 884EFBAC251478D0006B9457 /* ResearchMegaphone.swift in Sources */ = {isa = PBXBuildFile; fileRef = 884EFBAB251478D0006B9457 /* ResearchMegaphone.swift */; }; 885091C02525650100428A37 /* PHPhotoLibrary+Xcode11.swift in Sources */ = {isa = PBXBuildFile; fileRef = 885091BF2525650100428A37 /* PHPhotoLibrary+Xcode11.swift */; }; 8851DB3F24CCE9C8001EACD2 /* MentionTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8851DB3E24CCE9C8001EACD2 /* MentionTextView.swift */; }; @@ -801,7 +802,7 @@ 88A4CC1D246D00090082211F /* DeviceTransferProgressViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88A4CC1C246D00090082211F /* DeviceTransferProgressViewController.swift */; }; 88A505F423DA16E10005C012 /* ExperienceUpgradeManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88A505F323DA16E10005C012 /* ExperienceUpgradeManager.swift */; }; 88A505FA23DBA1360005C012 /* IntroducingPINs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88A505F923DBA1360005C012 /* IntroducingPINs.swift */; }; - 88A595E323AB48E80029E835 /* NotificationServiceExtensionContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88A595E223AB48E80029E835 /* NotificationServiceExtensionContext.swift */; }; + 88A595E323AB48E80029E835 /* NSEContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88A595E223AB48E80029E835 /* NSEContext.swift */; }; 88A9419524099F7E000E9700 /* attachment_dark.json in Resources */ = {isa = PBXBuildFile; fileRef = 88A9419424099F7E000E9700 /* attachment_dark.json */; }; 88A9419724099F85000E9700 /* attachment_light.json in Resources */ = {isa = PBXBuildFile; fileRef = 88A9419624099F85000E9700 /* attachment_light.json */; }; 88A941992409A391000E9700 /* LottieToggleButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88A941982409A391000E9700 /* LottieToggleButton.swift */; }; @@ -1808,6 +1809,7 @@ 884596EF24B93E13002F66D3 /* cy */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = cy; path = translations/cy.lproj/Localizable.strings; sourceTree = ""; }; 884596F024B93E32002F66D3 /* cy */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = cy; path = translations/cy.lproj/InfoPlist.strings; sourceTree = ""; }; 8845B0C8264F12F800FA694C /* GroupDescriptionPreviewView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupDescriptionPreviewView.swift; sourceTree = ""; }; + 88489AFA266AB59800EE1166 /* NSEEnvironment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSEEnvironment.swift; sourceTree = ""; }; 884EFBAB251478D0006B9457 /* ResearchMegaphone.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResearchMegaphone.swift; sourceTree = ""; }; 885091BF2525650100428A37 /* PHPhotoLibrary+Xcode11.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PHPhotoLibrary+Xcode11.swift"; sourceTree = ""; }; 8851DB3E24CCE9C8001EACD2 /* MentionTextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MentionTextView.swift; sourceTree = ""; }; @@ -1958,7 +1960,7 @@ 88A4CC1C246D00090082211F /* DeviceTransferProgressViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceTransferProgressViewController.swift; sourceTree = ""; }; 88A505F323DA16E10005C012 /* ExperienceUpgradeManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExperienceUpgradeManager.swift; sourceTree = ""; }; 88A505F923DBA1360005C012 /* IntroducingPINs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IntroducingPINs.swift; sourceTree = ""; }; - 88A595E223AB48E80029E835 /* NotificationServiceExtensionContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationServiceExtensionContext.swift; sourceTree = ""; }; + 88A595E223AB48E80029E835 /* NSEContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSEContext.swift; sourceTree = ""; }; 88A695BC232C18DF002F7B9B /* AudioWaveformProgressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioWaveformProgressView.swift; sourceTree = ""; }; 88A9419424099F7E000E9700 /* attachment_dark.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = attachment_dark.json; sourceTree = ""; }; 88A9419624099F85000E9700 /* attachment_light.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = attachment_light.json; sourceTree = ""; }; @@ -3573,10 +3575,11 @@ children = ( 885AC57023AB428C0077705E /* NotificationServiceExtension.entitlements */, 8872409323E12DF500824D62 /* NotificationServiceExtension-AppStore.entitlements */, - 885AC56623AB42430077705E /* NotificationService.swift */, 885AC56823AB42430077705E /* Info.plist */, - 88A595E223AB48E80029E835 /* NotificationServiceExtensionContext.swift */, + 885AC56623AB42430077705E /* NotificationService.swift */, 88D7BA9B266804C40088D1C2 /* NSECallMessageHandler.swift */, + 88A595E223AB48E80029E835 /* NSEContext.swift */, + 88489AFA266AB59800EE1166 /* NSEEnvironment.swift */, ); path = NotificationServiceExtension; sourceTree = ""; @@ -5120,8 +5123,9 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 88489AFB266AB59800EE1166 /* NSEEnvironment.swift in Sources */, 88D7BA9C266804C40088D1C2 /* NSECallMessageHandler.swift in Sources */, - 88A595E323AB48E80029E835 /* NotificationServiceExtensionContext.swift in Sources */, + 88A595E323AB48E80029E835 /* NSEContext.swift in Sources */, 885AC56723AB42430077705E /* NotificationService.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/SignalMessaging/Storage Service/StorageServiceManager.swift b/SignalMessaging/Storage Service/StorageServiceManager.swift index 190760431e..34f251bdaf 100644 --- a/SignalMessaging/Storage Service/StorageServiceManager.swift +++ b/SignalMessaging/Storage Service/StorageServiceManager.swift @@ -18,28 +18,30 @@ public class StorageServiceManager: NSObject, StorageServiceManagerProtocol { SwiftSingletons.register(self) - AppReadiness.runNowOrWhenAppWillBecomeReady { - self.cleanUpUnknownData() - } + if CurrentAppContext().hasUI { + AppReadiness.runNowOrWhenAppWillBecomeReady { + self.cleanUpUnknownData() + } - AppReadiness.runNowOrWhenAppDidBecomeReadySync { - NotificationCenter.default.addObserver( - self, - selector: #selector(self.willResignActive), - name: .OWSApplicationWillResignActive, - object: nil - ) - } + AppReadiness.runNowOrWhenAppDidBecomeReadySync { + NotificationCenter.default.addObserver( + self, + selector: #selector(self.willResignActive), + name: .OWSApplicationWillResignActive, + object: nil + ) + } - AppReadiness.runNowOrWhenAppDidBecomeReadyAsync { - guard self.tsAccountManager.isRegisteredAndReady else { return } + AppReadiness.runNowOrWhenAppDidBecomeReadyAsync { + guard self.tsAccountManager.isRegisteredAndReady else { return } - // Schedule a restore. This will do nothing unless we've never - // registered a manifest before. - self.restoreOrCreateManifestIfNecessary() + // Schedule a restore. This will do nothing unless we've never + // registered a manifest before. + self.restoreOrCreateManifestIfNecessary() - // If we have any pending changes since we last launch, back them up now. - self.backupPendingChanges() + // If we have any pending changes since we last launch, back them up now. + self.backupPendingChanges() + } } } diff --git a/SignalServiceKit/src/Messages/MessageFetcherJob.swift b/SignalServiceKit/src/Messages/MessageFetcherJob.swift index ebcd9cb99a..244151e65d 100644 --- a/SignalServiceKit/src/Messages/MessageFetcherJob.swift +++ b/SignalServiceKit/src/Messages/MessageFetcherJob.swift @@ -35,7 +35,7 @@ public class MessageFetcherJob: NSObject { SwiftSingletons.register(self) - if CurrentAppContext().shouldProcessIncomingMessages { + if CurrentAppContext().shouldProcessIncomingMessages && CurrentAppContext().isMainApp { AppReadiness.runNowOrWhenAppDidBecomeReadySync { // Fetch messages as soon as possible after launching. In particular, when // launching from the background, without this, we end up waiting some extra