Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weโ€™ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PM-10600: Re-register device with push token every 7 days. #4303

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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].
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -279,36 +297,52 @@ 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 {
registerPushTokenIfNecessaryInternal(
userId = userId,
token = it,
)
registerStoredPushTokenIfNecessaryInternal(userId)
}
}
}

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 = {
Expand All @@ -318,7 +352,7 @@ class PushManagerImpl @Inject constructor(
)
pushDiskSource.storeCurrentPushToken(
userId = userId,
pushToken = token,
pushToken = newToken,
)
},
onFailure = {
Expand Down
Loading