@@ -2,73 +2,24 @@ package com.adamratzman.spotify.auth
2
2
3
3
import android.annotation.SuppressLint
4
4
import android.app.Activity
5
+ import android.app.Application
5
6
import android.content.Context
6
- import android.content.Intent
7
7
import android.content.SharedPreferences
8
8
import android.os.Build
9
9
import androidx.annotation.RequiresApi
10
10
import androidx.security.crypto.EncryptedSharedPreferences
11
11
import androidx.security.crypto.MasterKeys
12
+ import com.adamratzman.spotify.GenericSpotifyApi
13
+ import com.adamratzman.spotify.SpotifyApi
12
14
import com.adamratzman.spotify.SpotifyApiOptions
13
- import com.adamratzman.spotify.SpotifyException
15
+ import com.adamratzman.spotify.SpotifyClientApi
14
16
import com.adamratzman.spotify.SpotifyImplicitGrantApi
15
- import com.adamratzman.spotify.auth.SpotifyDefaultAuthHelper.activityBack
17
+ import com.adamratzman.spotify.SpotifyUserAuthorization
16
18
import com.adamratzman.spotify.models.Token
19
+ import com.adamratzman.spotify.spotifyClientPkceApi
17
20
import com.adamratzman.spotify.spotifyImplicitGrantApi
21
+ import com.adamratzman.spotify.utils.logToConsole
18
22
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
- }
72
23
73
24
/* *
74
25
* Provided credential store for holding current Spotify token credentials, allowing you to easily store and retrieve
@@ -79,7 +30,11 @@ public object SpotifyDefaultAuthHelper {
79
30
*
80
31
*/
81
32
@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
+ ) {
83
38
public companion object {
84
39
/* *
85
40
* The key used with spotify token expiry in [EncryptedSharedPreferences]
@@ -91,8 +46,20 @@ public class SpotifyDefaultCredentialStore constructor(private val clientId: Str
91
46
*/
92
47
public const val SpotifyAccessTokenKey : String = " spotifyAccessToken"
93
48
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
94
58
}
95
59
60
+
61
+ public var credentialTypeStored: CredentialType ? = null
62
+
96
63
/* *
97
64
* The [EncryptedSharedPreferences] that this API saves to/retrieves from.
98
65
*/
@@ -126,6 +93,14 @@ public class SpotifyDefaultCredentialStore constructor(private val clientId: Str
126
93
get() = encryptedPreferences.getString(SpotifyAccessTokenKey , null )
127
94
set(value) = encryptedPreferences.edit().putString(SpotifyAccessTokenKey , value).apply ()
128
95
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
+
129
104
/* *
130
105
* Get/set the Spotify [Token] obtained from [spotifyToken].
131
106
* If the token has expired according to [spotifyTokenExpiresAt], this will return null.
@@ -136,15 +111,28 @@ public class SpotifyDefaultCredentialStore constructor(private val clientId: Str
136
111
val accessToken = spotifyAccessToken ? : return null
137
112
if (tokenExpiresAt < System .currentTimeMillis()) return null
138
113
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
+ )
140
121
}
141
122
set(token) {
142
123
if (token == null ) {
143
124
spotifyAccessToken = null
144
125
spotifyTokenExpiresAt = null
126
+ spotifyRefreshToken = null
127
+
128
+ credentialTypeStored = null
145
129
} else {
146
130
spotifyAccessToken = token.accessToken
147
131
spotifyTokenExpiresAt = token.expiresAt
132
+ spotifyRefreshToken = token.refreshToken
133
+
134
+ credentialTypeStored =
135
+ if (token.refreshToken != null ) CredentialType .Pkce else CredentialType .ImplicitGrant
148
136
}
149
137
}
150
138
@@ -159,11 +147,33 @@ public class SpotifyDefaultCredentialStore constructor(private val clientId: Str
159
147
}
160
148
161
149
/* *
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 .
163
151
*
164
- * @param api A valid [SpotifyImplicitGrantApi]
152
+ * @param block Applied configuration to the [SpotifyImplicitGrantApi]
165
153
*/
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 ) {
167
177
spotifyToken = api.token
168
178
}
169
179
@@ -179,6 +189,12 @@ public class SpotifyDefaultCredentialStore constructor(private val clientId: Str
179
189
}
180
190
}
181
191
192
+ public enum class CredentialType {
193
+ ImplicitGrant ,
194
+ Pkce
195
+ }
182
196
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