mirror of
https://github.com/permissionlesstech/bitchat.git
synced 2026-07-26 01:45:18 +00:00
Inject notification/favorites singletons through coordinator contexts
NotificationService.shared and FavoritesPersistenceService.shared accesses across nine coordinators/components consolidate into ChatViewModel witnesses behind intent-named context members (notifyPrivateMessage, notifyMention, favoriteRelationship(forNoiseKey:), allFavoriteRelationships, postLocalNotification, ...). Singleton reach from coordinators is now zero; nine new tests cover previously untestable notification/favorites flows. Also root-causes the long-flaky gift-wrap dedup test: parallel tests share LocationChannelManager.shared, so channel-switch tests trigger clearProcessedNostrEvents() on every live ChatViewModel, wiping the dedup record mid-test. The invariant (forged-signature copies never poison dedup) now lives as a deterministic NostrInboundPipeline mock-context test; two Schnorr concurrency probes added along the way stay as regression guards for the P256K shared-context assumptions. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -20,12 +20,23 @@ protocol ChatNostrContext: GeohashSubscriptionContext, NostrInboundPipelineConte
|
||||
func routeFavoriteNotification(to peerID: PeerID, isFavorite: Bool)
|
||||
func sendGeohashDeliveryAck(for messageID: String, toRecipientHex recipientHex: String, from identity: NostrIdentity)
|
||||
func sendGeohashReadReceipt(_ messageID: String, toRecipientHex recipientHex: String, from identity: NostrIdentity)
|
||||
|
||||
// MARK: Favorites & notifications (shared with the other contexts)
|
||||
/// The persisted favorite relationship for the peer's Noise static key, if any.
|
||||
func favoriteRelationship(forNoiseKey noiseKey: Data) -> FavoritesPersistenceService.FavoriteRelationship?
|
||||
/// Adds (or updates) a favorite in the favorites store.
|
||||
func addFavorite(noiseKey: Data, nostrPublicKey: String?, nickname: String)
|
||||
/// Posts a generic local user notification.
|
||||
func postLocalNotification(title: String, body: String, identifier: String)
|
||||
}
|
||||
|
||||
extension ChatViewModel: ChatNostrContext {
|
||||
// All requirements — including the component-context witnesses declared
|
||||
// in `GeohashSubscriptionManager.swift`, `NostrInboundPipeline.swift`,
|
||||
// and `GeoPresenceTracker.swift` — already exist on `ChatViewModel`.
|
||||
// `GeoPresenceTracker.swift`, and the favorites/notification witnesses in
|
||||
// `ChatPrivateConversationCoordinator.swift`,
|
||||
// `ChatPeerIdentityCoordinator.swift`, and
|
||||
// `ChatVerificationCoordinator.swift` — already exist on `ChatViewModel`.
|
||||
}
|
||||
|
||||
/// Thin facade over the Nostr stack: owns and wires the three components and
|
||||
@@ -89,16 +100,17 @@ final class ChatNostrCoordinator {
|
||||
|
||||
@MainActor
|
||||
func handleFavoriteNotification(content: String, from nostrPubkey: String) {
|
||||
guard let context else { return }
|
||||
guard let senderNoiseKey = inbound.findNoiseKey(for: nostrPubkey) else { return }
|
||||
|
||||
let isFavorite = content.contains("FAVORITE:TRUE")
|
||||
let senderNickname = content.components(separatedBy: "|").last ?? "Unknown"
|
||||
|
||||
if isFavorite {
|
||||
FavoritesPersistenceService.shared.addFavorite(
|
||||
peerNoisePublicKey: senderNoiseKey,
|
||||
peerNostrPublicKey: nostrPubkey,
|
||||
peerNickname: senderNickname
|
||||
context.addFavorite(
|
||||
noiseKey: senderNoiseKey,
|
||||
nostrPublicKey: nostrPubkey,
|
||||
nickname: senderNickname
|
||||
)
|
||||
}
|
||||
|
||||
@@ -123,14 +135,14 @@ final class ChatNostrCoordinator {
|
||||
"💾 Storing Nostr key association for \(senderNickname): \(extractedNostrPubkey!.prefix(16))...",
|
||||
category: .session
|
||||
)
|
||||
FavoritesPersistenceService.shared.addFavorite(
|
||||
peerNoisePublicKey: senderNoiseKey,
|
||||
peerNostrPublicKey: extractedNostrPubkey,
|
||||
peerNickname: senderNickname
|
||||
context.addFavorite(
|
||||
noiseKey: senderNoiseKey,
|
||||
nostrPublicKey: extractedNostrPubkey,
|
||||
nickname: senderNickname
|
||||
)
|
||||
}
|
||||
|
||||
NotificationService.shared.sendLocalNotification(
|
||||
context.postLocalNotification(
|
||||
title: isFavorite ? "New Favorite" : "Favorite Removed",
|
||||
body: "\(senderNickname) \(isFavorite ? "favorited" : "unfavorited") you",
|
||||
identifier: "fav-\(UUID().uuidString)"
|
||||
@@ -140,7 +152,7 @@ final class ChatNostrCoordinator {
|
||||
@MainActor
|
||||
func sendFavoriteNotificationViaNostr(noisePublicKey: Data, isFavorite: Bool) {
|
||||
guard let context else { return }
|
||||
guard let relationship = FavoritesPersistenceService.shared.getFavoriteStatus(for: noisePublicKey),
|
||||
guard let relationship = context.favoriteRelationship(forNoiseKey: noisePublicKey),
|
||||
relationship.peerNostrPublicKey != nil else {
|
||||
SecureLogger.warning("⚠️ Cannot send favorite notification - no Nostr key for peer", category: .session)
|
||||
return
|
||||
|
||||
Reference in New Issue
Block a user