mirror of
https://github.com/bitwarden/android.git
synced 2026-06-05 22:15:09 -05:00
BIT-1547: Hook up remaining push notification sync handling (#848)
This commit is contained in:
committed by
Álison Fernandes
parent
d2ffd7bf01
commit
8489bd1476
@@ -54,6 +54,7 @@ import com.x8bit.bitwarden.data.auth.repository.util.userOrganizationsList
|
||||
import com.x8bit.bitwarden.data.auth.repository.util.userOrganizationsListFlow
|
||||
import com.x8bit.bitwarden.data.auth.util.KdfParamsConstants.DEFAULT_PBKDF2_ITERATIONS
|
||||
import com.x8bit.bitwarden.data.auth.util.toSdkParams
|
||||
import com.x8bit.bitwarden.data.platform.manager.PushManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.dispatcher.DispatcherManager
|
||||
import com.x8bit.bitwarden.data.platform.repository.EnvironmentRepository
|
||||
import com.x8bit.bitwarden.data.platform.repository.SettingsRepository
|
||||
@@ -71,7 +72,9 @@ import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asSharedFlow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.filter
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import javax.inject.Singleton
|
||||
|
||||
@@ -95,6 +98,7 @@ class AuthRepositoryImpl(
|
||||
private val settingsRepository: SettingsRepository,
|
||||
private val vaultRepository: VaultRepository,
|
||||
private val userLogoutManager: UserLogoutManager,
|
||||
private val pushManager: PushManager,
|
||||
dispatcherManager: DispatcherManager,
|
||||
private val elapsedRealtimeMillisProvider: () -> Long = { SystemClock.elapsedRealtime() },
|
||||
) : AuthRepository {
|
||||
@@ -117,7 +121,9 @@ class AuthRepositoryImpl(
|
||||
* use of [Dispatchers.Unconfined] allows for this to happen synchronously whenever any of
|
||||
* these flows changes.
|
||||
*/
|
||||
private val collectionScope = CoroutineScope(dispatcherManager.unconfined)
|
||||
private val unconfinedScope = CoroutineScope(dispatcherManager.unconfined)
|
||||
|
||||
private val ioScope = CoroutineScope(dispatcherManager.io)
|
||||
|
||||
override var twoFactorResponse: TwoFactorRequired? = null
|
||||
|
||||
@@ -136,7 +142,7 @@ class AuthRepositoryImpl(
|
||||
?: AuthState.Unauthenticated
|
||||
}
|
||||
.stateIn(
|
||||
scope = collectionScope,
|
||||
scope = unconfinedScope,
|
||||
started = SharingStarted.Eagerly,
|
||||
initialValue = AuthState.Uninitialized,
|
||||
)
|
||||
@@ -169,7 +175,7 @@ class AuthRepositoryImpl(
|
||||
!mutableHasPendingAccountDeletionStateFlow.value
|
||||
}
|
||||
.stateIn(
|
||||
scope = collectionScope,
|
||||
scope = unconfinedScope,
|
||||
started = SharingStarted.Eagerly,
|
||||
initialValue = authDiskSource
|
||||
.userState
|
||||
@@ -199,6 +205,22 @@ class AuthRepositoryImpl(
|
||||
override var hasPendingAccountAddition: Boolean
|
||||
by mutableHasPendingAccountAdditionStateFlow::value
|
||||
|
||||
init {
|
||||
pushManager
|
||||
.syncOrgKeysFlow
|
||||
.onEach {
|
||||
val userId = activeUserId ?: return@onEach
|
||||
refreshAccessTokenSynchronously(userId)
|
||||
vaultRepository.sync()
|
||||
}
|
||||
.launchIn(ioScope)
|
||||
|
||||
pushManager
|
||||
.logoutFlow
|
||||
.onEach { logout() }
|
||||
.launchIn(unconfinedScope)
|
||||
}
|
||||
|
||||
override fun clearPendingAccountDeletion() {
|
||||
mutableHasPendingAccountDeletionStateFlow.value = false
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import com.x8bit.bitwarden.data.auth.datasource.sdk.AuthSdkSource
|
||||
import com.x8bit.bitwarden.data.auth.manager.UserLogoutManager
|
||||
import com.x8bit.bitwarden.data.auth.repository.AuthRepository
|
||||
import com.x8bit.bitwarden.data.auth.repository.AuthRepositoryImpl
|
||||
import com.x8bit.bitwarden.data.platform.manager.PushManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.dispatcher.DispatcherManager
|
||||
import com.x8bit.bitwarden.data.platform.repository.EnvironmentRepository
|
||||
import com.x8bit.bitwarden.data.platform.repository.SettingsRepository
|
||||
@@ -49,6 +50,7 @@ object AuthRepositoryModule {
|
||||
settingsRepository: SettingsRepository,
|
||||
vaultRepository: VaultRepository,
|
||||
userLogoutManager: UserLogoutManager,
|
||||
pushManager: PushManager,
|
||||
): AuthRepository = AuthRepositoryImpl(
|
||||
accountsService = accountsService,
|
||||
authRequestsService = authRequestsService,
|
||||
@@ -65,5 +67,6 @@ object AuthRepositoryModule {
|
||||
settingsRepository = settingsRepository,
|
||||
vaultRepository = vaultRepository,
|
||||
userLogoutManager = userLogoutManager,
|
||||
pushManager = pushManager,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -149,6 +149,8 @@ class PushManagerImpl @Inject constructor(
|
||||
SyncCipherUpsertData(
|
||||
cipherId = payload.id,
|
||||
revisionDate = payload.revisionDate,
|
||||
organizationId = payload.organizationId,
|
||||
collectionIds = payload.collectionIds,
|
||||
isUpdate = type == NotificationType.SYNC_CIPHER_UPDATE,
|
||||
),
|
||||
)
|
||||
|
||||
@@ -23,7 +23,7 @@ sealed class NotificationPayload {
|
||||
@SerialName("id") val id: String,
|
||||
@SerialName("userId") override val userId: String?,
|
||||
@SerialName("organizationId") val organizationId: String?,
|
||||
@SerialName("collectionIds") val collectionIds: List<String>,
|
||||
@SerialName("collectionIds") val collectionIds: List<String>?,
|
||||
@Contextual
|
||||
@SerialName("revisionDate") val revisionDate: ZonedDateTime,
|
||||
) : NotificationPayload()
|
||||
|
||||
@@ -13,5 +13,7 @@ import java.time.ZonedDateTime
|
||||
data class SyncCipherUpsertData(
|
||||
val cipherId: String,
|
||||
val revisionDate: ZonedDateTime,
|
||||
val organizationId: String?,
|
||||
val collectionIds: List<String>?,
|
||||
val isUpdate: Boolean,
|
||||
)
|
||||
|
||||
@@ -19,8 +19,11 @@ import com.x8bit.bitwarden.data.platform.datasource.disk.SettingsDiskSource
|
||||
import com.x8bit.bitwarden.data.platform.datasource.network.util.isNoConnectionError
|
||||
import com.x8bit.bitwarden.data.platform.manager.PushManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.dispatcher.DispatcherManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.model.SyncCipherDeleteData
|
||||
import com.x8bit.bitwarden.data.platform.manager.model.SyncCipherUpsertData
|
||||
import com.x8bit.bitwarden.data.platform.manager.model.SyncFolderDeleteData
|
||||
import com.x8bit.bitwarden.data.platform.manager.model.SyncFolderUpsertData
|
||||
import com.x8bit.bitwarden.data.platform.manager.model.SyncSendDeleteData
|
||||
import com.x8bit.bitwarden.data.platform.manager.model.SyncSendUpsertData
|
||||
import com.x8bit.bitwarden.data.platform.repository.model.DataState
|
||||
import com.x8bit.bitwarden.data.platform.repository.util.bufferedMutableSharedFlow
|
||||
@@ -91,11 +94,13 @@ import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asSharedFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.flow.firstOrNull
|
||||
import kotlinx.coroutines.flow.flatMapLatest
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.mapNotNull
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.flow.onStart
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
@@ -236,16 +241,36 @@ class VaultRepositoryImpl(
|
||||
}
|
||||
.launchIn(unconfinedScope)
|
||||
|
||||
pushManager
|
||||
.fullSyncFlow
|
||||
.onEach { syncIfNecessary() }
|
||||
.launchIn(unconfinedScope)
|
||||
|
||||
pushManager
|
||||
.syncCipherDeleteFlow
|
||||
.onEach(::deleteCipher)
|
||||
.launchIn(unconfinedScope)
|
||||
|
||||
pushManager
|
||||
.syncCipherUpsertFlow
|
||||
.onEach(::syncCipherIfNecessary)
|
||||
.launchIn(ioScope)
|
||||
|
||||
pushManager
|
||||
.syncSendDeleteFlow
|
||||
.onEach(::deleteSend)
|
||||
.launchIn(unconfinedScope)
|
||||
|
||||
pushManager
|
||||
.syncSendUpsertFlow
|
||||
.onEach(::syncSendIfNecessary)
|
||||
.launchIn(ioScope)
|
||||
|
||||
pushManager
|
||||
.syncFolderDeleteFlow
|
||||
.onEach(::deleteFolder)
|
||||
.launchIn(unconfinedScope)
|
||||
|
||||
pushManager
|
||||
.syncFolderUpsertFlow
|
||||
.onEach(::syncFolderIfNecessary)
|
||||
@@ -1231,19 +1256,76 @@ class VaultRepositoryImpl(
|
||||
.onEach { mutableSendDataStateFlow.value = it }
|
||||
|
||||
//region Push notification helpers
|
||||
/**
|
||||
* Deletes the cipher specified by [syncCipherDeleteData] from disk.
|
||||
*/
|
||||
private suspend fun deleteCipher(syncCipherDeleteData: SyncCipherDeleteData) {
|
||||
val userId = activeUserId ?: return
|
||||
|
||||
val cipherId = syncCipherDeleteData.cipherId
|
||||
vaultDiskSource.deleteCipher(
|
||||
userId = userId,
|
||||
cipherId = cipherId,
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Syncs an individual cipher contained in [syncCipherUpsertData] to disk if certain criteria
|
||||
* are met. If the resource cannot be found cloud-side, and it was updated, delete it from disk
|
||||
* for now.
|
||||
*/
|
||||
@Suppress("ReturnCount")
|
||||
private suspend fun syncCipherIfNecessary(syncCipherUpsertData: SyncCipherUpsertData) {
|
||||
val userId = activeUserId ?: return
|
||||
|
||||
// TODO Handle other filtering logic including revision date comparison. This will still be
|
||||
// handled as part of BIT-1547.
|
||||
|
||||
val cipherId = syncCipherUpsertData.cipherId
|
||||
val organizationId = syncCipherUpsertData.organizationId
|
||||
val collectionIds = syncCipherUpsertData.collectionIds
|
||||
val revisionDate = syncCipherUpsertData.revisionDate
|
||||
val isUpdate = syncCipherUpsertData.isUpdate
|
||||
|
||||
val localCipher = ciphersStateFlow
|
||||
.mapNotNull { it.data }
|
||||
.first()
|
||||
.find { it.id == cipherId }
|
||||
|
||||
// Return if local cipher is more recent
|
||||
if (localCipher != null &&
|
||||
localCipher.revisionDate.epochSecond > revisionDate.toEpochSecond()
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
var shouldUpdate: Boolean
|
||||
val shouldCheckCollections: Boolean
|
||||
|
||||
when {
|
||||
isUpdate -> {
|
||||
shouldUpdate = localCipher != null
|
||||
shouldCheckCollections = true
|
||||
}
|
||||
|
||||
collectionIds == null || organizationId == null -> {
|
||||
shouldUpdate = localCipher == null
|
||||
shouldCheckCollections = false
|
||||
}
|
||||
|
||||
else -> {
|
||||
shouldUpdate = false
|
||||
shouldCheckCollections = true
|
||||
}
|
||||
}
|
||||
|
||||
if (!shouldUpdate && shouldCheckCollections && organizationId != null) {
|
||||
// Check if there are any collections in common
|
||||
shouldUpdate = collectionsStateFlow
|
||||
.mapNotNull { it.data }
|
||||
.first()
|
||||
.mapNotNull { it.id }
|
||||
.any { collectionIds?.contains(it) == true } == true
|
||||
}
|
||||
|
||||
if (!shouldUpdate) return
|
||||
|
||||
ciphersService
|
||||
.getCipher(cipherId)
|
||||
.fold(
|
||||
@@ -1259,6 +1341,19 @@ class VaultRepositoryImpl(
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the send specified by [syncSendDeleteData] from disk.
|
||||
*/
|
||||
private suspend fun deleteSend(syncSendDeleteData: SyncSendDeleteData) {
|
||||
val userId = activeUserId ?: return
|
||||
|
||||
val sendId = syncSendDeleteData.sendId
|
||||
vaultDiskSource.deleteSend(
|
||||
userId = userId,
|
||||
sendId = sendId,
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Syncs an individual send contained in [syncSendUpsertData] to disk if certain criteria are
|
||||
* met. If the resource cannot be found cloud-side, and it was updated, delete it from disk for
|
||||
@@ -1266,12 +1361,22 @@ class VaultRepositoryImpl(
|
||||
*/
|
||||
private suspend fun syncSendIfNecessary(syncSendUpsertData: SyncSendUpsertData) {
|
||||
val userId = activeUserId ?: return
|
||||
|
||||
// TODO Handle other filtering logic including revision date comparison. This will still be
|
||||
// handled as part of BIT-1547.
|
||||
|
||||
val sendId = syncSendUpsertData.sendId
|
||||
val isUpdate = syncSendUpsertData.isUpdate
|
||||
val revisionDate = syncSendUpsertData.revisionDate
|
||||
|
||||
val localSend = sendDataStateFlow
|
||||
.mapNotNull { it.data }
|
||||
.first()
|
||||
.sendViewList
|
||||
.find { it.id == sendId }
|
||||
val isValidCreate = !isUpdate && localSend == null
|
||||
val isValidUpdate = isUpdate &&
|
||||
localSend != null &&
|
||||
localSend.revisionDate.epochSecond < revisionDate.toEpochSecond()
|
||||
|
||||
if (!isValidCreate && !isValidUpdate) return
|
||||
|
||||
sendsService
|
||||
.getSend(sendId)
|
||||
.fold(
|
||||
@@ -1287,17 +1392,44 @@ class VaultRepositoryImpl(
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the folder specified by [syncFolderDeleteData] from disk.
|
||||
*/
|
||||
private suspend fun deleteFolder(syncFolderDeleteData: SyncFolderDeleteData) {
|
||||
val userId = activeUserId ?: return
|
||||
|
||||
val folderId = syncFolderDeleteData.folderId
|
||||
clearFolderIdFromCiphers(
|
||||
folderId = folderId,
|
||||
userId = userId,
|
||||
)
|
||||
vaultDiskSource.deleteFolder(
|
||||
folderId = folderId,
|
||||
userId = userId,
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Syncs an individual folder contained in [syncFolderUpsertData] to disk if certain criteria
|
||||
* are met.
|
||||
*/
|
||||
private suspend fun syncFolderIfNecessary(syncFolderUpsertData: SyncFolderUpsertData) {
|
||||
val userId = activeUserId ?: return
|
||||
|
||||
// TODO Handle other filtering logic including revision date comparison. This will still be
|
||||
// handled as part of BIT-1547.
|
||||
|
||||
val folderId = syncFolderUpsertData.folderId
|
||||
val isUpdate = syncFolderUpsertData.isUpdate
|
||||
val revisionDate = syncFolderUpsertData.revisionDate
|
||||
|
||||
val localFolder = foldersStateFlow
|
||||
.mapNotNull { it.data }
|
||||
.first()
|
||||
.find { it.id == folderId }
|
||||
val isValidCreate = !isUpdate && localFolder == null
|
||||
val isValidUpdate = isUpdate &&
|
||||
localFolder != null &&
|
||||
localFolder.revisionDate.epochSecond < revisionDate.toEpochSecond()
|
||||
|
||||
if (!isValidCreate && !isValidUpdate) return
|
||||
|
||||
folderService
|
||||
.getFolder(folderId)
|
||||
.onSuccess { vaultDiskSource.saveFolder(userId, it) }
|
||||
|
||||
Reference in New Issue
Block a user