You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

173 lines
6.5 KiB

//
// DailyNotificationBackgroundTasks.swift
// DailyNotificationPlugin
//
// Created by Matthew Raymer on 2025-09-22
// Copyright © 2025 TimeSafari. All rights reserved.
//
import Foundation
import BackgroundTasks
import UserNotifications
import CoreData
/**
* Background task handlers for iOS Daily Notification Plugin
* Implements BGTaskScheduler handlers for content fetch and notification delivery
*
* @author Matthew Raymer
* @version 1.1.0
* @created 2025-09-22 09:22:32 UTC
*/
extension DailyNotificationPlugin {
private func handleBackgroundFetch(task: BGAppRefreshTask) {
print("DNP-FETCH-START: Background fetch task started")
task.expirationHandler = {
print("DNP-FETCH-TIMEOUT: Background fetch task expired")
task.setTaskCompleted(success: false)
}
Task {
do {
let startTime = Date()
let content = try await performContentFetch()
// Store content in Core Data
try await storeContent(content)
let duration = Date().timeIntervalSince(startTime)
print("DNP-FETCH-SUCCESS: Content fetch completed in \(duration)s")
// Fire callbacks
try await fireCallbacks(eventType: "onFetchSuccess", payload: content)
task.setTaskCompleted(success: true)
} catch {
print("DNP-FETCH-FAILURE: Content fetch failed: \(error)")
task.setTaskCompleted(success: false)
}
}
}
private func handleBackgroundNotify(task: BGProcessingTask) {
print("DNP-NOTIFY-START: Background notify task started")
task.expirationHandler = {
print("DNP-NOTIFY-TIMEOUT: Background notify task expired")
task.setTaskCompleted(success: false)
}
Task {
do {
let startTime = Date()
// Get latest cached content
guard let latestContent = try await getLatestContent() else {
print("DNP-NOTIFY-SKIP: No cached content available")
task.setTaskCompleted(success: true)
return
}
// Check TTL
if isContentExpired(content: latestContent) {
print("DNP-NOTIFY-SKIP-TTL: Content TTL expired, skipping notification")
try await recordHistory(kind: "notify", outcome: "skipped_ttl")
task.setTaskCompleted(success: true)
return
}
// Show notification
try await showNotification(content: latestContent)
let duration = Date().timeIntervalSince(startTime)
print("DNP-NOTIFY-SUCCESS: Notification displayed in \(duration)s")
// Fire callbacks
try await fireCallbacks(eventType: "onNotifyDelivered", payload: latestContent)
task.setTaskCompleted(success: true)
} catch {
print("DNP-NOTIFY-FAILURE: Notification failed: \(error)")
task.setTaskCompleted(success: false)
}
}
}
private func performContentFetch() async throws -> [String: Any] {
// Mock content fetch implementation
// In production, this would make actual HTTP requests
let mockContent = [
"id": "fetch_\(Date().timeIntervalSince1970)",
"timestamp": Date().timeIntervalSince1970,
"content": "Daily notification content from iOS",
"source": "ios_platform"
] as [String: Any]
return mockContent
}
private func storeContent(_ content: [String: Any]) async throws {
let context = persistenceController.container.viewContext
let contentEntity = ContentCache(context: context)
contentEntity.id = content["id"] as? String
contentEntity.fetchedAt = Date(timeIntervalSince1970: content["timestamp"] as? TimeInterval ?? 0)
contentEntity.ttlSeconds = 3600 // 1 hour default TTL
contentEntity.payload = try JSONSerialization.data(withJSONObject: content)
contentEntity.meta = "fetched_by_ios_bg_task"
try context.save()
print("DNP-CACHE-STORE: Content stored in Core Data")
}
private func getLatestContent() async throws -> [String: Any]? {
let context = persistenceController.container.viewContext
let request: NSFetchRequest<ContentCache> = ContentCache.fetchRequest()
request.sortDescriptors = [NSSortDescriptor(keyPath: \ContentCache.fetchedAt, ascending: false)]
request.fetchLimit = 1
let results = try context.fetch(request)
guard let latest = results.first else { return nil }
return try JSONSerialization.jsonObject(with: latest.payload!) as? [String: Any]
}
private func isContentExpired(content: [String: Any]) -> Bool {
guard let timestamp = content["timestamp"] as? TimeInterval else { return true }
let fetchedAt = Date(timeIntervalSince1970: timestamp)
let ttlExpiry = fetchedAt.addingTimeInterval(3600) // 1 hour TTL
return Date() > ttlExpiry
}
private func showNotification(content: [String: Any]) async throws {
let notificationContent = UNMutableNotificationContent()
notificationContent.title = "Daily Notification"
notificationContent.body = content["content"] as? String ?? "Your daily update is ready"
notificationContent.sound = .default
let request = UNNotificationRequest(
identifier: "daily-notification-\(Date().timeIntervalSince1970)",
content: notificationContent,
trigger: nil // Immediate delivery
)
try await notificationCenter.add(request)
print("DNP-NOTIFY-DISPLAY: Notification displayed")
}
private func recordHistory(kind: String, outcome: String) async throws {
let context = persistenceController.container.viewContext
let history = History(context: context)
history.id = "\(kind)_\(Date().timeIntervalSince1970)"
history.kind = kind
history.occurredAt = Date()
history.outcome = outcome
try context.save()
}
}