@wwdrew/expo-spotify-sdk 0.4.0 → 0.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/android/build.gradle +48 -0
- package/android/src/main/AndroidManifest.xml +11 -0
- package/android/src/main/java/expo/modules/spotifysdk/ExpoSpotifySDKModule.kt +180 -0
- package/package.json +4 -4
- package/external/android/SpotifySDK/auth-lib/spotify-auth-release-2.1.0.aar +0 -0
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@ An Expo Module for the native [iOS](https://github.com/spotify/ios-sdk/) and [An
|
|
|
4
4
|
|
|
5
5
|
## Supported Features
|
|
6
6
|
|
|
7
|
-
- Authentication
|
|
7
|
+
- Authentication
|
|
8
8
|
|
|
9
9
|
More to come...
|
|
10
10
|
|
|
@@ -52,7 +52,7 @@ authenticateAsync(config: SpotifyConfig): Promise<SpotifySession>
|
|
|
52
52
|
|
|
53
53
|
Starts the authentication process. Requires an array of OAuth scopes. If the Spotify app is installed on the target device it will interact directly with it, otherwise it will open a web view to authenticate with the Spotify website.
|
|
54
54
|
|
|
55
|
-
**Note for Android:** If not providing a token swap or refresh URL, the Spotify session response access token will expire after 60 minutes and will not include a refresh token. This is due to a limitation in the Android Spotify SDK. It's
|
|
55
|
+
**Note for Android:** If not providing a token swap or refresh URL, the Spotify session response access token will expire after 60 minutes and will not include a refresh token. This is due to a limitation in the Android Spotify SDK. It's recommended to [implement a token swap endpoint](#token-swap) for this reason.
|
|
56
56
|
|
|
57
57
|
### Parameters
|
|
58
58
|
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
apply plugin: 'com.android.library'
|
|
2
|
+
|
|
3
|
+
group = 'expo.modules.spotifysdk'
|
|
4
|
+
version = '0.1.1'
|
|
5
|
+
|
|
6
|
+
def expoModulesCorePlugin = new File(project(":expo-modules-core").projectDir.absolutePath, "ExpoModulesCorePlugin.gradle")
|
|
7
|
+
apply from: expoModulesCorePlugin
|
|
8
|
+
applyKotlinExpoModulesCorePlugin()
|
|
9
|
+
useCoreDependencies()
|
|
10
|
+
useExpoPublishing()
|
|
11
|
+
|
|
12
|
+
// If you want to use the managed Android SDK versions from expo-modules-core, set this to true.
|
|
13
|
+
// The Android SDK versions will be bumped from time to time in SDK releases and may introduce breaking changes in your module code.
|
|
14
|
+
// Most of the time, you may like to manage the Android SDK versions yourself.
|
|
15
|
+
def useManagedAndroidSdkVersions = false
|
|
16
|
+
if (useManagedAndroidSdkVersions) {
|
|
17
|
+
useDefaultAndroidSdkVersions()
|
|
18
|
+
} else {
|
|
19
|
+
buildscript {
|
|
20
|
+
// Simple helper that allows the root project to override versions declared by this library.
|
|
21
|
+
ext.safeExtGet = { prop, fallback ->
|
|
22
|
+
rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
project.android {
|
|
26
|
+
compileSdkVersion safeExtGet("compileSdkVersion", 34)
|
|
27
|
+
defaultConfig {
|
|
28
|
+
minSdkVersion safeExtGet("minSdkVersion", 21)
|
|
29
|
+
targetSdkVersion safeExtGet("targetSdkVersion", 34)
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
android {
|
|
35
|
+
namespace "expo.modules.spotifysdk"
|
|
36
|
+
defaultConfig {
|
|
37
|
+
versionCode 1
|
|
38
|
+
versionName "0.1.1"
|
|
39
|
+
}
|
|
40
|
+
lintOptions {
|
|
41
|
+
abortOnError false
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
dependencies {
|
|
46
|
+
implementation 'com.squareup.okhttp3:okhttp:4.9.2'
|
|
47
|
+
implementation "com.spotify.android:auth:2.1.2"
|
|
48
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="utf-8"?>
|
|
2
|
+
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
|
3
|
+
<application>
|
|
4
|
+
<meta-data
|
|
5
|
+
android:name="spotifyClientId"
|
|
6
|
+
android:value="${spotifyClientId}" />
|
|
7
|
+
<meta-data
|
|
8
|
+
android:name="spotifyRedirectUri"
|
|
9
|
+
android:value="${spotifyRedirectUri}" />
|
|
10
|
+
</application>
|
|
11
|
+
</manifest>
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
package expo.modules.spotifysdk
|
|
2
|
+
|
|
3
|
+
import android.content.pm.PackageManager
|
|
4
|
+
import expo.modules.kotlin.Promise
|
|
5
|
+
import expo.modules.kotlin.modules.Module
|
|
6
|
+
import expo.modules.kotlin.modules.ModuleDefinition
|
|
7
|
+
import com.spotify.sdk.android.auth.AuthorizationClient
|
|
8
|
+
import com.spotify.sdk.android.auth.AuthorizationRequest
|
|
9
|
+
import com.spotify.sdk.android.auth.AuthorizationResponse
|
|
10
|
+
import com.spotify.sdk.android.auth.app.SpotifyNativeAuthUtil
|
|
11
|
+
import expo.modules.kotlin.exception.Exceptions
|
|
12
|
+
import expo.modules.kotlin.records.Field
|
|
13
|
+
import expo.modules.kotlin.records.Record
|
|
14
|
+
|
|
15
|
+
import okhttp3.OkHttpClient
|
|
16
|
+
import okhttp3.FormBody
|
|
17
|
+
import okhttp3.Request
|
|
18
|
+
import okhttp3.Callback
|
|
19
|
+
import okhttp3.Call
|
|
20
|
+
import okhttp3.Response
|
|
21
|
+
|
|
22
|
+
import java.io.IOException
|
|
23
|
+
import org.json.JSONObject
|
|
24
|
+
|
|
25
|
+
class SpotifyConfigOptions : Record {
|
|
26
|
+
@Field
|
|
27
|
+
val scopes: List<String> = emptyList()
|
|
28
|
+
|
|
29
|
+
@Field
|
|
30
|
+
val tokenSwapURL: String? = null
|
|
31
|
+
|
|
32
|
+
@Field
|
|
33
|
+
val tokenRefreshURL: String? = null
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
class ExpoSpotifySDKModule : Module() {
|
|
37
|
+
|
|
38
|
+
private val requestCode = 2095
|
|
39
|
+
private var requestConfig: SpotifyConfigOptions? = null
|
|
40
|
+
private var authPromise: Promise? = null
|
|
41
|
+
private val context
|
|
42
|
+
get() = appContext.reactContext ?: throw Exceptions.ReactContextLost()
|
|
43
|
+
private val currentActivity
|
|
44
|
+
get() = appContext.currentActivity ?: throw Exceptions.MissingActivity()
|
|
45
|
+
|
|
46
|
+
override fun definition() = ModuleDefinition {
|
|
47
|
+
|
|
48
|
+
Name("ExpoSpotifySDK")
|
|
49
|
+
|
|
50
|
+
Function("isAvailable") {
|
|
51
|
+
return@Function SpotifyNativeAuthUtil.isSpotifyInstalled(context)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
AsyncFunction("authenticateAsync") { config: SpotifyConfigOptions, promise: Promise ->
|
|
55
|
+
try {
|
|
56
|
+
val packageInfo =
|
|
57
|
+
context.packageManager.getPackageInfo(context.packageName, PackageManager.GET_META_DATA)
|
|
58
|
+
val applicationInfo = packageInfo.applicationInfo
|
|
59
|
+
val metaData = applicationInfo.metaData
|
|
60
|
+
val clientId = metaData.getString("spotifyClientId")
|
|
61
|
+
val redirectUri = metaData.getString("spotifyRedirectUri")
|
|
62
|
+
|
|
63
|
+
requestConfig = config
|
|
64
|
+
|
|
65
|
+
if (clientId == null || redirectUri == null) {
|
|
66
|
+
promise.reject(
|
|
67
|
+
"ERR_EXPO_SPOTIFY_SDK",
|
|
68
|
+
"Missing Spotify configuration in AndroidManifest.xml. Ensure SPOTIFY_CLIENT_ID and SPOTIFY_REDIRECT_URI are set.",
|
|
69
|
+
null
|
|
70
|
+
)
|
|
71
|
+
return@AsyncFunction
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
val responseType = if (config.tokenSwapURL != null || config.tokenRefreshURL != null) {
|
|
75
|
+
AuthorizationResponse.Type.CODE
|
|
76
|
+
} else {
|
|
77
|
+
AuthorizationResponse.Type.TOKEN
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
val request = AuthorizationRequest.Builder(
|
|
81
|
+
clientId,
|
|
82
|
+
responseType,
|
|
83
|
+
redirectUri
|
|
84
|
+
)
|
|
85
|
+
.setScopes(config.scopes.toTypedArray())
|
|
86
|
+
.build()
|
|
87
|
+
|
|
88
|
+
authPromise = promise
|
|
89
|
+
AuthorizationClient.openLoginActivity(currentActivity, requestCode, request)
|
|
90
|
+
|
|
91
|
+
} catch (e: PackageManager.NameNotFoundException) {
|
|
92
|
+
promise.reject(
|
|
93
|
+
"ERR_EXPO_SPOTIFY_SDK",
|
|
94
|
+
"Missing Spotify configuration in AndroidManifest.xml",
|
|
95
|
+
e
|
|
96
|
+
)
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
OnActivityResult { _, payload ->
|
|
101
|
+
if (payload.requestCode == requestCode) {
|
|
102
|
+
val authResponse = AuthorizationClient.getResponse(payload.resultCode, payload.data)
|
|
103
|
+
|
|
104
|
+
when (authResponse.type) {
|
|
105
|
+
AuthorizationResponse.Type.TOKEN -> {
|
|
106
|
+
val expirationDate = System.currentTimeMillis() + authResponse.expiresIn * 1000
|
|
107
|
+
|
|
108
|
+
authPromise?.resolve(
|
|
109
|
+
mapOf(
|
|
110
|
+
"accessToken" to authResponse.accessToken,
|
|
111
|
+
"refreshToken" to null, // Spotify SDK does not return refresh token
|
|
112
|
+
"expirationDate" to expirationDate,
|
|
113
|
+
"scope" to requestConfig?.scopes
|
|
114
|
+
)
|
|
115
|
+
)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
AuthorizationResponse.Type.CODE -> {
|
|
119
|
+
val client = OkHttpClient()
|
|
120
|
+
val requestBody = FormBody.Builder()
|
|
121
|
+
.add("code", authResponse.code)
|
|
122
|
+
.build()
|
|
123
|
+
|
|
124
|
+
val request = Request.Builder()
|
|
125
|
+
.url(requestConfig?.tokenSwapURL!!)
|
|
126
|
+
.post(requestBody)
|
|
127
|
+
.header("Content-Type", "application/x-www-form-urlencoded")
|
|
128
|
+
.build()
|
|
129
|
+
|
|
130
|
+
client.newCall(request).enqueue(object : Callback {
|
|
131
|
+
override fun onFailure(call: Call, e: IOException) {
|
|
132
|
+
authPromise?.reject("ERR_EXPO_SPOTIFY_SDK", e.message, e)
|
|
133
|
+
authPromise = null
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
override fun onResponse(call: Call, response: Response) {
|
|
137
|
+
if (!response.isSuccessful) {
|
|
138
|
+
authPromise?.reject("ERR_EXPO_SPOTIFY_SDK", "Failed to swap code for token", null)
|
|
139
|
+
authPromise = null
|
|
140
|
+
return
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
response.body?.string()?.let { body ->
|
|
144
|
+
val json = JSONObject(body)
|
|
145
|
+
val accessToken = json.getString("access_token")
|
|
146
|
+
val refreshToken = json.getString("refresh_token")
|
|
147
|
+
val expiresIn = json.getInt("expires_in")
|
|
148
|
+
val scope = json.getString("scope")
|
|
149
|
+
val expirationDate = System.currentTimeMillis() + expiresIn * 1000
|
|
150
|
+
|
|
151
|
+
authPromise?.resolve(
|
|
152
|
+
mapOf(
|
|
153
|
+
"accessToken" to accessToken,
|
|
154
|
+
"refreshToken" to refreshToken,
|
|
155
|
+
"expirationDate" to expirationDate,
|
|
156
|
+
"scope" to scope.split(' ')
|
|
157
|
+
)
|
|
158
|
+
)
|
|
159
|
+
} ?: run {
|
|
160
|
+
authPromise?.reject("ERR_EXPO_SPOTIFY_SDK", "Empty response body", null)
|
|
161
|
+
}
|
|
162
|
+
authPromise = null
|
|
163
|
+
}
|
|
164
|
+
})
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
AuthorizationResponse.Type.ERROR -> {
|
|
168
|
+
authPromise?.reject("ERR_EXPO_SPOTIFY_SDK", authResponse.error, null)
|
|
169
|
+
authPromise = null
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
else -> {
|
|
173
|
+
authPromise?.reject("ERR_EXPO_SPOTIFY_SDK", "Unknown response type", null)
|
|
174
|
+
authPromise = null
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wwdrew/expo-spotify-sdk",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.2",
|
|
4
4
|
"description": "Expo Module for native Spotify SDK",
|
|
5
5
|
"main": "build/index.js",
|
|
6
6
|
"types": "build/index.d.ts",
|
|
@@ -25,11 +25,11 @@
|
|
|
25
25
|
"files": [
|
|
26
26
|
"plugin/build",
|
|
27
27
|
"build",
|
|
28
|
+
"android",
|
|
28
29
|
"ios",
|
|
29
30
|
"app.plugin.js",
|
|
30
31
|
"expo-module.config.json",
|
|
31
|
-
"server.js"
|
|
32
|
-
"external/android/SpotifySDK/auth-lib/spotify-auth-release-2.1.0.aar"
|
|
32
|
+
"server.js"
|
|
33
33
|
],
|
|
34
34
|
"jest": {
|
|
35
35
|
"preset": "expo-module-scripts"
|
|
@@ -51,4 +51,4 @@
|
|
|
51
51
|
"react": "*",
|
|
52
52
|
"react-native": "*"
|
|
53
53
|
}
|
|
54
|
-
}
|
|
54
|
+
}
|
|
Binary file
|