Fixed Swift compilation errors preventing iOS build: - Added explicit self capture [self] in closures in DailyNotificationReactivationManager - Removed invalid BGTaskScheduler.shared.registeredTaskIdentifiers API call - Fixed initialization order in DailyNotificationModel (verifyEntities after container init) Added missing configureNativeFetcher method to iOS plugin: - Implemented method matching Android functionality - Stores configuration in UserDefaults for persistence - Registered method in pluginMethods array - Supports both jwtToken and jwtSecret parameters for compatibility This resolves the runtime error "configureNativeFetcher is not a function" that was preventing the test app from configuring the plugin.
415 lines
15 KiB
Swift
415 lines
15 KiB
Swift
/**
|
|
* NotificationDeliveryDAO.swift
|
|
*
|
|
* Data Access Object (DAO) for NotificationDelivery Core Data entity
|
|
* Provides CRUD operations and query helpers for notification delivery tracking
|
|
*
|
|
* @author Matthew Raymer
|
|
* @version 1.0.0
|
|
* @created 2025-12-08
|
|
*/
|
|
|
|
import Foundation
|
|
import CoreData
|
|
|
|
/**
|
|
* Extension providing DAO methods for NotificationDelivery entity
|
|
*
|
|
* This extension adds CRUD operations and query helpers to the
|
|
* auto-generated NotificationDelivery Core Data class.
|
|
*/
|
|
extension NotificationDelivery {
|
|
|
|
// MARK: - Constants
|
|
|
|
private static let TAG = "DNP-NOTIFICATION-DELIVERY-DAO"
|
|
|
|
// MARK: - Create/Insert Methods
|
|
|
|
/**
|
|
* Create a new NotificationDelivery entity in the given context
|
|
*
|
|
* @param context Core Data managed object context
|
|
* @param id Unique delivery identifier
|
|
* @param notificationId Associated notification content ID
|
|
* @param notificationContent Associated NotificationContent entity
|
|
* @param timesafariDid TimeSafari device ID
|
|
* @param deliveryTimestamp When delivery occurred
|
|
* @param deliveryStatus Delivery status string
|
|
* @param deliveryMethod Delivery method string
|
|
* @param deliveryAttemptNumber Attempt number (1-based)
|
|
* @param deliveryDurationMs Duration of delivery in milliseconds
|
|
* @param userInteractionType Type of user interaction (if any)
|
|
* @param userInteractionTimestamp When user interacted
|
|
* @param userInteractionDurationMs Duration of interaction in milliseconds
|
|
* @param errorCode Error code (if delivery failed)
|
|
* @param errorMessage Error message (if delivery failed)
|
|
* @param deviceInfo Device information JSON string
|
|
* @param networkInfo Network information JSON string
|
|
* @param batteryLevel Battery level (0-100, -1 if unknown)
|
|
* @param dozeModeActive Whether device was in doze mode
|
|
* @param exactAlarmPermission Whether exact alarm permission granted
|
|
* @param notificationPermission Whether notification permission granted
|
|
* @param metadata Additional metadata (JSON string)
|
|
* @return Created NotificationDelivery entity
|
|
*/
|
|
static func create(
|
|
in context: NSManagedObjectContext,
|
|
id: String,
|
|
notificationId: String,
|
|
notificationContent: NotificationContentEntity? = nil,
|
|
timesafariDid: String? = nil,
|
|
deliveryTimestamp: Date,
|
|
deliveryStatus: String? = nil,
|
|
deliveryMethod: String? = nil,
|
|
deliveryAttemptNumber: Int32 = 1,
|
|
deliveryDurationMs: Int64 = 0,
|
|
userInteractionType: String? = nil,
|
|
userInteractionTimestamp: Date? = nil,
|
|
userInteractionDurationMs: Int64 = 0,
|
|
errorCode: String? = nil,
|
|
errorMessage: String? = nil,
|
|
deviceInfo: String? = nil,
|
|
networkInfo: String? = nil,
|
|
batteryLevel: Int32 = -1,
|
|
dozeModeActive: Bool = false,
|
|
exactAlarmPermission: Bool = false,
|
|
notificationPermission: Bool = false,
|
|
metadata: String? = nil
|
|
) -> NotificationDelivery {
|
|
let entity = NotificationDelivery(context: context)
|
|
|
|
entity.id = id
|
|
entity.notificationId = notificationId
|
|
entity.notificationContent = notificationContent
|
|
entity.timesafariDid = timesafariDid
|
|
entity.deliveryTimestamp = deliveryTimestamp
|
|
entity.deliveryStatus = deliveryStatus
|
|
entity.deliveryMethod = deliveryMethod
|
|
entity.deliveryAttemptNumber = deliveryAttemptNumber
|
|
entity.deliveryDurationMs = deliveryDurationMs
|
|
entity.userInteractionType = userInteractionType
|
|
entity.userInteractionTimestamp = userInteractionTimestamp
|
|
entity.userInteractionDurationMs = userInteractionDurationMs
|
|
entity.errorCode = errorCode
|
|
entity.errorMessage = errorMessage
|
|
entity.deviceInfo = deviceInfo
|
|
entity.networkInfo = networkInfo
|
|
entity.batteryLevel = batteryLevel
|
|
entity.dozeModeActive = dozeModeActive
|
|
entity.exactAlarmPermission = exactAlarmPermission
|
|
entity.notificationPermission = notificationPermission
|
|
entity.metadata = metadata
|
|
|
|
print("\(Self.TAG): Created NotificationDelivery with id: \(id)")
|
|
return entity
|
|
}
|
|
|
|
/**
|
|
* Create from dictionary representation
|
|
*
|
|
* @param context Core Data managed object context
|
|
* @param dict Dictionary with delivery data
|
|
* @param notificationContent Optional associated NotificationContent entity
|
|
* @return Created NotificationDelivery entity or nil
|
|
*/
|
|
static func create(
|
|
in context: NSManagedObjectContext,
|
|
from dict: [String: Any],
|
|
notificationContent: NotificationContentEntity? = nil
|
|
) -> NotificationDelivery? {
|
|
guard let id = dict["id"] as? String,
|
|
let notificationId = dict["notificationId"] as? String else {
|
|
print("\(Self.TAG): Missing required fields")
|
|
return nil
|
|
}
|
|
|
|
// Convert deliveryTimestamp from epoch milliseconds or Date
|
|
let deliveryTimestamp: Date
|
|
if let timeMillis = dict["deliveryTimestamp"] as? Int64 {
|
|
deliveryTimestamp = DailyNotificationDataConversions.dateFromEpochMillis(timeMillis)
|
|
} else if let timeDate = dict["deliveryTimestamp"] as? Date {
|
|
deliveryTimestamp = timeDate
|
|
} else {
|
|
print("\(Self.TAG): Missing or invalid 'deliveryTimestamp' field")
|
|
return nil
|
|
}
|
|
|
|
// Convert userInteractionTimestamp if present
|
|
let userInteractionTimestamp: Date?
|
|
if let interactionMillis = dict["userInteractionTimestamp"] as? Int64 {
|
|
userInteractionTimestamp = DailyNotificationDataConversions.dateFromEpochMillis(interactionMillis)
|
|
} else if let interactionDate = dict["userInteractionTimestamp"] as? Date {
|
|
userInteractionTimestamp = interactionDate
|
|
} else {
|
|
userInteractionTimestamp = nil
|
|
}
|
|
|
|
let entity = NotificationDelivery(context: context)
|
|
entity.id = id
|
|
entity.notificationId = notificationId
|
|
entity.notificationContent = notificationContent
|
|
entity.timesafariDid = dict["timesafariDid"] as? String
|
|
entity.deliveryTimestamp = deliveryTimestamp
|
|
entity.deliveryStatus = dict["deliveryStatus"] as? String
|
|
entity.deliveryMethod = dict["deliveryMethod"] as? String
|
|
entity.deliveryAttemptNumber = DailyNotificationDataConversions.int32FromInt(
|
|
dict["deliveryAttemptNumber"] as? Int ?? 1
|
|
)
|
|
entity.deliveryDurationMs = dict["deliveryDurationMs"] as? Int64 ?? 0
|
|
entity.userInteractionType = dict["userInteractionType"] as? String
|
|
entity.userInteractionTimestamp = userInteractionTimestamp
|
|
entity.userInteractionDurationMs = dict["userInteractionDurationMs"] as? Int64 ?? 0
|
|
entity.errorCode = dict["errorCode"] as? String
|
|
entity.errorMessage = dict["errorMessage"] as? String
|
|
entity.deviceInfo = dict["deviceInfo"] as? String
|
|
entity.networkInfo = dict["networkInfo"] as? String
|
|
entity.batteryLevel = DailyNotificationDataConversions.int32FromInt(
|
|
dict["batteryLevel"] as? Int ?? -1
|
|
)
|
|
entity.dozeModeActive = dict["dozeModeActive"] as? Bool ?? false
|
|
entity.exactAlarmPermission = dict["exactAlarmPermission"] as? Bool ?? false
|
|
entity.notificationPermission = dict["notificationPermission"] as? Bool ?? false
|
|
entity.metadata = dict["metadata"] as? String
|
|
|
|
return entity
|
|
}
|
|
|
|
// MARK: - Read/Query Methods
|
|
|
|
/**
|
|
* Fetch NotificationDelivery by ID
|
|
*
|
|
* @param context Core Data managed object context
|
|
* @param id Delivery ID
|
|
* @return NotificationDelivery entity or nil
|
|
*/
|
|
static func fetch(
|
|
by id: String,
|
|
in context: NSManagedObjectContext
|
|
) -> NotificationDelivery? {
|
|
let request: NSFetchRequest<NotificationDelivery> = NotificationDelivery.fetchRequest()
|
|
request.predicate = NSPredicate(format: "id == %@", id)
|
|
request.fetchLimit = 1
|
|
|
|
do {
|
|
let results = try context.fetch(request)
|
|
return results.first
|
|
} catch {
|
|
print("\(Self.TAG): Error fetching by id: \(error.localizedDescription)")
|
|
return nil
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Query by notificationId
|
|
*
|
|
* @param context Core Data managed object context
|
|
* @param notificationId Notification content ID
|
|
* @return Array of NotificationDelivery entities
|
|
*/
|
|
static func queryByNotificationId(
|
|
_ notificationId: String,
|
|
in context: NSManagedObjectContext
|
|
) -> [NotificationDelivery] {
|
|
let request: NSFetchRequest<NotificationDelivery> = NotificationDelivery.fetchRequest()
|
|
request.predicate = NSPredicate(format: "notificationId == %@", notificationId)
|
|
request.sortDescriptors = [NSSortDescriptor(key: "deliveryTimestamp", ascending: false)]
|
|
|
|
do {
|
|
return try context.fetch(request)
|
|
} catch {
|
|
print("\(Self.TAG): Error querying by notificationId: \(error.localizedDescription)")
|
|
return []
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Query by deliveryTimestamp range
|
|
*
|
|
* @param context Core Data managed object context
|
|
* @param startDate Start date (inclusive)
|
|
* @param endDate End date (inclusive)
|
|
* @return Array of NotificationDelivery entities
|
|
*/
|
|
static func query(
|
|
deliveryTimestampBetween startDate: Date,
|
|
and endDate: Date,
|
|
in context: NSManagedObjectContext
|
|
) -> [NotificationDelivery] {
|
|
let request: NSFetchRequest<NotificationDelivery> = NotificationDelivery.fetchRequest()
|
|
request.predicate = NSPredicate(
|
|
format: "deliveryTimestamp >= %@ AND deliveryTimestamp <= %@",
|
|
startDate as NSDate,
|
|
endDate as NSDate
|
|
)
|
|
request.sortDescriptors = [NSSortDescriptor(key: "deliveryTimestamp", ascending: false)]
|
|
|
|
do {
|
|
return try context.fetch(request)
|
|
} catch {
|
|
print("\(Self.TAG): Error querying by deliveryTimestamp range: \(error.localizedDescription)")
|
|
return []
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Query by deliveryStatus
|
|
*
|
|
* @param context Core Data managed object context
|
|
* @param deliveryStatus Delivery status string
|
|
* @return Array of NotificationDelivery entities
|
|
*/
|
|
static func queryByDeliveryStatus(
|
|
_ deliveryStatus: String,
|
|
in context: NSManagedObjectContext
|
|
) -> [NotificationDelivery] {
|
|
let request: NSFetchRequest<NotificationDelivery> = NotificationDelivery.fetchRequest()
|
|
request.predicate = NSPredicate(format: "deliveryStatus == %@", deliveryStatus)
|
|
request.sortDescriptors = [NSSortDescriptor(key: "deliveryTimestamp", ascending: false)]
|
|
|
|
do {
|
|
return try context.fetch(request)
|
|
} catch {
|
|
print("\(Self.TAG): Error querying by deliveryStatus: \(error.localizedDescription)")
|
|
return []
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Query by timesafariDid
|
|
*
|
|
* @param context Core Data managed object context
|
|
* @param timesafariDid TimeSafari device ID
|
|
* @return Array of NotificationDelivery entities
|
|
*/
|
|
static func queryByTimesafariDid(
|
|
_ timesafariDid: String,
|
|
in context: NSManagedObjectContext
|
|
) -> [NotificationDelivery] {
|
|
let request: NSFetchRequest<NotificationDelivery> = NotificationDelivery.fetchRequest()
|
|
request.predicate = NSPredicate(format: "timesafariDid == %@", timesafariDid)
|
|
request.sortDescriptors = [NSSortDescriptor(key: "deliveryTimestamp", ascending: false)]
|
|
|
|
do {
|
|
return try context.fetch(request)
|
|
} catch {
|
|
print("\(Self.TAG): Error querying by timesafariDid: \(error.localizedDescription)")
|
|
return []
|
|
}
|
|
}
|
|
|
|
// MARK: - Update Methods
|
|
|
|
/**
|
|
* Update delivery status
|
|
*
|
|
* @param status New delivery status
|
|
*/
|
|
func updateDeliveryStatus(_ status: String) {
|
|
self.deliveryStatus = status
|
|
}
|
|
|
|
/**
|
|
* Record user interaction
|
|
*
|
|
* @param interactionType Type of interaction
|
|
* @param timestamp When interaction occurred
|
|
* @param durationMs Duration of interaction in milliseconds
|
|
*/
|
|
func recordUserInteraction(
|
|
type: String,
|
|
timestamp: Date,
|
|
durationMs: Int64
|
|
) {
|
|
self.userInteractionType = type
|
|
self.userInteractionTimestamp = timestamp
|
|
self.userInteractionDurationMs = durationMs
|
|
}
|
|
|
|
// MARK: - Delete Methods
|
|
|
|
/**
|
|
* Delete NotificationDelivery by ID
|
|
*
|
|
* @param context Core Data managed object context
|
|
* @param id Delivery ID
|
|
* @return true if deleted, false otherwise
|
|
*/
|
|
static func delete(
|
|
by id: String,
|
|
in context: NSManagedObjectContext
|
|
) -> Bool {
|
|
guard let entity = fetch(by: id, in: context) else {
|
|
return false
|
|
}
|
|
|
|
context.delete(entity)
|
|
|
|
do {
|
|
try context.save()
|
|
print("\(Self.TAG): Deleted NotificationDelivery with id: \(id)")
|
|
return true
|
|
} catch {
|
|
print("\(Self.TAG): Error deleting: \(error.localizedDescription)")
|
|
context.rollback()
|
|
return false
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Delete all NotificationDelivery entities for a notification
|
|
*
|
|
* @param context Core Data managed object context
|
|
* @param notificationId Notification content ID
|
|
* @return Number of entities deleted
|
|
*/
|
|
static func deleteAll(
|
|
for notificationId: String,
|
|
in context: NSManagedObjectContext
|
|
) -> Int {
|
|
let deliveries = queryByNotificationId(notificationId, in: context)
|
|
let count = deliveries.count
|
|
|
|
for delivery in deliveries {
|
|
context.delete(delivery)
|
|
}
|
|
|
|
do {
|
|
try context.save()
|
|
print("\(Self.TAG): Deleted \(count) NotificationDelivery entities for notification: \(notificationId)")
|
|
return count
|
|
} catch {
|
|
print("\(Self.TAG): Error deleting all: \(error.localizedDescription)")
|
|
context.rollback()
|
|
return 0
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Delete all NotificationDelivery entities
|
|
*
|
|
* @param context Core Data managed object context
|
|
* @return Number of entities deleted
|
|
*/
|
|
static func deleteAll(
|
|
in context: NSManagedObjectContext
|
|
) -> Int {
|
|
let request: NSFetchRequest<NSFetchRequestResult> = NotificationDelivery.fetchRequest()
|
|
let deleteRequest = NSBatchDeleteRequest(fetchRequest: request)
|
|
|
|
do {
|
|
let result = try context.execute(deleteRequest) as? NSBatchDeleteResult
|
|
try context.save()
|
|
let count = result?.result as? Int ?? 0
|
|
print("\(Self.TAG): Deleted \(count) NotificationDelivery entities")
|
|
return count
|
|
} catch {
|
|
print("\(Self.TAG): Error deleting all: \(error.localizedDescription)")
|
|
context.rollback()
|
|
return 0
|
|
}
|
|
}
|
|
}
|
|
|