refactor(relay): drop LocalRelayConfig and local relay runtime forwarding

Removes LocalRelayConfig, LocalRelayWritePolicy, the LOCAL value in
RelaySetType, and isLocalRelayUrl from RelayConfig.kt. Strips local
relay state, forwarding paths, lifecycle pause/resume, and the
own-notes OutboxRouter branch from the relay layer.
This commit is contained in:
Barry Deen
2026-05-15 11:36:10 -04:00
parent 296b5f9702
commit 008d533ac5
4 changed files with 4 additions and 146 deletions
@@ -277,15 +277,6 @@ class OutboxRouter(
targetedRelays.addAll(relayPool.getRelayUrls())
}
// Also query local relay for own notes
if (pubkey == relayPool.localRelayUserPubkey) {
val localUrl = relayPool.getLocalRelayUrl()
if (localUrl != null) {
relayPool.sendToLocalRelay(ClientMessage.req(subId, filter))
targetedRelays.add(localUrl)
}
}
return targetedRelays
}
@@ -2,22 +2,11 @@ package com.wisp.app.relay
import kotlinx.serialization.Serializable
enum class LocalRelayWritePolicy { OWN_NOTES, TAGGED, ALL_NOTES }
@Serializable
data class LocalRelayConfig(
val url: String,
val enabled: Boolean = true,
val writePolicy: LocalRelayWritePolicy = LocalRelayWritePolicy.OWN_NOTES,
val kinds: Set<Int> = setOf(1, 1059, 9735)
)
enum class RelaySetType(val displayName: String, val eventKind: Int) {
GENERAL("General", 10002),
DM("DM", 10050),
SEARCH("Search", 10007),
BLOCKED("Blocked", 10006),
LOCAL("Local", 0)
BLOCKED("Blocked", 10006)
}
@Serializable
@@ -67,18 +56,5 @@ data class RelayConfig(
return true
}
private val LOCAL_HOST_REGEX = Regex(
"^ws://(" +
"localhost|" +
"127\\.0\\.0\\.1|" +
"10\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}|" +
"192\\.168\\.\\d{1,3}\\.\\d{1,3}|" +
"172\\.(1[6-9]|2\\d|3[01])\\.\\d{1,3}\\.\\d{1,3}|" +
"[\\w.-]+:\\d+" + // any host with explicit port
")(:\\d+)?(/.*)?$"
)
/** Returns true if the URL is a local/private relay (ws:// with localhost, loopback, or private IP). */
fun isLocalRelayUrl(url: String): Boolean = LOCAL_HOST_REGEX.matches(url)
}
}
@@ -97,7 +97,6 @@ class RelayLifecycleManager(
Log.d("RLC", "[Lifecycle] onAppPause — connectedCount=${relayPool.connectedCount.value}")
relayPool.appIsActive = false
relayPool.healthTracker?.closeAllSessions()
relayPool.pauseLocalRelay()
}
/**
@@ -112,7 +111,6 @@ class RelayLifecycleManager(
// Set suppression window to prevent network-change reconnects from
// firing shortly after this resume and causing a double reconnect.
resumeReconnectUntilMs = System.currentTimeMillis() + RESUME_SUPPRESSION_MS
relayPool.resumeLocalRelay()
reconnect(force = force)
}
@@ -47,11 +47,6 @@ class RelayPool(private val prefs: SharedPreferences? = null) {
private var blockedUrls = emptySet<String>()
fun getBlockedUrls(): Set<String> = blockedUrls
// --- Local relay ---
private var localRelay: Relay? = null
private var localRelayConfig: LocalRelayConfig? = null
@Volatile var localRelayUserPubkey: String? = null
/** URLs tagged as recipient DM delivery relays (tier 2 for AUTH). */
private val dmDeliveryTargets: MutableSet<String> = java.util.concurrent.ConcurrentHashMap.newKeySet()
@@ -409,10 +404,6 @@ class RelayPool(private val prefs: SharedPreferences? = null) {
}
_events.tryEmit(msg.event)
_relayEvents.tryEmit(RelayEvent(msg.event, relay.config.url, msg.subscriptionId))
// Forward to local relay (fire-and-forget)
if (localRelay != null && relay !== localRelay && shouldForwardToLocalRelay(msg.event)) {
localRelay?.send(ClientMessage.event(msg.event))
}
subEventCounts.getOrPut(msg.subscriptionId) { java.util.concurrent.atomic.AtomicInteger(0) }.incrementAndGet()
if (msg.subscriptionId.startsWith("feed")) {
val count = ++feedEventCounter
@@ -607,10 +598,6 @@ class RelayPool(private val prefs: SharedPreferences? = null) {
if (isEvent && appIsActive) healthTracker?.onEventSent(relay.config.url, message.length)
}
}
// Also forward to local relay for own published events
if (isEvent && localRelay != null && localRelayConfig?.enabled == true) {
localRelay?.send(message)
}
return sentCount
}
@@ -846,7 +833,7 @@ class RelayPool(private val prefs: SharedPreferences? = null) {
*/
fun connectEphemeralRelay(url: String) {
if (url in blockedUrls) return
if (!RelayConfig.isValidUrl(url) && !RelayConfig.isLocalRelayUrl(url)) return
if (!RelayConfig.isValidUrl(url)) return
if (ephemeralRelays.containsKey(url) || relayIndex.containsKey(url)) return
if (ephemeralRelays.size >= MAX_EPHEMERAL) return
ephemeralRelays.computeIfAbsent(url) {
@@ -870,7 +857,7 @@ class RelayPool(private val prefs: SharedPreferences? = null) {
): Boolean {
if (url in blockedUrls) return false
if (!skipBadCheck && healthTracker?.isBad(url) == true) return false
if (!RelayConfig.isValidUrl(url) && !RelayConfig.isLocalRelayUrl(url)) return false
if (!RelayConfig.isValidUrl(url)) return false
// Check cooldown for failed relays
val cooldownUntil = relayCooldowns[url]
@@ -1199,7 +1186,7 @@ class RelayPool(private val prefs: SharedPreferences? = null) {
*/
fun preConnectEphemeral(url: String) {
if (url in blockedUrls) return
if (!RelayConfig.isValidUrl(url) && !RelayConfig.isLocalRelayUrl(url)) return
if (!RelayConfig.isValidUrl(url)) return
if (ephemeralRelays.containsKey(url)) return
if (ephemeralRelays.size >= MAX_EPHEMERAL) return
val relay = Relay(RelayConfig(url, read = true, write = false), client, scope)
@@ -1272,97 +1259,6 @@ class RelayPool(private val prefs: SharedPreferences? = null) {
}
}
// --- Local relay management ---
fun updateLocalRelay(config: LocalRelayConfig?, userPubkey: String?) {
localRelayUserPubkey = userPubkey
val oldRelay = localRelay
val oldUrl = oldRelay?.config?.url
if (config == null || !config.enabled) {
// Remove local relay
if (oldRelay != null) {
oldRelay.forceDisconnect()
cancelRelayJobs(oldRelay.config.url)
relayIndex.remove(oldRelay.config.url)
localRelay = null
localRelayConfig = null
Log.d("RLC", "[Pool] local relay removed: $oldUrl")
}
return
}
localRelayConfig = config
if (oldUrl == config.url && oldRelay != null) {
// URL unchanged — just update config, reconnect if needed.
// Re-enable auto-reconnect in case the relay was paused via pauseLocalRelay().
oldRelay.reconnectEnabled = true
if (!oldRelay.isConnected) oldRelay.connect()
return
}
// Disconnect old if URL changed
if (oldRelay != null) {
oldRelay.forceDisconnect()
cancelRelayJobs(oldRelay.config.url)
relayIndex.remove(oldRelay.config.url)
}
// Create new local relay connection
val relayConfig = RelayConfig(config.url, read = true, write = true)
val relay = Relay(relayConfig, client)
localRelay = relay
relayIndex[config.url] = relay
collectMessages(relay)
relay.connect()
Log.d("RLC", "[Pool] local relay connected: ${config.url}")
}
/** Disconnect local relay socket but preserve references for cheap resume. */
fun pauseLocalRelay() {
val relay = localRelay ?: return
relay.reconnectEnabled = false
relay.disconnect()
Log.d("RLC", "[Pool] local relay paused: ${relay.config.url}")
}
/** Reconnect local relay if still configured and enabled. Safe to call repeatedly. */
fun resumeLocalRelay() {
val relay = localRelay ?: return
val cfg = localRelayConfig ?: return
if (!cfg.enabled) return
relay.reconnectEnabled = true
relay.resetBackoff()
if (!relay.isConnected) relay.connect()
Log.d("RLC", "[Pool] local relay resumed: ${relay.config.url}")
}
fun getLocalRelayUrl(): String? = localRelayConfig?.url
/** Send a message to the local relay if configured and connected. Fire-and-forget. */
fun sendToLocalRelay(message: String): Boolean {
val relay = localRelay ?: return false
if (localRelayConfig?.enabled != true) return false
return relay.send(message)
}
private fun shouldForwardToLocalRelay(event: NostrEvent): Boolean {
val config = localRelayConfig ?: return false
if (!config.enabled) return false
if (localRelay == null) return false
if (event.kind !in config.kinds) return false
return when (config.writePolicy) {
LocalRelayWritePolicy.OWN_NOTES -> event.pubkey == localRelayUserPubkey
LocalRelayWritePolicy.TAGGED -> {
event.pubkey == localRelayUserPubkey ||
event.tags.any { it.size >= 2 && it[0] == "p" && it[1] == localRelayUserPubkey }
}
LocalRelayWritePolicy.ALL_NOTES -> true
}
}
fun disconnectAll() {
relays.forEach { it.forceDisconnect() }
relays.clear()
@@ -1374,9 +1270,6 @@ class RelayPool(private val prefs: SharedPreferences? = null) {
ephemeralRelays.clear()
ephemeralLastUsed.clear()
relayCooldowns.clear()
localRelay?.forceDisconnect()
localRelay = null
localRelayConfig = null
relayIndex.clear()
relayJobs.values.forEach { it.cancel() }
relayJobs.clear()