mirror of
https://github.com/permissionlesstech/bitchat.git
synced 2026-07-25 02:25:20 +00:00
Update MessageRouter to use Peer
This commit is contained in:
@@ -61,7 +61,13 @@ extension Peer {
|
|||||||
|
|
||||||
/// Full Noise key hex (exact 64-hex)
|
/// Full Noise key hex (exact 64-hex)
|
||||||
var isNoiseKeyHex: Bool {
|
var isNoiseKeyHex: Bool {
|
||||||
id.count == Constants.maxIDLength && Data(hexString: id) != nil
|
noiseKey != nil
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Full Noise key (exact 64-hex) as Data
|
||||||
|
var noiseKey: Data? {
|
||||||
|
guard id.count == Constants.maxIDLength else { return nil }
|
||||||
|
return Data(hexString: id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import Foundation
|
|||||||
final class MessageRouter {
|
final class MessageRouter {
|
||||||
private let mesh: Transport
|
private let mesh: Transport
|
||||||
private let nostr: NostrTransport
|
private let nostr: NostrTransport
|
||||||
private var outbox: [String: [(content: String, nickname: String, messageID: String)]] = [:] // peerID -> queued messages
|
private var outbox: [Peer: [(content: String, nickname: String, messageID: String)]] = [:] // Peer -> queued messages
|
||||||
|
|
||||||
init(mesh: Transport, nostr: NostrTransport) {
|
init(mesh: Transport, nostr: NostrTransport) {
|
||||||
self.mesh = mesh
|
self.mesh = mesh
|
||||||
@@ -21,80 +21,80 @@ final class MessageRouter {
|
|||||||
) { [weak self] note in
|
) { [weak self] note in
|
||||||
guard let self = self else { return }
|
guard let self = self else { return }
|
||||||
if let data = note.userInfo?["peerPublicKey"] as? Data {
|
if let data = note.userInfo?["peerPublicKey"] as? Data {
|
||||||
let peerID = Peer(publicKey: data).id
|
let peer = Peer(publicKey: data)
|
||||||
Task { @MainActor in
|
Task { @MainActor in
|
||||||
self.flushOutbox(for: peerID)
|
self.flushOutbox(for: peer)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Handle key updates
|
// Handle key updates
|
||||||
if let newKey = note.userInfo?["peerPublicKey"] as? Data,
|
if let newKey = note.userInfo?["peerPublicKey"] as? Data,
|
||||||
let _ = note.userInfo?["isKeyUpdate"] as? Bool {
|
let _ = note.userInfo?["isKeyUpdate"] as? Bool {
|
||||||
let peerID = Peer(publicKey: newKey).id
|
let peer = Peer(publicKey: newKey)
|
||||||
Task { @MainActor in
|
Task { @MainActor in
|
||||||
self.flushOutbox(for: peerID)
|
self.flushOutbox(for: peer)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func sendPrivate(_ content: String, to peerID: String, recipientNickname: String, messageID: String) {
|
func sendPrivate(_ content: String, to peer: Peer, recipientNickname: String, messageID: String) {
|
||||||
let reachableMesh = mesh.isPeerReachable(peerID)
|
let reachableMesh = mesh.isPeerReachable(peer.id)
|
||||||
if reachableMesh {
|
if reachableMesh {
|
||||||
SecureLogger.debug("Routing PM via mesh (reachable) to \(peerID.prefix(8))… id=\(messageID.prefix(8))…", category: .session)
|
SecureLogger.debug("Routing PM via mesh (reachable) to \(peer.id.prefix(8))… id=\(messageID.prefix(8))…", category: .session)
|
||||||
// BLEService will initiate a handshake if needed and queue the message
|
// BLEService will initiate a handshake if needed and queue the message
|
||||||
mesh.sendPrivateMessage(content, to: peerID, recipientNickname: recipientNickname, messageID: messageID)
|
mesh.sendPrivateMessage(content, to: peer.id, recipientNickname: recipientNickname, messageID: messageID)
|
||||||
} else if canSendViaNostr(peerID: peerID) {
|
} else if canSendViaNostr(peer: peer) {
|
||||||
SecureLogger.debug("Routing PM via Nostr to \(peerID.prefix(8))… id=\(messageID.prefix(8))…", category: .session)
|
SecureLogger.debug("Routing PM via Nostr to \(peer.id.prefix(8))… id=\(messageID.prefix(8))…", category: .session)
|
||||||
nostr.sendPrivateMessage(content, to: peerID, recipientNickname: recipientNickname, messageID: messageID)
|
nostr.sendPrivateMessage(content, to: peer.id, recipientNickname: recipientNickname, messageID: messageID)
|
||||||
} else {
|
} else {
|
||||||
// Queue for later (when mesh connects or Nostr mapping appears)
|
// Queue for later (when mesh connects or Nostr mapping appears)
|
||||||
if outbox[peerID] == nil { outbox[peerID] = [] }
|
if outbox[peer] == nil { outbox[peer] = [] }
|
||||||
outbox[peerID]?.append((content, recipientNickname, messageID))
|
outbox[peer]?.append((content, recipientNickname, messageID))
|
||||||
SecureLogger.debug("Queued PM for \(peerID.prefix(8))… (no mesh, no Nostr mapping) id=\(messageID.prefix(8))…", category: .session)
|
SecureLogger.debug("Queued PM for \(peer.id.prefix(8))… (no mesh, no Nostr mapping) id=\(messageID.prefix(8))…", category: .session)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func sendReadReceipt(_ receipt: ReadReceipt, to peerID: String) {
|
func sendReadReceipt(_ receipt: ReadReceipt, to peer: Peer) {
|
||||||
// Prefer mesh for reachable peers; BLE will queue if handshake is needed
|
// Prefer mesh for reachable peers; BLE will queue if handshake is needed
|
||||||
if mesh.isPeerReachable(peerID) {
|
if mesh.isPeerReachable(peer.id) {
|
||||||
SecureLogger.debug("Routing READ ack via mesh (reachable) to \(peerID.prefix(8))… id=\(receipt.originalMessageID.prefix(8))…", category: .session)
|
SecureLogger.debug("Routing READ ack via mesh (reachable) to \(peer.id.prefix(8))… id=\(receipt.originalMessageID.prefix(8))…", category: .session)
|
||||||
mesh.sendReadReceipt(receipt, to: peerID)
|
mesh.sendReadReceipt(receipt, to: peer.id)
|
||||||
} else {
|
} else {
|
||||||
SecureLogger.debug("Routing READ ack via Nostr to \(peerID.prefix(8))… id=\(receipt.originalMessageID.prefix(8))…", category: .session)
|
SecureLogger.debug("Routing READ ack via Nostr to \(peer.id.prefix(8))… id=\(receipt.originalMessageID.prefix(8))…", category: .session)
|
||||||
nostr.sendReadReceipt(receipt, to: peerID)
|
nostr.sendReadReceipt(receipt, to: peer.id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func sendDeliveryAck(_ messageID: String, to peerID: String) {
|
func sendDeliveryAck(_ messageID: String, to peer: Peer) {
|
||||||
if mesh.isPeerReachable(peerID) {
|
if mesh.isPeerReachable(peer.id) {
|
||||||
SecureLogger.debug("Routing DELIVERED ack via mesh (reachable) to \(peerID.prefix(8))… id=\(messageID.prefix(8))…", category: .session)
|
SecureLogger.debug("Routing DELIVERED ack via mesh (reachable) to \(peer.id.prefix(8))… id=\(messageID.prefix(8))…", category: .session)
|
||||||
mesh.sendDeliveryAck(for: messageID, to: peerID)
|
mesh.sendDeliveryAck(for: messageID, to: peer.id)
|
||||||
} else {
|
} else {
|
||||||
nostr.sendDeliveryAck(for: messageID, to: peerID)
|
nostr.sendDeliveryAck(for: messageID, to: peer.id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func sendFavoriteNotification(to peerID: String, isFavorite: Bool) {
|
func sendFavoriteNotification(to peer: Peer, isFavorite: Bool) {
|
||||||
// Route via mesh when connected; else use Nostr
|
// Route via mesh when connected; else use Nostr
|
||||||
if mesh.isPeerConnected(peerID) {
|
if mesh.isPeerConnected(peer.id) {
|
||||||
mesh.sendFavoriteNotification(to: peerID, isFavorite: isFavorite)
|
mesh.sendFavoriteNotification(to: peer.id, isFavorite: isFavorite)
|
||||||
} else {
|
} else {
|
||||||
nostr.sendFavoriteNotification(to: peerID, isFavorite: isFavorite)
|
nostr.sendFavoriteNotification(to: peer.id, isFavorite: isFavorite)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Outbox Management
|
// MARK: - Outbox Management
|
||||||
private func canSendViaNostr(peerID: String) -> Bool {
|
private func canSendViaNostr(peer: Peer) -> Bool {
|
||||||
// Two forms are supported:
|
// Two forms are supported:
|
||||||
// - 64-hex Noise public key (32 bytes)
|
// - 64-hex Noise public key (32 bytes)
|
||||||
// - 16-hex short peer ID (derived from Noise pubkey)
|
// - 16-hex short peer ID (derived from Noise pubkey)
|
||||||
if peerID.count == 64, let noiseKey = Data(hexString: peerID) {
|
if let noiseKey = peer.noiseKey {
|
||||||
if let fav = FavoritesPersistenceService.shared.getFavoriteStatus(for: noiseKey),
|
if let fav = FavoritesPersistenceService.shared.getFavoriteStatus(for: noiseKey),
|
||||||
fav.peerNostrPublicKey != nil {
|
fav.peerNostrPublicKey != nil {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
} else if peerID.count == 16 {
|
} else if peer.isShort {
|
||||||
if let fav = FavoritesPersistenceService.shared.getFavoriteStatus(for: Peer(str: peerID)),
|
if let fav = FavoritesPersistenceService.shared.getFavoriteStatus(for: peer),
|
||||||
fav.peerNostrPublicKey != nil {
|
fav.peerNostrPublicKey != nil {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@@ -102,18 +102,18 @@ final class MessageRouter {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func flushOutbox(for peerID: String) {
|
func flushOutbox(for peer: Peer) {
|
||||||
guard let queued = outbox[peerID], !queued.isEmpty else { return }
|
guard let queued = outbox[peer], !queued.isEmpty else { return }
|
||||||
SecureLogger.debug("Flushing outbox for \(peerID.prefix(8))… count=\(queued.count)", category: .session)
|
SecureLogger.debug("Flushing outbox for \(peer.id.prefix(8))… count=\(queued.count)", category: .session)
|
||||||
var remaining: [(content: String, nickname: String, messageID: String)] = []
|
var remaining: [(content: String, nickname: String, messageID: String)] = []
|
||||||
// Prefer mesh if connected; else try Nostr if mapping exists
|
// Prefer mesh if connected; else try Nostr if mapping exists
|
||||||
for (content, nickname, messageID) in queued {
|
for (content, nickname, messageID) in queued {
|
||||||
if mesh.isPeerReachable(peerID) {
|
if mesh.isPeerReachable(peer.id) {
|
||||||
SecureLogger.debug("Outbox -> mesh for \(peerID.prefix(8))… id=\(messageID.prefix(8))…", category: .session)
|
SecureLogger.debug("Outbox -> mesh for \(peer.id.prefix(8))… id=\(messageID.prefix(8))…", category: .session)
|
||||||
mesh.sendPrivateMessage(content, to: peerID, recipientNickname: nickname, messageID: messageID)
|
mesh.sendPrivateMessage(content, to: peer.id, recipientNickname: nickname, messageID: messageID)
|
||||||
} else if canSendViaNostr(peerID: peerID) {
|
} else if canSendViaNostr(peer: peer) {
|
||||||
SecureLogger.debug("Outbox -> Nostr for \(peerID.prefix(8))… id=\(messageID.prefix(8))…", category: .session)
|
SecureLogger.debug("Outbox -> Nostr for \(peer.id.prefix(8))… id=\(messageID.prefix(8))…", category: .session)
|
||||||
nostr.sendPrivateMessage(content, to: peerID, recipientNickname: nickname, messageID: messageID)
|
nostr.sendPrivateMessage(content, to: peer.id, recipientNickname: nickname, messageID: messageID)
|
||||||
} else {
|
} else {
|
||||||
// Keep unsent items queued
|
// Keep unsent items queued
|
||||||
remaining.append((content, nickname, messageID))
|
remaining.append((content, nickname, messageID))
|
||||||
@@ -121,9 +121,9 @@ final class MessageRouter {
|
|||||||
}
|
}
|
||||||
// Persist only items we could not send
|
// Persist only items we could not send
|
||||||
if remaining.isEmpty {
|
if remaining.isEmpty {
|
||||||
outbox.removeValue(forKey: peerID)
|
outbox.removeValue(forKey: peer)
|
||||||
} else {
|
} else {
|
||||||
outbox[peerID] = remaining
|
outbox[peer] = remaining
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -231,7 +231,7 @@ final class PrivateChatManager: ObservableObject {
|
|||||||
if let router = messageRouter {
|
if let router = messageRouter {
|
||||||
SecureLogger.debug("PrivateChatManager: sending READ ack for \(message.id.prefix(8))… to \(senderPeerID.prefix(8))… via router", category: .session)
|
SecureLogger.debug("PrivateChatManager: sending READ ack for \(message.id.prefix(8))… to \(senderPeerID.prefix(8))… via router", category: .session)
|
||||||
Task { @MainActor in
|
Task { @MainActor in
|
||||||
router.sendReadReceipt(receipt, to: senderPeerID)
|
router.sendReadReceipt(receipt, to: Peer(str: senderPeerID))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Fallback: preserve previous behavior
|
// Fallback: preserve previous behavior
|
||||||
|
|||||||
@@ -307,7 +307,7 @@ final class UnifiedPeerService: ObservableObject, TransportPeerEventsDelegate {
|
|||||||
|
|
||||||
// Send favorite notification to the peer via router (mesh or Nostr)
|
// Send favorite notification to the peer via router (mesh or Nostr)
|
||||||
if let router = messageRouter {
|
if let router = messageRouter {
|
||||||
router.sendFavoriteNotification(to: peerID, isFavorite: !wasFavorite)
|
router.sendFavoriteNotification(to: Peer(str: peerID), isFavorite: !wasFavorite)
|
||||||
} else {
|
} else {
|
||||||
// Fallback to mesh-only if router not yet wired
|
// Fallback to mesh-only if router not yet wired
|
||||||
meshService.sendFavoriteNotification(to: peerID, isFavorite: !wasFavorite)
|
meshService.sendFavoriteNotification(to: peerID, isFavorite: !wasFavorite)
|
||||||
|
|||||||
@@ -2284,7 +2284,7 @@ final class ChatViewModel: ObservableObject, BitchatDelegate {
|
|||||||
|
|
||||||
// Send via appropriate transport (BLE if connected/reachable, else Nostr when possible)
|
// Send via appropriate transport (BLE if connected/reachable, else Nostr when possible)
|
||||||
if isConnected || isReachable || (isMutualFavorite && hasNostrKey) {
|
if isConnected || isReachable || (isMutualFavorite && hasNostrKey) {
|
||||||
messageRouter.sendPrivate(content, to: peerID, recipientNickname: recipientNickname ?? "user", messageID: messageID)
|
messageRouter.sendPrivate(content, to: Peer(str: peerID), recipientNickname: recipientNickname ?? "user", messageID: messageID)
|
||||||
// Optimistically mark as sent for both transports; delivery/read will update subsequently
|
// Optimistically mark as sent for both transports; delivery/read will update subsequently
|
||||||
if let idx = privateChats[peerID]?.firstIndex(where: { $0.id == messageID }) {
|
if let idx = privateChats[peerID]?.firstIndex(where: { $0.id == messageID }) {
|
||||||
privateChats[peerID]?[idx].deliveryStatus = .sent
|
privateChats[peerID]?[idx].deliveryStatus = .sent
|
||||||
@@ -2866,7 +2866,7 @@ final class ChatViewModel: ObservableObject, BitchatDelegate {
|
|||||||
switch sessionState {
|
switch sessionState {
|
||||||
case .established:
|
case .established:
|
||||||
// Send the message directly without going through sendPrivateMessage to avoid local echo
|
// Send the message directly without going through sendPrivateMessage to avoid local echo
|
||||||
messageRouter.sendPrivate(screenshotMessage, to: peerID, recipientNickname: peerNickname, messageID: UUID().uuidString)
|
messageRouter.sendPrivate(screenshotMessage, to: Peer(str: peerID), recipientNickname: peerNickname, messageID: UUID().uuidString)
|
||||||
default:
|
default:
|
||||||
// Don't send screenshot notification if no session exists
|
// Don't send screenshot notification if no session exists
|
||||||
SecureLogger.debug("Skipping screenshot notification to \(peerID) - no established session", category: .security)
|
SecureLogger.debug("Skipping screenshot notification to \(peerID) - no established session", category: .security)
|
||||||
@@ -2988,7 +2988,7 @@ final class ChatViewModel: ObservableObject, BitchatDelegate {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
// Use router to decide (mesh if reachable, else Nostr if available)
|
// Use router to decide (mesh if reachable, else Nostr if available)
|
||||||
messageRouter.sendReadReceipt(receipt, to: actualPeerID)
|
messageRouter.sendReadReceipt(receipt, to: Peer(str: actualPeerID))
|
||||||
}
|
}
|
||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
@@ -3049,7 +3049,7 @@ final class ChatViewModel: ObservableObject, BitchatDelegate {
|
|||||||
// Use stable Noise key hex if available; else fall back to peerID
|
// Use stable Noise key hex if available; else fall back to peerID
|
||||||
let recipPeer = (Data(hexString: peerID) != nil) ? peerID : (unifiedPeerService.getPeer(by: peerID)?.noisePublicKey.hexEncodedString() ?? peerID)
|
let recipPeer = (Data(hexString: peerID) != nil) ? peerID : (unifiedPeerService.getPeer(by: peerID)?.noisePublicKey.hexEncodedString() ?? peerID)
|
||||||
let receipt = ReadReceipt(originalMessageID: message.id, readerID: meshService.myPeerID, readerNickname: nickname)
|
let receipt = ReadReceipt(originalMessageID: message.id, readerID: meshService.myPeerID, readerNickname: nickname)
|
||||||
messageRouter.sendReadReceipt(receipt, to: recipPeer)
|
messageRouter.sendReadReceipt(receipt, to: Peer(str: recipPeer))
|
||||||
sentReadReceipts.insert(message.id)
|
sentReadReceipts.insert(message.id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -4623,7 +4623,7 @@ final class ChatViewModel: ObservableObject, BitchatDelegate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Flush any queued messages for this peer via router
|
// Flush any queued messages for this peer via router
|
||||||
messageRouter.flushOutbox(for: peerID)
|
messageRouter.flushOutbox(for: Peer(str: peerID))
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
@@ -5312,7 +5312,7 @@ final class ChatViewModel: ObservableObject, BitchatDelegate {
|
|||||||
|
|
||||||
if let key {
|
if let key {
|
||||||
SecureLogger.debug("Sending DELIVERED ack for \(message.id.prefix(8))… via router", category: .session)
|
SecureLogger.debug("Sending DELIVERED ack for \(message.id.prefix(8))… via router", category: .session)
|
||||||
messageRouter.sendDeliveryAck(message.id, to: key.hexEncodedString())
|
messageRouter.sendDeliveryAck(message.id, to: Peer(str: key.hexEncodedString()))
|
||||||
} else if let id = try? NostrIdentityBridge.getCurrentNostrIdentity() {
|
} else if let id = try? NostrIdentityBridge.getCurrentNostrIdentity() {
|
||||||
// Fallback: no Noise mapping yet — send directly to sender's Nostr pubkey
|
// Fallback: no Noise mapping yet — send directly to sender's Nostr pubkey
|
||||||
let nt = NostrTransport(keychain: keychain)
|
let nt = NostrTransport(keychain: keychain)
|
||||||
@@ -5333,7 +5333,7 @@ final class ChatViewModel: ObservableObject, BitchatDelegate {
|
|||||||
if let key {
|
if let key {
|
||||||
let receipt = ReadReceipt(originalMessageID: message.id, readerID: meshService.myPeerID, readerNickname: nickname)
|
let receipt = ReadReceipt(originalMessageID: message.id, readerID: meshService.myPeerID, readerNickname: nickname)
|
||||||
SecureLogger.debug("Viewing chat; sending READ ack for \(message.id.prefix(8))… via router", category: .session)
|
SecureLogger.debug("Viewing chat; sending READ ack for \(message.id.prefix(8))… via router", category: .session)
|
||||||
messageRouter.sendReadReceipt(receipt, to: key.hexEncodedString())
|
messageRouter.sendReadReceipt(receipt, to: Peer(str: key.hexEncodedString()))
|
||||||
sentReadReceipts.insert(message.id)
|
sentReadReceipts.insert(message.id)
|
||||||
} else if let id = try? NostrIdentityBridge.getCurrentNostrIdentity() {
|
} else if let id = try? NostrIdentityBridge.getCurrentNostrIdentity() {
|
||||||
let nt = NostrTransport(keychain: keychain)
|
let nt = NostrTransport(keychain: keychain)
|
||||||
@@ -5655,7 +5655,7 @@ final class ChatViewModel: ObservableObject, BitchatDelegate {
|
|||||||
@MainActor
|
@MainActor
|
||||||
private func sendFavoriteNotificationViaNostr(noisePublicKey: Data, isFavorite: Bool) {
|
private func sendFavoriteNotificationViaNostr(noisePublicKey: Data, isFavorite: Bool) {
|
||||||
let peerIDHex = noisePublicKey.hexEncodedString()
|
let peerIDHex = noisePublicKey.hexEncodedString()
|
||||||
messageRouter.sendFavoriteNotification(to: peerIDHex, isFavorite: isFavorite)
|
messageRouter.sendFavoriteNotification(to: Peer(str: peerIDHex), isFavorite: isFavorite)
|
||||||
}
|
}
|
||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
@@ -5675,12 +5675,12 @@ final class ChatViewModel: ObservableObject, BitchatDelegate {
|
|||||||
|
|
||||||
// Try mesh first for connected peers
|
// Try mesh first for connected peers
|
||||||
if meshService.isPeerConnected(peerID) {
|
if meshService.isPeerConnected(peerID) {
|
||||||
messageRouter.sendFavoriteNotification(to: peerID, isFavorite: isFavorite)
|
messageRouter.sendFavoriteNotification(to: Peer(str: peerID), isFavorite: isFavorite)
|
||||||
SecureLogger.debug("📤 Sent favorite notification via BLE to \(peerID)", category: .session)
|
SecureLogger.debug("📤 Sent favorite notification via BLE to \(peerID)", category: .session)
|
||||||
} else if let key = noiseKey {
|
} else if let key = noiseKey {
|
||||||
// Send via Nostr for offline peers (using router)
|
// Send via Nostr for offline peers (using router)
|
||||||
let recipientPeerID = key.hexEncodedString()
|
let recipientPeerID = key.hexEncodedString()
|
||||||
messageRouter.sendFavoriteNotification(to: recipientPeerID, isFavorite: isFavorite)
|
messageRouter.sendFavoriteNotification(to: Peer(str: recipientPeerID), isFavorite: isFavorite)
|
||||||
} else {
|
} else {
|
||||||
SecureLogger.warning("⚠️ Cannot send favorite notification - peer not connected and no Nostr pubkey", category: .session)
|
SecureLogger.warning("⚠️ Cannot send favorite notification - peer not connected and no Nostr pubkey", category: .session)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user