feat(ios): add local notification diagnostics for debug panels
Expose bridge-safe pending, delivered, and settings snapshots from UNUserNotificationCenter so apps can inspect local notification state without native date objects or large payloads. - Add getDeliveredNotifications and getNotificationSettings on iOS - Harden getPendingNotifications with triggerTimestamp/triggerDateIso - Always resolve with serializable values; omit userInfo from delivered - Register new Capacitor methods and TypeScript definitions/web stubs
This commit is contained in:
@@ -2170,55 +2170,89 @@ public class DailyNotificationPlugin: CAPPlugin {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get pending notifications (iOS-specific)
|
||||
*
|
||||
* Get pending notifications (iOS diagnostics)
|
||||
*
|
||||
* Exposes UNUserNotificationCenter pending requests with bridge-safe fields only.
|
||||
*
|
||||
* @param call Plugin call
|
||||
*/
|
||||
@objc func getPendingNotifications(_ call: CAPPluginCall) {
|
||||
Task {
|
||||
do {
|
||||
// Delegate to UNUserNotificationCenter for pending requests
|
||||
let requests = try await notificationCenter.pendingNotificationRequests()
|
||||
|
||||
var notifications: [[String: Any]] = []
|
||||
for request in requests {
|
||||
let content = request.content
|
||||
var triggerDate: Int64 = 0
|
||||
|
||||
if let calendarTrigger = request.trigger as? UNCalendarNotificationTrigger {
|
||||
if let nextDate = calendarTrigger.nextTriggerDate() {
|
||||
triggerDate = Int64(nextDate.timeIntervalSince1970 * 1000)
|
||||
}
|
||||
} else if let timeIntervalTrigger = request.trigger as? UNTimeIntervalNotificationTrigger {
|
||||
if let nextDate = timeIntervalTrigger.nextTriggerDate() {
|
||||
triggerDate = Int64(nextDate.timeIntervalSince1970 * 1000)
|
||||
}
|
||||
}
|
||||
|
||||
let notification: [String: Any] = [
|
||||
"identifier": request.identifier,
|
||||
"title": content.title,
|
||||
"body": content.body,
|
||||
"triggerDate": triggerDate,
|
||||
"triggerType": request.trigger is UNCalendarNotificationTrigger ? "calendar" : (request.trigger is UNTimeIntervalNotificationTrigger ? "timeInterval" : "location"),
|
||||
"repeats": request.trigger?.repeats ?? false
|
||||
]
|
||||
notifications.append(notification)
|
||||
}
|
||||
|
||||
let result: [String: Any] = [
|
||||
"count": notifications.count,
|
||||
"notifications": notifications
|
||||
diagnosticLog("getPendingNotifications called")
|
||||
notificationCenter.getPendingNotificationRequests { requests in
|
||||
var notifications: [[String: Any]] = []
|
||||
for request in requests {
|
||||
let content = request.content
|
||||
let (triggerTimestamp, triggerDateIso) = Self.pendingTriggerFields(from: request.trigger)
|
||||
var item: [String: Any] = [
|
||||
"identifier": request.identifier,
|
||||
"title": content.title,
|
||||
"body": content.body,
|
||||
"triggerType": Self.pendingTriggerTypeString(from: request.trigger),
|
||||
"repeats": request.trigger?.repeats ?? false
|
||||
]
|
||||
|
||||
DispatchQueue.main.async {
|
||||
call.resolve(result)
|
||||
}
|
||||
} catch {
|
||||
DispatchQueue.main.async {
|
||||
call.reject("Failed to get pending notifications: \(error.localizedDescription)", "pending_notifications_failed")
|
||||
if let triggerTimestamp = triggerTimestamp {
|
||||
item["triggerTimestamp"] = triggerTimestamp
|
||||
item["triggerDate"] = triggerTimestamp
|
||||
} else {
|
||||
item["triggerTimestamp"] = NSNull()
|
||||
item["triggerDate"] = NSNull()
|
||||
}
|
||||
item["triggerDateIso"] = triggerDateIso ?? NSNull()
|
||||
notifications.append(item)
|
||||
}
|
||||
self.diagnosticLog("getPendingNotifications resolved count=\(notifications.count)")
|
||||
call.resolve([
|
||||
"count": notifications.count,
|
||||
"notifications": notifications
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get delivered notification metadata (iOS diagnostics)
|
||||
*
|
||||
* Lightweight delivery snapshot; omits userInfo and attachment payloads.
|
||||
*
|
||||
* @param call Plugin call
|
||||
*/
|
||||
@objc func getDeliveredNotifications(_ call: CAPPluginCall) {
|
||||
diagnosticLog("getDeliveredNotifications called")
|
||||
notificationCenter.getDeliveredNotifications { delivered in
|
||||
var notifications: [[String: Any]] = []
|
||||
for notification in delivered {
|
||||
let request = notification.request
|
||||
let content = request.content
|
||||
let deliveredTimestamp = Int64(notification.date.timeIntervalSince1970 * 1000)
|
||||
notifications.append([
|
||||
"identifier": request.identifier,
|
||||
"deliveredTimestamp": deliveredTimestamp,
|
||||
"deliveredDateIso": Self.iso8601String(from: notification.date) ?? NSNull(),
|
||||
"title": content.title,
|
||||
"body": content.body,
|
||||
"categoryIdentifier": content.categoryIdentifier.isEmpty ? NSNull() : content.categoryIdentifier
|
||||
])
|
||||
}
|
||||
self.diagnosticLog("getDeliveredNotifications resolved count=\(notifications.count)")
|
||||
call.resolve([
|
||||
"count": notifications.count,
|
||||
"notifications": notifications
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get notification authorization and presentation settings (iOS diagnostics)
|
||||
*
|
||||
* Always resolves with serializable settings; does not require plugin scheduler init.
|
||||
*
|
||||
* @param call Plugin call
|
||||
*/
|
||||
@objc func getNotificationSettings(_ call: CAPPluginCall) {
|
||||
diagnosticLog("getNotificationSettings called")
|
||||
notificationCenter.getNotificationSettings { settings in
|
||||
let result = Self.serializeNotificationSettings(settings)
|
||||
self.diagnosticLog("getNotificationSettings resolved authorization=\(result["authorizationStatus"] ?? "unknown")")
|
||||
call.resolve(result)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2618,6 +2652,97 @@ public class DailyNotificationPlugin: CAPPlugin {
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Diagnostics helpers
|
||||
extension DailyNotificationPlugin {
|
||||
private func diagnosticLog(_ message: String) {
|
||||
NSLog("[DailyNotificationPlugin] %@", message)
|
||||
}
|
||||
|
||||
private static let iso8601Formatter: ISO8601DateFormatter = {
|
||||
let formatter = ISO8601DateFormatter()
|
||||
formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
|
||||
return formatter
|
||||
}()
|
||||
|
||||
static func iso8601String(from date: Date) -> String? {
|
||||
iso8601Formatter.string(from: date)
|
||||
}
|
||||
|
||||
static func pendingTriggerFields(from trigger: UNNotificationTrigger?) -> (Int64?, String?) {
|
||||
guard let trigger = trigger else { return (nil, nil) }
|
||||
var nextDate: Date?
|
||||
if let calendarTrigger = trigger as? UNCalendarNotificationTrigger {
|
||||
nextDate = calendarTrigger.nextTriggerDate()
|
||||
} else if let timeIntervalTrigger = trigger as? UNTimeIntervalNotificationTrigger {
|
||||
nextDate = timeIntervalTrigger.nextTriggerDate()
|
||||
}
|
||||
guard let date = nextDate else { return (nil, nil) }
|
||||
let timestamp = Int64(date.timeIntervalSince1970 * 1000)
|
||||
return (timestamp, iso8601String(from: date))
|
||||
}
|
||||
|
||||
static func pendingTriggerTypeString(from trigger: UNNotificationTrigger?) -> String {
|
||||
guard let trigger = trigger else { return "unknown" }
|
||||
if trigger is UNCalendarNotificationTrigger { return "calendar" }
|
||||
if trigger is UNTimeIntervalNotificationTrigger { return "timeInterval" }
|
||||
if trigger is UNLocationNotificationTrigger { return "location" }
|
||||
return "unknown"
|
||||
}
|
||||
|
||||
static func authorizationStatusString(_ status: UNAuthorizationStatus) -> String {
|
||||
if #available(iOS 14.0, *), status == .ephemeral {
|
||||
return "ephemeral"
|
||||
}
|
||||
switch status {
|
||||
case .notDetermined: return "notDetermined"
|
||||
case .denied: return "denied"
|
||||
case .authorized: return "authorized"
|
||||
case .provisional: return "provisional"
|
||||
@unknown default: return "unknown"
|
||||
}
|
||||
}
|
||||
|
||||
static func notificationSettingString(_ setting: UNNotificationSetting) -> String {
|
||||
switch setting {
|
||||
case .notSupported: return "notSupported"
|
||||
case .disabled: return "disabled"
|
||||
case .enabled: return "enabled"
|
||||
@unknown default: return "unknown"
|
||||
}
|
||||
}
|
||||
|
||||
static func showPreviewsSettingString(_ setting: UNShowPreviewsSetting) -> String {
|
||||
switch setting {
|
||||
case .always: return "always"
|
||||
case .whenAuthenticated: return "whenAuthenticated"
|
||||
case .never: return "never"
|
||||
@unknown default: return "unknown"
|
||||
}
|
||||
}
|
||||
|
||||
static func serializeNotificationSettings(_ settings: UNNotificationSettings) -> [String: Any] {
|
||||
var result: [String: Any] = [
|
||||
"authorizationStatus": authorizationStatusString(settings.authorizationStatus),
|
||||
"notificationCenterSetting": notificationSettingString(settings.notificationCenterSetting),
|
||||
"lockScreenSetting": notificationSettingString(settings.lockScreenSetting),
|
||||
"carPlaySetting": notificationSettingString(settings.carPlaySetting),
|
||||
"alertSetting": notificationSettingString(settings.alertSetting),
|
||||
"badgeSetting": notificationSettingString(settings.badgeSetting),
|
||||
"soundSetting": notificationSettingString(settings.soundSetting),
|
||||
"criticalAlertSetting": notificationSettingString(settings.criticalAlertSetting),
|
||||
"showPreviewsSetting": showPreviewsSettingString(settings.showPreviewsSetting),
|
||||
]
|
||||
if #available(iOS 13.0, *) {
|
||||
result["announcementSetting"] = notificationSettingString(settings.announcementSetting)
|
||||
}
|
||||
if #available(iOS 15.0, *) {
|
||||
result["scheduledDeliverySetting"] = notificationSettingString(settings.scheduledDeliverySetting)
|
||||
result["timeSensitiveSetting"] = notificationSettingString(settings.timeSensitiveSetting)
|
||||
}
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - CAPBridgedPlugin Conformance
|
||||
// This extension makes the plugin conform to CAPBridgedPlugin protocol
|
||||
// which is required for Capacitor to discover and register the plugin
|
||||
@@ -2651,6 +2776,8 @@ public class DailyNotificationPlugin: CAPPlugin {
|
||||
methods.append(CAPPluginMethod(name: "getNotificationPermissionStatus", returnType: CAPPluginReturnPromise))
|
||||
methods.append(CAPPluginMethod(name: "requestNotificationPermission", returnType: CAPPluginReturnPromise))
|
||||
methods.append(CAPPluginMethod(name: "getPendingNotifications", returnType: CAPPluginReturnPromise))
|
||||
methods.append(CAPPluginMethod(name: "getDeliveredNotifications", returnType: CAPPluginReturnPromise))
|
||||
methods.append(CAPPluginMethod(name: "getNotificationSettings", returnType: CAPPluginReturnPromise))
|
||||
methods.append(CAPPluginMethod(name: "getBackgroundTaskStatus", returnType: CAPPluginReturnPromise))
|
||||
methods.append(CAPPluginMethod(name: "openNotificationSettings", returnType: CAPPluginReturnPromise))
|
||||
methods.append(CAPPluginMethod(name: "openBackgroundAppRefreshSettings", returnType: CAPPluginReturnPromise))
|
||||
|
||||
Reference in New Issue
Block a user