forked from joreilly/Confetti
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathApolloClientCache.kt
More file actions
159 lines (140 loc) · 5.58 KB
/
ApolloClientCache.kt
File metadata and controls
159 lines (140 loc) · 5.58 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
package dev.johnoreilly.confetti
import com.apollographql.apollo.ApolloClient
import com.apollographql.apollo.annotations.ApolloExperimental
import com.apollographql.apollo.api.ApolloRequest
import com.apollographql.apollo.api.ApolloResponse
import com.apollographql.apollo.api.ExecutionContext
import com.apollographql.apollo.api.Operation
import com.apollographql.apollo.exception.ApolloException
import com.apollographql.apollo.exception.ApolloHttpException
import com.apollographql.apollo.interceptor.ApolloInterceptor
import com.apollographql.apollo.interceptor.ApolloInterceptorChain
import com.apollographql.cache.normalized.api.CacheControlCacheResolver
import com.apollographql.cache.normalized.api.SchemaCoordinatesMaxAgeProvider
import com.apollographql.cache.normalized.apolloStore
import com.apollographql.cache.normalized.maxStale
import com.apollographql.cache.normalized.normalizedCache
import com.apollographql.cache.normalized.storeReceivedDate
import dev.johnoreilly.confetti.cache.Cache
import dev.johnoreilly.confetti.di.getNormalizedCacheFactory
import dev.johnoreilly.confetti.utils.registerApolloDebugServer
import dev.johnoreilly.confetti.utils.unregisterApolloDebugServer
import kotlinx.atomicfu.locks.reentrantLock
import kotlinx.atomicfu.locks.withLock
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.emitAll
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.onEach
import org.koin.core.component.KoinComponent
import org.koin.core.component.get
import kotlin.time.Duration
interface TokenProvider {
suspend fun token(forceRefresh: Boolean): String?
}
class TokenProviderContext(val tokenProvider: TokenProvider) : ExecutionContext.Element {
override val key: ExecutionContext.Key<*>
get() = Key
companion object Key : ExecutionContext.Key<TokenProviderContext>
}
class ApolloClientCache : KoinComponent {
private val _clients = mutableMapOf<String, ApolloClient>()
private val mutex = reentrantLock()
private val tokenProviderInterceptor = object : ApolloInterceptor {
override fun <D : Operation.Data> intercept(
request: ApolloRequest<D>,
chain: ApolloInterceptorChain
): Flow<ApolloResponse<D>> = flow {
val tokenProvider = request.executionContext[TokenProviderContext]?.tokenProvider
if (tokenProvider == null) {
emitAll(chain.proceed(request))
return@flow
}
val token = tokenProvider.token(false)
if (token == null) {
emitAll(chain.proceed(request))
return@flow
}
val newRequest = request.newBuilder().addHttpHeader("Authorization", "Bearer $token").build()
val flow = chain.proceed(newRequest).onEach {
val exception = it.exception
if (exception is ApolloHttpException && exception.statusCode == 401) {
throw exception
}
}.catch {
val token2 = tokenProvider.token(true)
if (token2 != null) {
val newRequest2 = request.newBuilder().addHttpHeader("Authorization", "Bearer $token2").build()
emitAll(chain.proceed(newRequest2))
} else {
emit(ApolloResponse.Builder(request.operation, request.requestUuid).exception(it as ApolloException).build())
}
}
emitAll(flow)
}
}
fun getClient(conference: String, uid: String?): ApolloClient {
return mutex.withLock {
_clients.getOrPut("$conference-$uid") {
clientFor(conference, uid, true)
}
}
}
fun getClient(conference: String): ApolloClient {
return mutex.withLock {
_clients.getOrPut(conference) {
clientFor(conference, "none", conference != "all")
}
}
}
@OptIn(ApolloExperimental::class)
private fun clientFor(
conference: String,
uid: String?,
writeToCacheAsynchronously: Boolean
): ApolloClient {
val normalizedCacheFactory = getNormalizedCacheFactory(conference, uid)
return get<ApolloClient.Builder>()
// .networkMonitor(
// object : NetworkMonitor {
// override val isOnline: Boolean
// get() = true
// override suspend fun waitForNetwork() {}
// override fun close() {}
// }
// )
.addHttpHeader("conference", conference)
.normalizedCache(
normalizedCacheFactory,
writeToCacheAsynchronously = writeToCacheAsynchronously,
cacheResolver = CacheControlCacheResolver(
SchemaCoordinatesMaxAgeProvider(
maxAges = Cache.maxAges,
defaultMaxAge = Duration.INFINITE,
)
),
)
.storeReceivedDate(true)
.maxStale(Duration.INFINITE)
.autoPersistedQueries()
.addInterceptor(tokenProviderInterceptor)
.build()
.also {
it.registerApolloDebugServer(conference + uid)
}
}
fun close() {
_clients.values.forEach {
it.close()
it.unregisterApolloDebugServer()
}
_clients.clear()
}
fun clear() {
_clients.values.forEach {
it.apolloStore.clearAll()
it.close()
}
_clients.clear()
}
}