Skip to content

Commit ac77c9e

Browse files
authored
Merge pull request #269 from adamint/dev
update credential store
2 parents 438fb89 + f0c17a6 commit ac77c9e

File tree

6 files changed

+116
-51
lines changed

6 files changed

+116
-51
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ A [Kotlin](https://kotlinlang.org/) implementation of the [Spotify Web API](http
33
supporting Kotlin/JS, Kotlin/Android, Kotlin/JVM, and Kotlin/Native
44
(macOS, Windows, Linux).
55

6+
**Use this library with Kotlin, Java, JavaScript, or native code!**
7+
68
[![JCenter](https://maven-badges.herokuapp.com/maven-central/com.adamratzman/spotify-api-kotlin-core/badge.svg)](https://maven-badges.herokuapp.com/maven-central/com.adamratzman/spotify-api-kotlin-core)
79
[![](https://img.shields.io/badge/Documentation-latest-orange.svg)](https://adamint.github.io/spotify-web-api-kotlin-docs/spotify-web-api-kotlin/)
810
![](https://img.shields.io/badge/License-MIT-blue.svg)

build.gradle.kts

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,12 @@ import org.jetbrains.kotlin.gradle.plugin.KotlinJsCompilerType
33
import org.jetbrains.kotlin.gradle.targets.js.webpack.KotlinWebpackOutput.Target
44

55
plugins {
6+
// id("lt.petuska.npm.publish") version "1.1.2"
7+
kotlin("multiplatform") version "1.4.31"
68
`maven-publish`
79
signing
810
id("io.codearte.nexus-staging") version "0.22.0"
911
id("com.android.library")
10-
kotlin("multiplatform") version "1.4.31"
1112
kotlin("plugin.serialization") version "1.4.30"
1213
id("com.diffplug.spotless") version "5.9.0"
1314
id("com.moowork.node") version "1.3.1"
@@ -119,7 +120,9 @@ kotlin {
119120
}
120121
}
121122

122-
js(KotlinJsCompilerType.BOTH) {
123+
js(if (project.hasProperty("irOnly")) KotlinJsCompilerType.IR else KotlinJsCompilerType.BOTH) {
124+
//binaries.library()
125+
123126
mavenPublication {
124127
setupPom(artifactId)
125128
}
@@ -141,7 +144,7 @@ kotlin {
141144
nodejs {
142145
testTask {
143146
useMocha {
144-
timeout = "5000"
147+
timeout = "15000"
145148
}
146149
}
147150
}
@@ -201,6 +204,10 @@ kotlin {
201204

202205
val commonJvmLikeMain by creating {
203206
dependsOn(commonMain)
207+
208+
dependencies {
209+
implementation("net.sourceforge.streamsupport:android-retrofuture:1.7.3")
210+
}
204211
}
205212

206213
val jvmMain by getting {
@@ -327,6 +334,25 @@ signing {
327334
}
328335

329336
tasks {
337+
/*npmPublishing {
338+
readme = file("README.MD")
339+
340+
repositories {
341+
repository("npmjs") {
342+
registry = uri("https://registry.npmjs.org")
343+
(project.properties.get("npmauthtoken") as? String)?.let { authToken = it }
344+
println("auth token: $authToken")
345+
}
346+
}
347+
348+
publications {
349+
publication("js") {
350+
bundleKotlinDependencies = true
351+
shrinkwrapBundledDependencies = true
352+
}
353+
}
354+
}*/
355+
330356
val dokkaHtml by getting(DokkaTask::class) {
331357
outputDirectory.set(projectDir.resolve("docs"))
332358

settings.gradle.kts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
pluginManagement {
2-
val mainKotlinVersion = "1.4.21"
2+
val mainKotlinVersion = "1.4.31"
33

44
resolutionStrategy {
55
eachPlugin {

src/androidMain/kotlin/com/adamratzman/spotify/auth/SpotifyDefaultCredentialStore.kt

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import com.adamratzman.spotify.SpotifyClientApi
1717
import com.adamratzman.spotify.SpotifyImplicitGrantApi
1818
import com.adamratzman.spotify.SpotifyUserAuthorization
1919
import com.adamratzman.spotify.models.Token
20+
import com.adamratzman.spotify.refreshSpotifyClientToken
2021
import com.adamratzman.spotify.spotifyClientPkceApi
2122
import com.adamratzman.spotify.spotifyImplicitGrantApi
2223
import com.adamratzman.spotify.utils.logToConsole
@@ -148,10 +149,18 @@ public class SpotifyDefaultCredentialStore(
148149
/**
149150
* Create a new [SpotifyClientApi] instance using the [spotifyToken] stored using this credential store.
150151
*
151-
* @param block Applied configuration to the [SpotifyImplicitGrantApi]
152+
* @param block Applied configuration to the [SpotifyClientApi]
152153
*/
153154
public fun getSpotifyClientPkceApi(block: ((SpotifyApiOptions).() -> Unit)? = null): SpotifyClientApi? {
154-
val token = spotifyToken ?: return null
155+
val token = spotifyToken
156+
?: if (spotifyRefreshToken != null) {
157+
runBlocking {
158+
val newToken = refreshSpotifyClientToken(clientId, null, spotifyRefreshToken, true)
159+
spotifyToken = newToken
160+
newToken
161+
}
162+
} else return null
163+
155164
return runBlocking {
156165
spotifyClientPkceApi(
157166
clientId,
@@ -178,6 +187,15 @@ public class SpotifyDefaultCredentialStore(
178187
spotifyToken = api.token
179188
}
180189

190+
/**
191+
* Returns whether the [Token] stored in this Credential Store is refreshable (whether there is a refresh token associated
192+
* with it).
193+
*/
194+
public fun canApiBeRefreshed(): Boolean {
195+
return spotifyRefreshToken != null
196+
}
197+
198+
181199
/**
182200
* Clear the [SharedPreferences] instance corresponding to the Spotify credentials.
183201
*/

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

Lines changed: 63 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import com.adamratzman.spotify.http.SpotifyRequest
3333
import com.adamratzman.spotify.models.AuthenticationError
3434
import com.adamratzman.spotify.models.Token
3535
import com.adamratzman.spotify.models.TokenValidityResponse
36+
import com.adamratzman.spotify.models.serialization.nonstrictJson
3637
import com.adamratzman.spotify.models.serialization.toObject
3738
import com.adamratzman.spotify.utils.asList
3839
import com.adamratzman.spotify.utils.base64ByteEncode
@@ -588,51 +589,7 @@ public open class SpotifyClientApi(
588589

589590
require(api.clientId != null) { "The client id is not set" }
590591

591-
val response = if (!api.usesPkceAuth) {
592-
require(api.clientSecret != null) { "The client secret is not set" }
593-
executeTokenRequest(
594-
HttpConnection(
595-
"https://accounts.spotify.com/api/token",
596-
HttpRequestMethod.POST,
597-
getDefaultClientApiTokenBody(api),
598-
null,
599-
"application/x-www-form-urlencoded",
600-
listOf(),
601-
api
602-
), api.clientId, api.clientSecret
603-
)
604-
} else {
605-
HttpConnection(
606-
"https://accounts.spotify.com/api/token",
607-
HttpRequestMethod.POST,
608-
getDefaultClientApiTokenBody(api),
609-
null,
610-
"application/x-www-form-urlencoded",
611-
listOf(),
612-
api
613-
).execute()
614-
}
615-
616-
if (response.responseCode in 200..399) {
617-
response.body.toObject(Token.serializer(), api, api.spotifyApiOptions.json)
618-
} else throw BadRequestException(
619-
response.body.toObject(
620-
AuthenticationError.serializer(),
621-
api,
622-
api.spotifyApiOptions.json
623-
)
624-
)
625-
}
626-
627-
private fun getDefaultClientApiTokenBody(api: SpotifyClientApi): Map<String, String?> {
628-
val map = mutableMapOf(
629-
"grant_type" to "refresh_token",
630-
"refresh_token" to api.token.refreshToken
631-
)
632-
633-
if (api.usesPkceAuth) map += "client_id" to api.clientId
634-
635-
return map
592+
refreshSpotifyClientToken(api.clientId, api.clientSecret, api.token.refreshToken, api.usesPkceAuth)
636593
}
637594
}
638595
}
@@ -689,3 +646,64 @@ internal suspend fun executeTokenRequest(
689646
)
690647
)
691648
}
649+
650+
/**
651+
* Refresh a Spotify client token
652+
*
653+
* @param clientId The Spotify application client id.
654+
* @param clientSecret The Spotify application client secret (not needed for PKCE).
655+
* @param refreshToken The refresh token.
656+
* @param usesPkceAuth Whether this token was created using PKCE auth or not.
657+
*/
658+
public suspend fun refreshSpotifyClientToken(
659+
clientId: String,
660+
clientSecret: String?,
661+
refreshToken: String?,
662+
usesPkceAuth: Boolean
663+
): Token {
664+
fun getDefaultClientApiTokenBody(): Map<String, String?> {
665+
val map = mutableMapOf(
666+
"grant_type" to "refresh_token",
667+
"refresh_token" to refreshToken
668+
)
669+
670+
if (usesPkceAuth) map += "client_id" to clientId
671+
672+
return map
673+
}
674+
675+
val response = if (!usesPkceAuth) {
676+
require(clientSecret != null) { "The client secret is not set" }
677+
executeTokenRequest(
678+
HttpConnection(
679+
"https://accounts.spotify.com/api/token",
680+
HttpRequestMethod.POST,
681+
getDefaultClientApiTokenBody(),
682+
null,
683+
"application/x-www-form-urlencoded",
684+
listOf(),
685+
null
686+
), clientId, clientSecret
687+
)
688+
} else {
689+
HttpConnection(
690+
"https://accounts.spotify.com/api/token",
691+
HttpRequestMethod.POST,
692+
getDefaultClientApiTokenBody(),
693+
null,
694+
"application/x-www-form-urlencoded",
695+
listOf(),
696+
null
697+
).execute()
698+
}
699+
700+
return if (response.responseCode in 200..399) {
701+
response.body.toObject(Token.serializer(), null, nonstrictJson)
702+
} else throw BadRequestException(
703+
response.body.toObject(
704+
AuthenticationError.serializer(),
705+
null,
706+
nonstrictJson
707+
)
708+
)
709+
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1106,6 +1106,7 @@ public class SpotifyUserAuthorization(
11061106
* @param retryOnInternalServerErrorTimes Whether and how often to retry once if an internal server error (500..599) has been received. Set to 0
11071107
* to avoid retrying at all, or set to null to keep retrying until success.
11081108
* @param enableDebugMode Whether to enable debug mode (false by default). With debug mode, all response JSON will be outputted to console.
1109+
* @param afterTokenRefresh An optional block to execute after token refresh has been completed.
11091110
*/
11101111
public data class SpotifyApiOptions(
11111112
public var useCache: Boolean = true,

0 commit comments

Comments
 (0)