From 47ae6084961065333b2046909c85b529efff831f Mon Sep 17 00:00:00 2001 From: rainxchzed Date: Mon, 20 Apr 2026 15:25:45 +0500 Subject: [PATCH 1/2] BackendApiClient: Include GitHub token in search and explore requests - Inject `TokenStore` into `BackendApiClient`. - Implement `currentUserGithubToken()` to retrieve and trim the access token from `TokenStore`. - Add `X-GitHub-Token` header to `search` and `search/explore` requests if a token is available. - Update `SharedModule` to provide the `TokenStore` dependency to `BackendApiClient`. - Cleanup: Remove several detailed internal comments regarding language selection logic and DataStore timeouts in `TweaksViewModel`, `TweaksRepositoryImpl`, `DesktopApp`, and `MainActivity`. --- .../kotlin/zed/rainxch/githubstore/MainActivity.kt | 5 ----- .../kotlin/zed/rainxch/githubstore/DesktopApp.kt | 4 ---- .../kotlin/zed/rainxch/core/data/di/SharedModule.kt | 1 + .../rainxch/core/data/network/BackendApiClient.kt | 13 +++++++++++++ .../core/data/repository/TweaksRepositoryImpl.kt | 4 ---- .../rainxch/tweaks/presentation/TweaksViewModel.kt | 13 ------------- 6 files changed, 14 insertions(+), 26 deletions(-) diff --git a/composeApp/src/androidMain/kotlin/zed/rainxch/githubstore/MainActivity.kt b/composeApp/src/androidMain/kotlin/zed/rainxch/githubstore/MainActivity.kt index 93b906413..304a36753 100644 --- a/composeApp/src/androidMain/kotlin/zed/rainxch/githubstore/MainActivity.kt +++ b/composeApp/src/androidMain/kotlin/zed/rainxch/githubstore/MainActivity.kt @@ -49,11 +49,6 @@ class MainActivity : ComponentActivity() { // cheap and we only block once per Activity creation (including // the post-language-swap recreate() path below). Without this, // recreate() would briefly flash the old locale before settling. - // - // The 2s timeout + catch-all is defence against a stalled or - // corrupted DataStore: we'd rather boot in system language than - // leave the Activity stuck before super.onCreate(), which would - // hang the whole app with no visible error. runBlocking { val tag = try { diff --git a/composeApp/src/jvmMain/kotlin/zed/rainxch/githubstore/DesktopApp.kt b/composeApp/src/jvmMain/kotlin/zed/rainxch/githubstore/DesktopApp.kt index e9fc4acd2..0c4e6a917 100644 --- a/composeApp/src/jvmMain/kotlin/zed/rainxch/githubstore/DesktopApp.kt +++ b/composeApp/src/jvmMain/kotlin/zed/rainxch/githubstore/DesktopApp.kt @@ -51,10 +51,6 @@ fun main(args: Array) { // language swaps surface as a "restart required" snackbar from the // Tweaks screen; this block just covers the cold-start path so // users see their chosen language immediately on next launch. - // - // Timeout guards against a stalled DataStore read blocking window - // creation and deep-link dispatch — we fall back to system language - // rather than hang the launch. runBlocking { val koin = GlobalContext.get() val tweaksRepo = koin.get() diff --git a/core/data/src/commonMain/kotlin/zed/rainxch/core/data/di/SharedModule.kt b/core/data/src/commonMain/kotlin/zed/rainxch/core/data/di/SharedModule.kt index 1f27ec388..80b6d8414 100644 --- a/core/data/src/commonMain/kotlin/zed/rainxch/core/data/di/SharedModule.kt +++ b/core/data/src/commonMain/kotlin/zed/rainxch/core/data/di/SharedModule.kt @@ -155,6 +155,7 @@ val coreModule = get() BackendApiClient( proxyConfigFlow = ProxyManager.configFlow(ProxyScope.DISCOVERY), + tokenStore = get(), ) } // NOTE: the reviewer asked for a Koin onClose hook to call diff --git a/core/data/src/commonMain/kotlin/zed/rainxch/core/data/network/BackendApiClient.kt b/core/data/src/commonMain/kotlin/zed/rainxch/core/data/network/BackendApiClient.kt index 838d59ea5..bb6ce0568 100644 --- a/core/data/src/commonMain/kotlin/zed/rainxch/core/data/network/BackendApiClient.kt +++ b/core/data/src/commonMain/kotlin/zed/rainxch/core/data/network/BackendApiClient.kt @@ -6,6 +6,7 @@ import io.ktor.client.plugins.HttpTimeout import io.ktor.client.plugins.contentnegotiation.ContentNegotiation import io.ktor.client.plugins.defaultRequest import io.ktor.client.request.get +import io.ktor.client.request.header import io.ktor.client.request.parameter import io.ktor.http.isSuccess import io.ktor.serialization.kotlinx.json.json @@ -27,6 +28,7 @@ import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock +import zed.rainxch.core.data.data_source.TokenStore import zed.rainxch.core.data.dto.BackendExploreResponse import zed.rainxch.core.data.dto.BackendRepoResponse import zed.rainxch.core.data.dto.BackendSearchResponse @@ -42,6 +44,7 @@ import kotlin.coroutines.cancellation.CancellationException */ class BackendApiClient( proxyConfigFlow: StateFlow, + private val tokenStore: TokenStore, ) { private val scope = CoroutineScope(SupervisorJob() + Dispatchers.Default) private val mutex = Mutex() @@ -110,12 +113,14 @@ class BackendApiClient( offset: Int = 0, ): Result = safeCall { + val token = currentUserGithubToken() val response = httpClient.get("search") { parameter("q", query) if (platform != null) parameter("platform", platform) if (sort != null) parameter("sort", sort) parameter("limit", limit) parameter("offset", offset) + if (token != null) header(X_GITHUB_TOKEN_HEADER, token) } if (response.status.isSuccess()) { Result.success(response.body()) @@ -130,11 +135,13 @@ class BackendApiClient( page: Int = 1, ): Result = safeCall { + val token = currentUserGithubToken() val response = httpClient.get("search/explore") { parameter("q", query) if (platform != null) parameter("platform", platform) parameter("page", page) timeout { requestTimeoutMillis = 20_000 } + if (token != null) header(X_GITHUB_TOKEN_HEADER, token) } if (response.status.isSuccess()) { Result.success(response.body()) @@ -143,6 +150,11 @@ class BackendApiClient( } } + private suspend fun currentUserGithubToken(): String? = + runCatching { tokenStore.currentToken()?.accessToken?.trim() } + .getOrNull() + ?.takeIf { it.isNotEmpty() } + suspend fun getRepo(owner: String, name: String): Result = safeCall { val response = httpClient.get("repo/$owner/$name") @@ -180,6 +192,7 @@ class BackendApiClient( companion object { private const val BASE_URL = "https://api.github-store.org/v1/" + private const val X_GITHUB_TOKEN_HEADER = "X-GitHub-Token" } } diff --git a/core/data/src/commonMain/kotlin/zed/rainxch/core/data/repository/TweaksRepositoryImpl.kt b/core/data/src/commonMain/kotlin/zed/rainxch/core/data/repository/TweaksRepositoryImpl.kt index 36ad81b10..4d5aff5cf 100644 --- a/core/data/src/commonMain/kotlin/zed/rainxch/core/data/repository/TweaksRepositoryImpl.kt +++ b/core/data/src/commonMain/kotlin/zed/rainxch/core/data/repository/TweaksRepositoryImpl.kt @@ -214,10 +214,6 @@ class TweaksRepositoryImpl( override fun getAppLanguage(): Flow = preferences.data.map { prefs -> - // Treat blank *or* unknown tags as "unset" — guards against - // stale writes from older builds that shipped a language - // we no longer bundle resources for, which would otherwise - // pin the UI to an unresolvable locale. prefs[APP_LANGUAGE_KEY] ?.trim() ?.takeIf { it.isNotEmpty() && AppLanguages.containsTag(it) } diff --git a/feature/tweaks/presentation/src/commonMain/kotlin/zed/rainxch/tweaks/presentation/TweaksViewModel.kt b/feature/tweaks/presentation/src/commonMain/kotlin/zed/rainxch/tweaks/presentation/TweaksViewModel.kt index e05ae7438..2d625542a 100644 --- a/feature/tweaks/presentation/src/commonMain/kotlin/zed/rainxch/tweaks/presentation/TweaksViewModel.kt +++ b/feature/tweaks/presentation/src/commonMain/kotlin/zed/rainxch/tweaks/presentation/TweaksViewModel.kt @@ -752,22 +752,9 @@ class TweaksViewModel( } is TweaksAction.OnAppLanguageSelected -> { - // Skip the write + restart prompt when the user re-picks - // the language that's already active — tapping the - // current option shouldn't look like a change on - // Desktop (would fire a spurious "restart to apply" - // snackbar) or churn DataStore on Android. if (action.tag == _state.value.selectedAppLanguage) return viewModelScope.launch { tweaksRepository.setAppLanguage(action.tag) - // Android: `MainActivity` is subscribed to the - // same preference flow and calls `recreate()` on - // change — no extra nudging needed. Desktop has - // no recreate-equivalent, so we surface a - // "restart to apply" prompt; the user's choice is - // already persisted and will take effect on the - // next launch (or they can restart now via the - // snackbar action). if (getPlatform() != Platform.ANDROID) { _events.send(TweaksEvent.OnAppLanguageChangeRequiresRestart) } From 30ac22726fbc5dd6e902adb7587d19031dfdf902 Mon Sep 17 00:00:00 2001 From: "Usmon N." Date: Mon, 20 Apr 2026 15:39:15 +0500 Subject: [PATCH 2/2] Update core/data/src/commonMain/kotlin/zed/rainxch/core/data/network/BackendApiClient.kt Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- .../zed/rainxch/core/data/network/BackendApiClient.kt | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/core/data/src/commonMain/kotlin/zed/rainxch/core/data/network/BackendApiClient.kt b/core/data/src/commonMain/kotlin/zed/rainxch/core/data/network/BackendApiClient.kt index bb6ce0568..c5a908a3d 100644 --- a/core/data/src/commonMain/kotlin/zed/rainxch/core/data/network/BackendApiClient.kt +++ b/core/data/src/commonMain/kotlin/zed/rainxch/core/data/network/BackendApiClient.kt @@ -151,10 +151,13 @@ class BackendApiClient( } private suspend fun currentUserGithubToken(): String? = - runCatching { tokenStore.currentToken()?.accessToken?.trim() } - .getOrNull() - ?.takeIf { it.isNotEmpty() } - + try { + tokenStore.currentToken()?.accessToken?.trim()?.takeIf { it.isNotEmpty() } + } catch (e: CancellationException) { + throw e + } catch (_: Exception) { + null + } suspend fun getRepo(owner: String, name: String): Result = safeCall { val response = httpClient.get("repo/$owner/$name")