mirror of https://github.com/oxen-io/session-ios
				
				
				
			
			You cannot select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
	
	
		
			179 lines
		
	
	
		
			6.3 KiB
		
	
	
	
		
			Swift
		
	
		
		
			
		
	
	
			179 lines
		
	
	
		
			6.3 KiB
		
	
	
	
		
			Swift
		
	
| 
											8 years ago
										 | // | ||
| 
											7 years ago
										 | //  Copyright (c) 2019 Open Whisper Systems. All rights reserved. | ||
| 
											8 years ago
										 | // | ||
|  | 
 | ||
|  | import Foundation | ||
|  | import PromiseKit | ||
|  | import PushKit | ||
| 
											5 years ago
										 | import SignalUtilitiesKit | ||
|  | import SignalUtilitiesKit | ||
| 
											8 years ago
										 | 
 | ||
| 
											8 years ago
										 | public enum PushRegistrationError: Error { | ||
|  |     case assertionError(description: String) | ||
|  |     case pushNotSupported(description: String) | ||
|  |     case timeout | ||
|  | } | ||
|  | 
 | ||
| 
											8 years ago
										 | /**
 | ||
|  |  * Singleton used to integrate with push notification services - registration and routing received remote notifications. | ||
|  |  */ | ||
| 
											5 years ago
										 | @objc public class PushRegistrationManager: NSObject { | ||
| 
											8 years ago
										 | 
 | ||
| 
											7 years ago
										 |     // MARK: - Dependencies | ||
| 
											7 years ago
										 | 
 | ||
| 
											7 years ago
										 |     private var notificationPresenter: NotificationPresenter { | ||
|  |         return AppEnvironment.shared.notificationPresenter | ||
| 
											8 years ago
										 |     } | ||
|  | 
 | ||
| 
											7 years ago
										 |     // MARK: - Singleton class | ||
| 
											8 years ago
										 | 
 | ||
| 
											7 years ago
										 |     @objc | ||
|  |     public static var shared: PushRegistrationManager { | ||
|  |         get { | ||
|  |             return AppEnvironment.shared.pushRegistrationManager | ||
|  |         } | ||
|  |     } | ||
| 
											8 years ago
										 | 
 | ||
| 
											7 years ago
										 |     override init() { | ||
| 
											8 years ago
										 |         super.init() | ||
| 
											8 years ago
										 | 
 | ||
|  |         SwiftSingletons.register(self) | ||
| 
											8 years ago
										 |     } | ||
|  | 
 | ||
| 
											8 years ago
										 |     private var vanillaTokenPromise: Promise<Data>? | ||
| 
											7 years ago
										 |     private var vanillaTokenResolver: Resolver<Data>? | ||
| 
											8 years ago
										 | 
 | ||
|  |     private var voipRegistry: PKPushRegistry? | ||
|  |     private var voipTokenPromise: Promise<Data>? | ||
| 
											7 years ago
										 |     private var voipTokenResolver: Resolver<Data>? | ||
| 
											8 years ago
										 | 
 | ||
|  |     // MARK: Public interface | ||
|  | 
 | ||
|  |     public func requestPushTokens() -> Promise<(pushToken: String, voipToken: String)> { | ||
| 
											7 years ago
										 |         return firstly { | ||
|  |             self.registerUserNotificationSettings() | ||
|  |         }.then { () -> Promise<(pushToken: String, voipToken: String)> in | ||
| 
											8 years ago
										 |             guard !Platform.isSimulator else { | ||
| 
											8 years ago
										 |                 throw PushRegistrationError.pushNotSupported(description: "Push not supported on simulators") | ||
| 
											8 years ago
										 |             } | ||
|  | 
 | ||
| 
											6 years ago
										 |             return self.registerForVanillaPushToken().map { vanillaPushToken -> (pushToken: String, voipToken: String) in | ||
|  |                 return (pushToken: vanillaPushToken, voipToken: "") | ||
| 
											8 years ago
										 |             } | ||
|  |         } | ||
|  |     } | ||
|  | 
 | ||
|  |     // MARK: Vanilla push token | ||
|  | 
 | ||
|  |     // Vanilla push token is obtained from the system via AppDelegate | ||
|  |     @objc | ||
|  |     public func didReceiveVanillaPushToken(_ tokenData: Data) { | ||
| 
											7 years ago
										 |         guard let vanillaTokenResolver = self.vanillaTokenResolver else { | ||
| 
											7 years ago
										 |             owsFailDebug("promise completion in \(#function) unexpectedly nil") | ||
| 
											8 years ago
										 |             return | ||
|  |         } | ||
|  | 
 | ||
| 
											7 years ago
										 |         vanillaTokenResolver.fulfill(tokenData) | ||
| 
											8 years ago
										 |     } | ||
|  | 
 | ||
|  |     // Vanilla push token is obtained from the system via AppDelegate     | ||
|  |     @objc | ||
|  |     public func didFailToReceiveVanillaPushToken(error: Error) { | ||
| 
											7 years ago
										 |         guard let vanillaTokenResolver = self.vanillaTokenResolver else { | ||
| 
											7 years ago
										 |             owsFailDebug("promise completion in \(#function) unexpectedly nil") | ||
| 
											8 years ago
										 |             return | ||
|  |         } | ||
|  | 
 | ||
| 
											7 years ago
										 |         vanillaTokenResolver.reject(error) | ||
| 
											8 years ago
										 |     } | ||
|  | 
 | ||
|  |     // MARK: helpers | ||
|  | 
 | ||
|  |     // User notification settings must be registered *before* AppDelegate will | ||
| 
											7 years ago
										 |     // return any requested push tokens. | ||
| 
											7 years ago
										 |     public func registerUserNotificationSettings() -> Promise<Void> { | ||
| 
											7 years ago
										 |         AssertIsOnMainThread() | ||
| 
											7 years ago
										 |         return notificationPresenter.registerNotificationSettings() | ||
| 
											8 years ago
										 |     } | ||
|  | 
 | ||
| 
											8 years ago
										 |     /**
 | ||
| 
											8 years ago
										 |      * When users have disabled notifications and background fetch, the system hangs when returning a push token. | ||
|  |      * More specifically, after registering for remote notification, the app delegate calls neither | ||
|  |      * `didFailToRegisterForRemoteNotificationsWithError` nor `didRegisterForRemoteNotificationsWithDeviceToken` | ||
|  |      * This behavior is identical to what you'd see if we hadn't previously registered for user notification settings, though | ||
|  |      * in this case we've verified that we *have* properly registered notification settings. | ||
| 
											8 years ago
										 |      */ | ||
|  |     private var isSusceptibleToFailedPushRegistration: Bool { | ||
|  | 
 | ||
|  |         // Only affects users who have disabled both: background refresh *and* notifications | ||
|  |         guard UIApplication.shared.backgroundRefreshStatus == .denied else { | ||
|  |             return false | ||
|  |         } | ||
|  | 
 | ||
|  |         guard let notificationSettings = UIApplication.shared.currentUserNotificationSettings else { | ||
|  |             return false | ||
|  |         } | ||
|  | 
 | ||
|  |         guard notificationSettings.types == [] else { | ||
|  |             return false | ||
|  |         } | ||
|  | 
 | ||
|  |         return true | ||
|  |     } | ||
|  | 
 | ||
| 
											8 years ago
										 |     private func registerForVanillaPushToken() -> Promise<String> { | ||
| 
											7 years ago
										 |         AssertIsOnMainThread() | ||
| 
											8 years ago
										 | 
 | ||
| 
											8 years ago
										 |         guard self.vanillaTokenPromise == nil else { | ||
|  |             let promise = vanillaTokenPromise! | ||
|  |             assert(promise.isPending) | ||
| 
											7 years ago
										 |             return promise.map { $0.hexEncodedString } | ||
| 
											8 years ago
										 |         } | ||
|  | 
 | ||
| 
											5 years ago
										 |         // No pending vanilla token yet; create a new promise | ||
| 
											7 years ago
										 |         let (promise, resolver) = Promise<Data>.pending() | ||
| 
											8 years ago
										 |         self.vanillaTokenPromise = promise | ||
| 
											7 years ago
										 |         self.vanillaTokenResolver = resolver | ||
|  | 
 | ||
| 
											8 years ago
										 |         UIApplication.shared.registerForRemoteNotifications() | ||
|  | 
 | ||
| 
											8 years ago
										 |         let kTimeout: TimeInterval = 10 | ||
| 
											7 years ago
										 |         let timeout: Promise<Data> = after(seconds: kTimeout).map { throw PushRegistrationError.timeout } | ||
| 
											8 years ago
										 |         let promiseWithTimeout: Promise<Data> = race(promise, timeout) | ||
|  | 
 | ||
|  |         return promiseWithTimeout.recover { error -> Promise<Data> in | ||
|  |             switch error { | ||
|  |             case PushRegistrationError.timeout: | ||
|  |                 if self.isSusceptibleToFailedPushRegistration { | ||
|  |                     // If we've timed out on a device known to be susceptible to failures, quit trying | ||
|  |                     // so the user doesn't remain indefinitely hung for no good reason. | ||
|  |                     throw PushRegistrationError.pushNotSupported(description: "Device configuration disallows push notifications") | ||
|  |                 } else { | ||
|  |                     // Sometimes registration can just take a while. | ||
|  |                     // If we're not on a device known to be susceptible to push registration failure, | ||
|  |                     // just return the original promise. | ||
|  |                     return promise | ||
|  |                 } | ||
|  |             default: | ||
|  |                 throw error | ||
|  |             } | ||
| 
											7 years ago
										 |         }.map { (pushTokenData: Data) -> String in | ||
| 
											8 years ago
										 |             if self.isSusceptibleToFailedPushRegistration { | ||
| 
											5 years ago
										 |                 // Sentinal in case this bug is fixed | ||
| 
											5 years ago
										 |                 OWSLogger.debug("Device was unexpectedly able to complete push registration even though it was susceptible to failure.") | ||
| 
											8 years ago
										 |             } | ||
|  | 
 | ||
|  |             return pushTokenData.hexEncodedString | ||
| 
											7 years ago
										 |         }.ensure { | ||
| 
											8 years ago
										 |             self.vanillaTokenPromise = nil | ||
| 
											8 years ago
										 |         } | ||
|  |     } | ||
|  | } | ||
|  | 
 | ||
|  | // We transmit pushToken data as hex encoded string to the server | ||
|  | fileprivate extension Data { | ||
| 
											8 years ago
										 |     var hexEncodedString: String { | ||
| 
											8 years ago
										 |         return map { String(format: "%02hhx", $0) }.joined() | ||
|  |     } | ||
|  | } |