Files
daily-notification-plugin/ios/Plugin/NotificationDeliveryDAO.swift
Jose Olarte III 1bfd87a0e4 fix(ios): resolve build errors and add missing configureNativeFetcher method
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.
2025-12-11 16:44:18 +08:00

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