mirror of
https://github.com/block/buzz.git
synced 2026-11-03 07:51:22 +00:00
fix(ios): preserve navigation for empty reply markers
Signed-off-by: Tom Brow <tomb@block.xyz>
This commit is contained in:
@@ -31,6 +31,19 @@ public struct BuzzPushNavigationTarget: Codable, Equatable, Sendable {
|
||||
return value
|
||||
}
|
||||
|
||||
static func threadRootID(in tags: [[String]]) -> String? {
|
||||
// Inbox opens broadcast replies in the channel and other replies in their
|
||||
// direct parent's thread, even when the NIP-10 root names an outer thread.
|
||||
if tags.contains(where: { $0.count >= 2 && $0[0] == "broadcast" && $0[1] == "1" }) {
|
||||
return nil
|
||||
}
|
||||
let parent = tags.last(where: { $0.count >= 4 && $0[0] == "e" && $0[3] == "reply" })?[1]
|
||||
// Relay ingest ignores malformed reply IDs. An empty marker must not
|
||||
// invalidate the otherwise usable channel/event navigation payload.
|
||||
guard let parent, !parent.isEmpty else { return nil }
|
||||
return parent
|
||||
}
|
||||
|
||||
/// Decodes a target without trusting other fields from the APNs payload.
|
||||
public static func decodeIfPresent(
|
||||
from userInfo: [AnyHashable: Any]
|
||||
|
||||
@@ -463,7 +463,7 @@ public final class BuzzPushNotificationResolver: BuzzPushNotificationResolving {
|
||||
eventID: event.id,
|
||||
communityID: community.id,
|
||||
channelID: $0,
|
||||
threadRootID: navigationThreadRootID(event)
|
||||
threadRootID: BuzzPushNavigationTarget.threadRootID(in: event.tags)
|
||||
)
|
||||
},
|
||||
senderPubkey: event.pubkey.lowercased(),
|
||||
@@ -474,15 +474,6 @@ public final class BuzzPushNotificationResolver: BuzzPushNotificationResolving {
|
||||
)
|
||||
}
|
||||
|
||||
private static func navigationThreadRootID(_ event: VerifiedNostrEvent) -> String? {
|
||||
// Inbox opens broadcast replies in the channel and other replies in their
|
||||
// direct parent's thread, even when the NIP-10 root names an outer thread.
|
||||
if event.tags.contains(where: { $0.count >= 2 && $0[0] == "broadcast" && $0[1] == "1" }) {
|
||||
return nil
|
||||
}
|
||||
return event.tags.last(where: { $0.count >= 4 && $0[0] == "e" && $0[3] == "reply" })?[1]
|
||||
}
|
||||
|
||||
private static func ephemeralProfile(
|
||||
event: VerifiedNostrEvent,
|
||||
communityID: String,
|
||||
|
||||
@@ -103,3 +103,21 @@ import Testing
|
||||
) == nil)
|
||||
}
|
||||
}
|
||||
|
||||
@Test func `Empty reply marker preserves channel navigation`() {
|
||||
let thread = BuzzPushNavigationTarget.threadRootID(in: [["e", "", "", "reply"]])
|
||||
#expect(thread == nil)
|
||||
let target = BuzzPushNavigationTarget(
|
||||
eventID: "message", communityID: "community", channelID: "channel", threadRootID: thread
|
||||
)
|
||||
#expect(BuzzPushNavigationTarget.decodeIfPresent(
|
||||
from: [BuzzPushNavigationTarget.userInfoKey: target.userInfoValue]
|
||||
) == target)
|
||||
}
|
||||
|
||||
@Test func `Thread destination keeps direct parent and broadcast semantics`() {
|
||||
let tags = [["e", "outer-root", "", "root"], ["e", "direct-parent", "", "reply"]]
|
||||
#expect(BuzzPushNavigationTarget.threadRootID(in: tags) == "direct-parent")
|
||||
#expect(BuzzPushNavigationTarget.threadRootID(in: tags + [["broadcast", "1"]]) == nil)
|
||||
#expect(BuzzPushNavigationTarget.threadRootID(in: [["e", "outer-root", "", "root"]]) == nil)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user