Fix broken DM rendering

Currently NostrDB does not seem to handle encryption/decryption of DMs.
Since NostrDB now controls the block parsing process and fetches note
contents directly from the database, we have to add a specific condition
that injects decrypted content directly to the ndb content parser.

This is done in conjunction with some minor refactoring to `NdbBlocks`
and associated structs, as in C those are separated between the content
string and the offsets for each block, but in Swift this is more
ergonomically represented as a standalone/self-containing object.

No changelog entry is added because the previously broken version was
never released to the public, and therefore this fix produces no
user-facing changes compared to the last released version.

Changelog-None
Closes: https://github.com/damus-io/damus/issues/3106
Signed-off-by: Daniel D’Aquino <daniel@daquino.me>
This commit is contained in:
Daniel D’Aquino
2025-08-11 16:40:01 -07:00
parent 9c47d2e0bd
commit caa7802bce
11 changed files with 350 additions and 129 deletions
+15 -9
View File
@@ -67,22 +67,28 @@ func note_artifact_is_separated(kind: NostrKind?) -> Bool {
}
func render_immediately_available_note_content(ndb: Ndb, ev: NostrEvent, profiles: Profiles, keypair: Keypair) -> NoteArtifacts {
guard let blocks = ev.blocks(ndb: ndb) else {
return .separated(.just_content(ev.get_content(keypair)))
}
if ev.known_kind == .longform {
return .longform(LongformContent(ev.content))
}
return .separated(render_blocks(blocks: blocks.unsafeUnownedValue, profiles: profiles, note: ev, can_hide_last_previewable_refs: true))
do {
let blocks = try NdbBlockGroup.from(event: ev, using: ndb, and: keypair)
return .separated(render_blocks(blocks: blocks, profiles: profiles, can_hide_last_previewable_refs: true))
}
catch {
// TODO: Improve error handling in the future, bubbling it up so that the view can decide how display errors. Keep legacy behavior for now.
return .separated(.just_content(ev.get_content(keypair)))
}
}
actor ContentRenderer {
func render_note_content(ndb: Ndb, ev: NostrEvent, profiles: Profiles, keypair: Keypair) async -> NoteArtifacts {
guard let result = try? await ndb.waitFor(noteId: ev.id, timeout: 10) else {
return .separated(.just_content(ev.get_content(keypair)))
if ev.known_kind == .dm {
// Use the enhanced render_immediately_available_note_content which now handles DMs properly
// by decrypting and parsing the content with ndb_parse_content
return render_immediately_available_note_content(ndb: ndb, ev: ev, profiles: profiles, keypair: keypair)
}
let result = try? await ndb.waitFor(noteId: ev.id, timeout: 3)
return render_immediately_available_note_content(ndb: ndb, ev: ev, profiles: profiles, keypair: keypair)
}
}
@@ -92,14 +98,14 @@ actor ContentRenderer {
// Block previews should actually be rendered in the position of the note content where it was found.
// Currently, we put some previews at the bottom of the note, which is incorrect as they take things out of
// the author's intended context.
func render_blocks(blocks: NdbBlocks, profiles: Profiles, note: NdbNote, can_hide_last_previewable_refs: Bool = false) -> NoteArtifactsSeparated {
func render_blocks(blocks: borrowing NdbBlockGroup, profiles: Profiles, can_hide_last_previewable_refs: Bool = false) -> NoteArtifactsSeparated {
var invoices: [Invoice] = []
var urls: [UrlType] = []
var end_mention_count = 0
var end_url_count = 0
let ndb_blocks = blocks.iter(note: note).collect()
let ndb_blocks = blocks.blocks
let one_note_ref = ndb_blocks
.filter({
if case .mention(let mention) = $0,
+4 -6
View File
@@ -350,11 +350,10 @@ struct NoteContentView: View {
var body: some View {
ArtifactContent
.onReceive(handle_notify(.profile_updated)) { profile in
guard let blocks_txn = event.blocks(ndb: damus_state.ndb) else {
guard let blockGroup = try? NdbBlockGroup.from(event: event, using: damus_state.ndb, and: damus_state.keypair) else {
return
}
let blocks = blocks_txn.unsafeUnownedValue
for block in blocks.iter(note: event) {
for block in blockGroup.blocks {
switch block {
case .mention(let m):
guard let typ = m.bech32_type else {
@@ -536,11 +535,10 @@ struct NoteContentView_Previews: PreviewProvider {
}
func separate_images(ndb: Ndb, ev: NostrEvent, keypair: Keypair) -> [MediaUrl]? {
guard let blocks_txn = ev.blocks(ndb: ndb) else {
guard let blockGroup = try? NdbBlockGroup.from(event: ev, using: ndb, and: keypair) else {
return nil
}
let blocks = blocks_txn.unsafeUnownedValue
let urlBlocks: [URL] = blocks.iter(note: ev).reduce(into: []) { urls, block in
let urlBlocks: [URL] = blockGroup.blocks.reduce(into: []) { urls, block in
guard case .url(let url) = block,
let parsed_url = URL(string: url.as_str()) else {
return
@@ -72,9 +72,9 @@ func generate_local_notification_object(ndb: Ndb, from ev: NostrEvent, state: He
if type == .text,
state.settings.mention_notification,
let blocks = ev.blocks(ndb: ndb)?.unsafeUnownedValue
let blockGroup = try? NdbBlockGroup.from(event: ev, using: ndb, and: state.keypair)
{
for case .mention(let mention) in blocks.iter(note: ev) {
for case .mention(let mention) in blockGroup.blocks {
guard case .npub = mention.bech32_type,
(memcmp(state.keypair.pubkey.id.bytes, mention.bech32.npub.pubkey, 32) == 0) else {
continue