@@ -2,73 +2,24 @@ package com.adamratzman.spotify.auth
22
33import android.annotation.SuppressLint
44import android.app.Activity
5+ import android.app.Application
56import android.content.Context
6- import android.content.Intent
77import android.content.SharedPreferences
88import android.os.Build
99import androidx.annotation.RequiresApi
1010import androidx.security.crypto.EncryptedSharedPreferences
1111import androidx.security.crypto.MasterKeys
12+ import com.adamratzman.spotify.GenericSpotifyApi
13+ import com.adamratzman.spotify.SpotifyApi
1214import com.adamratzman.spotify.SpotifyApiOptions
13- import com.adamratzman.spotify.SpotifyException
15+ import com.adamratzman.spotify.SpotifyClientApi
1416import com.adamratzman.spotify.SpotifyImplicitGrantApi
15- import com.adamratzman.spotify.auth.SpotifyDefaultAuthHelper.activityBack
17+ import com.adamratzman.spotify.SpotifyUserAuthorization
1618import com.adamratzman.spotify.models.Token
19+ import com.adamratzman.spotify.spotifyClientPkceApi
1720import com.adamratzman.spotify.spotifyImplicitGrantApi
21+ import com.adamratzman.spotify.utils.logToConsole
1822
19- // Starting login activity
20-
21- /* *
22- * Start Spotify login activity within an existing activity.
23- */
24- public inline fun <reified T : AbstractSpotifyLoginActivity > Activity.startSpotifyLoginActivity () {
25- startSpotifyLoginActivity(T ::class .java)
26- }
27-
28- /* *
29- * Start Spotify login activity within an existing activity.
30- *
31- * @param spotifyLoginImplementationClass Your implementation of [AbstractSpotifyLoginActivity], defining what to do on Spotify login
32- */
33- public fun <T : AbstractSpotifyLoginActivity > Activity.startSpotifyLoginActivity (spotifyLoginImplementationClass : Class <T >) {
34- startActivity(Intent (this , spotifyLoginImplementationClass))
35- }
36-
37-
38- /* *
39- * Basic authentication guard - verifies that the user is logged in to Spotify and uses [SpotifyDefaultAuthHelper] to
40- * handle re-authentication and redirection back to the activity.
41- *
42- * Note: this should only be used for small applications.
43- *
44- * @param spotifyLoginImplementationClass Your implementation of [AbstractSpotifyLoginActivity], defining what to do on Spotify login
45- * @param classBackTo The activity to return to if re-authentication is necessary
46- * @block The code block to execute
47- */
48- public fun <T > Activity.guardValidSpotifyApi (
49- spotifyLoginImplementationClass : Class <out AbstractSpotifyLoginActivity >,
50- classBackTo : Class <out Activity >? = null,
51- block : () -> T
52- ): T ? {
53- return try {
54- block()
55- } catch (e: SpotifyException .ReAuthenticationNeededException ) {
56- activityBack = classBackTo
57- startSpotifyLoginActivity(spotifyLoginImplementationClass)
58- null
59- }
60- }
61-
62- /* *
63- * Default authentiction helper for Android. Contains static variables useful in authentication.
64- *
65- */
66- public object SpotifyDefaultAuthHelper {
67- /* *
68- * The activity to return to if re-authentication is necessary. Null except during authentication when using [guardValidSpotifyApi]
69- */
70- public var activityBack: Class <out Activity >? = null
71- }
7223
7324/* *
7425 * Provided credential store for holding current Spotify token credentials, allowing you to easily store and retrieve
@@ -79,7 +30,11 @@ public object SpotifyDefaultAuthHelper {
7930 *
8031 */
8132@RequiresApi(Build .VERSION_CODES .M )
82- public class SpotifyDefaultCredentialStore constructor(private val clientId : String , applicationContext : Context ) {
33+ public class SpotifyDefaultCredentialStore constructor(
34+ private val clientId : String ,
35+ private val redirectUri : String ,
36+ applicationContext : Context
37+ ) {
8338 public companion object {
8439 /* *
8540 * The key used with spotify token expiry in [EncryptedSharedPreferences]
@@ -91,8 +46,20 @@ public class SpotifyDefaultCredentialStore constructor(private val clientId: Str
9146 */
9247 public const val SpotifyAccessTokenKey : String = " spotifyAccessToken"
9348
49+ /* *
50+ * The key used with spotify refresh token in [EncryptedSharedPreferences]
51+ */
52+ public const val SpotifyRefreshTokenKey : String = " spotifyRefreshToken"
53+
54+ /* *
55+ * The activity to return to if re-authentication is necessary on implicit authentication. Null except during authentication when using [guardValidImplicitSpotifyApi]
56+ */
57+ public var activityBackOnImplicitAuth: Class <out Activity >? = null
9458 }
9559
60+
61+ public var credentialTypeStored: CredentialType ? = null
62+
9663 /* *
9764 * The [EncryptedSharedPreferences] that this API saves to/retrieves from.
9865 */
@@ -126,6 +93,14 @@ public class SpotifyDefaultCredentialStore constructor(private val clientId: Str
12693 get() = encryptedPreferences.getString(SpotifyAccessTokenKey , null )
12794 set(value) = encryptedPreferences.edit().putString(SpotifyAccessTokenKey , value).apply ()
12895
96+ /* *
97+ * Get/set the Spotify refresh token.
98+ */
99+ public var spotifyRefreshToken: String?
100+ get() = encryptedPreferences.getString(SpotifyRefreshTokenKey , null )
101+ set(value) = encryptedPreferences.edit().putString(SpotifyRefreshTokenKey , value).apply ()
102+
103+
129104 /* *
130105 * Get/set the Spotify [Token] obtained from [spotifyToken].
131106 * If the token has expired according to [spotifyTokenExpiresAt], this will return null.
@@ -136,15 +111,28 @@ public class SpotifyDefaultCredentialStore constructor(private val clientId: Str
136111 val accessToken = spotifyAccessToken ? : return null
137112 if (tokenExpiresAt < System .currentTimeMillis()) return null
138113
139- return Token (accessToken, " Bearer" , (tokenExpiresAt - System .currentTimeMillis()).toInt() / 1000 )
114+ val refreshToken = spotifyRefreshToken
115+ return Token (
116+ accessToken,
117+ " Bearer" ,
118+ (tokenExpiresAt - System .currentTimeMillis()).toInt() / 1000 ,
119+ refreshToken
120+ )
140121 }
141122 set(token) {
142123 if (token == null ) {
143124 spotifyAccessToken = null
144125 spotifyTokenExpiresAt = null
126+ spotifyRefreshToken = null
127+
128+ credentialTypeStored = null
145129 } else {
146130 spotifyAccessToken = token.accessToken
147131 spotifyTokenExpiresAt = token.expiresAt
132+ spotifyRefreshToken = token.refreshToken
133+
134+ credentialTypeStored =
135+ if (token.refreshToken != null ) CredentialType .Pkce else CredentialType .ImplicitGrant
148136 }
149137 }
150138
@@ -159,11 +147,33 @@ public class SpotifyDefaultCredentialStore constructor(private val clientId: Str
159147 }
160148
161149 /* *
162- * Sets [spotifyToken] using [SpotifyImplicitGrantApi.token]. This wraps around [spotifyToken]'s setter .
150+ * Create a new [SpotifyClientApi] instance using the [spotifyToken] stored using this credential store .
163151 *
164- * @param api A valid [SpotifyImplicitGrantApi]
152+ * @param block Applied configuration to the [SpotifyImplicitGrantApi]
165153 */
166- public fun setSpotifyImplicitGrantApi (api : SpotifyImplicitGrantApi ) {
154+ public suspend fun getSpotifyClientPkceApi (block : ((SpotifyApiOptions ).() -> Unit )? = null): SpotifyClientApi ? {
155+ val token = spotifyToken ? : return null
156+ return spotifyClientPkceApi(
157+ clientId,
158+ redirectUri,
159+ SpotifyUserAuthorization (token = token),
160+ block ? : {}
161+ ).build().apply {
162+ val previousAfterTokenRefresh = spotifyApiOptions.afterTokenRefresh
163+ spotifyApiOptions.afterTokenRefresh = {
164+ spotifyToken = this .token
165+ logToConsole(" Refreshed Spotify PKCE token in credential store... $token " )
166+ previousAfterTokenRefresh?.invoke(this )
167+ }
168+ }
169+ }
170+
171+ /* *
172+ * Sets [spotifyToken] using [SpotifyApi.token]. This wraps around [spotifyToken]'s setter.
173+ *
174+ * @param api A valid [GenericSpotifyApi]
175+ */
176+ public fun setSpotifyApi (api : GenericSpotifyApi ) {
167177 spotifyToken = api.token
168178 }
169179
@@ -179,6 +189,12 @@ public class SpotifyDefaultCredentialStore constructor(private val clientId: Str
179189 }
180190}
181191
192+ public enum class CredentialType {
193+ ImplicitGrant ,
194+ Pkce
195+ }
182196
183-
184-
197+ @RequiresApi(Build .VERSION_CODES .M )
198+ public fun Application.getDefaultCredentialStore (clientId : String , redirectUri : String ): SpotifyDefaultCredentialStore {
199+ return SpotifyDefaultCredentialStore (clientId, redirectUri, applicationContext)
200+ }
0 commit comments