Fix NIP-05 timeline crash

Signed-off-by: Daniel D’Aquino <daniel@daquino.me>
This commit is contained in:
Daniel D’Aquino
2025-09-01 12:13:52 -07:00
parent 0f26d50e08
commit de70d19135
11 changed files with 83 additions and 51 deletions

View File

@@ -67,40 +67,40 @@ class NotificationService: UNNotificationServiceExtension {
nip05: profile?.nip05) nip05: profile?.nip05)
}() }()
let sender_pubkey = nostr_event.pubkey let sender_pubkey = nostr_event.pubkey
// Don't show notification details that match mute list.
// TODO: Remove this code block once we get notification suppression entitlement from Apple. It will be covered by the `guard should_display_notification` block
if state.mutelist_manager.is_event_muted(nostr_event) {
// We cannot really suppress muted notifications until we have the notification supression entitlement.
// The best we can do if we ever get those muted notifications (which we generally won't due to server-side processing) is to obscure the details
let content = UNMutableNotificationContent()
content.title = NSLocalizedString("Muted event", comment: "Title for a push notification which has been muted")
content.body = NSLocalizedString("This is an event that has been muted according to your mute list rules. We cannot suppress this notification, but we obscured the details to respect your preferences", comment: "Description for a push notification which has been muted, and explanation that we cannot suppress it")
content.sound = UNNotificationSound.default
contentHandler(content)
return
}
guard should_display_notification(state: state, event: nostr_event, mode: .push) else {
Log.debug("should_display_notification failed", for: .push_notifications)
// We should not display notification for this event. Suppress notification.
// contentHandler(UNNotificationContent())
// TODO: We cannot really suppress until we have the notification supression entitlement. Show the raw notification
contentHandler(request.content)
return
}
guard let notification_object = generate_local_notification_object(ndb: state.ndb, from: nostr_event, state: state) else {
Log.debug("generate_local_notification_object failed", for: .push_notifications)
// We could not process this notification. Probably an unsupported nostr event kind. Suppress.
// contentHandler(UNNotificationContent())
// TODO: We cannot really suppress until we have the notification supression entitlement. Show the raw notification
contentHandler(request.content)
return
}
Task { Task {
// Don't show notification details that match mute list.
// TODO: Remove this code block once we get notification suppression entitlement from Apple. It will be covered by the `guard should_display_notification` block
if await state.mutelist_manager.is_event_muted(nostr_event) {
// We cannot really suppress muted notifications until we have the notification supression entitlement.
// The best we can do if we ever get those muted notifications (which we generally won't due to server-side processing) is to obscure the details
let content = UNMutableNotificationContent()
content.title = NSLocalizedString("Muted event", comment: "Title for a push notification which has been muted")
content.body = NSLocalizedString("This is an event that has been muted according to your mute list rules. We cannot suppress this notification, but we obscured the details to respect your preferences", comment: "Description for a push notification which has been muted, and explanation that we cannot suppress it")
content.sound = UNNotificationSound.default
contentHandler(content)
return
}
guard await should_display_notification(state: state, event: nostr_event, mode: .push) else {
Log.debug("should_display_notification failed", for: .push_notifications)
// We should not display notification for this event. Suppress notification.
// contentHandler(UNNotificationContent())
// TODO: We cannot really suppress until we have the notification supression entitlement. Show the raw notification
contentHandler(request.content)
return
}
guard let notification_object = generate_local_notification_object(ndb: state.ndb, from: nostr_event, state: state) else {
Log.debug("generate_local_notification_object failed", for: .push_notifications)
// We could not process this notification. Probably an unsupported nostr event kind. Suppress.
// contentHandler(UNNotificationContent())
// TODO: We cannot really suppress until we have the notification supression entitlement. Show the raw notification
contentHandler(request.content)
return
}
let sender_dn = DisplayName(name: sender_profile.name, display_name: sender_profile.display_name, pubkey: sender_pubkey) let sender_dn = DisplayName(name: sender_profile.name, display_name: sender_profile.display_name, pubkey: sender_pubkey)
guard let (improvedContent, _) = await NotificationFormatter.shared.format_message(displayName: sender_dn.displayName, notify: notification_object, state: state) else { guard let (improvedContent, _) = await NotificationFormatter.shared.format_message(displayName: sender_dn.displayName, notify: notification_object, state: state) else {

View File

@@ -15,11 +15,17 @@ struct ReplyQuoteView: View {
@ObservedObject var thread: ThreadModel @ObservedObject var thread: ThreadModel
let options: EventViewOptions let options: EventViewOptions
@State var can_show_event = true
func update_should_show_event(event: NdbNote) async {
self.can_show_event = await should_show_event(event: event, damus_state: state)
}
func content(event: NdbNote) -> some View { func content(event: NdbNote) -> some View {
ZStack(alignment: .leading) { ZStack(alignment: .leading) {
VStack(alignment: .leading) { VStack(alignment: .leading) {
HStack(alignment: .center) { HStack(alignment: .center) {
if should_show_event(event: event, damus_state: state) { if can_show_event {
ProfilePicView(pubkey: event.pubkey, size: 14, highlight: .reply, profiles: state.profiles, disable_animation: false) ProfilePicView(pubkey: event.pubkey, size: 14, highlight: .reply, profiles: state.profiles, disable_animation: false)
let blur_images = should_blur_images(settings: state.settings, contacts: state.contacts, ev: event, our_pubkey: state.pubkey) let blur_images = should_blur_images(settings: state.settings, contacts: state.contacts, ev: event, our_pubkey: state.pubkey)
NoteContentView(damus_state: state, event: event, blur_images: blur_images, size: .small, options: options) NoteContentView(damus_state: state, event: event, blur_images: blur_images, size: .small, options: options)
@@ -56,6 +62,9 @@ struct ReplyQuoteView: View {
Group { Group {
if let event = state.events.lookup(event_id) { if let event = state.events.lookup(event_id) {
self.content(event: event) self.content(event: event)
.onAppear {
Task { await self.update_should_show_event(event: event) }
}
} }
} }
} }

View File

@@ -51,7 +51,8 @@ class FollowPackModel: ObservableObject {
event = ev.toOwned() event = ev.toOwned()
} }
guard let event else { return } guard let event else { return }
if event.is_textlike && should_show_event(state: damus_state, ev: event) && !event.is_reply() let should_show_event = await should_show_event(state: damus_state, ev: event)
if event.is_textlike && should_show_event && !event.is_reply()
{ {
if await self.events.insert(event) { if await self.events.insert(event) {
DispatchQueue.main.async { DispatchQueue.main.async {

View File

@@ -23,6 +23,7 @@ func loadOldMutedThreads(pubkey: Pubkey) -> [NoteId] {
// We need to still use it since existing users might have their muted threads stored in UserDefaults // We need to still use it since existing users might have their muted threads stored in UserDefaults
// So now all it's doing is moving a users muted threads to the new kind:10000 system // So now all it's doing is moving a users muted threads to the new kind:10000 system
// It should not be used for any purpose beyond that // It should not be used for any purpose beyond that
@MainActor
func migrate_old_muted_threads_to_new_mutelist(keypair: Keypair, damus_state: DamusState) { func migrate_old_muted_threads_to_new_mutelist(keypair: Keypair, damus_state: DamusState) {
// Ensure that keypair is fullkeypair // Ensure that keypair is fullkeypair
guard let fullKeypair = keypair.to_full() else { return } guard let fullKeypair = keypair.to_full() else { return }

View File

@@ -7,6 +7,7 @@
import Foundation import Foundation
@MainActor
class MutelistManager { class MutelistManager {
let user_keypair: Keypair let user_keypair: Keypair
private(set) var event: NostrEvent? = nil private(set) var event: NostrEvent? = nil
@@ -26,7 +27,7 @@ class MutelistManager {
var muted_notes_cache: [NoteId: EventMuteStatus] = [:] var muted_notes_cache: [NoteId: EventMuteStatus] = [:]
init(user_keypair: Keypair) { nonisolated init(user_keypair: Keypair) {
self.user_keypair = user_keypair self.user_keypair = user_keypair
} }

View File

@@ -76,6 +76,7 @@ class NIP05DomainEventsModel: ObservableObject {
guard let event else { return } guard let event else { return }
await self.add_event(event) await self.add_event(event)
case .eose: case .eose:
DispatchQueue.main.async { self.loading = false }
continue continue
} }
} }
@@ -86,7 +87,7 @@ class NIP05DomainEventsModel: ObservableObject {
return return
} }
guard should_show_event(state: state, ev: ev) else { guard await should_show_event(state: state, ev: ev) else {
return return
} }

View File

@@ -12,8 +12,8 @@ import UIKit
let EVENT_MAX_AGE_FOR_NOTIFICATION: TimeInterval = 12 * 60 * 60 let EVENT_MAX_AGE_FOR_NOTIFICATION: TimeInterval = 12 * 60 * 60
func process_local_notification(state: HeadlessDamusState, event ev: NostrEvent) { func process_local_notification(state: HeadlessDamusState, event ev: NostrEvent) async {
guard should_display_notification(state: state, event: ev, mode: .local) else { guard await should_display_notification(state: state, event: ev, mode: .local) else {
// We should not display notification. Exit. // We should not display notification. Exit.
return return
} }
@@ -25,7 +25,7 @@ func process_local_notification(state: HeadlessDamusState, event ev: NostrEvent)
create_local_notification(profiles: state.profiles, notify: local_notification) create_local_notification(profiles: state.profiles, notify: local_notification)
} }
func should_display_notification(state: HeadlessDamusState, event ev: NostrEvent, mode: UserSettingsStore.NotificationsMode) -> Bool { func should_display_notification(state: HeadlessDamusState, event ev: NostrEvent, mode: UserSettingsStore.NotificationsMode) async -> Bool {
// Do not show notification if it's coming from a mode different from the one selected by our user // Do not show notification if it's coming from a mode different from the one selected by our user
guard state.settings.notification_mode == mode else { guard state.settings.notification_mode == mode else {
return false return false
@@ -46,7 +46,7 @@ func should_display_notification(state: HeadlessDamusState, event ev: NostrEvent
} }
// Don't show notifications that match mute list. // Don't show notifications that match mute list.
if state.mutelist_manager.is_event_muted(ev) { if await state.mutelist_manager.is_event_muted(ev) {
return false return false
} }

View File

@@ -34,6 +34,7 @@ class SearchHomeModel: ObservableObject {
return filter return filter
} }
@MainActor
func filter_muted() { func filter_muted() {
events.filter { should_show_event(state: damus_state, ev: $0) } events.filter { should_show_event(state: damus_state, ev: $0) }
self.objectWillChange.send() self.objectWillChange.send()

View File

@@ -26,6 +26,7 @@ class SearchModel: ObservableObject {
}) })
} }
@MainActor
func filter_muted() { func filter_muted() {
self.events.filter { self.events.filter {
should_show_event(state: state, ev: $0) should_show_event(state: state, ev: $0)

View File

@@ -34,6 +34,7 @@ func nsfw_tag_filter(ev: NostrEvent) -> Bool {
return ev.referenced_hashtags.first(where: { t in t.hashtag.caseInsensitiveCompare("nsfw") == .orderedSame }) == nil return ev.referenced_hashtags.first(where: { t in t.hashtag.caseInsensitiveCompare("nsfw") == .orderedSame }) == nil
} }
@MainActor
func get_repost_of_muted_user_filter(damus_state: DamusState) -> ((_ ev: NostrEvent) -> Bool) { func get_repost_of_muted_user_filter(damus_state: DamusState) -> ((_ ev: NostrEvent) -> Bool) {
return { ev in return { ev in
guard ev.known_kind == .boost else { return true } guard ev.known_kind == .boost else { return true }
@@ -65,10 +66,12 @@ struct ContentFilters {
} }
extension ContentFilters { extension ContentFilters {
@MainActor
static func default_filters(damus_state: DamusState) -> ContentFilters { static func default_filters(damus_state: DamusState) -> ContentFilters {
return ContentFilters(filters: ContentFilters.defaults(damus_state: damus_state)) return ContentFilters(filters: ContentFilters.defaults(damus_state: damus_state))
} }
@MainActor
static func defaults(damus_state: DamusState) -> [(NostrEvent) -> Bool] { static func defaults(damus_state: DamusState) -> [(NostrEvent) -> Bool] {
var filters = Array<(NostrEvent) -> Bool>() var filters = Array<(NostrEvent) -> Bool>()
if damus_state.settings.hide_nsfw_tagged_content { if damus_state.settings.hide_nsfw_tagged_content {

View File

@@ -84,7 +84,9 @@ class HomeModel: ContactsDelegate {
init() { init() {
self.damus_state = DamusState.empty self.damus_state = DamusState.empty
self.setup_debouncer() self.setup_debouncer()
filter_events() DispatchQueue.main.async {
self.filter_events()
}
events.on_queue = preloader events.on_queue = preloader
//self.events = EventHolder(on_queue: preloader) //self.events = EventHolder(on_queue: preloader)
} }
@@ -353,6 +355,7 @@ class HomeModel: ContactsDelegate {
} }
} }
@MainActor
func filter_events() { func filter_events() {
events.filter { ev in events.filter { ev in
!damus_state.mutelist_manager.is_muted(.user(ev.pubkey, nil)) !damus_state.mutelist_manager.is_muted(.user(ev.pubkey, nil))
@@ -422,6 +425,7 @@ class HomeModel: ContactsDelegate {
} }
} }
@MainActor
func handle_like_event(_ ev: NostrEvent) { func handle_like_event(_ ev: NostrEvent) {
guard let e = ev.last_refid() else { guard let e = ev.last_refid() else {
// no id ref? invalid like event // no id ref? invalid like event
@@ -682,6 +686,7 @@ class HomeModel: ContactsDelegate {
case nwc case nwc
} }
@MainActor
func handle_mute_list_event(_ ev: NostrEvent) { func handle_mute_list_event(_ ev: NostrEvent) {
// we only care about our mutelist // we only care about our mutelist
guard ev.pubkey == damus_state.pubkey else { guard ev.pubkey == damus_state.pubkey else {
@@ -700,6 +705,7 @@ class HomeModel: ContactsDelegate {
migrate_old_muted_threads_to_new_mutelist(keypair: damus_state.keypair, damus_state: damus_state) migrate_old_muted_threads_to_new_mutelist(keypair: damus_state.keypair, damus_state: damus_state)
} }
@MainActor
func handle_old_list_event(_ ev: NostrEvent) { func handle_old_list_event(_ ev: NostrEvent) {
// we only care about our lists // we only care about our lists
guard ev.pubkey == damus_state.pubkey else { guard ev.pubkey == damus_state.pubkey else {
@@ -731,6 +737,7 @@ class HomeModel: ContactsDelegate {
return m[kind] return m[kind]
} }
@MainActor
func handle_notification(ev: NostrEvent) { func handle_notification(ev: NostrEvent) {
// don't show notifications from ourselves // don't show notifications from ourselves
guard ev.pubkey != damus_state.pubkey, guard ev.pubkey != damus_state.pubkey,
@@ -750,7 +757,7 @@ class HomeModel: ContactsDelegate {
} }
if handle_last_event(ev: ev, timeline: .notifications) { if handle_last_event(ev: ev, timeline: .notifications) {
process_local_notification(state: damus_state, event: ev) Task { await process_local_notification(state: damus_state, event: ev) }
} }
} }
@@ -773,6 +780,7 @@ class HomeModel: ContactsDelegate {
} }
@MainActor
func handle_text_event(_ ev: NostrEvent, context: SubscriptionContext) { func handle_text_event(_ ev: NostrEvent, context: SubscriptionContext) {
guard should_show_event(state: damus_state, ev: ev) else { guard should_show_event(state: damus_state, ev: ev) else {
return return
@@ -808,17 +816,21 @@ class HomeModel: ContactsDelegate {
} }
func got_new_dm(notifs: NewEventsBits, ev: NostrEvent) { func got_new_dm(notifs: NewEventsBits, ev: NostrEvent) {
notification_status.new_events = notifs Task {
notification_status.new_events = notifs
guard should_display_notification(state: damus_state, event: ev, mode: .local),
let notification_object = generate_local_notification_object(ndb: self.damus_state.ndb, from: ev, state: damus_state)
else { guard await should_display_notification(state: damus_state, event: ev, mode: .local),
return let notification_object = generate_local_notification_object(ndb: self.damus_state.ndb, from: ev, state: damus_state)
else {
return
}
create_local_notification(profiles: damus_state.profiles, notify: notification_object)
} }
create_local_notification(profiles: damus_state.profiles, notify: notification_object)
} }
@MainActor
func handle_dm(_ ev: NostrEvent) { func handle_dm(_ ev: NostrEvent) {
guard should_show_event(state: damus_state, ev: ev) else { guard should_show_event(state: damus_state, ev: ev) else {
return return
@@ -1150,6 +1162,7 @@ func event_has_our_pubkey(_ ev: NostrEvent, our_pubkey: Pubkey) -> Bool {
return ev.referenced_pubkeys.contains(our_pubkey) return ev.referenced_pubkeys.contains(our_pubkey)
} }
@MainActor
func should_show_event(event: NostrEvent, damus_state: DamusState) -> Bool { func should_show_event(event: NostrEvent, damus_state: DamusState) -> Bool {
return should_show_event( return should_show_event(
state: damus_state, state: damus_state,
@@ -1157,6 +1170,7 @@ func should_show_event(event: NostrEvent, damus_state: DamusState) -> Bool {
) )
} }
@MainActor
func should_show_event(state: DamusState, ev: NostrEvent) -> Bool { func should_show_event(state: DamusState, ev: NostrEvent) -> Bool {
let event_muted = state.mutelist_manager.is_event_muted(ev) let event_muted = state.mutelist_manager.is_event_muted(ev)
if event_muted { if event_muted {