diff --git a/BDKSwiftExampleWallet/App/BDKSwiftExampleWalletApp.swift b/BDKSwiftExampleWallet/App/BDKSwiftExampleWalletApp.swift index 7d7520ac..ed32229c 100644 --- a/BDKSwiftExampleWallet/App/BDKSwiftExampleWalletApp.swift +++ b/BDKSwiftExampleWallet/App/BDKSwiftExampleWalletApp.swift @@ -6,6 +6,7 @@ // import BitcoinDevKit +import BackgroundTasks import SwiftUI @main @@ -13,6 +14,12 @@ struct BDKSwiftExampleWalletApp: App { @AppStorage("isOnboarding") var isOnboarding: Bool = true @State private var navigationPath = NavigationPath() @State private var refreshTrigger = UUID() + @Environment(\.scenePhase) private var scenePhase + + init() { + BackgroundCBFSyncTask.register() + BackgroundCBFSyncTask.schedule() + } var body: some Scene { WindowGroup { @@ -30,6 +37,11 @@ struct BDKSwiftExampleWalletApp: App { BDKClient.live.setNeedsFullScan(true) navigationPath = NavigationPath() } + .onChange(of: scenePhase) { _, newValue in + if newValue == .background { + BackgroundCBFSyncTask.schedule() + } + } } } } @@ -42,3 +54,69 @@ extension BDKSwiftExampleWalletApp { return (try? KeyClient.live.getBackupInfo()) != nil } } + +private enum BackgroundCBFSyncTask { + static let identifier = "com.bitcoindevkit.bdkswiftexamplewallet.cbf-sync" + private static let minimumInterval: TimeInterval = 60 * 60 * 24 * 7 + + static func register() { + BGTaskScheduler.shared.register( + forTaskWithIdentifier: identifier, + using: nil + ) { task in + guard let processingTask = task as? BGProcessingTask else { + task.setTaskCompleted(success: false) + return + } + handle(processingTask) + } + } + + static func schedule() { + let request = BGProcessingTaskRequest(identifier: identifier) + request.requiresNetworkConnectivity = true + request.requiresExternalPower = true + request.earliestBeginDate = Date(timeIntervalSinceNow: minimumInterval) + + do { + try BGTaskScheduler.shared.submit(request) + } catch { + print("[BackgroundCBF] Failed to schedule task: \(error)") + } + } + + private static func handle(_ task: BGProcessingTask) { + schedule() + + let syncTask = Task.detached(priority: .background) { + do { + try await runKyotoSync() + task.setTaskCompleted(success: true) + } catch { + print("[BackgroundCBF] Background sync failed: \(error)") + task.setTaskCompleted(success: false) + } + } + + task.expirationHandler = { + syncTask.cancel() + } + } + + private static func runKyotoSync() async throws { + let bdkClient = BDKClient.live + + guard (try? bdkClient.getBackupInfo()) != nil else { + return + } + + try bdkClient.loadWallet() + + guard bdkClient.getClientType() == .kyoto else { + return + } + + let inspector = WalletSyncScriptInspector(updateProgress: { _, _ in }) + try await bdkClient.syncWithInspector(inspector) + } +} diff --git a/BDKSwiftExampleWallet/Info.plist b/BDKSwiftExampleWallet/Info.plist index 990bd2ef..bd7bfd67 100644 --- a/BDKSwiftExampleWallet/Info.plist +++ b/BDKSwiftExampleWallet/Info.plist @@ -2,9 +2,17 @@ + BGTaskSchedulerPermittedIdentifiers + + com.bitcoindevkit.bdkswiftexamplewallet.cbf-sync + ITSAppUsesNonExemptEncryption NSPasteboardUsageDescription "To allow users to copy and paste text between the app and other apps" + UIBackgroundModes + + processing + diff --git a/BDKSwiftExampleWallet/Service/Key Service/KeyService.swift b/BDKSwiftExampleWallet/Service/Key Service/KeyService.swift index 4ba5b546..02b0b03b 100644 --- a/BDKSwiftExampleWallet/Service/Key Service/KeyService.swift +++ b/BDKSwiftExampleWallet/Service/Key Service/KeyService.swift @@ -16,7 +16,7 @@ private struct KeyService { let keychain = Keychain(service: "com.matthewramsden.bdkswiftexamplewallet.testservice") .label(Bundle.main.displayName) .synchronizable(false) - .accessibility(.whenUnlocked) + .accessibility(.afterFirstUnlock) self.keychain = keychain }