1+ package com.getcode.network.appcheck
2+
3+ import com.codeinc.gen.common.v1.Model
4+ import com.getcode.api.BuildConfig
5+ import com.google.firebase.Firebase
6+ import com.google.firebase.appcheck.AppCheckToken
7+ import com.google.firebase.appcheck.AppCheckTokenResult
8+ import com.google.firebase.appcheck.appCheck
9+ import com.google.firebase.appcheck.debug.DebugAppCheckProviderFactory
10+ import com.google.firebase.appcheck.debug.internal.DebugAppCheckProvider
11+ import com.google.firebase.appcheck.playintegrity.PlayIntegrityAppCheckProviderFactory
12+ import io.reactivex.rxjava3.core.BackpressureStrategy
13+ import io.reactivex.rxjava3.core.Flowable
14+ import io.reactivex.rxjava3.core.Single
15+ import kotlinx.coroutines.flow.Flow
16+ import kotlinx.coroutines.reactive.asFlow
17+ import timber.log.Timber
18+ import java.lang.Exception
19+
20+ data class DeviceTokenResult (
21+ val token : AppCheckToken ?
22+ )
23+
24+ object AppCheck {
25+
26+ fun register () {
27+ if (BuildConfig .DEBUG ) {
28+ Firebase .appCheck.installAppCheckProviderFactory(
29+ DebugAppCheckProviderFactory .getInstance()
30+ )
31+ } else {
32+ Firebase .appCheck.installAppCheckProviderFactory(
33+ PlayIntegrityAppCheckProviderFactory .getInstance()
34+ )
35+ }
36+ }
37+
38+ private fun handleAppCheckError (error : Exception ): Boolean {
39+ val match = " code: \\ d+" .toRegex().find(error.message.orEmpty())
40+ val errorCode = match?.value?.removePrefix(" code: " )?.toInt() ? : - 1
41+ Timber .e(" Failed to get appcheck token: errorCode=$errorCode ${error.message} " )
42+
43+ return errorCode == 403 // bad attestation
44+ || errorCode >= 500
45+ }
46+
47+ @Deprecated(" Replace with Flow variant" )
48+ fun limitedUseTokenSingle (): Single <DeviceTokenResult > {
49+ return Single .create { emitter ->
50+ Firebase .appCheck.limitedUseAppCheckToken
51+ .addOnSuccessListener {
52+ Timber .d(" attestation passed" )
53+ emitter.onSuccess(DeviceTokenResult (it))
54+ }
55+ .addOnFailureListener { error ->
56+ if (! handleAppCheckError(error)) {
57+ emitter.onError(error)
58+ return @addOnFailureListener
59+ }
60+
61+ emitter.onSuccess(DeviceTokenResult (null ))
62+ }
63+ }
64+ }
65+
66+ @Deprecated(" Replace with Flow variant" )
67+ fun limitedUseTokenFlowable (
68+ backpressureStrategy : BackpressureStrategy = BackpressureStrategy .BUFFER
69+ ): Flowable <DeviceTokenResult > {
70+ return Flowable .create({ emitter ->
71+ Firebase .appCheck.limitedUseAppCheckToken
72+ .addOnSuccessListener {
73+ Timber .d(" attestation passed" )
74+ emitter.onNext(DeviceTokenResult (it))
75+ }
76+ .addOnFailureListener { error ->
77+ if (! handleAppCheckError(error)) {
78+ emitter.onError(error)
79+ return @addOnFailureListener
80+ }
81+
82+ emitter.onNext(DeviceTokenResult (null ))
83+ }
84+ }, backpressureStrategy)
85+ }
86+
87+ fun limitedUseToken (
88+ backpressureStrategy : BackpressureStrategy = BackpressureStrategy .BUFFER
89+ ): Flow <DeviceTokenResult > {
90+ return limitedUseTokenFlowable(backpressureStrategy).asFlow()
91+ }
92+ }
93+
94+ fun AppCheckToken.toDeviceToken () = Model .DeviceToken .newBuilder().setValue(this .token).build()
95+ fun String.toDeviceToken () = Model .DeviceToken .newBuilder().setValue(this ).build()
0 commit comments