Nip17 dms to extend the mesh (#313)

* favorite each other

* show favorites as system messsages

* wip show glove when offline but mayb edoesnt work

* send and receive works

* wip kinda works but no

* getting there

* kinda

* show offline peers in peer list

* kinda wonky but almost works

* fixes

* nostr user goes offline works

* nostr -> mesh works

* handoff works

* background message processing

* read works

* seen message store was missing
This commit is contained in:
callebtc
2025-08-25 18:38:10 +02:00
committed by GitHub
parent 941be1b98f
commit 6fe6e049ad
15 changed files with 1153 additions and 173 deletions
@@ -47,6 +47,11 @@ data class FavoriteRelationship(
}
}
interface FavoritesChangeListener {
fun onFavoriteChanged(noiseKeyHex: String)
fun onAllCleared()
}
/**
* Manages favorites with Noise↔Nostr mapping
* Singleton pattern matching iOS implementation
@@ -77,6 +82,7 @@ class FavoritesPersistenceService private constructor(private val context: Conte
private val stateManager = SecureIdentityStateManager(context)
private val gson = Gson()
private val favorites = mutableMapOf<String, FavoriteRelationship>() // noiseKeyHex -> relationship
private val listeners = mutableListOf<FavoritesChangeListener>()
init {
loadFavorites()
@@ -133,6 +139,7 @@ class FavoritesPersistenceService private constructor(private val context: Conte
}
saveFavorites()
notifyChanged(keyHex)
Log.d(TAG, "Updated Nostr pubkey association for ${keyHex.take(16)}...")
}
@@ -164,7 +171,8 @@ class FavoritesPersistenceService private constructor(private val context: Conte
favorites[keyHex] = updated
saveFavorites()
notifyChanged(keyHex)
Log.d(TAG, "Updated favorite status for $nickname: $isFavorite")
}
@@ -182,6 +190,7 @@ class FavoritesPersistenceService private constructor(private val context: Conte
)
favorites[keyHex] = updated
saveFavorites()
notifyChanged(keyHex)
Log.d(TAG, "Updated peer favorited us for ${keyHex.take(16)}...: $theyFavoritedUs")
}
@@ -208,13 +217,19 @@ class FavoritesPersistenceService private constructor(private val context: Conte
favorites.clear()
saveFavorites()
Log.i(TAG, "Cleared all favorites")
notifyAllCleared()
}
/**
* Find Noise key by Nostr pubkey
*/
fun findNoiseKey(forNostrPubkey: String): ByteArray? {
return favorites.values.firstOrNull { it.peerNostrPublicKey == forNostrPubkey }?.peerNoisePublicKey
val targetHex = normalizeNostrKeyToHex(forNostrPubkey) ?: return null
return favorites.values.firstOrNull { rel ->
rel.peerNostrPublicKey?.let { stored ->
normalizeNostrKeyToHex(stored)
} == targetHex
}?.peerNoisePublicKey
}
/**
@@ -265,6 +280,40 @@ class FavoritesPersistenceService private constructor(private val context: Conte
Log.e(TAG, "Failed to save favorites: ${e.message}")
}
}
// MARK: - Listeners
fun addListener(listener: FavoritesChangeListener) {
synchronized(listeners) {
if (!listeners.contains(listener)) listeners.add(listener)
}
}
fun removeListener(listener: FavoritesChangeListener) {
synchronized(listeners) { listeners.remove(listener) }
}
private fun notifyChanged(noiseKeyHex: String) {
val snapshot = synchronized(listeners) { listeners.toList() }
snapshot.forEach { runCatching { it.onFavoriteChanged(noiseKeyHex) } }
}
private fun notifyAllCleared() {
val snapshot = synchronized(listeners) { listeners.toList() }
snapshot.forEach { runCatching { it.onAllCleared() } }
}
/**
* Normalize a Nostr public key string (npub bech32 or hex) to lowercase hex for comparison
*/
private fun normalizeNostrKeyToHex(value: String): String? {
return try {
if (value.startsWith("npub1")) {
val (hrp, data) = com.bitchat.android.nostr.Bech32.decode(value)
if (hrp != "npub") return null
data.joinToString("") { "%02x".format(it) }
} else {
// Assume hex
value.lowercase()
}
} catch (_: Exception) { null }
}
}
/**