feat: add history sync worker

This commit is contained in:
2026-06-07 17:16:47 +02:00
parent 3f25ee4c41
commit 3b14f2fba9
25 changed files with 807 additions and 120 deletions
@@ -3,9 +3,10 @@ package com.bitcointxoko.gudariwallet.api
import com.bitcointxoko.gudariwallet.BuildConfig
import com.bitcointxoko.gudariwallet.security.SecretStore
import com.bitcointxoko.gudariwallet.util.WalletConstants
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.runBlocking
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.Interceptor
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import retrofit2.Retrofit
@@ -16,11 +17,10 @@ object LNbitsClient {
// Single shared client — used by both Retrofit and WebSocket
val httpClient: OkHttpClient = OkHttpClient.Builder()
.connectTimeout(WalletConstants.CONNECT_TIMEOUT_S, TimeUnit.SECONDS) // time to establish TCP connection
.readTimeout(WalletConstants.READ_TIMEOUT_S, TimeUnit.SECONDS) // time to wait for data (longer for WebSocket pings)
.writeTimeout(WalletConstants.WRITE_TIMEOUT_S, TimeUnit.SECONDS) // time to send a request body
.connectTimeout(WalletConstants.CONNECT_TIMEOUT_S, TimeUnit.SECONDS)
.readTimeout(WalletConstants.READ_TIMEOUT_S, TimeUnit.SECONDS)
.writeTimeout(WalletConstants.WRITE_TIMEOUT_S, TimeUnit.SECONDS)
.apply {
// Only log in debug builds — prevents API keys leaking in production logs
if (BuildConfig.DEBUG) {
addInterceptor(HttpLoggingInterceptor().apply {
level = HttpLoggingInterceptor.Level.BODY
@@ -30,14 +30,57 @@ object LNbitsClient {
.build()
fun create(secrets: SecretStore): LNbitsApi {
val baseUrl = runBlocking(Dispatchers.IO) {
secrets.credentials.first().baseUrl
}.ifBlank { "https://placeholder.invalid" }
val normalizedUrl = if (baseUrl.endsWith("/")) baseUrl else "$baseUrl/"
// Auth header interceptor — reads invoice key per-request (unchanged)
val authInterceptor = Interceptor { chain ->
val key = runBlocking { secrets.invoiceKey() }
chain.proceed(
chain.request().newBuilder()
.header("X-Api-Key", key)
.build()
)
}
// Dynamic base URL interceptor — rewrites the placeholder host to the
// real base URL at request time, not at Retrofit construction time.
// This means credentials only need to be in DataStore by the time the
// first HTTP request fires, not at app startup.
val dynamicBaseUrlInterceptor = Interceptor { chain ->
val realBaseUrl = runBlocking { secrets.baseUrl() }
.ifBlank { "https://placeholder.invalid" }
.trimEnd('/')
val realHttpUrl = realBaseUrl.toHttpUrl()
val originalUrl = chain.request().url
val newUrl = originalUrl.newBuilder()
.scheme(realHttpUrl.scheme)
.host(realHttpUrl.host)
.port(realHttpUrl.port)
.build()
chain.proceed(
chain.request().newBuilder()
.url(newUrl)
.build()
)
}
val client = httpClient.newBuilder()
.addInterceptor(authInterceptor)
.addInterceptor(dynamicBaseUrlInterceptor)
.apply {
// Logging goes LAST — after URL rewrite — so logs show the real URL
if (BuildConfig.DEBUG) {
addInterceptor(HttpLoggingInterceptor().apply {
level = HttpLoggingInterceptor.Level.BODY
})
}
}
.build()
return Retrofit.Builder()
.baseUrl(normalizedUrl)
.client(httpClient)
.baseUrl("https://placeholder.invalid/") // structurally required by Retrofit;
// always overwritten by the interceptor
.client(client)
.addConverterFactory(GsonConverterFactory.create(GsonProvider.gson))
.build()
.create(LNbitsApi::class.java)