Skip to content

Commit 3b6ef36

Browse files
committed
Implement possibility to suspend function for requests
* Reformat code * Simplified some code (e. g. HttpConnection) * Allow the user to select the scope of the network request by setting all functions in between to be suspending
1 parent 3cfae05 commit 3b6ef36

File tree

28 files changed

+319
-309
lines changed

28 files changed

+319
-309
lines changed

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

Lines changed: 38 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import com.adamratzman.spotify.http.HttpRequestMethod
66
import com.adamratzman.spotify.models.SpotifyAuthenticationException
77
import com.adamratzman.spotify.models.Token
88
import com.adamratzman.spotify.models.serialization.toObject
9+
import com.adamratzman.spotify.utils.runBlocking
910
import kotlinx.coroutines.Dispatchers
1011
import kotlinx.coroutines.GlobalScope
1112
import kotlinx.coroutines.Job
@@ -18,7 +19,7 @@ import kotlinx.coroutines.withContext
1819
fun spotifyAppApi(block: SpotifyAppApiBuilder.() -> Unit) = SpotifyAppApiBuilder().apply(block)
1920
fun spotifyClientApi(block: SpotifyClientApiBuilder.() -> Unit) = SpotifyClientApiBuilder().apply(block)
2021

21-
/**
22+
/** //TODO add suspend functions
2223
* Spotify API builder
2324
*/
2425
class SpotifyApiBuilder(
@@ -242,39 +243,41 @@ class SpotifyClientApiBuilder(
242243

243244
require((clientId != null && clientSecret != null && redirectUri != null) || authorization.token != null || authorization.tokenString != null) { "You need to specify a valid clientId, clientSecret, and redirectUri in the credentials block!" }
244245
return when {
245-
authorization.authorizationCode != null -> try {
246-
require(clientId != null && clientSecret != null && redirectUri != null) { "You need to specify a valid clientId, clientSecret, and redirectUri in the credentials block!" }
247-
248-
val response = executeTokenRequest(
249-
HttpConnection(
250-
"https://accounts.spotify.com/api/token",
251-
HttpRequestMethod.POST,
252-
mapOf(
253-
"grant_type" to "authorization_code",
254-
"code" to authorization.authorizationCode,
255-
"redirect_uri" to redirectUri
256-
),
257-
null,
258-
"application/x-www-form-urlencoded",
259-
listOf(),
260-
null
261-
), clientId, clientSecret
262-
)
263-
264-
SpotifyClientApi(
265-
clientId,
266-
clientSecret,
267-
redirectUri,
268-
response.body.toObject(Token.serializer(), null),
269-
options.useCache,
270-
options.cacheLimit,
271-
options.automaticRefresh,
272-
options.retryWhenRateLimited,
273-
options.enableLogger,
274-
options.testTokenValidity
275-
)
276-
} catch (e: Exception) {
277-
throw SpotifyAuthenticationException("Invalid credentials provided in the login process", e)
246+
authorization.authorizationCode != null -> runBlocking {
247+
try {
248+
require(clientId != null && clientSecret != null && redirectUri != null) { "You need to specify a valid clientId, clientSecret, and redirectUri in the credentials block!" }
249+
250+
val response = executeTokenRequest(
251+
HttpConnection(
252+
"https://accounts.spotify.com/api/token",
253+
HttpRequestMethod.POST,
254+
mapOf(
255+
"grant_type" to "authorization_code",
256+
"code" to authorization.authorizationCode,
257+
"redirect_uri" to redirectUri
258+
),
259+
null,
260+
"application/x-www-form-urlencoded",
261+
listOf(),
262+
null
263+
), clientId, clientSecret
264+
)
265+
266+
SpotifyClientApi(
267+
clientId,
268+
clientSecret,
269+
redirectUri,
270+
response.body.toObject(Token.serializer(), null),
271+
options.useCache,
272+
options.cacheLimit,
273+
options.automaticRefresh,
274+
options.retryWhenRateLimited,
275+
options.enableLogger,
276+
options.testTokenValidity
277+
)
278+
} catch (e: Exception) {
279+
throw SpotifyAuthenticationException("Invalid credentials provided in the login process", e)
280+
}
278281
}
279282
authorization.token != null -> SpotifyClientApi(
280283
clientId,
@@ -390,7 +393,7 @@ class SpotifyAppApiBuilder(
390393
}
391394
else -> try {
392395
require(clientId != null && clientSecret != null) { "Illegal credentials provided" }
393-
val token = getCredentialedToken(clientId, clientSecret, null)
396+
val token = runBlocking { getCredentialedToken(clientId, clientSecret, null) }
394397
SpotifyAppApi(
395398
clientId,
396399
clientSecret,

src/commonMain/kotlin/com.adamratzman.spotify/SpotifyApi.kt

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import com.adamratzman.spotify.models.SpotifyAuthenticationException
2626
import com.adamratzman.spotify.models.Token
2727
import com.adamratzman.spotify.models.TokenValidityResponse
2828
import com.adamratzman.spotify.models.serialization.toObject
29+
import com.adamratzman.spotify.utils.runBlocking
2930
import com.adamratzman.spotify.utils.toList
3031

3132
internal const val base = "https://api.spotify.com/v1"
@@ -103,7 +104,9 @@ abstract class SpotifyApi internal constructor(
103104
* @return The old access token if refresh was successful
104105
* @throws BadRequestException if refresh fails
105106
*/
106-
abstract fun refreshToken(): Token
107+
fun refreshToken(): Token = runBlocking {
108+
suspendRefreshToken()
109+
}
107110

108111
/**
109112
* A list of all endpoints included in this api type
@@ -152,7 +155,7 @@ abstract class SpotifyApi internal constructor(
152155
return getAuthUrlFull(*scopes, clientId = clientId, redirectUri = redirectUri)
153156
}
154157

155-
/**
158+
/** //TODO add suspend version of it
156159
* Tests whether the current [token] is actually valid. By default, an endpoint is called *once* to verify
157160
* validity.
158161
*
@@ -174,6 +177,15 @@ abstract class SpotifyApi internal constructor(
174177
TokenValidityResponse(false, e)
175178
}
176179
}
180+
181+
/**
182+
* If the method used to create the [token] supports token refresh and
183+
* the information in [token] is accurate, attempt to refresh the token
184+
*
185+
* @return The old access token if refresh was successful
186+
* @throws BadRequestException if refresh fails
187+
*/
188+
abstract suspend fun suspendRefreshToken(): Token
177189
}
178190

179191
/**
@@ -239,7 +251,7 @@ class SpotifyAppApi internal constructor(
239251
*/
240252
override val following: FollowingApi = FollowingApi(this)
241253

242-
override fun refreshToken(): Token {
254+
override suspend fun suspendRefreshToken(): Token {
243255
require(clientId != null && clientSecret != null) { "Either the client id or the client secret is not set" }
244256
val currentToken = this.token
245257

@@ -389,7 +401,7 @@ class SpotifyClientApi internal constructor(
389401
runExecutableFunctions = false
390402
}
391403

392-
override fun refreshToken(): Token {
404+
override suspend fun suspendRefreshToken(): Token {
393405
require(clientId != null && clientSecret != null) { "Either the client id or the client secret is not set" }
394406

395407
val currentToken = this.token
@@ -483,7 +495,9 @@ class SpotifyClientApi internal constructor(
483495
* Whether the current access token allows access to all of the provided scopes
484496
*/
485497
fun hasScopes(scope: SpotifyScope, vararg scopes: SpotifyScope): Boolean =
486-
!isTokenValid(false).isValid && (scopes.toList() + scope).all { token.scopes.contains(it) }
498+
!isTokenValid(false).isValid &&
499+
token.scopes.contains(scope) &&
500+
scopes.all { token.scopes.contains(it) }
487501
}
488502

489503
typealias SpotifyAPI = SpotifyApi
@@ -497,7 +511,7 @@ fun getAuthUrlFull(vararg scopes: SpotifyScope, clientId: String, redirectUri: S
497511
if (scopes.isEmpty()) "" else "&scope=${scopes.joinToString("%20") { it.uri }}"
498512
}
499513

500-
fun getCredentialedToken(clientId: String, clientSecret: String, api: SpotifyApi?): Token {
514+
suspend fun getCredentialedToken(clientId: String, clientSecret: String, api: SpotifyApi?): Token {
501515
val response = executeTokenRequest(
502516
HttpConnection(
503517
"https://accounts.spotify.com/api/token",
@@ -515,7 +529,11 @@ fun getCredentialedToken(clientId: String, clientSecret: String, api: SpotifyApi
515529
throw SpotifyException.BadRequestException(response.body.toObject(AuthenticationError.serializer(), null))
516530
}
517531

518-
internal fun executeTokenRequest(httpConnection: HttpConnection, clientId: String, clientSecret: String): HttpResponse {
532+
internal suspend fun executeTokenRequest(
533+
httpConnection: HttpConnection,
534+
clientId: String,
535+
clientSecret: String
536+
): HttpResponse {
519537
return httpConnection.execute(
520538
listOf(
521539
HttpHeader(

src/commonMain/kotlin/com.adamratzman.spotify/SpotifyExceptions.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ sealed class SpotifyException(message: String, cause: Throwable? = null) : Excep
2525
"Authentication error: ${authenticationError.error}. Description: ${authenticationError.description}",
2626
401
2727
)
28+
2829
constructor(responseException: ResponseException) :
2930
this(
3031
responseException.message ?: "Bad Request",

0 commit comments

Comments
 (0)