Skip to content

Commit 4eb7ef7

Browse files
author
Adam Ratzman
committed
pkce fixes
1 parent 77c0c89 commit 4eb7ef7

File tree

5 files changed

+148
-85
lines changed

5 files changed

+148
-85
lines changed

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

Lines changed: 84 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ fun getSpotifyAuthorizationUrl(
5858
* @param state This provides protection against attacks such as cross-site request forgery.
5959
* @param codeChallenge In order to generate the code challenge, your app should hash the code verifier using the SHA256 algorithm.
6060
* Then, base64url encode the hash that you generated.
61+
*
6162
*/
6263
fun getPkceAuthorizationUrl(
6364
vararg scopes: SpotifyScope,
@@ -106,8 +107,12 @@ expect fun getSpotifyPkceCodeChallenge(codeVerifier: String): String
106107
*
107108
* @return [SpotifyImplicitGrantApi] that can immediately begin making calls
108109
*/
109-
fun spotifyImplicitGrantApi(clientId: String?, redirectUri: String?, token: Token, options: SpotifyApiOptions = SpotifyApiOptionsBuilder().build()) =
110-
SpotifyImplicitGrantApi(
110+
fun spotifyImplicitGrantApi(
111+
clientId: String?,
112+
redirectUri: String?,
113+
token: Token,
114+
options: SpotifyApiOptionsBuilder = SpotifyApiOptionsBuilder()
115+
) = SpotifyImplicitGrantApi(
111116
clientId,
112117
null,
113118
redirectUri,
@@ -142,10 +147,17 @@ fun spotifyImplicitGrantApi(clientId: String?, redirectUri: String?, token: Toke
142147
*
143148
* @param clientId Spotify [client id](https://developer.spotify.com/documentation/general/guides/app-settings/)
144149
* @param clientSecret Spotify [client secret](https://developer.spotify.com/documentation/general/guides/app-settings/)
150+
* @param options Override default API options such as the cache limit
145151
*
146152
* @return Configurable [SpotifyAppApiBuilder] that, when built, creates a new [SpotifyAppApi]
147153
*/
148-
fun spotifyAppApi(clientId: String, clientSecret: String) = spotifyAppApi(clientId, clientSecret) {}
154+
fun spotifyAppApi(
155+
clientId: String,
156+
clientSecret: String,
157+
options: SpotifyApiOptionsBuilder? = null
158+
) = spotifyAppApi(clientId, clientSecret) {
159+
options?.let { this.options = it.build() }
160+
}
149161

150162
/**
151163
* Instantiate a new [SpotifyAppApiBuilder] using a Spotify [clientId] and [clientSecret], with the ability to configure
@@ -157,8 +169,11 @@ fun spotifyAppApi(clientId: String, clientSecret: String) = spotifyAppApi(client
157169
*
158170
* @return Configurable [SpotifyAppApiBuilder] that, when built, creates a new [SpotifyAppApi]
159171
*/
160-
fun spotifyAppApi(clientId: String, clientSecret: String, block: SpotifyAppApiBuilder.() -> Unit = {}) =
161-
SpotifyAppApiBuilder().apply(block).apply {
172+
fun spotifyAppApi(
173+
clientId: String,
174+
clientSecret: String,
175+
block: SpotifyAppApiBuilder.() -> Unit
176+
) = SpotifyAppApiBuilder().apply(block).apply {
162177
credentials {
163178
this.clientId = clientId
164179
this.clientSecret = clientSecret
@@ -171,19 +186,26 @@ fun spotifyAppApi(clientId: String, clientSecret: String, block: SpotifyAppApiBu
171186
* @param clientId Spotify [client id](https://developer.spotify.com/documentation/general/guides/app-settings/)
172187
* @param clientSecret Spotify [client secret](https://developer.spotify.com/documentation/general/guides/app-settings/)
173188
* @param apiToken The access token that will be associated with the built API instance
189+
* @param options Override default API options such as the cache limit
174190
*
175191
* @return Configurable [SpotifyAppApiBuilder] that, when built, creates a new [SpotifyAppApi]
176192
*/
177-
fun spotifyAppApi(clientId: String?, clientSecret: String?, apiToken: Token) =
178-
SpotifyAppApiBuilder().apply {
179-
credentials {
180-
this.clientId = clientId
181-
this.clientSecret = clientSecret
182-
}
183-
authentication {
184-
token = apiToken
185-
}
186-
}
193+
fun spotifyAppApi(
194+
clientId: String?,
195+
clientSecret: String?,
196+
apiToken: Token,
197+
options: SpotifyApiOptionsBuilder? = null
198+
) = SpotifyAppApiBuilder().apply {
199+
credentials {
200+
this.clientId = clientId
201+
this.clientSecret = clientSecret
202+
}
203+
authentication {
204+
token = apiToken
205+
}
206+
207+
options?.let { this.options = it.build() }
208+
}
187209

188210
/**
189211
* Instantiate a new [SpotifyAppApiBuilder] by providing a builder initialization [block].
@@ -244,13 +266,15 @@ fun spotifyClientApi(
244266
* @param clientSecret Spotify [client secret](https://developer.spotify.com/documentation/general/guides/app-settings/)
245267
* @param redirectUri Spotify [redirect uri](https://developer.spotify.com/documentation/general/guides/app-settings/)
246268
* @param apiToken The access token that will be associated with the built API instance
269+
* @param options Override default API options such as the cache limit
247270
*
248271
* @return Configurable [SpotifyClientApiBuilder] that, when built, creates a new [SpotifyClientApi]
249272
*/
250273
fun spotifyClientApi(
251274
clientId: String?,
252275
clientSecret: String?,
253276
redirectUri: String?,
277+
options: SpotifyApiOptionsBuilder? = null,
254278
apiToken: Token
255279
) = SpotifyClientApiBuilder().apply {
256280
credentials {
@@ -262,6 +286,8 @@ fun spotifyClientApi(
262286
authentication {
263287
token = apiToken
264288
}
289+
290+
options?.let { this.options = it.build() }
265291
}
266292

267293
/**
@@ -272,14 +298,16 @@ fun spotifyClientApi(
272298
* @param clientSecret Spotify [client secret](https://developer.spotify.com/documentation/general/guides/app-settings/)
273299
* @param redirectUri Spotify [redirect uri](https://developer.spotify.com/documentation/general/guides/app-settings/)
274300
* @param authCode The authorization code retrieved after OAuth authorization
301+
* @param options Override default API options such as the cache limit
275302
*
276303
* @return Configurable [SpotifyClientApiBuilder] that, when built, creates a new [SpotifyClientApi]
277304
*/
278305
fun spotifyClientApi(
279306
clientId: String?,
280307
clientSecret: String?,
281308
redirectUri: String?,
282-
authCode: String
309+
authCode: String,
310+
options: SpotifyApiOptionsBuilder? = null
283311
) = SpotifyClientApiBuilder().apply {
284312
credentials {
285313
this.clientId = clientId
@@ -290,6 +318,8 @@ fun spotifyClientApi(
290318
authentication {
291319
authorizationCode = authCode
292320
}
321+
322+
options?.let { this.options = it.build() }
293323
}
294324

295325
/**
@@ -313,15 +343,15 @@ fun spotifyClientApi(
313343
clientSecret: String,
314344
redirectUri: String,
315345
authorization: SpotifyUserAuthorization,
316-
options: SpotifyApiOptions? = null,
346+
options: SpotifyApiOptionsBuilder? = null,
317347
block: SpotifyClientApiBuilder.() -> Unit = {}
318348
) = SpotifyClientApiBuilder().apply(block).apply {
319349
credentials {
320350
this.clientId = clientId
321351
this.clientSecret = clientSecret
322352
this.redirectUri = redirectUri
323353
}
324-
options?.let { this.options = options }
354+
options?.let { this.options = options.build() }
325355
this.authorization = authorization
326356
}
327357

@@ -359,36 +389,7 @@ fun spotifyClientApi(block: SpotifyClientApiBuilder.() -> Unit) = SpotifyClientA
359389
* @param redirectUri Spotify [redirect uri](https://developer.spotify.com/documentation/general/guides/app-settings/)
360390
* @param authCode The authorization code retrieved after OAuth authorization
361391
* @param codeVerifier Your code verifier plaintext.
362-
*
363-
* @return Configurable [SpotifyClientApiBuilder] that, when built, creates a new [SpotifyClientApi]
364-
*/
365-
fun spotifyClientPkceApi(
366-
clientId: String?,
367-
redirectUri: String?,
368-
authCode: String,
369-
codeVerifier: String
370-
) = SpotifyClientApiBuilder().apply {
371-
credentials {
372-
this.clientId = clientId
373-
this.redirectUri = redirectUri
374-
}
375-
376-
authentication {
377-
authorizationCode = authCode
378-
pkceCodeVerifier = codeVerifier
379-
}
380-
}
381-
382-
/**
383-
* Instantiate a new [SpotifyClientApiBuilder] using an [authCode] and [codeVerifier]. This is for **PKCE authorization**.
384-
*
385-
*
386-
* @param clientId Spotify [client id](https://developer.spotify.com/documentation/general/guides/app-settings/)
387-
* @param clientSecret Spotify [client secret](https://developer.spotify.com/documentation/general/guides/app-settings/)
388-
* @param redirectUri Spotify [redirect uri](https://developer.spotify.com/documentation/general/guides/app-settings/)
389-
* @param authCode The authorization code retrieved after OAuth authorization
390-
* @param codeVerifier Your code verifier plaintext.
391-
* @param block Api settings block
392+
* @param options Override default API options such as the cache limit
392393
*
393394
* @return Configurable [SpotifyClientApiBuilder] that, when built, creates a new [SpotifyClientApi]
394395
*/
@@ -397,7 +398,7 @@ fun spotifyClientPkceApi(
397398
redirectUri: String?,
398399
authCode: String,
399400
codeVerifier: String,
400-
block: SpotifyClientApiBuilder.() -> Unit = {}
401+
options: SpotifyApiOptionsBuilder? = null
401402
) = SpotifyClientApiBuilder().apply {
402403
credentials {
403404
this.clientId = clientId
@@ -408,7 +409,9 @@ fun spotifyClientPkceApi(
408409
authorizationCode = authCode
409410
pkceCodeVerifier = codeVerifier
410411
}
411-
}.apply(block)
412+
413+
options?.let { this.options = it.build() }
414+
}
412415

413416
/**
414417
* Spotify API builder
@@ -649,6 +652,11 @@ interface ISpotifyApiBuilder<T : SpotifyApi<T, B>, B : ISpotifyApiBuilder<T, B>>
649652
*/
650653
fun options(options: SpotifyApiOptions) = apply { this.options = options }
651654

655+
/**
656+
* Allows you to override default values for caching, token refresh, and logging
657+
*/
658+
fun options(options: SpotifyApiOptionsBuilder) = apply { this.options = options.build() }
659+
652660
/**
653661
* Build the [T] by provided information
654662
*/
@@ -751,7 +759,8 @@ class SpotifyClientApiBuilder(
751759
options.requestTimeoutMillis,
752760
options.json,
753761
options.refreshTokenProducer,
754-
false
762+
false,
763+
options.onTokenRefresh
755764
)
756765
} catch (e: CancellationException) {
757766
throw e
@@ -793,7 +802,8 @@ class SpotifyClientApiBuilder(
793802
options.requestTimeoutMillis,
794803
options.json,
795804
options.refreshTokenProducer,
796-
true
805+
true,
806+
options.onTokenRefresh
797807
)
798808
} catch (e: CancellationException) {
799809
throw e
@@ -816,7 +826,8 @@ class SpotifyClientApiBuilder(
816826
options.requestTimeoutMillis,
817827
options.json,
818828
options.refreshTokenProducer,
819-
false
829+
false,
830+
options.onTokenRefresh
820831
)
821832
authorization.tokenString != null -> SpotifyClientApi(
822833
clientId,
@@ -840,7 +851,8 @@ class SpotifyClientApiBuilder(
840851
options.requestTimeoutMillis,
841852
options.json,
842853
options.refreshTokenProducer,
843-
false
854+
false,
855+
options.onTokenRefresh
844856
)
845857
else -> throw IllegalArgumentException(
846858
"At least one of: authorizationCode, tokenString, or token must be provided " +
@@ -885,7 +897,8 @@ class SpotifyAppApiBuilder(
885897
options.allowBulkRequests,
886898
options.requestTimeoutMillis,
887899
options.json,
888-
options.refreshTokenProducer
900+
options.refreshTokenProducer,
901+
options.onTokenRefresh
889902
)
890903
authorization.tokenString != null -> {
891904
SpotifyAppApi(
@@ -905,7 +918,8 @@ class SpotifyAppApiBuilder(
905918
options.allowBulkRequests,
906919
options.requestTimeoutMillis,
907920
options.json,
908-
options.refreshTokenProducer
921+
options.refreshTokenProducer,
922+
options.onTokenRefresh
909923
)
910924
}
911925
authorization.refreshTokenString != null -> {
@@ -923,7 +937,8 @@ class SpotifyAppApiBuilder(
923937
options.allowBulkRequests,
924938
options.requestTimeoutMillis,
925939
options.json,
926-
options.refreshTokenProducer
940+
options.refreshTokenProducer,
941+
options.onTokenRefresh
927942
)
928943
}
929944
else -> try {
@@ -943,7 +958,8 @@ class SpotifyAppApiBuilder(
943958
options.allowBulkRequests,
944959
options.requestTimeoutMillis,
945960
options.json,
946-
options.refreshTokenProducer
961+
options.refreshTokenProducer,
962+
options.onTokenRefresh
947963
)
948964
} catch (e: CancellationException) {
949965
throw e
@@ -1028,6 +1044,7 @@ data class SpotifyUserAuthorization(
10281044
* @property allowBulkRequests Allow splitting too-large requests into smaller, allowable api requests
10291045
* @property requestTimeoutMillis The maximum time, in milliseconds, before terminating an http request
10301046
* @property refreshTokenProducer Provide if you want to use your own logic when refreshing a Spotify token
1047+
* @property onTokenRefresh Provide if you want to act on token refresh event
10311048
*
10321049
*/
10331050
class SpotifyApiOptionsBuilder(
@@ -1042,7 +1059,8 @@ class SpotifyApiOptionsBuilder(
10421059
var allowBulkRequests: Boolean = true,
10431060
var requestTimeoutMillis: Long? = null,
10441061
var json: Json = nonstrictJson,
1045-
var refreshTokenProducer: (suspend (SpotifyApi<*, *>) -> Token)? = null
1062+
var refreshTokenProducer: (suspend (GenericSpotifyApi) -> Token)? = null,
1063+
var onTokenRefresh: (suspend (GenericSpotifyApi) -> Unit)? = null
10461064
) {
10471065
fun build() =
10481066
if (enableAllOptions)
@@ -1057,7 +1075,8 @@ class SpotifyApiOptionsBuilder(
10571075
allowBulkRequests = true,
10581076
requestTimeoutMillis = requestTimeoutMillis,
10591077
json = json,
1060-
refreshTokenProducer = refreshTokenProducer
1078+
refreshTokenProducer = refreshTokenProducer,
1079+
onTokenRefresh = onTokenRefresh
10611080
)
10621081
else
10631082
SpotifyApiOptions(
@@ -1071,7 +1090,8 @@ class SpotifyApiOptionsBuilder(
10711090
allowBulkRequests,
10721091
requestTimeoutMillis,
10731092
json,
1074-
refreshTokenProducer
1093+
refreshTokenProducer,
1094+
onTokenRefresh
10751095
)
10761096
}
10771097

@@ -1089,6 +1109,7 @@ class SpotifyApiOptionsBuilder(
10891109
* @property allowBulkRequests Allow splitting too-large requests into smaller, allowable api requests. Default: true
10901110
* @property requestTimeoutMillis The maximum time, in milliseconds, before terminating an http request. Default: 100000ms
10911111
* @property refreshTokenProducer Provide if you want to use your own logic when refreshing a Spotify token.
1112+
* @property onTokenRefresh Provide if you want to act on token refresh event
10921113
*
10931114
*/
10941115

@@ -1103,7 +1124,8 @@ data class SpotifyApiOptions(
11031124
var allowBulkRequests: Boolean,
11041125
var requestTimeoutMillis: Long?,
11051126
var json: Json,
1106-
var refreshTokenProducer: (suspend (SpotifyApi<*, *>) -> Token)?
1127+
var refreshTokenProducer: (suspend (SpotifyApi<*, *>) -> Token)?,
1128+
var onTokenRefresh: (suspend (SpotifyApi<*, *>) -> Unit)?
11071129
)
11081130

11091131
@Deprecated("Name has been replaced by `options`", ReplaceWith("SpotifyApiOptions"))

0 commit comments

Comments
 (0)