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.
 
 
 
 
 
 

260 lines
9.4 KiB

/**
* DailyNotificationPlugin.swift
*
* Main iOS plugin class for handling daily notifications
*
* @author Matthew Raymer
* @version 1.0.0
*/
import Foundation
import Capacitor
import BackgroundTasks
import UserNotifications
@objc(DailyNotificationPlugin)
public class DailyNotificationPlugin: CAPPlugin {
private static let TAG = "DailyNotificationPlugin"
private var database: DailyNotificationDatabase?
private var ttlEnforcer: DailyNotificationTTLEnforcer?
private var rollingWindow: DailyNotificationRollingWindow?
private var backgroundTaskManager: DailyNotificationBackgroundTaskManager?
private var useSharedStorage: Bool = false
private var databasePath: String?
private var ttlSeconds: TimeInterval = 3600
private var prefetchLeadMinutes: Int = 15
public override func load() {
super.load()
print("\(Self.TAG): DailyNotificationPlugin loading")
initializeComponents()
if #available(iOS 13.0, *) {
backgroundTaskManager?.registerBackgroundTask()
}
print("\(Self.TAG): DailyNotificationPlugin loaded successfully")
}
private func initializeComponents() {
if useSharedStorage, let databasePath = databasePath {
database = DailyNotificationDatabase(path: databasePath)
}
ttlEnforcer = DailyNotificationTTLEnforcer(database: database, useSharedStorage: useSharedStorage)
rollingWindow = DailyNotificationRollingWindow(ttlEnforcer: ttlEnforcer!,
database: database,
useSharedStorage: useSharedStorage)
if #available(iOS 13.0, *) {
backgroundTaskManager = DailyNotificationBackgroundTaskManager(database: database,
ttlEnforcer: ttlEnforcer!,
rollingWindow: rollingWindow!)
}
print("\(Self.TAG): All components initialized successfully")
}
@objc func configure(_ call: CAPPluginCall) {
print("\(Self.TAG): Configuring plugin")
if let dbPath = call.getString("dbPath") {
databasePath = dbPath
}
if let storage = call.getString("storage") {
useSharedStorage = (storage == "shared")
}
if let ttl = call.getDouble("ttlSeconds") {
ttlSeconds = ttl
}
if let leadMinutes = call.getInt("prefetchLeadMinutes") {
prefetchLeadMinutes = leadMinutes
}
storeConfiguration()
initializeComponents()
call.resolve()
}
private func storeConfiguration() {
if useSharedStorage, let database = database {
// Store in SQLite
print("\(Self.TAG): Storing configuration in SQLite")
} else {
// Store in UserDefaults
UserDefaults.standard.set(databasePath, forKey: "databasePath")
UserDefaults.standard.set(useSharedStorage, forKey: "useSharedStorage")
UserDefaults.standard.set(ttlSeconds, forKey: "ttlSeconds")
UserDefaults.standard.set(prefetchLeadMinutes, forKey: "prefetchLeadMinutes")
}
}
@objc func maintainRollingWindow(_ call: CAPPluginCall) {
print("\(Self.TAG): Manual rolling window maintenance requested")
if let rollingWindow = rollingWindow {
rollingWindow.forceMaintenance()
call.resolve()
} else {
call.reject("Rolling window not initialized")
}
}
@objc func getRollingWindowStats(_ call: CAPPluginCall) {
print("\(Self.TAG): Rolling window stats requested")
if let rollingWindow = rollingWindow {
let stats = rollingWindow.getRollingWindowStats()
let result = [
"stats": stats,
"maintenanceNeeded": rollingWindow.isMaintenanceNeeded(),
"timeUntilNextMaintenance": rollingWindow.getTimeUntilNextMaintenance()
] as [String : Any]
call.resolve(result)
} else {
call.reject("Rolling window not initialized")
}
}
@objc func scheduleBackgroundTask(_ call: CAPPluginCall) {
print("\(Self.TAG): Scheduling background task")
guard let scheduledTimeString = call.getString("scheduledTime") else {
call.reject("scheduledTime parameter is required")
return
}
let formatter = DateFormatter()
formatter.dateFormat = "HH:mm"
guard let scheduledTime = formatter.date(from: scheduledTimeString) else {
call.reject("Invalid scheduledTime format")
return
}
if #available(iOS 13.0, *) {
backgroundTaskManager?.scheduleBackgroundTask(scheduledTime: scheduledTime,
prefetchLeadMinutes: prefetchLeadMinutes)
call.resolve()
} else {
call.reject("Background tasks not available on this iOS version")
}
}
@objc func getBackgroundTaskStatus(_ call: CAPPluginCall) {
print("\(Self.TAG): Background task status requested")
if #available(iOS 13.0, *) {
let status = backgroundTaskManager?.getBackgroundTaskStatus() ?? [:]
call.resolve(status)
} else {
call.resolve(["available": false, "reason": "iOS version not supported"])
}
}
@objc func cancelAllBackgroundTasks(_ call: CAPPluginCall) {
print("\(Self.TAG): Cancelling all background tasks")
if #available(iOS 13.0, *) {
backgroundTaskManager?.cancelAllBackgroundTasks()
call.resolve()
} else {
call.reject("Background tasks not available on this iOS version")
}
}
@objc func getTTLViolationStats(_ call: CAPPluginCall) {
print("\(Self.TAG): TTL violation stats requested")
if let ttlEnforcer = ttlEnforcer {
let stats = ttlEnforcer.getTTLViolationStats()
call.resolve(["stats": stats])
} else {
call.reject("TTL enforcer not initialized")
}
}
@objc func scheduleDailyNotification(_ call: CAPPluginCall) {
print("\(Self.TAG): Scheduling daily notification")
guard let time = call.getString("time") else {
call.reject("Time parameter is required")
return
}
let formatter = DateFormatter()
formatter.dateFormat = "HH:mm"
guard let scheduledTime = formatter.date(from: time) else {
call.reject("Invalid time format")
return
}
let notification = NotificationContent(
id: UUID().uuidString,
title: call.getString("title") ?? "Daily Update",
body: call.getString("body") ?? "Your daily notification is ready",
scheduledTime: scheduledTime.timeIntervalSince1970 * 1000,
fetchedAt: Date().timeIntervalSince1970 * 1000,
url: call.getString("url"),
payload: nil,
etag: nil
)
if let ttlEnforcer = ttlEnforcer, !ttlEnforcer.validateBeforeArming(notification) {
call.reject("Notification content violates TTL")
return
}
scheduleNotification(notification)
if #available(iOS 13.0, *) {
backgroundTaskManager?.scheduleBackgroundTask(scheduledTime: scheduledTime,
prefetchLeadMinutes: prefetchLeadMinutes)
}
call.resolve()
}
private func scheduleNotification(_ notification: NotificationContent) {
let content = UNMutableNotificationContent()
content.title = notification.title ?? "Daily Notification"
content.body = notification.body ?? "Your daily notification is ready"
content.sound = UNNotificationSound.default
let scheduledTime = Date(timeIntervalSince1970: notification.scheduledTime / 1000)
let trigger = UNCalendarNotificationTrigger(dateMatching: Calendar.current.dateComponents([.year, .month, .day, .hour, .minute], from: scheduledTime), repeats: false)
let request = UNNotificationRequest(identifier: notification.id, content: content, trigger: trigger)
UNUserNotificationCenter.current().add(request) { error in
if let error = error {
print("\(Self.TAG): Failed to schedule notification \(notification.id): \(error)")
} else {
print("\(Self.TAG): Successfully scheduled notification: \(notification.id)")
}
}
}
@objc func getLastNotification(_ call: CAPPluginCall) {
let result = [
"id": "placeholder",
"title": "Last Notification",
"body": "This is a placeholder",
"timestamp": Date().timeIntervalSince1970 * 1000
] as [String : Any]
call.resolve(result)
}
@objc func cancelAllNotifications(_ call: CAPPluginCall) {
UNUserNotificationCenter.current().removeAllPendingNotificationRequests()
call.resolve()
}
}