Skip to content

Commit 83d7567

Browse files
committed
enable automatic refresh by default
1 parent c4321e9 commit 83d7567

File tree

6 files changed

+228
-210
lines changed

6 files changed

+228
-210
lines changed

src/main/kotlin/com/adamratzman/spotify/Builder.kt

Lines changed: 55 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,7 @@
22
package com.adamratzman.spotify
33

44
import com.adamratzman.spotify.http.HttpConnection
5-
import com.adamratzman.spotify.http.HttpHeader
65
import com.adamratzman.spotify.http.HttpRequestMethod
7-
import com.adamratzman.spotify.http.byteEncode
86
import com.adamratzman.spotify.models.Token
97
import com.adamratzman.spotify.models.serialization.toObject
108

@@ -14,10 +12,8 @@ import com.adamratzman.spotify.models.serialization.toObject
1412
*/
1513
fun spotifyApi(block: SpotifyApiBuilderDsl.() -> Unit) = SpotifyApiBuilderDsl().apply(block)
1614

17-
// Java-friendly builder
18-
1915
/**
20-
* A builder in the style of traditional Java builders
16+
* Spotify traditional Java style API builder
2117
*/
2218
class SpotifyApiBuilder(
2319
val clientId: String,
@@ -26,7 +22,8 @@ class SpotifyApiBuilder(
2622
var authorizationCode: String? = null,
2723
var tokenString: String? = null,
2824
var token: Token? = null,
29-
var useCache: Boolean = true
25+
var useCache: Boolean = true,
26+
var automaticRefresh: Boolean = true
3027
) {
3128
/**
3229
* Instantiate the builder with the application [clientId] and [clientSecret]
@@ -48,22 +45,22 @@ class SpotifyApiBuilder(
4845
* Instantiate the builder with the application [clientId], [clientSecret], application
4946
* [redirectUri], and an [authorizationCode]
5047
*/
51-
constructor(clientId: String, clientSecret: String, redirectUri: String?, authorizationCode: String, useCache: Boolean)
52-
: this(clientId, clientSecret, redirectUri, authorizationCode, null, useCache = useCache)
48+
constructor(clientId: String, clientSecret: String, redirectUri: String?, authorizationCode: String, useCache: Boolean) :
49+
this(clientId, clientSecret, redirectUri, authorizationCode, null, useCache = useCache)
5350

5451
/**
5552
* Instantiate the builder with the application [clientId], [clientSecret], application
5653
* [redirectUri], and an access token string ([tokenString])
5754
*/
58-
constructor(clientId: String, clientSecret: String, redirectUri: String?, tokenString: String)
59-
: this(clientId, clientSecret, redirectUri, null, tokenString)
55+
constructor(clientId: String, clientSecret: String, redirectUri: String?, tokenString: String) :
56+
this(clientId, clientSecret, redirectUri, null, tokenString)
6057

6158
/**
6259
* Instantiate the builder with the application [clientId], [clientSecret], application
6360
* [redirectUri], and a [token]
6461
*/
65-
constructor(clientId: String, clientSecret: String, redirectUri: String?, token: Token, useCache: Boolean)
66-
: this(clientId, clientSecret, redirectUri, null, null, token, useCache)
62+
constructor(clientId: String, clientSecret: String, redirectUri: String?, token: Token, useCache: Boolean) :
63+
this(clientId, clientSecret, redirectUri, null, null, token, useCache)
6764

6865
/**
6966
* Set whether to cache requests. Default: true
@@ -90,6 +87,11 @@ class SpotifyApiBuilder(
9087
*/
9188
fun token(token: Token?) = apply { this.token = token }
9289

90+
/**
91+
* Enable or disable automatic refresh of the Spotify access token
92+
*/
93+
fun automaticRefresh(automaticRefresh: Boolean) = apply { this.automaticRefresh = automaticRefresh }
94+
9395
/*fun build(type: AuthorizationType, automaticRefresh: Boolean = true) {
9496
9597
}*/
@@ -106,9 +108,11 @@ class SpotifyApiBuilder(
106108
token = this@SpotifyApiBuilder.token
107109
tokenString = this@SpotifyApiBuilder.tokenString
108110
}
111+
112+
automaticRefresh = this@SpotifyApiBuilder.automaticRefresh
109113
}.buildCredentialed()
110114

111-
fun buildClient(automaticRefresh: Boolean = true) = spotifyApi {
115+
fun buildClient() = spotifyApi {
112116
credentials {
113117
clientId = this@SpotifyApiBuilder.clientId
114118
clientSecret = this@SpotifyApiBuilder.clientSecret
@@ -119,7 +123,9 @@ class SpotifyApiBuilder(
119123
tokenString = this@SpotifyApiBuilder.tokenString
120124
token = this@SpotifyApiBuilder.token
121125
}
122-
}.buildClient(automaticRefresh)
126+
127+
automaticRefresh = this@SpotifyApiBuilder.automaticRefresh
128+
}.buildClient()
123129
}
124130

125131
/**
@@ -159,6 +165,7 @@ class SpotifyApiBuilderDsl {
159165
private var credentials: SpotifyCredentials = SpotifyCredentials(null, null, null)
160166
private var authentication = SpotifyUserAuthorizationBuilder()
161167
var useCache: Boolean = true
168+
var automaticRefresh: Boolean = true
162169

163170
fun credentials(block: SpotifyCredentialsBuilder.() -> Unit) {
164171
credentials = SpotifyCredentialsBuilder().apply(block).build()
@@ -181,7 +188,7 @@ class SpotifyApiBuilderDsl {
181188

182189
fun buildCredentialedAsync(consumer: (SpotifyAPI) -> Unit) = Runnable { consumer(buildCredentialed()) }.run()
183190

184-
fun buildCredentialed(automaticRefresh: Boolean = true): SpotifyAPI {
191+
fun buildCredentialed(): SpotifyAPI {
185192
val clientId = credentials.clientId
186193
val clientSecret = credentials.clientSecret
187194
if ((clientId == null || clientSecret == null) && (authentication.token == null && authentication.tokenString == null)) {
@@ -190,7 +197,7 @@ class SpotifyApiBuilderDsl {
190197
return when {
191198
authentication.token != null -> {
192199
SpotifyAppAPI(clientId ?: "not-set", clientSecret
193-
?: "not-set", authentication.token!!, useCache, automaticRefresh)
200+
?: "not-set", authentication.token!!, useCache, false)
194201
}
195202
authentication.tokenString != null -> {
196203
SpotifyAppAPI(
@@ -206,22 +213,21 @@ class SpotifyApiBuilderDsl {
206213
}
207214
else -> try {
208215
if (clientId == null || clientSecret == null) throw IllegalArgumentException("Illegal credentials provided")
209-
val token = getCredentialedToken(clientId, clientSecret)
210-
?: throw IllegalArgumentException("Invalid credentials provided")
216+
val token = getCredentialedToken(clientId, clientSecret, null)
211217
SpotifyAppAPI(clientId, clientSecret, token, useCache, automaticRefresh)
212218
} catch (e: Exception) {
213219
throw SpotifyException("Invalid credentials provided in the login process", e)
214220
}
215221
}
216222
}
217223

218-
fun buildClientAsync(consumer: (SpotifyClientAPI) -> Unit, automaticRefresh: Boolean = false) =
219-
Runnable { consumer(buildClient(automaticRefresh)) }.run()
224+
fun buildClientAsync(consumer: (SpotifyClientAPI) -> Unit) =
225+
Runnable { consumer(buildClient()) }.run()
220226

221-
fun buildClient(automaticRefresh: Boolean = false): SpotifyClientAPI =
227+
fun buildClient(): SpotifyClientAPI =
222228
buildClient(
223229
authentication.authorizationCode, authentication.tokenString,
224-
authentication.token, automaticRefresh
230+
authentication.token
225231
)
226232

227233
/**
@@ -237,8 +243,7 @@ class SpotifyApiBuilderDsl {
237243
private fun buildClient(
238244
authorizationCode: String? = null,
239245
tokenString: String? = null,
240-
token: Token? = null,
241-
automaticRefresh: Boolean = false
246+
token: Token? = null
242247
): SpotifyClientAPI {
243248
val clientId = credentials.clientId
244249
val clientSecret = credentials.clientSecret
@@ -249,21 +254,22 @@ class SpotifyApiBuilderDsl {
249254
}
250255
return when {
251256
authorizationCode != null -> try {
257+
clientId ?: throw IllegalArgumentException()
258+
clientSecret ?: throw IllegalArgumentException()
259+
redirectUri ?: IllegalArgumentException()
260+
261+
val response = executeTokenRequest(HttpConnection(
262+
url = "https://accounts.spotify.com/api/token",
263+
method = HttpRequestMethod.POST,
264+
body = "grant_type=authorization_code&code=$authorizationCode&redirect_uri=$redirectUri",
265+
contentType = "application/x-www-form-urlencoded",
266+
api = null
267+
), clientId, clientSecret)
268+
252269
SpotifyClientAPI(
253-
clientId ?: throw IllegalArgumentException(),
254-
clientSecret ?: throw IllegalArgumentException(),
255-
HttpConnection(
256-
url = "https://accounts.spotify.com/api/token",
257-
method = HttpRequestMethod.POST,
258-
body = "grant_type=authorization_code&code=$authorizationCode&redirect_uri=$redirectUri",
259-
contentType = "application/x-www-form-urlencoded",
260-
api = null
261-
).execute(
262-
HttpHeader(
263-
"Authorization",
264-
"Basic ${"$clientId:$clientSecret".byteEncode()}"
265-
)
266-
).body.toObject(null),
270+
clientId,
271+
clientSecret,
272+
response.body.toObject(null),
267273
automaticRefresh,
268274
redirectUri ?: throw IllegalArgumentException(),
269275
useCache
@@ -280,10 +286,17 @@ class SpotifyApiBuilderDsl {
280286
useCache
281287
)
282288
tokenString != null -> SpotifyClientAPI(
283-
clientId ?: "not-set", clientSecret ?: "not-set", Token(
284-
tokenString, "client_credentials", 1000,
285-
null, null
286-
), false, redirectUri ?: "not-set",
289+
clientId ?: "not-set",
290+
clientSecret ?: "not-set",
291+
Token(
292+
tokenString,
293+
"client_credentials",
294+
1000,
295+
null,
296+
null
297+
),
298+
false,
299+
redirectUri ?: "not-set",
287300
useCache
288301
)
289302
else -> throw IllegalArgumentException(

src/main/kotlin/com/adamratzman/spotify/SpotifyAPI.kt

Lines changed: 36 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import com.adamratzman.spotify.endpoints.public.UserAPI
1818
import com.adamratzman.spotify.http.HttpConnection
1919
import com.adamratzman.spotify.http.HttpHeader
2020
import com.adamratzman.spotify.http.HttpRequestMethod
21+
import com.adamratzman.spotify.http.HttpResponse
2122
import com.adamratzman.spotify.http.SpotifyEndpoint
2223
import com.adamratzman.spotify.http.byteEncode
2324
import com.adamratzman.spotify.models.AuthenticationError
@@ -58,11 +59,11 @@ internal val base = "https://api.spotify.com/v1"
5859
*
5960
*/
6061
abstract class SpotifyAPI internal constructor(
61-
val clientId: String,
62-
val clientSecret: String,
63-
var token: Token,
64-
useCache: Boolean,
65-
var automaticRefresh: Boolean
62+
val clientId: String,
63+
val clientSecret: String,
64+
var token: Token,
65+
useCache: Boolean,
66+
var automaticRefresh: Boolean
6667
) {
6768
private var refreshFuture: ScheduledFuture<*>? = null
6869

@@ -151,11 +152,13 @@ abstract class SpotifyAPI internal constructor(
151152
* An API instance created with application credentials, not through
152153
* client authentication
153154
*/
154-
class SpotifyAppAPI internal constructor(clientId: String,
155-
clientSecret: String,
156-
token: Token,
157-
useCache: Boolean,
158-
automaticRefresh: Boolean) :
155+
class SpotifyAppAPI internal constructor(
156+
clientId: String,
157+
clientSecret: String,
158+
token: Token,
159+
useCache: Boolean,
160+
automaticRefresh: Boolean
161+
) :
159162
SpotifyAPI(clientId, clientSecret, token, useCache, automaticRefresh) {
160163

161164
override val search: SearchAPI = SearchAPI(this)
@@ -185,7 +188,7 @@ class SpotifyAppAPI internal constructor(clientId: String,
185188
if (clientId != "not-set" && clientSecret != "not-set") {
186189
val currentToken = this.token
187190

188-
token = getCredentialedToken(clientId, clientSecret)
191+
token = getCredentialedToken(clientId, clientSecret, this)
189192
expireTime = System.currentTimeMillis() + token.expiresIn * 1000
190193

191194
return currentToken
@@ -221,12 +224,12 @@ class SpotifyAppAPI internal constructor(clientId: String,
221224
* managed through the scopes exposed in [token]
222225
*/
223226
class SpotifyClientAPI internal constructor(
224-
clientId: String,
225-
clientSecret: String,
226-
token: Token,
227-
automaticRefresh: Boolean = false,
228-
var redirectUri: String,
229-
useCache: Boolean
227+
clientId: String,
228+
clientSecret: String,
229+
token: Token,
230+
automaticRefresh: Boolean,
231+
var redirectUri: String,
232+
useCache: Boolean
230233
) : SpotifyAPI(clientId, clientSecret, token, useCache, automaticRefresh) {
231234
override val search: SearchAPI = SearchAPI(this)
232235
override val albums: AlbumAPI = AlbumAPI(this)
@@ -299,14 +302,13 @@ class SpotifyClientAPI internal constructor(
299302
override fun refreshToken(): Token {
300303
val currentToken = this.token
301304

302-
val response =
303-
HttpConnection(
304-
url = "https://accounts.spotify.com/api/token",
305-
method = HttpRequestMethod.POST,
306-
body = "grant_type=refresh_token&refresh_token=${token.refreshToken ?: ""}",
307-
contentType = "application/x-www-form-urlencoded",
308-
api = this
309-
).execute(HttpHeader("Authorization", "Basic ${"$clientId:$clientSecret".byteEncode()}"))
305+
val response = executeTokenRequest(HttpConnection(
306+
url = "https://accounts.spotify.com/api/token",
307+
method = HttpRequestMethod.POST,
308+
body = "grant_type=refresh_token&refresh_token=${token.refreshToken ?: ""}",
309+
contentType = "application/x-www-form-urlencoded",
310+
api = this
311+
), clientId, clientSecret)
310312

311313
if (response.responseCode / 200 == 1) {
312314
val tempToken = response.body.toObject<Token>(this)
@@ -316,8 +318,7 @@ class SpotifyClientAPI internal constructor(
316318
)
317319
logger.logInfo("Successfully refreshed the Spotify token")
318320
return currentToken
319-
}
320-
else throw BadRequestException(response.body.toObject<AuthenticationError>(this))
321+
} else throw BadRequestException(response.body.toObject<AuthenticationError>(this))
321322
}
322323

323324
override fun clearCache() = clearAllCaches(
@@ -367,14 +368,14 @@ fun getAuthUrlFull(vararg scopes: SpotifyScope, clientId: String, redirectUri: S
367368
if (scopes.isEmpty()) "" else "&scope=${scopes.joinToString("%20") { it.uri }}"
368369
}
369370

370-
fun getCredentialedToken(clientId: String, clientSecret: String): Token {
371-
val response = HttpConnection(
371+
fun getCredentialedToken(clientId: String, clientSecret: String, api: SpotifyAPI?): Token {
372+
val response = executeTokenRequest(HttpConnection(
372373
url = "https://accounts.spotify.com/api/token",
373374
method = HttpRequestMethod.POST,
374375
body = "grant_type=client_credentials",
375376
contentType = "application/x-www-form-urlencoded",
376-
api = null
377-
).execute(HttpHeader("Authorization", "Basic ${"$clientId:$clientSecret".byteEncode()}"))
377+
api = api
378+
), clientId, clientSecret)
378379

379380
if (response.responseCode / 200 == 1) return response.body.toObject(null)
380381

@@ -387,3 +388,7 @@ private fun getKlaxon(api: SpotifyAPI) = Klaxon()
387388
.converter(getAlbumConverter(api))
388389
.converter(getSavedTrackConverter(api))
389390
.converter(getPublicUserConverter(api))
391+
392+
internal fun executeTokenRequest(httpConnection: HttpConnection, clientId: String, clientSecret: String): HttpResponse {
393+
return httpConnection.execute(HttpHeader("Authorization", "Basic ${"$clientId:$clientSecret".byteEncode()}"))
394+
}

src/main/kotlin/com/adamratzman/spotify/http/HttpConnection.kt

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,12 @@ internal enum class HttpRequestMethod { GET, POST, PUT, DELETE }
99
internal data class HttpHeader(val key: String, val value: String)
1010

1111
internal class HttpConnection(
12-
private val url: String,
13-
private val method: HttpRequestMethod,
14-
private val body: String?,
15-
private val contentType: String?,
16-
private vararg val headers: HttpHeader,
17-
val api: SpotifyAPI? = null
12+
private val url: String,
13+
private val method: HttpRequestMethod,
14+
private val body: String?,
15+
private val contentType: String?,
16+
private vararg val headers: HttpHeader,
17+
val api: SpotifyAPI? = null
1818
) {
1919

2020
fun execute(vararg additionalHeaders: HttpHeader?, retryIf502: Boolean = true): HttpResponse {
@@ -52,8 +52,8 @@ internal class HttpConnection(
5252
text
5353
}
5454

55-
if (responseCode == 401 && body.contains("access token")
56-
&& api != null && api.automaticRefresh) {
55+
if (responseCode == 401 && body.contains("access token") &&
56+
api != null && api.automaticRefresh) {
5757
api.refreshToken()
5858
return execute(*additionalHeaders)
5959
}

0 commit comments

Comments
 (0)