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.
		
		
		
		
		
			
		
			
				
	
	
		
			176 lines
		
	
	
		
			6.7 KiB
		
	
	
	
		
			Swift
		
	
			
		
		
	
	
			176 lines
		
	
	
		
			6.7 KiB
		
	
	
	
		
			Swift
		
	
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
 | 
						|
 | 
						|
import Foundation
 | 
						|
import GRDB
 | 
						|
import PromiseKit
 | 
						|
import SignalCoreKit
 | 
						|
import SessionMessagingKit
 | 
						|
import SessionUtilitiesKit
 | 
						|
 | 
						|
public enum SyncPushTokensJob: JobExecutor {
 | 
						|
    public static let maxFailureCount: Int = -1
 | 
						|
    public static let requiresThreadId: Bool = false
 | 
						|
    public static let requiresInteractionId: Bool = false
 | 
						|
    
 | 
						|
    public static func run(
 | 
						|
        _ job: Job,
 | 
						|
        queue: DispatchQueue,
 | 
						|
        success: @escaping (Job, Bool) -> (),
 | 
						|
        failure: @escaping (Job, Error?, Bool) -> (),
 | 
						|
        deferred: @escaping (Job) -> ()
 | 
						|
    ) {
 | 
						|
        // Don't run when inactive or not in main app
 | 
						|
        guard (UserDefaults.sharedLokiProject?[.isMainAppActive]).defaulting(to: false) else {
 | 
						|
            deferred(job) // Don't need to do anything if it's not the main app
 | 
						|
            return
 | 
						|
        }
 | 
						|
        
 | 
						|
        // We need to check a UIApplication setting which needs to run on the main thread so if we aren't on
 | 
						|
        // the main thread then swap to it
 | 
						|
        guard Thread.isMainThread else {
 | 
						|
            DispatchQueue.main.async {
 | 
						|
                run(job, queue: queue, success: success, failure: failure, deferred: deferred)
 | 
						|
            }
 | 
						|
            return
 | 
						|
        }
 | 
						|
        
 | 
						|
        // Push tokens don't normally change while the app is launched, so checking once during launch is
 | 
						|
        // usually sufficient, but e.g. on iOS11, users who have disabled "Allow Notifications" and disabled
 | 
						|
        // "Background App Refresh" will not be able to obtain an APN token. Enabling those settings does not
 | 
						|
        // restart the app, so we check every activation for users who haven't yet registered.
 | 
						|
        guard job.behaviour != .recurringOnActive || !UIApplication.shared.isRegisteredForRemoteNotifications else {
 | 
						|
            deferred(job) // Don't need to do anything if push notifications are already registered
 | 
						|
            return
 | 
						|
        }
 | 
						|
        
 | 
						|
        Logger.info("Retrying remote notification registration since user hasn't registered yet.")
 | 
						|
        
 | 
						|
        // Determine if we want to upload only if stale (Note: This should default to true, and be true if
 | 
						|
        // 'details' isn't provided)
 | 
						|
        let uploadOnlyIfStale: Bool = ((try? JSONDecoder().decode(Details.self, from: job.details ?? Data()))?.uploadOnlyIfStale ?? true)
 | 
						|
        
 | 
						|
        // Get the app version info (used to determine if we want to update the push tokens)
 | 
						|
        let lastAppVersion: String? = AppVersion.sharedInstance().lastAppVersion
 | 
						|
        let currentAppVersion: String? = AppVersion.sharedInstance().currentAppVersion
 | 
						|
        
 | 
						|
        PushRegistrationManager.shared.requestPushTokens()
 | 
						|
            .then(on: queue) { (pushToken: String, voipToken: String) -> Promise<Void> in
 | 
						|
                let lastPushToken: String? = Storage.shared[.lastRecordedPushToken]
 | 
						|
                let lastVoipToken: String? = Storage.shared[.lastRecordedVoipToken]
 | 
						|
                let shouldUploadTokens: Bool = (
 | 
						|
                    !uploadOnlyIfStale || (
 | 
						|
                        lastPushToken != pushToken ||
 | 
						|
                        lastVoipToken != voipToken
 | 
						|
                    ) ||
 | 
						|
                    lastAppVersion != currentAppVersion
 | 
						|
                )
 | 
						|
 | 
						|
                guard shouldUploadTokens else { return Promise.value(()) }
 | 
						|
                
 | 
						|
                let (promise, seal) = Promise<Void>.pending()
 | 
						|
                
 | 
						|
                SyncPushTokensJob.registerForPushNotifications(
 | 
						|
                    pushToken: pushToken,
 | 
						|
                    voipToken: voipToken,
 | 
						|
                    isForcedUpdate: shouldUploadTokens,
 | 
						|
                    success: { seal.fulfill(()) },
 | 
						|
                    failure: seal.reject
 | 
						|
                )
 | 
						|
                
 | 
						|
                return promise
 | 
						|
                    .done(on: queue) { _ in
 | 
						|
                        Logger.warn("Recording push tokens locally. pushToken: \(redact(pushToken)), voipToken: \(redact(voipToken))")
 | 
						|
 | 
						|
                        Storage.shared.write { db in
 | 
						|
                            db[.lastRecordedPushToken] = pushToken
 | 
						|
                            db[.lastRecordedVoipToken] = voipToken
 | 
						|
                        }
 | 
						|
                    }
 | 
						|
            }
 | 
						|
            .ensure(on: queue) { success(job, false) }    // We want to complete this job regardless of success or failure
 | 
						|
            .retainUntilComplete()
 | 
						|
    }
 | 
						|
    
