@superfan-app/spotify-auth 0.1.70 → 0.1.72

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.
@@ -82,6 +82,10 @@ def resolvedKotlinVersion() {
82
82
  return '2.0.21'
83
83
  }
84
84
 
85
+ repositories {
86
+ mavenCentral()
87
+ }
88
+
85
89
  dependencies {
86
90
  implementation project(':expo-modules-core')
87
91
 
@@ -1,4 +1,13 @@
1
1
  <manifest xmlns:android="http://schemas.android.com/apk/res/android">
2
- <!-- No permissions needed at the library level.
2
+ <!-- Required for token exchange with backend -->
3
+ <uses-permission android:name="android.permission.INTERNET" />
4
+
5
+ <!-- Package visibility for Android 11+ (API 30+) -->
6
+ <!-- Required for the app to detect and launch Spotify -->
7
+ <queries>
8
+ <package android:name="com.spotify.music" />
9
+ </queries>
10
+
11
+ <!-- No other permissions needed at the library level.
3
12
  The config plugin will add intent-filters to the host app's manifest. -->
4
13
  </manifest>
@@ -50,8 +50,18 @@ class SpotifyAuthAuth private constructor(private val appContext: AppContext) {
50
50
 
51
51
  /** Weak-ish reference to the module for sending events back to JS. */
52
52
  var module: SpotifyAuthModule? = null
53
+ set(value) {
54
+ field = value
55
+ if (value != null) {
56
+ secureLog("Module reference set successfully")
57
+ } else {
58
+ Log.w(TAG, "Module reference set to null")
59
+ }
60
+ }
53
61
 
54
62
  private var isAuthenticating = false
63
+ private var authTimeoutHandler: Runnable? = null
64
+ private val AUTH_TIMEOUT_MS = 60_000L // 60 seconds timeout
55
65
  private var currentSession: SpotifySessionData? = null
56
66
  set(value) {
57
67
  field = value
@@ -85,10 +95,21 @@ class SpotifyAuthAuth private constructor(private val appContext: AppContext) {
85
95
  )
86
96
  ai.metaData?.getString(key)
87
97
  } catch (e: Exception) {
98
+ Log.e(TAG, "Failed to get meta-data for key $key: ${e.message}")
88
99
  null
89
100
  }
90
101
  }
91
102
 
103
+ private fun isSpotifyInstalled(): Boolean {
104
+ val context = appContext.reactContext ?: return false
105
+ return try {
106
+ context.packageManager.getPackageInfo("com.spotify.music", 0)
107
+ true
108
+ } catch (e: Exception) {
109
+ false
110
+ }
111
+ }
112
+
92
113
  private val clientID: String
93
114
  get() = getMetaData("SpotifyClientID")
94
115
  ?: throw SpotifyAuthException.MissingConfiguration("SpotifyClientID in AndroidManifest meta-data")
@@ -112,6 +133,53 @@ class SpotifyAuthAuth private constructor(private val appContext: AppContext) {
112
133
  return scopesStr.split(",").map { it.trim() }.filter { it.isNotEmpty() }
113
134
  }
114
135
 
136
+ /**
137
+ * Verify the app configuration and log any potential issues.
138
+ * Helps diagnose setup problems.
139
+ */
140
+ private fun verifyConfiguration() {
141
+ try {
142
+ val context = appContext.reactContext
143
+ if (context == null) {
144
+ Log.e(TAG, "React context is null - app may not be fully initialized")
145
+ return
146
+ }
147
+
148
+ // Check meta-data configuration
149
+ val configKeys = listOf(
150
+ "SpotifyClientID",
151
+ "SpotifyRedirectURL",
152
+ "SpotifyScopes",
153
+ "SpotifyTokenSwapURL",
154
+ "SpotifyTokenRefreshURL"
155
+ )
156
+
157
+ var allConfigured = true
158
+ for (key in configKeys) {
159
+ val value = getMetaData(key)
160
+ if (value.isNullOrEmpty()) {
161
+ Log.e(TAG, "Missing or empty configuration: $key")
162
+ allConfigured = false
163
+ } else {
164
+ Log.d(TAG, "Configuration $key: ${if (key.contains("URL") || key.contains("ID")) value.take(20) + "..." else value}")
165
+ }
166
+ }
167
+
168
+ if (allConfigured) {
169
+ Log.d(TAG, "All required configuration values are present")
170
+ } else {
171
+ Log.e(TAG, "Some configuration values are missing - auth will likely fail")
172
+ }
173
+
174
+ // Check if Spotify app is installed
175
+ val spotifyInstalled = isSpotifyInstalled()
176
+ Log.d(TAG, "Spotify app installed: $spotifyInstalled")
177
+
178
+ } catch (e: Exception) {
179
+ Log.e(TAG, "Error during configuration verification: ${e.message}", e)
180
+ }
181
+ }
182
+
115
183
  // endregion
116
184
 
117
185
  // region Authentication Flow
@@ -122,19 +190,52 @@ class SpotifyAuthAuth private constructor(private val appContext: AppContext) {
122
190
  * and WebView fallback automatically.
123
191
  */
124
192
  fun initAuth(config: AuthorizeConfig) {
193
+ secureLog("initAuth called with showDialog=${config.showDialog}")
194
+
195
+ // Verify configuration on first auth attempt
196
+ verifyConfiguration()
197
+
198
+ // Cancel any existing timeout
199
+ authTimeoutHandler?.let { mainHandler.removeCallbacks(it) }
200
+
125
201
  try {
202
+ if (module == null) {
203
+ Log.e(TAG, "CRITICAL: Module reference is null when initAuth called")
204
+ throw SpotifyAuthException.SessionError("Module not properly initialized")
205
+ }
206
+
126
207
  val activity = appContext.currentActivity
127
- ?: throw SpotifyAuthException.SessionError("No activity available")
208
+ if (activity == null) {
209
+ Log.e(TAG, "CRITICAL: No current activity available for auth")
210
+ throw SpotifyAuthException.SessionError("No activity available")
211
+ }
212
+
213
+ secureLog("Current activity: ${activity.javaClass.simpleName}")
128
214
 
129
215
  val clientId = clientID
130
216
  val redirectUri = redirectURL
131
217
  val scopeArray = scopes.toTypedArray()
132
218
 
219
+ val spotifyInstalled = isSpotifyInstalled()
220
+ secureLog("Configuration - ClientID: ${clientId.take(8)}..., RedirectURI: $redirectUri, Scopes: ${scopeArray.size}")
221
+ Log.d(TAG, "Spotify app installed: $spotifyInstalled (will use ${if (spotifyInstalled) "app-switch" else "WebView"} auth)")
222
+
223
+ if (!spotifyInstalled) {
224
+ Log.w(TAG, "Spotify app not detected. Will use WebView fallback. If WebView fails, check package visibility in AndroidManifest (<queries> tag)")
225
+ }
226
+
133
227
  if (scopeArray.isEmpty()) {
228
+ Log.e(TAG, "No valid scopes found in configuration")
134
229
  throw SpotifyAuthException.InvalidConfiguration("No valid scopes found in configuration")
135
230
  }
136
231
 
232
+ if (isAuthenticating) {
233
+ Log.w(TAG, "Auth already in progress, ignoring duplicate request")
234
+ return
235
+ }
236
+
137
237
  isAuthenticating = true
238
+ secureLog("Setting isAuthenticating to true")
138
239
 
139
240
  val builder = AuthorizationRequest.Builder(
140
241
  clientId,
@@ -145,6 +246,7 @@ class SpotifyAuthAuth private constructor(private val appContext: AppContext) {
145
246
 
146
247
  if (config.showDialog) {
147
248
  builder.setShowDialog(true)
249
+ secureLog("Force-showing login dialog")
148
250
  }
149
251
 
150
252
  // Note: The Android Spotify auth-lib doesn't support a 'campaign' parameter
@@ -152,18 +254,42 @@ class SpotifyAuthAuth private constructor(private val appContext: AppContext) {
152
254
 
153
255
  val request = builder.build()
154
256
 
155
- // AuthorizationClient.openLoginActivity handles both flows:
156
- // - If Spotify is installed: app-switch auth
157
- // - If Spotify is not installed: opens a WebView with Spotify login
158
- AuthorizationClient.openLoginActivity(activity, REQUEST_CODE, request)
257
+ secureLog("Opening Spotify authorization activity with REQUEST_CODE=$REQUEST_CODE")
258
+
259
+ // Set a timeout to detect if the auth flow doesn't complete
260
+ authTimeoutHandler = Runnable {
261
+ if (isAuthenticating) {
262
+ Log.e(TAG, "Auth timeout - no response received after ${AUTH_TIMEOUT_MS}ms")
263
+ isAuthenticating = false
264
+ module?.onAuthorizationError(
265
+ SpotifyAuthException.AuthenticationFailed("Authorization timed out. Please try again.")
266
+ )
267
+ }
268
+ }
269
+ mainHandler.postDelayed(authTimeoutHandler!!, AUTH_TIMEOUT_MS)
270
+
271
+ try {
272
+ // AuthorizationClient.openLoginActivity handles both flows:
273
+ // - If Spotify is installed: app-switch auth
274
+ // - If Spotify is not installed: opens a WebView with Spotify login
275
+ AuthorizationClient.openLoginActivity(activity, REQUEST_CODE, request)
276
+ secureLog("AuthorizationClient.openLoginActivity called successfully")
277
+ } catch (e: Exception) {
278
+ Log.e(TAG, "Failed to open authorization activity: ${e.message}", e)
279
+ throw SpotifyAuthException.AuthenticationFailed("Failed to open Spotify authorization: ${e.message}")
280
+ }
281
+
159
282
  } catch (e: SpotifyAuthException) {
283
+ Log.e(TAG, "Auth initialization failed (SpotifyAuthException): ${e.message}")
160
284
  isAuthenticating = false
161
- module?.onAuthorizationError(e)
285
+ authTimeoutHandler?.let { mainHandler.removeCallbacks(it) }
286
+ module?.onAuthorizationError(e) ?: Log.e(TAG, "Cannot send error - module is null")
162
287
  } catch (e: Exception) {
288
+ Log.e(TAG, "Auth initialization failed (Exception): ${e.message}", e)
163
289
  isAuthenticating = false
164
- module?.onAuthorizationError(
165
- SpotifyAuthException.AuthenticationFailed(e.message ?: "Unknown error")
166
- )
290
+ authTimeoutHandler?.let { mainHandler.removeCallbacks(it) }
291
+ val error = SpotifyAuthException.AuthenticationFailed(e.message ?: "Unknown error")
292
+ module?.onAuthorizationError(error) ?: Log.e(TAG, "Cannot send error - module is null")
167
293
  }
168
294
  }
169
295
 
@@ -172,43 +298,74 @@ class SpotifyAuthAuth private constructor(private val appContext: AppContext) {
172
298
  * Called by the module's OnActivityResult handler.
173
299
  */
174
300
  fun handleActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
175
- if (requestCode != REQUEST_CODE) return
301
+ Log.d(TAG, "handleActivityResult called - requestCode=$requestCode, resultCode=$resultCode, hasData=${data != null}")
302
+
303
+ if (requestCode != REQUEST_CODE) {
304
+ Log.d(TAG, "Ignoring activity result - wrong request code (expected $REQUEST_CODE, got $requestCode)")
305
+ return
306
+ }
307
+
308
+ // Cancel the timeout
309
+ authTimeoutHandler?.let {
310
+ mainHandler.removeCallbacks(it)
311
+ secureLog("Auth timeout cancelled")
312
+ }
313
+
314
+ if (!isAuthenticating) {
315
+ Log.w(TAG, "Received activity result but isAuthenticating was false")
316
+ }
176
317
 
177
318
  isAuthenticating = false
319
+ secureLog("Setting isAuthenticating to false")
178
320
 
179
- val response = AuthorizationClient.getResponse(resultCode, data)
321
+ if (module == null) {
322
+ Log.e(TAG, "CRITICAL: Module is null in handleActivityResult - cannot send events to JS")
323
+ return
324
+ }
180
325
 
181
- when (response.type) {
182
- AuthorizationResponse.Type.CODE -> {
183
- secureLog("Authorization code received")
184
- val code = response.code
185
- if (code != null) {
186
- exchangeCodeForToken(code)
187
- } else {
188
- module?.onAuthorizationError(
189
- SpotifyAuthException.AuthenticationFailed("No authorization code received")
190
- )
326
+ try {
327
+ val response = AuthorizationClient.getResponse(resultCode, data)
328
+ Log.d(TAG, "Spotify response type: ${response.type}")
329
+
330
+ when (response.type) {
331
+ AuthorizationResponse.Type.CODE -> {
332
+ val code = response.code
333
+ secureLog("Authorization code received, length=${code?.length ?: 0}")
334
+ if (code != null) {
335
+ exchangeCodeForToken(code)
336
+ } else {
337
+ Log.e(TAG, "Authorization code was null despite CODE response type")
338
+ module?.onAuthorizationError(
339
+ SpotifyAuthException.AuthenticationFailed("No authorization code received")
340
+ )
341
+ }
191
342
  }
192
- }
193
- AuthorizationResponse.Type.ERROR -> {
194
- val errorMsg = response.error ?: "Unknown error"
195
- secureLog("Authorization error: $errorMsg")
196
- if (errorMsg.contains("access_denied", ignoreCase = true) ||
197
- errorMsg.contains("cancelled", ignoreCase = true)) {
343
+ AuthorizationResponse.Type.ERROR -> {
344
+ val errorMsg = response.error ?: "Unknown error"
345
+ Log.e(TAG, "Spotify authorization error: $errorMsg")
346
+ if (errorMsg.contains("access_denied", ignoreCase = true) ||
347
+ errorMsg.contains("cancelled", ignoreCase = true)) {
348
+ module?.onAuthorizationError(SpotifyAuthException.UserCancelled())
349
+ } else {
350
+ module?.onAuthorizationError(SpotifyAuthException.AuthorizationError(errorMsg))
351
+ }
352
+ }
353
+ AuthorizationResponse.Type.EMPTY -> {
354
+ Log.w(TAG, "Authorization returned EMPTY - user likely cancelled")
198
355
  module?.onAuthorizationError(SpotifyAuthException.UserCancelled())
199
- } else {
200
- module?.onAuthorizationError(SpotifyAuthException.AuthorizationError(errorMsg))
356
+ }
357
+ else -> {
358
+ Log.e(TAG, "Unexpected Spotify response type: ${response.type}")
359
+ module?.onAuthorizationError(
360
+ SpotifyAuthException.AuthenticationFailed("Unexpected response type: ${response.type}")
361
+ )
201
362
  }
202
363
  }
203
- AuthorizationResponse.Type.EMPTY -> {
204
- secureLog("Authorization was cancelled or returned empty")
205
- module?.onAuthorizationError(SpotifyAuthException.UserCancelled())
206
- }
207
- else -> {
208
- module?.onAuthorizationError(
209
- SpotifyAuthException.AuthenticationFailed("Unexpected response type: ${response.type}")
210
- )
211
- }
364
+ } catch (e: Exception) {
365
+ Log.e(TAG, "Exception in handleActivityResult: ${e.message}", e)
366
+ module?.onAuthorizationError(
367
+ SpotifyAuthException.AuthenticationFailed("Error processing auth result: ${e.message}")
368
+ )
212
369
  }
213
370
  }
214
371
 
@@ -220,16 +377,29 @@ class SpotifyAuthAuth private constructor(private val appContext: AppContext) {
220
377
  * Exchange an authorization code for access + refresh tokens via the backend token swap URL.
221
378
  */
222
379
  private fun exchangeCodeForToken(code: String) {
380
+ secureLog("Starting token exchange process")
381
+
382
+ if (module == null) {
383
+ Log.e(TAG, "CRITICAL: Module is null in exchangeCodeForToken")
384
+ return
385
+ }
386
+
223
387
  executor.execute {
224
388
  try {
225
389
  val swapUrl = tokenSwapURL
226
390
  val redirect = redirectURL
227
391
 
392
+ Log.d(TAG, "Token swap URL: ${swapUrl.take(30)}...")
393
+ Log.d(TAG, "Redirect URL: $redirect")
394
+
228
395
  if (!swapUrl.startsWith("https://")) {
396
+ Log.e(TAG, "Token swap URL does not use HTTPS: $swapUrl")
229
397
  throw SpotifyAuthException.InvalidConfiguration("Token swap URL must use HTTPS")
230
398
  }
231
399
 
232
400
  val url = URL(swapUrl)
401
+ Log.d(TAG, "Opening connection to token swap URL")
402
+
233
403
  val connection = url.openConnection() as HttpURLConnection
234
404
  connection.apply {
235
405
  requestMethod = "POST"
@@ -240,14 +410,19 @@ class SpotifyAuthAuth private constructor(private val appContext: AppContext) {
240
410
  }
241
411
 
242
412
  val body = "code=${Uri.encode(code)}&redirect_uri=${Uri.encode(redirect)}"
413
+ Log.d(TAG, "Sending token exchange request (code length: ${code.length})")
414
+
243
415
  OutputStreamWriter(connection.outputStream).use { writer ->
244
416
  writer.write(body)
245
417
  writer.flush()
246
418
  }
247
419
 
248
420
  val responseCode = connection.responseCode
421
+ Log.d(TAG, "Token exchange response code: $responseCode")
422
+
249
423
  if (responseCode !in 200..299) {
250
424
  val errorMessage = extractErrorMessage(connection, responseCode)
425
+ Log.e(TAG, "Token exchange failed with status $responseCode: $errorMessage")
251
426
  throw SpotifyAuthException.NetworkError(errorMessage)
252
427
  }
253
428
 
@@ -255,6 +430,8 @@ class SpotifyAuthAuth private constructor(private val appContext: AppContext) {
255
430
  it.readText()
256
431
  }
257
432
 
433
+ Log.d(TAG, "Token exchange response received (body length: ${responseBody.length})")
434
+
258
435
  val parsed = parseTokenJSON(responseBody)
259
436
 
260
437
  val refreshToken = parsed.refreshToken
@@ -262,6 +439,8 @@ class SpotifyAuthAuth private constructor(private val appContext: AppContext) {
262
439
 
263
440
  val expirationTime = System.currentTimeMillis() + (parsed.expiresIn * 1000).toLong()
264
441
 
442
+ Log.d(TAG, "Token exchange successful - expires in ${parsed.expiresIn} seconds")
443
+
265
444
  val session = SpotifySessionData(
266
445
  accessToken = parsed.accessToken,
267
446
  refreshToken = refreshToken,
@@ -270,6 +449,7 @@ class SpotifyAuthAuth private constructor(private val appContext: AppContext) {
270
449
  )
271
450
 
272
451
  mainHandler.post {
452
+ secureLog("Setting currentSession and sending success event to JS")
273
453
  currentSession = session
274
454
  }
275
455
 
@@ -388,8 +568,17 @@ class SpotifyAuthAuth private constructor(private val appContext: AppContext) {
388
568
  }
389
569
 
390
570
  private fun securelyStoreToken(session: SpotifySessionData) {
571
+ secureLog("Storing token and sending to JS")
572
+
573
+ if (module == null) {
574
+ Log.e(TAG, "CRITICAL: Module is null in securelyStoreToken - cannot send token to JS")
575
+ return
576
+ }
577
+
391
578
  // Send the token back to JS
392
579
  val expiresIn = (session.expirationTime - System.currentTimeMillis()) / 1000.0
580
+ Log.d(TAG, "Sending access token to JS (expires in ${expiresIn}s)")
581
+
393
582
  module?.onAccessTokenObtained(
394
583
  session.accessToken,
395
584
  session.refreshToken,
@@ -404,8 +593,9 @@ class SpotifyAuthAuth private constructor(private val appContext: AppContext) {
404
593
  getEncryptedPrefs()?.edit()
405
594
  ?.putString(PREF_REFRESH_TOKEN_KEY, session.refreshToken)
406
595
  ?.apply()
596
+ Log.d(TAG, "Refresh token stored securely")
407
597
  } catch (e: Exception) {
408
- Log.e(TAG, "Failed to store refresh token securely: ${e.message}")
598
+ Log.e(TAG, "Failed to store refresh token securely: ${e.message}", e)
409
599
  }
410
600
  }
411
601
  }
@@ -449,6 +639,8 @@ class SpotifyAuthAuth private constructor(private val appContext: AppContext) {
449
639
  }
450
640
 
451
641
  fun cleanup() {
642
+ secureLog("Cleaning up SpotifyAuthAuth instance")
643
+ authTimeoutHandler?.let { mainHandler.removeCallbacks(it) }
452
644
  refreshHandler.removeCallbacksAndMessages(null)
453
645
  executor.shutdown()
454
646
  instance = null
@@ -24,8 +24,13 @@ class SpotifyAuthModule : Module() {
24
24
  Name("SpotifyAuth")
25
25
 
26
26
  OnCreate {
27
- spotifyAuth.module = this@SpotifyAuthModule
28
- secureLog("Module initialized")
27
+ secureLog("SpotifyAuthModule OnCreate called")
28
+ try {
29
+ spotifyAuth.module = this@SpotifyAuthModule
30
+ secureLog("Module initialized and linked to SpotifyAuthAuth")
31
+ } catch (e: Exception) {
32
+ android.util.Log.e("SpotifyAuth", "Failed to initialize module: ${e.message}", e)
33
+ }
29
34
  }
30
35
 
31
36
  Constants(
@@ -58,6 +63,7 @@ class SpotifyAuthModule : Module() {
58
63
  }
59
64
 
60
65
  OnActivityResult { _, payload ->
66
+ secureLog("OnActivityResult received in module - requestCode=${payload.requestCode}, resultCode=${payload.resultCode}")
61
67
  spotifyAuth.handleActivityResult(payload.requestCode, payload.resultCode, payload.data)
62
68
  }
63
69
 
@@ -85,7 +91,12 @@ class SpotifyAuthModule : Module() {
85
91
  "scope" to scope,
86
92
  "error" to null
87
93
  )
88
- sendEvent(SPOTIFY_AUTH_EVENT_NAME, eventData)
94
+ try {
95
+ sendEvent(SPOTIFY_AUTH_EVENT_NAME, eventData)
96
+ secureLog("Success event sent to JS")
97
+ } catch (e: Exception) {
98
+ android.util.Log.e("SpotifyAuth", "Failed to send success event to JS: ${e.message}", e)
99
+ }
89
100
  }
90
101
 
91
102
  fun onSignOut() {
@@ -103,6 +114,8 @@ class SpotifyAuthModule : Module() {
103
114
  }
104
115
 
105
116
  fun onAuthorizationError(error: Exception) {
117
+ android.util.Log.d("SpotifyAuth", "onAuthorizationError called with: ${error.javaClass.simpleName}: ${error.message}")
118
+
106
119
  // Skip sending error events for expected state transitions
107
120
  if (error is SpotifyAuthException.SessionError) {
108
121
  val msg = error.message ?: ""
@@ -125,7 +138,7 @@ class SpotifyAuthModule : Module() {
125
138
  )
126
139
  }
127
140
 
128
- secureLog("Authorization error: ${errorData["message"] ?: "Unknown error"}")
141
+ android.util.Log.e("SpotifyAuth", "Authorization error: ${errorData["message"] ?: "Unknown error"}")
129
142
 
130
143
  val eventData = mapOf(
131
144
  "success" to false,
@@ -136,7 +149,13 @@ class SpotifyAuthModule : Module() {
136
149
  "scope" to null,
137
150
  "error" to errorData
138
151
  )
139
- sendEvent(SPOTIFY_AUTH_EVENT_NAME, eventData)
152
+
153
+ try {
154
+ sendEvent(SPOTIFY_AUTH_EVENT_NAME, eventData)
155
+ secureLog("Error event sent to JS successfully")
156
+ } catch (e: Exception) {
157
+ android.util.Log.e("SpotifyAuth", "CRITICAL: Failed to send error event to JS: ${e.message}", e)
158
+ }
140
159
  }
141
160
 
142
161
  // endregion
@@ -594,7 +594,6 @@ final class SpotifyAuthAuth: NSObject, SPTSessionManagerDelegate {
594
594
  "user-modify-playback-state": .userModifyPlaybackState,
595
595
  "user-read-currently-playing": .userReadCurrentlyPlaying,
596
596
  "user-read-recently-played": .userReadRecentlyPlayed,
597
- "user-read-playback-position": .userReadPlaybackPosition,
598
597
  "openid": .openid
599
598
  ]
600
599
  return scopeMapping[scopeString]
@@ -844,10 +843,9 @@ extension SPTScope {
844
843
  (.userModifyPlaybackState, "user-modify-playback-state"),
845
844
  (.userReadCurrentlyPlaying, "user-read-currently-playing"),
846
845
  (.userReadRecentlyPlayed, "user-read-recently-played"),
847
- (.userReadPlaybackPosition, "user-read-playback-position"),
848
846
  (.openid, "openid")
849
847
  ]
850
-
848
+
851
849
  return scopeMapping.filter { contains($0.0) }.map { $0.1 }
852
850
  }
853
851
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@superfan-app/spotify-auth",
3
- "version": "0.1.70",
3
+ "version": "0.1.72",
4
4
  "description": "Spotify OAuth module for Expo",
5
5
  "main": "src/index.tsx",
6
6
  "types": "build/index.d.ts",