From 5218dd1693064022331704f17da158add016770a Mon Sep 17 00:00:00 2001 From: Maciej Zieniuk Date: Thu, 14 Nov 2024 16:34:33 +0000 Subject: [PATCH] PM-10600: Re-register device with push token every 7 days. The Azure Notification Hub tags can change frequently, like here for Notification Center work we need to add new tags to existing devices registrations. Instead of painful migration plan, this PR adds a frequent re-registration of push notification for the device against Bitwarden Server. --- .../data/platform/manager/PushManagerImpl.kt | 59 +++++++++++++++---- 1 file changed, 46 insertions(+), 13 deletions(-) diff --git a/app/src/main/java/com/x8bit/bitwarden/data/platform/manager/PushManagerImpl.kt b/app/src/main/java/com/x8bit/bitwarden/data/platform/manager/PushManagerImpl.kt index 85ad1603e..97112173f 100644 --- a/app/src/main/java/com/x8bit/bitwarden/data/platform/manager/PushManagerImpl.kt +++ b/app/src/main/java/com/x8bit/bitwarden/data/platform/manager/PushManagerImpl.kt @@ -20,18 +20,34 @@ import com.x8bit.bitwarden.data.platform.manager.model.SyncSendUpsertData import com.x8bit.bitwarden.data.platform.repository.util.bufferedMutableSharedFlow import com.x8bit.bitwarden.data.platform.util.decodeFromStringOrNull import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Job +import kotlinx.coroutines.delay import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.mapNotNull import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.isActive import kotlinx.coroutines.launch import kotlinx.serialization.json.Json import java.time.Clock import java.time.ZoneOffset import java.time.ZonedDateTime -import java.time.temporal.ChronoUnit import javax.inject.Inject +import kotlin.time.Duration +import kotlin.time.Duration.Companion.days +import kotlin.time.Duration.Companion.minutes +import kotlin.time.toJavaDuration + +/** + * The amount of time to delay before a subsequent attempts to expire the push token. + */ +private val PUSH_TOKEN_EXPIRE_INTERVAL: Duration = 1.minutes + +/** + * The amount of time to delay before expiring the push token. + */ +private val PUSH_TOKEN_EXPIRE_DELAY: Duration = 7.days /** * Primary implementation of [PushManager]. @@ -98,6 +114,8 @@ class PushManagerImpl @Inject constructor( private val activeUserId: String? get() = authDiskSource.userState?.activeUserId + private var registerPushTokenJob: Job = Job().apply { complete() } + init { authDiskSource .activeUserIdChangesFlow @@ -279,10 +297,14 @@ class PushManagerImpl @Inject constructor( val userId = activeUserId ?: return if (!isLoggedIn(userId)) return - // If the last registered token is from less than a day before, skip this for now - val lastRegistration = pushDiskSource.getLastPushTokenRegistrationDate(userId)?.toInstant() - val dayBefore = clock.instant().minus(1, ChronoUnit.DAYS) - if (lastRegistration?.isAfter(dayBefore) == true) return + // Periodically check if the token needs to be re-registered with Bitwarden Server + registerPushTokenJob.cancel() + registerPushTokenJob = ioScope.launch { + while (coroutineContext.isActive) { + delay(duration = PUSH_TOKEN_EXPIRE_INTERVAL) + registerStoredPushTokenIfNecessaryInternal(userId) + } + } ioScope.launch { pushDiskSource.registeredPushToken?.let { @@ -296,19 +318,30 @@ class PushManagerImpl @Inject constructor( private suspend fun registerPushTokenIfNecessaryInternal(userId: String, token: String) { val currentToken = pushDiskSource.getCurrentPushToken(userId) - if (token == currentToken) { - // Our token is up-to-date, so just update the last registration date - pushDiskSource.storeLastPushTokenRegistrationDate( - userId = userId, - registrationDate = ZonedDateTime.ofInstant(clock.instant(), ZoneOffset.UTC), - ) return } + registerPushTokenInternal(userId, token) + } + + private suspend fun registerStoredPushTokenIfNecessaryInternal(userId: String) { + if (!isLoggedIn(userId)) return + + val lastRegistration = + pushDiskSource.getLastPushTokenRegistrationDate(userId)?.toInstant() ?: return + val expiryTime = clock.instant().minus(PUSH_TOKEN_EXPIRE_DELAY.toJavaDuration()) + if (lastRegistration.isAfter(expiryTime)) return + + val currentToken = pushDiskSource.getCurrentPushToken(userId) ?: return + + registerPushTokenInternal(userId, currentToken) + } + + private suspend fun registerPushTokenInternal(userId: String, newToken: String) { pushService .putDeviceToken( - body = PushTokenRequest(token), + body = PushTokenRequest(newToken), ) .fold( onSuccess = { @@ -318,7 +351,7 @@ class PushManagerImpl @Inject constructor( ) pushDiskSource.storeCurrentPushToken( userId = userId, - pushToken = token, + pushToken = newToken, ) }, onFailure = {