@@ -11,6 +11,8 @@ import kotlinx.serialization.SerialName
1111import kotlinx.serialization.Serializable
1212import kotlinx.serialization.SerializationException
1313import kotlinx.serialization.json.Json
14+ import kotlin.time.Clock
15+ import kotlin.time.Duration.Companion.hours
1416
1517@Serializable
1618data class BootConfig (
@@ -141,6 +143,13 @@ class RealBootConfigProvider(
141143 val bootConfig = bootConfigService.getBootConfig(url)
142144 logger.d(" got bootconfig: $bootConfig " )
143145 settings.putString(BOOTCONFIG_KEY , Json .encodeToString(bootConfig))
146+ settings.putLong(BOOTCONFIG_FETCHED_AT_KEY , Clock .System .now().toEpochMilliseconds())
147+ }
148+
149+ private fun isStale (): Boolean {
150+ val fetchedAt = settings.getLongOrNull(BOOTCONFIG_FETCHED_AT_KEY ) ? : return true
151+ val age = Clock .System .now().toEpochMilliseconds() - fetchedAt
152+ return age >= REFRESH_INTERVAL .inWholeMilliseconds || age < 0
144153 }
145154
146155 private fun applyOverrides (config : BootConfig ): BootConfig {
@@ -186,6 +195,15 @@ class RealBootConfigProvider(
186195 override suspend fun getBootConfig (): BootConfig ? {
187196 if (! settings.hasKey(BOOTCONFIG_KEY )) {
188197 fetch()
198+ } else if (isStale()) {
199+ // Refresh in case tokens/URLs (e.g. the Rebble ASR access tokens baked into
200+ // voice.languages[].endpoint) have rotated. Best-effort: fall back to the cached
201+ // version if the network is unavailable.
202+ try {
203+ fetch()
204+ } catch (e: Exception ) {
205+ logger.w(e) { " Failed to refresh stale boot config, using cached version" }
206+ }
189207 }
190208 val config = loadFromSettings()
191209 if (config != null ) {
@@ -201,5 +219,7 @@ class RealBootConfigProvider(
201219 companion object {
202220 private val BOOTCONFIG_URL_KEY = " boot_config_url"
203221 private val BOOTCONFIG_KEY = " boot_config"
222+ private val BOOTCONFIG_FETCHED_AT_KEY = " boot_config_fetched_at"
223+ private val REFRESH_INTERVAL = 24 .hours
204224 }
205225}
0 commit comments