Add quick fix in notification troubleshot test to perform a Firebase token rotation
This commit is contained in:
parent
1f60509d92
commit
a6fdb90838
12 changed files with 243 additions and 33 deletions
|
|
@ -25,6 +25,7 @@ class FirebasePushProvider @Inject constructor(
|
|||
private val firebaseStore: FirebaseStore,
|
||||
private val pusherSubscriber: PusherSubscriber,
|
||||
private val isPlayServiceAvailable: IsPlayServiceAvailable,
|
||||
private val firebaseTokenRotator: FirebaseTokenRotator,
|
||||
) : PushProvider {
|
||||
override val index = FirebaseConfig.INDEX
|
||||
override val name = FirebaseConfig.NAME
|
||||
|
|
@ -71,6 +72,12 @@ class FirebasePushProvider @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
override fun canRotateToken(): Boolean = true
|
||||
|
||||
override suspend fun rotateToken(): Result<Unit> {
|
||||
return firebaseTokenRotator.rotate()
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val firebaseDistributor = Distributor("Firebase", "Firebase")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* Copyright 2023, 2024 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* Please see LICENSE in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.pushproviders.firebase
|
||||
|
||||
import com.google.firebase.messaging.FirebaseMessaging
|
||||
import com.squareup.anvil.annotations.ContributesBinding
|
||||
import io.element.android.libraries.di.AppScope
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
import kotlin.coroutines.resume
|
||||
import kotlin.coroutines.resumeWithException
|
||||
import kotlin.coroutines.suspendCoroutine
|
||||
|
||||
interface FirebaseTokenDeleter {
|
||||
suspend fun delete()
|
||||
}
|
||||
|
||||
/**
|
||||
* This class deletes the current Firebase token.
|
||||
*/
|
||||
@ContributesBinding(AppScope::class)
|
||||
class DefaultFirebaseTokenDeleter @Inject constructor(
|
||||
private val isPlayServiceAvailable: IsPlayServiceAvailable,
|
||||
) : FirebaseTokenDeleter {
|
||||
override suspend fun delete() {
|
||||
suspendCoroutine { continuation ->
|
||||
// 'app should always check the device for a compatible Google Play services APK before accessing Google Play services features'
|
||||
if (isPlayServiceAvailable.isAvailable()) {
|
||||
try {
|
||||
FirebaseMessaging.getInstance().deleteToken()
|
||||
.addOnSuccessListener {
|
||||
continuation.resume(Unit)
|
||||
}
|
||||
.addOnFailureListener { e ->
|
||||
Timber.e(e, "## deleteFirebaseToken() : failed")
|
||||
continuation.resumeWithException(e)
|
||||
}
|
||||
} catch (e: Throwable) {
|
||||
Timber.e(e, "## deleteFirebaseToken() : failed")
|
||||
continuation.resumeWithException(e)
|
||||
}
|
||||
} else {
|
||||
val e = Exception("No valid Google Play Services found. Cannot use FCM.")
|
||||
Timber.e(e)
|
||||
throw e
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
* Copyright 2023, 2024 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* Please see LICENSE in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.pushproviders.firebase
|
||||
|
||||
import com.google.firebase.messaging.FirebaseMessaging
|
||||
import com.squareup.anvil.annotations.ContributesBinding
|
||||
import io.element.android.libraries.di.AppScope
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
import kotlin.coroutines.resume
|
||||
import kotlin.coroutines.resumeWithException
|
||||
import kotlin.coroutines.suspendCoroutine
|
||||
|
||||
interface FirebaseTokenGetter {
|
||||
suspend fun get(): String
|
||||
}
|
||||
|
||||
/**
|
||||
* This class read the current Firebase token.
|
||||
* If the token does not exist, it will be generated.
|
||||
*/
|
||||
@ContributesBinding(AppScope::class)
|
||||
class DefaultFirebaseTokenGetter @Inject constructor(
|
||||
private val isPlayServiceAvailable: IsPlayServiceAvailable,
|
||||
) : FirebaseTokenGetter {
|
||||
override suspend fun get(): String {
|
||||
return suspendCoroutine { continuation ->
|
||||
// 'app should always check the device for a compatible Google Play services APK before accessing Google Play services features'
|
||||
if (isPlayServiceAvailable.isAvailable()) {
|
||||
try {
|
||||
FirebaseMessaging.getInstance().token
|
||||
.addOnSuccessListener { token ->
|
||||
continuation.resume(token)
|
||||
}
|
||||
.addOnFailureListener { e ->
|
||||
Timber.e(e, "## retrievedFirebaseToken() : failed")
|
||||
continuation.resumeWithException(e)
|
||||
}
|
||||
} catch (e: Throwable) {
|
||||
Timber.e(e, "## retrievedFirebaseToken() : failed")
|
||||
continuation.resumeWithException(e)
|
||||
}
|
||||
} else {
|
||||
val e = Exception("No valid Google Play Services found. Cannot use FCM.")
|
||||
Timber.e(e)
|
||||
continuation.resumeWithException(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* Copyright 2024 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* Please see LICENSE in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.pushproviders.firebase
|
||||
|
||||
import com.squareup.anvil.annotations.ContributesBinding
|
||||
import io.element.android.libraries.di.AppScope
|
||||
import javax.inject.Inject
|
||||
|
||||
interface FirebaseTokenRotator {
|
||||
suspend fun rotate(): Result<Unit>
|
||||
}
|
||||
|
||||
/**
|
||||
* This class delete the Firebase token and generate a new one.
|
||||
*/
|
||||
@ContributesBinding(AppScope::class)
|
||||
class DefaultFirebaseTokenRotator @Inject constructor(
|
||||
private val firebaseTokenDeleter: FirebaseTokenDeleter,
|
||||
private val firebaseTokenGetter: FirebaseTokenGetter,
|
||||
) : FirebaseTokenRotator {
|
||||
override suspend fun rotate(): Result<Unit> {
|
||||
return runCatching {
|
||||
firebaseTokenDeleter.delete()
|
||||
firebaseTokenGetter.get()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -7,14 +7,9 @@
|
|||
|
||||
package io.element.android.libraries.pushproviders.firebase
|
||||
|
||||
import com.google.firebase.messaging.FirebaseMessaging
|
||||
import com.squareup.anvil.annotations.ContributesBinding
|
||||
import io.element.android.libraries.di.AppScope
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
import kotlin.coroutines.resume
|
||||
import kotlin.coroutines.resumeWithException
|
||||
import kotlin.coroutines.suspendCoroutine
|
||||
|
||||
interface FirebaseTroubleshooter {
|
||||
suspend fun troubleshoot(): Result<Unit>
|
||||
|
|
@ -26,37 +21,12 @@ interface FirebaseTroubleshooter {
|
|||
@ContributesBinding(AppScope::class)
|
||||
class DefaultFirebaseTroubleshooter @Inject constructor(
|
||||
private val newTokenHandler: FirebaseNewTokenHandler,
|
||||
private val isPlayServiceAvailable: IsPlayServiceAvailable,
|
||||
private val firebaseTokenGetter: FirebaseTokenGetter,
|
||||
) : FirebaseTroubleshooter {
|
||||
override suspend fun troubleshoot(): Result<Unit> {
|
||||
return runCatching {
|
||||
val token = retrievedFirebaseToken()
|
||||
val token = firebaseTokenGetter.get()
|
||||
newTokenHandler.handle(token)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun retrievedFirebaseToken(): String {
|
||||
return suspendCoroutine { continuation ->
|
||||
// 'app should always check the device for a compatible Google Play services APK before accessing Google Play services features'
|
||||
if (isPlayServiceAvailable.isAvailable()) {
|
||||
try {
|
||||
FirebaseMessaging.getInstance().token
|
||||
.addOnSuccessListener { token ->
|
||||
continuation.resume(token)
|
||||
}
|
||||
.addOnFailureListener { e ->
|
||||
Timber.e(e, "## retrievedFirebaseToken() : failed")
|
||||
continuation.resumeWithException(e)
|
||||
}
|
||||
} catch (e: Throwable) {
|
||||
Timber.e(e, "## retrievedFirebaseToken() : failed")
|
||||
continuation.resumeWithException(e)
|
||||
}
|
||||
} else {
|
||||
val e = Exception("No valid Google Play Services found. Cannot use FCM.")
|
||||
Timber.e(e)
|
||||
continuation.resumeWithException(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,18 @@
|
|||
/*
|
||||
* Copyright 2024 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* Please see LICENSE in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.pushproviders.firebase
|
||||
|
||||
import io.element.android.tests.testutils.lambda.lambdaError
|
||||
|
||||
class FakeFirebaseTokenRotator(
|
||||
private val rotateWithResult: () -> Result<Unit> = { lambdaError() }
|
||||
) : FirebaseTokenRotator {
|
||||
override suspend fun rotate(): Result<Unit> {
|
||||
return rotateWithResult()
|
||||
}
|
||||
}
|
||||
|
|
@ -166,15 +166,27 @@ class FirebasePushProviderTest {
|
|||
assertThat(result).isEqualTo(CurrentUserPushConfig(FirebaseConfig.PUSHER_HTTP_URL, "aToken"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `rotateToken invokes the FirebaseTokenRotator`() = runTest {
|
||||
val lambda = lambdaRecorder<Result<Unit>> { Result.success(Unit) }
|
||||
val firebasePushProvider = createFirebasePushProvider(
|
||||
firebaseTokenRotator = FakeFirebaseTokenRotator(lambda),
|
||||
)
|
||||
firebasePushProvider.rotateToken()
|
||||
lambda.assertions().isCalledOnce()
|
||||
}
|
||||
|
||||
private fun createFirebasePushProvider(
|
||||
firebaseStore: FirebaseStore = InMemoryFirebaseStore(),
|
||||
pusherSubscriber: PusherSubscriber = FakePusherSubscriber(),
|
||||
isPlayServiceAvailable: IsPlayServiceAvailable = FakeIsPlayServiceAvailable(false),
|
||||
firebaseTokenRotator: FirebaseTokenRotator = FakeFirebaseTokenRotator(),
|
||||
): FirebasePushProvider {
|
||||
return FirebasePushProvider(
|
||||
firebaseStore = firebaseStore,
|
||||
pusherSubscriber = pusherSubscriber,
|
||||
isPlayServiceAvailable = isPlayServiceAvailable,
|
||||
firebaseTokenRotator = firebaseTokenRotator,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue