@wwdrew/expo-spotify-sdk 0.7.1 → 1.0.0
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 +388 -108
- package/android/build.gradle +10 -0
- package/android/consumer-rules.pro +5 -0
- package/android/libs/SETUP.md +29 -0
- package/android/src/main/java/expo/modules/spotifysdk/ExpoSpotifySDKModule.kt +149 -12
- package/android/src/main/java/expo/modules/spotifysdk/SpotifyAppRemoteCoordinator.kt +556 -0
- package/android/src/main/java/expo/modules/spotifysdk/SpotifyConfig.kt +1 -0
- package/android/src/main/java/expo/modules/spotifysdk/SpotifyErrors.kt +112 -0
- package/build/ExpoSpotifySDK.types.d.ts +12 -0
- package/build/ExpoSpotifySDK.types.d.ts.map +1 -1
- package/build/ExpoSpotifySDK.types.js.map +1 -1
- package/build/ExpoSpotifySDKModule.web.d.ts +23 -0
- package/build/ExpoSpotifySDKModule.web.d.ts.map +1 -1
- package/build/ExpoSpotifySDKModule.web.js +74 -5
- package/build/ExpoSpotifySDKModule.web.js.map +1 -1
- package/build/app-remote/error.d.ts +8 -0
- package/build/app-remote/error.d.ts.map +1 -0
- package/build/app-remote/error.js +10 -0
- package/build/app-remote/error.js.map +1 -0
- package/build/app-remote/index.d.ts +79 -0
- package/build/app-remote/index.d.ts.map +1 -0
- package/build/app-remote/index.js +110 -0
- package/build/app-remote/index.js.map +1 -0
- package/build/auth/error.d.ts +8 -0
- package/build/auth/error.d.ts.map +1 -0
- package/build/auth/error.js +10 -0
- package/build/auth/error.js.map +1 -0
- package/build/auth/index.d.ts +124 -0
- package/build/auth/index.d.ts.map +1 -0
- package/build/auth/index.js +162 -0
- package/build/auth/index.js.map +1 -0
- package/build/content/error.d.ts +8 -0
- package/build/content/error.d.ts.map +1 -0
- package/build/content/error.js +10 -0
- package/build/content/error.js.map +1 -0
- package/build/content/index.d.ts +38 -0
- package/build/content/index.d.ts.map +1 -0
- package/build/content/index.js +56 -0
- package/build/content/index.js.map +1 -0
- package/build/error.d.ts +24 -0
- package/build/error.d.ts.map +1 -0
- package/build/error.js +25 -0
- package/build/error.js.map +1 -0
- package/build/hooks/index.d.ts +90 -0
- package/build/hooks/index.d.ts.map +1 -0
- package/build/hooks/index.js +326 -0
- package/build/hooks/index.js.map +1 -0
- package/build/images/error.d.ts +8 -0
- package/build/images/error.d.ts.map +1 -0
- package/build/images/error.js +10 -0
- package/build/images/error.js.map +1 -0
- package/build/images/index.d.ts +36 -0
- package/build/images/index.d.ts.map +1 -0
- package/build/images/index.js +59 -0
- package/build/images/index.js.map +1 -0
- package/build/index.d.ts +44 -42
- package/build/index.d.ts.map +1 -1
- package/build/index.js +49 -122
- package/build/index.js.map +1 -1
- package/build/player/error.d.ts +8 -0
- package/build/player/error.d.ts.map +1 -0
- package/build/player/error.js +10 -0
- package/build/player/error.js.map +1 -0
- package/build/player/index.d.ts +140 -0
- package/build/player/index.d.ts.map +1 -0
- package/build/player/index.js +136 -0
- package/build/player/index.js.map +1 -0
- package/build/uri/index.d.ts +55 -0
- package/build/uri/index.d.ts.map +1 -0
- package/build/uri/index.js +57 -0
- package/build/uri/index.js.map +1 -0
- package/build/user/error.d.ts +8 -0
- package/build/user/error.d.ts.map +1 -0
- package/build/user/error.js +10 -0
- package/build/user/error.js.map +1 -0
- package/build/user/index.d.ts +44 -0
- package/build/user/index.d.ts.map +1 -0
- package/build/user/index.js +97 -0
- package/build/user/index.js.map +1 -0
- package/ios/ExpoSpotifySDKModule.swift +233 -6
- package/ios/SPTScopeSerializer.swift +5 -0
- package/ios/SpotifyAppRemoteCoordinator.swift +939 -0
- package/ios/SpotifyAuthCoordinator.swift +67 -13
- package/ios/SpotifyTokenRefreshClient.swift +32 -1
- package/package.json +4 -3
|
@@ -8,13 +8,18 @@ import expo.modules.kotlin.functions.Coroutine
|
|
|
8
8
|
import expo.modules.kotlin.modules.Module
|
|
9
9
|
import expo.modules.kotlin.modules.ModuleDefinition
|
|
10
10
|
|
|
11
|
-
private const val SDK_VERSION = "0.
|
|
11
|
+
private const val SDK_VERSION = "1.0.0" // x-release-please-version
|
|
12
12
|
private const val EVENT_SESSION_CHANGE = "onSessionChange"
|
|
13
|
+
private const val EVENT_CONNECTION_STATE_CHANGE = "onConnectionStateChange"
|
|
14
|
+
private const val EVENT_CONNECTION_ERROR = "onConnectionError"
|
|
15
|
+
private const val EVENT_PLAYER_STATE_CHANGE = "onPlayerStateChange"
|
|
16
|
+
private const val EVENT_CAPABILITIES_CHANGE = "onCapabilitiesChange"
|
|
13
17
|
|
|
14
18
|
class ExpoSpotifySDKModule : Module() {
|
|
15
19
|
|
|
16
20
|
private lateinit var authLauncher: AppContextActivityResultLauncher<SpotifyAuthInput, AuthorizationResponse>
|
|
17
|
-
private val
|
|
21
|
+
private val authCoordinator = SpotifyAuthCoordinator()
|
|
22
|
+
private val appRemoteCoordinator = SpotifyAppRemoteCoordinator()
|
|
18
23
|
|
|
19
24
|
private val context
|
|
20
25
|
get() = appContext.reactContext ?: throw Exceptions.ReactContextLost()
|
|
@@ -58,17 +63,41 @@ class ExpoSpotifySDKModule : Module() {
|
|
|
58
63
|
)
|
|
59
64
|
}
|
|
60
65
|
|
|
61
|
-
private fun
|
|
66
|
+
private fun emitSessionError(code: String, message: String) {
|
|
62
67
|
sendEvent(
|
|
63
68
|
EVENT_SESSION_CHANGE,
|
|
64
|
-
mapOf("type" to
|
|
69
|
+
mapOf("type" to "didFail", "error" to mapOf("code" to code, "message" to message)),
|
|
65
70
|
)
|
|
66
71
|
}
|
|
67
72
|
|
|
68
73
|
override fun definition() = ModuleDefinition {
|
|
69
74
|
Name("ExpoSpotifySDK")
|
|
70
75
|
|
|
71
|
-
Events(
|
|
76
|
+
Events(
|
|
77
|
+
EVENT_SESSION_CHANGE,
|
|
78
|
+
EVENT_CONNECTION_STATE_CHANGE,
|
|
79
|
+
EVENT_CONNECTION_ERROR,
|
|
80
|
+
EVENT_PLAYER_STATE_CHANGE,
|
|
81
|
+
EVENT_CAPABILITIES_CHANGE,
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
// Wire up App Remote coordinator event callbacks once the module is alive.
|
|
85
|
+
OnCreate {
|
|
86
|
+
appRemoteCoordinator.onConnectionStateChange = { state ->
|
|
87
|
+
sendEvent(EVENT_CONNECTION_STATE_CHANGE, mapOf("state" to state))
|
|
88
|
+
}
|
|
89
|
+
appRemoteCoordinator.onConnectionError = { code, message ->
|
|
90
|
+
sendEvent(EVENT_CONNECTION_ERROR, mapOf("code" to code, "message" to message))
|
|
91
|
+
}
|
|
92
|
+
appRemoteCoordinator.onPlayerStateChange = { stateMap ->
|
|
93
|
+
sendEvent(EVENT_PLAYER_STATE_CHANGE, stateMap)
|
|
94
|
+
}
|
|
95
|
+
appRemoteCoordinator.onCapabilitiesChange = { capabilities ->
|
|
96
|
+
sendEvent(EVENT_CAPABILITIES_CHANGE, capabilities)
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// ── Auth ────────────────────────────────────────────────────────────────
|
|
72
101
|
|
|
73
102
|
Function("isAvailable") {
|
|
74
103
|
isSpotifyInstalled()
|
|
@@ -97,7 +126,7 @@ class ExpoSpotifySDKModule : Module() {
|
|
|
97
126
|
showDialog = config.showDialog,
|
|
98
127
|
)
|
|
99
128
|
|
|
100
|
-
val response =
|
|
129
|
+
val response = authCoordinator.authenticate(authLauncher, input)
|
|
101
130
|
|
|
102
131
|
val payload = when (response.type) {
|
|
103
132
|
AuthorizationResponse.Type.TOKEN -> SpotifySessionPayload(
|
|
@@ -122,20 +151,20 @@ class ExpoSpotifySDKModule : Module() {
|
|
|
122
151
|
)
|
|
123
152
|
}
|
|
124
153
|
AuthorizationResponse.Type.CANCELLED -> throw UserCancelledException()
|
|
125
|
-
AuthorizationResponse.Type.EMPTY -> throw
|
|
126
|
-
"Spotify returned an empty response (auth activity
|
|
154
|
+
AuthorizationResponse.Type.EMPTY -> throw UserCancelledException(
|
|
155
|
+
"Spotify returned an empty response (auth activity dismissed before completion)",
|
|
127
156
|
)
|
|
128
157
|
AuthorizationResponse.Type.ERROR -> throw SpotifyAuthErrorException(
|
|
129
158
|
response.error ?: "Spotify returned an unspecified error",
|
|
130
159
|
)
|
|
131
|
-
AuthorizationResponse.Type.UNKNOWN
|
|
160
|
+
AuthorizationResponse.Type.UNKNOWN -> throw UnknownSpotifyException(
|
|
132
161
|
"Unknown Spotify authorization response type",
|
|
133
162
|
)
|
|
134
163
|
}
|
|
135
164
|
emitSession("didInitiate", payload)
|
|
136
165
|
payload.toMap()
|
|
137
166
|
} catch (e: expo.modules.kotlin.exception.CodedException) {
|
|
138
|
-
|
|
167
|
+
emitSessionError(e.code ?: "UNKNOWN", e.localizedMessage ?: e.code ?: "Unknown error")
|
|
139
168
|
throw e
|
|
140
169
|
}
|
|
141
170
|
}
|
|
@@ -153,14 +182,122 @@ class ExpoSpotifySDKModule : Module() {
|
|
|
153
182
|
val payload = client.refresh(
|
|
154
183
|
refreshToken = options.refreshToken,
|
|
155
184
|
tokenRefreshURL = options.tokenRefreshURL,
|
|
156
|
-
previousScopes =
|
|
185
|
+
previousScopes = options.scopes,
|
|
157
186
|
)
|
|
158
187
|
emitSession("didRenew", payload)
|
|
159
188
|
payload.toMap()
|
|
160
189
|
} catch (e: expo.modules.kotlin.exception.CodedException) {
|
|
161
|
-
|
|
190
|
+
emitSessionError(e.code ?: "UNKNOWN", e.localizedMessage ?: e.code ?: "Unknown error")
|
|
162
191
|
throw e
|
|
163
192
|
}
|
|
164
193
|
}
|
|
194
|
+
|
|
195
|
+
// ── App Remote ──────────────────────────────────────────────────────────
|
|
196
|
+
|
|
197
|
+
AsyncFunction("appRemoteConnect") Coroutine { accessToken: String ->
|
|
198
|
+
val manifest = readManifestConfig()
|
|
199
|
+
appRemoteCoordinator.connect(
|
|
200
|
+
context = context,
|
|
201
|
+
clientId = manifest.clientId,
|
|
202
|
+
redirectUri = manifest.redirectUri,
|
|
203
|
+
accessToken = accessToken,
|
|
204
|
+
)
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
AsyncFunction("appRemoteDisconnect") Coroutine { ->
|
|
208
|
+
appRemoteCoordinator.disconnect()
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
Function("appRemoteIsConnected") {
|
|
212
|
+
appRemoteCoordinator.isConnected()
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
AsyncFunction("appRemoteGetConnectionState") Coroutine { ->
|
|
216
|
+
appRemoteCoordinator.getConnectionState()
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// ── Player ──────────────────────────────────────────────────────────────
|
|
220
|
+
|
|
221
|
+
AsyncFunction("playerPlay") Coroutine { uri: String ->
|
|
222
|
+
appRemoteCoordinator.playerPlay(uri)
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
AsyncFunction("playerPause") Coroutine { ->
|
|
226
|
+
appRemoteCoordinator.playerPause()
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
AsyncFunction("playerResume") Coroutine { ->
|
|
230
|
+
appRemoteCoordinator.playerResume()
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
AsyncFunction("playerSkipNext") Coroutine { ->
|
|
234
|
+
appRemoteCoordinator.playerSkipNext()
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
AsyncFunction("playerSkipPrevious") Coroutine { ->
|
|
238
|
+
appRemoteCoordinator.playerSkipPrevious()
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
AsyncFunction("playerSeekTo") Coroutine { positionMs: Long ->
|
|
242
|
+
appRemoteCoordinator.playerSeekTo(positionMs)
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
AsyncFunction("playerSetShuffle") Coroutine { enabled: Boolean ->
|
|
246
|
+
appRemoteCoordinator.playerSetShuffle(enabled)
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
AsyncFunction("playerSetRepeatMode") Coroutine { mode: Int ->
|
|
250
|
+
appRemoteCoordinator.playerSetRepeatMode(mode)
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
AsyncFunction("playerSetPodcastPlaybackSpeed") Coroutine { value: Double ->
|
|
254
|
+
appRemoteCoordinator.playerSetPodcastPlaybackSpeed(value.toFloat())
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
AsyncFunction("playerQueue") Coroutine { uri: String ->
|
|
258
|
+
appRemoteCoordinator.playerQueue(uri)
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
AsyncFunction("playerGetPlayerState") Coroutine { ->
|
|
262
|
+
appRemoteCoordinator.playerGetPlayerState()
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
AsyncFunction("playerGetCrossfadeState") Coroutine { ->
|
|
266
|
+
appRemoteCoordinator.playerGetCrossfadeState()
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// ── User ────────────────────────────────────────────────────────────────
|
|
270
|
+
|
|
271
|
+
AsyncFunction("userGetCapabilities") Coroutine { ->
|
|
272
|
+
appRemoteCoordinator.userGetCapabilities()
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
AsyncFunction("userGetLibraryState") Coroutine { uri: String ->
|
|
276
|
+
appRemoteCoordinator.userGetLibraryState(uri)
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
AsyncFunction("userAddToLibrary") Coroutine { uri: String ->
|
|
280
|
+
appRemoteCoordinator.userAddToLibrary(uri)
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
AsyncFunction("userRemoveFromLibrary") Coroutine { uri: String ->
|
|
284
|
+
appRemoteCoordinator.userRemoveFromLibrary(uri)
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// ── Content ───────────────────────────────────────────────────────────────
|
|
288
|
+
|
|
289
|
+
AsyncFunction("contentGetRecommendedContentItems") Coroutine { type: String ->
|
|
290
|
+
appRemoteCoordinator.contentGetRecommendedContentItems(type)
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
AsyncFunction("contentGetChildren") Coroutine { item: Map<String, Any?> ->
|
|
294
|
+
appRemoteCoordinator.contentGetChildren(item)
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// ── Images ────────────────────────────────────────────────────────────────
|
|
298
|
+
|
|
299
|
+
AsyncFunction("imagesLoad") Coroutine { imageIdentifier: String, size: String ->
|
|
300
|
+
appRemoteCoordinator.imagesLoad(imageIdentifier, size, context.cacheDir)
|
|
301
|
+
}
|
|
165
302
|
}
|
|
166
303
|
}
|