 | 
						|
    public static func run(uploadOnlyIfStale: Bool) {
 | 
						|
        guard let job: Job = Job(
 | 
						|
            variant: .syncPushTokens,
 | 
						|
            details: SyncPushTokensJob.Details(
 | 
						|
                uploadOnlyIfStale: uploadOnlyIfStale
 | 
						|
            )
 | 
						|
        )
 | 
						|
        else { return }
 | 
						|
                                 
 | 
						|
        SyncPushTokensJob.run(
 | 
						|
            job,
 | 
						|
            queue: DispatchQueue.global(qos: .default),
 | 
						|
            success: { _, _ in },
 | 
						|
            failure: { _, _, _ in },
 | 
						|
            deferred: { _ in }
 | 
						|
        )
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
// MARK: - SyncPushTokensJob.Details
 | 
						|
 | 
						|
extension SyncPushTokensJob {
 | 
						|
    public struct Details: Codable {
 | 
						|
        public let uploadOnlyIfStale: Bool
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
// MARK: - Convenience
 | 
						|
 | 
						|
private func redact(_ string: String) -> String {
 | 
						|
    return OWSIsDebugBuild() ? string : "[ READACTED \(string.prefix(2))...\(string.suffix(2)) ]"
 | 
						|
}
 | 
						|
 | 
						|
extension SyncPushTokensJob {
 | 
						|
    fileprivate static func registerForPushNotifications(
 | 
						|
        pushToken: String,
 | 
						|
        voipToken: String,
 | 
						|
        isForcedUpdate: Bool,
 | 
						|
        success: @escaping () -> (),
 | 
						|
        failure: @escaping (Error) -> (),
 | 
						|
        remainingRetries: Int = 3
 | 
						|
    ) {
 | 
						|
        let isUsingFullAPNs: Bool = UserDefaults.standard[.isUsingFullAPNs]
 | 
						|
        let pushTokenAsData = Data(hex: pushToken)
 | 
						|
        let promise: Promise<Void> = (isUsingFullAPNs ?
 | 
						|
            PushNotificationAPI.register(
 | 
						|
                with: pushTokenAsData,
 | 
						|
                publicKey: getUserHexEncodedPublicKey(),
 | 
						|
                isForcedUpdate: isForcedUpdate
 | 
						|
            ) :
 | 
						|
            PushNotificationAPI.unregister(pushTokenAsData)
 | 
						|
        )
 | 
						|
        
 | 
						|
        promise
 | 
						|
            .done { success() }
 | 
						|
            .catch { error in
 | 
						|
                guard remainingRetries == 0 else {
 | 
						|
                    SyncPushTokensJob.registerForPushNotifications(
 | 
						|
                        pushToken: pushToken,
 | 
						|
                        voipToken: voipToken,
 | 
						|
                        isForcedUpdate: isForcedUpdate,
 | 
						|
                        success: success,
 | 
						|
                        failure: failure,
 | 
						|
                        remainingRetries: (remainingRetries - 1)
 | 
						|
                    )
 | 
						|
                    return
 | 
						|
                }
 | 
						|
                
 | 
						|
                failure(error)
 | 
						|
            }
 | 
						|
            .retainUntilComplete()
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
// MARK: - Objective C Support
 | 
						|
 | 
						|
@objc(OWSSyncPushTokensJob)
 | 
						|
class OWSSyncPushTokensJob: NSObject {
 | 
						|
    @objc static func run() {
 | 
						|
        SyncPushTokensJob.run(uploadOnlyIfStale: false)
 | 
						|
    }
 | 
						|
}
 |