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
						
					
					
				
			
		
		
		
			
			
			
				
					
				
				
					
				
			
		
		
	
	
							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()
							 | 
						|
								    }
							 | 
						|
								}
							 | 
						|
								
							 |