mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
1705 lines
83 KiB
Swift
1705 lines
83 KiB
Swift
import UIKit
|
|
import SwiftSignalKit
|
|
import Display
|
|
import TelegramCore
|
|
import TelegramUI
|
|
import UserNotifications
|
|
import Intents
|
|
import HockeySDK
|
|
import Postbox
|
|
import PushKit
|
|
import AsyncDisplayKit
|
|
import CloudKit
|
|
|
|
private let handleVoipNotifications = false
|
|
|
|
private func encodeText(_ string: String, _ key: Int) -> String {
|
|
var result = ""
|
|
for c in string.unicodeScalars {
|
|
result.append(Character(UnicodeScalar(UInt32(Int(c.value) + key))!))
|
|
}
|
|
return result
|
|
}
|
|
|
|
private let statusBarRootViewClass: AnyClass = NSClassFromString("UIStatusBar")!
|
|
private let cutoutStatusBarForegroundClass: AnyClass? = NSClassFromString("_UIStatusBar")
|
|
private let keyboardViewClass: AnyClass? = NSClassFromString(encodeText("VJJoqvuTfuIptuWjfx", -1))!
|
|
private let keyboardViewContainerClass: AnyClass? = NSClassFromString(encodeText("VJJoqvuTfuDpoubjofsWjfx", -1))!
|
|
|
|
private let keyboardWindowClass: AnyClass? = {
|
|
if #available(iOS 9.0, *) {
|
|
return NSClassFromString(encodeText("VJSfnpufLfzcpbseXjoepx", -1))
|
|
} else {
|
|
return NSClassFromString(encodeText("VJUfyuFggfdutXjoepx", -1))
|
|
}
|
|
}()
|
|
|
|
private class ApplicationStatusBarHost: StatusBarHost {
|
|
private let application = UIApplication.shared
|
|
|
|
var statusBarFrame: CGRect {
|
|
return self.application.statusBarFrame
|
|
}
|
|
var statusBarStyle: UIStatusBarStyle {
|
|
get {
|
|
return self.application.statusBarStyle
|
|
} set(value) {
|
|
self.application.setStatusBarStyle(value, animated: false)
|
|
}
|
|
}
|
|
var statusBarWindow: UIView? {
|
|
return self.application.value(forKey: "statusBarWindow") as? UIView
|
|
}
|
|
|
|
var statusBarView: UIView? {
|
|
guard let containerView = self.statusBarWindow?.subviews.first else {
|
|
return nil
|
|
}
|
|
|
|
if containerView.isKind(of: statusBarRootViewClass) {
|
|
return containerView
|
|
}
|
|
|
|
for subview in containerView.subviews {
|
|
if let cutoutStatusBarForegroundClass = cutoutStatusBarForegroundClass, subview.isKind(of: cutoutStatusBarForegroundClass) {
|
|
return subview
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
var keyboardWindow: UIWindow? {
|
|
guard let keyboardWindowClass = keyboardWindowClass else {
|
|
return nil
|
|
}
|
|
|
|
for window in UIApplication.shared.windows {
|
|
if window.isKind(of: keyboardWindowClass) {
|
|
return window
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
var keyboardView: UIView? {
|
|
guard let keyboardWindow = self.keyboardWindow, let keyboardViewContainerClass = keyboardViewContainerClass, let keyboardViewClass = keyboardViewClass else {
|
|
return nil
|
|
}
|
|
|
|
for view in keyboardWindow.subviews {
|
|
if view.isKind(of: keyboardViewContainerClass) {
|
|
for subview in view.subviews {
|
|
if subview.isKind(of: keyboardViewClass) {
|
|
return subview
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
var handleVolumeControl: Signal<Bool, NoError> {
|
|
return MediaManager.globalAudioSession.isPlaybackActive()
|
|
}
|
|
}
|
|
|
|
private func legacyDocumentsPath() -> String {
|
|
return NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] + "/legacy"
|
|
}
|
|
|
|
protocol SupportedStartCallIntent {
|
|
@available(iOS 10.0, *)
|
|
var contacts: [INPerson]? { get }
|
|
}
|
|
|
|
@available(iOS 10.0, *)
|
|
extension INStartAudioCallIntent: SupportedStartCallIntent {}
|
|
|
|
private enum QueuedWakeup: Int32 {
|
|
case call
|
|
case backgroundLocation
|
|
}
|
|
|
|
@objc(AppDelegate) class AppDelegate: UIResponder, UIApplicationDelegate, PKPushRegistryDelegate, BITHockeyManagerDelegate, UNUserNotificationCenterDelegate, UIAlertViewDelegate {
|
|
@objc var window: UIWindow?
|
|
var nativeWindow: (UIWindow & WindowHost)?
|
|
var mainWindow: Window1!
|
|
private var dataImportSplash: LegacyDataImportSplash?
|
|
|
|
let episodeId = arc4random()
|
|
|
|
private let isInForegroundPromise = ValuePromise<Bool>(false, ignoreRepeated: true)
|
|
private var isInForegroundValue = false
|
|
private let isActivePromise = ValuePromise<Bool>(false, ignoreRepeated: true)
|
|
private var isActiveValue = false
|
|
let hasActiveAudioSession = Promise<Bool>(false)
|
|
|
|
private let accountManagerPromise = Promise<AccountManager>()
|
|
private let watchCommunicationManagerPromise = Promise<WatchCommunicationManager?>()
|
|
|
|
private var contextValue: ApplicationContext?
|
|
private let context = Promise<ApplicationContext?>()
|
|
private let contextDisposable = MetaDisposable()
|
|
|
|
private let openChatWhenReadyDisposable = MetaDisposable()
|
|
private let openUrlWhenReadyDisposable = MetaDisposable()
|
|
|
|
private let badgeDisposable = MetaDisposable()
|
|
private let quickActionsDisposable = MetaDisposable()
|
|
|
|
private var pushRegistry: PKPushRegistry?
|
|
|
|
private let notificationAuthorizationDisposable = MetaDisposable()
|
|
|
|
private var replyFromNotificationsDisposables = DisposableSet()
|
|
|
|
private var replyFromNotificationsTokensValue = Set<Int32>() {
|
|
didSet {
|
|
assert(Queue.mainQueue().isCurrent())
|
|
self.replyFromNotificationsTokensPromise.set(.single(self.replyFromNotificationsTokensValue))
|
|
}
|
|
}
|
|
private let replyFromNotificationsTokensPromise = Promise<Set<Int32>>(Set())
|
|
private var nextToken: Int32 = 0
|
|
private func takeNextToken() -> Int32 {
|
|
let value = self.nextToken
|
|
self.nextToken = value + 1
|
|
return value
|
|
}
|
|
private func addReplyFromNotificationsToken() -> Int32 {
|
|
let token = self.takeNextToken()
|
|
var value = self.replyFromNotificationsTokensValue
|
|
value.insert(token)
|
|
self.replyFromNotificationsTokensValue = value
|
|
return token
|
|
}
|
|
private func removeReplyFromNotificationsToken(_ token: Int32) {
|
|
var value = self.replyFromNotificationsTokensValue
|
|
value.remove(token)
|
|
self.replyFromNotificationsTokensValue = value
|
|
}
|
|
|
|
private var _notificationTokenPromise: Promise<Data>?
|
|
private let voipTokenPromise = Promise<Data>()
|
|
|
|
private var notificationTokenPromise: Promise<Data> {
|
|
if let current = self._notificationTokenPromise {
|
|
return current
|
|
} else {
|
|
let promise = Promise<Data>()
|
|
self._notificationTokenPromise = promise
|
|
|
|
return promise
|
|
}
|
|
}
|
|
|
|
private var queuedNotifications: [PKPushPayload] = []
|
|
private var queuedNotificationRequests: [(String, String, String?, NotificationManagedNotificationRequestId)] = []
|
|
private var queuedMutePolling = false
|
|
private var queuedAnnouncements: [String] = []
|
|
private var queuedWakeups = Set<QueuedWakeup>()
|
|
private var clearNotificationsManager: ClearNotificationsManager?
|
|
|
|
private let idleTimerExtensionSubscribers = Bag<Void>()
|
|
|
|
private var alertActions: (primary: (() -> Void)?, other: (() -> Void)?)?
|
|
|
|
func alertView(_ alertView: UIAlertView, clickedButtonAt buttonIndex: Int) {
|
|
if buttonIndex == alertView.firstOtherButtonIndex {
|
|
self.alertActions?.other?()
|
|
} else {
|
|
self.alertActions?.primary?()
|
|
}
|
|
}
|
|
|
|
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]? = nil) -> Bool {
|
|
let statusBarHost = ApplicationStatusBarHost()
|
|
let (window, hostView) = nativeWindowHostView()
|
|
self.mainWindow = Window1(hostView: hostView, statusBarHost: statusBarHost)
|
|
window.backgroundColor = UIColor.white
|
|
self.window = window
|
|
self.nativeWindow = window
|
|
|
|
self.clearNotificationsManager = ClearNotificationsManager(getNotificationIds: { completion in
|
|
if #available(iOS 10.0, *) {
|
|
UNUserNotificationCenter.current().getDeliveredNotifications(completionHandler: { notifications in
|
|
var result: [(String, NotificationManagedNotificationRequestId)] = []
|
|
for notification in notifications {
|
|
if let requestId = NotificationManagedNotificationRequestId(string: notification.request.identifier) {
|
|
result.append((notification.request.identifier, requestId))
|
|
} else {
|
|
let payload = notification.request.content.userInfo
|
|
var notificationRequestId: NotificationManagedNotificationRequestId?
|
|
|
|
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)))
|
|
}
|
|
}
|
|
|
|
if let notificationRequestId = notificationRequestId {
|
|
result.append((notification.request.identifier, notificationRequestId))
|
|
}
|
|
}
|
|
}
|
|
completion.f(result)
|
|
})
|
|
} else {
|
|
var result: [(String, NotificationManagedNotificationRequestId)] = []
|
|
if let notifications = UIApplication.shared.scheduledLocalNotifications {
|
|
for notification in notifications {
|
|
if let userInfo = notification.userInfo, let id = userInfo["id"] as? String {
|
|
if let requestId = NotificationManagedNotificationRequestId(string: id) {
|
|
result.append((id, requestId))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
completion.f(result)
|
|
}
|
|
}, removeNotificationIds: { ids in
|
|
if #available(iOS 10.0, *) {
|
|
UNUserNotificationCenter.current().removeDeliveredNotifications(withIdentifiers: ids)
|
|
} else {
|
|
if let notifications = UIApplication.shared.scheduledLocalNotifications {
|
|
for notification in notifications {
|
|
if let userInfo = notification.userInfo, let id = userInfo["id"] as? String {
|
|
if ids.contains(id) {
|
|
UIApplication.shared.cancelLocalNotification(notification)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}, getPendingNotificationIds: { completion in
|
|
if #available(iOS 10.0, *) {
|
|
UNUserNotificationCenter.current().getPendingNotificationRequests(completionHandler: { requests in
|
|
var result: [(String, NotificationManagedNotificationRequestId)] = []
|
|
for request in requests {
|
|
if let requestId = NotificationManagedNotificationRequestId(string: request.identifier) {
|
|
result.append((request.identifier, requestId))
|
|
}
|
|
}
|
|
completion.f(result)
|
|
})
|
|
} else {
|
|
var result: [(String, NotificationManagedNotificationRequestId)] = []
|
|
if let notifications = UIApplication.shared.scheduledLocalNotifications {
|
|
for notification in notifications {
|
|
if let userInfo = notification.userInfo, let id = userInfo["id"] as? String {
|
|
if let requestId = NotificationManagedNotificationRequestId(string: id) {
|
|
result.append((id, requestId))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
completion.f(result)
|
|
}
|
|
}, removePendingNotificationIds: { ids in
|
|
if #available(iOS 10.0, *) {
|
|
UNUserNotificationCenter.current().removePendingNotificationRequests(withIdentifiers: ids)
|
|
} else {
|
|
if let notifications = UIApplication.shared.scheduledLocalNotifications {
|
|
for notification in notifications {
|
|
if let userInfo = notification.userInfo, let id = userInfo["id"] as? String {
|
|
if ids.contains(id) {
|
|
UIApplication.shared.cancelLocalNotification(notification)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
})
|
|
|
|
#if DEBUG
|
|
for argument in ProcessInfo.processInfo.arguments {
|
|
if argument.hasPrefix("snapshot:") {
|
|
GlobalExperimentalSettings.isAppStoreBuild = true
|
|
|
|
guard let dataPath = ProcessInfo.processInfo.environment["snapshot-data-path"] else {
|
|
preconditionFailure()
|
|
}
|
|
setupSnapshotData(dataPath)
|
|
switch String(argument[argument.index(argument.startIndex, offsetBy: "snapshot:".count)...]) {
|
|
case "chat-list":
|
|
snapshotChatList(application: application, mainWindow: self.window!, window: self.mainWindow, statusBarHost: statusBarHost)
|
|
case "secret-chat":
|
|
snapshotSecretChat(application: application, mainWindow: self.window!, window: self.mainWindow, statusBarHost: statusBarHost)
|
|
case "settings":
|
|
snapshotSettings(application: application, mainWindow: self.window!, window: self.mainWindow, statusBarHost: statusBarHost)
|
|
case "appearance-settings":
|
|
snapshotAppearanceSettings(application: application, mainWindow: self.window!, window: self.mainWindow, statusBarHost: statusBarHost)
|
|
default:
|
|
break
|
|
}
|
|
self.window?.makeKeyAndVisible()
|
|
return true
|
|
}
|
|
}
|
|
#endif
|
|
|
|
let apiId: Int32 = BuildConfig.shared().apiId
|
|
let languagesCategory = "ios"
|
|
|
|
let appVersion = (Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String) ?? "unknown"
|
|
|
|
let networkArguments = NetworkInitializationArguments(apiId: apiId, languagesCategory: languagesCategory, appVersion: appVersion, voipMaxLayer: PresentationCallManager.voipMaxLayer)
|
|
|
|
let appGroupName = "group.\(Bundle.main.bundleIdentifier!)"
|
|
let maybeAppGroupUrl = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: appGroupName)
|
|
|
|
guard let appGroupUrl = maybeAppGroupUrl else {
|
|
UIAlertView(title: nil, message: "Error 2", delegate: nil, cancelButtonTitle: "OK").show()
|
|
return true
|
|
}
|
|
|
|
let rootPath = rootPathForBasePath(appGroupUrl.path)
|
|
performAppGroupUpgrades(appGroupPath: appGroupUrl.path, rootPath: rootPath)
|
|
|
|
TempBox.initializeShared(basePath: rootPath, processType: "app", launchSpecificId: arc4random64())
|
|
|
|
let logsPath = rootPath + "/logs"
|
|
let _ = try? FileManager.default.createDirectory(atPath: logsPath, withIntermediateDirectories: true, attributes: nil)
|
|
Logger.setSharedLogger(Logger(basePath: logsPath))
|
|
|
|
ASDisableLogging()
|
|
|
|
initializeLegacyComponents(application: application, currentSizeClassGetter: {
|
|
return UIUserInterfaceSizeClass.compact
|
|
}, currentHorizontalClassGetter: {
|
|
return UIUserInterfaceSizeClass.compact
|
|
}, documentsPath: legacyDocumentsPath(), currentApplicationBounds: {
|
|
return UIScreen.main.bounds
|
|
}, canOpenUrl: { url in
|
|
return UIApplication.shared.canOpenURL(url)
|
|
}, openUrl: { url in
|
|
UIApplication.shared.openURL(url)
|
|
})
|
|
|
|
if #available(iOS 10.0, *) {
|
|
UNUserNotificationCenter.current().delegate = self
|
|
}
|
|
|
|
telegramUIDeclareEncodables()
|
|
|
|
#if DEBUG
|
|
LoggingSettings.defaultSettings = LoggingSettings(logToFile: true, logToConsole: true, redactSensitiveData: true)
|
|
#else
|
|
if BuildConfig.shared().isInternalBuild {
|
|
LoggingSettings.defaultSettings = LoggingSettings(logToFile: true, logToConsole: false, redactSensitiveData: true)
|
|
}
|
|
#endif
|
|
|
|
GlobalExperimentalSettings.isAppStoreBuild = BuildConfig.shared().isAppStoreBuild
|
|
|
|
GlobalExperimentalSettings.enableFeed = false
|
|
#if DEBUG
|
|
//GlobalExperimentalSettings.enableFeed = true
|
|
#if targetEnvironment(simulator)
|
|
//GlobalTelegramCoreConfiguration.readMessages = false
|
|
#endif
|
|
#endif
|
|
|
|
self.window?.makeKeyAndVisible()
|
|
|
|
self.hasActiveAudioSession.set(MediaManager.globalAudioSession.isActive())
|
|
|
|
initializeAccountManagement()
|
|
self.accountManagerPromise.set(accountManager(basePath: rootPath + "/accounts-metadata")
|
|
|> mapToSignal { accountManager -> Signal<(AccountManager, LoggingSettings), NoError> in
|
|
return accountManager.transaction { transaction -> (AccountManager, LoggingSettings) in
|
|
return (accountManager, transaction.getSharedData(SharedDataKeys.loggingSettings) as? LoggingSettings ?? LoggingSettings.defaultSettings)
|
|
}
|
|
}
|
|
|> mapToSignal { accountManager, loggingSettings -> Signal<AccountManager, NoError> in
|
|
Logger.shared.logToFile = loggingSettings.logToFile
|
|
Logger.shared.logToConsole = loggingSettings.logToConsole
|
|
Logger.shared.redactSensitiveData = loggingSettings.redactSensitiveData
|
|
|
|
return importedLegacyAccount(basePath: appGroupUrl.path, accountManager: accountManager, present: { controller in
|
|
self.window?.rootViewController?.present(controller, animated: true, completion: nil)
|
|
})
|
|
|> `catch` { _ -> Signal<ImportedLegacyAccountEvent, NoError> in
|
|
return Signal { subscriber in
|
|
let alertView = UIAlertView(title: "", message: "An error occured while trying to upgrade application data. Would you like to logout?", delegate: self, cancelButtonTitle: "No", otherButtonTitles: "Yes")
|
|
self.alertActions = (primary: {
|
|
let statusPath = appGroupUrl.path + "/Documents/importcompleted"
|
|
let _ = try? FileManager.default.createDirectory(atPath: appGroupUrl.path + "/Documents", withIntermediateDirectories: true, attributes: nil)
|
|
let _ = try? Data().write(to: URL(fileURLWithPath: statusPath))
|
|
subscriber.putNext(.result(nil))
|
|
subscriber.putCompletion()
|
|
}, other: {
|
|
exit(0)
|
|
})
|
|
alertView.show()
|
|
|
|
return EmptyDisposable
|
|
} |> runOn(Queue.mainQueue())
|
|
}
|
|
|> mapToSignal { event -> Signal<AccountManager, NoError> in
|
|
switch event {
|
|
case let .progress(type, value):
|
|
Queue.mainQueue().async {
|
|
if self.dataImportSplash == nil {
|
|
self.dataImportSplash = LegacyDataImportSplash()
|
|
self.dataImportSplash?.serviceAction = {
|
|
self.debugPressed()
|
|
}
|
|
self.mainWindow.coveringView = self.dataImportSplash
|
|
}
|
|
self.dataImportSplash?.progress = (type, value)
|
|
}
|
|
return .complete()
|
|
case let .result(temporaryId):
|
|
Queue.mainQueue().async {
|
|
if let _ = self.dataImportSplash {
|
|
self.dataImportSplash = nil
|
|
self.mainWindow.coveringView = nil
|
|
}
|
|
}
|
|
if let temporaryId = temporaryId {
|
|
Queue.mainQueue().after(1.0, {
|
|
let statusPath = appGroupUrl.path + "/Documents/importcompleted"
|
|
let _ = try? FileManager.default.createDirectory(atPath: appGroupUrl.path + "/Documents", withIntermediateDirectories: true, attributes: nil)
|
|
let _ = try? Data().write(to: URL(fileURLWithPath: statusPath))
|
|
})
|
|
return accountManager.transaction { transaction -> AccountManager in
|
|
transaction.setCurrentId(temporaryId)
|
|
transaction.updateRecord(temporaryId, { record in
|
|
if let record = record {
|
|
return AccountRecord(id: record.id, attributes: record.attributes, temporarySessionId: nil)
|
|
}
|
|
return record
|
|
})
|
|
return accountManager
|
|
}
|
|
}
|
|
return .single(accountManager)
|
|
}
|
|
}
|
|
})
|
|
|
|
let _ = (self.accountManagerPromise.get()
|
|
|> mapToSignal { manager in
|
|
return managedCleanupAccounts(networkArguments: networkArguments, accountManager: manager, rootPath: rootPath, auxiliaryMethods: telegramAccountAuxiliaryMethods)
|
|
}).start()
|
|
|
|
let applicationBindings = TelegramApplicationBindings(isMainApp: true, containerPath: appGroupUrl.path, appSpecificScheme: BuildConfig.shared().appSpecificUrlScheme, openUrl: { url in
|
|
var parsedUrl = URL(string: url)
|
|
if let parsed = parsedUrl {
|
|
if parsed.scheme == nil || parsed.scheme!.isEmpty {
|
|
parsedUrl = URL(string: "https://\(url)")
|
|
}
|
|
if parsed.scheme == "tg" {
|
|
return
|
|
}
|
|
}
|
|
|
|
if let parsedUrl = parsedUrl {
|
|
UIApplication.shared.openURL(parsedUrl)
|
|
} else if let escapedUrl = url.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed), let parsedUrl = URL(string: escapedUrl) {
|
|
UIApplication.shared.openURL(parsedUrl)
|
|
}
|
|
}, openUniversalUrl: { url, completion in
|
|
if #available(iOS 10.0, *) {
|
|
var parsedUrl = URL(string: url)
|
|
if let parsed = parsedUrl {
|
|
if parsed.scheme == nil || parsed.scheme!.isEmpty {
|
|
parsedUrl = URL(string: "https://\(url)")
|
|
}
|
|
}
|
|
|
|
if let parsedUrl = parsedUrl {
|
|
return UIApplication.shared.open(parsedUrl, options: [UIApplicationOpenURLOptionUniversalLinksOnly: true as NSNumber], completionHandler: { value in
|
|
completion.completion(value)
|
|
})
|
|
} else if let escapedUrl = url.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed), let parsedUrl = URL(string: escapedUrl) {
|
|
return UIApplication.shared.open(parsedUrl, options: [UIApplicationOpenURLOptionUniversalLinksOnly: true as NSNumber], completionHandler: { value in
|
|
completion.completion(value)
|
|
})
|
|
} else {
|
|
completion.completion(false)
|
|
}
|
|
} else {
|
|
completion.completion(false)
|
|
}
|
|
}, canOpenUrl: { url in
|
|
var parsedUrl = URL(string: url)
|
|
if let parsed = parsedUrl {
|
|
if parsed.scheme == nil || parsed.scheme!.isEmpty {
|
|
parsedUrl = URL(string: "https://\(url)")
|
|
}
|
|
}
|
|
if let parsedUrl = parsedUrl {
|
|
return UIApplication.shared.canOpenURL(parsedUrl)
|
|
} else if let escapedUrl = url.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed), let parsedUrl = URL(string: escapedUrl) {
|
|
return UIApplication.shared.canOpenURL(parsedUrl)
|
|
} else {
|
|
return false
|
|
}
|
|
}, getTopWindow: {
|
|
for window in application.windows.reversed() {
|
|
if window === self.window || window === statusBarHost.keyboardWindow {
|
|
return window
|
|
}
|
|
}
|
|
return application.windows.last
|
|
}, displayNotification: { text in
|
|
}, applicationInForeground: self.isInForegroundPromise.get(),
|
|
applicationIsActive: self.isActivePromise.get(),
|
|
clearMessageNotifications: { ids in
|
|
for id in ids {
|
|
self.clearNotificationsManager?.append(id)
|
|
}
|
|
}, pushIdleTimerExtension: {
|
|
let disposable = MetaDisposable()
|
|
Queue.mainQueue().async {
|
|
let wasEmpty = self.idleTimerExtensionSubscribers.isEmpty
|
|
let index = self.idleTimerExtensionSubscribers.add(Void())
|
|
|
|
if wasEmpty {
|
|
application.isIdleTimerDisabled = true
|
|
}
|
|
|
|
disposable.set(ActionDisposable {
|
|
Queue.mainQueue().async {
|
|
self.idleTimerExtensionSubscribers.remove(index)
|
|
if self.idleTimerExtensionSubscribers.isEmpty {
|
|
application.isIdleTimerDisabled = false
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
return disposable
|
|
}, openSettings: {
|
|
if let url = URL(string: UIApplicationOpenSettingsURLString) {
|
|
UIApplication.shared.openURL(url)
|
|
}
|
|
}, openAppStorePage: {
|
|
let appStoreId = BuildConfig.shared().appStoreId
|
|
if let url = URL(string: "itms-apps://itunes.apple.com/app/id\(appStoreId)") {
|
|
UIApplication.shared.openURL(url)
|
|
}
|
|
}, registerForNotifications: { completion in
|
|
let _ = (self.currentAuthorizedContext()
|
|
|> take(1)
|
|
|> deliverOnMainQueue).start(next: { context in
|
|
if let context = context {
|
|
self.registerForNotifications(account: context.account, authorize: true, completion: completion)
|
|
}
|
|
})
|
|
}, requestSiriAuthorization: { completion in
|
|
if #available(iOS 10, *) {
|
|
INPreferences.requestSiriAuthorization { status in
|
|
if case .authorized = status {
|
|
completion(true)
|
|
} else {
|
|
completion(false)
|
|
}
|
|
}
|
|
} else {
|
|
completion(false)
|
|
}
|
|
}, siriAuthorization: {
|
|
if #available(iOS 10, *) {
|
|
switch INPreferences.siriAuthorizationStatus() {
|
|
case .authorized:
|
|
return .allowed
|
|
case .denied, .restricted:
|
|
return .denied
|
|
case .notDetermined:
|
|
return .notDetermined
|
|
}
|
|
} else {
|
|
return .denied
|
|
}
|
|
}, getWindowHost: {
|
|
return self.nativeWindow
|
|
}, presentNativeController: { controller in
|
|
self.window?.rootViewController?.present(controller, animated: true, completion: nil)
|
|
}, dismissNativeController: {
|
|
self.window?.rootViewController?.dismiss(animated: true, completion: nil)
|
|
})
|
|
|
|
let watchManagerArgumentsPromise = Promise<WatchManagerArguments?>()
|
|
|
|
#if DEBUG
|
|
let testingEnvironment = false
|
|
#else
|
|
let testingEnvironment = false
|
|
#endif
|
|
|
|
self.context.set(self.accountManagerPromise.get()
|
|
|> deliverOnMainQueue
|
|
|> mapToSignal { accountManager -> Signal<ApplicationContext?, NoError> in
|
|
let replyFromNotificationsActive = self.replyFromNotificationsTokensPromise.get()
|
|
|> map {
|
|
!$0.isEmpty
|
|
}
|
|
|> distinctUntilChanged
|
|
return applicationContext(networkArguments: networkArguments, applicationBindings: applicationBindings, replyFromNotificationsActive: replyFromNotificationsActive, backgroundAudioActive: self.hasActiveAudioSession.get() |> distinctUntilChanged, watchManagerArguments: watchManagerArgumentsPromise.get(), accountManager: accountManager, rootPath: rootPath, legacyBasePath: appGroupUrl.path, testingEnvironment: testingEnvironment, mainWindow: self.mainWindow, reinitializedNotificationSettings: {
|
|
let _ = (self.context.get()
|
|
|> take(1)
|
|
|> deliverOnMainQueue).start(next: { value in
|
|
if let value = value, case let .authorized(context) = value {
|
|
self.registerForNotifications(account: context.account, authorize: false)
|
|
}
|
|
})
|
|
})
|
|
})
|
|
|
|
self.contextDisposable.set(self.context.get().start(next: { context in
|
|
assert(Queue.mainQueue().isCurrent())
|
|
var network: Network?
|
|
if let context = context {
|
|
switch context {
|
|
case let .unauthorized(unauthorized):
|
|
network = unauthorized.account.network
|
|
case let .authorized(authorized):
|
|
network = authorized.account.network
|
|
default:
|
|
break
|
|
}
|
|
}
|
|
|
|
Logger.shared.log("App \(self.episodeId)", "received context \(String(describing: context)) account \(String(describing: context?.accountId)) network \(String(describing: network))")
|
|
|
|
if let contextValue = self.contextValue {
|
|
(contextValue.account?.applicationContext as? TelegramApplicationContext)?.isCurrent = false
|
|
switch contextValue {
|
|
case let .unauthorized(unauthorized):
|
|
unauthorized.account.shouldBeServiceTaskMaster.set(.single(.never))
|
|
case let .authorized(authorized):
|
|
authorized.account.shouldBeServiceTaskMaster.set(.single(.never))
|
|
authorized.account.shouldKeepOnlinePresence.set(.single(false))
|
|
authorized.account.shouldExplicitelyKeepWorkerConnections.set(.single(false))
|
|
authorized.account.shouldKeepBackgroundDownloadConnections.set(.single(false))
|
|
default:
|
|
break
|
|
}
|
|
}
|
|
self.contextValue = context
|
|
if let context = context {
|
|
(context.account?.applicationContext as? TelegramApplicationContext)?.isCurrent = true
|
|
updateLegacyComponentsAccount(context.account)
|
|
self.mainWindow.viewController = context.rootController
|
|
self.mainWindow.topLevelOverlayControllers = context.overlayControllers
|
|
self.maybeDequeueNotificationPayloads()
|
|
self.maybeDequeueNotificationRequests()
|
|
self.maybeDequeueWakeups()
|
|
switch context {
|
|
case let .authorized(context):
|
|
var authorizeNotifications = true
|
|
if #available(iOS 10.0, *) {
|
|
authorizeNotifications = false
|
|
}
|
|
self.registerForNotifications(account: context.account, authorize: authorizeNotifications)
|
|
context.account.notificationToken.set(self.notificationTokenPromise.get())
|
|
context.account.voipToken.set(self.voipTokenPromise.get())
|
|
case .unauthorized:
|
|
break
|
|
case .upgrading:
|
|
break
|
|
}
|
|
} else {
|
|
self.mainWindow.viewController = nil
|
|
}
|
|
}))
|
|
|
|
self.watchCommunicationManagerPromise.set(watchCommunicationManager(context: self.context))
|
|
let _ = self.watchCommunicationManagerPromise.get().start(next: { manager in
|
|
if let manager = manager {
|
|
watchManagerArgumentsPromise.set(.single(manager.arguments))
|
|
} else {
|
|
watchManagerArgumentsPromise.set(.single(nil))
|
|
}
|
|
})
|
|
|
|
let pushRegistry = PKPushRegistry(queue: .main)
|
|
pushRegistry.desiredPushTypes = Set([.voIP])
|
|
self.pushRegistry = pushRegistry
|
|
pushRegistry.delegate = self
|
|
|
|
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)
|
|
case .upgrading:
|
|
return .single(0)
|
|
}
|
|
} else {
|
|
return .never()
|
|
}
|
|
}
|
|
|> deliverOnMainQueue).start(next: { count in
|
|
UIApplication.shared.applicationIconBadgeNumber = Int(count)
|
|
}))
|
|
|
|
if #available(iOS 9.1, *) {
|
|
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.account.telegramApplicationContext.currentPresentationData.with { $0 }
|
|
return .single(applicationShortcutItems(strings: presentationData.strings))
|
|
case .unauthorized:
|
|
return .single([])
|
|
case .upgrading:
|
|
return .single([])
|
|
}
|
|
} else {
|
|
return .never()
|
|
}
|
|
}
|
|
|> distinctUntilChanged
|
|
|> deliverOnMainQueue).start(next: { items in
|
|
if items.isEmpty {
|
|
UIApplication.shared.shortcutItems = nil
|
|
} else {
|
|
UIApplication.shared.shortcutItems = items.map({ $0.shortcutItem() })
|
|
}
|
|
}))
|
|
}
|
|
|
|
let _ = self.isInForegroundPromise.get().start(next: { value in
|
|
Logger.shared.log("App \(self.episodeId)", "isInForeground = \(value)")
|
|
})
|
|
let _ = self.isActivePromise.get().start(next: { value in
|
|
Logger.shared.log("App \(self.episodeId)", "isActive = \(value)")
|
|
})
|
|
|
|
/*if let url = launchOptions?[.url] {
|
|
if let url = url as? URL, url.scheme == "tg" {
|
|
self.openUrlWhenReady(url: url.absoluteString)
|
|
} else if let url = url as? String, url.lowercased().hasPrefix("tg://") {
|
|
self.openUrlWhenReady(url: url)
|
|
}
|
|
}*/
|
|
|
|
if application.applicationState == .active {
|
|
self.isInForegroundValue = true
|
|
self.isInForegroundPromise.set(true)
|
|
self.isActiveValue = true
|
|
self.isActivePromise.set(true)
|
|
}
|
|
|
|
BITHockeyBaseManager.setPresentAlert({ [weak self] alert in
|
|
if let strongSelf = self, let alert = alert {
|
|
var actions: [TextAlertAction] = []
|
|
for action in alert.actions {
|
|
let isDefault = action.style == .default
|
|
actions.append(TextAlertAction(type: isDefault ? .defaultAction : .genericAction, title: action.title ?? "", action: {
|
|
if let action = action as? BITAlertAction {
|
|
action.invokeAction()
|
|
}
|
|
}))
|
|
}
|
|
if let contextValue = strongSelf.contextValue {
|
|
if case let .authorized(context) = contextValue {
|
|
let presentationData = context.applicationContext.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)
|
|
}
|
|
}
|
|
}
|
|
})
|
|
|
|
BITHockeyBaseManager.setPresentView({ [weak self] controller in
|
|
if let strongSelf = self, let controller = controller {
|
|
let parent = LegacyController(presentation: .modal(animateIn: true), theme: nil)
|
|
let navigationController = UINavigationController(rootViewController: controller)
|
|
controller.navigation_setDismiss({ [weak parent] in
|
|
parent?.dismiss()
|
|
}, rootController: nil)
|
|
parent.bind(controller: navigationController)
|
|
strongSelf.mainWindow.present(parent, on: .root)
|
|
}
|
|
})
|
|
|
|
if !BuildConfig.shared().hockeyAppId.isEmpty {
|
|
BITHockeyManager.shared().configure(withIdentifier: BuildConfig.shared().hockeyAppId, delegate: self)
|
|
BITHockeyManager.shared().crashManager.crashManagerStatus = .alwaysAsk
|
|
BITHockeyManager.shared().start()
|
|
BITHockeyManager.shared().authenticator.authenticateInstallation()
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
func applicationWillResignActive(_ application: UIApplication) {
|
|
self.isActiveValue = false
|
|
self.isActivePromise.set(false)
|
|
self.clearNotificationsManager?.commitNow()
|
|
}
|
|
|
|
func applicationDidEnterBackground(_ application: UIApplication) {
|
|
self.isInForegroundValue = false
|
|
self.isInForegroundPromise.set(false)
|
|
self.isActiveValue = false
|
|
self.isActivePromise.set(false)
|
|
|
|
var taskId: Int?
|
|
taskId = application.beginBackgroundTask(withName: "lock", expirationHandler: {
|
|
if let taskId = taskId {
|
|
UIApplication.shared.endBackgroundTask(taskId)
|
|
}
|
|
})
|
|
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 5.0, execute: {
|
|
if let taskId = taskId {
|
|
UIApplication.shared.endBackgroundTask(taskId)
|
|
}
|
|
})
|
|
}
|
|
|
|
func applicationWillEnterForeground(_ application: UIApplication) {
|
|
if self.isActiveValue {
|
|
self.isInForegroundValue = true
|
|
self.isInForegroundPromise.set(true)
|
|
} else {
|
|
if #available(iOSApplicationExtension 12.0, *) {
|
|
DispatchQueue.main.async {
|
|
self.isInForegroundValue = true
|
|
self.isInForegroundPromise.set(true)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func applicationDidBecomeActive(_ application: UIApplication) {
|
|
self.isInForegroundValue = true
|
|
self.isInForegroundPromise.set(true)
|
|
self.isActiveValue = true
|
|
self.isActivePromise.set(true)
|
|
}
|
|
|
|
func applicationWillTerminate(_ application: UIApplication) {
|
|
Logger.shared.log("App \(self.episodeId)", "terminating")
|
|
}
|
|
|
|
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
|
|
self.notificationTokenPromise.set(.single(deviceToken))
|
|
}
|
|
|
|
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
|
|
var redactedPayload = userInfo
|
|
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("App \(self.episodeId)", "remoteNotification: \(redactedPayload)")
|
|
completionHandler(UIBackgroundFetchResult.noData)
|
|
}
|
|
|
|
func application(_ application: UIApplication, didReceive notification: UILocalNotification) {
|
|
if (application.applicationState == .inactive) {
|
|
Logger.shared.log("App \(self.episodeId)", "tap local notification \(String(describing: notification.userInfo)), applicationState \(application.applicationState)")
|
|
}
|
|
}
|
|
|
|
public func pushRegistry(_ registry: PKPushRegistry, didUpdate credentials: PKPushCredentials, for type: PKPushType) {
|
|
if case PKPushType.voIP = type {
|
|
Logger.shared.log("App \(self.episodeId)", "pushRegistry credentials: \(credentials.token as NSData)")
|
|
|
|
self.voipTokenPromise.set(.single(credentials.token))
|
|
}
|
|
}
|
|
|
|
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*/
|
|
|
|
self.queuedNotifications.append(payload)
|
|
self.maybeDequeueNotificationPayloads()
|
|
}
|
|
}
|
|
|
|
private func processPushPayload(_ payload: PKPushPayload, 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 {
|
|
encryptedPayload = encryptedPayload.replacingOccurrences(of: "-", with: "+")
|
|
encryptedPayload = encryptedPayload.replacingOccurrences(of: "_", with: "/")
|
|
while encryptedPayload.count % 4 != 0 {
|
|
encryptedPayload.append("=")
|
|
}
|
|
if let data = Data(base64Encoded: encryptedPayload) {
|
|
decryptedPayload = decryptedNotificationPayload(account: account, data: data)
|
|
|> map { value -> [AnyHashable: Any]? in
|
|
if let value = value, let object = try? JSONSerialization.jsonObject(with: value, options: []) {
|
|
return object as? [AnyHashable: Any]
|
|
}
|
|
return nil
|
|
}
|
|
} else {
|
|
decryptedPayload = .single(nil)
|
|
}
|
|
} else {
|
|
decryptedPayload = .single(nil)
|
|
}
|
|
|
|
let _ = (decryptedPayload
|
|
|> deliverOnMainQueue).start(next: { payload in
|
|
guard let payload = payload else {
|
|
return
|
|
}
|
|
|
|
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]
|
|
|
|
if UIApplication.shared.applicationState == .background {
|
|
var readMessageId: MessageId?
|
|
var isCall = false
|
|
var isAnnouncement = false
|
|
var isLocationPolling = false
|
|
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 {
|
|
self.queuedAnnouncements.append(body)
|
|
self.maybeDequeueAnnouncements()
|
|
} else {
|
|
var peerId: PeerId?
|
|
var notificationRequestId: NotificationManagedNotificationRequestId?
|
|
|
|
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
|
|
}
|
|
|
|
if let notificationRequestId = notificationRequestId {
|
|
self.queuedNotificationRequests.append((title, body, apnsSound, notificationRequestId))
|
|
self.maybeDequeueNotificationRequests()
|
|
} else if isMutePolling {
|
|
self.queuedMutePolling = true
|
|
self.maybeDequeueNotificationRequests()
|
|
}
|
|
}
|
|
} 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))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
var addedWakeups = Set<QueuedWakeup>()
|
|
if isCall {
|
|
addedWakeups.insert(.call)
|
|
}
|
|
if isLocationPolling {
|
|
addedWakeups.insert(.backgroundLocation)
|
|
}
|
|
if !addedWakeups.isEmpty {
|
|
self.queuedWakeups.formUnion(addedWakeups)
|
|
self.maybeDequeueWakeups()
|
|
}
|
|
if let readMessageId = readMessageId {
|
|
self.clearNotificationsManager?.append(readMessageId)
|
|
self.clearNotificationsManager?.commitNow()
|
|
|
|
let signal = self.currentAuthorizedContext()
|
|
|> take(1)
|
|
|> mapToSignal { context -> Signal<Void, NoError> in
|
|
if let context = context {
|
|
return context.account.postbox.transaction (ignoreDisabled: true, { transaction -> Void in
|
|
transaction.applyIncomingReadMaxId(readMessageId)
|
|
})
|
|
} else {
|
|
return .complete()
|
|
}
|
|
}
|
|
let _ = signal.start()
|
|
}
|
|
|
|
if let (datacenterId, host, port, secret) = configurationUpdate {
|
|
let signal = self.currentAuthorizedContext()
|
|
|> take(1)
|
|
|> mapToSignal { context -> Signal<Void, NoError> in
|
|
if let context = context {
|
|
context.account.network.mergeBackupDatacenterAddress(datacenterId: datacenterId, host: host, port: port, secret: secret)
|
|
}
|
|
return .complete()
|
|
}
|
|
let _ = signal.start()
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
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 {
|
|
return .single(context)
|
|
} else {
|
|
return .complete()
|
|
}
|
|
}
|
|
}
|
|
|
|
func application(_ application: UIApplication, open url: URL, sourceApplication: String?) -> Bool {
|
|
self.openUrl(url: url)
|
|
return true
|
|
}
|
|
|
|
func application(_ application: UIApplication, open url: URL, sourceApplication: String?, annotation: Any) -> Bool {
|
|
self.openUrl(url: url)
|
|
return true
|
|
}
|
|
|
|
func application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool {
|
|
self.openUrl(url: url)
|
|
return true
|
|
}
|
|
|
|
func application(_ application: UIApplication, handleOpen url: URL) -> Bool {
|
|
self.openUrl(url: url)
|
|
return true
|
|
}
|
|
|
|
private func openUrl(url: URL) {
|
|
let _ = (self.context.get()
|
|
|> flatMap { $0 }
|
|
|> filter { context in
|
|
switch context {
|
|
case .authorized, .unauthorized:
|
|
return true
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|> 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.applicationContext.currentPresentationData.with({ $0 }).strings
|
|
let controller = ProxyServerActionSheetController(theme: defaultPresentationTheme, strings: strings, 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.applicationContext.currentPresentationData.with({ $0 }).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)
|
|
}
|
|
default:
|
|
break
|
|
}
|
|
})
|
|
}
|
|
|
|
func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([Any]?) -> Void) -> Bool {
|
|
if #available(iOS 10.0, *) {
|
|
if let startCallIntent = userActivity.interaction?.intent as? SupportedStartCallIntent {
|
|
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.applicationContext.callManager?.requestCall(peerId: PeerId(namespace: Namespaces.Peer.CloudUser, id: userId), endCurrentIfAny: false)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if userActivity.activityType == NSUserActivityTypeBrowsingWeb, let url = userActivity.webpageURL {
|
|
self.openUrl(url: url)
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
@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)
|
|
}
|
|
} else {
|
|
return .complete()
|
|
}
|
|
}
|
|
|> take(1)
|
|
|> deliverOnMainQueue).start(next: { contextValue in
|
|
if let contextValue = contextValue, case let .authorized(context) = contextValue {
|
|
if let type = ApplicationShortcutItemType(rawValue: shortcutItem.type) {
|
|
switch type {
|
|
case .search:
|
|
context.openRootSearch()
|
|
case .compose:
|
|
context.openRootCompose()
|
|
case .camera:
|
|
context.openRootCamera()
|
|
case .savedMessages:
|
|
self.openChatWhenReady(peerId: context.account.peerId)
|
|
}
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
private func openChatWhenReady(peerId: PeerId, messageId: MessageId? = nil) {
|
|
self.openChatWhenReadyDisposable.set((self.authorizedContext()
|
|
|> take(1)
|
|
|> deliverOnMainQueue).start(next: { context in
|
|
context.openChatWithPeerId(peerId: peerId, messageId: messageId)
|
|
}))
|
|
}
|
|
|
|
private func openUrlWhenReady(url: String) {
|
|
self.openUrlWhenReadyDisposable.set((self.authorizedContext()
|
|
|> take(1)
|
|
|> deliverOnMainQueue).start(next: { context in
|
|
let presentationData = context.account.telegramApplicationContext.currentPresentationData.with { $0 }
|
|
openExternalUrl(account: context.account, url: url, presentationData: presentationData, applicationContext: context.account.telegramApplicationContext, navigationController: context.rootController, dismissInput: {
|
|
|
|
})
|
|
}))
|
|
}
|
|
|
|
@available(iOS 10.0, *)
|
|
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
|
|
if response.actionIdentifier == UNNotificationDefaultActionIdentifier {
|
|
if let peerId = peerIdFromNotification(response.notification) {
|
|
var messageId: MessageId? = nil
|
|
if response.notification.request.content.categoryIdentifier == "watch" {
|
|
messageId = messageIdFromNotification(peerId: peerId, notification: response.notification)
|
|
}
|
|
self.openChatWhenReady(peerId: peerId, messageId: messageId)
|
|
}
|
|
completionHandler()
|
|
} else if response.actionIdentifier == "reply", let peerId = peerIdFromNotification(response.notification) {
|
|
if let response = response as? UNTextInputNotificationResponse, !response.userText.isEmpty {
|
|
let text = response.userText
|
|
let token = addReplyFromNotificationsToken()
|
|
|
|
let signal = self.authorizedContext()
|
|
|> take(1)
|
|
|> mapToSignal { context -> Signal<Void, NoError> in
|
|
if let messageId = messageIdFromNotification(peerId: peerId, notification: response.notification) {
|
|
let _ = applyMaxReadIndexInteractively(postbox: context.account.postbox, stateManager: context.account.stateManager, index: MessageIndex(id: messageId, timestamp: 0)).start()
|
|
}
|
|
return enqueueMessages(account: context.account, peerId: peerId, messages: [EnqueueMessage.message(text: text, attributes: [], mediaReference: nil, replyToMessageId: nil, localGroupingKey: nil)])
|
|
|> map { messageIds -> MessageId? in
|
|
if messageIds.isEmpty {
|
|
return nil
|
|
} else {
|
|
return messageIds[0]
|
|
}
|
|
}
|
|
|> mapToSignal { messageId -> Signal<Void, NoError> in
|
|
if let messageId = messageId {
|
|
return context.account.postbox.unsentMessageIdsView()
|
|
|> filter { view in
|
|
return !view.ids.contains(messageId)
|
|
}
|
|
|> take(1)
|
|
|> mapToSignal { _ -> Signal<Void, NoError> in
|
|
return .complete()
|
|
}
|
|
} else {
|
|
return .complete()
|
|
}
|
|
}
|
|
}
|
|
|> deliverOnMainQueue
|
|
|> timeout(15.0, queue: Queue.mainQueue(), alternate: .complete() |> beforeCompleted {
|
|
/*let content = UNMutableNotificationContent()
|
|
content.body = "Please open the app to continue sending messages"
|
|
content.sound = UNNotificationSound.default()
|
|
content.categoryIdentifier = "error"
|
|
content.userInfo = ["peerId": peerId as NSNumber]
|
|
|
|
let request = UNNotificationRequest(identifier: "reply-error", content: content, trigger: nil)
|
|
|
|
let center = UNUserNotificationCenter.current()
|
|
center.add(request)*/
|
|
})
|
|
|
|
let disposable = MetaDisposable()
|
|
disposable.set((signal
|
|
|> afterDisposed { [weak disposable] in
|
|
Queue.mainQueue().async {
|
|
if let disposable = disposable {
|
|
self.replyFromNotificationsDisposables.remove(disposable)
|
|
}
|
|
self.removeReplyFromNotificationsToken(token)
|
|
completionHandler()
|
|
}
|
|
}).start())
|
|
self.replyFromNotificationsDisposables.add(disposable)
|
|
} else {
|
|
completionHandler()
|
|
}
|
|
} else {
|
|
completionHandler()
|
|
}
|
|
}
|
|
|
|
private func registerForNotifications(account: Account, authorize: Bool = true, completion: @escaping (Bool) -> Void = { _ in }) {
|
|
let presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 }
|
|
let _ = (account.postbox.transaction { transaction -> Bool in
|
|
let settings = transaction.getPreferencesEntry(key: ApplicationSpecificPreferencesKeys.inAppNotificationSettings) as? InAppNotificationSettings ?? InAppNotificationSettings.defaultSettings
|
|
return settings.displayNameOnLockscreen
|
|
}
|
|
|> deliverOnMainQueue).start(next: { displayNames in
|
|
self.registerForNotifications(replyString: presentationData.strings.Notification_Reply, messagePlaceholderString: presentationData.strings.Conversation_InputTextPlaceholder, hiddenContentString: presentationData.strings.Watch_MessageView_Title, includeNames: displayNames, authorize: authorize, completion: completion)
|
|
})
|
|
}
|
|
|
|
|
|
private func registerForNotifications(replyString: String, messagePlaceholderString: String, hiddenContentString: String, includeNames: Bool, authorize: Bool = true, completion: @escaping (Bool) -> Void = { _ in }) {
|
|
if #available(iOS 10.0, *) {
|
|
let notificationCenter = UNUserNotificationCenter.current()
|
|
notificationCenter.getNotificationSettings(completionHandler: { settings in
|
|
switch (settings.authorizationStatus, authorize) {
|
|
case (.authorized, _), (.notDetermined, true):
|
|
notificationCenter.requestAuthorization(options: [.badge, .sound, .alert], completionHandler: { result, _ in
|
|
completion(result)
|
|
if result {
|
|
Queue.mainQueue().async {
|
|
let reply = UNTextInputNotificationAction(identifier: "reply", title: replyString, options: [], textInputButtonTitle: replyString, textInputPlaceholder: messagePlaceholderString)
|
|
|
|
let unknownMessageCategory: UNNotificationCategory
|
|
let replyMessageCategory: UNNotificationCategory
|
|
let replyLegacyMessageCategory: UNNotificationCategory
|
|
let replyLegacyMediaMessageCategory: UNNotificationCategory
|
|
let replyMediaMessageCategory: UNNotificationCategory
|
|
let muteMessageCategory: UNNotificationCategory
|
|
let muteMediaMessageCategory: UNNotificationCategory
|
|
if #available(iOS 11.0, *) {
|
|
var options: UNNotificationCategoryOptions = []
|
|
if includeNames {
|
|
options.insert(.hiddenPreviewsShowTitle)
|
|
}
|
|
|
|
unknownMessageCategory = UNNotificationCategory(identifier: "unknown", actions: [], intentIdentifiers: [], hiddenPreviewsBodyPlaceholder: hiddenContentString, options: options)
|
|
replyMessageCategory = UNNotificationCategory(identifier: "withReply", actions: [reply], intentIdentifiers: [], hiddenPreviewsBodyPlaceholder: hiddenContentString, options: options)
|
|
replyLegacyMessageCategory = UNNotificationCategory(identifier: "r", actions: [reply], intentIdentifiers: [], hiddenPreviewsBodyPlaceholder: hiddenContentString, options: options)
|
|
replyLegacyMediaMessageCategory = UNNotificationCategory(identifier: "m", actions: [reply], intentIdentifiers: [], hiddenPreviewsBodyPlaceholder: hiddenContentString, options: options)
|
|
replyMediaMessageCategory = UNNotificationCategory(identifier: "withReplyMedia", actions: [reply], intentIdentifiers: [], hiddenPreviewsBodyPlaceholder: hiddenContentString, options: options)
|
|
muteMessageCategory = UNNotificationCategory(identifier: "withMute", actions: [], intentIdentifiers: [], hiddenPreviewsBodyPlaceholder: hiddenContentString, options: options)
|
|
muteMediaMessageCategory = UNNotificationCategory(identifier: "withMuteMedia", actions: [], intentIdentifiers: [], hiddenPreviewsBodyPlaceholder: hiddenContentString, options: options)
|
|
} else {
|
|
unknownMessageCategory = UNNotificationCategory(identifier: "unknown", actions: [], intentIdentifiers: [], options: [])
|
|
replyMessageCategory = UNNotificationCategory(identifier: "withReply", actions: [reply], intentIdentifiers: [], options: [])
|
|
replyLegacyMessageCategory = UNNotificationCategory(identifier: "r", actions: [reply], intentIdentifiers: [], options: [])
|
|
replyLegacyMediaMessageCategory = UNNotificationCategory(identifier: "m", actions: [reply], intentIdentifiers: [], options: [])
|
|
replyMediaMessageCategory = UNNotificationCategory(identifier: "withReplyMedia", actions: [reply], intentIdentifiers: [], options: [])
|
|
muteMessageCategory = UNNotificationCategory(identifier: "withMute", actions: [], intentIdentifiers: [], options: [])
|
|
muteMediaMessageCategory = UNNotificationCategory(identifier: "withMuteMedia", actions: [], intentIdentifiers: [], options: [])
|
|
}
|
|
|
|
UNUserNotificationCenter.current().setNotificationCategories([unknownMessageCategory, replyMessageCategory, replyLegacyMessageCategory, replyLegacyMediaMessageCategory, replyMediaMessageCategory, muteMessageCategory, muteMediaMessageCategory])
|
|
|
|
UIApplication.shared.registerForRemoteNotifications()
|
|
}
|
|
}
|
|
})
|
|
default:
|
|
break
|
|
}
|
|
})
|
|
} else {
|
|
let settings = UIUserNotificationSettings(types: [.badge, .sound, .alert], categories:[])
|
|
UIApplication.shared.registerUserNotificationSettings(settings)
|
|
|
|
UIApplication.shared.registerForRemoteNotifications()
|
|
}
|
|
}
|
|
|
|
private func maybeDequeueNotificationPayloads() {
|
|
if let contextValue = self.contextValue, case let .authorized(context) = contextValue, !self.queuedNotifications.isEmpty {
|
|
let queuedNotifications = self.queuedNotifications
|
|
self.queuedNotifications = []
|
|
for payload in queuedNotifications {
|
|
self.processPushPayload(payload, account: context.account)
|
|
}
|
|
}
|
|
}
|
|
|
|
private func maybeDequeueNotificationRequests() {
|
|
if let contextValue = self.contextValue, case let .authorized(context) = contextValue {
|
|
let requests = self.queuedNotificationRequests
|
|
self.queuedNotificationRequests = []
|
|
let queuedMutePolling = self.queuedMutePolling
|
|
self.queuedMutePolling = false
|
|
|
|
let _ = (context.account.postbox.transaction(ignoreDisabled: true, { transaction -> PostboxAccessChallengeData in
|
|
return transaction.getAccessChallengeData()
|
|
})
|
|
|> deliverOnMainQueue).start(next: { accessChallengeData in
|
|
guard let contextValue = self.contextValue, case let .authorized(context) = contextValue else {
|
|
Logger.shared.log("App \(self.episodeId)", "Couldn't process remote notification request")
|
|
return
|
|
}
|
|
|
|
let strings = context.account.telegramApplicationContext.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.wakeupManager.wakeupForIncomingMessages(completion: { messageIds -> Signal<Void, NoError> in
|
|
if let contextValue = self.contextValue, case let .authorized(context) = contextValue {
|
|
if handleVoipNotifications {
|
|
return context.notificationManager.commitRemoteNotification(originalRequestId: requestId, messageIds: messageIds)
|
|
} else {
|
|
return context.notificationManager.commitRemoteNotification(originalRequestId: nil, messageIds: [])
|
|
}
|
|
} else {
|
|
Logger.shared.log("App \(self.episodeId)", "Couldn't process remote notifications wakeup result")
|
|
return .complete()
|
|
}
|
|
})
|
|
}
|
|
if queuedMutePolling {
|
|
context.wakeupManager.wakeupForIncomingMessages(completion: { messageIds -> Signal<Void, NoError> in
|
|
if let contextValue = self.contextValue, case .authorized = contextValue {
|
|
return .single(Void())
|
|
} else {
|
|
Logger.shared.log("App \(self.episodeId)", "Couldn't process remote notifications wakeup result")
|
|
return .single(Void())
|
|
}
|
|
})
|
|
}
|
|
})
|
|
} else {
|
|
Logger.shared.log("App \(self.episodeId)", "maybeDequeueNotificationRequests failed, no active context")
|
|
}
|
|
}
|
|
|
|
private func maybeDequeueAnnouncements() {
|
|
if let contextValue = self.contextValue, case let .authorized(context) = contextValue, !self.queuedAnnouncements.isEmpty {
|
|
let queuedAnnouncements = self.queuedAnnouncements
|
|
self.queuedAnnouncements = []
|
|
let _ = (context.account.postbox.transaction(ignoreDisabled: true, { transaction -> [MessageId: String] in
|
|
var result: [MessageId: String] = [:]
|
|
let timestamp = Int32(context.account.network.globalTime)
|
|
let servicePeer = TelegramUser(id: PeerId(namespace: Namespaces.Peer.CloudUser, id: 777000), accessHash: nil, firstName: "Telegram", lastName: nil, username: nil, phone: "42777", photo: [], botInfo: nil, restrictionInfo: nil, flags: [.isVerified])
|
|
if transaction.getPeer(servicePeer.id) == nil {
|
|
transaction.updatePeersInternal([servicePeer], update: { _, updated in
|
|
return updated
|
|
})
|
|
}
|
|
for body in queuedAnnouncements {
|
|
let globalId = arc4random64()
|
|
var attributes: [MessageAttribute] = []
|
|
let entities = generateTextEntities(body, enabledTypes: .all)
|
|
if !entities.isEmpty {
|
|
attributes.append(TextEntitiesMessageAttribute(entities: entities))
|
|
}
|
|
let message = StoreMessage(id: .Partial(servicePeer.id, Namespaces.Message.Local), globallyUniqueId: globalId, groupingKey: nil, timestamp: timestamp, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, authorId: servicePeer.id, text: body, attributes: attributes, media: [])
|
|
let ids = transaction.addMessages([message], location: .Random)
|
|
if let id = ids[globalId] {
|
|
result[id] = body
|
|
}
|
|
}
|
|
return result
|
|
}) |> deliverOnMainQueue).start(next: { result in
|
|
if let contextValue = self.contextValue, case let .authorized(context) = contextValue {
|
|
for (id, text) in result {
|
|
context.notificationManager.enqueueRemoteNotification(title: "", text: text, apnsSound: nil, requestId: .messageId(id), strings: context.account.telegramApplicationContext.currentPresentationData.with({ $0 }).strings, accessChallengeData: .none)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
private func maybeDequeueWakeups() {
|
|
for wakeup in self.queuedWakeups {
|
|
switch wakeup {
|
|
case .call:
|
|
if let contextValue = self.contextValue, case let .authorized(context) = contextValue {
|
|
context.wakeupManager.wakeupForIncomingMessages()
|
|
}
|
|
case .backgroundLocation:
|
|
if UIApplication.shared.applicationState == .background {
|
|
if let contextValue = self.contextValue, case let .authorized(context) = contextValue {
|
|
context.applicationContext.liveLocationManager?.pollOnce()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
override var next: UIResponder? {
|
|
if let contextValue = self.contextValue, case let .authorized(context) = contextValue, let controller = context.applicationContext.keyShortcutsController {
|
|
return controller
|
|
}
|
|
return super.next
|
|
}
|
|
|
|
@objc func debugPressed() {
|
|
let _ = (Logger.shared.collectLogs()
|
|
|> deliverOnMainQueue).start(next: { logs in
|
|
var activityItems: [Any] = []
|
|
for (_, path) in logs {
|
|
activityItems.append(URL(fileURLWithPath: path))
|
|
}
|
|
|
|
let activityController = UIActivityViewController(activityItems: activityItems, applicationActivities: nil)
|
|
|
|
self.window?.rootViewController?.present(activityController, animated: true, completion: nil)
|
|
})
|
|
}
|
|
}
|
|
|
|
@available(iOS 10.0, *)
|
|
private func peerIdFromNotification(_ notification: UNNotification) -> PeerId? {
|
|
if let peerId = notification.request.content.userInfo["peerId"] as? Int64 {
|
|
return PeerId(peerId)
|
|
} else {
|
|
let payload = notification.request.content.userInfo
|
|
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))
|
|
} else if let fromId = payload["encryption_id"] {
|
|
let fromIdValue = fromId as! NSString
|
|
peerId = PeerId(namespace: Namespaces.Peer.SecretChat, id: Int32(fromIdValue.intValue))
|
|
}
|
|
return peerId
|
|
}
|
|
}
|
|
|
|
@available(iOS 10.0, *)
|
|
private func messageIdFromNotification(peerId: PeerId, notification: UNNotification) -> MessageId? {
|
|
let payload = notification.request.content.userInfo
|
|
if let messageIdNamespace = payload["messageId.namespace"] as? Int32, let messageIdId = payload["messageId.id"] as? Int32 {
|
|
return MessageId(peerId: peerId, namespace: messageIdNamespace, id: messageIdId)
|
|
}
|
|
|
|
if let msgId = payload["msg_id"] {
|
|
let msgIdValue = msgId as! NSString
|
|
return MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: Int32(msgIdValue.intValue))
|
|
}
|
|
return nil
|
|
}
|