mirror of
https://github.com/permissionlesstech/bitchat.git
synced 2026-07-25 11:25:20 +00:00
Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e1bf069c74 | ||
|
|
2e43770d0e |
@@ -13,12 +13,16 @@ final class FavoritesPersistenceService: ObservableObject {
|
|||||||
let theyFavoritedUs: Bool
|
let theyFavoritedUs: Bool
|
||||||
let favoritedAt: Date
|
let favoritedAt: Date
|
||||||
let lastUpdated: Date
|
let lastUpdated: Date
|
||||||
|
// Track what we last sent as OUR npub to this peer, to avoid resending unless it changes
|
||||||
|
// Note: we do not track which npub we last sent to them; sending happens only on favorite toggle
|
||||||
|
|
||||||
var isMutual: Bool {
|
var isMutual: Bool {
|
||||||
isFavorite && theyFavoritedUs
|
isFavorite && theyFavoritedUs
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// We intentionally do not track when we last sent our npub; sending happens only on favorite toggle.
|
||||||
|
|
||||||
private static let storageKey = "chat.bitchat.favorites"
|
private static let storageKey = "chat.bitchat.favorites"
|
||||||
private static let keychainService = "chat.bitchat.favorites"
|
private static let keychainService = "chat.bitchat.favorites"
|
||||||
|
|
||||||
|
|||||||
@@ -74,6 +74,7 @@ final class MessageRouter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func sendFavoriteNotification(to peerID: String, isFavorite: Bool) {
|
func sendFavoriteNotification(to peerID: String, isFavorite: Bool) {
|
||||||
|
// Route via mesh when connected; else use Nostr
|
||||||
if mesh.isPeerConnected(peerID) {
|
if mesh.isPeerConnected(peerID) {
|
||||||
mesh.sendFavoriteNotification(to: peerID, isFavorite: isFavorite)
|
mesh.sendFavoriteNotification(to: peerID, isFavorite: isFavorite)
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -195,27 +195,6 @@ final class UnifiedPeerService: ObservableObject, TransportPeerEventsDelegate {
|
|||||||
let favoriteStatus = favorites[noiseKey] {
|
let favoriteStatus = favorites[noiseKey] {
|
||||||
peer.favoriteStatus = favoriteStatus
|
peer.favoriteStatus = favoriteStatus
|
||||||
peer.nostrPublicKey = favoriteStatus.peerNostrPublicKey
|
peer.nostrPublicKey = favoriteStatus.peerNostrPublicKey
|
||||||
} else {
|
|
||||||
// Check by nickname for reconnected peers
|
|
||||||
let favoriteByNickname = favorites.values.first {
|
|
||||||
$0.peerNickname == peerInfo.nickname
|
|
||||||
}
|
|
||||||
|
|
||||||
if let favorite = favoriteByNickname,
|
|
||||||
let noiseKey = peerInfo.noisePublicKey {
|
|
||||||
SecureLogger.debug("🔄 Found favorite for '\(peerInfo.nickname)' by nickname, updating noise key", category: .session)
|
|
||||||
|
|
||||||
// Update the favorite's key in persistence
|
|
||||||
favoritesService.updateNoisePublicKey(
|
|
||||||
from: favorite.peerNoisePublicKey,
|
|
||||||
to: noiseKey,
|
|
||||||
peerNickname: peerInfo.nickname
|
|
||||||
)
|
|
||||||
|
|
||||||
// Get updated favorite
|
|
||||||
peer.favoriteStatus = favoritesService.getFavoriteStatus(for: noiseKey)
|
|
||||||
peer.nostrPublicKey = peer.favoriteStatus?.peerNostrPublicKey ?? favorite.peerNostrPublicKey
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return peer
|
return peer
|
||||||
|
|||||||
@@ -420,6 +420,7 @@ final class ChatViewModel: ObservableObject, BitchatDelegate {
|
|||||||
private var geoSamplingSubs: [String: String] = [:] // subID -> geohash
|
private var geoSamplingSubs: [String: String] = [:] // subID -> geohash
|
||||||
private var lastGeoNotificationAt: [String: Date] = [:] // geohash -> last notify time
|
private var lastGeoNotificationAt: [String: Date] = [:] // geohash -> last notify time
|
||||||
|
|
||||||
|
|
||||||
// MARK: - Message Delivery Tracking
|
// MARK: - Message Delivery Tracking
|
||||||
|
|
||||||
// Delivery tracking
|
// Delivery tracking
|
||||||
@@ -4599,16 +4600,8 @@ final class ChatViewModel: ObservableObject, BitchatDelegate {
|
|||||||
// Register ephemeral session with identity manager
|
// Register ephemeral session with identity manager
|
||||||
SecureIdentityStateManager.shared.registerEphemeralSession(peerID: peerID)
|
SecureIdentityStateManager.shared.registerEphemeralSession(peerID: peerID)
|
||||||
|
|
||||||
// Check if we favorite this peer and resend notification on reconnect
|
// Intentionally do not resend favorites on reconnect.
|
||||||
// This ensures Nostr key mapping is maintained across reconnections
|
// We only send our npub when a favorite is toggled on, or if our npub changes.
|
||||||
if let peer = unifiedPeerService.getPeer(by: peerID),
|
|
||||||
let favoriteStatus = FavoritesPersistenceService.shared.getFavoriteStatus(for: peer.noisePublicKey),
|
|
||||||
favoriteStatus.isFavorite {
|
|
||||||
// Resend favorite notification with our Nostr key after a short delay
|
|
||||||
try? await Task.sleep(nanoseconds: TransportConfig.uiAsyncMediumSleepNs) // 0.5 seconds
|
|
||||||
meshService.sendFavoriteNotification(to: peerID, isFavorite: true)
|
|
||||||
SecureLogger.debug("📤 Resent favorite notification to reconnected peer \(peerID)", category: .session)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Force UI refresh
|
// Force UI refresh
|
||||||
objectWillChange.send()
|
objectWillChange.send()
|
||||||
@@ -4957,6 +4950,26 @@ final class ChatViewModel: ObservableObject, BitchatDelegate {
|
|||||||
messages.append(systemMessage)
|
messages.append(systemMessage)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Add a system message to the mesh timeline only (never geohash).
|
||||||
|
/// If mesh is currently active, also append to the visible `messages`.
|
||||||
|
@MainActor
|
||||||
|
private func addMeshOnlySystemMessage(_ content: String) {
|
||||||
|
let systemMessage = BitchatMessage(
|
||||||
|
sender: "system",
|
||||||
|
content: content,
|
||||||
|
timestamp: Date(),
|
||||||
|
isRelay: false
|
||||||
|
)
|
||||||
|
// Persist to mesh timeline
|
||||||
|
meshTimeline.append(systemMessage)
|
||||||
|
trimMeshTimelineIfNeeded()
|
||||||
|
// Only show inline if mesh is the active channel
|
||||||
|
if case .mesh = activeChannel {
|
||||||
|
messages.append(systemMessage)
|
||||||
|
}
|
||||||
|
objectWillChange.send()
|
||||||
|
}
|
||||||
|
|
||||||
/// Public helper to add a system message to the public chat timeline.
|
/// Public helper to add a system message to the public chat timeline.
|
||||||
/// Also persists the message into the active channel's backing store so it survives timeline rebinds.
|
/// Also persists the message into the active channel's backing store so it survives timeline rebinds.
|
||||||
@MainActor
|
@MainActor
|
||||||
@@ -5384,23 +5397,27 @@ final class ChatViewModel: ObservableObject, BitchatDelegate {
|
|||||||
SecureLogger.warning("⚠️ Cannot get Noise key for peer \(peerID)", category: .session)
|
SecureLogger.warning("⚠️ Cannot get Noise key for peer \(peerID)", category: .session)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
// Determine prior state to avoid duplicate system messages on repeated notifications
|
||||||
// Update the favorite relationship
|
let prior = FavoritesPersistenceService.shared.getFavoriteStatus(for: finalNoiseKey)?.theyFavoritedUs ?? false
|
||||||
|
|
||||||
|
// Update the favorite relationship (idempotent storage)
|
||||||
FavoritesPersistenceService.shared.updatePeerFavoritedUs(
|
FavoritesPersistenceService.shared.updatePeerFavoritedUs(
|
||||||
peerNoisePublicKey: finalNoiseKey,
|
peerNoisePublicKey: finalNoiseKey,
|
||||||
favorited: isFavorite,
|
favorited: isFavorite,
|
||||||
peerNickname: senderNickname,
|
peerNickname: senderNickname,
|
||||||
peerNostrPublicKey: nostrPubkey
|
peerNostrPublicKey: nostrPubkey
|
||||||
)
|
)
|
||||||
|
|
||||||
// If they favorited us and provided their Nostr key, ensure it's stored
|
// If they favorited us and provided their Nostr key, ensure it's stored (log only)
|
||||||
if isFavorite && nostrPubkey != nil {
|
if isFavorite && nostrPubkey != nil {
|
||||||
SecureLogger.info("💾 Storing Nostr key association for \(senderNickname): \(nostrPubkey!.prefix(16))...", category: .session)
|
SecureLogger.info("💾 Storing Nostr key association for \(senderNickname): \(nostrPubkey!.prefix(16))...", category: .session)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Show system message
|
// Only show a system message when the state changes, and only in mesh
|
||||||
let action = isFavorite ? "favorited" : "unfavorited"
|
if prior != isFavorite {
|
||||||
addSystemMessage("\(senderNickname) \(action) you")
|
let action = isFavorite ? "favorited" : "unfavorited"
|
||||||
|
addMeshOnlySystemMessage("\(senderNickname) \(action) you")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
|
|||||||
Reference in New Issue
Block a user