diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6faa4f34..ed5aa244 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -5,9 +5,9 @@ name: CI Test Workflow on: push: - branches: [ master, dev ] + branches: [ main, dev ] pull_request: - branches: [ master, dev ] + branches: [ main, dev ] jobs: test_android_jvm_linux: diff --git a/build.gradle.kts b/build.gradle.kts index 3fd2595a..8fe9b43b 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,3 +1,5 @@ +@file:Suppress("UnstableApiUsage") + import org.jetbrains.dokka.gradle.DokkaTask import org.jetbrains.kotlin.gradle.plugin.KotlinJsCompilerType import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget @@ -11,9 +13,9 @@ plugins { id("io.codearte.nexus-staging") version "0.30.0" id("com.android.library") kotlin("plugin.serialization") - id("com.diffplug.spotless") version "6.3.0" + id("com.diffplug.spotless") version "6.7.2" id("com.moowork.node") version "1.3.1" - id("org.jetbrains.dokka") + id("org.jetbrains.dokka") version "1.7.10" } repositories { @@ -46,18 +48,18 @@ tasks.withType { } android { - compileSdkVersion(30) + compileSdk = 30 compileOptions { sourceCompatibility = JavaVersion.VERSION_1_8 targetCompatibility = JavaVersion.VERSION_1_8 } packagingOptions { - exclude("META-INF/*.md") + resources.excludes.add("META-INF/*.md") } defaultConfig { - minSdkVersion(23) - targetSdkVersion(30) - compileSdkVersion(30) + minSdk = 23 + targetSdk = 30 + setCompileSdkVersion(30) testInstrumentationRunner = "android.support.test.runner.AndroidJUnitRunner" } buildTypes { @@ -217,10 +219,12 @@ kotlin { val kotlinxDatetimeVersion = "0.3.1" sourceSets { - val serializationVersion = "1.3.2" - val ktorVersion = "1.6.8" - val korlibsVersion = "2.2.0" + val kotlinxDatetimeVersion = "0.4.0" + val serializationVersion = "1.3.3" + val ktorVersion = "2.0.3" val sparkVersion = "2.9.3" + + val korlibsVersion = "2.2.0" val androidSpotifyAuthVersion = "1.2.5" val androidCryptoVersion = "1.0.0" val coroutineMTVersion = "1.6.0-native-mt" @@ -231,7 +235,6 @@ kotlin { implementation("io.ktor:ktor-client-core:$ktorVersion") implementation("com.soywiz.korlibs.krypto:krypto:$korlibsVersion") implementation("com.soywiz.korlibs.korim:korim:$korlibsVersion") - } } @@ -298,7 +301,7 @@ kotlin { implementation("com.pnikosis:materialish-progress:1.7") implementation("io.ktor:ktor-client-okhttp:$ktorVersion") implementation("androidx.security:security-crypto:$androidCryptoVersion") - implementation("androidx.appcompat:appcompat:1.3.1") + implementation("androidx.appcompat:appcompat:1.4.2") } } diff --git a/gradle.properties b/gradle.properties index 9aa540ff..62201eee 100644 --- a/gradle.properties +++ b/gradle.properties @@ -19,5 +19,5 @@ kotlin.native.enableDependencyPropagation=false android.useAndroidX=true android.enableJetifier=true kotlin.mpp.enableGranularSourceSetsMetadata=true -kotlinVersion=1.6.10 +kotlinVersion=1.7.10 androidBuildToolsVersion=7.0.0 \ No newline at end of file diff --git a/src/androidMain/kotlin/com/adamratzman/spotify/auth/SpotifyDefaultCredentialStore.kt b/src/androidMain/kotlin/com/adamratzman/spotify/auth/SpotifyDefaultCredentialStore.kt index b272198c..ef12d100 100644 --- a/src/androidMain/kotlin/com/adamratzman/spotify/auth/SpotifyDefaultCredentialStore.kt +++ b/src/androidMain/kotlin/com/adamratzman/spotify/auth/SpotifyDefaultCredentialStore.kt @@ -38,6 +38,11 @@ public class SpotifyDefaultCredentialStore( applicationContext: Context ) { public companion object { + /** + * The key used with spotify scope string in [EncryptedSharedPreferences] + */ + public const val SpotifyScopeStringKey: String = "spotifyTokenScopes" + /** * The key used with spotify token expiry in [EncryptedSharedPreferences] */ @@ -106,6 +111,13 @@ public class SpotifyDefaultCredentialStore( get() = encryptedPreferences.getString(SpotifyRefreshTokenKey, null) set(value) = encryptedPreferences.edit().putString(SpotifyRefreshTokenKey, value).apply() + /** + * Get/set the Spotify scope string. + */ + public var spotifyScopeString: String? + get() = encryptedPreferences.getString(SpotifyScopeStringKey, null) + set(value) = encryptedPreferences.edit().putString(SpotifyScopeStringKey, value).apply() + /** * Get/set the current Spotify PKCE code verifier. */ @@ -128,7 +140,8 @@ public class SpotifyDefaultCredentialStore( accessToken, "Bearer", (tokenExpiresAt - System.currentTimeMillis()).toInt() / 1000, - refreshToken + refreshToken, + scopeString = spotifyScopeString ) } set(token) { @@ -138,10 +151,12 @@ public class SpotifyDefaultCredentialStore( spotifyRefreshToken = null credentialTypeStored = null + spotifyScopeString = null } else { spotifyAccessToken = token.accessToken spotifyTokenExpiresAt = token.expiresAt spotifyRefreshToken = token.refreshToken + spotifyScopeString = token.scopeString credentialTypeStored = if (token.refreshToken != null) CredentialType.Pkce else CredentialType.ImplicitGrant diff --git a/src/androidMain/kotlin/com/adamratzman/spotify/auth/implicit/AbstractSpotifyAppCompatImplicitLoginActivity.kt b/src/androidMain/kotlin/com/adamratzman/spotify/auth/implicit/AbstractSpotifyAppCompatImplicitLoginActivity.kt index dd01e761..47e4dc74 100644 --- a/src/androidMain/kotlin/com/adamratzman/spotify/auth/implicit/AbstractSpotifyAppCompatImplicitLoginActivity.kt +++ b/src/androidMain/kotlin/com/adamratzman/spotify/auth/implicit/AbstractSpotifyAppCompatImplicitLoginActivity.kt @@ -24,6 +24,7 @@ public abstract class AbstractSpotifyAppCompatImplicitLoginActivity : SpotifyImp triggerLoginActivity() } + @Suppress("OVERRIDE_DEPRECATION") override fun onActivityResult(requestCode: Int, resultCode: Int, intent: Intent?) { super.onActivityResult(requestCode, resultCode, intent) processActivityResult(requestCode, resultCode, intent) diff --git a/src/commonMain/kotlin/com.adamratzman.spotify/SpotifyApiBuilder.kt b/src/commonMain/kotlin/com.adamratzman.spotify/SpotifyApiBuilder.kt index d1d0811f..91ea6468 100644 --- a/src/commonMain/kotlin/com.adamratzman.spotify/SpotifyApiBuilder.kt +++ b/src/commonMain/kotlin/com.adamratzman.spotify/SpotifyApiBuilder.kt @@ -9,7 +9,7 @@ import com.adamratzman.spotify.models.serialization.nonstrictJson import com.adamratzman.spotify.models.serialization.toObject import com.adamratzman.spotify.utils.urlEncodeBase64String import com.soywiz.krypto.SHA256 -import io.ktor.client.features.ServerResponseException +import io.ktor.client.plugins.ServerResponseException import io.ktor.utils.io.core.toByteArray import kotlinx.coroutines.CancellationException import kotlinx.serialization.json.Json diff --git a/src/commonMain/kotlin/com.adamratzman.spotify/SpotifyExceptions.kt b/src/commonMain/kotlin/com.adamratzman.spotify/SpotifyExceptions.kt index 5eaeb9b4..0446b0c1 100644 --- a/src/commonMain/kotlin/com.adamratzman.spotify/SpotifyExceptions.kt +++ b/src/commonMain/kotlin/com.adamratzman.spotify/SpotifyExceptions.kt @@ -3,7 +3,7 @@ package com.adamratzman.spotify import com.adamratzman.spotify.models.AuthenticationError import com.adamratzman.spotify.models.ErrorObject -import io.ktor.client.features.ResponseException +import io.ktor.client.plugins.ResponseException public sealed class SpotifyException(message: String, cause: Throwable? = null) : Exception(message, cause) { public abstract class UnNullableException(message: String) : SpotifyException(message) diff --git a/src/commonMain/kotlin/com.adamratzman.spotify/http/HttpConnection.kt b/src/commonMain/kotlin/com.adamratzman.spotify/http/HttpConnection.kt index 6c99c703..50233d95 100644 --- a/src/commonMain/kotlin/com.adamratzman.spotify/http/HttpConnection.kt +++ b/src/commonMain/kotlin/com.adamratzman.spotify/http/HttpConnection.kt @@ -11,14 +11,14 @@ import com.adamratzman.spotify.models.ErrorResponse import com.adamratzman.spotify.models.SpotifyRatelimitedException import com.adamratzman.spotify.models.serialization.nonstrictJson import com.adamratzman.spotify.models.serialization.toObject -import com.soywiz.korio.dynamic.KDynamic.Companion.toLong import io.ktor.client.HttpClient -import io.ktor.client.features.ResponseException +import io.ktor.client.plugins.ResponseException import io.ktor.client.request.HttpRequestBuilder import io.ktor.client.request.header import io.ktor.client.request.request +import io.ktor.client.request.setBody import io.ktor.client.request.url -import io.ktor.client.statement.readText +import io.ktor.client.statement.bodyAsText import io.ktor.http.ContentType import io.ktor.http.HttpMethod import io.ktor.http.content.ByteArrayContent @@ -62,19 +62,21 @@ public class HttpConnection constructor( url(this@HttpConnection.url) method = this@HttpConnection.method.externalMethod - body = when (this@HttpConnection.method) { - HttpRequestMethod.DELETE -> { - bodyString.toByteArrayContent() ?: body - } - HttpRequestMethod.PUT, HttpRequestMethod.POST -> { - val contentString = if (contentType == ContentType.Application.FormUrlEncoded) { - bodyMap?.map { "${it.key}=${it.value}" }?.joinToString("&") ?: bodyString - } else bodyString + setBody( + when (this@HttpConnection.method) { + HttpRequestMethod.DELETE -> { + bodyString.toByteArrayContent() ?: body + } + HttpRequestMethod.PUT, HttpRequestMethod.POST -> { + val contentString = if (contentType == ContentType.Application.FormUrlEncoded) { + bodyMap?.map { "${it.key}=${it.value}" }?.joinToString("&") ?: bodyString + } else bodyString - contentString.toByteArrayContent() ?: ByteArrayContent("".toByteArray(), contentType) + contentString.toByteArrayContent() ?: ByteArrayContent("".toByteArray(), contentType) + } + else -> body } - else -> body - } + ) // let additionalHeaders overwrite headers val allHeaders = if (additionalHeaders == null) this@HttpConnection.headers @@ -92,7 +94,7 @@ public class HttpConnection constructor( val httpRequest = buildRequest(additionalHeaders) if (api?.spotifyApiOptions?.enableDebugMode == true) println("DEBUG MODE: Request: $this") try { - return httpClient.request(httpRequest).let { response -> + return httpClient.request(httpRequest).let { response -> val respCode = response.status.value if (respCode in 500..599 && (retryIfInternalServerErrorLeft == null || retryIfInternalServerErrorLeft == -1 || retryIfInternalServerErrorLeft > 0)) { @@ -116,7 +118,7 @@ public class HttpConnection constructor( } else throw SpotifyRatelimitedException(ratelimit) } - val body: String = response.readText() + val body: String = response.bodyAsText() if (api?.spotifyApiOptions?.enableDebugMode == true) println("DEBUG MODE: $body") if (respCode == 401 && body.contains("access token") && api?.spotifyApiOptions?.automaticRefresh == true) { @@ -145,7 +147,7 @@ public class HttpConnection constructor( } catch (e: CancellationException) { throw e } catch (e: ResponseException) { - val errorBody = e.response.readText() + val errorBody = e.response.bodyAsText() if (api?.spotifyApiOptions?.enableDebugMode == true) println("DEBUG MODE: $errorBody") try { val respCode = e.response.status.value