@wwdrew/expo-spotify-sdk 1.0.0 → 2.1.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.
Files changed (61) hide show
  1. package/README.md +92 -350
  2. package/android/build.gradle +2 -2
  3. package/android/src/main/java/expo/modules/spotifysdk/ExpoSpotifySDKModule.kt +3 -5
  4. package/android/src/main/java/expo/modules/spotifysdk/SpotifyAppRemoteCoordinator.kt +6 -64
  5. package/android/src/main/java/expo/modules/spotifysdk/SpotifyAppRemoteErrorMapping.kt +106 -0
  6. package/android/src/main/java/expo/modules/spotifysdk/SpotifyTokenSwapClient.kt +2 -7
  7. package/app.plugin.js +1 -1
  8. package/build/app-remote/index.d.ts +2 -2
  9. package/build/app-remote/index.d.ts.map +1 -1
  10. package/build/app-remote/index.js +11 -24
  11. package/build/app-remote/index.js.map +1 -1
  12. package/build/auth/index.d.ts +2 -2
  13. package/build/auth/index.d.ts.map +1 -1
  14. package/build/auth/index.js +17 -34
  15. package/build/auth/index.js.map +1 -1
  16. package/build/content/index.d.ts.map +1 -1
  17. package/build/content/index.js +12 -25
  18. package/build/content/index.js.map +1 -1
  19. package/build/hooks/index.d.ts +4 -4
  20. package/build/hooks/index.d.ts.map +1 -1
  21. package/build/hooks/index.js +38 -115
  22. package/build/hooks/index.js.map +1 -1
  23. package/build/images/index.d.ts +2 -2
  24. package/build/images/index.d.ts.map +1 -1
  25. package/build/images/index.js +12 -25
  26. package/build/images/index.js.map +1 -1
  27. package/build/index.d.ts +0 -18
  28. package/build/index.d.ts.map +1 -1
  29. package/build/index.js +0 -27
  30. package/build/index.js.map +1 -1
  31. package/build/internal/native-errors.d.ts +14 -0
  32. package/build/internal/native-errors.d.ts.map +1 -0
  33. package/build/internal/native-errors.js +29 -0
  34. package/build/internal/native-errors.js.map +1 -0
  35. package/build/internal/sync-external-store.d.ts +14 -0
  36. package/build/internal/sync-external-store.d.ts.map +1 -0
  37. package/build/internal/sync-external-store.js +36 -0
  38. package/build/internal/sync-external-store.js.map +1 -0
  39. package/build/player/index.d.ts +1 -1
  40. package/build/player/index.d.ts.map +1 -1
  41. package/build/player/index.js +15 -27
  42. package/build/player/index.js.map +1 -1
  43. package/build/uri/index.js.map +1 -1
  44. package/build/user/index.d.ts +2 -2
  45. package/build/user/index.d.ts.map +1 -1
  46. package/build/user/index.js +16 -26
  47. package/build/user/index.js.map +1 -1
  48. package/ios/ExpoSpotifySDK.podspec +1 -1
  49. package/ios/ExpoSpotifySDKModule.swift +1 -1
  50. package/ios/SpotifyAppRemoteCoordinator.swift +29 -404
  51. package/ios/SpotifyAppRemoteErrorMapping.swift +110 -0
  52. package/ios/SpotifyAppRemoteErrors.swift +221 -0
  53. package/ios/SpotifyAppRemoteMappers.swift +88 -0
  54. package/package.json +28 -14
  55. package/plugin/build/index.d.ts +20 -3
  56. package/plugin/build/index.js +22 -25
  57. package/plugin/build/types.d.ts +17 -1
  58. package/plugin/build/withSpotifySdkConfig.d.ts +4 -0
  59. package/plugin/build/withSpotifySdkConfig.js +27 -0
  60. package/plugin/index.d.ts +2 -0
  61. package/plugin/index.js +1 -0
@@ -18,7 +18,6 @@ import com.spotify.protocol.types.ListItems
18
18
  import com.spotify.protocol.types.PlayerState
19
19
  import com.spotify.protocol.types.PlaybackSpeed
20
20
  import com.spotify.protocol.types.Repeat
21
- import expo.modules.kotlin.exception.CodedException
22
21
  import kotlinx.coroutines.suspendCancellableCoroutine
23
22
  import kotlinx.coroutines.sync.Mutex
24
23
  import java.io.File
@@ -336,7 +335,7 @@ class SpotifyAppRemoteCoordinator {
336
335
  suspendCancellableCoroutine { continuation ->
337
336
  setResultCallback { continuation.resume(Unit) }
338
337
  setErrorCallback { throwable ->
339
- continuation.resumeWithException(normalizePlayerError(throwable, callsite))
338
+ continuation.resumeWithException(SpotifyAppRemoteErrorMapping.mapPlayerError(throwable, callsite))
340
339
  }
341
340
  }
342
341
  }
@@ -347,7 +346,7 @@ class SpotifyAppRemoteCoordinator {
347
346
  return suspendCancellableCoroutine { continuation ->
348
347
  setResultCallback { result -> continuation.resume(result) }
349
348
  setErrorCallback { throwable ->
350
- continuation.resumeWithException(normalizePlayerError(throwable, callsite))
349
+ continuation.resumeWithException(SpotifyAppRemoteErrorMapping.mapPlayerError(throwable, callsite))
351
350
  }
352
351
  }
353
352
  }
@@ -357,7 +356,7 @@ class SpotifyAppRemoteCoordinator {
357
356
  return suspendCancellableCoroutine { continuation ->
358
357
  setResultCallback { result -> continuation.resume(result) }
359
358
  setErrorCallback { throwable ->
360
- continuation.resumeWithException(normalizeUserError(throwable, callsite, uri))
359
+ continuation.resumeWithException(SpotifyAppRemoteErrorMapping.mapUserError(throwable, callsite, uri))
361
360
  }
362
361
  }
363
362
  }
@@ -366,7 +365,7 @@ class SpotifyAppRemoteCoordinator {
366
365
  return suspendCancellableCoroutine { continuation ->
367
366
  setResultCallback { result -> continuation.resume(result) }
368
367
  setErrorCallback { throwable ->
369
- continuation.resumeWithException(normalizeUserError(throwable, callsite, ""))
368
+ continuation.resumeWithException(SpotifyAppRemoteErrorMapping.mapUserError(throwable, callsite, ""))
370
369
  }
371
370
  }
372
371
  }
@@ -375,7 +374,7 @@ class SpotifyAppRemoteCoordinator {
375
374
  return suspendCancellableCoroutine { continuation ->
376
375
  setResultCallback { result -> continuation.resume(result) }
377
376
  setErrorCallback { throwable ->
378
- continuation.resumeWithException(normalizeContentError(throwable, callsite))
377
+ continuation.resumeWithException(SpotifyAppRemoteErrorMapping.mapContentError(throwable, callsite))
379
378
  }
380
379
  }
381
380
  }
@@ -384,68 +383,11 @@ class SpotifyAppRemoteCoordinator {
384
383
  return suspendCancellableCoroutine { continuation ->
385
384
  setResultCallback { result -> continuation.resume(result) }
386
385
  setErrorCallback { throwable ->
387
- continuation.resumeWithException(normalizeImagesError(throwable, callsite))
386
+ continuation.resumeWithException(SpotifyAppRemoteErrorMapping.mapImagesError(throwable, callsite))
388
387
  }
389
388
  }
390
389
  }
391
390
 
