1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
|
import Foundation
import UserNotifications
@MainActor
final class LocalNotificationService {
static let shared = LocalNotificationService()
private init() {}
func configureForegroundPresentation() {
UNUserNotificationCenter.current().delegate = NotificationCenterDelegate.shared
}
func requestAuthorizationIfNeeded() async -> Bool {
let center = UNUserNotificationCenter.current()
let settings = await center.notificationSettings()
switch settings.authorizationStatus {
case .authorized, .provisional, .ephemeral:
return true
case .notDetermined:
return (try? await center.requestAuthorization(options: [.alert, .badge, .sound])) ?? false
case .denied:
return false
@unknown default:
return false
}
}
func isAuthorizedForAlerts() async -> Bool {
let settings = await UNUserNotificationCenter.current().notificationSettings()
switch settings.authorizationStatus {
case .authorized, .provisional, .ephemeral:
return true
case .denied, .notDetermined:
return false
@unknown default:
return false
}
}
func notifyDomainEvent(domain: String, message: String, severity: ChangeSeverity) async {
await schedule(
identifier: "domain-change-\(domain)",
title: domain,
body: message,
interruptionLevel: severity == .high ? .timeSensitive : .active
)
}
func notifyCertificateWarning(domain: String, daysRemaining: Int) async {
await schedule(
identifier: "cert-warning-\(domain)",
title: domain,
body: "Certificate expires in \(daysRemaining) days",
interruptionLevel: .timeSensitive
)
}
func notifyMonitoringAlert(
domain: String,
message: String,
severity: MonitoringAlertSeverity
) async {
let interruptionLevel: UNNotificationInterruptionLevel
switch severity {
case .critical:
interruptionLevel = .timeSensitive
case .warning, .info:
interruptionLevel = .active
}
await schedule(
identifier: "monitoring-\(domain)-\(UUID().uuidString)",
title: domain,
body: message,
interruptionLevel: interruptionLevel
)
}
func notifySweepComplete(summary: BatchSweepSummary) async {
let body = "\(summary.changedDomains) changed, \(summary.warningDomains) warnings, \(summary.unchangedDomains) unchanged"
await schedule(
identifier: "sweep-complete",
title: summary.source == .watchlistRefresh ? "Check All Complete" : "Batch Complete",
body: body,
interruptionLevel: .active
)
}
private func schedule(
identifier: String,
title: String,
body: String,
interruptionLevel: UNNotificationInterruptionLevel
) async {
let content = UNMutableNotificationContent()
content.title = title
content.body = body
content.sound = .default
content.interruptionLevel = interruptionLevel
let request = UNNotificationRequest(
identifier: identifier,
content: content,
trigger: UNTimeIntervalNotificationTrigger(timeInterval: 0.1, repeats: false)
)
try? await UNUserNotificationCenter.current().add(request)
}
}
private final class NotificationCenterDelegate: NSObject, UNUserNotificationCenterDelegate {
static let shared = NotificationCenterDelegate()
func userNotificationCenter(
_ center: UNUserNotificationCenter,
willPresent notification: UNNotification
) async -> UNNotificationPresentationOptions {
[.banner, .list, .sound]
}
}
|