Skip to content

Commit 998ee60

Browse files
authored
Nostr refactor simplify (#390)
* fix bug * geoDM receive works, send doesnt, and incoming message doesnt make sender appear in peer list * fix nostr dm * geohash dms work * Geohash DM UI: stop mixing Nostr DM temp chats into mesh offline list; ensure geohash DM senders are added to geohash people list only. Removed nostr_* sidebar append in PeopleSection; kept 64-hex mesh offline favorites. Verified build. * refactor * nice * works * merging nostr -> mesh works * tripple click to delete all * fix sidebar icon * remove hash * dms have correct recipient * works * wip unread badge * geohash dms wip * dms work
1 parent ba51826 commit 998ee60

21 files changed

+1509
-2043
lines changed

app/src/main/java/com/bitchat/android/favorites/FavoritesPersistenceService.kt

Lines changed: 128 additions & 113 deletions
Large diffs are not rendered by default.

app/src/main/java/com/bitchat/android/mesh/BluetoothMeshService.kt

Lines changed: 16 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -284,6 +284,13 @@ class BluetoothMeshService(private val context: Context) {
284284

285285
// Store fingerprint for the peer via centralized fingerprint manager
286286
val fingerprint = peerManager.storeFingerprintForPeer(newPeerID, publicKey)
287+
288+
// Index existing Nostr mapping by the new peerID if we have it
289+
try {
290+
com.bitchat.android.favorites.FavoritesPersistenceService.shared.findNostrPubkey(publicKey)?.let { npub ->
291+
com.bitchat.android.favorites.FavoritesPersistenceService.shared.updateNostrPublicKeyForPeerID(newPeerID, npub)
292+
}
293+
} catch (_: Exception) { }
287294

288295
// If there was a previous peer ID, remove it to avoid duplicates
289296
previousPeerID?.let { oldPeerID ->
@@ -539,25 +546,13 @@ class BluetoothMeshService(private val context: Context) {
539546
*/
540547
fun sendPrivateMessage(content: String, recipientPeerID: String, recipientNickname: String, messageID: String? = null) {
541548
if (content.isEmpty() || recipientPeerID.isEmpty()) return
542-
if (!recipientPeerID.startsWith("nostr_") && recipientNickname.isEmpty()) return
549+
if (recipientNickname.isEmpty()) return
543550

544551
serviceScope.launch {
545552
val finalMessageID = messageID ?: java.util.UUID.randomUUID().toString()
546553

547554
Log.d(TAG, "📨 Sending PM to $recipientPeerID: ${content.take(30)}...")
548555

549-
// Check if this is a Nostr contact (geohash DM)
550-
if (recipientPeerID.startsWith("nostr_")) {
551-
// Get NostrGeohashService instance and send via Nostr
552-
try {
553-
val nostrGeohashService = com.bitchat.android.nostr.NostrGeohashService.getInstance(context.applicationContext as android.app.Application)
554-
nostrGeohashService.sendNostrGeohashDM(content, recipientPeerID, finalMessageID, myPeerID)
555-
} catch (e: Exception) {
556-
Log.e(TAG, "Failed to send Nostr geohash DM: ${e.message}")
557-
}
558-
return@launch
559-
}
560-
561556
// Check if we have an established Noise session
562557
if (encryptionService.hasEstablishedSession(recipientPeerID)) {
563558
try {
@@ -625,15 +620,14 @@ class BluetoothMeshService(private val context: Context) {
625620
serviceScope.launch {
626621
Log.d(TAG, "📖 Sending read receipt for message $messageID to $recipientPeerID")
627622

628-
// Check if this is a Nostr contact (geohash DM)
629-
if (recipientPeerID.startsWith("nostr_")) {
630-
// Get NostrGeohashService instance and send read receipt via Nostr
631-
try {
632-
val nostrGeohashService = com.bitchat.android.nostr.NostrGeohashService.getInstance(context.applicationContext as android.app.Application)
633-
nostrGeohashService.sendNostrGeohashReadReceipt(messageID, recipientPeerID, myPeerID)
634-
} catch (e: Exception) {
635-
Log.e(TAG, "Failed to send Nostr geohash read receipt: ${e.message}")
636-
}
623+
// Route geohash read receipts via MessageRouter instead of here
624+
val geo = runCatching { com.bitchat.android.services.MessageRouter.tryGetInstance() }.getOrNull()
625+
val isGeoAlias = try {
626+
val map = com.bitchat.android.nostr.GeohashAliasRegistry.snapshot()
627+
map.containsKey(recipientPeerID)
628+
} catch (_: Exception) { false }
629+
if (isGeoAlias && geo != null) {
630+
geo.sendReadReceipt(com.bitchat.android.model.ReadReceipt(messageID), recipientPeerID)
637631
return@launch
638632
}
639633

app/src/main/java/com/bitchat/android/mesh/MessageHandler.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -452,7 +452,9 @@ class MessageHandler(private val myPeerID: String) {
452452
if (noiseKey != null) {
453453
com.bitchat.android.favorites.FavoritesPersistenceService.shared.updatePeerFavoritedUs(noiseKey, isFavorite)
454454
if (npub != null) {
455+
// Index by noise key and current mesh peerID for fast Nostr routing
455456
com.bitchat.android.favorites.FavoritesPersistenceService.shared.updateNostrPublicKey(noiseKey, npub)
457+
com.bitchat.android.favorites.FavoritesPersistenceService.shared.updateNostrPublicKeyForPeerID(fromPeerID, npub)
456458
}
457459

458460
// Determine iOS-style guidance text
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package com.bitchat.android.nostr
2+
3+
import java.util.concurrent.ConcurrentHashMap
4+
5+
/**
6+
* GeohashAliasRegistry
7+
* - Global, thread-safe registry for alias->Nostr pubkey mappings (e.g., nostr_<pub16> -> pubkeyHex)
8+
* - Allows non-UI components (e.g., MessageRouter) to resolve geohash DM aliases without depending on UI ViewModels
9+
*/
10+
object GeohashAliasRegistry {
11+
private val map: MutableMap<String, String> = ConcurrentHashMap()
12+
13+
fun put(alias: String, pubkeyHex: String) {
14+
map[alias] = pubkeyHex
15+
}
16+
17+
fun get(alias: String): String? = map[alias]
18+
19+
fun contains(alias: String): Boolean = map.containsKey(alias)
20+
21+
fun snapshot(): Map<String, String> = HashMap(map)
22+
23+
fun clear() { map.clear() }
24+
}
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
package com.bitchat.android.nostr
2+
3+
import android.app.Application
4+
import android.util.Log
5+
import com.bitchat.android.model.BitchatMessage
6+
import com.bitchat.android.ui.ChatState
7+
import com.bitchat.android.ui.MessageManager
8+
import kotlinx.coroutines.CoroutineScope
9+
import kotlinx.coroutines.Dispatchers
10+
import kotlinx.coroutines.launch
11+
import kotlinx.coroutines.withContext
12+
import java.util.Date
13+
14+
/**
15+
* GeohashMessageHandler
16+
* - Processes kind=20000 Nostr events for geohash channels
17+
* - Updates repository for participants + nicknames
18+
* - Emits messages to MessageManager
19+
*/
20+
class GeohashMessageHandler(
21+
private val application: Application,
22+
private val state: ChatState,
23+
private val messageManager: MessageManager,
24+
private val repo: GeohashRepository,
25+
private val scope: CoroutineScope
26+
) {
27+
companion object { private const val TAG = "GeohashMessageHandler" }
28+
29+
// Simple event deduplication
30+
private val processedIds = ArrayDeque<String>()
31+
private val seen = HashSet<String>()
32+
private val max = 2000
33+
34+
private fun dedupe(id: String): Boolean {
35+
if (seen.contains(id)) return true
36+
seen.add(id)
37+
processedIds.addLast(id)
38+
if (processedIds.size > max) {
39+
val old = processedIds.removeFirst()
40+
seen.remove(old)
41+
}
42+
return false
43+
}
44+
45+
fun onEvent(event: NostrEvent, subscribedGeohash: String) {
46+
scope.launch(Dispatchers.Default) {
47+
try {
48+
if (event.kind != 20000) return@launch
49+
val tagGeo = event.tags.firstOrNull { it.size >= 2 && it[0] == "g" }?.getOrNull(1)
50+
if (tagGeo == null || !tagGeo.equals(subscribedGeohash, true)) return@launch
51+
if (dedupe(event.id)) return@launch
52+
53+
// PoW validation (if enabled)
54+
val pow = PoWPreferenceManager.getCurrentSettings()
55+
if (pow.enabled && pow.difficulty > 0) {
56+
if (!NostrProofOfWork.validateDifficulty(event, pow.difficulty)) return@launch
57+
}
58+
59+
// Blocked users check
60+
if (com.bitchat.android.ui.DataManager(application).isGeohashUserBlocked(event.pubkey)) return@launch
61+
62+
// Update repository (participants, nickname, teleport)
63+
// Update repository on a background-safe path; repository will post updates to LiveData
64+
repo.updateParticipant(subscribedGeohash, event.pubkey, Date(event.createdAt * 1000L))
65+
event.tags.find { it.size >= 2 && it[0] == "n" }?.let { repo.cacheNickname(event.pubkey, it[1]) }
66+
event.tags.find { it.size >= 2 && it[0] == "t" && it[1] == "teleport" }?.let { repo.markTeleported(event.pubkey) }
67+
// Register a geohash DM alias for this participant so MessageRouter can route DMs via Nostr
68+
try {
69+
com.bitchat.android.nostr.GeohashAliasRegistry.put("nostr_${event.pubkey.take(16)}", event.pubkey)
70+
} catch (_: Exception) { }
71+
72+
// Skip our own events for message emission
73+
val my = NostrIdentityBridge.deriveIdentity(subscribedGeohash, application)
74+
if (my.publicKeyHex.equals(event.pubkey, true)) return@launch
75+
76+
val isTeleportPresence = event.tags.any { it.size >= 2 && it[0] == "t" && it[1] == "teleport" } &&
77+
event.content.trim().isEmpty()
78+
if (isTeleportPresence) return@launch
79+
80+
val senderName = repo.displayNameForNostrPubkeyUI(event.pubkey)
81+
val msg = BitchatMessage(
82+
id = event.id,
83+
sender = senderName,
84+
content = event.content,
85+
timestamp = Date(event.createdAt * 1000L),
86+
isRelay = false,
87+
originalSender = repo.displayNameForNostrPubkey(event.pubkey),
88+
senderPeerID = "nostr:${event.pubkey.take(8)}",
89+
mentions = null,
90+
channel = "#$subscribedGeohash",
91+
powDifficulty = try { NostrProofOfWork.calculateDifficulty(event.id).takeIf { it > 0 } } catch (_: Exception) { null }
92+
)
93+
withContext(Dispatchers.Main) { messageManager.addChannelMessage("geo:$subscribedGeohash", msg) }
94+
} catch (e: Exception) {
95+
Log.e(TAG, "onEvent error: ${e.message}")
96+
}
97+
}
98+
}
99+
}

0 commit comments

Comments
 (0)