UnifiedPush: emit error when registration fails.
Note that I did not manage to have the method `onRegistrationFailed` invoked. If the network is not available for instance, unregistering the previous pusher will fail first.
This commit is contained in:
@@ -10,6 +10,7 @@ package io.element.android.libraries.push.api
|
|||||||
|
|
||||||
import io.element.android.libraries.matrix.api.MatrixClient
|
import io.element.android.libraries.matrix.api.MatrixClient
|
||||||
import io.element.android.libraries.matrix.api.core.SessionId
|
import io.element.android.libraries.matrix.api.core.SessionId
|
||||||
|
import io.element.android.libraries.matrix.api.core.UserId
|
||||||
import io.element.android.libraries.push.api.history.PushHistoryItem
|
import io.element.android.libraries.push.api.history.PushHistoryItem
|
||||||
import io.element.android.libraries.pushproviders.api.Distributor
|
import io.element.android.libraries.pushproviders.api.Distributor
|
||||||
import io.element.android.libraries.pushproviders.api.PushProvider
|
import io.element.android.libraries.pushproviders.api.PushProvider
|
||||||
@@ -73,4 +74,9 @@ interface PushService {
|
|||||||
* Reset the battery optimization state.
|
* Reset the battery optimization state.
|
||||||
*/
|
*/
|
||||||
suspend fun resetBatteryOptimizationState()
|
suspend fun resetBatteryOptimizationState()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notify the user that the service is un-registered.
|
||||||
|
*/
|
||||||
|
suspend fun onServiceUnregistered(userId: UserId)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,12 +14,14 @@ import dev.zacsweers.metro.SingleIn
|
|||||||
import dev.zacsweers.metro.binding
|
import dev.zacsweers.metro.binding
|
||||||
import io.element.android.libraries.matrix.api.MatrixClient
|
import io.element.android.libraries.matrix.api.MatrixClient
|
||||||
import io.element.android.libraries.matrix.api.core.SessionId
|
import io.element.android.libraries.matrix.api.core.SessionId
|
||||||
|
import io.element.android.libraries.matrix.api.core.UserId
|
||||||
import io.element.android.libraries.push.api.GetCurrentPushProvider
|
import io.element.android.libraries.push.api.GetCurrentPushProvider
|
||||||
import io.element.android.libraries.push.api.PushService
|
import io.element.android.libraries.push.api.PushService
|
||||||
import io.element.android.libraries.push.api.history.PushHistoryItem
|
import io.element.android.libraries.push.api.history.PushHistoryItem
|
||||||
import io.element.android.libraries.push.impl.push.MutableBatteryOptimizationStore
|
import io.element.android.libraries.push.impl.push.MutableBatteryOptimizationStore
|
||||||
import io.element.android.libraries.push.impl.store.PushDataStore
|
import io.element.android.libraries.push.impl.store.PushDataStore
|
||||||
import io.element.android.libraries.push.impl.test.TestPush
|
import io.element.android.libraries.push.impl.test.TestPush
|
||||||
|
import io.element.android.libraries.push.impl.unregistration.ServiceUnregisteredHandler
|
||||||
import io.element.android.libraries.pushproviders.api.Distributor
|
import io.element.android.libraries.pushproviders.api.Distributor
|
||||||
import io.element.android.libraries.pushproviders.api.PushProvider
|
import io.element.android.libraries.pushproviders.api.PushProvider
|
||||||
import io.element.android.libraries.pushstore.api.UserPushStoreFactory
|
import io.element.android.libraries.pushstore.api.UserPushStoreFactory
|
||||||
@@ -40,6 +42,7 @@ class DefaultPushService(
|
|||||||
private val pushClientSecretStore: PushClientSecretStore,
|
private val pushClientSecretStore: PushClientSecretStore,
|
||||||
private val pushDataStore: PushDataStore,
|
private val pushDataStore: PushDataStore,
|
||||||
private val mutableBatteryOptimizationStore: MutableBatteryOptimizationStore,
|
private val mutableBatteryOptimizationStore: MutableBatteryOptimizationStore,
|
||||||
|
private val serviceUnregisteredHandler: ServiceUnregisteredHandler,
|
||||||
) : PushService, SessionListener {
|
) : PushService, SessionListener {
|
||||||
init {
|
init {
|
||||||
observeSessions()
|
observeSessions()
|
||||||
@@ -141,4 +144,8 @@ class DefaultPushService(
|
|||||||
override suspend fun resetBatteryOptimizationState() {
|
override suspend fun resetBatteryOptimizationState() {
|
||||||
mutableBatteryOptimizationStore.reset()
|
mutableBatteryOptimizationStore.reset()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun onServiceUnregistered(userId: UserId) {
|
||||||
|
serviceUnregisteredHandler.handle(userId)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ interface NotificationDisplayer {
|
|||||||
fun cancelNotification(tag: String?, id: Int)
|
fun cancelNotification(tag: String?, id: Int)
|
||||||
fun displayDiagnosticNotification(notification: Notification): Boolean
|
fun displayDiagnosticNotification(notification: Notification): Boolean
|
||||||
fun dismissDiagnosticNotification()
|
fun dismissDiagnosticNotification()
|
||||||
|
fun displayUnregistrationNotification(notification: Notification): Boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
@ContributesBinding(AppScope::class)
|
@ContributesBinding(AppScope::class)
|
||||||
@@ -60,6 +61,14 @@ class DefaultNotificationDisplayer(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun displayUnregistrationNotification(notification: Notification): Boolean {
|
||||||
|
return showNotification(
|
||||||
|
tag = TAG_DIAGNOSTIC,
|
||||||
|
id = NOTIFICATION_ID_UNREGISTRATION,
|
||||||
|
notification = notification,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val TAG_DIAGNOSTIC = "DIAGNOSTIC"
|
private const val TAG_DIAGNOSTIC = "DIAGNOSTIC"
|
||||||
|
|
||||||
@@ -67,5 +76,6 @@ class DefaultNotificationDisplayer(
|
|||||||
* IDs for notifications
|
* IDs for notifications
|
||||||
* ========================================================================================== */
|
* ========================================================================================== */
|
||||||
private const val NOTIFICATION_ID_DIAGNOSTIC = 888
|
private const val NOTIFICATION_ID_DIAGNOSTIC = 888
|
||||||
|
private const val NOTIFICATION_ID_UNREGISTRATION = 889
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -91,6 +91,10 @@ interface NotificationCreator {
|
|||||||
@ColorInt color: Int,
|
@ColorInt color: Int,
|
||||||
): Notification
|
): Notification
|
||||||
|
|
||||||
|
fun createUnregistrationNotification(
|
||||||
|
notificationAccountParams: NotificationAccountParams,
|
||||||
|
): Notification
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
/**
|
/**
|
||||||
* Creates a tag for a message notification given its [roomId] and optional [threadId].
|
* Creates a tag for a message notification given its [roomId] and optional [threadId].
|
||||||
@@ -352,6 +356,28 @@ class DefaultNotificationCreator(
|
|||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun createUnregistrationNotification(
|
||||||
|
notificationAccountParams: NotificationAccountParams,
|
||||||
|
): Notification {
|
||||||
|
val userId = notificationAccountParams.user.userId
|
||||||
|
val text = if (notificationAccountParams.showSessionId) {
|
||||||
|
// TODO i18n
|
||||||
|
"$userId will not receive notifications anymore."
|
||||||
|
} else {
|
||||||
|
// TODO i18n
|
||||||
|
"You will not receive notifications anymore."
|
||||||
|
}
|
||||||
|
return NotificationCompat.Builder(context, notificationChannels.getChannelIdForTest())
|
||||||
|
.setContentTitle(stringProvider.getString(CommonStrings.dialog_title_warning))
|
||||||
|
.setContentText(text)
|
||||||
|
.configureWith(notificationAccountParams)
|
||||||
|
.setPriority(NotificationCompat.PRIORITY_MAX)
|
||||||
|
.setCategory(NotificationCompat.CATEGORY_ERROR)
|
||||||
|
.setAutoCancel(true)
|
||||||
|
.setContentIntent(pendingIntentFactory.createOpenSessionPendingIntent(userId))
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
private suspend fun MessagingStyle.addMessagesFromEvents(
|
private suspend fun MessagingStyle.addMessagesFromEvents(
|
||||||
events: List<NotifiableMessageEvent>,
|
events: List<NotifiableMessageEvent>,
|
||||||
imageLoader: ImageLoader,
|
imageLoader: ImageLoader,
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2025 Element Creations Ltd.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||||
|
* Please see LICENSE files in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.libraries.push.impl.unregistration
|
||||||
|
|
||||||
|
import androidx.compose.ui.graphics.toArgb
|
||||||
|
import dev.zacsweers.metro.AppScope
|
||||||
|
import dev.zacsweers.metro.ContributesBinding
|
||||||
|
import io.element.android.appconfig.NotificationConfig
|
||||||
|
import io.element.android.features.enterprise.api.EnterpriseService
|
||||||
|
import io.element.android.libraries.matrix.api.core.UserId
|
||||||
|
import io.element.android.libraries.matrix.api.user.MatrixUser
|
||||||
|
import io.element.android.libraries.push.impl.notifications.NotificationDisplayer
|
||||||
|
import io.element.android.libraries.push.impl.notifications.factories.NotificationAccountParams
|
||||||
|
import io.element.android.libraries.push.impl.notifications.factories.NotificationCreator
|
||||||
|
import io.element.android.libraries.sessionstorage.api.SessionStore
|
||||||
|
import kotlinx.coroutines.flow.first
|
||||||
|
|
||||||
|
interface ServiceUnregisteredHandler {
|
||||||
|
suspend fun handle(userId: UserId)
|
||||||
|
}
|
||||||
|
|
||||||
|
@ContributesBinding(AppScope::class)
|
||||||
|
class DefaultServiceUnregisteredHandler(
|
||||||
|
private val enterpriseService: EnterpriseService,
|
||||||
|
private val notificationCreator: NotificationCreator,
|
||||||
|
private val notificationDisplayer: NotificationDisplayer,
|
||||||
|
private val sessionStore: SessionStore,
|
||||||
|
) : ServiceUnregisteredHandler {
|
||||||
|
override suspend fun handle(userId: UserId) {
|
||||||
|
val color = enterpriseService.brandColorsFlow(userId).first()?.toArgb()
|
||||||
|
?: NotificationConfig.NOTIFICATION_ACCENT_COLOR
|
||||||
|
val hasMultipleAccounts = sessionStore.numberOfSessions() > 1
|
||||||
|
val notification = notificationCreator.createUnregistrationNotification(
|
||||||
|
NotificationAccountParams(
|
||||||
|
user = MatrixUser(userId),
|
||||||
|
color = color,
|
||||||
|
showSessionId = hasMultipleAccounts,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
notificationDisplayer.displayDiagnosticNotification(notification)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -25,6 +25,8 @@ import io.element.android.libraries.push.impl.store.InMemoryPushDataStore
|
|||||||
import io.element.android.libraries.push.impl.store.PushDataStore
|
import io.element.android.libraries.push.impl.store.PushDataStore
|
||||||
import io.element.android.libraries.push.impl.test.FakeTestPush
|
import io.element.android.libraries.push.impl.test.FakeTestPush
|
||||||
import io.element.android.libraries.push.impl.test.TestPush
|
import io.element.android.libraries.push.impl.test.TestPush
|
||||||
|
import io.element.android.libraries.push.impl.unregistration.FakeServiceUnregisteredHandler
|
||||||
|
import io.element.android.libraries.push.impl.unregistration.ServiceUnregisteredHandler
|
||||||
import io.element.android.libraries.push.test.FakeGetCurrentPushProvider
|
import io.element.android.libraries.push.test.FakeGetCurrentPushProvider
|
||||||
import io.element.android.libraries.pushproviders.api.Config
|
import io.element.android.libraries.pushproviders.api.Config
|
||||||
import io.element.android.libraries.pushproviders.api.Distributor
|
import io.element.android.libraries.pushproviders.api.Distributor
|
||||||
@@ -346,6 +348,7 @@ class DefaultPushServiceTest {
|
|||||||
pushClientSecretStore: PushClientSecretStore = InMemoryPushClientSecretStore(),
|
pushClientSecretStore: PushClientSecretStore = InMemoryPushClientSecretStore(),
|
||||||
pushDataStore: PushDataStore = InMemoryPushDataStore(),
|
pushDataStore: PushDataStore = InMemoryPushDataStore(),
|
||||||
mutableBatteryOptimizationStore: MutableBatteryOptimizationStore = FakeMutableBatteryOptimizationStore(),
|
mutableBatteryOptimizationStore: MutableBatteryOptimizationStore = FakeMutableBatteryOptimizationStore(),
|
||||||
|
serviceUnregisteredHandler: ServiceUnregisteredHandler = FakeServiceUnregisteredHandler(),
|
||||||
): DefaultPushService {
|
): DefaultPushService {
|
||||||
return DefaultPushService(
|
return DefaultPushService(
|
||||||
testPush = testPush,
|
testPush = testPush,
|
||||||
@@ -356,6 +359,7 @@ class DefaultPushServiceTest {
|
|||||||
pushClientSecretStore = pushClientSecretStore,
|
pushClientSecretStore = pushClientSecretStore,
|
||||||
pushDataStore = pushDataStore,
|
pushDataStore = pushDataStore,
|
||||||
mutableBatteryOptimizationStore = mutableBatteryOptimizationStore,
|
mutableBatteryOptimizationStore = mutableBatteryOptimizationStore,
|
||||||
|
serviceUnregisteredHandler = serviceUnregisteredHandler,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,6 +41,8 @@ class FakeNotificationCreator(
|
|||||||
> = lambdaRecorder { _, _, _, _, _ -> A_NOTIFICATION },
|
> = lambdaRecorder { _, _, _, _, _ -> A_NOTIFICATION },
|
||||||
var createDiagnosticNotificationResult: LambdaOneParamRecorder<Int, Notification> =
|
var createDiagnosticNotificationResult: LambdaOneParamRecorder<Int, Notification> =
|
||||||
lambdaRecorder<Int, Notification> { _ -> A_NOTIFICATION },
|
lambdaRecorder<Int, Notification> { _ -> A_NOTIFICATION },
|
||||||
|
val createUnregistrationNotificationResult: LambdaOneParamRecorder<NotificationAccountParams, Notification> =
|
||||||
|
lambdaRecorder { _ -> A_NOTIFICATION },
|
||||||
) : NotificationCreator {
|
) : NotificationCreator {
|
||||||
override suspend fun createMessagesListNotification(
|
override suspend fun createMessagesListNotification(
|
||||||
notificationAccountParams: NotificationAccountParams,
|
notificationAccountParams: NotificationAccountParams,
|
||||||
@@ -93,4 +95,8 @@ class FakeNotificationCreator(
|
|||||||
): Notification {
|
): Notification {
|
||||||
return createDiagnosticNotificationResult(color)
|
return createDiagnosticNotificationResult(color)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun createUnregistrationNotification(notificationAccountParams: NotificationAccountParams): Notification {
|
||||||
|
return createUnregistrationNotificationResult(notificationAccountParams)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ class FakeNotificationDisplayer(
|
|||||||
var cancelNotificationResult: LambdaTwoParamsRecorder<String?, Int, Unit> = lambdaRecorder { _, _ -> },
|
var cancelNotificationResult: LambdaTwoParamsRecorder<String?, Int, Unit> = lambdaRecorder { _, _ -> },
|
||||||
var displayDiagnosticNotificationResult: LambdaOneParamRecorder<Notification, Boolean> = lambdaRecorder { _ -> true },
|
var displayDiagnosticNotificationResult: LambdaOneParamRecorder<Notification, Boolean> = lambdaRecorder { _ -> true },
|
||||||
var dismissDiagnosticNotificationResult: LambdaNoParamRecorder<Unit> = lambdaRecorder { -> },
|
var dismissDiagnosticNotificationResult: LambdaNoParamRecorder<Unit> = lambdaRecorder { -> },
|
||||||
|
var displayUnregistrationNotificationResult: LambdaOneParamRecorder<Notification, Boolean> = lambdaRecorder { _ -> true },
|
||||||
) : NotificationDisplayer {
|
) : NotificationDisplayer {
|
||||||
override fun showNotification(tag: String?, id: Int, notification: Notification): Boolean {
|
override fun showNotification(tag: String?, id: Int, notification: Notification): Boolean {
|
||||||
return showNotificationResult(tag, id, notification)
|
return showNotificationResult(tag, id, notification)
|
||||||
@@ -41,6 +42,10 @@ class FakeNotificationDisplayer(
|
|||||||
return dismissDiagnosticNotificationResult()
|
return dismissDiagnosticNotificationResult()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun displayUnregistrationNotification(notification: Notification): Boolean {
|
||||||
|
return displayUnregistrationNotificationResult(notification)
|
||||||
|
}
|
||||||
|
|
||||||
fun verifySummaryCancelled(times: Int = 1) {
|
fun verifySummaryCancelled(times: Int = 1) {
|
||||||
cancelNotificationResult.assertions().isCalledExactly(times).withSequence(
|
cancelNotificationResult.assertions().isCalledExactly(times).withSequence(
|
||||||
listOf(value(null), value(NotificationIdProvider.getSummaryNotificationId(A_SESSION_ID)))
|
listOf(value(null), value(NotificationIdProvider.getSummaryNotificationId(A_SESSION_ID)))
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2025 Element Creations Ltd.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||||
|
* Please see LICENSE files in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.libraries.push.impl.unregistration
|
||||||
|
|
||||||
|
import io.element.android.libraries.matrix.api.core.UserId
|
||||||
|
import io.element.android.tests.testutils.lambda.lambdaError
|
||||||
|
|
||||||
|
class FakeServiceUnregisteredHandler(
|
||||||
|
private val handleResult: (UserId) -> Unit = { lambdaError() },
|
||||||
|
) : ServiceUnregisteredHandler {
|
||||||
|
override suspend fun handle(userId: UserId) {
|
||||||
|
handleResult(userId)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,6 +10,7 @@ package io.element.android.libraries.push.test
|
|||||||
|
|
||||||
import io.element.android.libraries.matrix.api.MatrixClient
|
import io.element.android.libraries.matrix.api.MatrixClient
|
||||||
import io.element.android.libraries.matrix.api.core.SessionId
|
import io.element.android.libraries.matrix.api.core.SessionId
|
||||||
|
import io.element.android.libraries.matrix.api.core.UserId
|
||||||
import io.element.android.libraries.push.api.PushService
|
import io.element.android.libraries.push.api.PushService
|
||||||
import io.element.android.libraries.push.api.history.PushHistoryItem
|
import io.element.android.libraries.push.api.history.PushHistoryItem
|
||||||
import io.element.android.libraries.pushproviders.api.Distributor
|
import io.element.android.libraries.pushproviders.api.Distributor
|
||||||
@@ -30,6 +31,7 @@ class FakePushService(
|
|||||||
private val setIgnoreRegistrationErrorLambda: (SessionId, Boolean) -> Unit = { _, _ -> lambdaError() },
|
private val setIgnoreRegistrationErrorLambda: (SessionId, Boolean) -> Unit = { _, _ -> lambdaError() },
|
||||||
private val resetPushHistoryResult: () -> Unit = { lambdaError() },
|
private val resetPushHistoryResult: () -> Unit = { lambdaError() },
|
||||||
private val resetBatteryOptimizationStateResult: () -> Unit = { lambdaError() },
|
private val resetBatteryOptimizationStateResult: () -> Unit = { lambdaError() },
|
||||||
|
private val onServiceUnregisteredResult: (UserId) -> Unit = { lambdaError() },
|
||||||
) : PushService {
|
) : PushService {
|
||||||
override suspend fun getCurrentPushProvider(sessionId: SessionId): PushProvider? {
|
override suspend fun getCurrentPushProvider(sessionId: SessionId): PushProvider? {
|
||||||
return registeredPushProvider ?: currentPushProvider(sessionId)
|
return registeredPushProvider ?: currentPushProvider(sessionId)
|
||||||
@@ -98,4 +100,8 @@ class FakePushService(
|
|||||||
override suspend fun resetBatteryOptimizationState() {
|
override suspend fun resetBatteryOptimizationState() {
|
||||||
resetBatteryOptimizationStateResult()
|
resetBatteryOptimizationStateResult()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun onServiceUnregistered(userId: UserId) {
|
||||||
|
onServiceUnregisteredResult(userId)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ dependencies {
|
|||||||
implementation(projects.libraries.androidutils)
|
implementation(projects.libraries.androidutils)
|
||||||
implementation(projects.libraries.core)
|
implementation(projects.libraries.core)
|
||||||
implementation(projects.libraries.matrix.api)
|
implementation(projects.libraries.matrix.api)
|
||||||
|
implementation(projects.libraries.push.api)
|
||||||
implementation(projects.libraries.uiStrings)
|
implementation(projects.libraries.uiStrings)
|
||||||
api(projects.libraries.troubleshoot.api)
|
api(projects.libraries.troubleshoot.api)
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import dev.zacsweers.metro.ContributesBinding
|
|||||||
import io.element.android.libraries.core.extensions.flatMap
|
import io.element.android.libraries.core.extensions.flatMap
|
||||||
import io.element.android.libraries.core.log.logger.LoggerTag
|
import io.element.android.libraries.core.log.logger.LoggerTag
|
||||||
import io.element.android.libraries.matrix.api.MatrixClientProvider
|
import io.element.android.libraries.matrix.api.MatrixClientProvider
|
||||||
|
import io.element.android.libraries.push.api.PushService
|
||||||
import io.element.android.libraries.pushstore.api.clientsecret.PushClientSecret
|
import io.element.android.libraries.pushstore.api.clientsecret.PushClientSecret
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
|
||||||
@@ -20,7 +21,7 @@ private val loggerTag = LoggerTag("UnifiedPushRemovedGatewayHandler", LoggerTag.
|
|||||||
/**
|
/**
|
||||||
* Handle new endpoint received from UnifiedPush. Will update the session matching the client secret.
|
* Handle new endpoint received from UnifiedPush. Will update the session matching the client secret.
|
||||||
*/
|
*/
|
||||||
interface UnifiedPushRemovedGatewayHandler {
|
fun interface UnifiedPushRemovedGatewayHandler {
|
||||||
suspend fun handle(clientSecret: String): Result<Unit>
|
suspend fun handle(clientSecret: String): Result<Unit>
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -29,9 +30,10 @@ class DefaultUnifiedPushRemovedGatewayHandler(
|
|||||||
private val unregisterUnifiedPushUseCase: UnregisterUnifiedPushUseCase,
|
private val unregisterUnifiedPushUseCase: UnregisterUnifiedPushUseCase,
|
||||||
private val pushClientSecret: PushClientSecret,
|
private val pushClientSecret: PushClientSecret,
|
||||||
private val matrixClientProvider: MatrixClientProvider,
|
private val matrixClientProvider: MatrixClientProvider,
|
||||||
|
private val pushService: PushService,
|
||||||
) : UnifiedPushRemovedGatewayHandler {
|
) : UnifiedPushRemovedGatewayHandler {
|
||||||
override suspend fun handle(clientSecret: String): Result<Unit> {
|
override suspend fun handle(clientSecret: String): Result<Unit> {
|
||||||
// Unregister the pusher for the session with this client secret, if is it using UnifiedPush.
|
// Unregister the pusher for the session with this client secret.
|
||||||
val userId = pushClientSecret.getUserIdFromSecret(clientSecret) ?: return Result.failure<Unit>(
|
val userId = pushClientSecret.getUserIdFromSecret(clientSecret) ?: return Result.failure<Unit>(
|
||||||
IllegalStateException("Unable to retrieve session")
|
IllegalStateException("Unable to retrieve session")
|
||||||
).also {
|
).also {
|
||||||
@@ -39,15 +41,42 @@ class DefaultUnifiedPushRemovedGatewayHandler(
|
|||||||
}
|
}
|
||||||
return matrixClientProvider
|
return matrixClientProvider
|
||||||
.getOrRestore(userId)
|
.getOrRestore(userId)
|
||||||
|
.onFailure {
|
||||||
|
Timber.tag(loggerTag.value).w(it, "Fails to restore client")
|
||||||
|
}
|
||||||
.flatMap { client ->
|
.flatMap { client ->
|
||||||
unregisterUnifiedPushUseCase.unregister(
|
unregisterUnifiedPushUseCase.unregister(
|
||||||
matrixClient = client,
|
matrixClient = client,
|
||||||
clientSecret = clientSecret,
|
clientSecret = clientSecret,
|
||||||
unregisterUnifiedPush = false,
|
unregisterUnifiedPush = false,
|
||||||
)
|
)
|
||||||
|
.onFailure {
|
||||||
|
Timber.tag(loggerTag.value).w(it, "Unable to unregister pusher")
|
||||||
|
}
|
||||||
|
.flatMap {
|
||||||
|
val pushProvider = pushService.getCurrentPushProvider(userId)
|
||||||
|
val distributor = pushProvider?.getCurrentDistributor(userId)
|
||||||
|
// Attempt to register again
|
||||||
|
if (pushProvider != null && distributor != null) {
|
||||||
|
pushService.registerWith(
|
||||||
|
client,
|
||||||
|
pushProvider,
|
||||||
|
distributor,
|
||||||
|
)
|
||||||
|
.onFailure {
|
||||||
|
Timber.tag(loggerTag.value).w(it, "Unable to register with current data")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Result.failure(IllegalStateException("Unable to register again"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.onFailure {
|
||||||
|
// Let the user know
|
||||||
|
pushService.onServiceUnregistered(userId)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.onFailure {
|
.onFailure {
|
||||||
Timber.tag(loggerTag.value).w(it, "Unable to unregister pusher")
|
Timber.tag(loggerTag.value).w(it, "Issue during pusher unregistration / re registration")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -106,12 +106,14 @@ class VectorUnifiedPushMessagingReceiver : MessagingReceiver() {
|
|||||||
*/
|
*/
|
||||||
override fun onRegistrationFailed(context: Context, reason: FailedReason, instance: String) {
|
override fun onRegistrationFailed(context: Context, reason: FailedReason, instance: String) {
|
||||||
Timber.tag(loggerTag.value).e("onRegistrationFailed for $instance, reason: $reason")
|
Timber.tag(loggerTag.value).e("onRegistrationFailed for $instance, reason: $reason")
|
||||||
/*
|
coroutineScope.launch {
|
||||||
Toast.makeText(context, "Push service registration failed", Toast.LENGTH_SHORT).show()
|
endpointRegistrationHandler.registrationDone(
|
||||||
val mode = BackgroundSyncMode.FDROID_BACKGROUND_SYNC_MODE_FOR_REALTIME
|
RegistrationResult(
|
||||||
pushDataStore.setFdroidSyncBackgroundMode(mode)
|
clientSecret = instance,
|
||||||
guardServiceStarter.start()
|
result = Result.failure(Exception("Registration failed. Reason: $reason")),
|
||||||
*/
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -12,14 +12,21 @@ import io.element.android.libraries.matrix.api.MatrixClient
|
|||||||
import io.element.android.tests.testutils.lambda.lambdaError
|
import io.element.android.tests.testutils.lambda.lambdaError
|
||||||
|
|
||||||
class FakeUnregisterUnifiedPushUseCase(
|
class FakeUnregisterUnifiedPushUseCase(
|
||||||
private val unregisterLambda: (MatrixClient, String) -> Result<Unit> = { _, _ -> lambdaError() },
|
private val unregisterLambda: (MatrixClient, String, Boolean) -> Result<Unit> = { _, _, _ -> lambdaError() },
|
||||||
private val cleanupLambda: (String) -> Unit = { lambdaError() },
|
private val cleanupLambda: (String, Boolean) -> Unit = { _, _ -> lambdaError() },
|
||||||
) : UnregisterUnifiedPushUseCase {
|
) : UnregisterUnifiedPushUseCase {
|
||||||
override suspend fun unregister(matrixClient: MatrixClient, clientSecret: String): Result<Unit> {
|
override suspend fun unregister(
|
||||||
return unregisterLambda(matrixClient, clientSecret)
|
matrixClient: MatrixClient,
|
||||||
|
clientSecret: String,
|
||||||
|
unregisterUnifiedPush: Boolean,
|
||||||
|
): Result<Unit> {
|
||||||
|
return unregisterLambda(matrixClient, clientSecret, unregisterUnifiedPush)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun cleanup(clientSecret: String) {
|
override fun cleanup(
|
||||||
cleanupLambda(clientSecret)
|
clientSecret: String,
|
||||||
|
unregisterUnifiedPush: Boolean,
|
||||||
|
) {
|
||||||
|
cleanupLambda(clientSecret, unregisterUnifiedPush)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -118,7 +118,7 @@ class UnifiedPushProviderTest {
|
|||||||
fun `unregister ok`() = runTest {
|
fun `unregister ok`() = runTest {
|
||||||
val matrixClient = FakeMatrixClient()
|
val matrixClient = FakeMatrixClient()
|
||||||
val getSecretForUserResultLambda = lambdaRecorder<SessionId, String> { A_SECRET }
|
val getSecretForUserResultLambda = lambdaRecorder<SessionId, String> { A_SECRET }
|
||||||
val unregisterLambda = lambdaRecorder<MatrixClient, String, Result<Unit>> { _, _ -> Result.success(Unit) }
|
val unregisterLambda = lambdaRecorder<MatrixClient, String, Boolean, Result<Unit>> { _, _, _ -> Result.success(Unit) }
|
||||||
val unifiedPushProvider = createUnifiedPushProvider(
|
val unifiedPushProvider = createUnifiedPushProvider(
|
||||||
pushClientSecret = FakePushClientSecret(
|
pushClientSecret = FakePushClientSecret(
|
||||||
getSecretForUserResult = getSecretForUserResultLambda,
|
getSecretForUserResult = getSecretForUserResultLambda,
|
||||||
@@ -134,14 +134,14 @@ class UnifiedPushProviderTest {
|
|||||||
.with(value(A_SESSION_ID))
|
.with(value(A_SESSION_ID))
|
||||||
unregisterLambda.assertions()
|
unregisterLambda.assertions()
|
||||||
.isCalledOnce()
|
.isCalledOnce()
|
||||||
.with(value(matrixClient), value(A_SECRET))
|
.with(value(matrixClient), value(A_SECRET), value(true))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `unregister ko`() = runTest {
|
fun `unregister ko`() = runTest {
|
||||||
val matrixClient = FakeMatrixClient()
|
val matrixClient = FakeMatrixClient()
|
||||||
val getSecretForUserResultLambda = lambdaRecorder<SessionId, String> { A_SECRET }
|
val getSecretForUserResultLambda = lambdaRecorder<SessionId, String> { A_SECRET }
|
||||||
val unregisterLambda = lambdaRecorder<MatrixClient, String, Result<Unit>> { _, _ -> Result.failure(AN_EXCEPTION) }
|
val unregisterLambda = lambdaRecorder<MatrixClient, String, Boolean, Result<Unit>> { _, _, _ -> Result.failure(AN_EXCEPTION) }
|
||||||
val unifiedPushProvider = createUnifiedPushProvider(
|
val unifiedPushProvider = createUnifiedPushProvider(
|
||||||
pushClientSecret = FakePushClientSecret(
|
pushClientSecret = FakePushClientSecret(
|
||||||
getSecretForUserResult = getSecretForUserResultLambda,
|
getSecretForUserResult = getSecretForUserResultLambda,
|
||||||
@@ -157,7 +157,7 @@ class UnifiedPushProviderTest {
|
|||||||
.with(value(A_SESSION_ID))
|
.with(value(A_SESSION_ID))
|
||||||
unregisterLambda.assertions()
|
unregisterLambda.assertions()
|
||||||
.isCalledOnce()
|
.isCalledOnce()
|
||||||
.with(value(matrixClient), value(A_SECRET))
|
.with(value(matrixClient), value(A_SECRET), value(true))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -230,7 +230,7 @@ class UnifiedPushProviderTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `onSessionDeleted should do the cleanup`() = runTest {
|
fun `onSessionDeleted should do the cleanup`() = runTest {
|
||||||
val cleanupLambda = lambdaRecorder<String, Unit> { }
|
val cleanupLambda = lambdaRecorder<String, Boolean, Unit> { _, _ -> }
|
||||||
val unifiedPushProvider = createUnifiedPushProvider(
|
val unifiedPushProvider = createUnifiedPushProvider(
|
||||||
pushClientSecret = FakePushClientSecret(
|
pushClientSecret = FakePushClientSecret(
|
||||||
getSecretForUserResult = { A_SECRET }
|
getSecretForUserResult = { A_SECRET }
|
||||||
@@ -240,7 +240,7 @@ class UnifiedPushProviderTest {
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
unifiedPushProvider.onSessionDeleted(A_SESSION_ID)
|
unifiedPushProvider.onSessionDeleted(A_SESSION_ID)
|
||||||
cleanupLambda.assertions().isCalledOnce().with(value(A_SECRET))
|
cleanupLambda.assertions().isCalledOnce().with(value(A_SECRET), value(true))
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createUnifiedPushProvider(
|
private fun createUnifiedPushProvider(
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import io.element.android.libraries.pushproviders.api.PushData
|
|||||||
import io.element.android.libraries.pushproviders.api.PushHandler
|
import io.element.android.libraries.pushproviders.api.PushHandler
|
||||||
import io.element.android.libraries.pushproviders.unifiedpush.registration.EndpointRegistrationHandler
|
import io.element.android.libraries.pushproviders.unifiedpush.registration.EndpointRegistrationHandler
|
||||||
import io.element.android.libraries.pushproviders.unifiedpush.registration.RegistrationResult
|
import io.element.android.libraries.pushproviders.unifiedpush.registration.RegistrationResult
|
||||||
|
import io.element.android.tests.testutils.lambda.lambdaError
|
||||||
import io.element.android.tests.testutils.lambda.lambdaRecorder
|
import io.element.android.tests.testutils.lambda.lambdaRecorder
|
||||||
import io.element.android.tests.testutils.lambda.value
|
import io.element.android.tests.testutils.lambda.value
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
@@ -51,10 +52,17 @@ class VectorUnifiedPushMessagingReceiverTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `onUnregistered does nothing`() = runTest {
|
fun `onUnregistered invokes the removedGatewayHandler`() = runTest {
|
||||||
val context = InstrumentationRegistry.getInstrumentation().context
|
val context = InstrumentationRegistry.getInstrumentation().context
|
||||||
val vectorUnifiedPushMessagingReceiver = createVectorUnifiedPushMessagingReceiver()
|
val handleResult = lambdaRecorder<String, Result<Unit>> {
|
||||||
|
Result.success(Unit)
|
||||||
|
}
|
||||||
|
val vectorUnifiedPushMessagingReceiver = createVectorUnifiedPushMessagingReceiver(
|
||||||
|
removedGatewayHandler = UnifiedPushRemovedGatewayHandler { handleResult(it) },
|
||||||
|
)
|
||||||
vectorUnifiedPushMessagingReceiver.onUnregistered(context, A_SECRET)
|
vectorUnifiedPushMessagingReceiver.onUnregistered(context, A_SECRET)
|
||||||
|
advanceUntilIdle()
|
||||||
|
handleResult.assertions().isCalledOnce().with(value(A_SECRET))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -199,6 +207,7 @@ class VectorUnifiedPushMessagingReceiverTest {
|
|||||||
unifiedPushGatewayUrlResolver: UnifiedPushGatewayUrlResolver = FakeUnifiedPushGatewayUrlResolver(),
|
unifiedPushGatewayUrlResolver: UnifiedPushGatewayUrlResolver = FakeUnifiedPushGatewayUrlResolver(),
|
||||||
unifiedPushNewGatewayHandler: UnifiedPushNewGatewayHandler = FakeUnifiedPushNewGatewayHandler(),
|
unifiedPushNewGatewayHandler: UnifiedPushNewGatewayHandler = FakeUnifiedPushNewGatewayHandler(),
|
||||||
endpointRegistrationHandler: EndpointRegistrationHandler = EndpointRegistrationHandler(),
|
endpointRegistrationHandler: EndpointRegistrationHandler = EndpointRegistrationHandler(),
|
||||||
|
removedGatewayHandler: UnifiedPushRemovedGatewayHandler = UnifiedPushRemovedGatewayHandler { lambdaError() },
|
||||||
): VectorUnifiedPushMessagingReceiver {
|
): VectorUnifiedPushMessagingReceiver {
|
||||||
return VectorUnifiedPushMessagingReceiver().apply {
|
return VectorUnifiedPushMessagingReceiver().apply {
|
||||||
this.pushParser = unifiedPushParser
|
this.pushParser = unifiedPushParser
|
||||||
@@ -208,6 +217,7 @@ class VectorUnifiedPushMessagingReceiverTest {
|
|||||||
this.unifiedPushGatewayResolver = unifiedPushGatewayResolver
|
this.unifiedPushGatewayResolver = unifiedPushGatewayResolver
|
||||||
this.unifiedPushGatewayUrlResolver = unifiedPushGatewayUrlResolver
|
this.unifiedPushGatewayUrlResolver = unifiedPushGatewayUrlResolver
|
||||||
this.newGatewayHandler = unifiedPushNewGatewayHandler
|
this.newGatewayHandler = unifiedPushNewGatewayHandler
|
||||||
|
this.removedGatewayHandler = removedGatewayHandler
|
||||||
this.endpointRegistrationHandler = endpointRegistrationHandler
|
this.endpointRegistrationHandler = endpointRegistrationHandler
|
||||||
this.coroutineScope = this@createVectorUnifiedPushMessagingReceiver
|
this.coroutineScope = this@createVectorUnifiedPushMessagingReceiver
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user