Migrate to BGAppRefreshTask
This commit is contained in:
parent
7acc370f67
commit
5df23bdaed
@ -937,6 +937,7 @@
|
||||
66C2B1562A1400E8008DDE72 /* SVR2WebsocketConfigurator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66C2B1552A1400E8008DDE72 /* SVR2WebsocketConfigurator.swift */; };
|
||||
66C336D12A994B97000F4F50 /* FailedStorySendDisplayController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66C336D02A994B97000F4F50 /* FailedStorySendDisplayController.swift */; };
|
||||
66C3887E29CA537400E6DC00 /* RegistrationTransferProgressViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66C3887D29CA537400E6DC00 /* RegistrationTransferProgressViewController.swift */; };
|
||||
66CDB7522AF9D117009A36EC /* MessageFetchBGRefreshTask.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66CDB7512AF9D117009A36EC /* MessageFetchBGRefreshTask.swift */; };
|
||||
66CDE5C42A58928000B7C607 /* SpoilerableTextViewAnimator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66CDE5C32A58928000B7C607 /* SpoilerableTextViewAnimator.swift */; };
|
||||
66CE755F28C332AF00D5FA79 /* PaymentOnboarding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66CE755E28C332AF00D5FA79 /* PaymentOnboarding.swift */; };
|
||||
66D13F0A2A731E590092D47B /* RecipientHidingManager+SignalServiceAddress.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66D13F092A731E590092D47B /* RecipientHidingManager+SignalServiceAddress.swift */; };
|
||||
@ -3535,6 +3536,7 @@
|
||||
66C2B1552A1400E8008DDE72 /* SVR2WebsocketConfigurator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SVR2WebsocketConfigurator.swift; sourceTree = "<group>"; };
|
||||
66C336D02A994B97000F4F50 /* FailedStorySendDisplayController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FailedStorySendDisplayController.swift; sourceTree = "<group>"; };
|
||||
66C3887D29CA537400E6DC00 /* RegistrationTransferProgressViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RegistrationTransferProgressViewController.swift; sourceTree = "<group>"; };
|
||||
66CDB7512AF9D117009A36EC /* MessageFetchBGRefreshTask.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageFetchBGRefreshTask.swift; sourceTree = "<group>"; };
|
||||
66CDE5C32A58928000B7C607 /* SpoilerableTextViewAnimator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpoilerableTextViewAnimator.swift; sourceTree = "<group>"; };
|
||||
66CE755E28C332AF00D5FA79 /* PaymentOnboarding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaymentOnboarding.swift; sourceTree = "<group>"; };
|
||||
66D13F092A731E590092D47B /* RecipientHidingManager+SignalServiceAddress.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RecipientHidingManager+SignalServiceAddress.swift"; sourceTree = "<group>"; };
|
||||
@ -7232,6 +7234,7 @@
|
||||
76EB03C218170B33006006FC /* AppDelegate.h */,
|
||||
76EB03C318170B33006006FC /* AppDelegate.m */,
|
||||
F92074752888648A00B7F087 /* AppDelegate.swift */,
|
||||
66CDB7512AF9D117009A36EC /* MessageFetchBGRefreshTask.swift */,
|
||||
45B201741DAECBFD00C461E0 /* Signal-Bridging-Header.h */,
|
||||
);
|
||||
path = src;
|
||||
@ -12116,6 +12119,7 @@
|
||||
4CB5F26920F7D060004D1B42 /* MessageActions.swift in Sources */,
|
||||
4CB5F26720F6E1E2004D1B42 /* MessageActionsToolbar.swift in Sources */,
|
||||
45F32C242057297A00A300D5 /* MessageDetailViewController.swift in Sources */,
|
||||
66CDB7522AF9D117009A36EC /* MessageFetchBGRefreshTask.swift in Sources */,
|
||||
34DE9C02256575300080E4AF /* MessageLoader.swift in Sources */,
|
||||
881218F0238CA51600E6F271 /* MessageReactionPicker.swift in Sources */,
|
||||
88D1D40222EBB5A100F472C5 /* MessageRequestView.swift in Sources */,
|
||||
|
||||
@ -2,6 +2,10 @@
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>BGTaskSchedulerPermittedIdentifiers</key>
|
||||
<array>
|
||||
<string>MessageFetchBGRefreshTask</string>
|
||||
</array>
|
||||
<key>BuildDetails</key>
|
||||
<dict/>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
|
||||
@ -448,25 +448,4 @@ static void uncaughtExceptionHandler(NSException *exception)
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)application:(UIApplication *)application
|
||||
performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult result))completionHandler
|
||||
{
|
||||
OWSLogInfo(@"performing background fetch");
|
||||
AppReadinessRunNowOrWhenAppDidBecomeReadySync(^{
|
||||
[self.messageFetcherJob runObjc].done(^(id value) {
|
||||
// HACK: Call completion handler after n seconds.
|
||||
//
|
||||
// We don't currently have a convenient API to know when message fetching is *done* when
|
||||
// working with the websocket.
|
||||
//
|
||||
// We *could* substantially rewrite the SocketManager to take advantage of the `empty` message
|
||||
// But once our REST endpoint is fixed to properly de-enqueue fallback notifications, we can easily
|
||||
// use the rest endpoint here rather than the websocket and circumvent making changes to critical code.
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
||||
completionHandler(UIBackgroundFetchResultNewData);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@ -77,6 +77,8 @@ extension AppDelegate {
|
||||
|
||||
Cryptography.seedRandom()
|
||||
|
||||
MessageFetchBGRefreshTask.register()
|
||||
|
||||
// This *must* happen before we try and access or verify the database,
|
||||
// since we may be in a state where the database has been partially
|
||||
// restored from transfer (e.g. the key was replaced, but the database
|
||||
@ -377,7 +379,7 @@ extension AppDelegate {
|
||||
DebugLogger.shared().postLaunchLogCleanup(appContext: appContext)
|
||||
AppVersionImpl.shared.mainAppLaunchDidComplete()
|
||||
|
||||
enableBackgroundRefreshIfNecessary()
|
||||
scheduleBgAppRefresh()
|
||||
Self.updateApplicationShortcutItems(isRegistered: tsRegistrationState.isRegistered)
|
||||
|
||||
let notificationCenter = NotificationCenter.default
|
||||
@ -412,18 +414,8 @@ extension AppDelegate {
|
||||
SignalApp.shared.showLaunchInterface(launchInterface, launchStartedAt: launchStartedAt)
|
||||
}
|
||||
|
||||
private func enableBackgroundRefreshIfNecessary() {
|
||||
let interval: TimeInterval
|
||||
if
|
||||
OWS2FAManager.shared.isRegistrationLockEnabled,
|
||||
DependenciesBridge.shared.tsAccountManager.registrationStateWithMaybeSneakyTransaction.isRegistered
|
||||
{
|
||||
// Ping server once a day to keep-alive reglock clients.
|
||||
interval = 24 * 60 * 60
|
||||
} else {
|
||||
interval = UIApplication.backgroundFetchIntervalNever
|
||||
}
|
||||
UIApplication.shared.setMinimumBackgroundFetchInterval(interval)
|
||||
private func scheduleBgAppRefresh() {
|
||||
MessageFetchBGRefreshTask.shared?.scheduleTask()
|
||||
}
|
||||
|
||||
/// The user must unlock the device once after reboot before the database encryption key can be accessed.
|
||||
@ -861,7 +853,7 @@ extension AppDelegate {
|
||||
|
||||
Logger.info("registrationStateDidChange")
|
||||
|
||||
enableBackgroundRefreshIfNecessary()
|
||||
scheduleBgAppRefresh()
|
||||
|
||||
let tsAccountManager = DependenciesBridge.shared.tsAccountManager
|
||||
let isRegistered = tsAccountManager.registrationStateWithMaybeSneakyTransaction.isRegistered
|
||||
@ -885,7 +877,7 @@ extension AppDelegate {
|
||||
|
||||
@objc
|
||||
private func registrationLockDidChange() {
|
||||
enableBackgroundRefreshIfNecessary()
|
||||
scheduleBgAppRefresh()
|
||||
}
|
||||
|
||||
// MARK: - Utilities
|
||||
|
||||
138
Signal/src/MessageFetchBGRefreshTask.swift
Normal file
138
Signal/src/MessageFetchBGRefreshTask.swift
Normal file
@ -0,0 +1,138 @@
|
||||
//
|
||||
// Copyright 2023 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
//
|
||||
|
||||
import BackgroundTasks
|
||||
import SignalServiceKit
|
||||
|
||||
/**
|
||||
* Utility class for managing the BGAppRefreshTask we use as a "keepalive" for
|
||||
* registration lock.
|
||||
*
|
||||
* Ensures that while reglock is active, we try to fetch messages every once in a while
|
||||
* even if the app or NSE don't launch, so that the server keeps the account active
|
||||
* and reglock alive.
|
||||
*/
|
||||
public class MessageFetchBGRefreshTask {
|
||||
|
||||
private static var _shared: MessageFetchBGRefreshTask = {
|
||||
return MessageFetchBGRefreshTask(
|
||||
dateProvider: { Date() },
|
||||
messageFetcherJob: NSObject.messageFetcherJob,
|
||||
ows2FAManager: NSObject.ows2FAManager,
|
||||
tsAccountManager: DependenciesBridge.shared.tsAccountManager
|
||||
)
|
||||
}()
|
||||
|
||||
public static var shared: MessageFetchBGRefreshTask? {
|
||||
guard AppReadiness.isAppReady else {
|
||||
return nil
|
||||
}
|
||||
return _shared
|
||||
}
|
||||
|
||||
// Must be kept in sync with the value in info.plist.
|
||||
private static let taskIdentifier = "MessageFetchBGRefreshTask"
|
||||
|
||||
private let dateProvider: DateProvider
|
||||
private let messageFetcherJob: MessageFetcherJob
|
||||
private let ows2FAManager: OWS2FAManager
|
||||
private let tsAccountManager: TSAccountManager
|
||||
|
||||
private init(
|
||||
dateProvider: @escaping DateProvider,
|
||||
messageFetcherJob: MessageFetcherJob,
|
||||
ows2FAManager: OWS2FAManager,
|
||||
tsAccountManager: TSAccountManager
|
||||
) {
|
||||
self.dateProvider = dateProvider
|
||||
self.messageFetcherJob = messageFetcherJob
|
||||
self.ows2FAManager = ows2FAManager
|
||||
self.tsAccountManager = tsAccountManager
|
||||
}
|
||||
|
||||
public static func register() {
|
||||
BGTaskScheduler.shared.register(
|
||||
forTaskWithIdentifier: Self.taskIdentifier,
|
||||
using: nil,
|
||||
launchHandler: { task in
|
||||
AppReadiness.runNowOrWhenAppDidBecomeReadyAsync {
|
||||
Self.shared?.performTask(task)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
public func scheduleTask() {
|
||||
// Note: this file only exists in the main app (Signal/src) so we
|
||||
// don't check for that. But if this ever moves, it should check
|
||||
// appContext.isMainApp.
|
||||
|
||||
guard ows2FAManager.isRegistrationLockEnabled else {
|
||||
// No need to do the keepalive for reglock.
|
||||
return
|
||||
}
|
||||
|
||||
guard tsAccountManager.registrationStateWithMaybeSneakyTransaction.isRegistered else {
|
||||
return
|
||||
}
|
||||
|
||||
// Ping server once a day to keep-alive reglock clients.
|
||||
// Ideally, we would schedule this for 24 hours _since we last talked to the chat server_.
|
||||
// Without knowing that, we risk scheduling this 24 hours out over and over every time you
|
||||
// launch the app without internet. That scenario is unlikely, so is left unhandled.
|
||||
let interval: TimeInterval = 24 * 60 * 60
|
||||
let request = BGAppRefreshTaskRequest(identifier: Self.taskIdentifier)
|
||||
request.earliestBeginDate = dateProvider().addingTimeInterval(interval)
|
||||
|
||||
do {
|
||||
try BGTaskScheduler.shared.submit(request)
|
||||
} catch let error {
|
||||
let errorCode = (error as NSError).code
|
||||
switch errorCode {
|
||||
case BGTaskScheduler.Error.Code.notPermitted.rawValue:
|
||||
Logger.warn("Skipping bg task; user permission required.")
|
||||
case BGTaskScheduler.Error.Code.tooManyPendingTaskRequests.rawValue:
|
||||
// If we reschedule the same identifier, we don't get this error.
|
||||
// This means a task with a different identifier was scheduled (not allowed).
|
||||
Logger.error("Too many pending bg tasks; only one app refresh task identifier is allowed at any time.")
|
||||
case BGTaskScheduler.Error.Code.unavailable.rawValue:
|
||||
Logger.error("Trying to schedule bg task from an extension?")
|
||||
default:
|
||||
Logger.error("Unknown error code scheduling bg task: \(errorCode)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func performTask(_ task: BGTask) {
|
||||
Logger.info("performing background fetch")
|
||||
AppReadiness.runNowOrWhenUIDidBecomeReadySync {
|
||||
self.messageFetcherJob.run().promise
|
||||
.timeout(seconds: 10)
|
||||
.then {
|
||||
// HACK: Call completion handler after 5 seconds.
|
||||
//
|
||||
// We don't currently have a convenient API to know when message fetching is *done* when
|
||||
// working with the websocket.
|
||||
//
|
||||
// We *could* substantially rewrite the SocketManager to take advantage of the `empty` message
|
||||
// But once our REST endpoint is fixed to properly de-enqueue fallback notifications, we can easily
|
||||
// use the rest endpoint here rather than the websocket and circumvent making changes to critical code.
|
||||
return Guarantee.after(seconds: 5)
|
||||
}
|
||||
.observe { result in
|
||||
switch result {
|
||||
case .success:
|
||||
Logger.info("success")
|
||||
task.setTaskCompleted(success: true)
|
||||
case .failure:
|
||||
Logger.error("Failing task; failed to fetch messages")
|
||||
task.setTaskCompleted(success: false)
|
||||
}
|
||||
// Schedule the next run now.
|
||||
self.scheduleTask()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user