392
- private fun normalizePlayerError(throwable: Throwable, callsite: String): CodedException {
393
- val msg = throwable.message ?: "Unknown error"
394
- return when {
395
- msg.contains("premium", ignoreCase = true) -> PlayerPremiumRequiredException(callsite)
396
- msg.contains("disconnected", ignoreCase = true) ||
397
- msg.contains("not connected", ignoreCase = true) ->
398
- PlayerConnectionLostException("$callsite: $msg", throwable)
399
- msg.contains("not allowed", ignoreCase = true) ||
400
- msg.contains("restriction", ignoreCase = true) ->
401
- PlayerOperationNotAllowedException("$callsite: $msg", throwable)
402
- else -> PlayerUnknownException("$callsite: $msg", throwable)
403
- }
404
- }
405
-
406
- private fun normalizeUserError(throwable: Throwable, callsite: String, uri: String): CodedException {
407
- val msg = throwable.message ?: "Unknown error"
408
- return when {
409
- msg.contains("disconnected", ignoreCase = true) ||
410
- msg.contains("not connected", ignoreCase = true) ->
411
- UserConnectionLostException("$callsite: $msg", throwable)
412
- msg.contains("uri", ignoreCase = true) ||
413
- msg.contains("invalid", ignoreCase = true) ->
414
- UserInvalidURIException(uri, throwable)
415
- msg.contains("not allowed", ignoreCase = true) ||
416
- msg.contains("restriction", ignoreCase = true) ->
417
- UserOperationNotAllowedException("$callsite: $msg", throwable)
418
- else -> UserUnknownException("$callsite: $msg", throwable)
419
- }
420
- }
421
-
422
- private fun normalizeContentError(throwable: Throwable, callsite: String): CodedException {
423
- val msg = throwable.message ?: "Unknown error"
424
- return when {
425
- msg.contains("disconnected", ignoreCase = true) ||
426
- msg.contains("not connected", ignoreCase = true) ->
427
- ContentConnectionLostException("$callsite: $msg", throwable)
428
- msg.contains("not supported", ignoreCase = true) ||
429
- msg.contains("unsupported", ignoreCase = true) ->
430
- ContentApiUnavailableException("$callsite: content API unavailable on this Spotify app version", throwable)
431
- else -> ContentUnknownException("$callsite: $msg", throwable)
432
- }
433
- }
434
-
435
- private fun normalizeImagesError(throwable: Throwable, callsite: String): CodedException {
436
- val msg = throwable.message ?: "Unknown error"
437
- return when {
438
- msg.contains("disconnected", ignoreCase = true) ||
439
- msg.contains("not connected", ignoreCase = true) ->
440
- ImagesNotConnectedException(callsite)
441
- msg.contains("invalid", ignoreCase = true) ->
442
- ImagesInvalidURIException("$callsite: invalid image identifier", throwable)
443
- msg.contains("load", ignoreCase = true) || msg.contains("image", ignoreCase = true) ->
444
- ImagesLoadFailedException("$callsite: $msg", throwable)
445
- else -> ImagesUnknownException("$callsite: $msg", throwable)
446
- }
447
- }
448
-
449
391
  private fun writeBitmapToTempFile(bitmap: Bitmap, cacheDir: File): File {
450
392
  val out = File(cacheDir, "expo-spotify-image-${System.currentTimeMillis()}-${(0..9999).random()}.png")
451
393
  FileOutputStream(out).use { stream ->
@@ -0,0 +1,106 @@
1
+ package expo.modules.spotifysdk
2
+
3
+ import com.spotify.android.appremote.api.error.SpotifyConnectionTerminatedException
4
+ import com.spotify.android.appremote.api.error.SpotifyDisconnectedException
5
+ import com.spotify.android.appremote.api.error.SpotifyRemoteServiceException
6
+ import com.spotify.protocol.client.error.RemoteClientException
7
+ import com.spotify.protocol.error.SpotifyAppRemoteException
8
+ import expo.modules.kotlin.exception.CodedException
9
+
10
+ /**
11
+ * Maps Spotify App Remote SDK throwables into per-namespace [CodedException] types.
12
+ *
13
+ * Typed SDK exceptions are checked first; message heuristics are a fallback aligned
14
+ * with [ios/SpotifyAppRemoteErrorMapping.swift]. See [docs/app-remote-error-mapping.md].
15
+ */
16
+ object SpotifyAppRemoteErrorMapping {
17
+ private fun connectionLostMessage(callsite: String): String =
18
+ "$callsite: connection to Spotify app was terminated"
19
+
20
+ private fun isConnectionTerminated(throwable: Throwable): Boolean =
21
+ throwable is SpotifyConnectionTerminatedException ||
22
+ throwable is SpotifyDisconnectedException
23
+
24
+ private fun messageIndicatesConnectionLost(message: String): Boolean {
25
+ val lower = message.lowercase()
26
+ return lower.contains("disconnected") || lower.contains("not connected")
27
+ }
28
+
29
+ private fun containsRestriction(message: String): Boolean {
30
+ val lower = message.lowercase()
31
+ return lower.contains("not allowed") || lower.contains("restriction")
32
+ }
33
+
34
+ /** Request-level failures from the Spotify protocol layer (mirrors iOS `requestFailedError`). */
35
+ private fun isRequestFailed(throwable: Throwable): Boolean =
36
+ throwable is RemoteClientException ||
37
+ throwable is SpotifyAppRemoteException ||
38
+ throwable is SpotifyRemoteServiceException
39
+
40
+ fun mapPlayerError(throwable: Throwable, callsite: String): CodedException {
41
+ val message = throwable.message ?: "Unknown error"
42
+ if (isConnectionTerminated(throwable) || messageIndicatesConnectionLost(message)) {
43
+ return PlayerConnectionLostException(connectionLostMessage(callsite), throwable)
44
+ }
45
+ if (message.contains("premium", ignoreCase = true)) {
46
+ return PlayerPremiumRequiredException(
47
+ "$callsite: Spotify Premium is required for on-demand playback",
48
+ )
49
+ }
50
+ if (containsRestriction(message)) {
51
+ return PlayerOperationNotAllowedException("$callsite: $message", throwable)
52
+ }
53
+ if (isRequestFailed(throwable) && message.contains("invalid", ignoreCase = true)) {
54
+ return PlayerInvalidParameterException("$callsite: $message", throwable)
55
+ }
56
+ return PlayerUnknownException("$callsite: $message", throwable)
57
+ }
58
+
59
+ fun mapUserError(throwable: Throwable, callsite: String, uri: String): CodedException {
60
+ val message = throwable.message ?: "Unknown error"
61
+ if (isConnectionTerminated(throwable) || messageIndicatesConnectionLost(message)) {
62
+ return UserConnectionLostException(connectionLostMessage(callsite), throwable)
63
+ }
64
+ if (
65
+ isRequestFailed(throwable) &&
66
+ (message.contains("uri", ignoreCase = true) || message.contains("invalid", ignoreCase = true))
67
+ ) {
68
+ return UserInvalidURIException(uri, throwable)
69
+ }
70
+ if (containsRestriction(message)) {
71
+ return UserOperationNotAllowedException("$callsite: $message", throwable)
72
+ }
73
+ return UserUnknownException("$callsite: $message", throwable)
74
+ }
75
+
76
+ fun mapContentError(throwable: Throwable, callsite: String): CodedException {
77
+ val message = throwable.message ?: "Unknown error"
78
+ if (isConnectionTerminated(throwable) || messageIndicatesConnectionLost(message)) {
79
+ return ContentConnectionLostException(connectionLostMessage(callsite), throwable)
80
+ }
81
+ if (isRequestFailed(throwable)) {
82
+ val lower = message.lowercase()
83
+ if (lower.contains("not supported") || lower.contains("unsupported")) {
84
+ return ContentApiUnavailableException(
85
+ "$callsite: content API is unavailable on this Spotify app version",
86
+ throwable,
87
+ )
88
+ }
89
+ }
90
+ return ContentUnknownException("$callsite: $message", throwable)
91
+ }
92
+
93
+ fun mapImagesError(throwable: Throwable, callsite: String): CodedException {
94
+ val message = throwable.message ?: "Unknown error"
95
+ if (isConnectionTerminated(throwable) || messageIndicatesConnectionLost(message)) {
96
+ return ImagesNotConnectedException(connectionLostMessage(callsite))
97
+ }
98
+ if (isRequestFailed(throwable) && message.contains("invalid", ignoreCase = true)) {
99
+ return ImagesInvalidURIException("$callsite: invalid image identifier", throwable)
100
+ }
101
+ if (isRequestFailed(throwable)) {
102
+ return ImagesLoadFailedException("$callsite: Spotify rejected image request", throwable)
103
+ }
104
+ return ImagesUnknownException("$callsite: $message", throwable)
105
+ }
106
+ }
@@ -20,16 +20,15 @@ import kotlin.coroutines.resumeWithException
20
20
  *
21
21
  * Server contract:
22
22
  * POST {tokenSwapURL}
23
- * body: code=<auth-code>&redirect_uri=<redirect-uri>&client_id=<client-id>
23
+ * body: code=<auth-code>
24
24
  * 200 -> { access_token, expires_in, refresh_token?, scope? }
25
25
  *
26
26
  * POST {tokenRefreshURL}
27
- * body: refresh_token=<token>&client_id=<client-id>
27
+ * body: refresh_token=<token>
28
28
  * 200 -> { access_token, expires_in, refresh_token?, scope? }
29
29
  */
30
30
  class SpotifyTokenSwapClient(
31
31
  private val sdkVersion: String,
32
- private val clientId: String,
33
32
  ) {
34
33
  private val client: OkHttpClient = OkHttpClient.Builder()
35
34
  .connectTimeout(30, TimeUnit.SECONDS)
@@ -41,14 +40,11 @@ class SpotifyTokenSwapClient(
41
40
 
42
41
  suspend fun swap(
43
42
  code: String,
44
- redirectUri: String,
45
43
  tokenSwapURL: String,
46
44
  requestedScopes: List<String>,
47
45
  ): SpotifySessionPayload {
48
46
  val body = FormBody.Builder()
49
47
  .add("code", code)
50
- .add("redirect_uri", redirectUri)
51
- .add("client_id", clientId)
52
48
  .build()
53
49
  val request = Request.Builder()
54
50
  .url(tokenSwapURL)
@@ -66,7 +62,6 @@ class SpotifyTokenSwapClient(
66
62
  ): SpotifySessionPayload {
67
63
  val body = FormBody.Builder()
68
64
  .add("refresh_token", refreshToken)
69
- .add("client_id", clientId)
70
65
  .build()
71
66
  val request = Request.Builder()
72
67
  .url(tokenRefreshURL)
package/app.plugin.js CHANGED
@@ -1 +1 @@
1
- module.exports = require("./plugin/build");
1
+ module.exports = require("./plugin/build/withSpotifySdkConfig");
@@ -1,5 +1,5 @@
1
- import { EventSubscription } from "expo-modules-core";
2
- import { AppRemoteErrorCode } from "./error";
1
+ import type { EventSubscription } from "expo-modules-core";
2
+ import { type AppRemoteErrorCode } from "./error";
3
3
  export type { AppRemoteErrorCode } from "./error";
4
4
  export { AppRemoteError } from "./error";
5
5
  /** Current state of the IPC connection to the Spotify app. */
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/app-remote/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAGtD,OAAO,EAAkB,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAE7D,YAAY,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAClD,OAAO,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAMzC,8DAA8D;AAC9D,MAAM,MAAM,eAAe,GAAG,cAAc,GAAG,YAAY,GAAG,WAAW,CAAC;AAE1E,oDAAoD;AACpD,MAAM,WAAW,0BAA0B;IACzC,KAAK,EAAE,eAAe,CAAC;CACxB;AAED,8CAA8C;AAC9C,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,kBAAkB,CAAC;IACzB,OAAO,EAAE,MAAM,CAAC;CACjB;AAqCD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,eAAO,MAAM,SAAS;IACpB;;;;;;;;;;;OAWG;oCACkB,MAAM,KAAG,OAAO,CAAC,IAAI,CAAC;IAM3C;;;OAGG;+BACW,OAAO,CAAC,IAAI,CAAC;IAI3B;;;;OAIG;gCACY,OAAO;IAItB;;;OAGG;uCACmB,OAAO,CAAC,eAAe,CAAC;IAI9C;;;;;;;;;OASG;2BACS,CAAC,SAAS,uBAAuB,GAAG,iBAAiB,SACxD,CAAC,YACE,CAAC,SAAS,uBAAuB,GACvC,CAAC,KAAK,EAAE,0BAA0B,KAAK,IAAI,GAC3C,CAAC,KAAK,EAAE,oBAAoB,KAAK,IAAI,KACxC,iBAAiB;CAUZ,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/app-remote/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAG3D,OAAO,EAAkB,KAAK,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAGlE,YAAY,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAClD,OAAO,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAMzC,8DAA8D;AAC9D,MAAM,MAAM,eAAe,GAAG,cAAc,GAAG,YAAY,GAAG,WAAW,CAAC;AAE1E,oDAAoD;AACpD,MAAM,WAAW,0BAA0B;IACzC,KAAK,EAAE,eAAe,CAAC;CACxB;AAED,8CAA8C;AAC9C,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,kBAAkB,CAAC;IACzB,OAAO,EAAE,MAAM,CAAC;CACjB;AAqBD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,eAAO,MAAM,SAAS;IACpB;;;;;;;;;;;OAWG;oCACkB,MAAM,KAAG,OAAO,CAAC,IAAI,CAAC;IAM3C;;;OAGG;+BACW,OAAO,CAAC,IAAI,CAAC;IAI3B;;;;OAIG;gCACY,OAAO;IAItB;;;OAGG;uCACmB,OAAO,CAAC,eAAe,CAAC;IAI9C;;;;;;;;;OASG;2BACS,CAAC,SAAS,uBAAuB,GAAG,iBAAiB,SACxD,CAAC,YACE,CAAC,SAAS,uBAAuB,GACvC,CAAC,KAAK,EAAE,0BAA0B,KAAK,IAAI,GAC3C,CAAC,KAAK,EAAE,oBAAoB,KAAK,IAAI,KACxC,iBAAiB;CAUZ,CAAC"}
@@ -1,33 +1,20 @@
1
1
  import ExpoSpotifySDKModule from "../ExpoSpotifySDKModule";
2
2
  import { AppRemoteError } from "./error";
3
+ import { createNativeErrorRethrow } from "../internal/native-errors";
3
4
  export { AppRemoteError } from "./error";
4
5
  // ---------------------------------------------------------------------------
5
6
  // Internal helpers
6
7
  // ---------------------------------------------------------------------------
7
- const VALID_APPREMOTE_CODES = new Set([
8
- "CONNECTION_FAILED",
9
- "CONNECTION_LOST",
10
- "NOT_CONNECTED",
11
- "UNKNOWN",
12
- ]);
13
- const CAUSE_SEPARATOR = "→ Caused by: ";
14
- function unwrapReason(message) {
15
- const idx = message.lastIndexOf(CAUSE_SEPARATOR);
16
- return idx === -1 ? message : message.slice(idx + CAUSE_SEPARATOR.length);
17
- }
18
- function rethrowAsAppRemoteError(err) {
19
- if (err instanceof AppRemoteError)
20
- throw err;
21
- if (err instanceof Error) {
22
- const reason = unwrapReason(err.message);
23
- const maybeCode = err.code;
24
- if (maybeCode && VALID_APPREMOTE_CODES.has(maybeCode)) {
25
- throw new AppRemoteError(maybeCode, reason);
26
- }
27
- throw new AppRemoteError("UNKNOWN", reason);
28
- }
29
- throw new AppRemoteError("UNKNOWN", String(err));
30
- }
8
+ const rethrowAsAppRemoteError = createNativeErrorRethrow({
9
+ ErrorClass: AppRemoteError,
10
+ unknownCode: "UNKNOWN",
11
+ validCodes: new Set([
12
+ "CONNECTION_FAILED",
13
+ "CONNECTION_LOST",
14
+ "NOT_CONNECTED",
15
+ "UNKNOWN",
16
+ ]),
17
+ });
31
18
  // ---------------------------------------------------------------------------
32
19
  // AppRemote namespace
33
20
  // ---------------------------------------------------------------------------
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/app-remote/index.ts"],"names":[],"mappings":"AAEA,OAAO,oBAAoB,MAAM,yBAAyB,CAAC;AAC3D,OAAO,EAAE,cAAc,EAAsB,MAAM,SAAS,CAAC;AAG7D,OAAO,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAoBzC,8EAA8E;AAC9E,mBAAmB;AACnB,8EAA8E;AAE9E,MAAM,qBAAqB,GAAG,IAAI,GAAG,CAAqB;IACxD,mBAAmB;IACnB,iBAAiB;IACjB,eAAe;IACf,SAAS;CACV,CAAC,CAAC;AAEH,MAAM,eAAe,GAAG,eAAe,CAAC;AAExC,SAAS,YAAY,CAAC,OAAe;IACnC,MAAM,GAAG,GAAG,OAAO,CAAC,WAAW,CAAC,eAAe,CAAC,CAAC;IACjD,OAAO,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,GAAG,GAAG,eAAe,CAAC,MAAM,CAAC,CAAC;AAC5E,CAAC;AAED,SAAS,uBAAuB,CAAC,GAAY;IAC3C,IAAI,GAAG,YAAY,cAAc;QAAE,MAAM,GAAG,CAAC;IAC7C,IAAI,GAAG,YAAY,KAAK,EAAE,CAAC;QACzB,MAAM,MAAM,GAAG,YAAY,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QACzC,MAAM,SAAS,GAAI,GAAiC,CAAC,IAAI,CAAC;QAC1D,IAAI,SAAS,IAAI,qBAAqB,CAAC,GAAG,CAAC,SAA+B,CAAC,EAAE,CAAC;YAC5E,MAAM,IAAI,cAAc,CAAC,SAA+B,EAAE,MAAM,CAAC,CAAC;QACpE,CAAC;QACD,MAAM,IAAI,cAAc,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC;IAC9C,CAAC;IACD,MAAM,IAAI,cAAc,CAAC,SAAS,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;AACnD,CAAC;AAED,8EAA8E;AAC9E,sBAAsB;AACtB,8EAA8E;AAE9E;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,CAAC,MAAM,SAAS,GAAG;IACvB;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,WAAmB;QACzB,OAAO,oBAAoB,CAAC,gBAAgB,CAAC,WAAW,CAAC,CAAC,KAAK,CAC7D,uBAAuB,CACxB,CAAC;IACJ,CAAC;IAED;;;OAGG;IACH,UAAU;QACR,OAAO,oBAAoB,CAAC,mBAAmB,EAAE,CAAC;IACpD,CAAC;IAED;;;;OAIG;IACH,WAAW;QACT,OAAO,oBAAoB,CAAC,oBAAoB,EAAE,CAAC;IACrD,CAAC;IAED;;;OAGG;IACH,kBAAkB;QAChB,OAAO,oBAAoB,CAAC,2BAA2B,EAAE,CAAC;IAC5D,CAAC;IAED;;;;;;;;;OASG;IACH,WAAW,CACT,KAAQ,EACR,QAEyC;QAEzC,MAAM,WAAW,GACf,KAAK,KAAK,uBAAuB;YAC/B,CAAC,CAAC,yBAAyB;YAC3B,CAAC,CAAC,mBAAmB,CAAC;QAC1B,OAAO,oBAAoB,CAAC,WAAW,CACrC,WAAW,EACX,QAAoC,CAChB,CAAC;IACzB,CAAC;CACO,CAAC","sourcesContent":["import { EventSubscription } from \"expo-modules-core\";\n\nimport ExpoSpotifySDKModule from \"../ExpoSpotifySDKModule\";\nimport { AppRemoteError, AppRemoteErrorCode } from \"./error\";\n\nexport type { AppRemoteErrorCode } from \"./error\";\nexport { AppRemoteError } from \"./error\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/** Current state of the IPC connection to the Spotify app. */\nexport type ConnectionState = \"disconnected\" | \"connecting\" | \"connected\";\n\n/** Payload of the `connectionStateChange` event. */\nexport interface ConnectionStateChangeEvent {\n state: ConnectionState;\n}\n\n/** Payload of the `connectionError` event. */\nexport interface ConnectionErrorEvent {\n code: AppRemoteErrorCode;\n message: string;\n}\n\n// ---------------------------------------------------------------------------\n// Internal helpers\n// ---------------------------------------------------------------------------\n\nconst VALID_APPREMOTE_CODES = new Set<AppRemoteErrorCode>([\n \"CONNECTION_FAILED\",\n \"CONNECTION_LOST\",\n \"NOT_CONNECTED\",\n \"UNKNOWN\",\n]);\n\nconst CAUSE_SEPARATOR = \"→ Caused by: \";\n\nfunction unwrapReason(message: string): string {\n const idx = message.lastIndexOf(CAUSE_SEPARATOR);\n return idx === -1 ? message : message.slice(idx + CAUSE_SEPARATOR.length);\n}\n\nfunction rethrowAsAppRemoteError(err: unknown): never {\n if (err instanceof AppRemoteError) throw err;\n if (err instanceof Error) {\n const reason = unwrapReason(err.message);\n const maybeCode = (err as Error & { code?: string }).code;\n if (maybeCode && VALID_APPREMOTE_CODES.has(maybeCode as AppRemoteErrorCode)) {\n throw new AppRemoteError(maybeCode as AppRemoteErrorCode, reason);\n }\n throw new AppRemoteError(\"UNKNOWN\", reason);\n }\n throw new AppRemoteError(\"UNKNOWN\", String(err));\n}\n\n// ---------------------------------------------------------------------------\n// AppRemote namespace\n// ---------------------------------------------------------------------------\n\n/**\n * Spotify App Remote namespace. Manages the IPC connection to the running\n * Spotify app. All `Player`, `User`, `Content`, and `Images` calls require an\n * active connection established via `AppRemote.connect()`.\n *\n * @example\n * ```ts\n * import { AppRemote } from \"@wwdrew/expo-spotify-sdk\";\n *\n * // Connect using the access token from Auth.authenticate()\n * await AppRemote.connect(session.accessToken);\n *\n * const sub = AppRemote.addListener(\"connectionStateChange\", ({ state }) => {\n * console.log(\"connection state:\", state);\n * });\n *\n * // later:\n * await AppRemote.disconnect();\n * sub.remove();\n * ```\n */\nexport const AppRemote = {\n /**\n * Opens a connection to the running Spotify app using the provided access\n * token. Resolves when the connection is established; rejects with an\n * {@link AppRemoteError} if the connection fails.\n *\n * **Android note:** The access token is accepted for API parity with iOS but\n * the Android App Remote SDK does not accept it directly — it uses the\n * session cached in the Spotify app from your earlier `Auth.authenticate()`\n * call. Ensure `Auth.authenticate()` has succeeded before calling `connect()`.\n *\n * Calling `connect()` while already connected is a no-op.\n */\n connect(accessToken: string): Promise<void> {\n return ExpoSpotifySDKModule.appRemoteConnect(accessToken).catch(\n rethrowAsAppRemoteError,\n );\n },\n\n /**\n * Disconnects from the Spotify app. Safe to call when already disconnected.\n * Resolves once the disconnection is complete.\n */\n disconnect(): Promise<void> {\n return ExpoSpotifySDKModule.appRemoteDisconnect();\n },\n\n /**\n * Returns `true` if currently connected to the Spotify app.\n * This is a synchronous snapshot — subscribe to `\"connectionStateChange\"`\n * for reactive updates.\n */\n isConnected(): boolean {\n return ExpoSpotifySDKModule.appRemoteIsConnected();\n },\n\n /**\n * Returns the current {@link ConnectionState} synchronously.\n * Subscribe to `\"connectionStateChange\"` for reactive updates.\n */\n getConnectionState(): Promise<ConnectionState> {\n return ExpoSpotifySDKModule.appRemoteGetConnectionState();\n },\n\n /**\n * Subscribes to connection lifecycle events.\n *\n * | Event | Payload | When |\n * |---|---|---|\n * | `\"connectionStateChange\"` | `{ state: ConnectionState }` | State transitions |\n * | `\"connectionError\"` | `{ code, message }` | Connection failures and drops |\n *\n * Returns a `Subscription` — call `.remove()` to unsubscribe.\n */\n addListener<E extends \"connectionStateChange\" | \"connectionError\">(\n event: E,\n listener: E extends \"connectionStateChange\"\n ? (event: ConnectionStateChangeEvent) => void\n : (event: ConnectionErrorEvent) => void,\n ): EventSubscription {\n const nativeEvent =\n event === \"connectionStateChange\"\n ? \"onConnectionStateChange\"\n : \"onConnectionError\";\n return ExpoSpotifySDKModule.addListener(\n nativeEvent,\n listener as (event: unknown) => void,\n ) as EventSubscription;\n },\n} as const;\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/app-remote/index.ts"],"names":[],"mappings":"AAEA,OAAO,oBAAoB,MAAM,yBAAyB,CAAC;AAC3D,OAAO,EAAE,cAAc,EAA2B,MAAM,SAAS,CAAC;AAClE,OAAO,EAAE,wBAAwB,EAAE,MAAM,2BAA2B,CAAC;AAGrE,OAAO,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAoBzC,8EAA8E;AAC9E,mBAAmB;AACnB,8EAA8E;AAE9E,MAAM,uBAAuB,GAAG,wBAAwB,CAAC;IACvD,UAAU,EAAE,cAAc;IAC1B,WAAW,EAAE,SAAS;IACtB,UAAU,EAAE,IAAI,GAAG,CAAqB;QACtC,mBAAmB;QACnB,iBAAiB;QACjB,eAAe;QACf,SAAS;KACV,CAAC;CACH,CAAC,CAAC;AAEH,8EAA8E;AAC9E,sBAAsB;AACtB,8EAA8E;AAE9E;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,CAAC,MAAM,SAAS,GAAG;IACvB;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,WAAmB;QACzB,OAAO,oBAAoB,CAAC,gBAAgB,CAAC,WAAW,CAAC,CAAC,KAAK,CAC7D,uBAAuB,CACxB,CAAC;IACJ,CAAC;IAED;;;OAGG;IACH,UAAU;QACR,OAAO,oBAAoB,CAAC,mBAAmB,EAAE,CAAC;IACpD,CAAC;IAED;;;;OAIG;IACH,WAAW;QACT,OAAO,oBAAoB,CAAC,oBAAoB,EAAE,CAAC;IACrD,CAAC;IAED;;;OAGG;IACH,kBAAkB;QAChB,OAAO,oBAAoB,CAAC,2BAA2B,EAAE,CAAC;IAC5D,CAAC;IAED;;;;;;;;;OASG;IACH,WAAW,CACT,KAAQ,EACR,QAEyC;QAEzC,MAAM,WAAW,GACf,KAAK,KAAK,uBAAuB;YAC/B,CAAC,CAAC,yBAAyB;YAC3B,CAAC,CAAC,mBAAmB,CAAC;QAC1B,OAAO,oBAAoB,CAAC,WAAW,CACrC,WAAW,EACX,QAAoC,CAChB,CAAC;IACzB,CAAC;CACO,CAAC","sourcesContent":["import type { EventSubscription } from \"expo-modules-core\";\n\nimport ExpoSpotifySDKModule from \"../ExpoSpotifySDKModule\";\nimport { AppRemoteError, type AppRemoteErrorCode } from \"./error\";\nimport { createNativeErrorRethrow } from \"../internal/native-errors\";\n\nexport type { AppRemoteErrorCode } from \"./error\";\nexport { AppRemoteError } from \"./error\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/** Current state of the IPC connection to the Spotify app. */\nexport type ConnectionState = \"disconnected\" | \"connecting\" | \"connected\";\n\n/** Payload of the `connectionStateChange` event. */\nexport interface ConnectionStateChangeEvent {\n state: ConnectionState;\n}\n\n/** Payload of the `connectionError` event. */\nexport interface ConnectionErrorEvent {\n code: AppRemoteErrorCode;\n message: string;\n}\n\n// ---------------------------------------------------------------------------\n// Internal helpers\n// ---------------------------------------------------------------------------\n\nconst rethrowAsAppRemoteError = createNativeErrorRethrow({\n ErrorClass: AppRemoteError,\n unknownCode: \"UNKNOWN\",\n validCodes: new Set<AppRemoteErrorCode>([\n \"CONNECTION_FAILED\",\n \"CONNECTION_LOST\",\n \"NOT_CONNECTED\",\n \"UNKNOWN\",\n ]),\n});\n\n// ---------------------------------------------------------------------------\n// AppRemote namespace\n// ---------------------------------------------------------------------------\n\n/**\n * Spotify App Remote namespace. Manages the IPC connection to the running\n * Spotify app. All `Player`, `User`, `Content`, and `Images` calls require an\n * active connection established via `AppRemote.connect()`.\n *\n * @example\n * ```ts\n * import { AppRemote } from \"@wwdrew/expo-spotify-sdk\";\n *\n * // Connect using the access token from Auth.authenticate()\n * await AppRemote.connect(session.accessToken);\n *\n * const sub = AppRemote.addListener(\"connectionStateChange\", ({ state }) => {\n * console.log(\"connection state:\", state);\n * });\n *\n * // later:\n * await AppRemote.disconnect();\n * sub.remove();\n * ```\n */\nexport const AppRemote = {\n /**\n * Opens a connection to the running Spotify app using the provided access\n * token. Resolves when the connection is established; rejects with an\n * {@link AppRemoteError} if the connection fails.\n *\n * **Android note:** The access token is accepted for API parity with iOS but\n * the Android App Remote SDK does not accept it directly — it uses the\n * session cached in the Spotify app from your earlier `Auth.authenticate()`\n * call. Ensure `Auth.authenticate()` has succeeded before calling `connect()`.\n *\n * Calling `connect()` while already connected is a no-op.\n */\n connect(accessToken: string): Promise<void> {\n return ExpoSpotifySDKModule.appRemoteConnect(accessToken).catch(\n rethrowAsAppRemoteError,\n );\n },\n\n /**\n * Disconnects from the Spotify app. Safe to call when already disconnected.\n * Resolves once the disconnection is complete.\n */\n disconnect(): Promise<void> {\n return ExpoSpotifySDKModule.appRemoteDisconnect();\n },\n\n /**\n * Returns `true` if currently connected to the Spotify app.\n * This is a synchronous snapshot — subscribe to `\"connectionStateChange\"`\n * for reactive updates.\n */\n isConnected(): boolean {\n return ExpoSpotifySDKModule.appRemoteIsConnected();\n },\n\n /**\n * Returns the current {@link ConnectionState} synchronously.\n * Subscribe to `\"connectionStateChange\"` for reactive updates.\n */\n getConnectionState(): Promise<ConnectionState> {\n return ExpoSpotifySDKModule.appRemoteGetConnectionState();\n },\n\n /**\n * Subscribes to connection lifecycle events.\n *\n * | Event | Payload | When |\n * |---|---|---|\n * | `\"connectionStateChange\"` | `{ state: ConnectionState }` | State transitions |\n * | `\"connectionError\"` | `{ code, message }` | Connection failures and drops |\n *\n * Returns a `Subscription` — call `.remove()` to unsubscribe.\n */\n addListener<E extends \"connectionStateChange\" | \"connectionError\">(\n event: E,\n listener: E extends \"connectionStateChange\"\n ? (event: ConnectionStateChangeEvent) => void\n : (event: ConnectionErrorEvent) => void,\n ): EventSubscription {\n const nativeEvent =\n event === \"connectionStateChange\"\n ? \"onConnectionStateChange\"\n : \"onConnectionError\";\n return ExpoSpotifySDKModule.addListener(\n nativeEvent,\n listener as (event: unknown) => void,\n ) as EventSubscription;\n },\n} as const;\n"]}
@@ -1,5 +1,5 @@
1
- import { EventSubscription } from "expo-modules-core";
2
- import { AuthErrorCode } from "./error";
1
+ import type { EventSubscription } from "expo-modules-core";
2
+ import { type AuthErrorCode } from "./error";
3
3
  export type { AuthErrorCode } from "./error";
4
4
  export { AuthError } from "./error";
5
5
  export interface SpotifySession {
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/auth/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAY,MAAM,mBAAmB,CAAC;AAGhE,OAAO,EAAa,aAAa,EAAE,MAAM,SAAS,CAAC;AAEnD,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAC7C,OAAO,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAMpC,MAAM,WAAW,cAAc;IAC7B,0BAA0B;IAC1B,WAAW,EAAE,MAAM,CAAC;IACpB;;;;OAIG;IACH,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,uDAAuD;IACvD,cAAc,EAAE,MAAM,CAAC;IACvB,+EAA+E;IAC/E,MAAM,EAAE,YAAY,EAAE,CAAC;CACxB;AAED,MAAM,MAAM,YAAY,GACpB,kBAAkB,GAClB,0BAA0B,GAC1B,4BAA4B,GAC5B,6BAA6B,GAC7B,oBAAoB,GACpB,WAAW,GACX,uBAAuB,GACvB,6BAA6B,GAC7B,yBAAyB,GACzB,wBAAwB,GACxB,oBAAoB,GACpB,kBAAkB,GAClB,eAAe,GACf,2BAA2B,GAC3B,qBAAqB,GACrB,mBAAmB,GACnB,iBAAiB,GACjB,mBAAmB,CAAC;AAExB,MAAM,WAAW,kBAAkB;IACjC,gEAAgE;IAChE,MAAM,EAAE,YAAY,EAAE,CAAC;IACvB;;;;OAIG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;OAGG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB;;;OAGG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAED,MAAM,WAAW,aAAa;IAC5B,oEAAoE;IACpE,YAAY,EAAE,MAAM,CAAC;IACrB,iDAAiD;IACjD,eAAe,EAAE,MAAM,CAAC;IACxB;;;OAGG;IACH,MAAM,CAAC,EAAE,YAAY,EAAE,CAAC;CACzB;AAED;;GAEG;AACH,MAAM,MAAM,kBAAkB,GAC1B;IAAE,IAAI,EAAE,aAAa,CAAC;IAAC,OAAO,EAAE,cAAc,CAAA;CAAE,GAChD;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,OAAO,EAAE,cAAc,CAAA;CAAE,GAC7C;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,KAAK,EAAE;QAAE,IAAI,EAAE,aAAa,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE,CAAC;AAgFzE;;;;;;;;;GASG;AACH,eAAO,MAAM,IAAI;IACf;;OAEG;gCACY,OAAO;IAItB;;;OAGG;oCACkB,kBAAkB,KAAG,OAAO,CAAC,cAAc,CAAC;IAmBjE;;;;OAIG;+BACa,aAAa,KAAG,OAAO,CAAC,cAAc,CAAC;IAgBvD;;;;;;;OAOG;kCACc,OAAO,CAAC,IAAI,CAAC;IAO9B;;;;;;;;;;;;;;;;;;;OAmBG;kCAEM,eAAe,YACZ,CAAC,KAAK,EAAE,kBAAkB,KAAK,IAAI,KAC5C,iBAAiB;CAMZ,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/auth/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAI3D,OAAO,EAAa,KAAK,aAAa,EAAE,MAAM,SAAS,CAAC;AAGxD,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAC7C,OAAO,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAMpC,MAAM,WAAW,cAAc;IAC7B,0BAA0B;IAC1B,WAAW,EAAE,MAAM,CAAC;IACpB;;;;OAIG;IACH,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,uDAAuD;IACvD,cAAc,EAAE,MAAM,CAAC;IACvB,+EAA+E;IAC/E,MAAM,EAAE,YAAY,EAAE,CAAC;CACxB;AAED,MAAM,MAAM,YAAY,GACpB,kBAAkB,GAClB,0BAA0B,GAC1B,4BAA4B,GAC5B,6BAA6B,GAC7B,oBAAoB,GACpB,WAAW,GACX,uBAAuB,GACvB,6BAA6B,GAC7B,yBAAyB,GACzB,wBAAwB,GACxB,oBAAoB,GACpB,kBAAkB,GAClB,eAAe,GACf,2BAA2B,GAC3B,qBAAqB,GACrB,mBAAmB,GACnB,iBAAiB,GACjB,mBAAmB,CAAC;AAExB,MAAM,WAAW,kBAAkB;IACjC,gEAAgE;IAChE,MAAM,EAAE,YAAY,EAAE,CAAC;IACvB;;;;OAIG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;OAGG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB;;;OAGG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAED,MAAM,WAAW,aAAa;IAC5B,oEAAoE;IACpE,YAAY,EAAE,MAAM,CAAC;IACrB,iDAAiD;IACjD,eAAe,EAAE,MAAM,CAAC;IACxB;;;OAGG;IACH,MAAM,CAAC,EAAE,YAAY,EAAE,CAAC;CACzB;AAED;;GAEG;AACH,MAAM,MAAM,kBAAkB,GAC1B;IAAE,IAAI,EAAE,aAAa,CAAC;IAAC,OAAO,EAAE,cAAc,CAAA;CAAE,GAChD;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,OAAO,EAAE,cAAc,CAAA;CAAE,GAC7C;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,KAAK,EAAE;QAAE,IAAI,EAAE,aAAa,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE,CAAC;AA+DzE;;;;;;;;;GASG;AACH,eAAO,MAAM,IAAI;IACf;;OAEG;gCACY,OAAO;IAItB;;;OAGG;oCACkB,kBAAkB,KAAG,OAAO,CAAC,cAAc,CAAC;IAsBjE;;;;OAIG;+BACa,aAAa,KAAG,OAAO,CAAC,cAAc,CAAC;IAgBvD;;;;;;;OAOG;kCACc,OAAO,CAAC,IAAI,CAAC;IAO9B;;;;;;;;;;;;;;;;;;;OAmBG;kCAEM,eAAe,YACZ,CAAC,KAAK,EAAE,kBAAkB,KAAK,IAAI,KAC5C,iBAAiB;CAMZ,CAAC"}
@@ -1,6 +1,7 @@
1
1
  import { Platform } from "expo-modules-core";
2
2
  import ExpoSpotifySDKModule from "../ExpoSpotifySDKModule";
3
3
  import { AuthError } from "./error";
4
+ import { createNativeErrorRethrow } from "../internal/native-errors";
4
5
  export { AuthError } from "./error";
5
6
  // ---------------------------------------------------------------------------
6
7
  // Internal helpers
@@ -10,40 +11,22 @@ const ANDROID_TOKEN_FLOW_WARNING = "[expo-spotify-sdk] You are using Auth.authen
10
11
  "the actual granted scopes through this path; see the README's " +
11
12
  "'Android implicit (TOKEN) flow is not recommended' section.";
12
13
  let warnedAboutAndroidTokenFlow = false;
13
- const VALID_AUTH_CODES = new Set([
14
- "USER_CANCELLED",
15
- "AUTH_IN_PROGRESS",
16
- "INVALID_CONFIG",
17
- "NETWORK_ERROR",
18
- "TOKEN_SWAP_FAILED",
19
- "TOKEN_SWAP_PARSE_ERROR",
20
- "SPOTIFY_NOT_INSTALLED",
21
- "AUTH_ERROR",
22
- "UNKNOWN",
23
- ]);
24
- const CAUSE_SEPARATOR = "→ Caused by: ";
25
- const LEGACY_CODE_PREFIX_RE = /^([A-Z_][A-Z0-9_]*):\s*(.*)$/s;
26
- function unwrapReason(message) {
27
- const idx = message.lastIndexOf(CAUSE_SEPARATOR);
28
- return idx === -1 ? message : message.slice(idx + CAUSE_SEPARATOR.length);
29
- }
30
- function rethrowAsAuthError(err) {
31
- if (err instanceof AuthError)
32
- throw err;
33
- if (err instanceof Error) {
34
- const reason = unwrapReason(err.message);
35
- const maybeCode = err.code;
36
- if (maybeCode && VALID_AUTH_CODES.has(maybeCode)) {
37
- throw new AuthError(maybeCode, reason);
38
- }
39
- const m = reason.match(LEGACY_CODE_PREFIX_RE);
40
- if (m && VALID_AUTH_CODES.has(m[1])) {
41
- throw new AuthError(m[1], m[2]);
42
- }
43
- throw new AuthError("UNKNOWN", reason);
44
- }
45
- throw new AuthError("UNKNOWN", String(err));
46
- }
14
+ const rethrowAsAuthError = createNativeErrorRethrow({
15
+ ErrorClass: AuthError,
16
+ unknownCode: "UNKNOWN",
17
+ legacyCodePrefix: true,
18
+ validCodes: new Set([
19
+ "USER_CANCELLED",
20
+ "AUTH_IN_PROGRESS",
21
+ "INVALID_CONFIG",
22
+ "NETWORK_ERROR",
23
+ "TOKEN_SWAP_FAILED",
24
+ "TOKEN_SWAP_PARSE_ERROR",
25
+ "SPOTIFY_NOT_INSTALLED",
26
+ "AUTH_ERROR",
27
+ "UNKNOWN",
28
+ ]),
29
+ });
47
30
  function normaliseSession(raw) {
48
31
  if (!raw || typeof raw !== "object") {
49
32
  throw new AuthError("UNKNOWN", "Native module returned a non-object session");
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/auth/index.ts"],"names":[],"mappings":"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","sourcesContent":["import { EventSubscription, Platform } from \"expo-modules-core\";\n\nimport ExpoSpotifySDKModule from \"../ExpoSpotifySDKModule\";\nimport { AuthError, AuthErrorCode } from \"./error\";\n\nexport type { AuthErrorCode } from \"./error\";\nexport { AuthError } from \"./error\";\n\n// ---------------------------------------------------------------------------\n// Auth-specific types\n// ---------------------------------------------------------------------------\n\nexport interface SpotifySession {\n /** OAuth access token. */\n accessToken: string;\n /**\n * OAuth refresh token. `null` on Android when no `tokenSwapURL` is provided\n * (the Spotify Android SDK does not expose a refresh token for implicit\n * grants).\n */\n refreshToken: string | null;\n /** Expiration timestamp as Unix epoch milliseconds. */\n expirationDate: number;\n /** Scopes the access token was granted (or requested, on Android implicit). */\n scopes: SpotifyScope[];\n}\n\nexport type SpotifyScope =\n | \"ugc-image-upload\"\n | \"user-read-playback-state\"\n | \"user-modify-playback-state\"\n | \"user-read-currently-playing\"\n | \"app-remote-control\"\n | \"streaming\"\n | \"playlist-read-private\"\n | \"playlist-read-collaborative\"\n | \"playlist-modify-private\"\n | \"playlist-modify-public\"\n | \"user-follow-modify\"\n | \"user-follow-read\"\n | \"user-top-read\"\n | \"user-read-recently-played\"\n | \"user-library-modify\"\n | \"user-library-read\"\n | \"user-read-email\"\n | \"user-read-private\";\n\nexport interface AuthenticateConfig {\n /** OAuth scopes to request. Must contain at least one entry. */\n scopes: SpotifyScope[];\n /**\n * If supplied, requests an authorization code rather than an implicit\n * token, then POSTs the code to this URL to exchange it for tokens.\n * **Required on Android** to receive a usable `refreshToken`.\n */\n tokenSwapURL?: string;\n /**\n * Used by the iOS SDK to refresh access tokens automatically, and by\n * `Auth.refresh()` on both platforms.\n */\n tokenRefreshURL?: string;\n /**\n * If `true`, forces Spotify to show the authorization dialog even when the\n * user already has an active session. Defaults to `false`.\n */\n showDialog?: boolean;\n}\n\nexport interface RefreshConfig {\n /** The refresh token from a previous `Auth.authenticate()` call. */\n refreshToken: string;\n /** URL of your token refresh server endpoint. */\n tokenRefreshURL: string;\n /**\n * Scopes that were granted by the previous session. Used as a fallback when\n * the refresh response omits the `scope` field.\n */\n scopes?: SpotifyScope[];\n}\n\n/**\n * Payload delivered to `Auth.addListener(\"sessionChange\", ...)` subscribers.\n */\nexport type SessionChangeEvent =\n | { type: \"didInitiate\"; session: SpotifySession }\n | { type: \"didRenew\"; session: SpotifySession }\n | { type: \"didFail\"; error: { code: AuthErrorCode; message: string } };\n\n// ---------------------------------------------------------------------------\n// Internal helpers\n// ---------------------------------------------------------------------------\n\nconst ANDROID_TOKEN_FLOW_WARNING =\n \"[expo-spotify-sdk] You are using Auth.authenticate on Android without a \" +\n \"tokenSwapURL. The Spotify Android SDK does NOT return a refresh token or \" +\n \"the actual granted scopes through this path; see the README's \" +\n \"'Android implicit (TOKEN) flow is not recommended' section.\";\n\nlet warnedAboutAndroidTokenFlow = false;\n\nconst VALID_AUTH_CODES = new Set<AuthErrorCode>([\n \"USER_CANCELLED\",\n \"AUTH_IN_PROGRESS\",\n \"INVALID_CONFIG\",\n \"NETWORK_ERROR\",\n \"TOKEN_SWAP_FAILED\",\n \"TOKEN_SWAP_PARSE_ERROR\",\n \"SPOTIFY_NOT_INSTALLED\",\n \"AUTH_ERROR\",\n \"UNKNOWN\",\n]);\n\nconst CAUSE_SEPARATOR = \"→ Caused by: \";\nconst LEGACY_CODE_PREFIX_RE = /^([A-Z_][A-Z0-9_]*):\\s*(.*)$/s;\n\nfunction unwrapReason(message: string): string {\n const idx = message.lastIndexOf(CAUSE_SEPARATOR);\n return idx === -1 ? message : message.slice(idx + CAUSE_SEPARATOR.length);\n}\n\nfunction rethrowAsAuthError(err: unknown): never {\n if (err instanceof AuthError) throw err;\n if (err instanceof Error) {\n const reason = unwrapReason(err.message);\n const maybeCode = (err as Error & { code?: string }).code;\n if (maybeCode && VALID_AUTH_CODES.has(maybeCode as AuthErrorCode)) {\n throw new AuthError(maybeCode as AuthErrorCode, reason);\n }\n const m = reason.match(LEGACY_CODE_PREFIX_RE);\n if (m && VALID_AUTH_CODES.has(m[1] as AuthErrorCode)) {\n throw new AuthError(m[1] as AuthErrorCode, m[2]);\n }\n throw new AuthError(\"UNKNOWN\", reason);\n }\n throw new AuthError(\"UNKNOWN\", String(err));\n}\n\nfunction normaliseSession(raw: unknown): SpotifySession {\n if (!raw || typeof raw !== \"object\") {\n throw new AuthError(\"UNKNOWN\", \"Native module returned a non-object session\");\n }\n const r = raw as Record<string, unknown>;\n const accessToken = r.accessToken;\n if (typeof accessToken !== \"string\" || accessToken.length === 0) {\n throw new AuthError(\"UNKNOWN\", \"Session is missing accessToken\");\n }\n const expirationDate = r.expirationDate;\n if (typeof expirationDate !== \"number\") {\n throw new AuthError(\"UNKNOWN\", \"Session is missing expirationDate\");\n }\n const refreshTokenRaw = r.refreshToken;\n const refreshToken =\n typeof refreshTokenRaw === \"string\" && refreshTokenRaw.length > 0\n ? refreshTokenRaw\n : null;\n const scopesRaw = r.scopes;\n const scopes = Array.isArray(scopesRaw)\n ? (scopesRaw.filter((s) => typeof s === \"string\") as SpotifyScope[])\n : [];\n return { accessToken, refreshToken, expirationDate, scopes };\n}\n\n// ---------------------------------------------------------------------------\n// Auth namespace\n// ---------------------------------------------------------------------------\n\n/**\n * Spotify Auth namespace. Handles OAuth authentication and session lifecycle.\n *\n * @example\n * ```ts\n * import { Auth } from \"@wwdrew/expo-spotify-sdk\";\n *\n * const session = await Auth.authenticate({ scopes: [\"streaming\"] });\n * ```\n */\nexport const Auth = {\n /**\n * Returns `true` if the Spotify app is installed on the device.\n */\n isAvailable(): boolean {\n return ExpoSpotifySDKModule.isAvailable();\n },\n\n /**\n * Starts a Spotify OAuth flow. Resolves with a {@link SpotifySession};\n * rejects with an {@link AuthError} carrying a `code`.\n */\n authenticate(config: AuthenticateConfig): Promise<SpotifySession> {\n if (!config.scopes || config.scopes.length === 0) {\n return Promise.reject(\n new AuthError(\"INVALID_CONFIG\", \"scopes must contain at least one entry\"),\n );\n }\n if (\n Platform.OS === \"android\" &&\n !config.tokenSwapURL &&\n !warnedAboutAndroidTokenFlow\n ) {\n warnedAboutAndroidTokenFlow = true;\n console.warn(ANDROID_TOKEN_FLOW_WARNING);\n }\n return ExpoSpotifySDKModule.authenticateAsync(config)\n .then(normaliseSession)\n .catch(rethrowAsAuthError);\n },\n\n /**\n * Exchanges a refresh token for a new access token via your token refresh\n * server. Resolves with a fresh {@link SpotifySession}; rejects with an\n * {@link AuthError}.\n */\n refresh(config: RefreshConfig): Promise<SpotifySession> {\n if (!config.refreshToken) {\n return Promise.reject(\n new AuthError(\"INVALID_CONFIG\", \"refreshToken is required\"),\n );\n }\n if (!config.tokenRefreshURL) {\n return Promise.reject(\n new AuthError(\"INVALID_CONFIG\", \"tokenRefreshURL is required\"),\n );\n }\n return ExpoSpotifySDKModule.refreshSessionAsync(config)\n .then(normaliseSession)\n .catch(rethrowAsAuthError);\n },\n\n /**\n * Forcibly cancel any in-flight `Auth.authenticate()` call. No-op on\n * Android (the Android coordinator self-cleans via structured concurrency).\n *\n * Use before `Auth.authenticate()` to defensively clear any leaked iOS\n * coordinator state (the `SPTSessionManager` delegate callbacks are not\n * guaranteed to fire).\n */\n cancelPending(): Promise<void> {\n if (Platform.OS !== \"ios\") {\n return Promise.resolve();\n }\n return ExpoSpotifySDKModule.cancelPendingAuthAsync();\n },\n\n /**\n * Subscribes to session lifecycle events.\n *\n * Events fire for every `Auth.authenticate()` and `Auth.refresh()` call,\n * regardless of whether the call was awaited. Useful for persisting tokens\n * in a central store without coupling the store to the call sites.\n *\n * Returns a `Subscription` — call `.remove()` to unsubscribe.\n *\n * @example\n * ```ts\n * const sub = Auth.addListener(\"sessionChange\", (event) => {\n * if (event.type === \"didInitiate\" || event.type === \"didRenew\") {\n * store.setSession(event.session);\n * }\n * });\n * // later:\n * sub.remove();\n * ```\n */\n addListener(\n event: \"sessionChange\",\n listener: (event: SessionChangeEvent) => void,\n ): EventSubscription {\n return ExpoSpotifySDKModule.addListener(\n \"onSessionChange\",\n listener,\n ) as EventSubscription;\n },\n} as const;\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/auth/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAE7C,OAAO,oBAAoB,MAAM,yBAAyB,CAAC;AAC3D,OAAO,EAAE,SAAS,EAAsB,MAAM,SAAS,CAAC;AACxD,OAAO,EAAE,wBAAwB,EAAE,MAAM,2BAA2B,CAAC;AAGrE,OAAO,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAkFpC,8EAA8E;AAC9E,mBAAmB;AACnB,8EAA8E;AAE9E,MAAM,0BAA0B,GAC9B,0EAA0E;IAC1E,2EAA2E;IAC3E,gEAAgE;IAChE,6DAA6D,CAAC;AAEhE,IAAI,2BAA2B,GAAG,KAAK,CAAC;AAExC,MAAM,kBAAkB,GAAG,wBAAwB,CAAC;IAClD,UAAU,EAAE,SAAS;IACrB,WAAW,EAAE,SAAS;IACtB,gBAAgB,EAAE,IAAI;IACtB,UAAU,EAAE,IAAI,GAAG,CAAgB;QACjC,gBAAgB;QAChB,kBAAkB;QAClB,gBAAgB;QAChB,eAAe;QACf,mBAAmB;QACnB,wBAAwB;QACxB,uBAAuB;QACvB,YAAY;QACZ,SAAS;KACV,CAAC;CACH,CAAC,CAAC;AAEH,SAAS,gBAAgB,CAAC,GAAY;IACpC,IAAI,CAAC,GAAG,IAAI,OAAO,GAAG,KAAK,QAAQ,EAAE,CAAC;QACpC,MAAM,IAAI,SAAS,CACjB,SAAS,EACT,6CAA6C,CAC9C,CAAC;IACJ,CAAC;IACD,MAAM,CAAC,GAAG,GAA8B,CAAC;IACzC,MAAM,WAAW,GAAG,CAAC,CAAC,WAAW,CAAC;IAClC,IAAI,OAAO,WAAW,KAAK,QAAQ,IAAI,WAAW,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAChE,MAAM,IAAI,SAAS,CAAC,SAAS,EAAE,gCAAgC,CAAC,CAAC;IACnE,CAAC;IACD,MAAM,cAAc,GAAG,CAAC,CAAC,cAAc,CAAC;IACxC,IAAI,OAAO,cAAc,KAAK,QAAQ,EAAE,CAAC;QACvC,MAAM,IAAI,SAAS,CAAC,SAAS,EAAE,mCAAmC,CAAC,CAAC;IACtE,CAAC;IACD,MAAM,eAAe,GAAG,CAAC,CAAC,YAAY,CAAC;IACvC,MAAM,YAAY,GAChB,OAAO,eAAe,KAAK,QAAQ,IAAI,eAAe,CAAC,MAAM,GAAG,CAAC;QAC/D,CAAC,CAAC,eAAe;QACjB,CAAC,CAAC,IAAI,CAAC;IACX,MAAM,SAAS,GAAG,CAAC,CAAC,MAAM,CAAC;IAC3B,MAAM,MAAM,GAAG,KAAK,CAAC,OAAO,CAAC,SAAS,CAAC;QACrC,CAAC,CAAE,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,QAAQ,CAAoB;QACpE,CAAC,CAAC,EAAE,CAAC;IACP,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,EAAE,CAAC;AAC/D,CAAC;AAED,8EAA8E;AAC9E,iBAAiB;AACjB,8EAA8E;AAE9E;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,IAAI,GAAG;IAClB;;OAEG;IACH,WAAW;QACT,OAAO,oBAAoB,CAAC,WAAW,EAAE,CAAC;IAC5C,CAAC;IAED;;;OAGG;IACH,YAAY,CAAC,MAA0B;QACrC,IAAI,CAAC,MAAM,CAAC,MAAM,IAAI,MAAM,CAAC,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACjD,OAAO,OAAO,CAAC,MAAM,CACnB,IAAI,SAAS,CACX,gBAAgB,EAChB,wCAAwC,CACzC,CACF,CAAC;QACJ,CAAC;QACD,IACE,QAAQ,CAAC,EAAE,KAAK,SAAS;YACzB,CAAC,MAAM,CAAC,YAAY;YACpB,CAAC,2BAA2B,EAC5B,CAAC;YACD,2BAA2B,GAAG,IAAI,CAAC;YACnC,OAAO,CAAC,IAAI,CAAC,0BAA0B,CAAC,CAAC;QAC3C,CAAC;QACD,OAAO,oBAAoB,CAAC,iBAAiB,CAAC,MAAM,CAAC;aAClD,IAAI,CAAC,gBAAgB,CAAC;aACtB,KAAK,CAAC,kBAAkB,CAAC,CAAC;IAC/B,CAAC;IAED;;;;OAIG;IACH,OAAO,CAAC,MAAqB;QAC3B,IAAI,CAAC,MAAM,CAAC,YAAY,EAAE,CAAC;YACzB,OAAO,OAAO,CAAC,MAAM,CACnB,IAAI,SAAS,CAAC,gBAAgB,EAAE,0BAA0B,CAAC,CAC5D,CAAC;QACJ,CAAC;QACD,IAAI,CAAC,MAAM,CAAC,eAAe,EAAE,CAAC;YAC5B,OAAO,OAAO,CAAC,MAAM,CACnB,IAAI,SAAS,CAAC,gBAAgB,EAAE,6BAA6B,CAAC,CAC/D,CAAC;QACJ,CAAC;QACD,OAAO,oBAAoB,CAAC,mBAAmB,CAAC,MAAM,CAAC;aACpD,IAAI,CAAC,gBAAgB,CAAC;aACtB,KAAK,CAAC,kBAAkB,CAAC,CAAC;IAC/B,CAAC;IAED;;;;;;;OAOG;IACH,aAAa;QACX,IAAI,QAAQ,CAAC,EAAE,KAAK,KAAK,EAAE,CAAC;YAC1B,OAAO,OAAO,CAAC,OAAO,EAAE,CAAC;QAC3B,CAAC;QACD,OAAO,oBAAoB,CAAC,sBAAsB,EAAE,CAAC;IACvD,CAAC;IAED;;;;;;;;;;;;;;;;;;;OAmBG;IACH,WAAW,CACT,KAAsB,EACtB,QAA6C;QAE7C,OAAO,oBAAoB,CAAC,WAAW,CACrC,iBAAiB,EACjB,QAAQ,CACY,CAAC;IACzB,CAAC;CACO,CAAC","sourcesContent":["import type { EventSubscription } from \"expo-modules-core\";\nimport { Platform } from \"expo-modules-core\";\n\nimport ExpoSpotifySDKModule from \"../ExpoSpotifySDKModule\";\nimport { AuthError, type AuthErrorCode } from \"./error\";\nimport { createNativeErrorRethrow } from \"../internal/native-errors\";\n\nexport type { AuthErrorCode } from \"./error\";\nexport { AuthError } from \"./error\";\n\n// ---------------------------------------------------------------------------\n// Auth-specific types\n// ---------------------------------------------------------------------------\n\nexport interface SpotifySession {\n /** OAuth access token. */\n accessToken: string;\n /**\n * OAuth refresh token. `null` on Android when no `tokenSwapURL` is provided\n * (the Spotify Android SDK does not expose a refresh token for implicit\n * grants).\n */\n refreshToken: string | null;\n /** Expiration timestamp as Unix epoch milliseconds. */\n expirationDate: number;\n /** Scopes the access token was granted (or requested, on Android implicit). */\n scopes: SpotifyScope[];\n}\n\nexport type SpotifyScope =\n | \"ugc-image-upload\"\n | \"user-read-playback-state\"\n | \"user-modify-playback-state\"\n | \"user-read-currently-playing\"\n | \"app-remote-control\"\n | \"streaming\"\n | \"playlist-read-private\"\n | \"playlist-read-collaborative\"\n | \"playlist-modify-private\"\n | \"playlist-modify-public\"\n | \"user-follow-modify\"\n | \"user-follow-read\"\n | \"user-top-read\"\n | \"user-read-recently-played\"\n | \"user-library-modify\"\n | \"user-library-read\"\n | \"user-read-email\"\n | \"user-read-private\";\n\nexport interface AuthenticateConfig {\n /** OAuth scopes to request. Must contain at least one entry. */\n scopes: SpotifyScope[];\n /**\n * If supplied, requests an authorization code rather than an implicit\n * token, then POSTs the code to this URL to exchange it for tokens.\n * **Required on Android** to receive a usable `refreshToken`.\n */\n tokenSwapURL?: string;\n /**\n * Used by the iOS SDK to refresh access tokens automatically, and by\n * `Auth.refresh()` on both platforms.\n */\n tokenRefreshURL?: string;\n /**\n * If `true`, forces Spotify to show the authorization dialog even when the\n * user already has an active session. Defaults to `false`.\n */\n showDialog?: boolean;\n}\n\nexport interface RefreshConfig {\n /** The refresh token from a previous `Auth.authenticate()` call. */\n refreshToken: string;\n /** URL of your token refresh server endpoint. */\n tokenRefreshURL: string;\n /**\n * Scopes that were granted by the previous session. Used as a fallback when\n * the refresh response omits the `scope` field.\n */\n scopes?: SpotifyScope[];\n}\n\n/**\n * Payload delivered to `Auth.addListener(\"sessionChange\", ...)` subscribers.\n */\nexport type SessionChangeEvent =\n | { type: \"didInitiate\"; session: SpotifySession }\n | { type: \"didRenew\"; session: SpotifySession }\n | { type: \"didFail\"; error: { code: AuthErrorCode; message: string } };\n\n// ---------------------------------------------------------------------------\n// Internal helpers\n// ---------------------------------------------------------------------------\n\nconst ANDROID_TOKEN_FLOW_WARNING =\n \"[expo-spotify-sdk] You are using Auth.authenticate on Android without a \" +\n \"tokenSwapURL. The Spotify Android SDK does NOT return a refresh token or \" +\n \"the actual granted scopes through this path; see the README's \" +\n \"'Android implicit (TOKEN) flow is not recommended' section.\";\n\nlet warnedAboutAndroidTokenFlow = false;\n\nconst rethrowAsAuthError = createNativeErrorRethrow({\n ErrorClass: AuthError,\n unknownCode: \"UNKNOWN\",\n legacyCodePrefix: true,\n validCodes: new Set<AuthErrorCode>([\n \"USER_CANCELLED\",\n \"AUTH_IN_PROGRESS\",\n \"INVALID_CONFIG\",\n \"NETWORK_ERROR\",\n \"TOKEN_SWAP_FAILED\",\n \"TOKEN_SWAP_PARSE_ERROR\",\n \"SPOTIFY_NOT_INSTALLED\",\n \"AUTH_ERROR\",\n \"UNKNOWN\",\n ]),\n});\n\nfunction normaliseSession(raw: unknown): SpotifySession {\n if (!raw || typeof raw !== \"object\") {\n throw new AuthError(\n \"UNKNOWN\",\n \"Native module returned a non-object session\",\n );\n }\n const r = raw as Record<string, unknown>;\n const accessToken = r.accessToken;\n if (typeof accessToken !== \"string\" || accessToken.length === 0) {\n throw new AuthError(\"UNKNOWN\", \"Session is missing accessToken\");\n }\n const expirationDate = r.expirationDate;\n if (typeof expirationDate !== \"number\") {\n throw new AuthError(\"UNKNOWN\", \"Session is missing expirationDate\");\n }\n const refreshTokenRaw = r.refreshToken;\n const refreshToken =\n typeof refreshTokenRaw === \"string\" && refreshTokenRaw.length > 0\n ? refreshTokenRaw\n : null;\n const scopesRaw = r.scopes;\n const scopes = Array.isArray(scopesRaw)\n ? (scopesRaw.filter((s) => typeof s === \"string\") as SpotifyScope[])\n : [];\n return { accessToken, refreshToken, expirationDate, scopes };\n}\n\n// ---------------------------------------------------------------------------\n// Auth namespace\n// ---------------------------------------------------------------------------\n\n/**\n * Spotify Auth namespace. Handles OAuth authentication and session lifecycle.\n *\n * @example\n * ```ts\n * import { Auth } from \"@wwdrew/expo-spotify-sdk\";\n *\n * const session = await Auth.authenticate({ scopes: [\"streaming\"] });\n * ```\n */\nexport const Auth = {\n /**\n * Returns `true` if the Spotify app is installed on the device.\n */\n isAvailable(): boolean {\n return ExpoSpotifySDKModule.isAvailable();\n },\n\n /**\n * Starts a Spotify OAuth flow. Resolves with a {@link SpotifySession};\n * rejects with an {@link AuthError} carrying a `code`.\n */\n authenticate(config: AuthenticateConfig): Promise<SpotifySession> {\n if (!config.scopes || config.scopes.length === 0) {\n return Promise.reject(\n new AuthError(\n \"INVALID_CONFIG\",\n \"scopes must contain at least one entry\",\n ),\n );\n }\n if (\n Platform.OS === \"android\" &&\n !config.tokenSwapURL &&\n !warnedAboutAndroidTokenFlow\n ) {\n warnedAboutAndroidTokenFlow = true;\n console.warn(ANDROID_TOKEN_FLOW_WARNING);\n }\n return ExpoSpotifySDKModule.authenticateAsync(config)\n .then(normaliseSession)\n .catch(rethrowAsAuthError);\n },\n\n /**\n * Exchanges a refresh token for a new access token via your token refresh\n * server. Resolves with a fresh {@link SpotifySession}; rejects with an\n * {@link AuthError}.\n */\n refresh(config: RefreshConfig): Promise<SpotifySession> {\n if (!config.refreshToken) {\n return Promise.reject(\n new AuthError(\"INVALID_CONFIG\", \"refreshToken is required\"),\n );\n }\n if (!config.tokenRefreshURL) {\n return Promise.reject(\n new AuthError(\"INVALID_CONFIG\", \"tokenRefreshURL is required\"),\n );\n }\n return ExpoSpotifySDKModule.refreshSessionAsync(config)\n .then(normaliseSession)\n .catch(rethrowAsAuthError);\n },\n\n /**\n * Forcibly cancel any in-flight `Auth.authenticate()` call. No-op on\n * Android (the Android coordinator self-cleans via structured concurrency).\n *\n * Use before `Auth.authenticate()` to defensively clear any leaked iOS\n * coordinator state (the `SPTSessionManager` delegate callbacks are not\n * guaranteed to fire).\n */\n cancelPending(): Promise<void> {\n if (Platform.OS !== \"ios\") {\n return Promise.resolve();\n }\n return ExpoSpotifySDKModule.cancelPendingAuthAsync();\n },\n\n /**\n * Subscribes to session lifecycle events.\n *\n * Events fire for every `Auth.authenticate()` and `Auth.refresh()` call,\n * regardless of whether the call was awaited. Useful for persisting tokens\n * in a central store without coupling the store to the call sites.\n *\n * Returns a `Subscription` — call `.remove()` to unsubscribe.\n *\n * @example\n * ```ts\n * const sub = Auth.addListener(\"sessionChange\", (event) => {\n * if (event.type === \"didInitiate\" || event.type === \"didRenew\") {\n * store.setSession(event.session);\n * }\n * });\n * // later:\n * sub.remove();\n * ```\n */\n addListener(\n event: \"sessionChange\",\n listener: (event: SessionChangeEvent) => void,\n ): EventSubscription {\n return ExpoSpotifySDKModule.addListener(\n \"onSessionChange\",\n listener,\n ) as EventSubscription;\n },\n} as const;\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/content/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAChD,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AASvC,MAAM,MAAM,WAAW,GAAG,SAAS,GAAG,YAAY,GAAG,SAAS,GAAG,QAAQ,CAAC;AAE1E,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,kBAAkB,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,UAAU,EAAE,MAAM,CAAC;IACnB,GAAG,EAAE,MAAM,CAAC;IACZ,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,kBAAkB,EAAE,OAAO,CAAC;IAC5B,UAAU,EAAE,OAAO,CAAC;IACpB,WAAW,EAAE,OAAO,CAAC;IACrB,QAAQ,EAAE,OAAO,CAAC;IAClB,QAAQ,CAAC,EAAE,WAAW,EAAE,CAAC;CAC1B;AA6BD;;;;;;;;;;GAUG;AACH,eAAO,MAAM,OAAO;IAClB;;OAEG;gDAC8B,WAAW,KAAG,OAAO,CAAC,WAAW,EAAE,CAAC;IAMrE;;OAEG;iCACe,WAAW,KAAG,OAAO,CAAC,WAAW,EAAE,CAAC;CAG9C,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/content/index.ts"],"names":[],"mappings":"AAQA,YAAY,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAChD,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAEvC,MAAM,MAAM,WAAW,GAAG,SAAS,GAAG,YAAY,GAAG,SAAS,GAAG,QAAQ,CAAC;AAE1E,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,kBAAkB,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,UAAU,EAAE,MAAM,CAAC;IACnB,GAAG,EAAE,MAAM,CAAC;IACZ,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,kBAAkB,EAAE,OAAO,CAAC;IAC5B,UAAU,EAAE,OAAO,CAAC;IACpB,WAAW,EAAE,OAAO,CAAC;IACrB,QAAQ,EAAE,OAAO,CAAC;IAClB,QAAQ,CAAC,EAAE,WAAW,EAAE,CAAC;CAC1B;AAaD;;;;;;;;;;GAUG;AACH,eAAO,MAAM,OAAO;IAClB;;OAEG;gDAC8B,WAAW,KAAG,OAAO,CAAC,WAAW,EAAE,CAAC;IAMrE;;OAEG;iCACe,WAAW,KAAG,OAAO,CAAC,WAAW,EAAE,CAAC;CAK9C,CAAC"}
@@ -1,33 +1,20 @@
1
- export { ContentError } from "./error";
2
1
  // ---------------------------------------------------------------------------
3
2
  // Types
4
3
  // ---------------------------------------------------------------------------
5
4
  import ExpoSpotifySDKModule from "../ExpoSpotifySDKModule";
6
5
  import { ContentError } from "./error";
7
- const VALID_CONTENT_CODES = new Set([
8
- "NOT_CONNECTED",
9
- "CONNECTION_LOST",
10
- "CONTENT_API_UNAVAILABLE",
11
- "UNKNOWN",
12
- ]);
13
- const CAUSE_SEPARATOR = "→ Caused by: ";
14
- function unwrapReason(message) {
15
- const idx = message.lastIndexOf(CAUSE_SEPARATOR);
16
- return idx === -1 ? message : message.slice(idx + CAUSE_SEPARATOR.length);
17
- }
18
- function rethrowAsContentError(err) {
19
- if (err instanceof ContentError)
20
- throw err;
21
- if (err instanceof Error) {
22
- const reason = unwrapReason(err.message);
23
- const maybeCode = err.code;
24
- if (maybeCode && VALID_CONTENT_CODES.has(maybeCode)) {
25
- throw new ContentError(maybeCode, reason);
26
- }
27
- throw new ContentError("UNKNOWN", reason);
28
- }
29
- throw new ContentError("UNKNOWN", String(err));
30
- }
6
+ import { createNativeErrorRethrow } from "../internal/native-errors";
7
+ export { ContentError } from "./error";
8
+ const rethrowAsContentError = createNativeErrorRethrow({
9
+ ErrorClass: ContentError,
10
+ unknownCode: "UNKNOWN",
11
+ validCodes: new Set([
12
+ "NOT_CONNECTED",
13
+ "CONNECTION_LOST",
14
+ "CONTENT_API_UNAVAILABLE",
15
+ "UNKNOWN",
16
+ ]),
17
+ });
31
18
  /**
32
19
  * Spotify Content namespace. Browse Spotify's curated content tree.
33
20
  * Requires `AppRemote.connect()` to be resolved before any call.
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/content/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAEvC,8EAA8E;AAC9E,QAAQ;AACR,8EAA8E;AAE9E,OAAO,oBAAoB,MAAM,yBAAyB,CAAC;AAC3D,OAAO,EAAE,YAAY,EAAoB,MAAM,SAAS,CAAC;AAkBzD,MAAM,mBAAmB,GAAG,IAAI,GAAG,CAAmB;IACpD,eAAe;IACf,iBAAiB;IACjB,yBAAyB;IACzB,SAAS;CACV,CAAC,CAAC;AAEH,MAAM,eAAe,GAAG,eAAe,CAAC;AAExC,SAAS,YAAY,CAAC,OAAe;IACnC,MAAM,GAAG,GAAG,OAAO,CAAC,WAAW,CAAC,eAAe,CAAC,CAAC;IACjD,OAAO,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,GAAG,GAAG,eAAe,CAAC,MAAM,CAAC,CAAC;AAC5E,CAAC;AAED,SAAS,qBAAqB,CAAC,GAAY;IACzC,IAAI,GAAG,YAAY,YAAY;QAAE,MAAM,GAAG,CAAC;IAC3C,IAAI,GAAG,YAAY,KAAK,EAAE,CAAC;QACzB,MAAM,MAAM,GAAG,YAAY,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QACzC,MAAM,SAAS,GAAI,GAAiC,CAAC,IAAI,CAAC;QAC1D,IAAI,SAAS,IAAI,mBAAmB,CAAC,GAAG,CAAC,SAA6B,CAAC,EAAE,CAAC;YACxE,MAAM,IAAI,YAAY,CAAC,SAA6B,EAAE,MAAM,CAAC,CAAC;QAChE,CAAC;QACD,MAAM,IAAI,YAAY,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC;IAC5C,CAAC;IACD,MAAM,IAAI,YAAY,CAAC,SAAS,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;AACjD,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,CAAC,MAAM,OAAO,GAAG;IACrB;;OAEG;IACH,0BAA0B,CAAC,IAAiB;QAC1C,OAAO,oBAAoB,CAAC,iCAAiC,CAAC,IAAI,CAAC,CAAC,KAAK,CACvE,qBAAqB,CACtB,CAAC;IACJ,CAAC;IAED;;OAEG;IACH,WAAW,CAAC,IAAiB;QAC3B,OAAO,oBAAoB,CAAC,kBAAkB,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,qBAAqB,CAAC,CAAC;IACpF,CAAC;CACO,CAAC","sourcesContent":["export type { ContentErrorCode } from \"./error\";\nexport { ContentError } from \"./error\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nimport ExpoSpotifySDKModule from \"../ExpoSpotifySDKModule\";\nimport { ContentError, ContentErrorCode } from \"./error\";\n\nexport type ContentType = \"default\" | \"navigation\" | \"fitness\" | \"gaming\";\n\nexport interface ContentItem {\n title: string | null;\n subtitle: string | null;\n contentDescription: string | null;\n identifier: string;\n uri: string;\n imageIdentifier: string | null;\n isAvailableOffline: boolean;\n isPlayable: boolean;\n isContainer: boolean;\n isPinned: boolean;\n children?: ContentItem[];\n}\n\nconst VALID_CONTENT_CODES = new Set<ContentErrorCode>([\n \"NOT_CONNECTED\",\n \"CONNECTION_LOST\",\n \"CONTENT_API_UNAVAILABLE\",\n \"UNKNOWN\",\n]);\n\nconst CAUSE_SEPARATOR = \"→ Caused by: \";\n\nfunction unwrapReason(message: string): string {\n const idx = message.lastIndexOf(CAUSE_SEPARATOR);\n return idx === -1 ? message : message.slice(idx + CAUSE_SEPARATOR.length);\n}\n\nfunction rethrowAsContentError(err: unknown): never {\n if (err instanceof ContentError) throw err;\n if (err instanceof Error) {\n const reason = unwrapReason(err.message);\n const maybeCode = (err as Error & { code?: string }).code;\n if (maybeCode && VALID_CONTENT_CODES.has(maybeCode as ContentErrorCode)) {\n throw new ContentError(maybeCode as ContentErrorCode, reason);\n }\n throw new ContentError(\"UNKNOWN\", reason);\n }\n throw new ContentError(\"UNKNOWN\", String(err));\n}\n\n/**\n * Spotify Content namespace. Browse Spotify's curated content tree.\n * Requires `AppRemote.connect()` to be resolved before any call.\n *\n * @example\n * ```ts\n * import { Content } from \"@wwdrew/expo-spotify-sdk\";\n *\n * const items = await Content.getRecommendedContentItems(\"default\");\n * ```\n */\nexport const Content = {\n /**\n * Returns Spotify-curated recommended content for the given feed type.\n */\n getRecommendedContentItems(type: ContentType): Promise<ContentItem[]> {\n return ExpoSpotifySDKModule.contentGetRecommendedContentItems(type).catch(\n rethrowAsContentError,\n );\n },\n\n /**\n * Returns children of a browsable (container) content item.\n */\n getChildren(item: ContentItem): Promise<ContentItem[]> {\n return ExpoSpotifySDKModule.contentGetChildren(item).catch(rethrowAsContentError);\n },\n} as const;\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/content/index.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAC9E,QAAQ;AACR,8EAA8E;AAE9E,OAAO,oBAAoB,MAAM,yBAAyB,CAAC;AAC3D,OAAO,EAAE,YAAY,EAAyB,MAAM,SAAS,CAAC;AAC9D,OAAO,EAAE,wBAAwB,EAAE,MAAM,2BAA2B,CAAC;AAGrE,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAkBvC,MAAM,qBAAqB,GAAG,wBAAwB,CAAC;IACrD,UAAU,EAAE,YAAY;IACxB,WAAW,EAAE,SAAS;IACtB,UAAU,EAAE,IAAI,GAAG,CAAmB;QACpC,eAAe;QACf,iBAAiB;QACjB,yBAAyB;QACzB,SAAS;KACV,CAAC;CACH,CAAC,CAAC;AAEH;;;;;;;;;;GAUG;AACH,MAAM,CAAC,MAAM,OAAO,GAAG;IACrB;;OAEG;IACH,0BAA0B,CAAC,IAAiB;QAC1C,OAAO,oBAAoB,CAAC,iCAAiC,CAAC,IAAI,CAAC,CAAC,KAAK,CACvE,qBAAqB,CACtB,CAAC;IACJ,CAAC;IAED;;OAEG;IACH,WAAW,CAAC,IAAiB;QAC3B,OAAO,oBAAoB,CAAC,kBAAkB,CAAC,IAAI,CAAC,CAAC,KAAK,CACxD,qBAAqB,CACtB,CAAC;IACJ,CAAC;CACO,CAAC","sourcesContent":["// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nimport ExpoSpotifySDKModule from \"../ExpoSpotifySDKModule\";\nimport { ContentError, type ContentErrorCode } from \"./error\";\nimport { createNativeErrorRethrow } from \"../internal/native-errors\";\n\nexport type { ContentErrorCode } from \"./error\";\nexport { ContentError } from \"./error\";\n\nexport type ContentType = \"default\" | \"navigation\" | \"fitness\" | \"gaming\";\n\nexport interface ContentItem {\n title: string | null;\n subtitle: string | null;\n contentDescription: string | null;\n identifier: string;\n uri: string;\n imageIdentifier: string | null;\n isAvailableOffline: boolean;\n isPlayable: boolean;\n isContainer: boolean;\n isPinned: boolean;\n children?: ContentItem[];\n}\n\nconst rethrowAsContentError = createNativeErrorRethrow({\n ErrorClass: ContentError,\n unknownCode: \"UNKNOWN\",\n validCodes: new Set<ContentErrorCode>([\n \"NOT_CONNECTED\",\n \"CONNECTION_LOST\",\n \"CONTENT_API_UNAVAILABLE\",\n \"UNKNOWN\",\n ]),\n});\n\n/**\n * Spotify Content namespace. Browse Spotify's curated content tree.\n * Requires `AppRemote.connect()` to be resolved before any call.\n *\n * @example\n * ```ts\n * import { Content } from \"@wwdrew/expo-spotify-sdk\";\n *\n * const items = await Content.getRecommendedContentItems(\"default\");\n * ```\n */\nexport const Content = {\n /**\n * Returns Spotify-curated recommended content for the given feed type.\n */\n getRecommendedContentItems(type: ContentType): Promise<ContentItem[]> {\n return ExpoSpotifySDKModule.contentGetRecommendedContentItems(type).catch(\n rethrowAsContentError,\n );\n },\n\n /**\n * Returns children of a browsable (container) content item.\n */\n getChildren(item: ContentItem): Promise<ContentItem[]> {\n return ExpoSpotifySDKModule.contentGetChildren(item).catch(\n rethrowAsContentError,\n );\n },\n} as const;\n"]}
@@ -1,8 +1,8 @@
1
- import { ConnectionState } from "../app-remote";
2
- import { SpotifySession } from "../auth";
3
- import { PlayerState, Track } from "../player";
4
- import { Capabilities, LibraryState } from "../user";
1
+ import { type ConnectionState } from "../app-remote";
2
+ import { type SpotifySession } from "../auth";
3
+ import { type PlayerState, type Track } from "../player";
5
4
  import type { SpotifyURI as SpotifyURIType } from "../uri";
5
+ import { type Capabilities, type LibraryState } from "../user";
6
6
  /**
7
7
  * Returns the current Spotify OAuth session, or `null` when not authenticated.
8
8
  * Updates automatically whenever `Auth.authenticate()` or `Auth.refresh()`
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/hooks/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAa,eAAe,EAAE,MAAM,eAAe,CAAC;AAC3D,OAAO,EAAQ,cAAc,EAAE,MAAM,SAAS,CAAC;AAC/C,OAAO,EAAU,WAAW,EAAE,KAAK,EAAE,MAAM,WAAW,CAAC;AACvD,OAAO,EAAE,YAAY,EAAE,YAAY,EAAQ,MAAM,SAAS,CAAC;AAC3D,OAAO,KAAK,EAAE,UAAU,IAAI,cAAc,EAAE,MAAM,QAAQ,CAAC;AAmR3D;;;;;;GAMG;AACH,wBAAgB,UAAU,IAAI,cAAc,GAAG,IAAI,CAElD;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,kBAAkB,IAAI,eAAe,CAMpD;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,cAAc,IAAI,WAAW,GAAG,IAAI,CAEnD;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,IAAI,KAAK,GAAG,IAAI,CAE9C;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,IAAI,OAAO,CAGtC;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,mBAAmB,IAAI,MAAM,CAE5C;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,IAAI,YAAY,GAAG,IAAI,CAMrD;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,cAAc,GAAG,YAAY,GAAG,IAAI,CAMxE"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/hooks/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAa,KAAK,eAAe,EAAE,MAAM,eAAe,CAAC;AAChE,OAAO,EAAQ,KAAK,cAAc,EAAE,MAAM,SAAS,CAAC;AAEpD,OAAO,EAAU,KAAK,WAAW,EAAE,KAAK,KAAK,EAAE,MAAM,WAAW,CAAC;AACjE,OAAO,KAAK,EAAE,UAAU,IAAI,cAAc,EAAE,MAAM,QAAQ,CAAC;AAC3D,OAAO,EAAE,KAAK,YAAY,EAAE,KAAK,YAAY,EAAQ,MAAM,SAAS,CAAC;AAsMrE;;;;;;GAMG;AACH,wBAAgB,UAAU,IAAI,cAAc,GAAG,IAAI,CAMlD;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,kBAAkB,IAAI,eAAe,CAMpD;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,cAAc,IAAI,WAAW,GAAG,IAAI,CAMnD;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,IAAI,KAAK,GAAG,IAAI,CAE9C;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,IAAI,OAAO,CAGtC;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,mBAAmB,IAAI,MAAM,CAE5C;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,IAAI,YAAY,GAAG,IAAI,CAMrD;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,cAAc,GAAG,YAAY,GAAG,IAAI,CAMxE"}