Fixed an issue where the users push token might never get unregistered

pull/751/head
Morgan Pretty 2 years ago
parent f976d85c27
commit 3151aa8901

@ -6587,7 +6587,7 @@
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
COPY_PHASE_STRIP = NO; COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 414; CURRENT_PROJECT_VERSION = 415;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = SUQ8J2PCT7; DEVELOPMENT_TEAM = SUQ8J2PCT7;
FRAMEWORK_SEARCH_PATHS = "$(inherited)"; FRAMEWORK_SEARCH_PATHS = "$(inherited)";
@ -6659,7 +6659,7 @@
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
COPY_PHASE_STRIP = NO; COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 414; CURRENT_PROJECT_VERSION = 415;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = SUQ8J2PCT7; DEVELOPMENT_TEAM = SUQ8J2PCT7;
ENABLE_NS_ASSERTIONS = NO; ENABLE_NS_ASSERTIONS = NO;
@ -6724,7 +6724,7 @@
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
COPY_PHASE_STRIP = NO; COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 414; CURRENT_PROJECT_VERSION = 415;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = SUQ8J2PCT7; DEVELOPMENT_TEAM = SUQ8J2PCT7;
FRAMEWORK_SEARCH_PATHS = "$(inherited)"; FRAMEWORK_SEARCH_PATHS = "$(inherited)";
@ -6798,7 +6798,7 @@
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
COPY_PHASE_STRIP = NO; COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 414; CURRENT_PROJECT_VERSION = 415;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = SUQ8J2PCT7; DEVELOPMENT_TEAM = SUQ8J2PCT7;
ENABLE_NS_ASSERTIONS = NO; ENABLE_NS_ASSERTIONS = NO;
@ -7706,7 +7706,7 @@
CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements; CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements;
CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_IDENTITY = "iPhone Developer";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CURRENT_PROJECT_VERSION = 414; CURRENT_PROJECT_VERSION = 415;
DEVELOPMENT_TEAM = SUQ8J2PCT7; DEVELOPMENT_TEAM = SUQ8J2PCT7;
FRAMEWORK_SEARCH_PATHS = ( FRAMEWORK_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
@ -7777,7 +7777,7 @@
CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements; CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements;
CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_IDENTITY = "iPhone Developer";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CURRENT_PROJECT_VERSION = 414; CURRENT_PROJECT_VERSION = 415;
DEVELOPMENT_TEAM = SUQ8J2PCT7; DEVELOPMENT_TEAM = SUQ8J2PCT7;
FRAMEWORK_SEARCH_PATHS = ( FRAMEWORK_SEARCH_PATHS = (
"$(inherited)", "$(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 // Apple's documentation states that we should re-register for notifications on every launch:
// during launch is sufficient, but e.g. on iOS11, users who have disabled "Allow Notifications" // https://developer.apple.com/library/archive/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/HandlingRemoteNotifications.html#//apple_ref/doc/uid/TP40008194-CH6-SW1
// and disabled "Background App Refresh" will not be able to obtain an APN token. Enabling those guard job.behaviour == .runOnce || !isRegisteredForRemoteNotifications else {
// settings does not restart the app, so we check every activation for users who haven't yet SNLog("[SyncPushTokensJob] Deferred due to Fast Mode disabled")
// 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")
deferred(job) // Don't need to do anything if push notifications are already registered deferred(job) // Don't need to do anything if push notifications are already registered
return 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 // If the job is running and 'Fast Mode' is disabled then we should try to unregister the existing
PushRegistrationManager.shared.requestPushTokens() // token
.subscribe(on: queue) guard isUsingFullAPNs else {
.flatMap { (pushToken: String, voipToken: String) -> AnyPublisher<Void, Error> in Just(Storage.shared[.lastRecordedPushToken])
Deferred { .setFailureType(to: Error.self)
Future<Void, Error> { resolver in .flatMap { lastRecordedPushToken in
SyncPushTokensJob.registerForPushNotifications( if let existingToken: String = lastRecordedPushToken {
pushToken: pushToken, SNLog("[SyncPushTokensJob] Unregister using last recorded push token: \(redact(existingToken))")
voipToken: voipToken, return Just(existingToken)
isForcedUpdate: true, .setFailureType(to: Error.self)
success: { resolver(Result.success(())) }, .eraseToAnyPublisher()
failure: { resolver(Result.failure($0)) }
)
} }
SNLog("[SyncPushTokensJob] Unregister using live token provided from device")
return PushRegistrationManager.shared.requestPushTokens()
.map { token, _ in token }
.eraseToAnyPublisher()
}
.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 ()
} }
.handleEvents( .subscribe(on: queue)
.sinkUntilComplete(
receiveCompletion: { result in receiveCompletion: { result in
switch result { switch result {
case .failure(let error): case .finished: SNLog("[SyncPushTokensJob] Unregister Completed")
SNLog("[SyncPushTokensJob] Failed to register due to error: \(error)") case .failure: SNLog("[SyncPushTokensJob] Unregister Failed")
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
}
} }
// 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( .sinkUntilComplete(
// We want to complete this job regardless of success or failure // We want to complete this job regardless of success or failure
receiveCompletion: { _ in success(job, false) }, receiveCompletion: { _ in success(job, false) }
receiveValue: { _ in }
) )
} }
@ -147,68 +169,3 @@ extension SyncPushTokensJob {
private func redact(_ string: String) -> String { private func redact(_ string: String) -> String {
return OWSIsDebugBuild() ? string : "[ READACTED \(string.prefix(2))...\(string.suffix(2)) ]" 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 migrationsCompletion: { [weak self] result, needsConfigSync in
switch result { 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: case .success:
DispatchQueue.main.async { DispatchQueue.main.async {
self?.versionMigrationsDidComplete(needsConfigSync: needsConfigSync) self?.versionMigrationsDidComplete(needsConfigSync: needsConfigSync)

Loading…
Cancel
Save