-
Notifications
You must be signed in to change notification settings - Fork 17
Expand file tree
/
Copy pathFlagsmithRetrofitService.kt
More file actions
167 lines (146 loc) · 6.8 KB
/
FlagsmithRetrofitService.kt
File metadata and controls
167 lines (146 loc) · 6.8 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
160
161
162
163
164
165
166
167
package com.flagsmith.internal;
import android.content.Context
import android.util.Log
import com.flagsmith.FlagsmithCacheConfig
import com.flagsmith.entities.Flag
import com.flagsmith.entities.IdentityAndTraits
import com.flagsmith.entities.IdentityAndTraitsSerializer
import com.flagsmith.entities.IdentityFlagsAndTraits
import com.flagsmith.entities.Trait
import com.flagsmith.entities.TraitSerializer
import okhttp3.Cache
import okhttp3.Interceptor
import okhttp3.OkHttpClient
import retrofit2.*
import retrofit2.converter.gson.GsonConverterFactory
import retrofit2.http.Body
import retrofit2.http.GET
import retrofit2.http.POST
import retrofit2.http.Query
import com.google.gson.GsonBuilder
interface FlagsmithRetrofitService {
@GET("identities/")
fun getIdentityFlagsAndTraits(@Query("identifier") identity: String, @Query("transient") transient: Boolean? = null) : Call<IdentityFlagsAndTraits>
@GET("flags/")
fun getFlags() : Call<List<Flag>>
// todo: rename this function
@POST("identities/")
fun postTraits(@Body identity: IdentityAndTraits) : Call<IdentityFlagsAndTraits>
@POST("analytics/flags/")
fun postAnalytics(@Body eventMap: Map<String, Int?>) : Call<Unit>
companion object {
private const val UPDATED_AT_HEADER = "x-flagsmith-document-updated-at"
private const val ACCEPT_HEADER_VALUE = "application/json"
private const val CONTENT_TYPE_HEADER_VALUE = "application/json; charset=utf-8"
private const val USER_AGENT_HEADER = "User-Agent"
// x-release-please-start-version
private const val SDK_VERSION = "1.8.0"
// x-release-please-end
fun userAgentInterceptor(): Interceptor {
return Interceptor { chain ->
val request = chain.request().newBuilder()
.header(USER_AGENT_HEADER, "flagsmith-kotlin-android-sdk/$SDK_VERSION")
.build()
chain.proceed(request)
}
}
fun <T : FlagsmithRetrofitService> create(
baseUrl: String,
environmentKey: String,
context: Context?,
cacheConfig: FlagsmithCacheConfig,
requestTimeoutSeconds: Long,
readTimeoutSeconds: Long,
writeTimeoutSeconds: Long,
timeTracker: FlagsmithEventTimeTracker,
klass: Class<T>
): Pair<FlagsmithRetrofitService, Cache?> {
fun cacheControlInterceptor(): Interceptor {
return Interceptor { chain ->
val response = chain.proceed(chain.request())
response.newBuilder()
.header("Cache-Control", "public, max-age=${cacheConfig.cacheTTLSeconds}")
.removeHeader("Pragma")
.build()
}
}
fun jsonContentTypeInterceptor(): Interceptor {
return Interceptor { chain ->
val request = chain.request()
if (chain.request().method == "POST" || chain.request().method == "PUT" || chain.request().method == "PATCH") {
val newRequest = request.newBuilder()
.header("Content-Type", CONTENT_TYPE_HEADER_VALUE)
.header("Accept", ACCEPT_HEADER_VALUE)
.build()
chain.proceed(newRequest)
} else {
chain.proceed(request)
}
}
}
fun updatedAtInterceptor(tracker: FlagsmithEventTimeTracker): Interceptor {
return Interceptor { chain ->
val response = chain.proceed(chain.request())
val updatedAtString = response.header(UPDATED_AT_HEADER)
Log.i("Flagsmith", "updatedAt: $updatedAtString")
// Update in the tracker (Flagsmith class) if we got a new value
tracker.lastFlagFetchTime = updatedAtString?.toDoubleOrNull() ?: tracker.lastFlagFetchTime
return@Interceptor response
}
}
val cache = if (context != null && cacheConfig.enableCache) Cache(context.cacheDir, cacheConfig.cacheSize) else null
val client = OkHttpClient.Builder()
.addInterceptor(envKeyInterceptor(environmentKey))
.addInterceptor(userAgentInterceptor())
.addInterceptor(updatedAtInterceptor(timeTracker))
.addInterceptor(jsonContentTypeInterceptor())
.let { if (cacheConfig.enableCache) it.addNetworkInterceptor(cacheControlInterceptor()) else it }
.callTimeout(requestTimeoutSeconds, java.util.concurrent.TimeUnit.SECONDS)
.readTimeout(readTimeoutSeconds, java.util.concurrent.TimeUnit.SECONDS)
.writeTimeout(writeTimeoutSeconds, java.util.concurrent.TimeUnit.SECONDS)
.cache(cache)
.build()
val gson = GsonBuilder()
.registerTypeAdapter(IdentityAndTraits::class.java, IdentityAndTraitsSerializer())
.registerTypeAdapter(Trait::class.java, TraitSerializer())
.create()
val retrofit = Retrofit.Builder()
.baseUrl(baseUrl)
.addConverterFactory(GsonConverterFactory.create(gson))
.client(client)
.build()
return Pair(retrofit.create(klass), cache)
}
// This is used by both the FlagsmithRetrofitService and the FlagsmithEventService
fun envKeyInterceptor(environmentKey: String): Interceptor {
return Interceptor { chain ->
val request = chain.request().newBuilder()
.addHeader("X-environment-key", environmentKey)
.build()
chain.proceed(request)
}
}
}
}
// Convert a Retrofit Call to a standard Kotlin Result by extending the Call class
// This avoids having to use the suspend keyword in the FlagsmithClient to break the API
// And also avoids a lot of code duplication
fun <T> Call<T>.enqueueWithResult(defaults: T? = null, result: (Result<T>) -> Unit) {
this.enqueue(object : Callback<T> {
override fun onResponse(call: Call<T>, response: Response<T>) {
if (response.isSuccessful && response.body() != null) {
result(Result.success(response.body()!!))
} else {
onFailure(call, HttpException(response))
}
}
override fun onFailure(call: Call<T>, t: Throwable) {
// If we've got defaults to return, return them
if (defaults != null) {
result(Result.success(defaults))
} else {
result(Result.failure(t))
}
}
})
}