-
-
Notifications
You must be signed in to change notification settings - Fork 405
feature: implement proxy connectivity testing and improve Android sys… #405
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,7 +1,10 @@ | ||
| { | ||
| "permissions": { | ||
| "allow": [ | ||
| "Bash(grep -r \"\\\\.FLATPAK\\\\|Flatpak\\\\|flatpak\" D:/development/Github-Store --include=*.kt)" | ||
| "Bash(grep -r \"\\\\.FLATPAK\\\\|Flatpak\\\\|flatpak\" . --include=*.kt)", | ||
| "Bash(./gradlew :composeApp:assembleDebug)", | ||
| "Bash(./gradlew :composeApp:jvmJar)", | ||
| "Bash(./gradlew :composeApp:assembleDebug :composeApp:jvmJar)" | ||
| ] | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,70 @@ | ||
| package zed.rainxch.core.data.network | ||
|
|
||
| import io.ktor.client.plugins.HttpRequestTimeoutException | ||
| import io.ktor.client.plugins.HttpTimeout | ||
| import io.ktor.client.request.get | ||
| import io.ktor.client.statement.HttpResponse | ||
| import io.ktor.util.network.UnresolvedAddressException | ||
| import zed.rainxch.core.domain.model.ProxyConfig | ||
| import zed.rainxch.core.domain.network.ProxyTestOutcome | ||
| import zed.rainxch.core.domain.network.ProxyTester | ||
| import java.io.IOException | ||
| import java.net.ConnectException | ||
| import java.net.SocketTimeoutException | ||
| import java.net.UnknownHostException | ||
|
Comment on lines
+11
to
+14
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "== Declared Kotlin targets =="
rg -n --glob '**/*.gradle.kts' --glob '**/*.gradle' 'ios\(|macos|linuxX64|mingwX64|js\(|wasm|androidTarget|jvm\('
echo
echo "== JVM imports inside commonMain =="
rg -n --glob '**/src/commonMain/**/*.kt' '^import java\.'Repository: OpenHub-Store/GitHub-Store Length of output: 99 🏁 Script executed: # Confirm the file exists and view its imports
cat -n "core/data/src/commonMain/kotlin/zed/rainxch/core/data/network/ProxyTesterImpl.kt" | head -20Repository: OpenHub-Store/GitHub-Store Length of output: 995 🏁 Script executed: # Search for Kotlin Multiplatform configuration
find . -name "build.gradle.kts" -o -name "build.gradle" -o -name "settings.gradle.kts" | head -10Repository: OpenHub-Store/GitHub-Store Length of output: 412 🏁 Script executed: # Check for kotlin block in any gradle files more broadly
rg -l "kotlin\s*\{" --glob "**/*.gradle.kts" --glob "**/*.gradle"Repository: OpenHub-Store/GitHub-Store Length of output: 1434 🏁 Script executed: # Read the core/data/build.gradle.kts to check KMP target configuration
cat "core/data/build.gradle.kts"Repository: OpenHub-Store/GitHub-Store Length of output: 1285 🏁 Script executed: # Check the convention plugin definition for KMP library
cat "build-logic/convention/build.gradle.kts"Repository: OpenHub-Store/GitHub-Store Length of output: 2739 🏁 Script executed: # Also search for where the convention plugin targets are defined
find "build-logic" -name "*.gradle.kts" -o -name "*.kt" | grep -i kmp | head -5Repository: OpenHub-Store/GitHub-Store Length of output: 138 🏁 Script executed: # Read the KMP Library convention plugin
cat "build-logic/convention/src/main/kotlin/KmpLibraryConventionPlugin.kt"Repository: OpenHub-Store/GitHub-Store Length of output: 1325 🏁 Script executed: # Search for the configureKotlinMultiplatform function
rg -n "fun configureKotlinMultiplatform" "build-logic/convention/src/main/kotlin/"Repository: OpenHub-Store/GitHub-Store Length of output: 52 🏁 Script executed: # Look for convention files that define multiplatform config
fd "\.kt$" "build-logic/convention/src/main/kotlin/" | xargs grep -l "kotlin\s*\{" | head -10Repository: OpenHub-Store/GitHub-Store Length of output: 88 🏁 Script executed: # Search for configureKotlinMultiplatform more broadly
rg "configureKotlinMultiplatform" "build-logic/convention/src/main/kotlin/"Repository: OpenHub-Store/GitHub-Store Length of output: 643 🏁 Script executed: # List all .kt files in the convention directory
fd "\.kt$" "build-logic/convention/src/main/kotlin/" | sortRepository: OpenHub-Store/GitHub-Store Length of output: 1352 🏁 Script executed: # Read the KotlinMultiplatform.kt file
cat "build-logic/convention/src/main/kotlin/zed/rainxch/githubstore/convention/KotlinMultiplatform.kt"Repository: OpenHub-Store/GitHub-Store Length of output: 900 🏁 Script executed: # Read the full ProxyTesterImpl.kt to see how java.* exceptions are used
cat "core/data/src/commonMain/kotlin/zed/rainxch/core/data/network/ProxyTesterImpl.kt"Repository: OpenHub-Store/GitHub-Store Length of output: 2699 🏁 Script executed: # Search for other ProxyTester implementations
rg -n "class.*ProxyTester" --glob "**/*.kt"Repository: OpenHub-Store/GitHub-Store Length of output: 191 🏁 Script executed: # Check if these java.* exceptions are actually needed or if Ktor exceptions would suffice
rg -n "SocketTimeoutException|UnknownHostException|ConnectException|IOException" "core/data/src/commonMain/kotlin/zed/rainxch/core/data/network/ProxyTesterImpl.kt"Repository: OpenHub-Store/GitHub-Store Length of output: 394 Move JVM-specific exception handling out of Lines 11-14 import 🤖 Prompt for AI Agents |
||
| import kotlin.coroutines.cancellation.CancellationException | ||
| import kotlin.time.TimeSource | ||
|
|
||
| class ProxyTesterImpl : ProxyTester { | ||
| override suspend fun test(config: ProxyConfig): ProxyTestOutcome { | ||
| val client = | ||
| createPlatformHttpClient(config).config { | ||
| install(HttpTimeout) { | ||
| requestTimeoutMillis = TEST_TIMEOUT_MS | ||
| connectTimeoutMillis = TEST_TIMEOUT_MS | ||
| socketTimeoutMillis = TEST_TIMEOUT_MS | ||
| } | ||
| expectSuccess = false | ||
| } | ||
|
|
||
| return try { | ||
| val started = TimeSource.Monotonic.markNow() | ||
| val response: HttpResponse = client.get(TEST_URL) | ||
| val elapsed = started.elapsedNow().inWholeMilliseconds | ||
|
|
||
| when { | ||
| response.status.value == 407 -> | ||
| ProxyTestOutcome.Failure.ProxyAuthRequired | ||
|
|
||
| response.status.value in 200..299 -> | ||
| ProxyTestOutcome.Success(latencyMs = elapsed) | ||
|
|
||
| else -> | ||
| ProxyTestOutcome.Failure.UnexpectedResponse(response.status.value) | ||
| } | ||
| } catch (e: CancellationException) { | ||
| throw e | ||
| } catch (e: HttpRequestTimeoutException) { | ||
| ProxyTestOutcome.Failure.Timeout | ||
| } catch (e: SocketTimeoutException) { | ||
| ProxyTestOutcome.Failure.Timeout | ||
| } catch (e: UnresolvedAddressException) { | ||
| ProxyTestOutcome.Failure.DnsFailure | ||
| } catch (e: UnknownHostException) { | ||
| ProxyTestOutcome.Failure.DnsFailure | ||
| } catch (e: ConnectException) { | ||
| ProxyTestOutcome.Failure.ProxyUnreachable | ||
| } catch (e: IOException) { | ||
| ProxyTestOutcome.Failure.Unknown(e.message) | ||
| } catch (e: Exception) { | ||
| ProxyTestOutcome.Failure.Unknown(e.message) | ||
| } finally { | ||
| client.close() | ||
| } | ||
| } | ||
|
|
||
| private companion object { | ||
| const val TEST_URL = "https://api.github.com/zen" | ||
| const val TEST_TIMEOUT_MS = 8_000L | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,44 @@ | ||
| package zed.rainxch.core.domain.network | ||
|
|
||
| import zed.rainxch.core.domain.model.ProxyConfig | ||
|
|
||
| /** | ||
| * Verifies that a [ProxyConfig] can actually reach the GitHub API. Implementations | ||
| * should issue a single lightweight request through a throwaway HTTP client built | ||
| * with the supplied config so the test exercises the same engine code path the | ||
| * real client uses. | ||
| */ | ||
| interface ProxyTester { | ||
| suspend fun test(config: ProxyConfig): ProxyTestOutcome | ||
| } | ||
|
|
||
| sealed interface ProxyTestOutcome { | ||
| /** Connection succeeded. [latencyMs] is the round-trip time of the test request. */ | ||
| data class Success( | ||
| val latencyMs: Long, | ||
| ) : ProxyTestOutcome | ||
|
|
||
| sealed interface Failure : ProxyTestOutcome { | ||
| /** Could not resolve a hostname (DNS failure or unresolved proxy host). */ | ||
| data object DnsFailure : Failure | ||
|
|
||
| /** Reached the network but could not connect to the proxy itself. */ | ||
| data object ProxyUnreachable : Failure | ||
|
|
||
| /** Connection or socket timed out. */ | ||
| data object Timeout : Failure | ||
|
|
||
| /** Proxy returned 407 / requested authentication. */ | ||
| data object ProxyAuthRequired : Failure | ||
|
|
||
| /** Proxy or upstream returned a non-2xx HTTP status. */ | ||
| data class UnexpectedResponse( | ||
| val statusCode: Int, | ||
| ) : Failure | ||
|
|
||
| /** Anything else (TLS errors, malformed config, etc.). */ | ||
| data class Unknown( | ||
| val message: String?, | ||
| ) : Failure | ||
| } | ||
| } |
Uh oh!
There was an error while loading. Please reload this page.