Defers NIP05 Resolver/NamecoinResolver Builder until needed

This commit is contained in:
Vitor Pamplona
2026-03-26 11:07:56 -04:00
parent d73dd662c6
commit 44c39b01ea
19 changed files with 57 additions and 50 deletions
@@ -158,29 +158,36 @@ class AppModules(
// Offers easy methods to know when connections are happening through Tor or not // Offers easy methods to know when connections are happening through Tor or not
val roleBasedHttpClientBuilder = RoleBasedHttpClientBuilder(okHttpClients, torPrefs.value) val roleBasedHttpClientBuilder = RoleBasedHttpClientBuilder(okHttpClients, torPrefs.value)
// Custom fetcher that considers tor settings and avoids forwarding. val namecoinResolver by
val nip05Fetcher = OkHttpNip05Fetcher(roleBasedHttpClientBuilder::okHttpClientForNip05) lazy {
Log.d("AppModules", "Namecoin Resolver Init")
val namecoinResolver = NamecoinNameResolver(
NamecoinNameResolver( electrumxClient =
electrumxClient = ElectrumXClient(
ElectrumXClient( socketFactory = { roleBasedHttpClientBuilder.socketFactoryForNip05() },
socketFactory = { roleBasedHttpClientBuilder.socketFactoryForNip05() }, ),
), serverListProvider = {
serverListProvider = { // User-configured custom servers take priority
// User-configured custom servers take priority namecoinPrefs.customServersOrNull
namecoinPrefs.customServersOrNull ?: if (roleBasedHttpClientBuilder.shouldUseTorForNIP05("https://electrumx.example.com")) {
?: if (roleBasedHttpClientBuilder.shouldUseTorForNIP05("https://electrumx.example.com")) { TOR_ELECTRUMX_SERVERS
TOR_ELECTRUMX_SERVERS } else {
} else { DEFAULT_ELECTRUMX_SERVERS
DEFAULT_ELECTRUMX_SERVERS }
} },
}, )
) }
val nip05Client = Nip05Client(nip05Fetcher, namecoinResolver)
// Application-wide block height request cache // Application-wide block height request cache
val otsBlockHeightCache by lazy { OtsBlockHeightCache() } val otsBlockHeightCache by lazy { OtsBlockHeightCache() }
val nip05Client by
lazy {
Log.d("AppModules", "NIP05Client Init")
Nip05Client(
fetcher = OkHttpNip05Fetcher(roleBasedHttpClientBuilder::okHttpClientForNip05),
namecoinResolverBuilder = { namecoinResolver },
)
}
val otsResolverBuilder: TorAwareOkHttpOtsResolverBuilder = val otsResolverBuilder: TorAwareOkHttpOtsResolverBuilder =
TorAwareOkHttpOtsResolverBuilder( TorAwareOkHttpOtsResolverBuilder(
@@ -280,8 +287,8 @@ class AppModules(
val sessionManager = val sessionManager =
AccountSessionManager( AccountSessionManager(
accountsCache = accountsCache, accountsCache = accountsCache,
nip05Client = nip05Client,
client = client, client = client,
nip05ClientBuilder = { nip05Client },
localPreferences = LocalPreferences, localPreferences = LocalPreferences,
scope = applicationIOScope, scope = applicationIOScope,
) )
@@ -119,7 +119,7 @@ open class EditPostViewModel : ViewModel() {
this.editedFromNote = edit this.editedFromNote = edit
this.userSuggestions?.reset() this.userSuggestions?.reset()
this.userSuggestions = UserSuggestionState(accountViewModel.account, accountViewModel.nip05Client) this.userSuggestions = UserSuggestionState(accountViewModel.account, accountViewModel.nip05ClientBuilder())
} }
fun sendPost() { fun sendPost() {
@@ -134,7 +134,7 @@ private fun VerifyAndDisplayNIP05OrStatusLine(
if (nip05VerifState.isExpired()) { if (nip05VerifState.isExpired()) {
LaunchedEffect(key1 = nip05VerifState) { LaunchedEffect(key1 = nip05VerifState) {
accountViewModel.runOnIO { accountViewModel.runOnIO {
nip05State.checkAndUpdate(accountViewModel.nip05Client) nip05State.checkAndUpdate(accountViewModel.nip05ClientBuilder)
} }
} }
} }
@@ -442,7 +442,7 @@ fun ObserveAndRenderNIP05VerifiedSymbol(
if (state.isExpired()) { if (state.isExpired()) {
LaunchedEffect(key1 = state) { LaunchedEffect(key1 = state) {
accountViewModel.runOnIO { accountViewModel.runOnIO {
nip05State.checkAndUpdate(accountViewModel.nip05Client) nip05State.checkAndUpdate(accountViewModel.nip05ClientBuilder)
} }
} }
} }
@@ -209,7 +209,7 @@ open class CommentPostViewModel :
this.canAddZapRaiser = hasLnAddress() this.canAddZapRaiser = hasLnAddress()
this.userSuggestions?.reset() this.userSuggestions?.reset()
this.userSuggestions = UserSuggestionState(accountVM.account, accountVM.nip05Client) this.userSuggestions = UserSuggestionState(accountVM.account, accountVM.nip05ClientBuilder())
this.emojiSuggestions?.reset() this.emojiSuggestions?.reset()
this.emojiSuggestions = EmojiSuggestionState(accountVM.account) this.emojiSuggestions = EmojiSuggestionState(accountVM.account)
@@ -93,8 +93,8 @@ sealed class AccountState {
@Stable @Stable
class AccountSessionManager( class AccountSessionManager(
val accountsCache: AccountCacheState, val accountsCache: AccountCacheState,
val nip05Client: Nip05Client,
val client: INostrClient, val client: INostrClient,
val nip05ClientBuilder: () -> Nip05Client,
val localPreferences: LocalPreferences, val localPreferences: LocalPreferences,
val scope: CoroutineScope, val scope: CoroutineScope,
) { ) {
@@ -227,7 +227,7 @@ class AccountSessionManager(
onError("Could not parse nip05 address: $nip05") onError("Could not parse nip05 address: $nip05")
} else { } else {
try { try {
val pubkeyInfo = nip05Client.get(nip05) val pubkeyInfo = nip05ClientBuilder().get(nip05)
if (pubkeyInfo == null) { if (pubkeyInfo == null) {
onError("User not found in the nip05 server: $nip05") onError("User not found in the nip05 server: $nip05")
} else { } else {
@@ -173,7 +173,7 @@ class AccountViewModel(
val torSettings: TorSettingsFlow, val torSettings: TorSettingsFlow,
val dataSources: RelaySubscriptionsCoordinator, val dataSources: RelaySubscriptionsCoordinator,
val httpClientBuilder: IRoleBasedHttpClientBuilder, val httpClientBuilder: IRoleBasedHttpClientBuilder,
val nip05Client: INip05Client, val nip05ClientBuilder: () -> INip05Client,
) : ViewModel(), ) : ViewModel(),
Dao { Dao {
var firstRoute: Route? = null var firstRoute: Route? = null
@@ -1290,7 +1290,7 @@ class AccountViewModel(
val torSettings: TorSettingsFlow, val torSettings: TorSettingsFlow,
val dataSources: RelaySubscriptionsCoordinator, val dataSources: RelaySubscriptionsCoordinator,
val okHttpClient: RoleBasedHttpClientBuilder, val okHttpClient: RoleBasedHttpClientBuilder,
val nip05Client: Nip05Client, val nip05ClientBuilder: () -> Nip05Client,
) : ViewModelProvider.Factory { ) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
override fun <T : ViewModel> create(modelClass: Class<T>): T = override fun <T : ViewModel> create(modelClass: Class<T>): T =
@@ -1300,7 +1300,7 @@ class AccountViewModel(
torSettings, torSettings,
dataSources, dataSources,
okHttpClient, okHttpClient,
nip05Client, nip05ClientBuilder,
) as T ) as T
} }
@@ -1822,7 +1822,7 @@ fun mockAccountViewModel(): AccountViewModel {
torSettings = TorSettingsFlow(torType = MutableStateFlow(TorType.OFF)), torSettings = TorSettingsFlow(torType = MutableStateFlow(TorType.OFF)),
httpClientBuilder = EmptyRoleBasedHttpClientBuilder(), httpClientBuilder = EmptyRoleBasedHttpClientBuilder(),
dataSources = RelaySubscriptionsCoordinator(LocalCache, client, authenticator, failureTracker, scope), dataSources = RelaySubscriptionsCoordinator(LocalCache, client, authenticator, failureTracker, scope),
nip05Client = EmptyNip05Client(), nip05ClientBuilder = { EmptyNip05Client() },
).also { ).also {
mockedCache = it mockedCache = it
} }
@@ -1873,7 +1873,7 @@ fun mockVitorAccountViewModel(): AccountViewModel {
torSettings = TorSettingsFlow(torType = MutableStateFlow(TorType.OFF)), torSettings = TorSettingsFlow(torType = MutableStateFlow(TorType.OFF)),
httpClientBuilder = EmptyRoleBasedHttpClientBuilder(), httpClientBuilder = EmptyRoleBasedHttpClientBuilder(),
dataSources = RelaySubscriptionsCoordinator(LocalCache, client, authenticator, failureTracker, scope), dataSources = RelaySubscriptionsCoordinator(LocalCache, client, authenticator, failureTracker, scope),
nip05Client = EmptyNip05Client(), nip05ClientBuilder = { EmptyNip05Client() },
).also { ).also {
vitorCache = it vitorCache = it
} }
@@ -72,7 +72,7 @@ fun LoggedInPage(
torSettings = Amethyst.instance.torPrefs.value, torSettings = Amethyst.instance.torPrefs.value,
dataSources = Amethyst.instance.sources, dataSources = Amethyst.instance.sources,
okHttpClient = Amethyst.instance.roleBasedHttpClientBuilder, okHttpClient = Amethyst.instance.roleBasedHttpClientBuilder,
nip05Client = Amethyst.instance.nip05Client, nip05ClientBuilder = { Amethyst.instance.nip05Client },
), ),
) )
@@ -260,7 +260,7 @@ class ChatNewMessageViewModel :
this.canAddZapRaiser = hasLnAddress() this.canAddZapRaiser = hasLnAddress()
this.userSuggestions?.reset() this.userSuggestions?.reset()
this.userSuggestions = UserSuggestionState(accountVM.account, accountVM.nip05Client) this.userSuggestions = UserSuggestionState(accountVM.account, accountVM.nip05ClientBuilder())
this.emojiSuggestions?.reset() this.emojiSuggestions?.reset()
this.emojiSuggestions = EmojiSuggestionState(accountVM.account) this.emojiSuggestions = EmojiSuggestionState(accountVM.account)
@@ -186,7 +186,7 @@ open class ChannelNewMessageViewModel :
this.canAddZapRaiser = hasLnAddress() this.canAddZapRaiser = hasLnAddress()
this.userSuggestions?.reset() this.userSuggestions?.reset()
this.userSuggestions = UserSuggestionState(accountVM.account, accountVM.nip05Client) this.userSuggestions = UserSuggestionState(accountVM.account, accountVM.nip05ClientBuilder())
this.emojiSuggestions?.reset() this.emojiSuggestions?.reset()
this.emojiSuggestions = EmojiSuggestionState(accountVM.account) this.emojiSuggestions = EmojiSuggestionState(accountVM.account)
@@ -211,7 +211,7 @@ class LongFormPostViewModel :
this.canAddZapRaiser = hasLnAddress() this.canAddZapRaiser = hasLnAddress()
this.userSuggestions?.reset() this.userSuggestions?.reset()
this.userSuggestions = UserSuggestionState(accountVM.account, accountVM.nip05Client) this.userSuggestions = UserSuggestionState(accountVM.account, accountVM.nip05ClientBuilder())
this.emojiSuggestions?.reset() this.emojiSuggestions?.reset()
this.emojiSuggestions = EmojiSuggestionState(accountVM.account) this.emojiSuggestions = EmojiSuggestionState(accountVM.account)
@@ -200,7 +200,7 @@ open class NewProductViewModel :
this.canAddZapRaiser = hasLnAddress() this.canAddZapRaiser = hasLnAddress()
this.userSuggestions?.reset() this.userSuggestions?.reset()
this.userSuggestions = UserSuggestionState(accountVM.account, accountVM.nip05Client) this.userSuggestions = UserSuggestionState(accountVM.account, accountVM.nip05ClientBuilder())
this.emojiSuggestions?.reset() this.emojiSuggestions?.reset()
this.emojiSuggestions = EmojiSuggestionState(accountVM.account) this.emojiSuggestions = EmojiSuggestionState(accountVM.account)
@@ -301,7 +301,7 @@ open class ShortNotePostViewModel :
this.canAddZapRaiser = hasLnAddress() this.canAddZapRaiser = hasLnAddress()
this.userSuggestions?.reset() this.userSuggestions?.reset()
this.userSuggestions = UserSuggestionState(accountVM.account, accountVM.nip05Client) this.userSuggestions = UserSuggestionState(accountVM.account, accountVM.nip05ClientBuilder())
this.emojiSuggestions?.reset() this.emojiSuggestions?.reset()
this.emojiSuggestions = EmojiSuggestionState(accountVM.account) this.emojiSuggestions = EmojiSuggestionState(accountVM.account)
@@ -72,7 +72,7 @@ class PeopleListViewModel : ViewModel() {
) { ) {
if (!this::account.isInitialized || this.account != accountVM.account) { if (!this::account.isInitialized || this.account != accountVM.account) {
this.account = accountVM.account this.account = accountVM.account
this.userSuggestions = UserSuggestionState(accountVM.account, accountVM.nip05Client) this.userSuggestions = UserSuggestionState(accountVM.account, accountVM.nip05ClientBuilder())
} }
this.selectedDTag.tryEmit(selectedDTag) this.selectedDTag.tryEmit(selectedDTag)
@@ -72,7 +72,7 @@ class FollowPackViewModel : ViewModel() {
) { ) {
if (!this::account.isInitialized || this.account != accountVM.account) { if (!this::account.isInitialized || this.account != accountVM.account) {
this.account = accountVM.account this.account = accountVM.account
this.userSuggestions = UserSuggestionState(accountVM.account, accountVM.nip05Client) this.userSuggestions = UserSuggestionState(accountVM.account, accountVM.nip05ClientBuilder())
} }
this.selectedDTag.tryEmit(selectedDTag) this.selectedDTag.tryEmit(selectedDTag)
@@ -116,7 +116,7 @@ fun ImportFollowListSelectUserScreen(
) { ) {
val viewModel: ImportFollowListSelectUserViewModel = val viewModel: ImportFollowListSelectUserViewModel =
viewModel( viewModel(
factory = ImportFollowListSelectUserViewModel.Factory(accountViewModel.account, accountViewModel.nip05Client), factory = ImportFollowListSelectUserViewModel.Factory(accountViewModel.account, accountViewModel.nip05ClientBuilder()),
) )
Scaffold( Scaffold(
@@ -209,7 +209,7 @@ class NewPublicMessageViewModel :
this.canAddZapRaiser = hasLnAddress() this.canAddZapRaiser = hasLnAddress()
this.userSuggestions?.reset() this.userSuggestions?.reset()
this.userSuggestions = UserSuggestionState(accountVM.account, accountVM.nip05Client) this.userSuggestions = UserSuggestionState(accountVM.account, accountVM.nip05ClientBuilder())
this.emojiSuggestions?.reset() this.emojiSuggestions?.reset()
this.emojiSuggestions = EmojiSuggestionState(accountVM.account) this.emojiSuggestions = EmojiSuggestionState(accountVM.account)
@@ -91,7 +91,7 @@ fun SearchScreen(
factory = factory =
SearchBarViewModel.Factory( SearchBarViewModel.Factory(
accountViewModel.account, accountViewModel.account,
accountViewModel.nip05Client, accountViewModel.nip05ClientBuilder(),
), ),
) )
@@ -49,11 +49,11 @@ sealed interface Nip05State {
fun reset() = verificationState.tryEmit(Nip05VerifState.NotStarted) fun reset() = verificationState.tryEmit(Nip05VerifState.NotStarted)
suspend fun checkAndUpdate(nip05Client: INip05Client) { suspend fun checkAndUpdate(nip05ClientBuilder: () -> INip05Client) {
if (verificationState.value.isExpired()) { if (verificationState.value.isExpired()) {
markAsVerifying() markAsVerifying()
try { try {
if (nip05Client.verify(nip05, hexKey)) { if (nip05ClientBuilder().verify(nip05, hexKey)) {
markAsVerified() markAsVerified()
} else { } else {
markAsInvalid() markAsInvalid()
@@ -28,7 +28,7 @@ import kotlinx.coroutines.CancellationException
@Stable @Stable
class Nip05Client( class Nip05Client(
val fetcher: Nip05Fetcher, val fetcher: Nip05Fetcher,
val namecoinResolver: NamecoinNameResolver? = null, val namecoinResolverBuilder: (() -> NamecoinNameResolver)? = null,
) : INip05Client { ) : INip05Client {
val parser = Nip05Parser() val parser = Nip05Parser()
@@ -37,8 +37,8 @@ class Nip05Client(
hexKey: HexKey, hexKey: HexKey,
): Boolean { ): Boolean {
// Namecoin: route .bit domains to blockchain verification // Namecoin: route .bit domains to blockchain verification
if (namecoinResolver != null && NamecoinNameResolver.isNamecoinIdentifier(nip05.toValue())) { if (namecoinResolverBuilder != null && NamecoinNameResolver.isNamecoinIdentifier(nip05.toValue())) {
val result = namecoinResolver.resolve(nip05.toValue()) val result = namecoinResolverBuilder().resolve(nip05.toValue())
return result?.pubkey == hexKey return result?.pubkey == hexKey
} }
@@ -61,8 +61,8 @@ class Nip05Client(
override suspend fun get(nip05: Nip05Id): Nip05KeyInfo? { override suspend fun get(nip05: Nip05Id): Nip05KeyInfo? {
// Namecoin: route .bit domains to blockchain resolution // Namecoin: route .bit domains to blockchain resolution
if (namecoinResolver != null && NamecoinNameResolver.isNamecoinIdentifier(nip05.toValue())) { if (namecoinResolverBuilder != null && NamecoinNameResolver.isNamecoinIdentifier(nip05.toValue())) {
val result = namecoinResolver.resolve(nip05.toValue()) ?: return null val result = namecoinResolverBuilder().resolve(nip05.toValue()) ?: return null
return Nip05KeyInfo(result.pubkey, result.relays) return Nip05KeyInfo(result.pubkey, result.relays)
} }