Skip to content

Commit b8e965f

Browse files
committed
warm up jwks cache when user enters activation flow
1 parent 562504c commit b8e965f

File tree

2 files changed

+43
-0
lines changed

2 files changed

+43
-0
lines changed

subscriptions/subscriptions-impl/src/main/java/com/duckduckgo/subscriptions/impl/ui/RestoreSubscriptionViewModel.kt

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,15 @@ package com.duckduckgo.subscriptions.impl.ui
1919
import androidx.lifecycle.ViewModel
2020
import androidx.lifecycle.viewModelScope
2121
import com.duckduckgo.anvil.annotations.ContributesViewModel
22+
import com.duckduckgo.app.di.AppCoroutineScope
2223
import com.duckduckgo.common.utils.DispatcherProvider
2324
import com.duckduckgo.di.scopes.ActivityScope
2425
import com.duckduckgo.subscriptions.api.SubscriptionStatus
2526
import com.duckduckgo.subscriptions.impl.RealSubscriptionsManager.Companion.SUBSCRIPTION_NOT_FOUND_ERROR
2627
import com.duckduckgo.subscriptions.impl.RealSubscriptionsManager.RecoverSubscriptionResult
2728
import com.duckduckgo.subscriptions.impl.SubscriptionsChecker
2829
import com.duckduckgo.subscriptions.impl.SubscriptionsManager
30+
import com.duckduckgo.subscriptions.impl.auth2.AuthClient
2931
import com.duckduckgo.subscriptions.impl.pixels.SubscriptionPixelSender
3032
import com.duckduckgo.subscriptions.impl.repository.isExpired
3133
import com.duckduckgo.subscriptions.impl.ui.RestoreSubscriptionViewModel.Command.Error
@@ -35,6 +37,7 @@ import com.duckduckgo.subscriptions.impl.ui.RestoreSubscriptionViewModel.Command
3537
import com.duckduckgo.subscriptions.impl.ui.RestoreSubscriptionViewModel.Command.SubscriptionNotFound
3638
import com.duckduckgo.subscriptions.impl.ui.RestoreSubscriptionViewModel.Command.Success
3739
import javax.inject.Inject
40+
import kotlinx.coroutines.CoroutineScope
3841
import kotlinx.coroutines.channels.BufferOverflow.DROP_OLDEST
3942
import kotlinx.coroutines.channels.Channel
4043
import kotlinx.coroutines.flow.Flow
@@ -44,13 +47,16 @@ import kotlinx.coroutines.flow.launchIn
4447
import kotlinx.coroutines.flow.onEach
4548
import kotlinx.coroutines.flow.receiveAsFlow
4649
import kotlinx.coroutines.launch
50+
import logcat.logcat
4751

4852
@ContributesViewModel(ActivityScope::class)
4953
class RestoreSubscriptionViewModel @Inject constructor(
5054
private val subscriptionsManager: SubscriptionsManager,
5155
private val subscriptionsChecker: SubscriptionsChecker,
5256
private val dispatcherProvider: DispatcherProvider,
5357
private val pixelSender: SubscriptionPixelSender,
58+
private val authClient: AuthClient,
59+
@AppCoroutineScope private val appCoroutineScope: CoroutineScope,
5460
) : ViewModel() {
5561

5662
private val command = Channel<Command>(1, DROP_OLDEST)
@@ -106,6 +112,7 @@ class RestoreSubscriptionViewModel @Inject constructor(
106112
viewModelScope.launch {
107113
command.send(RestoreFromEmail)
108114
}
115+
warmUpJwksCache()
109116
}
110117

111118
fun onSubscriptionRestoredFromEmail() = viewModelScope.launch {
@@ -116,6 +123,20 @@ class RestoreSubscriptionViewModel @Inject constructor(
116123
}
117124
}
118125

126+
/*
127+
We'll need JWKs to validate auth tokens returned by FE after the user completes activation flow using email.
128+
Prefetching them is optional, but it reduces the risk of failure when the network connection is unstable.
129+
*/
130+
private fun warmUpJwksCache() {
131+
appCoroutineScope.launch {
132+
try {
133+
authClient.getJwks()
134+
} catch (e: Exception) {
135+
logcat { "Failed to warm-up JWKs cache, e: ${e.stackTraceToString()}" }
136+
}
137+
}
138+
}
139+
119140
sealed class Command {
120141
data object RestoreFromEmail : Command()
121142
data object Success : Command()

subscriptions/subscriptions-impl/src/test/java/com/duckduckgo/subscriptions/impl/ui/RestoreSubscriptionViewModelTest.kt

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import com.duckduckgo.subscriptions.impl.RealSubscriptionsManager.Companion.SUBS
1010
import com.duckduckgo.subscriptions.impl.RealSubscriptionsManager.RecoverSubscriptionResult
1111
import com.duckduckgo.subscriptions.impl.SubscriptionsChecker
1212
import com.duckduckgo.subscriptions.impl.SubscriptionsManager
13+
import com.duckduckgo.subscriptions.impl.auth2.AuthClient
1314
import com.duckduckgo.subscriptions.impl.pixels.SubscriptionPixelSender
1415
import com.duckduckgo.subscriptions.impl.repository.Subscription
1516
import com.duckduckgo.subscriptions.impl.ui.RestoreSubscriptionViewModel.Command.Error
@@ -37,6 +38,7 @@ class RestoreSubscriptionViewModelTest {
3738
private val subscriptionsManager: SubscriptionsManager = mock()
3839
private val pixelSender: SubscriptionPixelSender = mock()
3940
private val subscriptionsChecker: SubscriptionsChecker = mock()
41+
private val authClient: AuthClient = mock()
4042
private lateinit var viewModel: RestoreSubscriptionViewModel
4143

4244
@Before
@@ -46,6 +48,8 @@ class RestoreSubscriptionViewModelTest {
4648
dispatcherProvider = coroutineTestRule.testDispatcherProvider,
4749
pixelSender = pixelSender,
4850
subscriptionsChecker = subscriptionsChecker,
51+
authClient = authClient,
52+
appCoroutineScope = coroutineTestRule.testScope,
4953
)
5054
}
5155

@@ -190,6 +194,24 @@ class RestoreSubscriptionViewModelTest {
190194
}
191195
}
192196

197+
@Test
198+
fun whenRestoreFromEmailThenJwksCacheIsWarmedUp() = runTest {
199+
viewModel.restoreFromEmail()
200+
verify(authClient).getJwks()
201+
}
202+
203+
@Test
204+
fun whenWarmUpJwksFailsThenNoCrashOccurs() = runTest {
205+
whenever(authClient.getJwks()).thenThrow(RuntimeException("Network error"))
206+
207+
viewModel.restoreFromEmail()
208+
209+
viewModel.commands().test {
210+
assertTrue(awaitItem() is RestoreFromEmail)
211+
}
212+
verify(pixelSender).reportActivateSubscriptionEnterEmailClick()
213+
}
214+
193215
private fun subscriptionActive(): Subscription {
194216
return Subscription(
195217
productId = "productId",

0 commit comments

Comments
 (0)