@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 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 (currently iOS only, Android coming soon)
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 generally recommended to [implement a token swap endpoint](#token-swap) for this reason.
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.0",
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
+ }