mirror of
https://github.com/permissionlesstech/bitchat-android.git
synced 2026-07-25 07:05:21 +00:00
Fix wifi aware routing (#534)
* Background persistence (#505) * persistence step 1 * fix build * messages in the background work, notifications not yet * app state store * DM icon shows up * notification launches when app is closed! * keep ui updated * lifecycle fixes * extensive logging, maybe revert later * send nickname in announcement * quit in notification * setting in about sheet * fix quit bitchat * lifecycle fixes * power mode based on background state * stats for both direciotns * fix graph persistence * better counting * count per device * only compute when debug sheet is open? untested * fix read receipts * fix read receipts fully * fix unread badge if messages have been read in focus * foreground promotion fix * fix app kill in notification * adjust to new tor * nice * about sheet design * bump version 1.6.0 (#524) * Automated update of relay data - Sun Dec 14 06:06:53 UTC 2025 * bump targetSdk (#526) * Automated update of relay data - Sun Dec 21 06:06:56 UTC 2025 * Automated update of relay data - Sun Dec 28 06:07:12 UTC 2025 * Prevent quit notification from reappearing (#530) * shutdown sequence * Prevent quit notification from reappearing * Restrict force-finish broadcast * Cancel quit shutdown on relaunch * fix(wifi-aware): use bindSocket and scoped IPv6 instead of bindProcessToNetwork * Merge branch 'upstream/main' into fix/wifi-aware-socket-binding * Fix Wi-Fi Aware connectivity and UI integration post-merge - Replace bindProcessToNetwork with bindSocket for VPN compatibility. - Implement Scoped IPv6 address resolution (aware0) for mesh routing. - Bridge Wi-Fi Aware incoming messages to AppStateStore for UI visibility. - Fix syntax errors and variable name conflicts in Debug UI. * Enhance Wi-Fi Aware robustness and debug UI display - Clean up transport resources (sockets, server sockets, network callbacks) immediately on peer disconnection. - Implement resolveScopedAddress to show scoped IPv6 (e.g., %aware0) in Debug UI. - Fix Map type mismatch warning in ChatViewModel bridge. - Filter self-ID from peer cleanup tables to prevent recursive self-removal. * Share GossipSyncManager across transports to prevent redundant message synchronization - Registered BluetoothMeshService's GossipSyncManager as a singleton in MeshServiceHolder. - Modified WifiAwareMeshService to use the shared GossipSyncManager if available. - Added background cleanup for peer mappings on socket disconnection. - Fixed Kotlin type mismatch during nickname map merging. * Restore VPN acquisition logic and improve peer cleanup - Revert removal of NET_CAPABILITY_NOT_VPN to allow hardware handle acquisition while VPN is active. - Refactor handlePeerDisconnection to more reliably cleanup initial and routed IDs. - Switch cleanup logging to debug level to reduce log noise. * fix wifi aware routing --------- Co-authored-by: GitHub Action <action@github.com> Co-authored-by: aidenvalue <>
This commit is contained in:
co-authored by
GitHub Action
aidenvalue <>
parent
bd272bb5ea
commit
c12af45b6b
@@ -744,6 +744,10 @@ class ChatViewModel(
|
||||
override fun didUpdatePeerList(peers: List<String>) {
|
||||
meshDelegateHandler.didUpdatePeerList(peers)
|
||||
}
|
||||
|
||||
fun onWifiPeersUpdated(peers: List<String>) {
|
||||
meshDelegateHandler.onWifiPeersUpdated(peers)
|
||||
}
|
||||
|
||||
override fun didReceiveChannelLeave(channel: String, fromPeer: String) {
|
||||
meshDelegateHandler.didReceiveChannelLeave(channel, fromPeer)
|
||||
|
||||
@@ -94,104 +94,123 @@ class MeshDelegateHandler(
|
||||
}
|
||||
}
|
||||
|
||||
private var blePeers: Set<String> = emptySet()
|
||||
private var wifiPeers: Set<String> = emptySet()
|
||||
|
||||
override fun didUpdatePeerList(peers: List<String>) {
|
||||
coroutineScope.launch {
|
||||
// Merge peers from multiple transports to avoid flapping
|
||||
val current = state.getConnectedPeersValue().toMutableSet()
|
||||
current.addAll(peers)
|
||||
state.setConnectedPeers(current.toList())
|
||||
state.setIsConnected(peers.isNotEmpty())
|
||||
notificationManager.showActiveUserNotification(peers)
|
||||
// Flush router outbox for any peers that just connected (and their noiseHex aliases)
|
||||
runCatching { com.bitchat.android.services.MessageRouter.tryGetInstance()?.onPeersUpdated(peers) }
|
||||
blePeers = peers.toSet()
|
||||
processPeerUpdate()
|
||||
}
|
||||
}
|
||||
|
||||
// Clean up channel members who disconnected
|
||||
channelManager.cleanupDisconnectedMembers(peers, getMyPeerID())
|
||||
fun onWifiPeersUpdated(peers: List<String>) {
|
||||
coroutineScope.launch {
|
||||
wifiPeers = peers.toSet()
|
||||
processPeerUpdate()
|
||||
}
|
||||
}
|
||||
|
||||
// Handle chat view migration based on current selection and new peer list
|
||||
state.getSelectedPrivateChatPeerValue()?.let { currentPeer ->
|
||||
val isNostrAlias = currentPeer.startsWith("nostr_")
|
||||
val isNoiseHex = currentPeer.length == 64 && currentPeer.matches(Regex("^[0-9a-fA-F]+$"))
|
||||
val isMeshEphemeral = currentPeer.length == 16 && currentPeer.matches(Regex("^[0-9a-fA-F]+$"))
|
||||
private suspend fun processPeerUpdate() {
|
||||
// Merge peers from multiple transports
|
||||
val mergedPeers = (blePeers + wifiPeers).toList()
|
||||
|
||||
// Update process-wide state as source of truth
|
||||
try { com.bitchat.android.services.AppStateStore.setPeers(mergedPeers) } catch (_: Exception) { }
|
||||
|
||||
if (isNostrAlias || isNoiseHex) {
|
||||
// Reverse case: Nostr/offline chat is open, and peer may have come online on mesh.
|
||||
// Resolve canonical target (prefer connected mesh peer if available)
|
||||
val canonical = com.bitchat.android.services.ConversationAliasResolver.resolveCanonicalPeerID(
|
||||
selectedPeerID = currentPeer,
|
||||
connectedPeers = peers,
|
||||
meshNoiseKeyForPeer = { pid -> getPeerInfo(pid)?.noisePublicKey },
|
||||
meshHasPeer = { pid -> peers.contains(pid) },
|
||||
nostrPubHexForAlias = { alias ->
|
||||
// Use GeohashAliasRegistry for geohash aliases, but for mesh favorites, derive from favorites mapping
|
||||
if (com.bitchat.android.nostr.GeohashAliasRegistry.contains(alias)) {
|
||||
com.bitchat.android.nostr.GeohashAliasRegistry.get(alias)
|
||||
} else {
|
||||
// Best-effort: derive pub hex from favorites mapping for mesh nostr_ aliases
|
||||
val prefix = alias.removePrefix("nostr_")
|
||||
val favs = try { com.bitchat.android.favorites.FavoritesPersistenceService.shared.getOurFavorites() } catch (_: Exception) { emptyList() }
|
||||
favs.firstNotNullOfOrNull { rel ->
|
||||
rel.peerNostrPublicKey?.let { s ->
|
||||
runCatching { com.bitchat.android.nostr.Bech32.decode(s) }.getOrNull()?.let { dec ->
|
||||
if (dec.first == "npub") dec.second.joinToString("") { b -> "%02x".format(b) } else null
|
||||
}
|
||||
state.setConnectedPeers(mergedPeers)
|
||||
state.setIsConnected(mergedPeers.isNotEmpty())
|
||||
notificationManager.showActiveUserNotification(mergedPeers)
|
||||
|
||||
// Flush router outbox for any peers that just connected (and their noiseHex aliases)
|
||||
runCatching { com.bitchat.android.services.MessageRouter.tryGetInstance()?.onPeersUpdated(mergedPeers) }
|
||||
|
||||
// Clean up channel members who disconnected
|
||||
channelManager.cleanupDisconnectedMembers(mergedPeers, getMyPeerID())
|
||||
|
||||
// Handle chat view migration based on current selection and new peer list
|
||||
state.getSelectedPrivateChatPeerValue()?.let { currentPeer ->
|
||||
val isNostrAlias = currentPeer.startsWith("nostr_")
|
||||
val isNoiseHex = currentPeer.length == 64 && currentPeer.matches(Regex("^[0-9a-fA-F]+$"))
|
||||
val isMeshEphemeral = currentPeer.length == 16 && currentPeer.matches(Regex("^[0-9a-fA-F]+$"))
|
||||
|
||||
if (isNostrAlias || isNoiseHex) {
|
||||
// Reverse case: Nostr/offline chat is open, and peer may have come online on mesh.
|
||||
// Resolve canonical target (prefer connected mesh peer if available)
|
||||
val canonical = com.bitchat.android.services.ConversationAliasResolver.resolveCanonicalPeerID(
|
||||
selectedPeerID = currentPeer,
|
||||
connectedPeers = mergedPeers,
|
||||
meshNoiseKeyForPeer = { pid -> getPeerInfo(pid)?.noisePublicKey },
|
||||
meshHasPeer = { pid -> mergedPeers.contains(pid) },
|
||||
nostrPubHexForAlias = { alias ->
|
||||
// Use GeohashAliasRegistry for geohash aliases, but for mesh favorites, derive from favorites mapping
|
||||
if (com.bitchat.android.nostr.GeohashAliasRegistry.contains(alias)) {
|
||||
com.bitchat.android.nostr.GeohashAliasRegistry.get(alias)
|
||||
} else {
|
||||
// Best-effort: derive pub hex from favorites mapping for mesh nostr_ aliases
|
||||
val prefix = alias.removePrefix("nostr_")
|
||||
val favs = try { com.bitchat.android.favorites.FavoritesPersistenceService.shared.getOurFavorites() } catch (_: Exception) { emptyList() }
|
||||
favs.firstNotNullOfOrNull { rel ->
|
||||
rel.peerNostrPublicKey?.let { s ->
|
||||
runCatching { com.bitchat.android.nostr.Bech32.decode(s) }.getOrNull()?.let { dec ->
|
||||
if (dec.first == "npub") dec.second.joinToString("") { b -> "%02x".format(b) } else null
|
||||
}
|
||||
}?.takeIf { it.startsWith(prefix, ignoreCase = true) }
|
||||
}
|
||||
},
|
||||
findNoiseKeyForNostr = { key -> com.bitchat.android.favorites.FavoritesPersistenceService.shared.findNoiseKey(key) }
|
||||
)
|
||||
if (canonical != currentPeer) {
|
||||
// Merge conversations and switch selection to the live mesh peer (or noiseHex)
|
||||
com.bitchat.android.services.ConversationAliasResolver.unifyChatsIntoPeer(state, canonical, listOf(currentPeer))
|
||||
state.setSelectedPrivateChatPeer(canonical)
|
||||
}
|
||||
} else if (isMeshEphemeral && !peers.contains(currentPeer)) {
|
||||
// Forward case: Mesh chat lost connection. If mutual favorite exists, migrate to Nostr (noiseHex)
|
||||
val favoriteRel = try {
|
||||
val info = getPeerInfo(currentPeer)
|
||||
val noiseKey = info?.noisePublicKey
|
||||
if (noiseKey != null) {
|
||||
com.bitchat.android.favorites.FavoritesPersistenceService.shared.getFavoriteStatus(noiseKey)
|
||||
} else null
|
||||
} catch (_: Exception) { null }
|
||||
|
||||
if (favoriteRel?.isMutual == true) {
|
||||
val noiseHex = favoriteRel.peerNoisePublicKey.joinToString("") { b -> "%02x".format(b) }
|
||||
if (noiseHex != currentPeer) {
|
||||
com.bitchat.android.services.ConversationAliasResolver.unifyChatsIntoPeer(
|
||||
state = state,
|
||||
targetPeerID = noiseHex,
|
||||
keysToMerge = listOf(currentPeer)
|
||||
)
|
||||
state.setSelectedPrivateChatPeer(noiseHex)
|
||||
}
|
||||
}?.takeIf { it.startsWith(prefix, ignoreCase = true) }
|
||||
}
|
||||
} else {
|
||||
privateChatManager.cleanupDisconnectedPeer(currentPeer)
|
||||
},
|
||||
findNoiseKeyForNostr = { key -> com.bitchat.android.favorites.FavoritesPersistenceService.shared.findNoiseKey(key) }
|
||||
)
|
||||
if (canonical != currentPeer) {
|
||||
// Merge conversations and switch selection to the live mesh peer (or noiseHex)
|
||||
com.bitchat.android.services.ConversationAliasResolver.unifyChatsIntoPeer(state, canonical, listOf(currentPeer))
|
||||
state.setSelectedPrivateChatPeer(canonical)
|
||||
}
|
||||
} else if (isMeshEphemeral && !mergedPeers.contains(currentPeer)) {
|
||||
// Forward case: Mesh chat lost connection. If mutual favorite exists, migrate to Nostr (noiseHex)
|
||||
val favoriteRel = try {
|
||||
val info = getPeerInfo(currentPeer)
|
||||
val noiseKey = info?.noisePublicKey
|
||||
if (noiseKey != null) {
|
||||
com.bitchat.android.favorites.FavoritesPersistenceService.shared.getFavoriteStatus(noiseKey)
|
||||
} else null
|
||||
} catch (_: Exception) { null }
|
||||
|
||||
if (favoriteRel?.isMutual == true) {
|
||||
val noiseHex = favoriteRel.peerNoisePublicKey.joinToString("") { b -> "%02x".format(b) }
|
||||
if (noiseHex != currentPeer) {
|
||||
com.bitchat.android.services.ConversationAliasResolver.unifyChatsIntoPeer(
|
||||
state = state,
|
||||
targetPeerID = noiseHex,
|
||||
keysToMerge = listOf(currentPeer)
|
||||
)
|
||||
state.setSelectedPrivateChatPeer(noiseHex)
|
||||
}
|
||||
} else {
|
||||
privateChatManager.cleanupDisconnectedPeer(currentPeer)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Global unification: for each connected peer, merge any offline/stable conversations
|
||||
// (noiseHex or nostr_<pub16>) into the connected peer's chat so there is only one chat per identity.
|
||||
peers.forEach { pid ->
|
||||
try {
|
||||
val info = getPeerInfo(pid)
|
||||
val noiseKey = info?.noisePublicKey ?: return@forEach
|
||||
val noiseHex = noiseKey.joinToString("") { b -> "%02x".format(b) }
|
||||
// Global unification: for each connected peer, merge any offline/stable conversations
|
||||
// (noiseHex or nostr_<pub16>) into the connected peer's chat so there is only one chat per identity.
|
||||
mergedPeers.forEach { pid ->
|
||||
try {
|
||||
val info = getPeerInfo(pid)
|
||||
val noiseKey = info?.noisePublicKey ?: return@forEach
|
||||
val noiseHex = noiseKey.joinToString("") { b -> "%02x".format(b) }
|
||||
|
||||
// Derive temp nostr key from favorites npub
|
||||
val npub = com.bitchat.android.favorites.FavoritesPersistenceService.shared.findNostrPubkey(noiseKey)
|
||||
val tempNostrKey: String? = try {
|
||||
if (npub != null) {
|
||||
val (hrp, data) = com.bitchat.android.nostr.Bech32.decode(npub)
|
||||
if (hrp == "npub") "nostr_${data.joinToString("") { b -> "%02x".format(b) }.take(16)}" else null
|
||||
} else null
|
||||
} catch (_: Exception) { null }
|
||||
// Derive temp nostr key from favorites npub
|
||||
val npub = com.bitchat.android.favorites.FavoritesPersistenceService.shared.findNostrPubkey(noiseKey)
|
||||
val tempNostrKey: String? = try {
|
||||
if (npub != null) {
|
||||
val (hrp, data) = com.bitchat.android.nostr.Bech32.decode(npub)
|
||||
if (hrp == "npub") "nostr_${data.joinToString("") { b -> "%02x".format(b) }.take(16)}" else null
|
||||
} else null
|
||||
} catch (_: Exception) { null }
|
||||
|
||||
unifyChatsIntoPeer(pid, listOfNotNull(noiseHex, tempNostrKey))
|
||||
} catch (_: Exception) { }
|
||||
}
|
||||
unifyChatsIntoPeer(pid, listOfNotNull(noiseHex, tempNostrKey))
|
||||
} catch (_: Exception) { }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user