Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ internal class PassiveChallengeViewModel @Inject constructor(
val result = hCaptchaService.performPassiveHCaptcha(
activity = activity,
siteKey = passiveCaptchaParams.siteKey,
rqData = passiveCaptchaParams.rqData
rqData = passiveCaptchaParams.rqData,
tokenTimeoutSeconds = passiveCaptchaParams.tokenTimeoutSeconds
)
when (result) {
is HCaptchaService.Result.Failure -> {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.stripe.android.hcaptcha

import android.os.SystemClock
import androidx.fragment.app.FragmentActivity
import com.stripe.android.hcaptcha.analytics.CaptchaEventsReporter
import com.stripe.hcaptcha.HCaptcha
Expand All @@ -24,15 +25,19 @@ internal class DefaultHCaptchaService(
) : HCaptchaService {
private val cachedResult = MutableStateFlow<CachedResult>(CachedResult.Idle)

override suspend fun warmUp(activity: FragmentActivity, siteKey: String, rqData: String?) {
override suspend fun warmUp(
activity: FragmentActivity,
siteKey: String,
rqData: String?
) {
if (cachedResult.value.canWarmUp.not()) return
cachedResult.emit(CachedResult.Loading)
val update = when (val result = performPassiveHCaptchaHelper(activity, siteKey, rqData)) {
is HCaptchaService.Result.Failure -> {
CachedResult.Failure(result.error)
}
is HCaptchaService.Result.Success -> {
CachedResult.Success(result.token)
CachedResult.Success(result.token, createdAt = SystemClock.elapsedRealtime())
}
}
cachedResult.emit(update)
Expand All @@ -41,13 +46,19 @@ internal class DefaultHCaptchaService(
override suspend fun performPassiveHCaptcha(
activity: FragmentActivity,
siteKey: String,
rqData: String?
rqData: String?,
tokenTimeoutSeconds: Int?
): HCaptchaService.Result {
captchaEventsReporter.attachStart()
val isReady = cachedResult.value.isReady
val result = runCatching {
withTimeout(TIMEOUT) {
transformCachedResult(activity, siteKey, rqData)
transformCachedResult(
activity,
siteKey,
rqData,
tokenTimeoutSeconds = tokenTimeoutSeconds ?: Int.MAX_VALUE
)
}
}.getOrElse { e ->
HCaptchaService.Result.Failure(e)
Expand Down Expand Up @@ -97,7 +108,7 @@ internal class DefaultHCaptchaService(
private suspend fun performPassiveHCaptchaHelper(
activity: FragmentActivity,
siteKey: String,
rqData: String?
rqData: String?,
): HCaptchaService.Result {
val hCaptcha = hCaptchaProvider.get()
captchaEventsReporter.init(siteKey)
Expand All @@ -106,7 +117,7 @@ internal class DefaultHCaptchaService(
activity = activity,
siteKey = siteKey,
rqData = rqData,
hCaptcha = hCaptcha
hCaptcha = hCaptcha,
)
}.getOrElse { e ->
HCaptchaService.Result.Failure(e)
Expand All @@ -123,10 +134,12 @@ internal class DefaultHCaptchaService(
return result
}

@Suppress("MagicNumber")
private suspend fun transformCachedResult(
activity: FragmentActivity,
siteKey: String,
rqData: String?
rqData: String?,
tokenTimeoutSeconds: Int
): HCaptchaService.Result {
return cachedResult.mapNotNull { cachedResult ->
when (cachedResult) {
Expand All @@ -136,7 +149,15 @@ internal class DefaultHCaptchaService(
CachedResult.Loading -> {
null
}
is CachedResult.Success -> HCaptchaService.Result.Success(cachedResult.token)
is CachedResult.Success -> {
val elapsedSeconds = (SystemClock.elapsedRealtime() - cachedResult.createdAt) / 1000
val isExpired = elapsedSeconds >= tokenTimeoutSeconds
if (isExpired) {
performPassiveHCaptchaHelper(activity, siteKey, rqData)
} else {
HCaptchaService.Result.Success(cachedResult.token)
}
}
is CachedResult.Failure -> HCaptchaService.Result.Failure(cachedResult.error)
}
}.first()
Expand All @@ -145,7 +166,10 @@ internal class DefaultHCaptchaService(
sealed interface CachedResult {
data object Idle : CachedResult
data object Loading : CachedResult
data class Success(val token: String) : CachedResult
data class Success(
val token: String,
val createdAt: Long
) : CachedResult
data class Failure(val error: Throwable) : CachedResult

val canWarmUp: Boolean
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,14 @@ interface HCaptchaService {
suspend fun warmUp(
activity: FragmentActivity,
siteKey: String,
rqData: String?
rqData: String?,
)

suspend fun performPassiveHCaptcha(
activity: FragmentActivity,
siteKey: String,
rqData: String?
rqData: String?,
tokenTimeoutSeconds: Int?
): Result

@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,6 @@ import kotlinx.parcelize.Parcelize
@Parcelize
data class PassiveCaptchaParams(
val siteKey: String,
val rqData: String?
val rqData: String?,
val tokenTimeoutSeconds: Int?
) : StripeModel
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,14 @@ internal class PassiveCaptchaJsonParser : ModelJsonParser<PassiveCaptchaParams>
}
return PassiveCaptchaParams(
siteKey = json.getString(FIELD_SITE_KEY),
rqData = StripeJsonUtils.optString(json, FIELD_RQ_DATA)
rqData = StripeJsonUtils.optString(json, FIELD_RQ_DATA),
tokenTimeoutSeconds = StripeJsonUtils.optInteger(json, FIELD_TOKEN_TIMEOUT_SECONDS)
)
}

internal companion object {
private const val FIELD_SITE_KEY = "site_key"
private const val FIELD_RQ_DATA = "rqdata"
private const val FIELD_TOKEN_TIMEOUT_SECONDS = "token_timeout_seconds"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ internal class PassiveChallengeActivityContractTest {
fun `createIntent creates intent correctly with PassiveChallengeArgs`() {
val passiveCaptchaParams = PassiveCaptchaParams(
siteKey = "test_site_key",
rqData = "test_rq_data"
rqData = "test_rq_data",
tokenTimeoutSeconds = null
)
val args = PassiveChallengeActivityContract.Args(
passiveCaptchaParams,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,8 @@ internal class PassiveChallengeActivityTest {
companion object {
private val passiveCaptchaParams = PassiveCaptchaParams(
siteKey = "test_site_key",
rqData = "test_rq_data"
rqData = "test_rq_data",
tokenTimeoutSeconds = null
)

private val args = PassiveChallengeArgs(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ internal class PassiveChallengeViewModelTest {

private val testPassiveCaptchaParams = PassiveCaptchaParams(
siteKey = "test_site_key",
rqData = "test_rq_data"
rqData = "test_rq_data",
tokenTimeoutSeconds = null
)

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,8 @@ internal class PassiveChallengeWarmerActivityTest {
companion object {
private val passiveCaptchaParams = PassiveCaptchaParams(
siteKey = "test_site_key",
rqData = "test_rq_data"
rqData = "test_rq_data",
tokenTimeoutSeconds = null
)

private val args = PassiveChallengeWarmerArgs(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ internal class PassiveChallengeWarmerViewModelTest {

private val testPassiveCaptchaParams = PassiveCaptchaParams(
siteKey = "test_site_key",
rqData = "test_rq_data"
rqData = "test_rq_data",
tokenTimeoutSeconds = null
)

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ internal class DefaultPassiveChallengeWarmerTest {

private val testPassiveCaptchaParams = PassiveCaptchaParams(
siteKey = "test_site_key",
rqData = "test_rq_data"
rqData = "test_rq_data",
tokenTimeoutSeconds = null
)
private val testPublishableKey = "pk_test_123"
private val testProductUsage = setOf("PaymentSheet")
Expand Down
Loading
Loading