migration
This commit is contained in:
60
NotificationServiceExtension/NotificationService.swift
Normal file
60
NotificationServiceExtension/NotificationService.swift
Normal file
@@ -0,0 +1,60 @@
|
||||
import UserNotifications
|
||||
|
||||
/// Rich (image) push — see docs/api/push-notifications-integration-guide.md §4.3.
|
||||
/// Only fires when Atomenta's push payload sets `"mutable-content": 1`, which
|
||||
/// it does by default on any push carrying an image (real wire key is
|
||||
/// `"image"`, not the guide's `"imageUrl"` — verified against
|
||||
/// `PushNotificationService.dispatchApns`/`dispatchApnsMany` in Atomenta).
|
||||
/// `@unchecked Sendable`: the extension process only ever runs one
|
||||
/// `didReceive`/`serviceExtensionTimeWillExpire` cycle at a time — there's no
|
||||
/// real concurrent access to `contentHandler`/`bestAttemptContent` to guard
|
||||
/// against, just a background download completion handing back to this
|
||||
/// instance.
|
||||
final class NotificationService: UNNotificationServiceExtension, @unchecked Sendable {
|
||||
private var contentHandler: ((UNNotificationContent) -> Void)?
|
||||
private var bestAttemptContent: UNMutableNotificationContent?
|
||||
|
||||
override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
|
||||
self.contentHandler = contentHandler
|
||||
let mutableContent = (request.content.mutableCopy() as? UNMutableNotificationContent) ?? UNMutableNotificationContent()
|
||||
bestAttemptContent = mutableContent
|
||||
|
||||
guard let imageURLString = request.content.userInfo["image"] as? String,
|
||||
let imageURL = URL(string: imageURLString) else {
|
||||
deliver()
|
||||
return
|
||||
}
|
||||
|
||||
URLSession.shared.downloadTask(with: imageURL) { [weak self] location, _, _ in
|
||||
if let location, let attachment = Self.attachment(fromDownloadedFile: location) {
|
||||
self?.bestAttemptContent?.attachments = [attachment]
|
||||
}
|
||||
self?.deliver()
|
||||
}.resume()
|
||||
}
|
||||
|
||||
/// The system calls this if `didReceive` doesn't finish within its time
|
||||
/// budget — must still deliver the best content built so far, since
|
||||
/// `contentHandler` is contractually required to run exactly once.
|
||||
override func serviceExtensionTimeWillExpire() {
|
||||
deliver()
|
||||
}
|
||||
|
||||
private func deliver() {
|
||||
guard let bestAttemptContent else { return }
|
||||
contentHandler?(bestAttemptContent)
|
||||
contentHandler = nil
|
||||
}
|
||||
|
||||
private static func attachment(fromDownloadedFile location: URL) -> UNNotificationAttachment? {
|
||||
let tmpURL = FileManager.default.temporaryDirectory
|
||||
.appendingPathComponent(UUID().uuidString)
|
||||
.appendingPathExtension("jpg")
|
||||
do {
|
||||
try FileManager.default.moveItem(at: location, to: tmpURL)
|
||||
return try UNNotificationAttachment(identifier: "image", url: tmpURL)
|
||||
} catch {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user