diff --git a/app/src/main/java/com/droiddeck/launcher/stores/amazon/AmazonCredentialStore.kt b/app/src/main/java/com/droiddeck/launcher/stores/amazon/AmazonCredentialStore.kt index f4b00de6..97973a54 100644 --- a/app/src/main/java/com/droiddeck/launcher/stores/amazon/AmazonCredentialStore.kt +++ b/app/src/main/java/com/droiddeck/launcher/stores/amazon/AmazonCredentialStore.kt @@ -70,6 +70,9 @@ object AmazonCredentialStore { private fun fresh(a: Account): Boolean = a.expiresAt - System.currentTimeMillis() > REFRESH_MARGIN_MS || a.refreshToken.isEmpty() + private fun unexpired(a: Account): String? = + a.accessToken.takeIf { a.expiresAt <= 0 || a.expiresAt > System.currentTimeMillis() } + private fun refreshLocked(context: Context, a: Account): String? { if (a.refreshToken.isEmpty()) return null val answer = AmazonNative.call("auth.refresh", JSONObject().put("refreshToken", a.refreshToken)) @@ -90,7 +93,7 @@ object AmazonCredentialStore { synchronized(refreshLock) { val again = load(context) ?: return null if (fresh(again)) return again.accessToken - return refreshLocked(context, again) ?: again.accessToken + return refreshLocked(context, again) ?: unexpired(again) } } diff --git a/app/src/main/java/com/droiddeck/launcher/stores/epic/EpicCredentialStore.kt b/app/src/main/java/com/droiddeck/launcher/stores/epic/EpicCredentialStore.kt index 38423bc6..e261203b 100644 --- a/app/src/main/java/com/droiddeck/launcher/stores/epic/EpicCredentialStore.kt +++ b/app/src/main/java/com/droiddeck/launcher/stores/epic/EpicCredentialStore.kt @@ -85,6 +85,9 @@ object EpicCredentialStore { private fun fresh(c: Credentials): Boolean = c.expiresAt - System.currentTimeMillis() > REFRESH_MARGIN_MS || c.refreshToken.isNullOrEmpty() + private fun unexpired(c: Credentials): String? = + c.accessToken.takeIf { c.expiresAt <= 0 || c.expiresAt > System.currentTimeMillis() } + @JvmStatic fun getValidAccessToken(context: Context): String? { val current = load(context) ?: return null @@ -96,7 +99,7 @@ object EpicCredentialStore { val result = answer?.let(TokenResult::from) if (result == null) { Log.w(TAG, "token refresh failed: ${answer?.optString("error").orEmpty().ifEmpty { "no answer" }}") - return again.accessToken + return unexpired(again) } save(context, result) return result.accessToken diff --git a/app/src/main/rust/stores/JNI.md b/app/src/main/rust/stores/JNI.md index b5098138..38ab4f08 100644 --- a/app/src/main/rust/stores/JNI.md +++ b/app/src/main/rust/stores/JNI.md @@ -118,9 +118,10 @@ On-disk protocol (what a resumed run, by either engine, relies on): a file is as Everything Epic runs in the Rust `epic` module: the web-service calls (sign-in, refresh, exchange codes, library, catalog, build information, shop shelves, achievements, cloud saves), manifest parsing and the install engine. Tokens are passed in per call and never stored or logged by the -library; the Kotlin facade keeps them in the shared Keystore-sealed account store. All requests send -one User-Agent, `DroidDeck/` (`epic/net.rs::USER_AGENT_PRODUCT`; the version comes from -`nativeConfigure`). +library; the Kotlin facade keeps them in the shared Keystore-sealed account store. Requests send +`DroidDeck/` (`epic/net.rs::USER_AGENT_PRODUCT`; the version comes from +`nativeConfigure`), except achievements, which Epic only answers for its launcher agent +(`epic/net.rs::EPIC_LAUNCHER_USER_AGENT`). | Method | JNI signature | Symbol suffix | |---|---|---| diff --git a/docs/development/epic-cleanroom.md b/docs/development/epic-cleanroom.md index 029c7c1e..e121d4ca 100644 --- a/docs/development/epic-cleanroom.md +++ b/docs/development/epic-cleanroom.md @@ -66,9 +66,10 @@ Numbers in brackets refer to the spec's list of unknowns. **Chosen where the spec left it open** 3. **User-Agent** [1]: every HTTP request (account, library, catalog, build info, CDNs, store - GraphQL, promotions, achievements, cloud saves) sends `DroidDeck/`, defined once in - `epic/net.rs` (`USER_AGENT_PRODUCT`). Whether every endpoint accepts it is untested; changing that - constant is the one place to adjust. The sign-in **WebView** keeps the launcher-style UA it used + GraphQL, promotions, cloud saves) sends `DroidDeck/`, defined once in + `epic/net.rs` (`USER_AGENT_PRODUCT`); changing that constant is the one place to adjust. + Achievements are the exception: Epic's achievements service rejects other agents, so + `epic/achievements.rs` sends Epic's launcher agent (`net.rs::EPIC_LAUNCHER_USER_AGENT`). The sign-in **WebView** keeps the launcher-style UA it used before, because a sign-in page that rejects the UA would lock every user out; that string lives in `epic/account.rs` (`WEBVIEW_USER_AGENT`). 4. **Shared fetch core** [14]: chunk downloads reuse the crate's store-agnostic `fetch_core` (also @@ -138,6 +139,6 @@ Numbers in brackets refer to the spec's list of unknowns. - Sign-in through the WebView, and that an **existing** saved account (sealed `credentials.json`) stays signed in after the update. - Every endpoint with the DroidDeck User-Agent: library, catalog, build info, CDN downloads, store - GraphQL and promotions, achievements, cloud saves. + GraphQL and promotions, cloud saves (achievements use the launcher agent, see above). - Library sync and shelves; install, pause/resume, cancel, update and repair; a card install with the internal scratch cache; launch with an exchange code; cloud-save up/down.