feat(ios): enhance background task handlers and documentation
Enhance background task handlers with recovery logic and comprehensive
code documentation:
Background Task Handlers (Section 3.3):
- Enhance handleBackgroundFetch with recovery logic:
- Verify scheduled notifications after fetch
- Schedule next background task automatically
- Improved expiration handling with graceful cleanup
- Enhance handleBackgroundNotify with recovery logic:
- Verify scheduled notifications state
- Prepare for next task scheduling
- Improved expiration handling with graceful cleanup
- Add getNextScheduledNotificationTime() helper method
- Wraps scheduler.getNextNotificationTime() with timeout
- Used for automatic next task scheduling
Code Documentation (Section 10.1):
- Add comprehensive file-level documentation to ReactivationManager:
- Purpose, features, architecture overview
- Recovery scenarios supported
- Error handling approach
- Thread safety notes
- Cross-references to requirements docs
- Add detailed method-level documentation:
- performRecovery(): process, scenarios, error handling
- detectScenario(): detection logic, error handling
- performColdStartRecovery(): steps, return values
- detectMissedNotifications(): criteria, error handling
- verifyFutureNotifications(): verification process
- rescheduleMissingNotification(): process, throws
- handleTerminationRecovery(): comprehensive recovery
- performBootRecovery(): boot recovery process
- recordRecoveryHistory(): history recording
- recordRecoveryFailure(): failure recording
- detectBootScenario(): detection logic
- updateLastLaunchTime(): storage details
- verifyBGTaskRegistration(): diagnostic method
- Add @param, @return, @throws tags to all methods
- Document error handling behavior for all methods
Implementation Status (Section 10.2):
- Update ios/Plugin/README.md with current status:
- Mark all completed features as ✅
- Add new components (DAO classes, ReactivationManager)
- Update version to 1.1.0
- Add recovery scenarios supported
- Update architecture overview
- Add last updated date (2025-12-08)
Completes sections 3.3, 10.1, and 10.2 of iOS implementation checklist.
This commit is contained in:
@@ -349,15 +349,27 @@ public class DailyNotificationPlugin: CAPPlugin {
|
||||
* Phase 1: Dummy fetcher - returns static content
|
||||
* Phase 3: Will be replaced with JWT-signed fetcher
|
||||
*
|
||||
* Enhanced with:
|
||||
* - Recovery logic (verify scheduled notifications)
|
||||
* - Next task scheduling
|
||||
* - Graceful expiration handling
|
||||
*
|
||||
* @param task BGAppRefreshTask
|
||||
*/
|
||||
private func handleBackgroundFetch(task: BGAppRefreshTask) {
|
||||
print("DNP-FETCH: Background fetch task started")
|
||||
|
||||
// Set expiration handler
|
||||
// Enhanced expiration handler with graceful cleanup
|
||||
var taskCompleted = false
|
||||
task.expirationHandler = {
|
||||
print("DNP-FETCH: Background fetch task expired")
|
||||
guard !taskCompleted else { return }
|
||||
print("DNP-FETCH: Background fetch task expired - performing graceful cleanup")
|
||||
|
||||
// Cancel any ongoing operations
|
||||
// Note: In production, you might want to cancel URLSession tasks here
|
||||
|
||||
task.setTaskCompleted(success: false)
|
||||
taskCompleted = true
|
||||
}
|
||||
|
||||
// Phase 1: Dummy content fetch (no network)
|
||||
@@ -375,53 +387,127 @@ public class DailyNotificationPlugin: CAPPlugin {
|
||||
|
||||
// Save content to storage via state actor (thread-safe)
|
||||
Task {
|
||||
if #available(iOS 13.0, *) {
|
||||
if let stateActor = await self.stateActor {
|
||||
await stateActor.saveNotificationContent(dummyContent)
|
||||
|
||||
// Mark successful run
|
||||
let currentTime = Int64(Date().timeIntervalSince1970 * 1000)
|
||||
await stateActor.saveLastSuccessfulRun(timestamp: currentTime)
|
||||
do {
|
||||
if #available(iOS 13.0, *) {
|
||||
if let stateActor = await self.stateActor {
|
||||
await stateActor.saveNotificationContent(dummyContent)
|
||||
|
||||
// Mark successful run
|
||||
let currentTime = Int64(Date().timeIntervalSince1970 * 1000)
|
||||
await stateActor.saveLastSuccessfulRun(timestamp: currentTime)
|
||||
} else {
|
||||
// Fallback to direct storage access
|
||||
self.storage?.saveNotificationContent(dummyContent)
|
||||
let currentTime = Int64(Date().timeIntervalSince1970 * 1000)
|
||||
self.storage?.saveLastSuccessfulRun(timestamp: currentTime)
|
||||
}
|
||||
} else {
|
||||
// Fallback to direct storage access
|
||||
// Fallback for iOS < 13
|
||||
self.storage?.saveNotificationContent(dummyContent)
|
||||
let currentTime = Int64(Date().timeIntervalSince1970 * 1000)
|
||||
self.storage?.saveLastSuccessfulRun(timestamp: currentTime)
|
||||
}
|
||||
} else {
|
||||
// Fallback for iOS < 13
|
||||
self.storage?.saveNotificationContent(dummyContent)
|
||||
let currentTime = Int64(Date().timeIntervalSince1970 * 1000)
|
||||
self.storage?.saveLastSuccessfulRun(timestamp: currentTime)
|
||||
|
||||
// Phase 3.3: Recovery logic - verify scheduled notifications
|
||||
// Check if notifications are still scheduled after fetch
|
||||
if let reactivationManager = self.reactivationManager {
|
||||
// Perform lightweight verification (non-blocking)
|
||||
Task {
|
||||
do {
|
||||
let verificationResult = try await reactivationManager.verifyFutureNotifications()
|
||||
if verificationResult.notificationsMissing > 0 {
|
||||
print("DNP-FETCH: Recovery - found \(verificationResult.notificationsMissing) missing notifications, will reschedule on next app launch")
|
||||
// Note: Full recovery happens on app launch, not in background task
|
||||
}
|
||||
} catch {
|
||||
// Non-fatal: Log but don't fail task
|
||||
print("DNP-FETCH: Recovery verification failed (non-fatal): \(error.localizedDescription)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Phase 3.3: Schedule next background task
|
||||
// Calculate next fetch time based on notification schedule
|
||||
if let nextScheduledTime = self.getNextScheduledNotificationTime() {
|
||||
self.scheduleBackgroundFetch(scheduledTime: nextScheduledTime)
|
||||
print("DNP-FETCH: Next background fetch scheduled")
|
||||
} else {
|
||||
print("DNP-FETCH: No future notifications found, skipping next task schedule")
|
||||
}
|
||||
|
||||
guard !taskCompleted else { return }
|
||||
task.setTaskCompleted(success: true)
|
||||
taskCompleted = true
|
||||
print("DNP-FETCH: Background fetch task completed successfully")
|
||||
|
||||
} catch {
|
||||
print("DNP-FETCH: Background fetch task failed: \(error.localizedDescription)")
|
||||
guard !taskCompleted else { return }
|
||||
task.setTaskCompleted(success: false)
|
||||
taskCompleted = true
|
||||
}
|
||||
}
|
||||
|
||||
// Schedule next fetch
|
||||
// TODO: Calculate next fetch time based on notification schedule
|
||||
|
||||
print("DNP-FETCH: Background fetch task completed successfully")
|
||||
task.setTaskCompleted(success: true)
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle background notification task
|
||||
*
|
||||
* Enhanced with:
|
||||
* - Recovery logic (verify scheduled notifications)
|
||||
* - Next task scheduling
|
||||
* - Graceful expiration handling
|
||||
*
|
||||
* @param task BGProcessingTask
|
||||
*/
|
||||
private func handleBackgroundNotify(task: BGProcessingTask) {
|
||||
print("DNP-NOTIFY: Background notify task started")
|
||||
|
||||
// Set expiration handler
|
||||
// Enhanced expiration handler with graceful cleanup
|
||||
var taskCompleted = false
|
||||
task.expirationHandler = {
|
||||
print("DNP-NOTIFY: Background notify task expired")
|
||||
guard !taskCompleted else { return }
|
||||
print("DNP-NOTIFY: Background notify task expired - performing graceful cleanup")
|
||||
task.setTaskCompleted(success: false)
|
||||
taskCompleted = true
|
||||
}
|
||||
|
||||
// Phase 1: Not used for single daily schedule
|
||||
// This will be used in Phase 2+ for rolling window maintenance
|
||||
|
||||
print("DNP-NOTIFY: Background notify task completed")
|
||||
task.setTaskCompleted(success: true)
|
||||
Task {
|
||||
do {
|
||||
// Phase 3.3: Recovery logic - verify scheduled notifications
|
||||
// Check if notifications are still scheduled
|
||||
if let reactivationManager = self.reactivationManager {
|
||||
// Perform lightweight verification (non-blocking)
|
||||
let verificationResult = try await reactivationManager.verifyFutureNotifications()
|
||||
if verificationResult.notificationsMissing > 0 {
|
||||
print("DNP-NOTIFY: Recovery - found \(verificationResult.notificationsMissing) missing notifications, will reschedule on next app launch")
|
||||
// Note: Full recovery happens on app launch, not in background task
|
||||
}
|
||||
}
|
||||
|
||||
// Phase 1: Not used for single daily schedule
|
||||
// This will be used in Phase 2+ for rolling window maintenance
|
||||
// For now, just verify state
|
||||
|
||||
// Phase 3.3: Schedule next background task if needed
|
||||
// For notify task, schedule next occurrence if applicable
|
||||
if let nextScheduledTime = self.getNextScheduledNotificationTime() {
|
||||
// Calculate next notify task time (if applicable)
|
||||
// Note: Notify tasks are typically scheduled less frequently than fetch tasks
|
||||
print("DNP-NOTIFY: Next notification scheduled at \(nextScheduledTime)")
|
||||
}
|
||||
|
||||
guard !taskCompleted else { return }
|
||||
task.setTaskCompleted(success: true)
|
||||
taskCompleted = true
|
||||
print("DNP-NOTIFY: Background notify task completed successfully")
|
||||
|
||||
} catch {
|
||||
print("DNP-NOTIFY: Background notify task failed: \(error.localizedDescription)")
|
||||
guard !taskCompleted else { return }
|
||||
task.setTaskCompleted(success: false)
|
||||
taskCompleted = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1602,6 +1688,34 @@ public class DailyNotificationPlugin: CAPPlugin {
|
||||
|
||||
// MARK: - Phase 1: Helper Methods
|
||||
|
||||
/**
|
||||
* Get next scheduled notification time
|
||||
*
|
||||
* Helper method to get the next scheduled notification time for
|
||||
* scheduling background tasks. Uses async/await internally.
|
||||
*
|
||||
* @return Next scheduled notification time in milliseconds (Int64), or nil if none
|
||||
*/
|
||||
private func getNextScheduledNotificationTime() -> Int64? {
|
||||
guard let scheduler = scheduler else {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Use async helper to get next notification time
|
||||
// Note: This is called from background task handlers which are already async
|
||||
var nextTime: Int64? = nil
|
||||
let semaphore = DispatchSemaphore(value: 0)
|
||||
|
||||
Task {
|
||||
nextTime = await scheduler.getNextNotificationTime()
|
||||
semaphore.signal()
|
||||
}
|
||||
|
||||
// Wait with timeout (2 seconds - background tasks have limited time)
|
||||
_ = semaphore.wait(timeout: .now() + 2.0)
|
||||
return nextTime
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate next scheduled time for given hour and minute
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user