Support updated shared context

This commit is contained in:
Peter Iakovlev 2019-02-01 14:42:46 +04:00
parent cb86c69d55
commit 6764936d2c
15 changed files with 906 additions and 453 deletions

View File

@ -88,6 +88,14 @@ class NotificationViewController: UIViewController, UNNotificationContentExtensi
initializeAccountManagement()
let accountManager = AccountManager(basePath: rootPath + "/accounts-metadata")
var initialPresentationDataAndSettings: InitialPresentationDataAndSettings?
let semaphore = DispatchSemaphore(value: 0)
let _ = currentPresentationDataAndSettings(accountManager: accountManager).start(next: { value in
initialPresentationDataAndSettings = value
semaphore.signal()
})
semaphore.wait()
let applicationBindings = TelegramApplicationBindings(isMainApp: false, containerPath: appGroupUrl.path, appSpecificScheme: BuildConfig.shared().appSpecificUrlScheme, openUrl: { _ in
}, openUniversalUrl: { _, completion in
completion.completion(false)
@ -109,7 +117,7 @@ class NotificationViewController: UIViewController, UNNotificationContentExtensi
let appVersion = (Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String) ?? "unknown"
sharedAccountContext = SharedAccountContext(accountManager: accountManager, applicationBindings: applicationBindings, networkArguments: NetworkInitializationArguments(apiId: apiId, languagesCategory: languagesCategory, appVersion: appVersion, voipMaxLayer: 0), rootPath: rootPath, apsNotificationToken: .never(), voipNotificationToken: .never())
sharedAccountContext = SharedAccountContext(mainWindow: nil, accountManager: accountManager, applicationBindings: applicationBindings, initialPresentationDataAndSettings: initialPresentationDataAndSettings!, networkArguments: NetworkInitializationArguments(apiId: apiId, languagesCategory: languagesCategory, appVersion: appVersion, voipMaxLayer: 0), rootPath: rootPath, apsNotificationToken: .never(), voipNotificationToken: .never())
}
}

View File

@ -144,7 +144,15 @@ class ShareRootController: UIViewController {
initializeAccountManagement()
let accountManager = AccountManager(basePath: rootPath + "/accounts-metadata")
let sharedContext = SharedAccountContext(accountManager: accountManager, applicationBindings: applicationBindings, networkArguments: NetworkInitializationArguments(apiId: apiId, languagesCategory: languagesCategory, appVersion: appVersion, voipMaxLayer: 0), rootPath: rootPath, apsNotificationToken: .never(), voipNotificationToken: .never())
var initialPresentationDataAndSettings: InitialPresentationDataAndSettings?
let semaphore = DispatchSemaphore(value: 0)
let _ = currentPresentationDataAndSettings(accountManager: accountManager).start(next: { value in
initialPresentationDataAndSettings = value
semaphore.signal()
})
semaphore.wait()
let sharedContext = SharedAccountContext(mainWindow: nil, accountManager: accountManager, applicationBindings: applicationBindings, initialPresentationDataAndSettings: initialPresentationDataAndSettings!, networkArguments: NetworkInitializationArguments(apiId: apiId, languagesCategory: languagesCategory, appVersion: appVersion, voipMaxLayer: 0), rootPath: rootPath, apsNotificationToken: .never(), voipNotificationToken: .never())
account = accountManager.transaction { transaction -> (SharedAccountContext, LoggingSettings) in
return (sharedContext, transaction.getSharedData(SharedDataKeys.loggingSettings) as? LoggingSettings ?? LoggingSettings.defaultSettings)
@ -181,14 +189,17 @@ class ShareRootController: UIViewController {
let shouldBeMaster = self.shouldBeMaster
let applicationInterface = account
|> mapToSignal { sharedContext, account -> Signal<(AccountContext, PostboxAccessChallengeData), ShareAuthorizationError> in
return combineLatest(sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.presentationPasscodeSettings]), currentPresentationDataAndSettings(accountManager: sharedContext.accountManager, postbox: account.postbox), sharedContext.accountManager.accessChallengeData())
let limitsConfiguration = account.postbox.transaction { transaction -> LimitsConfiguration in
return transaction.getPreferencesEntry(key: PreferencesKeys.limitsConfiguration) as? LimitsConfiguration ?? LimitsConfiguration.defaultValue
}
return combineLatest(sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.presentationPasscodeSettings]), limitsConfiguration, sharedContext.accountManager.accessChallengeData())
|> take(1)
|> deliverOnMainQueue
|> introduceError(ShareAuthorizationError.self)
|> map { sharedData, dataAndSettings, data -> (AccountContext, PostboxAccessChallengeData) in
|> map { sharedData, limitsConfiguration, data -> (AccountContext, PostboxAccessChallengeData) in
accountCache = (sharedContext, account)
updateLegacyLocalization(strings: dataAndSettings.presentationData.strings)
let context = AccountContext(sharedContext: sharedContext, account: account, initialPresentationDataAndSettings: dataAndSettings)
updateLegacyLocalization(strings: sharedContext.currentPresentationData.with({ $0 }).strings)
let context = AccountContext(sharedContext: sharedContext, account: account, limitsConfiguration: limitsConfiguration)
return (context, data.data)
}
}

View File

@ -361,6 +361,8 @@
D0AF32291FACA1920097362B /* Accelerate.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D0D17E891CAAD66600C4750B /* Accelerate.framework */; };
D0AF322C1FACA1B00097362B /* libc++.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = D0B8445F1DACF561005F29E1 /* libc++.tbd */; };
D0AF322F1FACBA280097362B /* TGShareLocationSignals.m in Sources */ = {isa = PBXBuildFile; fileRef = D0AF322D1FACBA270097362B /* TGShareLocationSignals.m */; };
D0B21B0D2203A9A1003F741D /* SharedWakeupManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0B21B0C2203A9A1003F741D /* SharedWakeupManager.swift */; };
D0B21B0F220438E9003F741D /* SharedNotificationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0B21B0E220438E9003F741D /* SharedNotificationManager.swift */; };
D0B2F738204F4C9900D3BFB9 /* NotificationCenter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D0E41A381D65A69C00FBFC00 /* NotificationCenter.framework */; };
D0B2F742204F4C9900D3BFB9 /* Widget.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = D0B2F737204F4C9900D3BFB9 /* Widget.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
D0B2F74A204F4D6100D3BFB9 /* Postbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D0B2F74F204F4D6100D3BFB9 /* Postbox.framework */; };
@ -1092,6 +1094,8 @@
D0AF322A1FACA1A80097362B /* libstdc++.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = "libstdc++.tbd"; path = "usr/lib/libstdc++.tbd"; sourceTree = SDKROOT; };
D0AF322D1FACBA270097362B /* TGShareLocationSignals.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TGShareLocationSignals.m; sourceTree = "<group>"; };
D0AF322E1FACBA270097362B /* TGShareLocationSignals.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TGShareLocationSignals.h; sourceTree = "<group>"; };
D0B21B0C2203A9A1003F741D /* SharedWakeupManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SharedWakeupManager.swift; sourceTree = "<group>"; };
D0B21B0E220438E9003F741D /* SharedNotificationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SharedNotificationManager.swift; sourceTree = "<group>"; };
D0B2F737204F4C9900D3BFB9 /* Widget.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = Widget.appex; sourceTree = BUILT_PRODUCTS_DIR; };
D0B2F74E204F4D6100D3BFB9 /* Display.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Display.framework; sourceTree = BUILT_PRODUCTS_DIR; };
D0B2F74F204F4D6100D3BFB9 /* Postbox.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Postbox.framework; sourceTree = BUILT_PRODUCTS_DIR; };
@ -1975,6 +1979,8 @@
D0ADF956212B56C200310BBC /* Legacy Data Import */,
D0ECCB8B1FE9CE2B00609802 /* Snapshots */,
D008599F1B28189D00EAF753 /* Supporting Files */,
D0B21B0C2203A9A1003F741D /* SharedWakeupManager.swift */,
D0B21B0E220438E9003F741D /* SharedNotificationManager.swift */,
);
path = "Telegram-iOS";
sourceTree = "<group>";
@ -3212,6 +3218,7 @@
D039FB172170F06A00BD1BAD /* PreFetchedLegacyResource.swift in Sources */,
09D304292174343300C00567 /* TGBridgeBotInfo.m in Sources */,
09D304272174341E00C00567 /* TGBridgeChatMessages.m in Sources */,
D0B21B0F220438E9003F741D /* SharedNotificationManager.swift in Sources */,
D02E31231BD803E800CD3F01 /* main.m in Sources */,
D05B37FD1FEA8D870041D2A5 /* SnapshotResources.swift in Sources */,
D0EB243B201B77C400F6CC13 /* ClearNotificationsManager.swift in Sources */,
@ -3221,6 +3228,7 @@
D06E4C2F21347D9200088087 /* UIImage+ImageEffects.m in Sources */,
D0B3B53B21666C0000FC60A0 /* LegacyFileImport.swift in Sources */,
D0ADF95E212C818F00310BBC /* LegacyPreferencesImport.swift in Sources */,
D0B21B0D2203A9A1003F741D /* SharedWakeupManager.swift in Sources */,
09D304282174342E00C00567 /* TGBridgeChat.m in Sources */,
09C50E8A2173AEDB009E676F /* WatchRequestHandlers.swift in Sources */,
09D304302174344900C00567 /* TGBridgeForwardedMessageMediaAttachment.m in Sources */,

View File

