The code paths for generating zap notifications were very different from the paths used by most other notifications. In this commit, I include the logic and data structures necessary for formatting zap notifications in the same fashion as local notifications. A good amount of refactoring and moving functions/structures around was necessary to reuse zap local notification logic. I also attempted to make the notification generation process more consistent between zaps and other notifications, without changing too much of existing logic to avoid even more regression risk. General push notifications + local notifications test ----------------------------------------------------- PASS Device: iPhone 15 Pro simulator iOS: 17.0.1 Damus: This commit Setup: - Two phones running Damus on different accounts - Local relay with strfry-push-notify test setup - Apple push notification test tool Coverage: 1. Mention notifications 2. DM notifications 3. Reaction notifications 4. Repost notifications Steps for each notification type: 1. Trigger a notification (local and then push) 2. Ensure that the notification is received on the other device 3. Ensure that the notification is formatted correctly 4. Ensure that DMs are decrypted correctly 5. Ensure that profile names are unfurled correctly 6. Click on the notification and ensure that the app opens to the correct screen Result: PASS (all notifications received and formatted correctly) Notes: - For some reason my relay is not receiving zap events, so I could not test zap notifications yet. - Reply notifications do not seem to be implemented yet - These apply to the tests below as well Changelog-Added: Zap notification support for push notifications Signed-off-by: Daniel D’Aquino <daniel@daquino.me> Signed-off-by: William Casarin <jb55@jb55.com>
71 lines
2.9 KiB
Swift
71 lines
2.9 KiB
Swift
//
|
||
// NotificationService.swift
|
||
// DamusNotificationService
|
||
//
|
||
// Created by Daniel D’Aquino on 2023-11-10.
|
||
//
|
||
|
||
import UserNotifications
|
||
import Foundation
|
||
|
||
class NotificationService: UNNotificationServiceExtension {
|
||
|
||
var contentHandler: ((UNNotificationContent) -> Void)?
|
||
var bestAttemptContent: UNMutableNotificationContent?
|
||
|
||
override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
|
||
self.contentHandler = contentHandler
|
||
|
||
guard let nostr_event_json = request.content.userInfo["nostr_event"] as? String,
|
||
let nostr_event = NdbNote.owned_from_json(json: nostr_event_json)
|
||
else {
|
||
// No nostr event detected. Just display the original notification
|
||
contentHandler(request.content)
|
||
return;
|
||
}
|
||
|
||
// Log that we got a push notification
|
||
Log.debug("Got nostr event push notification from pubkey %s", for: .push_notifications, nostr_event.pubkey.hex())
|
||
|
||
guard let state = NotificationExtensionState(),
|
||
let display_name = state.ndb.lookup_profile(nostr_event.pubkey).unsafeUnownedValue?.profile?.display_name // We are not holding the txn here.
|
||
else {
|
||
// Something failed to initialize so let's go for the next best thing
|
||
guard let improved_content = NotificationFormatter.shared.format_message(event: nostr_event) else {
|
||
// We cannot format this nostr event. Suppress notification.
|
||
contentHandler(UNNotificationContent())
|
||
return
|
||
}
|
||
contentHandler(improved_content)
|
||
return
|
||
}
|
||
|
||
guard should_display_notification(state: state, event: nostr_event) else {
|
||
// We should not display notification for this event. Suppress notification.
|
||
contentHandler(UNNotificationContent())
|
||
return
|
||
}
|
||
|
||
guard let notification_object = generate_local_notification_object(from: nostr_event, state: state) else {
|
||
// We could not process this notification. Probably an unsupported nostr event kind. Suppress.
|
||
contentHandler(UNNotificationContent())
|
||
return
|
||
}
|
||
|
||
Task {
|
||
if let (improvedContent, _) = await NotificationFormatter.shared.format_message(displayName: display_name, notify: notification_object, state: state) {
|
||
contentHandler(improvedContent)
|
||
}
|
||
}
|
||
}
|
||
|
||
override func serviceExtensionTimeWillExpire() {
|
||
// Called just before the extension will be terminated by the system.
|
||
// Use this as an opportunity to deliver your "best attempt" at modified content, otherwise the original push payload will be used.
|
||
if let contentHandler = contentHandler, let bestAttemptContent = bestAttemptContent {
|
||
contentHandler(bestAttemptContent)
|
||
}
|
||
}
|
||
|
||
}
|