mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2026-10-06 03:38:23 +00:00
refactor(napplet): route blob fetches through the shared OkHttpClientFactory
The t4 onion-location proposal hand-wired OnionLocationInterceptor + OnionUrlRewriteInterceptor into NappletResourceFetcher's private, torPort-keyed OkHttpClient. That reached onion-routing parity but duplicated the exact wiring OkHttpClientFactory already does, and the private client still missed the local Blossom cache redirect, the shared connection pool / HTTP-2 keepalive, and SurgeDns. Inject the app-wide client instead: NappletResourceFetcher now takes a () -> OkHttpClient and the broker supplies `okHttpClients.getHttpClient(useProxy = true)` — the same DualHttpClientManager path the image pipeline uses. Behavior-preserving for Tor (proxied when Tor is active, clearnet when not) and, since these are sha256 blobs, the shared Blossom-cache redirect is now a feature, not a loss. Drops the private client + its cache and the hand-wired interceptors. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
6209378b83
commit
c797033ba5
@@ -320,7 +320,8 @@ class NappletBrokerService : Service() {
|
||||
context = applicationContext,
|
||||
ledger = ledger,
|
||||
storage = storage,
|
||||
torPort = { Amethyst.instance.torManager.activePortOrNull.value ?: -1 },
|
||||
// Prefer Tor when active; the shared manager falls back to clearnet when it isn't.
|
||||
httpClient = { Amethyst.instance.okHttpClients.getHttpClient(useProxy = true) },
|
||||
).broker()
|
||||
cachedBroker = account to broker
|
||||
return broker
|
||||
|
||||
+6
-5
@@ -56,6 +56,7 @@ import com.vitorpamplona.quartz.nip47WalletConnect.rpc.PayInvoiceSuccessResponse
|
||||
import com.vitorpamplona.quartz.utils.sha256.sha256
|
||||
import kotlinx.coroutines.CompletableDeferred
|
||||
import kotlinx.coroutines.withTimeout
|
||||
import okhttp3.OkHttpClient
|
||||
import java.io.ByteArrayInputStream
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
@@ -70,14 +71,14 @@ class AccountNappletGateways(
|
||||
private val context: Context,
|
||||
private val ledger: NappletPermissionLedger,
|
||||
private val storage: NappletStorage,
|
||||
private val torPort: () -> Int,
|
||||
private val httpClient: () -> OkHttpClient,
|
||||
) {
|
||||
private val consentSummary = NappletConsentSummary(context)
|
||||
|
||||
// Share the app-wide OnionLocationCache so any `Onion-Location` learned
|
||||
// elsewhere (NIP-11 docs, relay handshakes, image hosts, money endpoints)
|
||||
// also benefits napplet HTTP blob fetches over Tor — and vice versa.
|
||||
private val resourceFetcher = NappletResourceFetcher(account, torPort, Amethyst.instance.onionLocationCache)
|
||||
// Reuse the app-wide HTTP client so napplet blob fetches inherit the same Tor
|
||||
// routing, Onion-Location discovery/rewriting, Blossom cache and pool as the
|
||||
// rest of the app, instead of a private client that has to re-wire all of it.
|
||||
private val resourceFetcher = NappletResourceFetcher(account, httpClient)
|
||||
private val identityReader = AccountIdentityReader(account)
|
||||
|
||||
fun broker(): NappletBroker {
|
||||
|
||||
+12
-40
@@ -23,9 +23,6 @@ package com.vitorpamplona.amethyst.napplet.gateways
|
||||
import android.util.Base64
|
||||
import com.vitorpamplona.amethyst.commons.napplet.NappletResource
|
||||
import com.vitorpamplona.amethyst.model.Account
|
||||
import com.vitorpamplona.amethyst.service.okhttp.OnionLocationCache
|
||||
import com.vitorpamplona.amethyst.service.okhttp.OnionLocationInterceptor
|
||||
import com.vitorpamplona.amethyst.service.okhttp.OnionUrlRewriteInterceptor
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Address
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.client.accessories.fetchAll
|
||||
@@ -43,8 +40,6 @@ import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import java.net.InetSocketAddress
|
||||
import java.net.Proxy
|
||||
import java.net.URLDecoder
|
||||
|
||||
/**
|
||||
@@ -53,22 +48,17 @@ import java.net.URLDecoder
|
||||
* `https:`, and `blossom:` URLs; blossom blobs are content-addressed and **sha256-verified** before
|
||||
* returning, so a wrong server can never substitute the blob.
|
||||
*
|
||||
* Owns a Tor-routed [OkHttpClient], cached and rebuilt only when the active Tor port ([torPort])
|
||||
* changes. Built per account (so it reads the right Blossom server list); consent is enforced by the
|
||||
* broker before [fetch] ever runs.
|
||||
* Network goes through the app-wide [OkHttpClient] supplied by [httpClient] (the shared
|
||||
* [com.vitorpamplona.amethyst.service.okhttp.DualHttpClientManager]). Reusing it — rather than
|
||||
* standing up a private client — means napplet blob fetches inherit the same Tor routing,
|
||||
* passive `Onion-Location` discovery + `.onion` rewriting, local Blossom cache redirect,
|
||||
* connection pool and DNS as every other HTTP role. Built per account (so it reads the right
|
||||
* Blossom server list); consent is enforced by the broker before [fetch] ever runs.
|
||||
*/
|
||||
class NappletResourceFetcher(
|
||||
private val account: Account,
|
||||
private val torPort: () -> Int,
|
||||
// Shared with the rest of the app so an `Onion-Location` learned via any
|
||||
// OkHttp client (e.g. an NIP-11 fetch on a relay socket) transparently
|
||||
// applies to napplet blob fetches too — and vice versa. Optional so unit
|
||||
// tests can construct this without standing up the cache.
|
||||
private val onionCache: OnionLocationCache? = null,
|
||||
private val httpClient: () -> OkHttpClient,
|
||||
) {
|
||||
// Reused blob HTTP client, keyed by the active Tor port (see client()).
|
||||
private var cachedHttp: Pair<Int, OkHttpClient>? = null
|
||||
|
||||
/** Fetches an https/data/blossom resource, or null if unsupported/unavailable. */
|
||||
suspend fun fetch(url: String): NappletResource? =
|
||||
withContext(Dispatchers.IO) {
|
||||
@@ -150,30 +140,12 @@ class NappletResourceFetcher(
|
||||
}
|
||||
|
||||
/**
|
||||
* Tor-routed OkHttp client for host-side blob fetches (the applet has no direct network).
|
||||
* Cached and reused for connection pooling; rebuilt only when the Tor proxy port changes.
|
||||
* The app-wide OkHttp client for host-side blob fetches (the applet has no direct network).
|
||||
* The shared manager already routes through Tor when active, captures `Onion-Location` and
|
||||
* rewrites `.onion`s, bridges the local Blossom cache, and pools connections — so there is no
|
||||
* private client to build or cache here.
|
||||
*/
|
||||
@Synchronized
|
||||
private fun client(): OkHttpClient {
|
||||
val port = torPort()
|
||||
cachedHttp?.let { (cachedPort, client) -> if (cachedPort == port) return client }
|
||||
// Both variants passively capture `Onion-Location` headers into the
|
||||
// shared cache. The Tor-routed variant additionally rewrites outbound
|
||||
// URLs to known `.onion`s so applet blob fetches over Tor avoid exit
|
||||
// nodes when the destination has advertised an onion. Clearnet variant
|
||||
// never rewrites (DNS would fail on `.onion`).
|
||||
val builder = OkHttpClient.Builder()
|
||||
if (port > 0) {
|
||||
builder.proxy(Proxy(Proxy.Type.SOCKS, InetSocketAddress("127.0.0.1", port)))
|
||||
}
|
||||
onionCache?.let { builder.addInterceptor(OnionLocationInterceptor(it)) }
|
||||
if (port > 0) {
|
||||
onionCache?.let { builder.addInterceptor(OnionUrlRewriteInterceptor(it)) }
|
||||
}
|
||||
val client = builder.build()
|
||||
cachedHttp = port to client
|
||||
return client
|
||||
}
|
||||
private fun client(): OkHttpClient = httpClient()
|
||||
|
||||
/**
|
||||
* Fetches a `blossom:<sha256>` (or `blossom://<sha256>`) blob from the user's Blossom servers
|
||||
|
||||
Reference in New Issue
Block a user