mirror of
https://github.com/bitwarden/android.git
synced 2026-10-22 02:53:19 -05:00
[PM-33506] feat: Add PremiumStatusChanged push notification support (#6656)
This commit is contained in:
1 parent
85c3a1deb8
commit
13c8cc08a6
6 files changed
+117
-1
No files matched your search
@@ -2,6 +2,7 @@ package com.x8bit.bitwarden.data.platform.manager
|
||||
|
||||
import com.x8bit.bitwarden.data.platform.manager.model.NotificationLogoutData
|
||||
import com.x8bit.bitwarden.data.platform.manager.model.PasswordlessRequestData
|
||||
import com.x8bit.bitwarden.data.platform.manager.model.PremiumStatusChangedData
|
||||
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
|
||||
@@ -29,6 +30,11 @@ interface PushManager {
|
||||
*/
|
||||
val passwordlessRequestFlow: Flow<PasswordlessRequestData>
|
||||
|
||||
/**
|
||||
* Flow that represents premium status change notifications.
|
||||
*/
|
||||
val premiumStatusChangedFlow: Flow<PremiumStatusChangedData>
|
||||
|
||||
/**
|
||||
* Flow that represents requests intended to trigger a sync cipher delete.
|
||||
*/
|
||||
|
||||
@@ -14,6 +14,7 @@ import com.x8bit.bitwarden.data.platform.manager.model.NotificationLogoutData
|
||||
import com.x8bit.bitwarden.data.platform.manager.model.NotificationPayload
|
||||
import com.x8bit.bitwarden.data.platform.manager.model.NotificationType
|
||||
import com.x8bit.bitwarden.data.platform.manager.model.PasswordlessRequestData
|
||||
import com.x8bit.bitwarden.data.platform.manager.model.PremiumStatusChangedData
|
||||
import com.x8bit.bitwarden.data.platform.manager.model.PushNotificationLogOutReason
|
||||
import com.x8bit.bitwarden.data.platform.manager.model.SyncCipherDeleteData
|
||||
import com.x8bit.bitwarden.data.platform.manager.model.SyncCipherUpsertData
|
||||
@@ -63,6 +64,8 @@ class PushManagerImpl @Inject constructor(
|
||||
private val mutableLogoutSharedFlow = bufferedMutableSharedFlow<NotificationLogoutData>()
|
||||
private val mutablePasswordlessRequestSharedFlow =
|
||||
bufferedMutableSharedFlow<PasswordlessRequestData>()
|
||||
private val mutablePremiumStatusChangedSharedFlow =
|
||||
bufferedMutableSharedFlow<PremiumStatusChangedData>()
|
||||
private val mutableSyncCipherDeleteSharedFlow =
|
||||
bufferedMutableSharedFlow<SyncCipherDeleteData>()
|
||||
private val mutableSyncCipherUpsertSharedFlow =
|
||||
@@ -86,6 +89,9 @@ class PushManagerImpl @Inject constructor(
|
||||
override val passwordlessRequestFlow: SharedFlow<PasswordlessRequestData>
|
||||
get() = mutablePasswordlessRequestSharedFlow.asSharedFlow()
|
||||
|
||||
override val premiumStatusChangedFlow: SharedFlow<PremiumStatusChangedData>
|
||||
get() = mutablePremiumStatusChangedSharedFlow.asSharedFlow()
|
||||
|
||||
override val syncCipherDeleteFlow: SharedFlow<SyncCipherDeleteData>
|
||||
get() = mutableSyncCipherDeleteSharedFlow.asSharedFlow()
|
||||
|
||||
@@ -305,6 +311,25 @@ class PushManagerImpl @Inject constructor(
|
||||
}
|
||||
?.let { mutableSyncSendDeleteSharedFlow.tryEmit(it) }
|
||||
}
|
||||
|
||||
NotificationType.PREMIUM_STATUS_CHANGED -> {
|
||||
json
|
||||
.decodeFromString<NotificationPayload.PremiumStatusChangedNotification>(
|
||||
string = notification.payload,
|
||||
)
|
||||
.takeIf { it.userId != null && it.isPremium != null }
|
||||
?.let { payload ->
|
||||
mutablePremiumStatusChangedSharedFlow.tryEmit(
|
||||
PremiumStatusChangedData(
|
||||
userId = requireNotNull(payload.userId),
|
||||
isPremium = requireNotNull(payload.isPremium),
|
||||
),
|
||||
)
|
||||
mutableFullSyncSharedFlow.tryEmit(
|
||||
requireNotNull(payload.userId),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+10
-1
@@ -10,7 +10,7 @@ import java.time.Instant
|
||||
/**
|
||||
* The payload of a push notification.
|
||||
*
|
||||
* Note: The data we receive is not always reliable, so everything is nullable and we validate the
|
||||
* Note: The data we receive is not always reliable, so everything is nullable, and we validate the
|
||||
* data in the [PushManager] as necessary.
|
||||
*/
|
||||
@OptIn(ExperimentalSerializationApi::class)
|
||||
@@ -97,4 +97,13 @@ sealed class NotificationPayload {
|
||||
data class SyncNotification(
|
||||
@JsonNames("UserId", "userId") override val userId: String?,
|
||||
) : NotificationPayload()
|
||||
|
||||
/**
|
||||
* A notification payload for premium status changes.
|
||||
*/
|
||||
@Serializable
|
||||
data class PremiumStatusChangedNotification(
|
||||
@JsonNames("UserId", "userId") override val userId: String?,
|
||||
@JsonNames("Premium", "premium") val isPremium: Boolean?,
|
||||
) : NotificationPayload()
|
||||
}
|
||||
+3
@@ -60,6 +60,9 @@ enum class NotificationType {
|
||||
|
||||
@SerialName("16")
|
||||
AUTH_REQUEST_RESPONSE,
|
||||
|
||||
@SerialName("27")
|
||||
PREMIUM_STATUS_CHANGED,
|
||||
}
|
||||
|
||||
@Keep
|
||||
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
package com.x8bit.bitwarden.data.platform.manager.model
|
||||
|
||||
/**
|
||||
* Data class representing a premium status changed push notification.
|
||||
*
|
||||
* @property userId The user ID associated with the status change.
|
||||
* @property isPremium Whether premium is now enabled.
|
||||
*/
|
||||
data class PremiumStatusChangedData(
|
||||
val userId: String,
|
||||
val isPremium: Boolean,
|
||||
)
|
||||
@@ -19,6 +19,7 @@ import com.x8bit.bitwarden.data.platform.datasource.disk.PushDiskSource
|
||||
import com.x8bit.bitwarden.data.platform.datasource.disk.PushDiskSourceImpl
|
||||
import com.x8bit.bitwarden.data.platform.manager.model.NotificationLogoutData
|
||||
import com.x8bit.bitwarden.data.platform.manager.model.PasswordlessRequestData
|
||||
import com.x8bit.bitwarden.data.platform.manager.model.PremiumStatusChangedData
|
||||
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
|
||||
@@ -163,6 +164,48 @@ class PushManagerTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `onMessageReceived with premium status changed emits to premiumStatusChangedFlow`() =
|
||||
runTest {
|
||||
pushManager.premiumStatusChangedFlow.test {
|
||||
pushManager.onMessageReceived(
|
||||
PREMIUM_STATUS_CHANGED_NOTIFICATION_MAP,
|
||||
)
|
||||
assertEquals(
|
||||
PremiumStatusChangedData(
|
||||
userId = "078966a2-93c2-4618-ae2a-0a2394c88d37",
|
||||
isPremium = true,
|
||||
),
|
||||
awaitItem(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `onMessageReceived with premium status changed also emits to fullSyncFlow`() =
|
||||
runTest {
|
||||
pushManager.fullSyncFlow.test {
|
||||
pushManager.onMessageReceived(
|
||||
PREMIUM_STATUS_CHANGED_NOTIFICATION_MAP,
|
||||
)
|
||||
assertEquals(
|
||||
"078966a2-93c2-4618-ae2a-0a2394c88d37",
|
||||
awaitItem(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `onMessageReceived with premium status changed with null fields does not emit`() =
|
||||
runTest {
|
||||
pushManager.premiumStatusChangedFlow.test {
|
||||
pushManager.onMessageReceived(
|
||||
PREMIUM_STATUS_CHANGED_NULL_FIELDS_NOTIFICATION_MAP,
|
||||
)
|
||||
expectNoEvents()
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
inner class LoggedOutUserState {
|
||||
@BeforeEach
|
||||
@@ -1063,3 +1106,21 @@ private val SYNC_VAULT_NOTIFICATION_MAP = mapOf(
|
||||
"Date": "2023-10-27T12:00:00.000Z"
|
||||
}""",
|
||||
)
|
||||
|
||||
private val PREMIUM_STATUS_CHANGED_NOTIFICATION_MAP = mapOf(
|
||||
"contextId" to "801f459d-8e51-47d0-b072-3f18c9f66f64",
|
||||
"type" to "27",
|
||||
"payload" to """{
|
||||
"UserId": "078966a2-93c2-4618-ae2a-0a2394c88d37",
|
||||
"Premium": true
|
||||
}""",
|
||||
)
|
||||
|
||||
private val PREMIUM_STATUS_CHANGED_NULL_FIELDS_NOTIFICATION_MAP = mapOf(
|
||||
"contextId" to "801f459d-8e51-47d0-b072-3f18c9f66f64",
|
||||
"type" to "27",
|
||||
"payload" to """{
|
||||
"UserId": null,
|
||||
"Premium": null
|
||||
}""",
|
||||
)
|
||||
Reference in new issue
Block a user