From a976d40d4547820a85d27f54b397d6baffc4ee30 Mon Sep 17 00:00:00 2001 From: enlineawork Date: Tue, 8 Sep 2026 17:11:30 -0300 Subject: [PATCH] feat(android): serialize rotating mobile refresh tokens --- .../dhinspeccion/data/SessionRefreshGate.kt | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 android-app/app/src/main/java/com/korexlabs/dhinspeccion/data/SessionRefreshGate.kt diff --git a/android-app/app/src/main/java/com/korexlabs/dhinspeccion/data/SessionRefreshGate.kt b/android-app/app/src/main/java/com/korexlabs/dhinspeccion/data/SessionRefreshGate.kt new file mode 100644 index 0000000..5429408 --- /dev/null +++ b/android-app/app/src/main/java/com/korexlabs/dhinspeccion/data/SessionRefreshGate.kt @@ -0,0 +1,32 @@ +package com.korexlabs.dhinspeccion.data + +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock + +/** + * Serializa la rotación del refresh token a nivel de proceso. + * + * El backend rota el token en cada refresh. Si dos repositorios reciben 401 al mismo tiempo, + * sólo el primero debe consumir el token anterior; el segundo reutiliza la sesión ya renovada. + */ +class SessionRefreshGate { + private val mutex = Mutex() + + suspend fun refreshIfNeeded( + previousRefreshToken: String, + load: () -> StoredSession?, + save: (MobileSessionResponse) -> StoredSession, + clear: () -> Unit, + refresh: suspend (String) -> MobileSessionResponse, + ): StoredSession = mutex.withLock { + val latest = load() ?: throw IllegalStateException("Sesión no iniciada") + if (latest.refreshToken != previousRefreshToken) return@withLock latest + + try { + save(refresh(previousRefreshToken)) + } catch (error: Throwable) { + clear() + throw error + } + } +}