mirror of
https://github.com/permissionlesstech/bitchat.git
synced 2026-07-25 18:45:20 +00:00
refactor(nostr-outbox): add NostrOutboxService and migrate ChatViewModel geohash PM/ACK sends to it
Centralize Nostr geohash outbox (PM, DELIVERED, READ) into a service wrapping NostrTransport; inject into ChatViewModel and remove ad-hoc NostrTransport instantiations.
This commit is contained in:
@@ -60,6 +60,8 @@
|
|||||||
ABCD00032E5CCCC300162C4A /* ReadReceiptTracker.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABCD00012E5CCCC300162C4A /* ReadReceiptTracker.swift */; };
|
ABCD00032E5CCCC300162C4A /* ReadReceiptTracker.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABCD00012E5CCCC300162C4A /* ReadReceiptTracker.swift */; };
|
||||||
ABCD00052E5CCCC300162C4C /* NostrInboxService.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABCD00042E5CCCC300162C4C /* NostrInboxService.swift */; };
|
ABCD00052E5CCCC300162C4C /* NostrInboxService.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABCD00042E5CCCC300162C4C /* NostrInboxService.swift */; };
|
||||||
ABCD00062E5CCCC300162C4D /* NostrInboxService.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABCD00042E5CCCC300162C4C /* NostrInboxService.swift */; };
|
ABCD00062E5CCCC300162C4D /* NostrInboxService.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABCD00042E5CCCC300162C4C /* NostrInboxService.swift */; };
|
||||||
|
ABCD00082E5CCCC300162C4E /* NostrOutboxService.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABCD00072E5CCCC300162C4E /* NostrOutboxService.swift */; };
|
||||||
|
ABCD00092E5CCCC300162C4F /* NostrOutboxService.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABCD00072E5CCCC300162C4E /* NostrOutboxService.swift */; };
|
||||||
0AE840940F21AFC07C226636 /* PrivateChatE2ETests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A262EDDC04B7D7B5E31F321 /* PrivateChatE2ETests.swift */; };
|
0AE840940F21AFC07C226636 /* PrivateChatE2ETests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A262EDDC04B7D7B5E31F321 /* PrivateChatE2ETests.swift */; };
|
||||||
0B6F25559A21F8C69C8357C6 /* BinaryProtocolTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0B3CC6FA298729906109F61B /* BinaryProtocolTests.swift */; };
|
0B6F25559A21F8C69C8357C6 /* BinaryProtocolTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0B3CC6FA298729906109F61B /* BinaryProtocolTests.swift */; };
|
||||||
10E68BB889356219189E38EC /* BitchatApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = EF625BB3AD919322C01A46B2 /* BitchatApp.swift */; };
|
10E68BB889356219189E38EC /* BitchatApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = EF625BB3AD919322C01A46B2 /* BitchatApp.swift */; };
|
||||||
@@ -219,6 +221,7 @@
|
|||||||
049BD3A42E51DC0E001A566B /* MessageDeduplicator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageDeduplicator.swift; sourceTree = "<group>"; };
|
049BD3A42E51DC0E001A566B /* MessageDeduplicator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageDeduplicator.swift; sourceTree = "<group>"; };
|
||||||
ABCD00012E5CCCC300162C4A /* ReadReceiptTracker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReadReceiptTracker.swift; sourceTree = "<group>"; };
|
ABCD00012E5CCCC300162C4A /* ReadReceiptTracker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReadReceiptTracker.swift; sourceTree = "<group>"; };
|
||||||
ABCD00042E5CCCC300162C4C /* NostrInboxService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NostrInboxService.swift; sourceTree = "<group>"; };
|
ABCD00042E5CCCC300162C4C /* NostrInboxService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NostrInboxService.swift; sourceTree = "<group>"; };
|
||||||
|
ABCD00072E5CCCC300162C4E /* NostrOutboxService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NostrOutboxService.swift; sourceTree = "<group>"; };
|
||||||
049BD3AA2E51E38E001A566B /* PeerIDResolver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PeerIDResolver.swift; sourceTree = "<group>"; };
|
049BD3AA2E51E38E001A566B /* PeerIDResolver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PeerIDResolver.swift; sourceTree = "<group>"; };
|
||||||
049BD3AD2E51ED60001A566B /* Transport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Transport.swift; sourceTree = "<group>"; };
|
049BD3AD2E51ED60001A566B /* Transport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Transport.swift; sourceTree = "<group>"; };
|
||||||
049BD3B02E51F319001A566B /* MessageRouter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageRouter.swift; sourceTree = "<group>"; };
|
049BD3B02E51F319001A566B /* MessageRouter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageRouter.swift; sourceTree = "<group>"; };
|
||||||
@@ -528,6 +531,7 @@
|
|||||||
048A4BE62E5CCCC300162C4A /* TransportConfig.swift */,
|
048A4BE62E5CCCC300162C4A /* TransportConfig.swift */,
|
||||||
ABCD00012E5CCCC300162C4A /* ReadReceiptTracker.swift */,
|
ABCD00012E5CCCC300162C4A /* ReadReceiptTracker.swift */,
|
||||||
ABCD00042E5CCCC300162C4C /* NostrInboxService.swift */,
|
ABCD00042E5CCCC300162C4C /* NostrInboxService.swift */,
|
||||||
|
ABCD00072E5CCCC300162C4E /* NostrOutboxService.swift */,
|
||||||
AA77BB10CC22DD33EE44FF55 /* VerificationService.swift */,
|
AA77BB10CC22DD33EE44FF55 /* VerificationService.swift */,
|
||||||
047502B82E560F690083520F /* RelayController.swift */,
|
047502B82E560F690083520F /* RelayController.swift */,
|
||||||
0475028B2E54171C0083520F /* LocationChannelManager.swift */,
|
0475028B2E54171C0083520F /* LocationChannelManager.swift */,
|
||||||
@@ -753,6 +757,7 @@
|
|||||||
048A4BE72E5CCCC300162C4A /* TransportConfig.swift in Sources */,
|
048A4BE72E5CCCC300162C4A /* TransportConfig.swift in Sources */,
|
||||||
ABCD00022E5CCCC300162C4A /* ReadReceiptTracker.swift in Sources */,
|
ABCD00022E5CCCC300162C4A /* ReadReceiptTracker.swift in Sources */,
|
||||||
ABCD00052E5CCCC300162C4C /* NostrInboxService.swift in Sources */,
|
ABCD00052E5CCCC300162C4C /* NostrInboxService.swift in Sources */,
|
||||||
|
ABCD00082E5CCCC300162C4E /* NostrOutboxService.swift in Sources */,
|
||||||
1234567890ABCDEFFEDCBA13 /* PeerDisplayNameResolver.swift in Sources */,
|
1234567890ABCDEFFEDCBA13 /* PeerDisplayNameResolver.swift in Sources */,
|
||||||
AA77BB12CC22DD33EE44FF56 /* VerificationService.swift in Sources */,
|
AA77BB12CC22DD33EE44FF56 /* VerificationService.swift in Sources */,
|
||||||
AA77BB15CC22DD33EE44FF59 /* VerificationViews.swift in Sources */,
|
AA77BB15CC22DD33EE44FF59 /* VerificationViews.swift in Sources */,
|
||||||
@@ -814,6 +819,7 @@
|
|||||||
048A4BE82E5CCCC300162C4A /* TransportConfig.swift in Sources */,
|
048A4BE82E5CCCC300162C4A /* TransportConfig.swift in Sources */,
|
||||||
ABCD00032E5CCCC300162C4A /* ReadReceiptTracker.swift in Sources */,
|
ABCD00032E5CCCC300162C4A /* ReadReceiptTracker.swift in Sources */,
|
||||||
ABCD00062E5CCCC300162C4D /* NostrInboxService.swift in Sources */,
|
ABCD00062E5CCCC300162C4D /* NostrInboxService.swift in Sources */,
|
||||||
|
ABCD00092E5CCCC300162C4F /* NostrOutboxService.swift in Sources */,
|
||||||
1234567890ABCDEFFEDCBA14 /* PeerDisplayNameResolver.swift in Sources */,
|
1234567890ABCDEFFEDCBA14 /* PeerDisplayNameResolver.swift in Sources */,
|
||||||
AA77BB11CC22DD33EE44FF55 /* VerificationService.swift in Sources */,
|
AA77BB11CC22DD33EE44FF55 /* VerificationService.swift in Sources */,
|
||||||
AA77BB14CC22DD33EE44FF58 /* VerificationViews.swift in Sources */,
|
AA77BB14CC22DD33EE44FF58 /* VerificationViews.swift in Sources */,
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
//
|
||||||
|
// NostrOutboxService.swift
|
||||||
|
// bitchat
|
||||||
|
//
|
||||||
|
// Centralized Nostr outbox for sending PMs and ACKs.
|
||||||
|
// This is free and unencumbered software released into the public domain.
|
||||||
|
// For more information, see <https://unlicense.org>
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
@MainActor
|
||||||
|
final class NostrOutboxService {
|
||||||
|
private let transport: NostrTransport
|
||||||
|
|
||||||
|
init(meshService: Transport) {
|
||||||
|
let t = NostrTransport()
|
||||||
|
t.senderPeerID = meshService.myPeerID
|
||||||
|
self.transport = t
|
||||||
|
}
|
||||||
|
|
||||||
|
func sendGeohashPM(content: String, toRecipientHex recipientHex: String, from identity: NostrIdentity, messageID: String) {
|
||||||
|
transport.sendPrivateMessageGeohash(content: content, toRecipientHex: recipientHex, from: identity, messageID: messageID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func sendGeohashDeliveredAck(messageID: String, toRecipientHex recipientHex: String, from identity: NostrIdentity) {
|
||||||
|
transport.sendDeliveryAckGeohash(for: messageID, toRecipientHex: recipientHex, from: identity)
|
||||||
|
}
|
||||||
|
|
||||||
|
func sendGeohashReadAck(messageID: String, toRecipientHex recipientHex: String, from identity: NostrIdentity) {
|
||||||
|
transport.sendReadReceiptGeohash(messageID, toRecipientHex: recipientHex, from: identity)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -358,6 +358,7 @@ class ChatViewModel: ObservableObject, BitchatDelegate {
|
|||||||
private var currentGeohash: String? = nil
|
private var currentGeohash: String? = nil
|
||||||
private var geoNicknames: [String: String] = [:] // pubkeyHex(lowercased) -> nickname
|
private var geoNicknames: [String: String] = [:] // pubkeyHex(lowercased) -> nickname
|
||||||
private let nostrInbox = NostrInboxService()
|
private let nostrInbox = NostrInboxService()
|
||||||
|
private var nostrOutbox: NostrOutboxService
|
||||||
|
|
||||||
// MARK: - Caches
|
// MARK: - Caches
|
||||||
|
|
||||||
@@ -463,6 +464,7 @@ class ChatViewModel: ObservableObject, BitchatDelegate {
|
|||||||
self.unifiedPeerService = UnifiedPeerService(meshService: meshService)
|
self.unifiedPeerService = UnifiedPeerService(meshService: meshService)
|
||||||
let nostrTransport = NostrTransport()
|
let nostrTransport = NostrTransport()
|
||||||
self.messageRouter = MessageRouter(mesh: meshService, nostr: nostrTransport)
|
self.messageRouter = MessageRouter(mesh: meshService, nostr: nostrTransport)
|
||||||
|
self.nostrOutbox = NostrOutboxService(meshService: meshService)
|
||||||
// Route receipts from PrivateChatManager through MessageRouter
|
// Route receipts from PrivateChatManager through MessageRouter
|
||||||
self.privateChatManager.messageRouter = self.messageRouter
|
self.privateChatManager.messageRouter = self.messageRouter
|
||||||
self.autocompleteService = AutocompleteService()
|
self.autocompleteService = AutocompleteService()
|
||||||
@@ -707,6 +709,7 @@ class ChatViewModel: ObservableObject, BitchatDelegate {
|
|||||||
self.messageRouter = messageRouter
|
self.messageRouter = messageRouter
|
||||||
self.readReceiptTracker = readReceiptTracker
|
self.readReceiptTracker = readReceiptTracker
|
||||||
self.autocompleteService = AutocompleteService()
|
self.autocompleteService = AutocompleteService()
|
||||||
|
self.nostrOutbox = NostrOutboxService(meshService: meshService)
|
||||||
|
|
||||||
// Wire up dependencies
|
// Wire up dependencies
|
||||||
self.commandProcessor.chatViewModel = self
|
self.commandProcessor.chatViewModel = self
|
||||||
@@ -986,9 +989,7 @@ class ChatViewModel: ObservableObject, BitchatDelegate {
|
|||||||
let messageId = pm.messageID
|
let messageId = pm.messageID
|
||||||
// Send delivery ACK immediately (once per message ID)
|
// Send delivery ACK immediately (once per message ID)
|
||||||
if !self.sentGeoDeliveryAcks.contains(messageId) {
|
if !self.sentGeoDeliveryAcks.contains(messageId) {
|
||||||
let nt = NostrTransport()
|
self.nostrOutbox.sendGeohashDeliveredAck(messageID: messageId, toRecipientHex: senderPubkey, from: id)
|
||||||
nt.senderPeerID = self.meshService.myPeerID
|
|
||||||
nt.sendDeliveryAckGeohash(for: messageId, toRecipientHex: senderPubkey, from: id)
|
|
||||||
self.sentGeoDeliveryAcks.insert(messageId)
|
self.sentGeoDeliveryAcks.insert(messageId)
|
||||||
}
|
}
|
||||||
// Dedup storage
|
// Dedup storage
|
||||||
@@ -1024,9 +1025,7 @@ class ChatViewModel: ObservableObject, BitchatDelegate {
|
|||||||
if isViewing {
|
if isViewing {
|
||||||
// pared back: omit pre-send READ log
|
// pared back: omit pre-send READ log
|
||||||
if !wasReadBefore {
|
if !wasReadBefore {
|
||||||
let nt = NostrTransport()
|
self.nostrOutbox.sendGeohashReadAck(messageID: messageId, toRecipientHex: senderPubkey, from: id)
|
||||||
nt.senderPeerID = self.meshService.myPeerID
|
|
||||||
nt.sendReadReceiptGeohash(messageId, toRecipientHex: senderPubkey, from: id)
|
|
||||||
self.readReceiptTracker.insert(messageId)
|
self.readReceiptTracker.insert(messageId)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -1663,10 +1662,7 @@ class ChatViewModel: ObservableObject, BitchatDelegate {
|
|||||||
category: SecureLogger.session, level: .info)
|
category: SecureLogger.session, level: .info)
|
||||||
// Send delivery ACK immediately (even if duplicate), once per messageID
|
// Send delivery ACK immediately (even if duplicate), once per messageID
|
||||||
if !self.sentGeoDeliveryAcks.contains(messageId) {
|
if !self.sentGeoDeliveryAcks.contains(messageId) {
|
||||||
let nostrTransport = NostrTransport()
|
self.nostrOutbox.sendGeohashDeliveredAck(messageID: messageId, toRecipientHex: senderPubkey, from: id)
|
||||||
nostrTransport.senderPeerID = self.meshService.myPeerID
|
|
||||||
// pared back: omit pre-send log
|
|
||||||
nostrTransport.sendDeliveryAckGeohash(for: messageId, toRecipientHex: senderPubkey, from: id)
|
|
||||||
self.sentGeoDeliveryAcks.insert(messageId)
|
self.sentGeoDeliveryAcks.insert(messageId)
|
||||||
}
|
}
|
||||||
// Duplicate check
|
// Duplicate check
|
||||||
@@ -1698,9 +1694,7 @@ class ChatViewModel: ObservableObject, BitchatDelegate {
|
|||||||
if isViewing {
|
if isViewing {
|
||||||
// pared back: omit pre-send READ log
|
// pared back: omit pre-send READ log
|
||||||
if !wasReadBefore {
|
if !wasReadBefore {
|
||||||
let nostrTransport = NostrTransport()
|
self.nostrOutbox.sendGeohashReadAck(messageID: messageId, toRecipientHex: senderPubkey, from: id)
|
||||||
nostrTransport.senderPeerID = self.meshService.myPeerID
|
|
||||||
nostrTransport.sendReadReceiptGeohash(messageId, toRecipientHex: senderPubkey, from: id)
|
|
||||||
self.readReceiptTracker.insert(messageId)
|
self.readReceiptTracker.insert(messageId)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -2059,9 +2053,7 @@ class ChatViewModel: ObservableObject, BitchatDelegate {
|
|||||||
}
|
}
|
||||||
SecureLogger.log("GeoDM: local send mid=\(messageID.prefix(8))… to=\(recipientHex.prefix(8))… conv=\(peerID)",
|
SecureLogger.log("GeoDM: local send mid=\(messageID.prefix(8))… to=\(recipientHex.prefix(8))… conv=\(peerID)",
|
||||||
category: SecureLogger.session, level: .debug)
|
category: SecureLogger.session, level: .debug)
|
||||||
let nostrTransport = NostrTransport()
|
self.nostrOutbox.sendGeohashPM(content: content, toRecipientHex: recipientHex, from: id, messageID: messageID)
|
||||||
nostrTransport.senderPeerID = meshService.myPeerID
|
|
||||||
nostrTransport.sendPrivateMessageGeohash(content: content, toRecipientHex: recipientHex, from: id, messageID: messageID)
|
|
||||||
if let msgIdx = privateChats[peerID]?.firstIndex(where: { $0.id == messageID }) {
|
if let msgIdx = privateChats[peerID]?.firstIndex(where: { $0.id == messageID }) {
|
||||||
privateChats[peerID]?[msgIdx].deliveryStatus = .sent
|
privateChats[peerID]?[msgIdx].deliveryStatus = .sent
|
||||||
}
|
}
|
||||||
@@ -2773,9 +2765,7 @@ class ChatViewModel: ObservableObject, BitchatDelegate {
|
|||||||
if !readReceiptTracker.contains(message.id) {
|
if !readReceiptTracker.contains(message.id) {
|
||||||
SecureLogger.log("GeoDM: sending READ for mid=\(message.id.prefix(8))… to=\(recipientHex.prefix(8))…",
|
SecureLogger.log("GeoDM: sending READ for mid=\(message.id.prefix(8))… to=\(recipientHex.prefix(8))…",
|
||||||
category: SecureLogger.session, level: .debug)
|
category: SecureLogger.session, level: .debug)
|
||||||
let nostrTransport = NostrTransport()
|
self.nostrOutbox.sendGeohashReadAck(messageID: message.id, toRecipientHex: recipientHex, from: id)
|
||||||
nostrTransport.senderPeerID = meshService.myPeerID
|
|
||||||
nostrTransport.sendReadReceiptGeohash(message.id, toRecipientHex: recipientHex, from: id)
|
|
||||||
readReceiptTracker.insert(message.id)
|
readReceiptTracker.insert(message.id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user