From 8a0e1a9a2c4aba7ce0330104753ef92573f60095 Mon Sep 17 00:00:00 2001 From: Sasha Weiss Date: Wed, 13 May 2026 16:14:19 -0700 Subject: [PATCH] Use Cron/scheduleFrequently for subscription redemption --- Signal/AppLaunch/AppEnvironment.swift | 71 ++++++++++++------- Signal/Backups/BackupEnablingManager.swift | 5 +- .../BackupTestFlightEntitlementManager.swift | 16 ++--- .../DonationSubscriptionManager.swift | 22 ------ 4 files changed, 55 insertions(+), 59 deletions(-) diff --git a/Signal/AppLaunch/AppEnvironment.swift b/Signal/AppLaunch/AppEnvironment.swift index f821117bf3..4a0e411a71 100644 --- a/Signal/AppLaunch/AppEnvironment.swift +++ b/Signal/AppLaunch/AppEnvironment.swift @@ -193,6 +193,50 @@ public class AppEnvironment: NSObject { operation: { try await identityKeyMismatchManager.validateLocalPniIdentityKeyIfNecessary() }, ) + let backupSubscriptionManager = DependenciesBridge.shared.backupSubscriptionManager + cron.scheduleFrequently( + mustBeRegistered: true, + mustBeConnected: true, + operation: { try await backupSubscriptionManager.redeemSubscriptionIfNecessary() }, + handleResult: { + switch $0 { + case .success, .failure(is CancellationError): + break + case .failure(let error): + Logger.warn("Terminally failed to redeem Backups subscription! \(error)") + } + }, + ) + + let backupTestFlightEntitlementManager = DependenciesBridge.shared.backupTestFlightEntitlementManager + cron.scheduleFrequently( + mustBeRegistered: true, + mustBeConnected: true, + operation: { try await backupTestFlightEntitlementManager.renewEntitlementIfNecessary() }, + handleResult: { + switch $0 { + case .success, .failure(is CancellationError): + break + case .failure(let error): + Logger.warn("Terminally failed to redeem Backups TestFlight subscription! \(error)") + } + }, + ) + + cron.scheduleFrequently( + mustBeRegistered: true, + mustBeConnected: true, + operation: { try await DonationSubscriptionManager.redeemSubscriptionIfNecessary() }, + handleResult: { + switch $0 { + case .success, .failure(is CancellationError): + break + case .failure(let error): + Logger.warn("Terminally failed to redeem Donations subscription! \(error)") + } + }, + ) + appReadiness.runNowOrWhenAppWillBecomeReady { self.badgeManager.startObservingChanges(in: DependenciesBridge.shared.databaseChangeObserver) self.appIconBadgeUpdater.startObserving() @@ -203,8 +247,6 @@ public class AppEnvironment: NSObject { let attachmentBackfillManager = DependenciesBridge.shared.attachmentBackfillManager let backupExportJobRunner = DependenciesBridge.shared.backupExportJobRunner let backupIdService = DependenciesBridge.shared.backupIdService - let backupSubscriptionManager = DependenciesBridge.shared.backupSubscriptionManager - let backupTestFlightEntitlementManager = DependenciesBridge.shared.backupTestFlightEntitlementManager let callRecordStore = DependenciesBridge.shared.callRecordStore let callRecordQuerier = DependenciesBridge.shared.callRecordQuerier let db = DependenciesBridge.shared.db @@ -305,31 +347,6 @@ public class AppEnvironment: NSObject { Task { await self.avatarHistoryManager.cleanupOrphanedImages() } - - Task { - do { - try await backupSubscriptionManager.redeemSubscriptionIfNecessary() - } catch { - owsFailDebug("Failed to redeem Backup subscription in launch job: \(error)") - } - } - - Task { - do { - try await backupTestFlightEntitlementManager.renewEntitlementIfNecessary() - } catch { - owsFailDebug("Failed to renew Backup entitlement for TestFlight in launch job: \(error)") - } - } - - Task { - await DonationSubscriptionManager.performMigrationToStorageServiceIfNecessary() - do { - try await DonationSubscriptionManager.redeemSubscriptionIfNecessary() - } catch { - owsFailDebug("Failed to redeem subscription in launch job: \(error)") - } - } } } } diff --git a/Signal/Backups/BackupEnablingManager.swift b/Signal/Backups/BackupEnablingManager.swift index c6793bf4d1..29365fa66e 100644 --- a/Signal/Backups/BackupEnablingManager.swift +++ b/Signal/Backups/BackupEnablingManager.swift @@ -263,7 +263,10 @@ final class BackupEnablingManager { private func enablePaidPlanWithoutStoreKit() async throws(SheetDisplayableError) { do { - try await backupTestFlightEntitlementManager.acquireEntitlement() + await db.awaitableWrite { tx in + backupTestFlightEntitlementManager.setRenewEntitlementIsNecessary(tx: tx) + } + try await backupTestFlightEntitlementManager.renewEntitlementIfNecessary() } catch where error.isNetworkFailureOrTimeout { throw .networkError } catch { diff --git a/SignalServiceKit/Subscriptions/Backups/BackupTestFlightEntitlementManager.swift b/SignalServiceKit/Subscriptions/Backups/BackupTestFlightEntitlementManager.swift index a4ae1f3459..2396c89f2e 100644 --- a/SignalServiceKit/Subscriptions/Backups/BackupTestFlightEntitlementManager.swift +++ b/SignalServiceKit/Subscriptions/Backups/BackupTestFlightEntitlementManager.swift @@ -9,8 +9,6 @@ import DeviceCheck /// Responsible for managing paid-tier Backup entitlements for TestFlight users, /// who aren't able to use StoreKit or perform real-money transactions. public protocol BackupTestFlightEntitlementManager { - func acquireEntitlement() async throws - func setRenewEntitlementIsNecessary(tx: DBWriteTransaction) func renewEntitlementIfNecessary() async throws } @@ -62,13 +60,7 @@ final class BackupTestFlightEntitlementManagerImpl: BackupTestFlightEntitlementM // MARK: - - func acquireEntitlement() async throws { - try await serialTaskQueue.run { - try await _acquireEntitlement() - } - } - - private func _acquireEntitlement() async throws { + private func acquireEntitlement() async throws { owsPrecondition(BuildFlags.Backups.avoidStoreKitForTesters) guard TSConstants.isUsingProductionService else { @@ -109,6 +101,12 @@ final class BackupTestFlightEntitlementManagerImpl: BackupTestFlightEntitlementM } func renewEntitlementIfNecessary() async throws { + try await serialTaskQueue.run { + try await _renewEntitlementIfNecessary() + } + } + + private func _renewEntitlementIfNecessary() async throws { let ( isRegisteredPrimaryDevice, isCurrentlyTesterBuild, diff --git a/SignalServiceKit/Subscriptions/Donations/DonationSubscriptionManager.swift b/SignalServiceKit/Subscriptions/Donations/DonationSubscriptionManager.swift index d981e3c9f4..4c7cd55cc5 100644 --- a/SignalServiceKit/Subscriptions/Donations/DonationSubscriptionManager.swift +++ b/SignalServiceKit/Subscriptions/Donations/DonationSubscriptionManager.swift @@ -64,27 +64,6 @@ public extension Notification.Name { /// similar things but designed around In-App Payments (StoreKit) and paid-tier /// Backups. public enum DonationSubscriptionManager { - public static func performMigrationToStorageServiceIfNecessary() async { - let hasMigratedToStorageService = SSKEnvironment.shared.databaseStorageRef.read { transaction in - subscriptionKVS.getBool(hasMigratedToStorageServiceKey, defaultValue: false, transaction: transaction) - } - - guard !hasMigratedToStorageService else { return } - - Logger.info("[Donations] Migrating to storage service") - - await SSKEnvironment.shared.databaseStorageRef.awaitableWrite { transaction in - subscriptionKVS.setBool(true, key: hasMigratedToStorageServiceKey, transaction: transaction) - - let localProfile = SSKEnvironment.shared.profileManagerRef.localUserProfile(tx: transaction) - let displayBadgesOnProfile = localProfile?.badges.count == localProfile?.visibleBadges.count - setDisplayBadgesOnProfile(displayBadgesOnProfile, transaction: transaction) - } - - SSKEnvironment.shared.storageServiceManagerRef.recordPendingLocalAccountUpdates() - } - - // MARK: - private static var receiptCredentialRedemptionJobQueue: DonationReceiptCredentialRedemptionJobQueue { SSKEnvironment.shared.donationReceiptCredentialRedemptionJobQueue @@ -108,7 +87,6 @@ public enum DonationSubscriptionManager { fileprivate static let mostRecentlyExpiredGiftBadgeIDKey = "mostRecentlyExpiredGiftBadgeIDKey" fileprivate static let showExpirySheetOnHomeScreenKey = "showExpirySheetOnHomeScreenKey" fileprivate static let mostRecentSubscriptionPaymentMethodKey = "mostRecentSubscriptionPaymentMethod" - fileprivate static let hasMigratedToStorageServiceKey = "hasMigratedToStorageServiceKey" // MARK: -