@ -120,6 +120,18 @@ private enum QueuedWakeup: Int32 {
case backgroundLocation
}
private final class SharedApplicationContext {
let sharedContext: SharedAccountContext
let notificationManager: SharedNotificationManager
let wakeupManager: SharedWakeupManager
init(sharedContext: SharedAccountContext, notificationManager: SharedNotificationManager, wakeupManager: SharedWakeupManager) {
self.sharedContext = sharedContext
self.notificationManager = notificationManager
self.wakeupManager = wakeupManager
}
}
@objc(AppDelegate) class AppDelegate: UIResponder, UIApplicationDelegate, PKPushRegistryDelegate, BITHockeyManagerDelegate, UNUserNotificationCenterDelegate, UIAlertViewDelegate {
@objc var window: UIWindow?
var nativeWindow: (UIWindow & WindowHost)?
@ -134,13 +146,17 @@ private enum QueuedWakeup: Int32 {
private var isActiveValue = false
let hasActiveAudioSession = Promise<Bool>(false)
private let sharedContextPromise = Promise<SharedAccountContext>()
private let sharedContextPromise = Promise<SharedApplicationContext>()
private let watchCommunicationManagerPromise = Promise<WatchCommunicationManager?>()
private var contextValue: ApplicationContext?
private let context = Promise<ApplicationContext?>()
private var contextValue: AuthorizedApplicationContext?
private let context = Promise<AuthorizedApplicationContext?>()
private let contextDisposable = MetaDisposable()
private var authContextValue: UnauthorizedApplicationContext?
private let authContext = Promise<UnauthorizedApplicationContext?>()
private let authContextDisposable = MetaDisposable()
private let openChatWhenReadyDisposable = MetaDisposable()
private let openUrlWhenReadyDisposable = MetaDisposable()
@ -193,8 +209,8 @@ private enum QueuedWakeup: Int32 {
}
}
private var queuedNotifications: [PKPushPayload] = []
private var queuedNotificationRequests: [(String, String, String?, NotificationManagedNotificationRequestId)] = []
//private var queuedNotifications: [[AnyHashable: Any]] = []
//private var queuedNotificationRequests: [(String, String, String?, NotificationManagedNotificationRequestId)] = []
private var queuedMutePolling = false
private var queuedAnnouncements: [String] = []
private var queuedWakeups = Set<QueuedWakeup>()
@ -220,7 +236,7 @@ private enum QueuedWakeup: Int32 {
self.window = window
self.nativeWindow = window
self.clearNotificationsManager = ClearNotificationsManager(getNotificationIds: { completion in
let clearNotificationsManager = ClearNotificationsManager(getNotificationIds: { completion in
if #available(iOS 10.0, *) {
UNUserNotificationCenter.current().getDeliveredNotifications(completionHandler: { notifications in
var result: [(String, NotificationManagedNotificationRequestId)] = []
@ -323,6 +339,7 @@ private enum QueuedWakeup: Int32 {
}
}
})
self.clearNotificationsManager = clearNotificationsManager
#if DEBUG
for argument in ProcessInfo.processInfo.arguments {
@ -543,7 +560,7 @@ private enum QueuedWakeup: Int32 {
UIApplication.shared.openURL(url)
}
}, registerForNotifications: { completion in
let _ = (self.currentAuthorizedContext()
let _ = (self.context.get()
|> take(1)
|> deliverOnMainQueue).start(next: { context in
if let context = context {
@ -584,18 +601,51 @@ private enum QueuedWakeup: Int32 {
})
let accountManager = AccountManager(basePath: rootPath + "/accounts-metadata")
let sharedContext = SharedAccountContext(accountManager: accountManager, applicationBindings: applicationBindings, networkArguments: networkArguments, rootPath: rootPath, apsNotificationToken: self.notificationTokenPromise.get() |> map(Optional.init), voipNotificationToken: self.voipTokenPromise.get() |> map(Optional.init))
var initialPresentationDataAndSettings: InitialPresentationDataAndSettings?
let semaphore = DispatchSemaphore(value: 0)
let _ = currentPresentationDataAndSettings(accountManager: accountManager).start(next: { value in
initialPresentationDataAndSettings = value
semaphore.signal()
})
semaphore.wait()
self.sharedContextPromise.set(
accountManager.transaction { transaction -> (SharedAccountContext, LoggingSettings) in
return (sharedContext, transaction.getSharedData(SharedDataKeys.loggingSettings) as? LoggingSettings ?? LoggingSettings.defaultSettings)
let sharedContext = SharedAccountContext(mainWindow: self.mainWindow, accountManager: accountManager, applicationBindings: applicationBindings, initialPresentationDataAndSettings: initialPresentationDataAndSettings!, networkArguments: networkArguments, rootPath: rootPath, apsNotificationToken: self.notificationTokenPromise.get() |> map(Optional.init), voipNotificationToken: self.voipTokenPromise.get() |> map(Optional.init))
sharedContext.presentGlobalController = { [weak self] c, a in
guard let strongSelf = self else {
return
}
strongSelf.mainWindow.present(c, on: .root)
}
|> mapToSignal { sharedContext, loggingSettings -> Signal<SharedAccountContext, NoError> in
sharedContext.presentCrossfadeController = { [weak self] in
guard let strongSelf = self else {
return
}
var exists = false
strongSelf.mainWindow.forEachViewController { controller in
if controller is ThemeSettingsCrossfadeController {
exists = true
}
return true
}
if !exists {
strongSelf.mainWindow.present(ThemeSettingsCrossfadeController(), on: .root)
}
}
let notificationManager = SharedNotificationManager(episodeId: self.episodeId, clearNotificationsManager: clearNotificationsManager, inForeground: applicationBindings.applicationInForeground, accounts: sharedContext.activeAccounts |> map { primary, accounts, _ in Array(accounts.values.map({ ($0, $0.id == primary?.id) })) })
let wakeupManager = SharedWakeupManager(activeAccounts: sharedContext.activeAccounts |> map { ($0.0, $0.1) }, inForeground: applicationBindings.applicationInForeground, hasActiveAudioSession: hasActiveAudioSession.get(), notificationManager: notificationManager)
let sharedApplicationContext = SharedApplicationContext(sharedContext: sharedContext, notificationManager: notificationManager, wakeupManager: wakeupManager)
self.sharedContextPromise.set(
accountManager.transaction { transaction -> (SharedApplicationContext, LoggingSettings) in
return (sharedApplicationContext, transaction.getSharedData(SharedDataKeys.loggingSettings) as? LoggingSettings ?? LoggingSettings.defaultSettings)
}
|> mapToSignal { sharedApplicationContext, loggingSettings -> Signal<SharedApplicationContext, NoError> in
Logger.shared.logToFile = loggingSettings.logToFile
Logger.shared.logToConsole = loggingSettings.logToConsole
Logger.shared.redactSensitiveData = loggingSettings.redactSensitiveData
return importedLegacyAccount(basePath: appGroupUrl.path, accountManager: sharedContext.accountManager, present: { controller in
return importedLegacyAccount(basePath: appGroupUrl.path, accountManager: sharedApplicationContext.sharedContext.accountManager, present: { controller in
self.window?.rootViewController?.present(controller, animated: true, completion: nil)
})
|> `catch` { _ -> Signal<ImportedLegacyAccountEvent, NoError> in
@ -615,7 +665,7 @@ private enum QueuedWakeup: Int32 {
return EmptyDisposable
} |> runOn(Queue.mainQueue())
}
|> mapToSignal { event -> Signal<SharedAccountContext, NoError> in
|> mapToSignal { event -> Signal<SharedApplicationContext, NoError> in
switch event {
case let .progress(type, value):
Queue.mainQueue().async {
@ -642,7 +692,7 @@ private enum QueuedWakeup: Int32 {
let _ = try? FileManager.default.createDirectory(atPath: appGroupUrl.path + "/Documents", withIntermediateDirectories: true, attributes: nil)
let _ = try? Data().write(to: URL(fileURLWithPath: statusPath))
})
return sharedContext.accountManager.transaction { transaction -> SharedAccountContext in
return sharedApplicationContext.sharedContext.accountManager.transaction { transaction -> SharedApplicationContext in
transaction.setCurrentId(temporaryId)
transaction.updateRecord(temporaryId, { record in
if let record = record {
@ -650,10 +700,10 @@ private enum QueuedWakeup: Int32 {
}
return record
})
return sharedContext
return sharedApplicationContext
}
} else {
return .single(sharedContext)
return .single(sharedApplicationContext)
}
}
}
@ -672,10 +722,55 @@ private enum QueuedWakeup: Int32 {
self.context.set(self.sharedContextPromise.get()
|> deliverOnMainQueue
|> mapToSignal { sharedContext -> Signal<ApplicationContext?, NoError> in
return sharedContext.activeAccounts
|> map { primary, _, auth -> (AnyObject?, Bool) in
return (auth ?? primary, primary != nil)
|> mapToSignal { sharedApplicationContext -> Signal<AuthorizedApplicationContext?, NoError> in
return sharedApplicationContext.sharedContext.activeAccounts
|> map { primary, _, _ -> Account? in
return primary
}
|> distinctUntilChanged(isEqual: { lhs, rhs in
if lhs !== rhs {
return false
}
return true
})
|> mapToSignal { account -> Signal<(Account, LimitsConfiguration, CallListSettings)?, NoError> in
return sharedApplicationContext.sharedContext.accountManager.transaction { transaction -> CallListSettings in
return transaction.getSharedData(ApplicationSpecificSharedDataKeys.callListSettings) as? CallListSettings ?? CallListSettings.defaultSettings
}
|> mapToSignal { callListSettings -> Signal<(Account, LimitsConfiguration, CallListSettings)?, NoError> in
if let account = account {
return account.postbox.transaction { transaction -> (Account, LimitsConfiguration, CallListSettings)? in
let limitsConfiguration = transaction.getPreferencesEntry(key: PreferencesKeys.limitsConfiguration) as? LimitsConfiguration ?? LimitsConfiguration.defaultValue
return (account, limitsConfiguration, callListSettings)
}
} else {
return .single(nil)
}
}
}
|> deliverOnMainQueue
|> map { accountAndSettings -> AuthorizedApplicationContext? in
return accountAndSettings.flatMap { account, limitsConfiguration, callListSettings in
let context = AccountContext(sharedContext: sharedApplicationContext.sharedContext, account: account, limitsConfiguration: limitsConfiguration)
return AuthorizedApplicationContext(mainWindow: self.mainWindow, replyFromNotificationsActive: replyFromNotificationsActive, backgroundAudioActive: backgroundAudioActive, watchManagerArguments: watchManagerArgumentsPromise.get(), context: context, accountManager: sharedApplicationContext.sharedContext.accountManager, legacyBasePath: appGroupUrl.path, showCallsTab: callListSettings.showTab, reinitializedNotificationSettings: {
let _ = (self.context.get()
|> take(1)
|> deliverOnMainQueue).start(next: { context in
if let context = context {
self.registerForNotifications(context: context.context, authorize: false)
}
})
})
}
}
})
self.authContext.set(self.sharedContextPromise.get()
|> deliverOnMainQueue
|> mapToSignal { sharedApplicationContext -> Signal<UnauthorizedApplicationContext?, NoError> in
return sharedApplicationContext.sharedContext.activeAccounts
|> map { _, accounts, auth -> (UnauthorizedAccount?, Bool) in
return (auth, !accounts.isEmpty)
}
|> distinctUntilChanged(isEqual: { lhs, rhs in
if lhs.0 !== rhs.0 {
@ -683,102 +778,64 @@ private enum QueuedWakeup: Int32 {
}
return true
})
|> mapToSignal { account, hasOther -> Signal<(AnyObject, InitialPresentationDataAndSettings, Bool)?, NoError> in
if let account = account as? Account {
return currentPresentationDataAndSettings(accountManager: sharedContext.accountManager, postbox: account.postbox)
|> map { initialSettings in
return (account, initialSettings, hasOther)
|> mapToSignal { account, hasOther -> Signal<(UnauthorizedAccount, LimitsConfiguration, CallListSettings, Bool)?, NoError> in
return sharedApplicationContext.sharedContext.accountManager.transaction { transaction -> CallListSettings in
return transaction.getSharedData(ApplicationSpecificSharedDataKeys.callListSettings) as? CallListSettings ?? CallListSettings.defaultSettings
}
} else if let account = account as? UnauthorizedAccount {
return currentPresentationDataAndSettings(accountManager: sharedContext.accountManager, postbox: account.postbox)
|> map { initialSettings in
return (account, initialSettings, hasOther)
|> mapToSignal { callListSettings -> Signal<(UnauthorizedAccount, LimitsConfiguration, CallListSettings, Bool)?, NoError> in
if let account = account {
return account.postbox.transaction { transaction -> (UnauthorizedAccount, LimitsConfiguration, CallListSettings, Bool)? in
let limitsConfiguration = transaction.getPreferencesEntry(key: PreferencesKeys.limitsConfiguration) as? LimitsConfiguration ?? LimitsConfiguration.defaultValue
return (account, limitsConfiguration, callListSettings, hasOther)
}
} else {
return .single(nil)
}
} else {
return .single(nil)
}
}
|> deliverOnMainQueue
|> map { accountAndSettings -> ApplicationContext? in
return accountAndSettings.flatMap { account, initialSettings, hasOther in
if let account = account as? Account {
let context = AccountContext(sharedContext: sharedContext, account: account, initialPresentationDataAndSettings: initialSettings)
return ApplicationContext.authorized(AuthorizedApplicationContext(mainWindow: self.mainWindow, replyFromNotificationsActive: replyFromNotificationsActive, backgroundAudioActive: backgroundAudioActive, watchManagerArguments: watchManagerArgumentsPromise.get(), context: context, accountManager: sharedContext.accountManager, legacyBasePath: appGroupUrl.path, showCallsTab: initialSettings.callListSettings.showTab, reinitializedNotificationSettings: {
let _ = (self.context.get()
|> take(1)
|> deliverOnMainQueue).start(next: { value in
if let value = value, case let .authorized(context) = value {
self.registerForNotifications(context: context.context, authorize: false)
}
})
}))
} else if let account = account as? UnauthorizedAccount {
return ApplicationContext.unauthorized(UnauthorizedApplicationContext(sharedContext: sharedContext, account: account, hasOtherAccounts: hasOther))
} else {
return nil
}
|> map { accountAndSettings -> UnauthorizedApplicationContext? in
return accountAndSettings.flatMap { account, limitsConfiguration, callListSettings, hasOther in
return UnauthorizedApplicationContext(sharedContext: sharedApplicationContext.sharedContext, account: account, hasOtherAccounts: hasOther)
}
}
})
let contextReadyDisposable = MetaDisposable()
self.contextDisposable.set(self.context.get().start(next: { context in
assert(Queue.mainQueue().isCurrent())
self.contextDisposable.set((self.context.get()
|> deliverOnMainQueue).start(next: { context in
var network: Network?
if let context = context {
switch context {
case let .unauthorized(unauthorized):
network = unauthorized.account.network
case let .authorized(authorized):
network = authorized.context.account.network
}
network = context.context.account.network
}
Logger.shared.log("App \(self.episodeId)", "received context \(String(describing: context)) account \(String(describing: context?.accountId)) network \(String(describing: network))")
Logger.shared.log("App \(self.episodeId)", "received context \(String(describing: context)) account \(String(describing: context?.context.account.id)) network \(String(describing: network))")
if let contextValue = self.contextValue {
switch contextValue {
case let .unauthorized(unauthorized):
unauthorized.account.shouldBeServiceTaskMaster.set(.single(.never))
case let .authorized(authorized):
authorized.context.isCurrent = false
authorized.context.account.shouldBeServiceTaskMaster.set(.single(.never))
authorized.context.account.shouldKeepOnlinePresence.set(.single(false))
authorized.context.account.shouldExplicitelyKeepWorkerConnections.set(.single(false))
authorized.context.account.shouldKeepBackgroundDownloadConnections.set(.single(false))
}
contextValue.context.isCurrent = false
contextValue.context.account.shouldExplicitelyKeepWorkerConnections.set(.single(false))
contextValue.context.account.shouldKeepBackgroundDownloadConnections.set(.single(false))
}
self.contextValue = context
if let context = context {
let isReady: Signal<Bool, NoError>
switch context {
case let .authorized(authorized):
setupLegacyComponents(context: authorized.context)
authorized.context.isCurrent = true
isReady = authorized.isReady.get()
default:
isReady = .single(true)
}
setupLegacyComponents(context: context.context)
context.context.isCurrent = true
let isReady = context.isReady.get()
contextReadyDisposable.set((isReady
|> filter { $0 }
|> take(1)
|> deliverOnMainQueue).start(next: { _ in
self.mainWindow.viewController = context.rootController
self.mainWindow.topLevelOverlayControllers = context.overlayControllers
self.maybeDequeueNotificationPayloads()
self.mainWindow.topLevelOverlayControllers = [context.overlayMediaController, context.notificationController]
/*self.maybeDequeueNotificationPayloads()
self.maybeDequeueNotificationRequests()
self.maybeDequeueWakeups()
switch context {
case let .authorized(context):
var authorizeNotifications = true
if #available(iOS 10.0, *) {
authorizeNotifications = false
}
self.registerForNotifications(context: context.context, authorize: authorizeNotifications)
case .unauthorized:
break
self.maybeDequeueWakeups()*/
var authorizeNotifications = true
if #available(iOS 10.0, *) {
authorizeNotifications = false
}
self.registerForNotifications(context: context.context, authorize: authorizeNotifications)
}))
} else {
self.mainWindow.viewController = nil
@ -787,6 +844,38 @@ private enum QueuedWakeup: Int32 {
}
}))
let authContextReadyDisposable = MetaDisposable()
self.authContextDisposable.set((self.authContext.get()
|> deliverOnMainQueue).start(next: { context in
var network: Network?
if let context = context {
network = context.account.network
}
Logger.shared.log("App \(self.episodeId)", "received auth context \(String(describing: context)) account \(String(describing: context?.account.id)) network \(String(describing: network))")
if let authContextValue = self.authContextValue {
authContextValue.account.shouldBeServiceTaskMaster.set(.single(.never))
authContextValue.rootController.view.endEditing(true)
authContextValue.rootController.dismiss()
}
self.authContextValue = context
if let context = context {
let isReady: Signal<Bool, NoError> = .single(true)
authContextReadyDisposable.set((isReady
|> filter { $0 }
|> take(1)
|> deliverOnMainQueue).start(next: { _ in
self.mainWindow.present(context.rootController, on: .root)
//self.mainWindow.viewController = context.rootController
//self.mainWindow.topLevelOverlayControllers = context.overlayControllers
}))
} else {
authContextReadyDisposable.set(nil)
}
}))
self.watchCommunicationManagerPromise.set(watchCommunicationManager(context: self.context))
let _ = self.watchCommunicationManagerPromise.get().start(next: { manager in
if let manager = manager {
@ -804,14 +893,9 @@ private enum QueuedWakeup: Int32 {
self.badgeDisposable.set((self.context.get()
|> mapToSignal { context -> Signal<Int32, NoError> in
if let context = context {
switch context {
case let .authorized(context):
return context.applicationBadge
case .unauthorized:
return .single(0)
}
return context.applicationBadge
} else {
return .never()
return .single(0)
}
}
|> deliverOnMainQueue).start(next: { count in
@ -822,15 +906,10 @@ private enum QueuedWakeup: Int32 {
self.quickActionsDisposable.set((self.context.get()
|> mapToSignal { context -> Signal<[ApplicationShortcutItem], NoError> in
if let context = context {
switch context {
case let .authorized(context):
let presentationData = context.context.currentPresentationData.with { $0 }
return .single(applicationShortcutItems(strings: presentationData.strings))
case .unauthorized:
return .single([])
}
let presentationData = context.context.sharedContext.currentPresentationData.with { $0 }
return .single(applicationShortcutItems(strings: presentationData.strings))
} else {
return .never()
return .single([])
}
}
|> distinctUntilChanged
@ -876,14 +955,8 @@ private enum QueuedWakeup: Int32 {
}
}))
}
if let contextValue = strongSelf.contextValue {
if case let .authorized(context) = contextValue {
let presentationData = context.context.currentPresentationData.with { $0 }
strongSelf.mainWindow.present(standardTextAlertController(theme: AlertControllerTheme(presentationTheme: presentationData.theme), title: alert.title, text: alert.message ?? "", actions: actions), on: .root)
} else if case let .unauthorized(context) = contextValue {
strongSelf.mainWindow.present(standardTextAlertController(theme: AlertControllerTheme(authTheme: context.rootController.theme), title: alert.title, text: alert.message ?? "", actions: actions), on: .root)
}
}
let presentationData = sharedContext.currentPresentationData.with { $0 }
strongSelf.mainWindow.present(standardTextAlertController(theme: AlertControllerTheme(presentationTheme: presentationData.theme), title: alert.title, text: alert.message ?? "", actions: actions), on: .root)
}
})
@ -916,6 +989,12 @@ private enum QueuedWakeup: Int32 {
}
func applicationDidEnterBackground(_ application: UIApplication) {
let _ = (self.sharedContextPromise.get()
|> take(1)
|> deliverOnMainQueue).start(next: { sharedApplicationContext in
sharedApplicationContext.wakeupManager.allowBackgroundTimeExtension(timeout: 4.0)
})
self.isInForegroundValue = false
self.isInForegroundPromise.set(false)
self.isActiveValue = false
@ -964,6 +1043,12 @@ private enum QueuedWakeup: Int32 {
}
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
let _ = (self.sharedContextPromise.get()
|> take(1)
|> deliverOnMainQueue).start(next: { sharedApplicationContext in
sharedApplicationContext.wakeupManager.allowBackgroundTimeExtension(timeout: 4.0)
})
var redactedPayload = userInfo
if var aps = redactedPayload["aps"] as? [AnyHashable: Any] {
if Logger.shared.redactSensitiveData {
@ -977,7 +1062,6 @@ private enum QueuedWakeup: Int32 {
redactedPayload["aps"] = aps
}
Logger.shared.log("App \(self.episodeId)", "remoteNotification: \(redactedPayload)")
completionHandler(UIBackgroundFetchResult.noData)
}
@ -996,28 +1080,24 @@ private enum QueuedWakeup: Int32 {
}
}
private var pushCnt = 0
public func pushRegistry(_ registry: PKPushRegistry, didReceiveIncomingPushWith payload: PKPushPayload, for type: PKPushType) {
if case PKPushType.voIP = type {
Logger.shared.log("App \(self.episodeId)", "pushRegistry payload: \(payload.dictionaryPayload)")
/*#if DEBUG
self.pushCnt += 1
if self.pushCnt % 2 != 0 {
Logger.shared.log("App \(self.episodeId)", "pushRegistry payload drop")
return
}
#endif*/
let _ = (self.sharedContextPromise.get()
|> take(1)
|> deliverOnMainQueue).start(next: { sharedApplicationContext in
sharedApplicationContext.wakeupManager.allowBackgroundTimeExtension(timeout: 4.0)
self.queuedNotifications.append(payload)
self.maybeDequeueNotificationPayloads()
}
if case PKPushType.voIP = type {
Logger.shared.log("App \(self.episodeId)", "pushRegistry payload: \(payload.dictionaryPayload)")
sharedApplicationContext.notificationManager.addEncryptedNotification(payload.dictionaryPayload)
}
})
}
private func processPushPayload(_ payload: PKPushPayload, account: Account) {
/*private func processPushPayload(_ payload: [AnyHashable: Any], account: Account) {
let decryptedPayload: Signal<[AnyHashable: Any]?, NoError>
if let _ = payload.dictionaryPayload["aps"] as? [AnyHashable: Any] {
decryptedPayload = .single(payload.dictionaryPayload as [AnyHashable: Any])
} else if var encryptedPayload = payload.dictionaryPayload["p"] as? String {
if let _ = payload["aps"] as? [AnyHashable: Any] {
decryptedPayload = .single(payload)
} else if var encryptedPayload = payload["p"] as? String {
encryptedPayload = encryptedPayload.replacingOccurrences(of: "-", with: "+")
encryptedPayload = encryptedPayload.replacingOccurrences(of: "_", with: "/")
while encryptedPayload.count % 4 != 0 {
@ -1234,7 +1314,7 @@ private enum QueuedWakeup: Int32 {
self.clearNotificationsManager?.append(readMessageId)
self.clearNotificationsManager?.commitNow()
let signal = self.currentAuthorizedContext()
let signal = self.context.get()
|> take(1)
|> mapToSignal { context -> Signal<Void, NoError> in
if let context = context {
@ -1249,7 +1329,7 @@ private enum QueuedWakeup: Int32 {
}
if let (datacenterId, host, port, secret) = configurationUpdate {
let signal = self.currentAuthorizedContext()
let signal = self.context.get()
|> take(1)
|> mapToSignal { context -> Signal<Void, NoError> in
if let context = context {
@ -1261,28 +1341,16 @@ private enum QueuedWakeup: Int32 {
}
}
})
}
}*/
public func pushRegistry(_ registry: PKPushRegistry, didInvalidatePushTokenFor type: PKPushType) {
Logger.shared.log("App \(self.episodeId)", "invalidated token for \(type)")
}
private func currentAuthorizedContext() -> Signal<AuthorizedApplicationContext?, NoError> {
return self.context.get()
|> take(1)
|> mapToSignal { contextValue -> Signal<AuthorizedApplicationContext?, NoError> in
if let contextValue = contextValue, case let .authorized(context) = contextValue {
return .single(context)
} else {
return .single(nil)
}
}
}
private func authorizedContext() -> Signal<AuthorizedApplicationContext, NoError> {
return self.context.get()
|> mapToSignal { contextValue -> Signal<AuthorizedApplicationContext, NoError> in
if let contextValue = contextValue, case let .authorized(context) = contextValue {
|> mapToSignal { context -> Signal<AuthorizedApplicationContext, NoError> in
if let context = context {
return .single(context)
} else {
return .complete()
@ -1311,30 +1379,35 @@ private enum QueuedWakeup: Int32 {
}
private func openUrl(url: URL) {
let _ = (self.context.get()
|> flatMap { $0 }
let _ = (self.sharedContextPromise.get()
|> take(1)
|> deliverOnMainQueue).start(next: { contextValue in
switch contextValue {
case let .authorized(context):
context.openUrl(url)
case let .unauthorized(context):
if let proxyData = parseProxyUrl(url) {
context.rootController.view.endEditing(true)
let strings = context.strings
let controller = ProxyServerActionSheetController(theme: defaultPresentationTheme, strings: strings, accountManager: context.sharedContext.accountManager, postbox: context.account.postbox, network: context.account.network, server: proxyData, presentationData: nil)
context.rootController.currentWindow?.present(controller, on: PresentationSurfaceLevel.root, blockInteraction: false, completion: {})
} else if let secureIdData = parseSecureIdUrl(url) {
let strings = context.strings
let theme = context.rootController.theme
context.rootController.currentWindow?.present(standardTextAlertController(theme: AlertControllerTheme(authTheme: theme), title: nil, text: strings.Passport_NotLoggedInMessage, actions: [TextAlertAction(type: .genericAction, title: strings.Calls_NotNow, action: {
if let callbackUrl = URL(string: secureIdCallbackUrl(with: secureIdData.callbackUrl, peerId: secureIdData.peerId, result: .cancel, parameters: [:])) {
UIApplication.shared.openURL(callbackUrl)
}
}), TextAlertAction(type: .defaultAction, title: strings.Common_OK, action: {})]), on: .root, blockInteraction: false, completion: {})
} else if let confirmationCode = parseConfirmationCodeUrl(url) {
context.rootController.applyConfirmationCode(confirmationCode)
}
|> mapToSignal { sharedApplicationContext -> Signal<(SharedAccountContext, AuthorizedApplicationContext?, UnauthorizedApplicationContext?), NoError> in
combineLatest(self.context.get(), self.authContext.get())
|> filter { $0 != nil || $1 != nil }
|> take(1)
|> map { context, authContext -> (SharedAccountContext, AuthorizedApplicationContext?, UnauthorizedApplicationContext?) in
return (sharedApplicationContext.sharedContext, context, authContext)
}
}
|> deliverOnMainQueue).start(next: { _, context, authContext in
if let context = context {
context.openUrl(url)
} else if let authContext = authContext {
if let proxyData = parseProxyUrl(url) {
authContext.rootController.view.endEditing(true)
let presentationData = authContext.sharedContext.currentPresentationData.with { $0 }
let controller = ProxyServerActionSheetController(theme: presentationData.theme, strings: presentationData.strings, accountManager: authContext.sharedContext.accountManager, postbox: authContext.account.postbox, network: authContext.account.network, server: proxyData, presentationData: nil)
authContext.rootController.currentWindow?.present(controller, on: PresentationSurfaceLevel.root, blockInteraction: false, completion: {})
} else if let secureIdData = parseSecureIdUrl(url) {
let presentationData = authContext.sharedContext.currentPresentationData.with { $0 }
authContext.rootController.currentWindow?.present(standardTextAlertController(theme: AlertControllerTheme(presentationTheme: presentationData.theme), title: nil, text: presentationData.strings.Passport_NotLoggedInMessage, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Calls_NotNow, action: {
if let callbackUrl = URL(string: secureIdCallbackUrl(with: secureIdData.callbackUrl, peerId: secureIdData.peerId, result: .cancel, parameters: [:])) {
UIApplication.shared.openURL(callbackUrl)
}
}), TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), on: .root, blockInteraction: false, completion: {})
} else if let confirmationCode = parseConfirmationCodeUrl(url) {
authContext.rootController.applyConfirmationCode(confirmationCode)
}
}
})
}
@ -1345,8 +1418,8 @@ private enum QueuedWakeup: Int32 {
if let contact = startCallIntent.contacts?.first {
if let handle = contact.personHandle?.value {
if let userId = Int32(handle) {
if let contextValue = self.contextValue, case let .authorized(context) = contextValue {
let _ = context.context.callManager?.requestCall(account: context.context.account, peerId: PeerId(namespace: Namespaces.Peer.CloudUser, id: userId), endCurrentIfAny: false)
if let context = self.contextValue {
let _ = context.context.sharedContext.callManager?.requestCall(account: context.context.account, peerId: PeerId(namespace: Namespaces.Peer.CloudUser, id: userId), endCurrentIfAny: false)
}
}
}
@ -1364,25 +1437,21 @@ private enum QueuedWakeup: Int32 {
@available(iOS 9.0, *)
func application(_ application: UIApplication, performActionFor shortcutItem: UIApplicationShortcutItem, completionHandler: @escaping (Bool) -> Void) {
let _ = (self.context.get()
|> mapToSignal { value -> Signal<ApplicationContext?, NoError> in
if let value = value {
if case let .authorized(context) = value {
return context.unlockedState
|> filter { $0 }
|> take(1)
|> map { _ -> ApplicationContext? in
return value
}
} else {
return .single(nil)
|> mapToSignal { context -> Signal<AuthorizedApplicationContext?, NoError> in
if let context = context {
return context.unlockedState
|> filter { $0 }
|> take(1)
|> map { _ -> AuthorizedApplicationContext? in
return context
}
} else {
return .complete()
}
}
|> take(1)
|> deliverOnMainQueue).start(next: { contextValue in
if let contextValue = contextValue, case let .authorized(context) = contextValue {
|> deliverOnMainQueue).start(next: { context in
if let context = context {
if let type = ApplicationShortcutItemType(rawValue: shortcutItem.type) {
switch type {
case .search:
@ -1402,9 +1471,9 @@ private enum QueuedWakeup: Int32 {
private func openChatWhenReady(accountId: AccountRecordId?, peerId: PeerId, messageId: MessageId? = nil) {
let signal = self.sharedContextPromise.get()
|> take(1)
|> mapToSignal { sharedContext -> Signal<AuthorizedApplicationContext, NoError> in
|> mapToSignal { sharedApplicationContext -> Signal<AuthorizedApplicationContext, NoError> in
if let accountId = accountId {
sharedContext.switchToAccount(id: accountId)
sharedApplicationContext.sharedContext.switchToAccount(id: accountId)
return self.authorizedContext()
|> filter { context in
context.context.account.id == accountId
@ -1425,7 +1494,7 @@ private enum QueuedWakeup: Int32 {
self.openUrlWhenReadyDisposable.set((self.authorizedContext()
|> take(1)
|> deliverOnMainQueue).start(next: { context in
let presentationData = context.context.currentPresentationData.with { $0 }
let presentationData = context.context.sharedContext.currentPresentationData.with { $0 }
openExternalUrl(context: context.context, url: url, presentationData: presentationData, navigationController: context.rootController, dismissInput: {
})
}))
@ -1511,7 +1580,7 @@ private enum QueuedWakeup: Int32 {
}
private func registerForNotifications(context: AccountContext, authorize: Bool = true, completion: @escaping (Bool) -> Void = { _ in }) {
let presentationData = context.currentPresentationData.with { $0 }
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
let _ = (context.sharedContext.accountManager.transaction { transaction -> Bool in
let settings = transaction.getSharedData(ApplicationSpecificSharedDataKeys.inAppNotificationSettings) as? InAppNotificationSettings ?? InAppNotificationSettings.defaultSettings
return settings.displayNameOnLockscreen
@ -1582,8 +1651,8 @@ private enum QueuedWakeup: Int32 {
}
}
private func maybeDequeueNotificationPayloads() {
if let contextValue = self.contextValue, case let .authorized(context) = contextValue, !self.queuedNotifications.isEmpty {
/*private func maybeDequeueNotificationPayloads() {
if let context = self.contextValue, !self.queuedNotifications.isEmpty {
let queuedNotifications = self.queuedNotifications
self.queuedNotifications = []
for payload in queuedNotifications {
@ -1593,7 +1662,7 @@ private enum QueuedWakeup: Int32 {
}
private func maybeDequeueNotificationRequests() {
if let contextValue = self.contextValue, case let .authorized(context) = contextValue {
if let context = self.contextValue {
let requests = self.queuedNotificationRequests
self.queuedNotificationRequests = []
let queuedMutePolling = self.queuedMutePolling
@ -1603,20 +1672,20 @@ private enum QueuedWakeup: Int32 {
return transaction.getAccessChallengeData()
})
|> deliverOnMainQueue).start(next: { accessChallengeData in
guard let contextValue = self.contextValue, case let .authorized(context) = contextValue else {
guard let context = self.contextValue else {
Logger.shared.log("App \(self.episodeId)", "Couldn't process remote notification request")
return
}
let strings = context.context.currentPresentationData.with({ $0 }).strings
let strings = context.context.sharedContext.currentPresentationData.with({ $0 }).strings
for (title, body, apnsSound, requestId) in requests {
if handleVoipNotifications {
context.notificationManager.enqueueRemoteNotification(title: title, text: body, apnsSound: apnsSound, requestId: requestId, strings: strings, accessChallengeData: accessChallengeData)
//context.notificationManager.enqueueRemoteNotification(title: title, text: body, apnsSound: apnsSound, requestId: requestId, strings: strings, accessChallengeData: accessChallengeData)
}
context.wakeupManager.wakeupForIncomingMessages(account: context.context.account, completion: { messageIds -> Signal<Void, NoError> in
if let contextValue = self.contextValue, case let .authorized(context) = contextValue {
/*context.wakeupManager.wakeupForIncomingMessages(account: context.context.account, completion: { messageIds -> Signal<Void, NoError> in
if let context = self.contextValue {
if handleVoipNotifications {
return context.notificationManager.commitRemoteNotification(context: context.context, originalRequestId: requestId, messageIds: messageIds)
} else {
@ -1626,17 +1695,17 @@ private enum QueuedWakeup: Int32 {
Logger.shared.log("App \(self.episodeId)", "Couldn't process remote notifications wakeup result")
return .complete()
}
})
})*/
}
if queuedMutePolling {
context.wakeupManager.wakeupForIncomingMessages(account: context.context.account, completion: { messageIds -> Signal<Void, NoError> in
if let contextValue = self.contextValue, case .authorized = contextValue {
/*context.wakeupManager.wakeupForIncomingMessages(account: context.context.account, completion: { messageIds -> Signal<Void, NoError> in
if let context = self.contextValue {
return .single(Void())
} else {
Logger.shared.log("App \(self.episodeId)", "Couldn't process remote notifications wakeup result")
return .single(Void())
}
})
})*/
}
})
} else {
@ -1645,7 +1714,7 @@ private enum QueuedWakeup: Int32 {
}
private func maybeDequeueAnnouncements() {
if let contextValue = self.contextValue, case let .authorized(context) = contextValue, !self.queuedAnnouncements.isEmpty {
if let context = self.contextValue, !self.queuedAnnouncements.isEmpty {
let queuedAnnouncements = self.queuedAnnouncements
self.queuedAnnouncements = []
let _ = (context.context.account.postbox.transaction(ignoreDisabled: true, { transaction -> [MessageId: String] in
@ -1672,9 +1741,9 @@ private enum QueuedWakeup: Int32 {
}
return result
}) |> deliverOnMainQueue).start(next: { result in
if let contextValue = self.contextValue, case let .authorized(context) = contextValue {
if let context = self.contextValue {
for (id, text) in result {
context.notificationManager.enqueueRemoteNotification(title: "", text: text, apnsSound: nil, requestId: .messageId(id), strings: context.context.currentPresentationData.with({ $0 }).strings, accessChallengeData: .none)
//context.notificationManager.enqueueRemoteNotification(title: "", text: text, apnsSound: nil, requestId: .messageId(id), strings: context.context.sharedContext.currentPresentationData.with({ $0 }).strings, accessChallengeData: .none)
}
}
})
@ -1685,12 +1754,12 @@ private enum QueuedWakeup: Int32 {
for wakeup in self.queuedWakeups {
switch wakeup {
case .call:
if let contextValue = self.contextValue, case let .authorized(context) = contextValue {
context.wakeupManager.wakeupForIncomingMessages(account: context.context.account)
if let context = self.contextValue {
//context.wakeupManager.wakeupForIncomingMessages(account: context.context.account)
}
case .backgroundLocation:
if UIApplication.shared.applicationState == .background {
if let contextValue = self.contextValue, case let .authorized(context) = contextValue {
if let context = self.contextValue {
context.context.liveLocationManager?.pollOnce()
}
}
@ -1698,19 +1767,19 @@ private enum QueuedWakeup: Int32 {
}
self.queuedWakeups.removeAll()
}
}*/
@available(iOS 10.0, *)
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
#if DEBUG
completionHandler([.alert])
#else
completionHandler([])
#endif
if let context = self.contextValue {
if let accountId = accountIdFromNotification(notification), context.context.account.id != accountId {
completionHandler([.alert])
}
}
}
override var next: UIResponder? {
if let contextValue = self.contextValue, case let .authorized(context) = contextValue, let controller = context.context.keyShortcutsController {
if let context = self.contextValue, let controller = context.context.keyShortcutsController {
return controller
}
return super.next

View File

@ -87,29 +87,6 @@ private struct PasscodeState: Equatable {
let enableBiometrics: Bool
}
private enum CallStatusText: Equatable {
case none
case inProgress(Double?)
static func ==(lhs: CallStatusText, rhs: CallStatusText) -> Bool {
switch lhs {
case .none:
if case .none = rhs {
return true
} else {
return false
}
case let .inProgress(lhsReferenceTime):
if case let .inProgress(rhsReferenceTime) = rhs, lhsReferenceTime == rhsReferenceTime {
return true
} else {
return false
}
}
}
}
final class AuthorizedApplicationContext {
let mainWindow: Window1
let lockedCoveringView: LockedWindowCoveringView
@ -125,8 +102,7 @@ final class AuthorizedApplicationContext {
private var scheduledOperChatWithPeerId: PeerId?
private var scheduledOpenExternalUrl: URL?
let wakeupManager: WakeupManager
let notificationManager: NotificationManager
//let notificationManager: NotificationManager
private let passcodeStatusDisposable = MetaDisposable()
private let passcodeLockDisposable = MetaDisposable()
@ -142,9 +118,6 @@ final class AuthorizedApplicationContext {
private var isLocked: Bool = true
private var passcodeController: ViewController?
private var callController: CallController?
private let hasOngoingCall = ValuePromise<Bool>(false)
private let callState = Promise<PresentationCallState?>(nil)
private var currentTermsOfServiceUpdate: TermsOfServiceUpdate?
private var currentPermissionsController: PermissionController?
@ -166,10 +139,6 @@ final class AuthorizedApplicationContext {
private var presentationDataDisposable: Disposable?
private var displayAlertsDisposable: Disposable?
private var removeNotificationsDisposable: Disposable?
private var callDisposable: Disposable?
private var callStateDisposable: Disposable?
private var currentCallStatusText: CallStatusText = .none
private var currentCallStatusTextTimer: SwiftSignalKit.Timer?
private var applicationInForegroundDisposable: Disposable?
@ -179,7 +148,7 @@ final class AuthorizedApplicationContext {
init(mainWindow: Window1, replyFromNotificationsActive: Signal<Bool, NoError>, backgroundAudioActive: Signal<Bool, NoError>, watchManagerArguments: Signal<WatchManagerArguments?, NoError>, context: AccountContext, accountManager: AccountManager, legacyBasePath: String, showCallsTab: Bool, reinitializedNotificationSettings: @escaping () -> Void) {
setupLegacyComponents(context: context)
let presentationData = context.currentPresentationData.with { $0 }
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
self.mainWindow = mainWindow
self.lockedCoveringView = LockedWindowCoveringView(theme: presentationData.theme)
@ -207,29 +176,18 @@ final class AuthorizedApplicationContext {
}
|> distinctUntilChanged
self.wakeupManager = WakeupManager(accountManager: context.sharedContext.accountManager, inForeground: context.sharedContext.applicationBindings.applicationInForeground, runningServiceTasks: context.account.importantTasksRunning, runningBackgroundLocationTasks: runningBackgroundLocationTasks, runningWatchTasks: runningWatchTasksPromise.get(), runningDownloadTasks: runningDownloadTasks)
//self.wakeupManager = WakeupManager(accountManager: context.sharedContext.accountManager, inForeground: context.sharedContext.applicationBindings.applicationInForeground, runningServiceTasks: context.account.importantTasksRunning, runningBackgroundLocationTasks: runningBackgroundLocationTasks, runningWatchTasks: runningWatchTasksPromise.get(), runningDownloadTasks: runningDownloadTasks)
self.showCallsTab = showCallsTab
self.notificationManager = NotificationManager()
self.notificationManager.isApplicationInForeground = false
//self.notificationManager = NotificationManager()
//self.notificationManager.isApplicationInForeground = false
self.overlayMediaController = OverlayMediaController()
context.attachOverlayMediaController(self.overlayMediaController)
var presentImpl: ((ViewController, Any?) -> Void)?
var openSettingsImpl: (() -> Void)?
let callManager = PresentationCallManager(accountManager: context.sharedContext.accountManager, account: context.account, getDeviceAccessData: {
return (context.currentPresentationData.with { $0 }, { c, a in
presentImpl?(c, a)
}, {
openSettingsImpl?()
})
}, networkType: context.account.networkType, audioSession: context.sharedContext.mediaManager.audioSession, activeAccounts: .single([context.account]))
context.callManager = callManager
context.hasOngoingCall = self.hasOngoingCall.get()
let shouldBeServiceTaskMaster = combineLatest(context.sharedContext.applicationBindings.applicationInForeground, self.wakeupManager.isWokenUp, replyFromNotificationsActive, backgroundAudioActive, callManager.hasActiveCalls)
/*let shouldBeServiceTaskMaster = combineLatest(context.sharedContext.applicationBindings.applicationInForeground, self.wakeupManager.isWokenUp, replyFromNotificationsActive, backgroundAudioActive, callManager.hasActiveCalls)
|> map { foreground, wokenUp, replyFromNotificationsActive, backgroundAudioActive, hasActiveCalls -> AccountServiceTaskMasterMode in
if foreground || wokenUp || replyFromNotificationsActive || hasActiveCalls {
return .always
@ -256,10 +214,8 @@ final class AuthorizedApplicationContext {
Logger.shared.log("ApplicationContext", "setting canBeginTransactions to \(next)")
context.account.postbox.setCanBeginTransactions(next)
}
})
})*/
context.account.shouldExplicitelyKeepWorkerConnections.set(backgroundAudioActive)
context.account.shouldKeepBackgroundDownloadConnections.set(context.fetchManager.hasUserInitiatedEntries)
context.account.shouldKeepOnlinePresence.set(context.sharedContext.applicationBindings.applicationInForeground)
let cache = TGCache(cachesPath: legacyBasePath + "/Caches")!
@ -295,56 +251,11 @@ final class AuthorizedApplicationContext {
context.keyShortcutsController = keyShortcutsController
}
self.applicationInForegroundDisposable = context.sharedContext.applicationBindings.applicationInForeground.start(next: { [weak self] value in
/*self.applicationInForegroundDisposable = context.sharedContext.applicationBindings.applicationInForeground.start(next: { [weak self] value in
Queue.mainQueue().async {
self?.notificationManager.isApplicationInForeground = value
}
})
self.mainWindow.inCallNavigate = { [weak self] in
if let strongSelf = self, let callController = strongSelf.callController {
if callController.isNodeLoaded && callController.view.superview == nil {
strongSelf.rootController.view.endEditing(true)
strongSelf.mainWindow.present(callController, on: .calls)
}
}
}
context.presentGlobalController = { [weak self] c, a in
self?.mainWindow.present(c, on: .root)
}
context.presentCrossfadeController = { [weak self] in
guard let strongSelf = self else {
return
}
var exists = false
strongSelf.mainWindow.forEachViewController { controller in
if controller is ThemeSettingsCrossfadeController {
exists = true
}
return true
}
if !exists {
mainWindow.present(ThemeSettingsCrossfadeController(), on: .root)
}
}
context.navigateToCurrentCall = { [weak self] in
if let strongSelf = self, let callController = strongSelf.callController {
if callController.isNodeLoaded && callController.view.superview == nil {
strongSelf.rootController.view.endEditing(true)
strongSelf.mainWindow.present(callController, on: .calls)
}
}
}
presentImpl = { [weak self] c, _ in
self?.mainWindow.present(c, on: .root)
}
openSettingsImpl = { [weak context] in
context?.sharedContext.applicationBindings.openSettings()
}
})*/
let previousPasscodeState = Atomic<PasscodeState?>(value: nil)
@ -400,7 +311,7 @@ final class AuthorizedApplicationContext {
}
strongSelf.isLocked = isLocked
strongSelf.notificationManager.isApplicationLocked = isLocked
//strongSelf.notificationManager.isApplicationLocked = isLocked
if isLocked {
if updatedState.isActive {
@ -418,7 +329,7 @@ final class AuthorizedApplicationContext {
case .plaintextPassword:
mode = TGPasscodeEntryControllerModeVerifyComplex
}
let presentationData = strongSelf.context.currentPresentationData.with { $0 }
let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 }
let presentAnimated = previousState != nil && previousState!.isActive
let legacyController = LegacyController(presentation: LegacyControllerPresentation.modal(animateIn: presentAnimated), theme: presentationData.theme)
let controller = TGPasscodeEntryController(context: legacyController.context, style: TGPasscodeEntryControllerStyleDefault, mode: mode, cancelEnabled: false, allowTouchId: updatedState.enableBiometrics, attemptData: attemptData, completion: { value in
@ -543,7 +454,7 @@ final class AuthorizedApplicationContext {
if #available(iOS 10.0, *) {
} else {
DeviceAccess.authorizeAccess(to: .contacts, presentationData: strongSelf.context.currentPresentationData.with { $0 }, present: { c, a in
DeviceAccess.authorizeAccess(to: .contacts, presentationData: strongSelf.context.sharedContext.currentPresentationData.with { $0 }, present: { c, a in
}, openSettings: {}, { _ in })
}
@ -657,7 +568,7 @@ final class AuthorizedApplicationContext {
}
if inAppNotificationSettings.displayPreviews {
let presentationData = strongSelf.context.currentPresentationData.with { $0 }
let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 }
strongSelf.notificationController.enqueue(ChatMessageNotificationItem(context: strongSelf.context, strings: presentationData.strings, nameDisplayOrder: presentationData.nameDisplayOrder, messages: messages, tapAction: {
if let strongSelf = self {
var foundOverlay = false
@ -713,7 +624,7 @@ final class AuthorizedApplicationContext {
strongSelf.currentTermsOfServiceUpdate = termsOfServiceUpdate
if let termsOfServiceUpdate = termsOfServiceUpdate {
let presentationData = strongSelf.context.currentPresentationData.with { $0 }
let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 }
var acceptImpl: ((String?) -> Void)?
var declineImpl: (() -> Void)?
let controller = TermsOfServiceController(theme: TermsOfServiceControllerTheme(presentationTheme: presentationData.theme), strings: presentationData.strings, text: termsOfServiceUpdate.text, entities: termsOfServiceUpdate.entities, ageConfirmation: termsOfServiceUpdate.ageConfirmation, signingUp: false, accept: { proccedBot in
@ -892,7 +803,7 @@ final class AuthorizedApplicationContext {
|> deliverOnMainQueue).start(next: { [weak self] alerts in
if let strongSelf = self{
for text in alerts {
let presentationData = strongSelf.context.currentPresentationData.with { $0 }
let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 }
let controller = standardTextAlertController(theme: AlertControllerTheme(presentationTheme: presentationData.theme), title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})])
(strongSelf.rootController.viewControllers.last as? ViewController)?.present(controller, in: .window(.root))
}
@ -906,84 +817,12 @@ final class AuthorizedApplicationContext {
}
})
self.callDisposable = (callManager.currentCallSignal
|> deliverOnMainQueue).start(next: { [weak self] call in
if let strongSelf = self {
if call !== strongSelf.callController?.call {
strongSelf.callController?.dismiss()
strongSelf.callController = nil
strongSelf.hasOngoingCall.set(false)
if let call = call {
let callController = CallController(context: strongSelf.context, call: call)
strongSelf.callController = callController
strongSelf.rootController.view?.endEditing(true)
strongSelf.mainWindow.present(callController, on: .calls)
strongSelf.callState.set(call.state
|> map(Optional.init))
strongSelf.hasOngoingCall.set(true)
strongSelf.notificationManager.setNotificationCall(call, strings: strongSelf.context.currentPresentationData.with({ $0 }).strings)
} else {
strongSelf.callState.set(.single(nil))
strongSelf.hasOngoingCall.set(false)
strongSelf.notificationManager.setNotificationCall(nil, strings: strongSelf.context.currentPresentationData.with({ $0 }).strings)
}
}
}
})
self.callStateDisposable = (self.callState.get()
|> deliverOnMainQueue).start(next: { [weak self] state in
if let strongSelf = self {
let resolvedText: CallStatusText
if let state = state {
switch state {
case .connecting, .requesting, .terminating, .ringing, .waiting:
resolvedText = .inProgress(nil)
case .terminated:
resolvedText = .none
case let .active(timestamp, _, _):
resolvedText = .inProgress(timestamp)
}
} else {
resolvedText = .none
}
if strongSelf.currentCallStatusText != resolvedText {
strongSelf.currentCallStatusText = resolvedText
var referenceTimestamp: Double?
if case let .inProgress(timestamp) = resolvedText, let concreteTimestamp = timestamp {
referenceTimestamp = concreteTimestamp
}
if let _ = referenceTimestamp {
if strongSelf.currentCallStatusTextTimer == nil {
let timer = SwiftSignalKit.Timer(timeout: 0.5, repeat: true, completion: {
if let strongSelf = self {
strongSelf.updateStatusBarText()
}
}, queue: Queue.mainQueue())
strongSelf.currentCallStatusTextTimer = timer
timer.start()
}
} else {
strongSelf.currentCallStatusTextTimer?.invalidate()
strongSelf.currentCallStatusTextTimer = nil
}
strongSelf.updateStatusBarText()
}
}
})
self.context.account.resetStateManagement()
let contactSynchronizationPreferencesKey = PostboxViewKey.preferences(keys: Set([ApplicationSpecificPreferencesKeys.contactSynchronizationSettings]))
let importableContacts = self.context.sharedContext.contactDataManager?.importable() ?? .single([:])
self.context.account.importableContacts.set(self.context.account.postbox.combinedView(keys: [contactSynchronizationPreferencesKey])
|> mapToSignal { preferences -> Signal<[DeviceContactNormalizedPhoneNumber: ImportableDeviceContactData], NoError> in
let settings: ContactSynchronizationSettings = ((preferences.views[contactSynchronizationPreferencesKey] as? PreferencesView)?.values[ApplicationSpecificPreferencesKeys.contactSynchronizationSettings] as? ContactSynchronizationSettings) ?? .defaultSettings
self.context.account.importableContacts.set(self.context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.contactSynchronizationSettings])
|> mapToSignal { sharedData -> Signal<[DeviceContactNormalizedPhoneNumber: ImportableDeviceContactData], NoError> in
let settings: ContactSynchronizationSettings = (sharedData.entries[ApplicationSpecificSharedDataKeys.contactSynchronizationSettings] as? ContactSynchronizationSettings) ?? .defaultSettings
if settings.synchronizeDeviceContacts {
return importableContacts
} else {
@ -992,7 +831,7 @@ final class AuthorizedApplicationContext {
})
let previousTheme = Atomic<PresentationTheme?>(value: nil)
self.presentationDataDisposable = (context.presentationData
self.presentationDataDisposable = (context.sharedContext.presentationData
|> deliverOnMainQueue).start(next: { [weak self] presentationData in
if let strongSelf = self {
if previousTheme.swap(presentationData.theme) !== presentationData.theme {
@ -1051,42 +890,18 @@ final class AuthorizedApplicationContext {
if chatIsVisible {
navigateToMessage()
} else {
let presentationData = strongSelf.context.currentPresentationData.with { $0 }
let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 }
let controller = standardTextAlertController(theme: AlertControllerTheme(presentationTheme: presentationData.theme), title: presentationData.strings.WatchRemote_AlertTitle, text: presentationData.strings.WatchRemote_AlertText, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .genericAction, title: presentationData.strings.WatchRemote_AlertOpen, action:navigateToMessage)])
(strongSelf.rootController.viewControllers.last as? ViewController)?.present(controller, in: .window(.root))
}
} else {
strongSelf.notificationManager.presentWatchContinuityNotification(context: strongSelf.context, messageId: messageId)
//strongSelf.notificationManager.presentWatchContinuityNotification(context: strongSelf.context, messageId: messageId)
}
}
}))
})
}
private func updateStatusBarText() {
if case let .inProgress(timestamp) = self.currentCallStatusText {
let text: String
let presentationData = self.context.currentPresentationData.with { $0 }
if let timestamp = timestamp {
let duration = Int32(CFAbsoluteTimeGetCurrent() - timestamp)
let durationString: String
if duration > 60 * 60 {
durationString = String(format: "%02d:%02d:%02d", arguments: [duration / 3600, (duration / 60) % 60, duration % 60])
} else {
durationString = String(format: "%02d:%02d", arguments: [(duration / 60) % 60, duration % 60])
}
text = presentationData.strings.Call_StatusBar(durationString).0
} else {
text = presentationData.strings.Call_StatusBar("").0
}
self.mainWindow.setForceInCallStatusBar(text)
} else {
self.mainWindow.setForceInCallStatusBar(nil)
}
}
deinit {
self.context.account.postbox.clearCaches()
self.context.account.shouldKeepOnlinePresence.set(.single(false))
@ -1099,9 +914,6 @@ final class AuthorizedApplicationContext {
self.passcodeStatusDisposable.dispose()
self.displayAlertsDisposable?.dispose()
self.removeNotificationsDisposable?.dispose()
self.callDisposable?.dispose()
self.callStateDisposable?.dispose()
self.currentCallStatusTextTimer?.invalidate()
self.presentationDataDisposable?.dispose()
self.enablePostboxTransactionsDiposable?.dispose()
self.termsOfServiceProceedToBotDisposable.dispose()
@ -1126,7 +938,7 @@ final class AuthorizedApplicationContext {
func openUrl(_ url: URL) {
if self.rootController.rootTabController != nil {
let presentationData = self.context.currentPresentationData.with { $0 }
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
openExternalUrl(context: self.context, url: url.absoluteString, presentationData: presentationData, navigationController: self.rootController, dismissInput: { [weak self] in
self?.rootController.view.endEditing(true)
})

View File

@ -476,15 +476,15 @@ func importLegacyPreferences(accountManager: AccountManager, account: TemporaryA
return settings
})
transaction.updateSharedData(ApplicationSpecificSharedDataKeys.contactSynchronizationSettings, { current in
var settings: ContactSynchronizationSettings = current as? ContactSynchronizationSettings ?? .defaultSettings
if let contactsInhibitSync = contactsInhibitSync, contactsInhibitSync {
settings.synchronizeDeviceContacts = false
}
return settings
})
return account.postbox.transaction { transaction -> Void in
transaction.updatePreferencesEntry(key: ApplicationSpecificPreferencesKeys.contactSynchronizationSettings, { current in
var settings: ContactSynchronizationSettings = current as? ContactSynchronizationSettings ?? .defaultSettings
if let contactsInhibitSync = contactsInhibitSync, contactsInhibitSync {
settings.synchronizeDeviceContacts = false
}
return settings
})
if let secretInlineBotsInitialized = secretInlineBotsInitialized, secretInlineBotsInitialized {
ApplicationSpecificNotice.setSecretChatInlineBotUsage(transaction: transaction)
}

View File

@ -323,7 +323,7 @@ final class NotificationManager {
}
private func processNotificationMessages(context: AccountContext, messageList: [([Message], PeerMessageSound, Bool, Bool)], isLocked: Bool) {
let presentationData = (context.currentPresentationData.with { $0 })
let presentationData = (context.sharedContext.currentPresentationData.with { $0 })
let strings = presentationData.strings
let nameDisplayOrder = presentationData.nameDisplayOrder
@ -801,7 +801,7 @@ final class NotificationManager {
}
}
}
let presentationData = context.currentPresentationData.with { $0 }
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
var userInfo: [AnyHashable : Any] = [:]
userInfo["peerId"] = messageId.peerId.toInt64()

View File

@ -0,0 +1,361 @@
import Foundation
import SwiftSignalKit
import Postbox
import TelegramCore
private final class PollStateContext {
let subscribers = Bag<(Bool) -> Void>()
var disposable: Disposable?
deinit {
self.disposable?.dispose()
}
var isEmpty: Bool {
return self.disposable == nil && self.subscribers.isEmpty
}
}
final class SharedNotificationManager {
private let episodeId: UInt32
private let clearNotificationsManager: ClearNotificationsManager
private var inForeground: Bool = false
private var inForegroundDisposable: Disposable?
private var accountsAndKeys: [(Account, Bool, MasterNotificationKey)]?
private var accountsAndKeysDisposable: Disposable?
private var encryptedNotifications: [[AnyHashable: Any]] = []
private var pollStateContexts: [AccountRecordId: PollStateContext] = [:]
init(episodeId: UInt32, clearNotificationsManager: ClearNotificationsManager, inForeground: Signal<Bool, NoError>, accounts: Signal<[(Account, Bool)], NoError>) {
assert(Queue.mainQueue().isCurrent())
self.episodeId = episodeId
self.clearNotificationsManager = clearNotificationsManager
self.inForegroundDisposable = (inForeground
|> deliverOnMainQueue).start(next: { [weak self] value in
guard let strongSelf = self else {
return
}
strongSelf.inForeground = value
})
self.accountsAndKeysDisposable = (accounts
|> mapToSignal { accounts -> Signal<[(Account, Bool, MasterNotificationKey)], NoError> in
let signals = accounts.map { account, isCurrent -> Signal<(Account, Bool, MasterNotificationKey), NoError> in
return masterNotificationsKey(account: account, ignoreDisabled: true)
|> map { key -> (Account, Bool, MasterNotificationKey) in
return (account, isCurrent, key)
}
}
return combineLatest(signals)
}
|> deliverOnMainQueue).start(next: { [weak self] accountsAndKeys in
guard let strongSelf = self else {
return
}
let shouldProcess = strongSelf.accountsAndKeys == nil
strongSelf.accountsAndKeys = accountsAndKeys
if shouldProcess {
strongSelf.process()
}
})
}
deinit {
self.inForegroundDisposable?.dispose()
self.accountsAndKeysDisposable?.dispose()
}
func isPollingState(accountId: AccountRecordId) -> Signal<Bool, NoError> {
return Signal { subscriber in
let context: PollStateContext
if let current = self.pollStateContexts[accountId] {
context = current
} else {
context = PollStateContext()
self.pollStateContexts[accountId] = context
}
subscriber.putNext(context.disposable != nil)
let index = context.subscribers.add({ value in
subscriber.putNext(value)
})
return ActionDisposable { [weak context] in
Queue.mainQueue().async {
if let current = self.pollStateContexts[accountId], current === context {
current.subscribers.remove(index)
if current.isEmpty {
self.pollStateContexts.removeValue(forKey: accountId)
}
}
}
}
}
}
private func beginPollingState(account: Account) {
let accountId = account.id
let context: PollStateContext
if let current = self.pollStateContexts[accountId] {
context = current
} else {
context = PollStateContext()
self.pollStateContexts[accountId] = context
}
let previousDisposable = context.disposable
context.disposable = (account.stateManager.pollStateUpdateCompletion()
|> deliverOnMainQueue).start(next: { [weak self, weak context] _ in
guard let strongSelf = self else {
return
}
if let current = strongSelf.pollStateContexts[accountId], current === context {
if let disposable = current.disposable {
disposable.dispose()
current.disposable = nil
for f in current.subscribers.copyItems() {
f(false)
}
}
if current.isEmpty {
strongSelf.pollStateContexts.removeValue(forKey: accountId)
}
}
})
previousDisposable?.dispose()
if previousDisposable == nil {
for f in context.subscribers.copyItems() {
f(true)
}
}
}
func addEncryptedNotification(_ dict: [AnyHashable: Any]) {
self.encryptedNotifications.append(dict)
if self.accountsAndKeys != nil {
self.process()
}
}
private func process() {
guard let accountsAndKeys = self.accountsAndKeys else {
return
}
var decryptedNotifications: [(Account, Bool, [AnyHashable: Any])] = []
for dict in self.encryptedNotifications {
if var encryptedPayload = dict["p"] as? String {
encryptedPayload = encryptedPayload.replacingOccurrences(of: "-", with: "+")
encryptedPayload = encryptedPayload.replacingOccurrences(of: "_", with: "/")
while encryptedPayload.count % 4 != 0 {
encryptedPayload.append("=")
}
if let data = Data(base64Encoded: encryptedPayload) {
inner: for (account, isCurrent, key) in accountsAndKeys {
if let decryptedData = decryptedNotificationPayload(key: key, data: data) {
if let decryptedDict = (try? JSONSerialization.jsonObject(with: decryptedData, options: [])) as? [AnyHashable: Any] {
decryptedNotifications.append((account, isCurrent, decryptedDict))
}
break inner
}
}
}
}
}
self.encryptedNotifications.removeAll()
for (account, isCurrent, payload) in decryptedNotifications {
var redactedPayload = payload
if var aps = redactedPayload["aps"] as? [AnyHashable: Any] {
if Logger.shared.redactSensitiveData {
if aps["alert"] != nil {
aps["alert"] = "[[redacted]]"
}
if aps["body"] != nil {
aps["body"] = "[[redacted]]"
}
}
redactedPayload["aps"] = aps
}
Logger.shared.log("Apns \(self.episodeId)", "\(redactedPayload)")
let aps = payload["aps"] as? [AnyHashable: Any]
var readMessageId: MessageId?
var isCall = false
var isAnnouncement = false
var isLocationPolling = false
var notificationRequestId: NotificationManagedNotificationRequestId?
var isMutePolling = false
var title: String = ""
var body: String?
var apnsSound: String?
var configurationUpdate: (Int32, String, Int32, Data?)?
if let aps = aps, let alert = aps["alert"] as? String {
if let range = alert.range(of: ": ") {
title = String(alert[..<range.lowerBound])
body = String(alert[range.upperBound...])
} else {
body = alert
}
} else if let aps = aps, let alert = aps["alert"] as? [AnyHashable: AnyObject] {
if let alertBody = alert["body"] as? String {
body = alertBody
if let alertTitle = alert["title"] as? String {
title = alertTitle
}
}
if let locKey = alert["loc-key"] as? String {
if locKey == "PHONE_CALL_REQUEST" {
isCall = true
} else if locKey == "GEO_LIVE_PENDING" {
isLocationPolling = true
} else if locKey == "MESSAGE_MUTED" {
isMutePolling = true
}
let string = NSLocalizedString(locKey, comment: "")
if !string.isEmpty {
if let locArgs = alert["loc-args"] as? [AnyObject] {
var args: [CVarArg] = []
var failed = false
for arg in locArgs {
if let arg = arg as? CVarArg {
args.append(arg)
} else {
failed = true
break
}
}
if failed {
body = "\(string)"
} else {
body = String(format: string, arguments: args)
}
} else {
body = "\(string)"
}
} else {
body = nil
}
} else {
body = nil
}
}
if let aps = aps, let address = aps["addr"] as? String, let datacenterId = aps["dc"] as? Int {
var host = address
var port: Int32 = 443
if let range = address.range(of: ":") {
host = String(address[address.startIndex ..< range.lowerBound])
if let portValue = Int(String(address[range.upperBound...])) {
port = Int32(portValue)
}
}
var secret: Data?
if let secretString = aps["sec"] as? String {
let data = dataWithHexString(secretString)
if data.count == 16 || data.count == 32 {
secret = data
}
}
configurationUpdate = (Int32(datacenterId), host, port, secret)
}
if let aps = aps, let sound = aps["sound"] as? String {
apnsSound = sound
}
if payload["call_id"] != nil {
isCall = true
}
if payload["announcement"] != nil {
isAnnouncement = true
}
if let body = body {
if isAnnouncement {
//presentAnnouncement
} else {
var peerId: PeerId?
if let fromId = payload["from_id"] {
let fromIdValue = fromId as! NSString
peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: Int32(fromIdValue.intValue))
} else if let fromId = payload["chat_id"] {
let fromIdValue = fromId as! NSString
peerId = PeerId(namespace: Namespaces.Peer.CloudGroup, id: Int32(fromIdValue.intValue))
} else if let fromId = payload["channel_id"] {
let fromIdValue = fromId as! NSString
peerId = PeerId(namespace: Namespaces.Peer.CloudChannel, id: Int32(fromIdValue.intValue))
}
if let msgId = payload["msg_id"] {
let msgIdValue = msgId as! NSString
if let peerId = peerId {
notificationRequestId = .messageId(MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: Int32(msgIdValue.intValue)))
}
} else if let randomId = payload["random_id"] {
let randomIdValue = randomId as! NSString
var peerId: PeerId?
if let encryptionIdString = payload["encryption_id"] as? String, let encryptionId = Int32(encryptionIdString) {
peerId = PeerId(namespace: Namespaces.Peer.SecretChat, id: encryptionId)
}
notificationRequestId = .globallyUniqueId(randomIdValue.longLongValue, peerId)
} else {
isMutePolling = true
}
}
} else if let _ = payload["max_id"] {
var peerId: PeerId?
if let fromId = payload["from_id"] {
let fromIdValue = fromId as! NSString
peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: Int32(fromIdValue.intValue))
} else if let fromId = payload["chat_id"] {
let fromIdValue = fromId as! NSString
peerId = PeerId(namespace: Namespaces.Peer.CloudGroup, id: Int32(fromIdValue.intValue))
} else if let fromId = payload["channel_id"] {
let fromIdValue = fromId as! NSString
peerId = PeerId(namespace: Namespaces.Peer.CloudChannel, id: Int32(fromIdValue.intValue))
}
if let peerId = peerId {
if let msgId = payload["max_id"] {
let msgIdValue = msgId as! NSString
if msgIdValue.intValue != 0 {
readMessageId = MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: Int32(msgIdValue.intValue))
}
}
}
}
if notificationRequestId != nil || isMutePolling || isCall {
if !self.inForeground || !isCurrent {
self.beginPollingState(account: account)
}
}
if isLocationPolling {
//addedWakeups.insert(.backgroundLocation)
}
if let readMessageId = readMessageId {
self.clearNotificationsManager.append(readMessageId)
self.clearNotificationsManager.commitNow()
let _ = account.postbox.transaction(ignoreDisabled: true, { transaction -> Void in
transaction.applyIncomingReadMaxId(readMessageId)
}).start()
}
if let (datacenterId, host, port, secret) = configurationUpdate {
account.network.mergeBackupDatacenterAddress(datacenterId: datacenterId, host: host, port: port, secret: secret)
}
}
}
}

View File

@ -0,0 +1,184 @@
import Foundation
import UIKit
import SwiftSignalKit
import Postbox
import TelegramCore
private struct AccountTasks {
let stateSynchronization: Bool
let importantTasks: AccountRunningImportantTasks
let backgroundLocation: Bool
let backgroundDownloads: Bool
var isEmpty: Bool {
if self.stateSynchronization {
return false
}
if !self.importantTasks.isEmpty {
return false
}
if self.backgroundLocation {
return false
}
if self.backgroundDownloads {
return false
}
return true
}
}
final class SharedWakeupManager {
private var inForeground: Bool = false
private var hasActiveAudioSession: Bool = false
private var allowBackgroundTimeExtensionDeadline: Double?
private var isInBackgroundExtension: Bool = false
private var inForegroundDisposable: Disposable?
private var hasActiveAudioSessionDisposable: Disposable?
private var tasksDisposable: Disposable?
private var currentTask: (UIBackgroundTaskIdentifier, Double, SwiftSignalKit.Timer)?
private var accountsAndTasks: [(Account, Bool, AccountTasks)] = []
init(activeAccounts: Signal<(primary: Account?, accounts: [AccountRecordId: Account]), NoError>, inForeground: Signal<Bool, NoError>, hasActiveAudioSession: Signal<Bool, NoError>, notificationManager: SharedNotificationManager) {
assert(Queue.mainQueue().isCurrent())
self.inForegroundDisposable = (inForeground
|> deliverOnMainQueue).start(next: { [weak self] value in
guard let strongSelf = self else {
return
}
strongSelf.inForeground = value
strongSelf.checkTasks()
})
self.hasActiveAudioSessionDisposable = (hasActiveAudioSession
|> deliverOnMainQueue).start(next: { [weak self] value in
guard let strongSelf = self else {
return
}
strongSelf.hasActiveAudioSession = value
strongSelf.checkTasks()
})
self.tasksDisposable = (activeAccounts
|> deliverOnMainQueue
|> mapToSignal { primary, accounts -> Signal<[(Account, Bool, AccountTasks)], NoError> in
let signals: [Signal<(Account, Bool, AccountTasks), NoError>] = accounts.values.map { account in
return combineLatest(queue: .mainQueue(), account.importantTasksRunning, notificationManager.isPollingState(accountId: account.id))
|> map { importantTasksRunning, isPollingState -> (Account, Bool, AccountTasks) in
return (account, primary?.id == account.id, AccountTasks(stateSynchronization: isPollingState, importantTasks: importantTasksRunning, backgroundLocation: false, backgroundDownloads: false))
}
}
return combineLatest(signals)
}
|> deliverOnMainQueue).start(next: { [weak self] accountsAndTasks in
guard let strongSelf = self else {
return
}
strongSelf.accountsAndTasks = accountsAndTasks
strongSelf.checkTasks()
})
}
deinit {
self.inForegroundDisposable?.dispose()
self.hasActiveAudioSessionDisposable?.dispose()
self.tasksDisposable?.dispose()
if let (taskId, _, timer) = self.currentTask {
timer.invalidate()
UIApplication.shared.endBackgroundTask(taskId)
}
}
func allowBackgroundTimeExtension(timeout: Double) {
let shouldCheckTasks = self.allowBackgroundTimeExtensionDeadline == nil
self.allowBackgroundTimeExtensionDeadline = CACurrentMediaTime() + timeout
if shouldCheckTasks {
self.checkTasks()
}
}
func checkTasks() {
if self.inForeground || self.hasActiveAudioSession {
if let (taskId, _, timer) = self.currentTask {
self.currentTask = nil
timer.invalidate()
UIApplication.shared.endBackgroundTask(taskId)
self.isInBackgroundExtension = false
}
} else {
var hasTasksForBackgroundExtension = false
for (_, _, tasks) in self.accountsAndTasks {
if !tasks.isEmpty {
hasTasksForBackgroundExtension = true
break
}
}
let canBeginBackgroundExtensionTasks = self.allowBackgroundTimeExtensionDeadline.flatMap({ CACurrentMediaTime() < $0 }) ?? false
if hasTasksForBackgroundExtension {
if canBeginBackgroundExtensionTasks {
var endTaskId: UIBackgroundTaskIdentifier?
let currentTime = CACurrentMediaTime()
if let (taskId, startTime, timer) = self.currentTask {
if startTime < currentTime + 1.0 {
self.currentTask = nil
timer.invalidate()
endTaskId = taskId
}
}
if self.currentTask == nil {
let handleExpiration:() -> Void = { [weak self] in
guard let strongSelf = self else {
return
}
strongSelf.isInBackgroundExtension = false
strongSelf.checkTasks()
}
let taskId = UIApplication.shared.beginBackgroundTask(withName: "background-wakeup", expirationHandler: {
handleExpiration()
})
let timer = SwiftSignalKit.Timer(timeout: min(30.0, UIApplication.shared.backgroundTimeRemaining), repeat: false, completion: {
handleExpiration()
}, queue: Queue.mainQueue())
self.currentTask = (taskId, currentTime, timer)
timer.start()
endTaskId.flatMap(UIApplication.shared.endBackgroundTask)
self.isInBackgroundExtension = true
}
}
} else if let (taskId, _, timer) = self.currentTask {
self.currentTask = nil
timer.invalidate()
UIApplication.shared.endBackgroundTask(taskId)
self.isInBackgroundExtension = false
}
}
self.updateAccounts()
}
private func updateAccounts() {
if self.inForeground || self.hasActiveAudioSession || self.isInBackgroundExtension {
for (account, primary, tasks) in self.accountsAndTasks {
if primary || !tasks.isEmpty {
account.shouldBeServiceTaskMaster.set(.single(.always))
} else {
account.shouldBeServiceTaskMaster.set(.single(.never))
}
account.shouldKeepOnlinePresence.set(.single(primary && self.inForeground))
account.shouldKeepBackgroundDownloadConnections.set(.single(tasks.backgroundDownloads))
}
} else {
for (account, _, _) in self.accountsAndTasks {
account.shouldBeServiceTaskMaster.set(.single(.never))
account.shouldKeepOnlinePresence.set(.single(false))
account.shouldKeepBackgroundDownloadConnections.set(.single(false))
}
}
}
}

View File

@ -15,7 +15,7 @@ final class WatchCommunicationManager {
private let presets = Promise<WatchPresetSettings?>(nil)
private let navigateToMessagePipe = ValuePipe<MessageId>()
init(queue: Queue, context: Promise<ApplicationContext?>) {
init(queue: Queue, context: Promise<AuthorizedApplicationContext?>) {
self.queue = queue
let handlers = allWatchRequestHandlers.reduce([String : AnyClass]()) { (map, handler) -> [String : AnyClass] in
@ -57,7 +57,7 @@ final class WatchCommunicationManager {
guard let strongSelf = self, appInstalled else {
return
}
if let appContext = appContext, case let .authorized(context) = appContext {
if let context = appContext {
strongSelf.accountContext.set(.single(context.context))
strongSelf.server.setAuthorized(true, userId: context.context.account.peerId.id)
strongSelf.server.setMicAccessAllowed(false)
@ -79,10 +79,10 @@ final class WatchCommunicationManager {
}))
self.presetsDisposable.set((combineLatest(self.watchAppInstalled, self.presets.get() |> distinctUntilChanged |> deliverOn(self.queue), context.get() |> deliverOn(self.queue))).start(next: { [weak self] appInstalled, presets, appContext in
guard let strongSelf = self, let presets = presets, let appContext = appContext, case let .authorized(context) = appContext, appInstalled, let tempPath = strongSelf.watchTemporaryStorePath else {
guard let strongSelf = self, let presets = presets, let context = appContext, appInstalled, let tempPath = strongSelf.watchTemporaryStorePath else {
return
}
let presentationData = context.context.currentPresentationData.with { $0 }
let presentationData = context.context.sharedContext.currentPresentationData.with { $0 }
let defaultSuggestions: [String : String] = [
"OK": presentationData.strings.Watch_Suggestion_OK,
"Thanks": presentationData.strings.Watch_Suggestion_Thanks,
@ -173,7 +173,7 @@ final class WatchCommunicationManager {
}
}
func watchCommunicationManager(context: Promise<ApplicationContext?>) -> Signal<WatchCommunicationManager?, NoError> {
func watchCommunicationManager(context: Promise<AuthorizedApplicationContext?>) -> Signal<WatchCommunicationManager?, NoError> {
return Signal { subscriber in
let queue = Queue()
queue.async {

View File

@ -39,7 +39,7 @@ final class WatchChatListHandler: WatchRequestHandler {
if let context = context {
return context.account.viewTracker.tailChatListView(groupId: nil, count: limit)
|> map { chatListView, _ -> (ChatListView, PresentationData) in
return (chatListView, context.currentPresentationData.with { $0 })
return (chatListView, context.sharedContext.currentPresentationData.with { $0 })
}
} else {
return .complete()
@ -87,7 +87,7 @@ final class WatchChatMessagesHandler: WatchRequestHandler {
if let context = context {
return context.account.viewTracker.aroundMessageHistoryViewForLocation(.peer(peerId), index: .upperBound, anchorIndex: .upperBound, count: limit, fixedCombinedReadStates: nil)
|> map { messageHistoryView, _, _ -> (MessageHistoryView, Bool, PresentationData) in
return (messageHistoryView, peerId == context.account.peerId, context.currentPresentationData.with { $0 })
return (messageHistoryView, peerId == context.account.peerId, context.sharedContext.currentPresentationData.with { $0 })
}
} else {
return .complete()
@ -140,7 +140,7 @@ final class WatchChatMessagesHandler: WatchRequestHandler {
let messageSignal = downloadMessage(postbox: context.account.postbox, network: context.account.network, messageId: MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: args.messageId))
|> map { message -> (Message, PresentationData)? in
if let message = message {
return (message, context.currentPresentationData.with { $0 })
return (message, context.sharedContext.currentPresentationData.with { $0 })
} else {
return nil
}

@ -1 +1 @@
Subproject commit 709df1faeffdd6fefc72bb8a1853b65a5e803bdc
Subproject commit 076af3926c4a5d55bcad4ffece41464059c891d9

@ -1 +1 @@
Subproject commit 88bc22e1192569ae4c0111b6d371119a87a91f08
Subproject commit 57821c9c362f56e8ea652b51086fda516e791999

@ -1 +1 @@
Subproject commit 84231fb6fcbb39f72b50b2812c728cab8c659b52
Subproject commit 3e4cc1a26fb677682de98117cf4c918331f5160d

@ -1 +1 @@
Subproject commit 0d8163db0b4d763090b44e3df750ae6b4632ff31
Subproject commit 867d73ba99f1caac56d997762415948de633ca8d