From 5dfa20202d9f6d59951bf6d41726caa9038f1ca7 Mon Sep 17 00:00:00 2001 From: Arjan Schrijver Date: Mon, 2 Feb 2026 22:40:41 +0100 Subject: [PATCH] Endurain: Improve token refresh logic --- .../endurain/EndurainPreferencesActivity.kt | 28 ++++++++------- .../endurain/EndurainSetupViewModel.kt | 20 ++--------- .../endurain/EndurainTokenManager.kt | 28 ++++++++++----- .../workouts/WorkoutDetailsFragment.kt | 35 +++++++++++-------- 4 files changed, 59 insertions(+), 52 deletions(-) diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/endurain/EndurainPreferencesActivity.kt b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/endurain/EndurainPreferencesActivity.kt index 289f3993cd..46496f12d7 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/endurain/EndurainPreferencesActivity.kt +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/endurain/EndurainPreferencesActivity.kt @@ -25,6 +25,8 @@ import nodomain.freeyourgadget.gadgetbridge.GBApplication import nodomain.freeyourgadget.gadgetbridge.R import nodomain.freeyourgadget.gadgetbridge.activities.AbstractPreferenceFragment import nodomain.freeyourgadget.gadgetbridge.activities.AbstractSettingsActivityV2 +import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils +import nodomain.freeyourgadget.gadgetbridge.util.GB class EndurainPreferencesActivity : AbstractSettingsActivityV2() { @@ -45,14 +47,16 @@ class EndurainPreferencesActivity : AbstractSettingsActivityV2() { updateLogoutPreferenceVisibility() setupLoginResultListener() - // Refresh auth token + // Refresh tokens val vm: EndurainSetupViewModel by viewModels() val server = GBApplication.getPrefs().preferences.getString("endurain_server", null) if (server != null) { - vm.performTokenRefresh(server) { - activity?.runOnUiThread { - updateStatus() - updateLogoutPreferenceVisibility() + if (vm.tokenManager.isAccessTokenExpired()) { + vm.performTokenRefresh(server) { + activity?.runOnUiThread { + updateStatus() + updateLogoutPreferenceVisibility() + } } } vm.fetchServerVersion(server) { @@ -106,29 +110,29 @@ class EndurainPreferencesActivity : AbstractSettingsActivityV2() { vm.logout { success -> activity?.runOnUiThread { if (success) { - Toast.makeText(requireContext(), "Logged out successfully", Toast.LENGTH_SHORT).show() + GB.toast("Logged out successfully", Toast.LENGTH_SHORT, GB.INFO) updateStatus() updateLogoutPreferenceVisibility() } else { - Toast.makeText(requireContext(), "Logout failed", Toast.LENGTH_SHORT).show() + GB.toast("Logout failed", Toast.LENGTH_SHORT, GB.WARN) } } } } private fun updateLogoutPreferenceVisibility() { - findPreference("pref_key_log_out")?.isVisible = vm.isLoggedIn() - findPreference("pref_key_log_in")?.isVisible = !vm.isLoggedIn() + findPreference("pref_key_log_out")?.isVisible = vm.tokenManager.isLoggedIn() + findPreference("pref_key_log_in")?.isVisible = !vm.tokenManager.isLoggedIn() } private fun updateStatus() { val statusPref = findPreference("pref_key_status") val server = GBApplication.getPrefs().preferences.getString("endurain_server", null) - val tokenExpiresAt = vm.getTokenExpiresAt() + val tokenExpiresAt = DateTimeUtils.parseTimeStamp(vm.tokenManager.getRefreshTokenExpiresAt()) var summaryText = "Not logged in, integration is disabled" - if (vm.isLoggedIn() && server != null) { - summaryText = "Logged in to $server\nAuth token expires: $tokenExpiresAt" + if (vm.tokenManager.isLoggedIn() && server != null) { + summaryText = "Logged in to $server\nRefresh token expires: $tokenExpiresAt" } if (vm.serverVersion != null) { summaryText += "\nServer version: ${vm.serverVersion}" diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/endurain/EndurainSetupViewModel.kt b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/endurain/EndurainSetupViewModel.kt index 2a5b09ec7e..612de43b54 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/endurain/EndurainSetupViewModel.kt +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/endurain/EndurainSetupViewModel.kt @@ -27,7 +27,6 @@ import java.util.Date class EndurainSetupViewModel(application: Application) : AndroidViewModel(application) { private val LOG = LoggerFactory.getLogger(EndurainSetupViewModel::class.java) - private val tokenManager = EndurainTokenManager(application) private lateinit var apiClient: EndurainApiClient enum class Step { @@ -38,6 +37,7 @@ class EndurainSetupViewModel(application: Application) : AndroidViewModel(applic SSO_LOGIN } + val tokenManager = EndurainTokenManager(application) var step = Step.SERVER var server = "" var localLoginEnabled = false @@ -45,7 +45,6 @@ class EndurainSetupViewModel(application: Application) : AndroidViewModel(applic var pendingMfaUsername: String? = null var serverVersion: String? = null - /** * Fetch server version */ @@ -57,9 +56,10 @@ class EndurainSetupViewModel(application: Application) : AndroidViewModel(applic try { apiClient = EndurainApiClient(serverUrl, tokenManager) serverVersion = apiClient.fetchVersion() + callback(true) } catch (e: Exception) { LOG.error("Fetching server version error", e) - callback(true) + callback(false) } }.start() } @@ -224,20 +224,6 @@ class EndurainSetupViewModel(application: Application) : AndroidViewModel(applic }.start() } - /** - * Check if user is currently logged in - */ - fun isLoggedIn(): Boolean { - return tokenManager.getAccessToken() != null && !tokenManager.isTokenExpired() - } - - /** - * Get token expiry date - */ - fun getTokenExpiresAt(): Date { - return DateTimeUtils.parseTimeStamp(tokenManager.getAccessTokenExpiresAt()) - } - /** * Logout and clear tokens */ diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/endurain/EndurainTokenManager.kt b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/endurain/EndurainTokenManager.kt index b2aa64b533..82d1d26aae 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/endurain/EndurainTokenManager.kt +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/endurain/EndurainTokenManager.kt @@ -38,18 +38,30 @@ class EndurainTokenManager(context: Context) { sharedPreferences.edit { putString("access_token", accessToken) .putString("refresh_token", refreshToken) - .putInt("expires_at", accessTokenExpiresAt) + .putInt("access_token_expires_at", accessTokenExpiresAt) + .putInt("refresh_token_expires_at", + ((System.currentTimeMillis() / 1000) + (7 * 24 * 60 * 60)).toInt() + ) // FIXME: 7 days is the Endurain default for refresh token expiry + // https://github.com/endurain-project/endurain/issues/514 } } - fun getAccessToken(): String? = sharedPreferences.getString("access_token", null) - fun getRefreshToken(): String? = sharedPreferences.getString("refresh_token", null) - fun getAccessTokenExpiresAt(): Int = sharedPreferences.getInt("expires_at", 0) - fun isTokenExpired(): Boolean { - return (System.currentTimeMillis() / 1000) >= getAccessTokenExpiresAt() - } - fun clearTokens() { sharedPreferences.edit { clear() } } + + fun getAccessToken(): String? = sharedPreferences.getString("access_token", null) + fun getAccessTokenExpiresAt(): Int = sharedPreferences.getInt("access_token_expires_at", 0) + fun getRefreshToken(): String? = sharedPreferences.getString("refresh_token", null) + fun getRefreshTokenExpiresAt(): Int = sharedPreferences.getInt("refresh_token_expires_at", 0) + + fun isLoggedIn(): Boolean { + return getRefreshToken() != null && !isRefreshTokenExpired() + } + fun isAccessTokenExpired(): Boolean { + return (System.currentTimeMillis() / 1000) >= getAccessTokenExpiresAt() + } + fun isRefreshTokenExpired(): Boolean { + return (System.currentTimeMillis() / 1000) >= getRefreshTokenExpiresAt() + } } \ No newline at end of file diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/workouts/WorkoutDetailsFragment.kt b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/workouts/WorkoutDetailsFragment.kt index e37de015a5..18dbb42c2c 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/workouts/WorkoutDetailsFragment.kt +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/workouts/WorkoutDetailsFragment.kt @@ -61,7 +61,6 @@ import nodomain.freeyourgadget.gadgetbridge.activities.ActivitySummariesChartFra import nodomain.freeyourgadget.gadgetbridge.activities.charts.DurationXLabelFormatter import nodomain.freeyourgadget.gadgetbridge.activities.endurain.EndurainApiClient import nodomain.freeyourgadget.gadgetbridge.activities.endurain.EndurainSetupViewModel -import nodomain.freeyourgadget.gadgetbridge.activities.endurain.EndurainTokenManager import nodomain.freeyourgadget.gadgetbridge.activities.fit.FitViewerActivity import nodomain.freeyourgadget.gadgetbridge.activities.workouts.charts.ChartDataRepository import nodomain.freeyourgadget.gadgetbridge.activities.workouts.charts.DefaultWorkoutCharts @@ -639,13 +638,7 @@ class WorkoutDetailsFragment : Fragment(), MenuProvider { val endurainVm: EndurainSetupViewModel by viewModels() val server = GBApplication.getPrefs().preferences.getString("endurain_server", null) - if (server != null) { - endurainVm.performTokenRefresh(server) { - activity?.runOnUiThread { - overflowMenu?.findItem(R.id.activity_action_upload_to_endurain)?.isVisible = hasGpx && endurainVm.isLoggedIn() - } - } - } + overflowMenu?.findItem(R.id.activity_action_upload_to_endurain)?.isVisible = hasGpx && server != null && endurainVm.tokenManager.isLoggedIn() } private fun takeSharedScreenshot() { @@ -761,14 +754,26 @@ class WorkoutDetailsFragment : Fragment(), MenuProvider { } try { - val tokenManager = EndurainTokenManager(requireContext()) + val endurainVm: EndurainSetupViewModel by viewModels() val serverUrl = GBApplication.getPrefs().preferences.getString("endurain_server", null) - val apiClient = EndurainApiClient(serverUrl!!, tokenManager) - apiClient.uploadActivity(gpxFile) { success -> - if (success) - GB.toast("Successfully uploaded to Endurain", Toast.LENGTH_SHORT, GB.INFO) - else - GB.toast("Error while uploading to Endurain", Toast.LENGTH_SHORT, GB.INFO) + val apiClient = EndurainApiClient(serverUrl!!, endurainVm.tokenManager) + endurainVm.performTokenRefresh(serverUrl) { + apiClient.uploadActivity(gpxFile) { success -> + activity?.runOnUiThread { + if (success) + GB.toast( + "Successfully uploaded to Endurain", + Toast.LENGTH_SHORT, + GB.INFO + ) + else + GB.toast( + "Error while uploading to Endurain", + Toast.LENGTH_SHORT, + GB.INFO + ) + } + } } } catch (e: Exception) { GB.toast(