Add smart device token caching for push notification registration

Cache device token in UserDefaults and only register with backend when
token changes. Also registers when app returns from background if token
differs from cached value, reducing unnecessary API calls.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Trey t
2025-12-04 20:18:08 -06:00
parent 22bf109cf7
commit 70d46da14a
3 changed files with 62 additions and 1 deletions

View File

@@ -9,6 +9,14 @@ class PushNotificationManager: NSObject, ObservableObject {
@Published var deviceToken: String?
@Published var notificationPermissionGranted = false
private let registeredTokenKey = "com.casera.registeredDeviceToken"
/// The last token that was successfully registered with the backend
private var lastRegisteredToken: String? {
get { UserDefaults.standard.string(forKey: registeredTokenKey) }
set { UserDefaults.standard.set(newValue, forKey: registeredTokenKey) }
}
override init() {
super.init()
}
@@ -58,12 +66,48 @@ class PushNotificationManager: NSObject, ObservableObject {
// MARK: - Backend Registration
private func registerDeviceWithBackend(token: String) async {
/// Call this after login to register any pending device token
func registerDeviceAfterLogin() {
guard let token = deviceToken else {
print("⚠️ No device token available for registration")
return
}
Task {
await registerDeviceWithBackend(token: token, force: false)
}
}
/// Call this when app returns from background to check and register if needed
func checkAndRegisterDeviceIfNeeded() {
guard let token = deviceToken else {
print("⚠️ No device token available for registration check")
return
}
// Skip if token hasn't changed
if token == lastRegisteredToken {
print("📱 Device token unchanged, skipping registration")
return
}
Task {
await registerDeviceWithBackend(token: token, force: false)
}
}
private func registerDeviceWithBackend(token: String, force: Bool = false) async {
guard TokenStorage.shared.getToken() != nil else {
print("⚠️ No auth token available, will register device after login")
return
}
// Skip if token hasn't changed (unless forced)
if !force && token == lastRegisteredToken {
print("📱 Device token unchanged, skipping registration")
return
}
// Get unique device identifier
let deviceId = await MainActor.run {
UIDevice.current.identifierForVendor?.uuidString ?? UUID().uuidString
@@ -84,6 +128,10 @@ class PushNotificationManager: NSObject, ObservableObject {
if let success = result as? ApiResultSuccess<DeviceRegistrationResponse> {
print("✅ Device registered successfully: \(success.data)")
// Cache the token on successful registration
await MainActor.run {
self.lastRegisteredToken = token
}
} else if let error = result as? ApiResultError {
print("❌ Failed to register device: \(error.message)")
} else {

View File

@@ -40,6 +40,9 @@ class AuthenticationManager: ObservableObject {
if let success = result as? ApiResultSuccess<User> {
self.isVerified = success.data?.verified ?? false
// Register device for push notifications for authenticated users
PushNotificationManager.shared.registerDeviceAfterLogin()
// Verify subscription entitlements with backend for verified users
if self.isVerified {
await StoreKitManager.shared.verifyEntitlementsOnLaunch()
@@ -65,6 +68,9 @@ class AuthenticationManager: ObservableObject {
func login(verified: Bool) {
isAuthenticated = true
isVerified = verified
// Register device for push notifications now that user is authenticated
PushNotificationManager.shared.registerDeviceAfterLogin()
}
func markVerified() {

View File

@@ -5,6 +5,7 @@ import ComposeApp
struct iOSApp: App {
@UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
@StateObject private var themeManager = ThemeManager.shared
@Environment(\.scenePhase) private var scenePhase
@State private var deepLinkResetToken: String?
init() {
@@ -35,6 +36,12 @@ struct iOSApp: App {
.onOpenURL { url in
handleDeepLink(url: url)
}
.onChange(of: scenePhase) { newPhase in
if newPhase == .active {
// Check and register device token when app becomes active
PushNotificationManager.shared.checkAndRegisterDeviceIfNeeded()
}
}
}
}