Skip to content

Commit 88e3cf0

Browse files
authored
Merge pull request #274 from adamint/dev
Add SpotifyRestAction, update kotlin to 1.4.32, rename com.adamratzman.spotify.endpoints.public to com.adamratzman.endpoints.pub
2 parents a378314 + c4131ff commit 88e3cf0

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+2501
-67
lines changed

build.gradle.kts

+4-4
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import org.jetbrains.kotlin.gradle.targets.js.webpack.KotlinWebpackOutput.Target
55

66
plugins {
77
// id("lt.petuska.npm.publish") version "1.1.2"
8-
kotlin("multiplatform") version "1.4.31"
8+
kotlin("multiplatform") version "1.4.32"
99
`maven-publish`
1010
signing
1111
id("io.codearte.nexus-staging") version "0.22.0"
@@ -29,7 +29,7 @@ buildscript {
2929
}
3030
dependencies {
3131
classpath("com.android.tools.build:gradle:3.5.4")
32-
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.4.31")
32+
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.4.32")
3333
}
3434
}
3535

@@ -216,8 +216,8 @@ kotlin {
216216
val korlibsVersion = "2.0.6"
217217
val sparkVersion = "2.9.3"
218218
val androidSpotifyAuthVersion = "1.2.3"
219-
val androidCryptoVersion = "1.1.0-alpha03"
220-
val coroutineMTVersion = "1.4.2-native-mt"
219+
val androidCryptoVersion = "1.0.0-rc04"
220+
val coroutineMTVersion = "1.4.3-native-mt"
221221

222222
val commonMain by getting {
223223
dependencies {

settings.gradle.kts

+4-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
pluginManagement {
2-
val mainKotlinVersion = "1.4.31"
2+
val mainKotlinVersion = "1.4.32"
33

44
resolutionStrategy {
55
eachPlugin {
@@ -29,4 +29,6 @@ pluginManagement {
2929
}
3030

3131
rootProject.name = "spotify-web-api-kotlin"
32-
32+
include("java-interop-basic-sample")
33+
findProject(":java-interop-basic-sample")?.name = "java-interop-basic"
34+
include("java-interop-sample")

src/androidMain/kotlin/com/adamratzman/spotify/utils/PlatformUtils.kt

+6
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import android.app.Activity
55
import android.content.Context
66
import android.util.Log
77
import android.widget.Toast
8+
import com.soywiz.korio.async.runBlockingNoJs
9+
import kotlinx.coroutines.CoroutineScope
810
import java.net.URLEncoder
911

1012
internal actual fun String.encodeUrl() = URLEncoder.encode(this, "UTF-8")!!
@@ -33,3 +35,7 @@ internal fun toast(context: Context?, message: String?, duration: Int = Toast.LE
3335
internal fun logToConsole(message: String) {
3436
Log.i("spotify-web-api-kotlin", message)
3537
}
38+
39+
public actual fun <T> runBlockingOnJvmAndNative(block: suspend CoroutineScope.() -> T): T {
40+
return runBlockingNoJs { block() }
41+
}

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

+94-16
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,16 @@ import com.adamratzman.spotify.endpoints.client.ClientPlaylistApi
1313
import com.adamratzman.spotify.endpoints.client.ClientProfileApi
1414
import com.adamratzman.spotify.endpoints.client.ClientSearchApi
1515
import com.adamratzman.spotify.endpoints.client.ClientShowApi
16-
import com.adamratzman.spotify.endpoints.public.AlbumApi
17-
import com.adamratzman.spotify.endpoints.public.ArtistApi
18-
import com.adamratzman.spotify.endpoints.public.BrowseApi
19-
import com.adamratzman.spotify.endpoints.public.EpisodeApi
20-
import com.adamratzman.spotify.endpoints.public.FollowingApi
21-
import com.adamratzman.spotify.endpoints.public.PlaylistApi
22-
import com.adamratzman.spotify.endpoints.public.SearchApi
23-
import com.adamratzman.spotify.endpoints.public.ShowApi
24-
import com.adamratzman.spotify.endpoints.public.TrackApi
25-
import com.adamratzman.spotify.endpoints.public.UserApi
16+
import com.adamratzman.spotify.endpoints.pub.AlbumApi
17+
import com.adamratzman.spotify.endpoints.pub.ArtistApi
18+
import com.adamratzman.spotify.endpoints.pub.BrowseApi
19+
import com.adamratzman.spotify.endpoints.pub.EpisodeApi
20+
import com.adamratzman.spotify.endpoints.pub.FollowingApi
21+
import com.adamratzman.spotify.endpoints.pub.PlaylistApi
22+
import com.adamratzman.spotify.endpoints.pub.SearchApi
23+
import com.adamratzman.spotify.endpoints.pub.ShowApi
24+
import com.adamratzman.spotify.endpoints.pub.TrackApi
25+
import com.adamratzman.spotify.endpoints.pub.UserApi
2626
import com.adamratzman.spotify.http.CacheState
2727
import com.adamratzman.spotify.http.HttpConnection
2828
import com.adamratzman.spotify.http.HttpHeader
@@ -37,8 +37,8 @@ import com.adamratzman.spotify.models.serialization.nonstrictJson
3737
import com.adamratzman.spotify.models.serialization.toObject
3838
import com.adamratzman.spotify.utils.asList
3939
import com.adamratzman.spotify.utils.base64ByteEncode
40-
import kotlin.jvm.JvmOverloads
4140
import kotlinx.serialization.json.Json
41+
import kotlin.jvm.JvmOverloads
4242

4343
/**
4444
* Represents an instance of the Spotify API client, with common
@@ -212,6 +212,20 @@ public sealed class SpotifyApi<T : SpotifyApi<T, B>, B : ISpotifyApiBuilder<T, B
212212
}
213213
}
214214

215+
/**
216+
* Tests whether the current [token] is actually valid. By default, an endpoint is called *once* to verify
217+
* validity.
218+
*
219+
* @param makeTestRequest Whether to also make an endpoint request to verify authentication.
220+
*
221+
* @return [TokenValidityResponse] containing whether this token is valid, and if not, an Exception explaining why
222+
*/
223+
@JvmOverloads
224+
public fun isTokenValidRestAction(makeTestRequest: Boolean = true): SpotifyRestAction<TokenValidityResponse> =
225+
SpotifyRestAction {
226+
isTokenValid(makeTestRequest)
227+
}
228+
215229
/**
216230
* If the method used to create the [token] supports token refresh and
217231
* the information in [token] is accurate, attempt to refresh the token
@@ -224,7 +238,18 @@ public sealed class SpotifyApi<T : SpotifyApi<T, B>, B : ISpotifyApiBuilder<T, B
224238
this@SpotifyApi.token = this
225239
spotifyApiOptions.onTokenRefresh?.invoke(this@SpotifyApi)
226240
spotifyApiOptions.afterTokenRefresh?.invoke(this@SpotifyApi)
227-
} ?: throw SpotifyException.ReAuthenticationNeededException(IllegalStateException("The refreshTokenProducer is null."))
241+
}
242+
?: throw SpotifyException.ReAuthenticationNeededException(IllegalStateException("The refreshTokenProducer is null."))
243+
244+
/**
245+
* If the method used to create the [token] supports token refresh and
246+
* the information in [token] is accurate, attempt to refresh the token
247+
*
248+
* @return The old access token if refresh was successful
249+
* @throws BadRequestException if refresh fails
250+
* @throws IllegalStateException if [SpotifyApiOptions.refreshTokenProducer] is null
251+
*/
252+
public fun refreshTokenRestAction(): SpotifyRestAction<Token> = SpotifyRestAction { refreshToken() }
228253

229254
public companion object {
230255
internal suspend fun testTokenValidity(api: GenericSpotifyApi) {
@@ -327,6 +352,22 @@ public sealed class SpotifyApi<T : SpotifyApi<T, B>, B : ISpotifyApiBuilder<T, B
327352

328353
throw BadRequestException(response.body.toObject(AuthenticationError.serializer(), null, json))
329354
}
355+
356+
/**
357+
*
358+
* Get an application token (can only access public methods) that can be used to instantiate a new [SpotifyAppApi]
359+
*
360+
* @param clientId Spotify [client id](https://developer.spotify.com/documentation/general/guides/app-settings/)
361+
* @param clientSecret Spotify [client secret](https://developer.spotify.com/documentation/general/guides/app-settings/)
362+
* @param api The Spotify Api instance, or null if one doesn't exist yet
363+
* @param json The json instance that will deserialize the response.
364+
*/
365+
public fun getCredentialedTokenRestAction(
366+
clientId: String,
367+
clientSecret: String,
368+
api: GenericSpotifyApi?,
369+
json: Json = api?.spotifyApiOptions?.json ?: Json.Default
370+
): SpotifyRestAction<Token> = SpotifyRestAction { getCredentialedToken(clientId, clientSecret, api, json) }
330371
}
331372
}
332373

@@ -501,18 +542,23 @@ public open class SpotifyClientApi(
501542
*/
502543
public val player: ClientPlayerApi = ClientPlayerApi(this)
503544

504-
private lateinit var userIdBacking: String
545+
private var userIdBacking: String? = null
505546

506547
private suspend fun initiatizeUserIdBacking(): String {
507548
userIdBacking = users.getClientProfile().id
508-
return userIdBacking
549+
return userIdBacking!!
509550
}
510551

511552
/**
512553
* The Spotify user id to which the api instance is connected
513554
*/
514555
public suspend fun getUserId(): String =
515-
if (::userIdBacking.isInitialized) userIdBacking else initiatizeUserIdBacking()
556+
if (userIdBacking != null) userIdBacking!! else initiatizeUserIdBacking()
557+
558+
/**
559+
* The Spotify user id to which the api instance is connected
560+
*/
561+
public fun getUserIdRestAction(): SpotifyRestAction<String> = SpotifyRestAction { getUserId() }
516562

517563
/**
518564
* Stop all automatic functions like refreshToken or clearCache and shut down the scheduled
@@ -574,6 +620,12 @@ public open class SpotifyClientApi(
574620
*/
575621
public suspend fun hasScope(scope: SpotifyScope): Boolean? = hasScopes(scope)
576622

623+
/**
624+
* Whether the current access token allows access to scope [scope]
625+
*/
626+
public fun hasScopeRestAction(scope: SpotifyScope): SpotifyRestAction<Boolean?> =
627+
SpotifyRestAction { hasScope(scope) }
628+
577629
/**
578630
* Whether the current access token allows access to all of the provided scopes
579631
*/
@@ -583,13 +635,21 @@ public open class SpotifyClientApi(
583635
token.scopes?.contains(scope) == true &&
584636
scopes.all { token.scopes?.contains(it) == true }
585637

638+
/**
639+
* Whether the current access token allows access to all of the provided scopes
640+
*/
641+
public fun hasScopesRestAction(scope: SpotifyScope, vararg scopes: SpotifyScope): SpotifyRestAction<Boolean?> =
642+
SpotifyRestAction {
643+
hasScopes(scope, *scopes)
644+
}
645+
586646
public companion object {
587647
private val defaultClientApiTokenRefreshProducer: suspend (GenericSpotifyApi) -> Token = { api ->
588648
api as SpotifyClientApi
589649

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

592-
refreshSpotifyClientToken(api.clientId, api.clientSecret, api.token.refreshToken, api.usesPkceAuth)
652+
refreshSpotifyClientToken(api.clientId, api.clientSecret, api.token.refreshToken, api.usesPkceAuth)
593653
}
594654
}
595655
}
@@ -707,3 +767,21 @@ public suspend fun refreshSpotifyClientToken(
707767
)
708768
)
709769
}
770+
771+
/**
772+
* Refresh a Spotify client token
773+
*
774+
* @param clientId The Spotify application client id.
775+
* @param clientSecret The Spotify application client secret (not needed for PKCE).
776+
* @param refreshToken The refresh token.
777+
* @param usesPkceAuth Whether this token was created using PKCE auth or not.
778+
*/
779+
public fun refreshSpotifyClientTokenRestAction(
780+
clientId: String,
781+
clientSecret: String?,
782+
refreshToken: String?,
783+
usesPkceAuth: Boolean
784+
): SpotifyRestAction<Token> =
785+
SpotifyRestAction { refreshSpotifyClientToken(clientId, clientSecret, refreshToken, usesPkceAuth) }
786+
787+

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

+31
Original file line numberDiff line numberDiff line change
@@ -691,11 +691,24 @@ public class SpotifyApiBuilder(
691691
else buildCredentialed()
692692
}
693693

694+
/**
695+
* Create a [SpotifyApi] instance with the given [SpotifyApiBuilder] parameters and the type -
696+
* [AuthorizationType.CLIENT] for client authentication, or otherwise [AuthorizationType.APPLICATION]
697+
*/
698+
public fun buildRestAction(type: AuthorizationType): SpotifyRestAction<GenericSpotifyApi> = SpotifyRestAction {
699+
build(type)
700+
}
701+
694702
/**
695703
* Create a new [SpotifyAppApi] that only has access to *public* endpoints and data
696704
*/
697705
public suspend fun buildPublic(): SpotifyAppApi = buildCredentialed()
698706

707+
/**
708+
* Create a new [SpotifyAppApi] that only has access to *public* endpoints and data
709+
*/
710+
public fun buildPublicRestAction(): SpotifyRestAction<SpotifyAppApi> = SpotifyRestAction{ buildPublic() }
711+
699712
/**
700713
* Create a new [SpotifyAppApi] that only has access to *public* endpoints and data
701714
*/
@@ -708,6 +721,11 @@ public class SpotifyApiBuilder(
708721
options(options)
709722
}.build()
710723

724+
/**
725+
* Create a new [SpotifyAppApi] that only has access to *public* endpoints and data
726+
*/
727+
public fun buildCredentialedRestAction(): SpotifyRestAction<SpotifyAppApi> = SpotifyRestAction { buildCredentialed() }
728+
711729
/**
712730
* Create a new [SpotifyClientApi] that has access to public endpoints, in addition to endpoints
713731
* requiring scopes contained in the client authorization request
@@ -721,6 +739,12 @@ public class SpotifyApiBuilder(
721739
authorization(authorization)
722740
options(options)
723741
}.build()
742+
743+
/**
744+
* Create a new [SpotifyClientApi] that has access to public endpoints, in addition to endpoints
745+
* requiring scopes contained in the client authorization request
746+
*/
747+
public fun buildClientRestAction(): SpotifyRestAction<SpotifyClientApi> = SpotifyRestAction { buildClient() }
724748
}
725749

726750
/**
@@ -811,6 +835,13 @@ public interface ISpotifyApiBuilder<T : SpotifyApi<T, B>, B : ISpotifyApiBuilder
811835
* Build the [T] by provided information
812836
*/
813837
public suspend fun build(enableDefaultTokenRefreshProducerIfNoneExists: Boolean = true): T
838+
839+
/**
840+
* Build the [T] by provided information
841+
*/
842+
public fun buildRestAction(enableDefaultTokenRefreshProducerIfNoneExists: Boolean = true): SpotifyRestAction<T> = SpotifyRestAction {
843+
build(enableDefaultTokenRefreshProducerIfNoneExists)
844+
}
814845
}
815846

816847
/**

0 commit comments

Comments
 (0)