mirror of
https://github.com/permissionlesstech/bitchat.git
synced 2026-08-29 07:27:16 +00:00
fix: route favorite notifications through the MessageRouter outbox
A favorite toggle sent while the peer was offline or mid-handshake was silently dropped — for mesh-only peers a missed npub exchange. Build the same [FAVORITED]:<npub> payload the transports built internally and route it through sendPrivate, so the outbox retains it until an ack and the alias flush drains it on reconnect/authentication under either PeerID form. Wire format unchanged: receivers already parse the prefix from PM content. Co-Authored-By: goose <goose@block.xyz>
This commit is contained in:
parent
a4502046ff
commit
265028c712
@ -48,6 +48,9 @@ final class MessageRouter {
|
||||
private let courierDirectory: CourierDirectory
|
||||
private let outboxStore: MessageOutboxStore?
|
||||
private let metrics: StoreAndForwardMetrics?
|
||||
// Stamps our npub onto favorite notifications so the recipient can learn
|
||||
// our Nostr identity. Optional so tests can omit it.
|
||||
private let idBridge: NostrIdentityBridge?
|
||||
|
||||
/// Invoked whenever a retained private message is dropped without a
|
||||
/// delivery ack (attempt cap, TTL expiry, or per-peer overflow eviction)
|
||||
@ -141,9 +144,11 @@ final class MessageRouter {
|
||||
now: @escaping () -> Date = Date.init,
|
||||
courierDirectory: CourierDirectory? = nil,
|
||||
outboxStore: MessageOutboxStore? = nil,
|
||||
metrics: StoreAndForwardMetrics? = nil
|
||||
metrics: StoreAndForwardMetrics? = nil,
|
||||
idBridge: NostrIdentityBridge? = nil
|
||||
) {
|
||||
self.transports = transports
|
||||
self.idBridge = idBridge
|
||||
self.now = now
|
||||
self.courierDirectory = courierDirectory ?? .favoritesBacked()
|
||||
self.outboxStore = outboxStore
|
||||
@ -572,11 +577,19 @@ final class MessageRouter {
|
||||
}
|
||||
|
||||
func sendFavoriteNotification(to peerID: PeerID, isFavorite: Bool) {
|
||||
if let transport = connectedTransport(for: peerID) {
|
||||
transport.sendFavoriteNotification(to: peerID, isFavorite: isFavorite)
|
||||
} else if let transport = reachableTransport(for: peerID) {
|
||||
transport.sendFavoriteNotification(to: peerID, isFavorite: isFavorite)
|
||||
// Route favorites through the outbox instead of fire-and-forget: a
|
||||
// toggle sent while the peer is offline or mid-handshake used to be
|
||||
// silently dropped, which for mesh-only peers meant a missed npub
|
||||
// exchange. The outbox retains until an ack, and the alias flush
|
||||
// drains the queue on reconnect/authentication no matter which PeerID
|
||||
// form the toggle was keyed under. The payload is the same
|
||||
// [FAVORITED]:<npub> content the transports built internally — the
|
||||
// receiver parses the prefix from PM content and never displays it.
|
||||
var content = isFavorite ? "[FAVORITED]" : "[UNFAVORITED]"
|
||||
if let identity = try? idBridge?.getCurrentNostrIdentity() {
|
||||
content += ":" + identity.npub
|
||||
}
|
||||
sendPrivate(content, to: peerID, recipientNickname: "", messageID: UUID().uuidString)
|
||||
}
|
||||
|
||||
/// Retries only messages that the router previously transmitted through
|
||||
|
||||
@ -33,7 +33,8 @@ struct ChatViewModelServiceBundle {
|
||||
let messageRouter = MessageRouter(
|
||||
transports: [meshService, nostrTransport],
|
||||
outboxStore: outboxStore,
|
||||
metrics: sfMetrics
|
||||
metrics: sfMetrics,
|
||||
idBridge: idBridge
|
||||
)
|
||||
|
||||
self.commandProcessor = commandProcessor
|
||||
|
||||
@ -653,7 +653,7 @@ struct MessageRouterTests {
|
||||
}
|
||||
|
||||
@Test @MainActor
|
||||
func sendFavoriteNotification_usesConnectedOrReachable() async {
|
||||
func sendFavoriteNotification_routesThroughOutboxAsPrivateMessage() async {
|
||||
let peerID = PeerID(str: "0000000000000004")
|
||||
let transport = MockTransport()
|
||||
transport.reachablePeers.insert(peerID)
|
||||
@ -661,7 +661,28 @@ struct MessageRouterTests {
|
||||
let router = MessageRouter(transports: [transport])
|
||||
router.sendFavoriteNotification(to: peerID, isFavorite: true)
|
||||
|
||||
#expect(transport.sentFavoriteNotifications.count == 1)
|
||||
// Favorites ride the outbox (sendPrivate) so they survive offline /
|
||||
// handshake gaps; the recipient parses the [FAVORITED] prefix.
|
||||
#expect(transport.sentFavoriteNotifications.isEmpty)
|
||||
#expect(transport.sentPrivateMessages.count == 1)
|
||||
#expect(transport.sentPrivateMessages.first?.content.hasPrefix("[FAVORITED]") == true)
|
||||
}
|
||||
|
||||
@Test @MainActor
|
||||
func sendFavoriteNotification_whileOffline_queuesAndFlushesOnReconnect() async {
|
||||
let peerID = PeerID(str: "0000000000000009")
|
||||
let transport = MockTransport()
|
||||
// Peer is neither connected nor reachable: the favorite must be retained.
|
||||
let router = MessageRouter(transports: [transport])
|
||||
router.sendFavoriteNotification(to: peerID, isFavorite: false)
|
||||
#expect(transport.sentPrivateMessages.isEmpty)
|
||||
|
||||
// Peer comes back: the queued favorite flushes.
|
||||
transport.connectedPeers.insert(peerID)
|
||||
router.flushOutbox(for: peerID)
|
||||
|
||||
#expect(transport.sentPrivateMessages.count == 1)
|
||||
#expect(transport.sentPrivateMessages.first?.content.hasPrefix("[UNFAVORITED]") == true)
|
||||
}
|
||||
|
||||
// MARK: - Courier deposits
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user