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