mirror of
https://github.com/block/buzz.git
synced 2026-10-27 12:40:30 +00:00
fix(mobile): select the last valid push reply marker
Signed-off-by: Tom Brow <tomb@block.xyz>
This commit is contained in:
@@ -37,11 +37,15 @@ public struct BuzzPushNavigationTarget: Codable, Equatable, Sendable {
|
|||||||
if tags.contains(where: { $0.count >= 2 && $0[0] == "broadcast" && $0[1] == "1" }) {
|
if tags.contains(where: { $0.count >= 2 && $0[0] == "broadcast" && $0[1] == "1" }) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
let parent = tags.last(where: { $0.count >= 4 && $0[0] == "e" && $0[3] == "reply" })?[1]
|
// Match relay ingest: the last valid 64-character ASCII-hex reply ID wins.
|
||||||
// Relay ingest ignores malformed reply IDs. An empty marker must not
|
// A later malformed marker must not hide an earlier valid parent.
|
||||||
// invalidate the otherwise usable channel/event navigation payload.
|
return tags.last(where: { tag in
|
||||||
guard let parent, !parent.isEmpty else { return nil }
|
guard tag.count >= 4, tag[0] == "e", tag[3] == "reply" else { return false }
|
||||||
return parent
|
let bytes = tag[1].utf8
|
||||||
|
return bytes.count == 64 && bytes.allSatisfy {
|
||||||
|
(48...57).contains($0) || (65...70).contains($0) || (97...102).contains($0)
|
||||||
|
}
|
||||||
|
})?[1]
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Decodes a target without trusting other fields from the APNs payload.
|
/// Decodes a target without trusting other fields from the APNs payload.
|
||||||
|
|||||||
@@ -116,8 +116,24 @@ import Testing
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test func `Thread destination keeps direct parent and broadcast semantics`() {
|
@Test func `Thread destination keeps direct parent and broadcast semantics`() {
|
||||||
let tags = [["e", "outer-root", "", "root"], ["e", "direct-parent", "", "reply"]]
|
let parent = String(repeating: "a", count: 64)
|
||||||
#expect(BuzzPushNavigationTarget.threadRootID(in: tags) == "direct-parent")
|
let tags = [["e", "outer-root", "", "root"], ["e", parent, "", "reply"]]
|
||||||
|
#expect(BuzzPushNavigationTarget.threadRootID(in: tags) == parent)
|
||||||
#expect(BuzzPushNavigationTarget.threadRootID(in: tags + [["broadcast", "1"]]) == nil)
|
#expect(BuzzPushNavigationTarget.threadRootID(in: tags + [["broadcast", "1"]]) == nil)
|
||||||
#expect(BuzzPushNavigationTarget.threadRootID(in: [["e", "outer-root", "", "root"]]) == nil)
|
#expect(BuzzPushNavigationTarget.threadRootID(in: [["e", "outer-root", "", "root"]]) == nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test func `Last valid reply marker wins despite trailing malformed markers`() {
|
||||||
|
let first = String(repeating: "a", count: 64)
|
||||||
|
let last = String(repeating: "B", count: 64)
|
||||||
|
let invalidIDs = ["", "bad", String(repeating: "a", count: 63),
|
||||||
|
String(repeating: "a", count: 65), String(repeating: "g", count: 64),
|
||||||
|
String(repeating: "é", count: 64)]
|
||||||
|
for invalid in invalidIDs {
|
||||||
|
let invalidTag = ["e", invalid, "", "reply"]
|
||||||
|
#expect(BuzzPushNavigationTarget.threadRootID(in: [invalidTag]) == nil)
|
||||||
|
#expect(BuzzPushNavigationTarget.threadRootID(in: [
|
||||||
|
["e", first, "", "reply"], ["e", last, "", "reply"], invalidTag,
|
||||||
|
]) == last)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -163,17 +163,20 @@ final class BuzzPushNotificationResolverTests: XCTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func testReplyNavigationUsesDirectParentAndKeepsExactEvent() throws {
|
func testReplyNavigationUsesDirectParentAndKeepsExactEvent() throws {
|
||||||
|
let parent = String(repeating: "a", count: 64)
|
||||||
let reply = event(id: "reply-id", content: "Reply preview", tags: [
|
let reply = event(id: "reply-id", content: "Reply preview", tags: [
|
||||||
["h", Self.channelID],
|
["h", Self.channelID],
|
||||||
["e", "outer-root", "", "root"],
|
["e", "outer-root", "", "root"],
|
||||||
["e", "direct-parent", "", "reply"],
|
["e", parent, "", "reply"],
|
||||||
|
["e", "bad", "", "reply"],
|
||||||
|
["e", "", "", "reply"],
|
||||||
])
|
])
|
||||||
let resolution = try XCTUnwrap(BuzzPushNotificationResolver.decodeResolution(
|
let resolution = try XCTUnwrap(BuzzPushNotificationResolver.decodeResolution(
|
||||||
events: [reply], community: community()
|
events: [reply], community: community()
|
||||||
)?.0)
|
)?.0)
|
||||||
let target = try XCTUnwrap(resolution.navigationTarget)
|
let target = try XCTUnwrap(resolution.navigationTarget)
|
||||||
XCTAssertEqual(target.eventID, "reply-id")
|
XCTAssertEqual(target.eventID, "reply-id")
|
||||||
XCTAssertEqual(target.userInfoValue["thread_root_id"], "direct-parent")
|
XCTAssertEqual(target.userInfoValue["thread_root_id"], parent)
|
||||||
}
|
}
|
||||||
|
|
||||||
func testNavigationKeepsTopLevelAndBroadcastMessagesInChannel() throws {
|
func testNavigationKeepsTopLevelAndBroadcastMessagesInChannel() throws {
|
||||||
|
|||||||
Reference in New Issue
Block a user