Fixed an issue where the users push token might never get unregistered
This commit is contained in:
parent
f976d85c27
commit
3151aa8901
|
@ -6587,7 +6587,7 @@
|
|||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 414;
|
||||
CURRENT_PROJECT_VERSION = 415;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
DEVELOPMENT_TEAM = SUQ8J2PCT7;
|
||||
FRAMEWORK_SEARCH_PATHS = "$(inherited)";
|
||||
|
@ -6659,7 +6659,7 @@
|
|||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 414;
|
||||
CURRENT_PROJECT_VERSION = 415;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
DEVELOPMENT_TEAM = SUQ8J2PCT7;
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
|
@ -6724,7 +6724,7 @@
|
|||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 414;
|
||||
CURRENT_PROJECT_VERSION = 415;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
DEVELOPMENT_TEAM = SUQ8J2PCT7;
|
||||
FRAMEWORK_SEARCH_PATHS = "$(inherited)";
|
||||
|
@ -6798,7 +6798,7 @@
|
|||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 414;
|
||||
CURRENT_PROJECT_VERSION = 415;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
DEVELOPMENT_TEAM = SUQ8J2PCT7;
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
|
@ -7706,7 +7706,7 @@
|
|||
CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements;
|
||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
CURRENT_PROJECT_VERSION = 414;
|
||||
CURRENT_PROJECT_VERSION = 415;
|
||||
DEVELOPMENT_TEAM = SUQ8J2PCT7;
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
|
@ -7777,7 +7777,7 @@
|
|||
CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements;
|
||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
CURRENT_PROJECT_VERSION = 414;
|
||||
CURRENT_PROJECT_VERSION = 415;
|
||||
DEVELOPMENT_TEAM = SUQ8J2PCT7;
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
|
|
|
@ -42,75 +42,97 @@ public enum SyncPushTokensJob: JobExecutor {
|
|||
}
|
||||
}()
|
||||
|
||||
// Push tokens don't normally change while the app is launched, so you would assume checking once
|
||||
// during launch is 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.
|
||||
//
|
||||
// It's also possible for a device to successfully register for push notifications but fail to
|
||||
// register with Session
|
||||
//
|
||||
// Due to the above we want to re-register at least once every ~12 hours to ensure users will
|
||||
// continue to receive push notifications
|
||||
//
|
||||
// In addition to this if we are custom running the job (eg. by toggling the push notification
|
||||
// setting) then we should run regardless of the other settings so users have a mechanism to force
|
||||
// the registration to run
|
||||
let lastPushNotificationSync: Date = UserDefaults.standard[.lastPushNotificationSync]
|
||||
.defaulting(to: Date.distantPast)
|
||||
|
||||
guard
|
||||
job.behaviour == .runOnce ||
|
||||
!isRegisteredForRemoteNotifications ||
|
||||
Date().timeIntervalSince(lastPushNotificationSync) >= SyncPushTokensJob.maxFrequency
|
||||
else {
|
||||
SNLog("[SyncPushTokensJob] Deferred due to Fast Mode disabled or recent-enough registration")
|
||||
// Apple's documentation states that we should re-register for notifications on every launch:
|
||||
// https://developer.apple.com/library/archive/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/HandlingRemoteNotifications.html#//apple_ref/doc/uid/TP40008194-CH6-SW1
|
||||
guard job.behaviour == .runOnce || !isRegisteredForRemoteNotifications else {
|
||||
SNLog("[SyncPushTokensJob] Deferred due to Fast Mode disabled")
|
||||
deferred(job) // Don't need to do anything if push notifications are already registered
|
||||
return
|
||||
}
|
||||
|
||||
Logger.info("Re-registering for remote notifications.")
|
||||
// Determine if the device has 'Fast Mode' (APNS) enabled
|
||||
let isUsingFullAPNs: Bool = UserDefaults.standard[.isUsingFullAPNs]
|
||||
|
||||
// Perform device registration
|
||||
PushRegistrationManager.shared.requestPushTokens()
|
||||
.subscribe(on: queue)
|
||||
.flatMap { (pushToken: String, voipToken: String) -> AnyPublisher<Void, Error> in
|
||||
Deferred {
|
||||
Future<Void, Error> { resolver in
|
||||
SyncPushTokensJob.registerForPushNotifications(
|
||||
pushToken: pushToken,
|
||||
voipToken: voipToken,
|
||||
isForcedUpdate: true,
|
||||
success: { resolver(Result.success(())) },
|
||||
failure: { resolver(Result.failure($0)) }
|
||||
)
|
||||
// If the job is running and 'Fast Mode' is disabled then we should try to unregister the existing
|
||||
// token
|
||||
guard isUsingFullAPNs else {
|
||||
Just(Storage.shared[.lastRecordedPushToken])
|
||||
.setFailureType(to: Error.self)
|
||||
.flatMap { lastRecordedPushToken in
|
||||
if let existingToken: String = lastRecordedPushToken {
|
||||
SNLog("[SyncPushTokensJob] Unregister using last recorded push token: \(redact(existingToken))")
|
||||
return Just(existingToken)
|
||||
.setFailureType(to: Error.self)
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
SNLog("[SyncPushTokensJob] Unregister using live token provided from device")
|
||||
return PushRegistrationManager.shared.requestPushTokens()
|
||||
.map { token, _ in token }
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
.handleEvents(
|
||||
.flatMap { pushToken in PushNotificationAPI.unregister(Data(hex: pushToken)) }
|
||||
.map {
|
||||
// Tell the device to unregister for remote notifications (essentially try to invalidate
|
||||
// the token if needed
|
||||
DispatchQueue.main.sync { UIApplication.shared.unregisterForRemoteNotifications() }
|
||||
|
||||
Storage.shared.write { db in
|
||||
db[.lastRecordedPushToken] = nil
|
||||
}
|
||||
return ()
|
||||
}
|
||||
.subscribe(on: queue)
|
||||
.sinkUntilComplete(
|
||||
receiveCompletion: { result in
|
||||
switch result {
|
||||
case .failure(let error):
|
||||
SNLog("[SyncPushTokensJob] Failed to register due to error: \(error)")
|
||||
|
||||
case .finished:
|
||||
Logger.warn("Recording push tokens locally. pushToken: \(redact(pushToken)), voipToken: \(redact(voipToken))")
|
||||
SNLog("[SyncPushTokensJob] Completed")
|
||||
UserDefaults.standard[.lastPushNotificationSync] = Date()
|
||||
|
||||
Storage.shared.write { db in
|
||||
db[.lastRecordedPushToken] = pushToken
|
||||
db[.lastRecordedVoipToken] = voipToken
|
||||
}
|
||||
case .finished: SNLog("[SyncPushTokensJob] Unregister Completed")
|
||||
case .failure: SNLog("[SyncPushTokensJob] Unregister Failed")
|
||||
}
|
||||
|
||||
// We want to complete this job regardless of success or failure
|
||||
success(job, false)
|
||||
}
|
||||
)
|
||||
.eraseToAnyPublisher()
|
||||
return
|
||||
}
|
||||
|
||||
// Perform device registration
|
||||
Logger.info("Re-registering for remote notifications.")
|
||||
PushRegistrationManager.shared.requestPushTokens()
|
||||
.flatMap { (pushToken: String, voipToken: String) -> AnyPublisher<Void, Error> in
|
||||
PushNotificationAPI
|
||||
.register(
|
||||
with: Data(hex: pushToken),
|
||||
publicKey: getUserHexEncodedPublicKey(),
|
||||
isForcedUpdate: true
|
||||
)
|
||||
.retry(3)
|
||||
.handleEvents(
|
||||
receiveCompletion: { result in
|
||||
switch result {
|
||||
case .failure(let error):
|
||||
SNLog("[SyncPushTokensJob] Failed to register due to error: \(error)")
|
||||
|
||||
case .finished:
|
||||
Logger.warn("Recording push tokens locally. pushToken: \(redact(pushToken)), voipToken: \(redact(voipToken))")
|
||||
SNLog("[SyncPushTokensJob] Completed")
|
||||
UserDefaults.standard[.lastPushNotificationSync] = Date()
|
||||
|
||||
Storage.shared.write { db in
|
||||
db[.lastRecordedPushToken] = pushToken
|
||||
db[.lastRecordedVoipToken] = voipToken
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
.map { _ in () }
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
.subscribe(on: queue)
|
||||
.sinkUntilComplete(
|
||||
// We want to complete this job regardless of success or failure
|
||||
receiveCompletion: { _ in success(job, false) },
|
||||
receiveValue: { _ in }
|
||||
receiveCompletion: { _ in success(job, false) }
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -147,68 +169,3 @@ extension SyncPushTokensJob {
|
|||
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]
|
||||
|
||||
Just(Data(hex: pushToken))
|
||||
.setFailureType(to: Error.self)
|
||||
.flatMap { pushTokenAsData -> AnyPublisher<Bool, Error> in
|
||||
guard isUsingFullAPNs else {
|
||||
return PushNotificationAPI.unregister(pushTokenAsData)
|
||||
.map { _ in true }
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
return PushNotificationAPI
|
||||
.register(
|
||||
with: pushTokenAsData,
|
||||
publicKey: getUserHexEncodedPublicKey(),
|
||||
isForcedUpdate: isForcedUpdate
|
||||
)
|
||||
.map { _ in true }
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
.catch { error -> AnyPublisher<Bool, Error> in
|
||||
guard remainingRetries == 0 else {
|
||||
SyncPushTokensJob.registerForPushNotifications(
|
||||
pushToken: pushToken,
|
||||
voipToken: voipToken,
|
||||
isForcedUpdate: isForcedUpdate,
|
||||
success: success,
|
||||
failure: failure,
|
||||
remainingRetries: (remainingRetries - 1)
|
||||
)
|
||||
|
||||
return Just(false)
|
||||
.setFailureType(to: Error.self)
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
return Fail(error: error)
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
.subscribe(on: DispatchQueue.global(qos: .userInitiated))
|
||||
.sinkUntilComplete(
|
||||
receiveCompletion: { result in
|
||||
switch result {
|
||||
case .finished: break
|
||||
case .failure(let error): failure(error)
|
||||
}
|
||||
},
|
||||
receiveValue: { didComplete in
|
||||
guard didComplete else { return }
|
||||
|
||||
success()
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -237,7 +237,8 @@ public final class NotificationServiceExtension: UNNotificationServiceExtension
|
|||
},
|
||||
migrationsCompletion: { [weak self] result, needsConfigSync in
|
||||
switch result {
|
||||
case .failure: SNLog("[NotificationServiceExtension] Failed to complete migrations")
|
||||
// Only 'NSLog' works in the extension - viewable via Console.app
|
||||
case .failure: NSLog("[NotificationServiceExtension] Failed to complete migrations")
|
||||
case .success:
|
||||
DispatchQueue.main.async {
|
||||
self?.versionMigrationsDidComplete(needsConfigSync: needsConfigSync)
|
||||
|
|
Loading…
Reference in New Issue