Skip to content

Commit ec53afa

Browse files
committed
spotify auth wrapper
Signed-off-by: Adam Ratzman <[email protected]>
1 parent 4de2bb5 commit ec53afa

File tree

5 files changed

+92
-10
lines changed

5 files changed

+92
-10
lines changed

build.gradle.kts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,10 @@ kotlin {
176176
val serializationVersion = "1.0.1"
177177
val ktorVersion = "1.5.1"
178178
val korlibsVersion = "2.0.6"
179+
val sparkVersion = "2.9.3"
180+
val androidSpotifyAuthVersion = "1.2.3"
181+
val androidCryptoVersion = "1.1.0-alpha03"
182+
val coroutineMTVersion = "1.4.2-native-mt"
179183

180184
val commonMain by getting {
181185
dependencies {
@@ -209,7 +213,7 @@ kotlin {
209213
val jvmTest by getting {
210214
dependencies {
211215
implementation(kotlin("test-junit"))
212-
implementation("com.sparkjava:spark-core:2.9.3")
216+
implementation("com.sparkjava:spark-core:$sparkVersion")
213217
runtimeOnly(kotlin("reflect"))
214218
}
215219
}
@@ -228,10 +232,6 @@ kotlin {
228232
val jsTest by getting {
229233
dependencies {
230234
implementation(kotlin("test-js"))
231-
232-
// implementation("io.kotest:kotest-assertions-core-js:4.3.2")
233-
// implementation("io.kotest:kotest-framework-api-js:4.3.2")
234-
// implementation("io.kotest:kotest-framework-engine-js:4.3.2")
235235
}
236236
}
237237

@@ -242,14 +242,16 @@ kotlin {
242242
}
243243

244244
dependencies {
245+
implementation("com.spotify.android:auth:$androidSpotifyAuthVersion")
245246
implementation("io.ktor:ktor-client-okhttp:$ktorVersion")
247+
implementation("androidx.security:security-crypto:$androidCryptoVersion")
246248
}
247249
}
248250

249251
val androidTest by getting {
250252
dependencies {
251253
implementation(kotlin("test-junit"))
252-
implementation("com.sparkjava:spark-core:2.9.3")
254+
implementation("com.sparkjava:spark-core:$sparkVersion")
253255
runtimeOnly(kotlin("reflect"))
254256
}
255257
}
@@ -258,8 +260,6 @@ kotlin {
258260
dependsOn(commonMain)
259261

260262
dependencies {
261-
val coroutineMTVersion = "1.4.2-native-mt"
262-
263263
implementation ("org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutineMTVersion") {
264264
version {
265265
strictly(coroutineMTVersion)
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
package com.adamratzman.spotify.auth
2+
3+
import android.app.Activity
4+
import android.content.Intent
5+
import android.os.Bundle
6+
import com.adamratzman.spotify.SpotifyException
7+
import com.adamratzman.spotify.SpotifyImplicitGrantApi
8+
import com.adamratzman.spotify.SpotifyScope
9+
import com.adamratzman.spotify.models.Token
10+
import com.adamratzman.spotify.spotifyImplicitGrantApi
11+
import com.spotify.sdk.android.auth.AuthorizationClient
12+
import com.spotify.sdk.android.auth.AuthorizationRequest
13+
import com.spotify.sdk.android.auth.AuthorizationResponse
14+
15+
public abstract class AbstractSpotifyLoginActivity : Activity() {
16+
public abstract val state: Int
17+
public abstract val clientId: String
18+
public abstract val redirectUri: String
19+
public abstract val useDefaultRedirectHandler: Boolean
20+
public abstract fun getRequestingScopes(): List<SpotifyScope>
21+
22+
public abstract fun onSuccessfulAuthentication(spotifyApi: SpotifyImplicitGrantApi)
23+
public abstract fun onAuthenticationFailed(errorMessage: String)
24+
public open fun redirectAfterOnSuccessAuthentication() {
25+
if (useDefaultRedirectHandler && SpotifyDefaultAuthHelper.activityBack != null) {
26+
startActivity(Intent(this, SpotifyDefaultAuthHelper.activityBack))
27+
SpotifyDefaultAuthHelper.activityBack = null
28+
}
29+
finish()
30+
}
31+
32+
override fun onCreate(savedInstanceState: Bundle?) {
33+
super.onCreate(savedInstanceState)
34+
35+
triggerLoginActivity()
36+
}
37+
38+
public fun triggerLoginActivity() {
39+
val authorizationRequest =
40+
AuthorizationRequest.Builder(clientId, AuthorizationResponse.Type.TOKEN, redirectUri)
41+
.setScopes(getRequestingScopes().map { it.uri }.toTypedArray())
42+
.setState(state.toString())
43+
.build()
44+
AuthorizationClient.openLoginActivity(this, state, authorizationRequest)
45+
}
46+
47+
override fun onActivityResult(requestCode: Int, resultCode: Int, intent: Intent?) {
48+
super.onActivityResult(requestCode, resultCode, intent)
49+
if (requestCode == state) {
50+
val response = AuthorizationClient.getResponse(resultCode, intent)
51+
when (response.type) {
52+
AuthorizationResponse.Type.TOKEN -> {
53+
val token = Token(
54+
response.accessToken,
55+
response.type.name,
56+
response.expiresIn
57+
)
58+
val api = spotifyImplicitGrantApi(
59+
clientId = clientId,
60+
token = token
61+
) {
62+
refreshTokenProducer = { throw SpotifyException.ReAuthenticationNeededException() }
63+
}
64+
65+
onSuccessfulAuthentication(api)
66+
redirectAfterOnSuccessAuthentication()
67+
}
68+
//AuthorizationResponse.Type.CODE -> TODO()
69+
//AuthorizationResponse.Type.UNKNOWN -> TODO()
70+
AuthorizationResponse.Type.ERROR -> onAuthenticationFailed(response.error)
71+
AuthorizationResponse.Type.EMPTY -> onAuthenticationFailed(response.error)
72+
}
73+
finish()
74+
}
75+
76+
}
77+
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -222,7 +222,7 @@ public sealed class SpotifyApi<T : SpotifyApi<T, B>, B : ISpotifyApiBuilder<T, B
222222
public suspend fun refreshToken(): Token = spotifyApiOptions.refreshTokenProducer?.invoke(this)?.apply {
223223
this@SpotifyApi.token = this
224224
spotifyApiOptions.onTokenRefresh?.let { it(this@SpotifyApi) }
225-
} ?: throw IllegalStateException("The refreshTokenProducer is null.")
225+
} ?: throw SpotifyException.ReAuthenticationNeededException(IllegalStateException("The refreshTokenProducer is null."))
226226

227227
public companion object {
228228
internal suspend fun testTokenValidity(api: GenericSpotifyApi) {

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,4 +45,9 @@ public sealed class SpotifyException(message: String, cause: Throwable? = null)
4545
}
4646

4747
public class TimeoutException(message: String, cause: Throwable? = null) : SpotifyException(message, cause)
48+
49+
/**
50+
* Exception signifying that re-authentication via spotify-auth is necessary. Thrown by default when refreshTokenProducer is null.
51+
*/
52+
public class ReAuthenticationNeededException(cause: Throwable? = null) : SpotifyException("Re-authentication is needed.", cause)
4853
}

src/commonMain/kotlin/com.adamratzman.spotify/models/Authentication.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import kotlinx.serialization.Transient
1313
*
1414
* @param accessToken An access token that can be provided in subsequent calls,
1515
* for example to Spotify Web API services.
16-
* @param tokenType How the access token may be used: always Bearer”.
16+
* @param tokenType How the access token may be used: always Bearer”.
1717
* @param expiresIn The time period (in seconds) for which the access token is valid.
1818
* @param refreshToken A token that can be sent to the Spotify Accounts service in place of an authorization code,
1919
* null if the token was created using a method that does not support token refresh

0 commit comments

Comments
 (0)