@wwdrew/expo-spotify-sdk 2.0.0 → 2.2.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 (126) hide show
  1. package/README.md +94 -346
  2. package/android/build.gradle +4 -5
  3. package/android/libs/SETUP.md +9 -24
  4. package/android/spotify-native-sdk.gradle +118 -0
  5. package/android/src/main/java/expo/modules/spotifysdk/ExpoSpotifySDKModule.kt +14 -5
  6. package/android/src/main/java/expo/modules/spotifysdk/SpotifyAppRemoteCoordinator.kt +38 -64
  7. package/android/src/main/java/expo/modules/spotifysdk/SpotifyAppRemoteErrorMapping.kt +106 -0
  8. package/android/src/main/java/expo/modules/spotifysdk/SpotifyTokenSwapClient.kt +2 -7
  9. package/build/app-remote/index.d.ts +34 -0
  10. package/build/app-remote/index.d.ts.map +1 -1
  11. package/build/app-remote/index.js +47 -24
  12. package/build/app-remote/index.js.map +1 -1
  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 +1 -1
  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.map +1 -1
  40. package/build/player/index.js +14 -27
  41. package/build/player/index.js.map +1 -1
  42. package/build/user/index.d.ts +2 -2
  43. package/build/user/index.d.ts.map +1 -1
  44. package/build/user/index.js +16 -26
  45. package/build/user/index.js.map +1 -1
  46. package/ios/ExpoSpotifyAppDelegate.swift +8 -1
  47. package/ios/ExpoSpotifySDK.podspec +26 -2
  48. package/ios/ExpoSpotifySDKModule.swift +16 -1
  49. package/ios/SpotifyAppRemoteCoordinator.swift +137 -404
  50. package/ios/SpotifyAppRemoteErrorMapping.swift +110 -0
  51. package/ios/SpotifyAppRemoteErrors.swift +221 -0
  52. package/ios/SpotifyAppRemoteMappers.swift +88 -0
  53. package/ios/spotify-native-sdk-versions.json +12 -0
  54. package/package.json +13 -5
  55. package/plugin/index.d.ts +2 -0
  56. package/plugin/index.js +1 -0
  57. package/ios/SpotifySDK/Licenses/MPMessagePack-LICENSE.md +0 -20
  58. package/ios/SpotifySDK/SpotifyiOS.xcframework/Info.plist +0 -40
  59. package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64/SpotifyiOS.framework/Headers/SPTAppRemote.h +0 -287
  60. package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64/SpotifyiOS.framework/Headers/SPTAppRemoteAlbum.h +0 -18
  61. package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64/SpotifyiOS.framework/Headers/SPTAppRemoteArtist.h +0 -18
  62. package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64/SpotifyiOS.framework/Headers/SPTAppRemoteCommon.h +0 -37
  63. package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64/SpotifyiOS.framework/Headers/SPTAppRemoteConnectionParams.h +0 -65
  64. package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64/SpotifyiOS.framework/Headers/SPTAppRemoteConnectivityAPI.h +0 -55
  65. package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64/SpotifyiOS.framework/Headers/SPTAppRemoteConnectivityState.h +0 -15
  66. package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64/SpotifyiOS.framework/Headers/SPTAppRemoteContentAPI.h +0 -78
  67. package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64/SpotifyiOS.framework/Headers/SPTAppRemoteContentItem.h +0 -49
  68. package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64/SpotifyiOS.framework/Headers/SPTAppRemoteCrossfadeState.h +0 -18
  69. package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64/SpotifyiOS.framework/Headers/SPTAppRemoteImageAPI.h +0 -25
  70. package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64/SpotifyiOS.framework/Headers/SPTAppRemoteImageRepresentable.h +0 -16
  71. package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64/SpotifyiOS.framework/Headers/SPTAppRemoteLibraryState.h +0 -19
  72. package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64/SpotifyiOS.framework/Headers/SPTAppRemotePlaybackOptions.h +0 -32
  73. package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64/SpotifyiOS.framework/Headers/SPTAppRemotePlaybackRestrictions.h +0 -32
  74. package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64/SpotifyiOS.framework/Headers/SPTAppRemotePlayerAPI.h +0 -267
  75. package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64/SpotifyiOS.framework/Headers/SPTAppRemotePlayerState.h +0 -40
  76. package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64/SpotifyiOS.framework/Headers/SPTAppRemotePodcastPlaybackSpeed.h +0 -15
  77. package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64/SpotifyiOS.framework/Headers/SPTAppRemoteTrack.h +0 -52
  78. package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64/SpotifyiOS.framework/Headers/SPTAppRemoteUserAPI.h +0 -102
  79. package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64/SpotifyiOS.framework/Headers/SPTAppRemoteUserCapabilities.h +0 -15
  80. package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64/SpotifyiOS.framework/Headers/SPTConfiguration.h +0 -66
  81. package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64/SpotifyiOS.framework/Headers/SPTError.h +0 -31
  82. package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64/SpotifyiOS.framework/Headers/SPTLogin.h +0 -10
  83. package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64/SpotifyiOS.framework/Headers/SPTMacros.h +0 -125
  84. package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64/SpotifyiOS.framework/Headers/SPTScope.h +0 -75
  85. package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64/SpotifyiOS.framework/Headers/SPTSession.h +0 -33
  86. package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64/SpotifyiOS.framework/Headers/SPTSessionManager.h +0 -170
  87. package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64/SpotifyiOS.framework/Headers/SpotifyAppRemote.h +0 -28
  88. package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64/SpotifyiOS.framework/Headers/SpotifyiOS.h +0 -7
  89. package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64/SpotifyiOS.framework/Info.plist +0 -0
  90. package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64/SpotifyiOS.framework/Modules/module.modulemap +0 -5
  91. package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64/SpotifyiOS.framework/PrivacyInfo.xcprivacy +0 -14
  92. package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64/SpotifyiOS.framework/SpotifyiOS +0 -0
  93. package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64_x86_64-simulator/SpotifyiOS.framework/Headers/SPTAppRemote.h +0 -287
  94. package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64_x86_64-simulator/SpotifyiOS.framework/Headers/SPTAppRemoteAlbum.h +0 -18
  95. package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64_x86_64-simulator/SpotifyiOS.framework/Headers/SPTAppRemoteArtist.h +0 -18
  96. package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64_x86_64-simulator/SpotifyiOS.framework/Headers/SPTAppRemoteCommon.h +0 -37
  97. package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64_x86_64-simulator/SpotifyiOS.framework/Headers/SPTAppRemoteConnectionParams.h +0 -65
  98. package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64_x86_64-simulator/SpotifyiOS.framework/Headers/SPTAppRemoteConnectivityAPI.h +0 -55
  99. package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64_x86_64-simulator/SpotifyiOS.framework/Headers/SPTAppRemoteConnectivityState.h +0 -15
  100. package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64_x86_64-simulator/SpotifyiOS.framework/Headers/SPTAppRemoteContentAPI.h +0 -78
  101. package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64_x86_64-simulator/SpotifyiOS.framework/Headers/SPTAppRemoteContentItem.h +0 -49
  102. package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64_x86_64-simulator/SpotifyiOS.framework/Headers/SPTAppRemoteCrossfadeState.h +0 -18
  103. package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64_x86_64-simulator/SpotifyiOS.framework/Headers/SPTAppRemoteImageAPI.h +0 -25
  104. package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64_x86_64-simulator/SpotifyiOS.framework/Headers/SPTAppRemoteImageRepresentable.h +0 -16
  105. package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64_x86_64-simulator/SpotifyiOS.framework/Headers/SPTAppRemoteLibraryState.h +0 -19
  106. package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64_x86_64-simulator/SpotifyiOS.framework/Headers/SPTAppRemotePlaybackOptions.h +0 -32
  107. package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64_x86_64-simulator/SpotifyiOS.framework/Headers/SPTAppRemotePlaybackRestrictions.h +0 -32
  108. package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64_x86_64-simulator/SpotifyiOS.framework/Headers/SPTAppRemotePlayerAPI.h +0 -267
  109. package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64_x86_64-simulator/SpotifyiOS.framework/Headers/SPTAppRemotePlayerState.h +0 -40
  110. package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64_x86_64-simulator/SpotifyiOS.framework/Headers/SPTAppRemotePodcastPlaybackSpeed.h +0 -15
  111. package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64_x86_64-simulator/SpotifyiOS.framework/Headers/SPTAppRemoteTrack.h +0 -52
  112. package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64_x86_64-simulator/SpotifyiOS.framework/Headers/SPTAppRemoteUserAPI.h +0 -102
  113. package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64_x86_64-simulator/SpotifyiOS.framework/Headers/SPTAppRemoteUserCapabilities.h +0 -15
  114. package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64_x86_64-simulator/SpotifyiOS.framework/Headers/SPTConfiguration.h +0 -66
  115. package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64_x86_64-simulator/SpotifyiOS.framework/Headers/SPTError.h +0 -31
  116. package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64_x86_64-simulator/SpotifyiOS.framework/Headers/SPTLogin.h +0 -10
  117. package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64_x86_64-simulator/SpotifyiOS.framework/Headers/SPTMacros.h +0 -125
  118. package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64_x86_64-simulator/SpotifyiOS.framework/Headers/SPTScope.h +0 -75
  119. package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64_x86_64-simulator/SpotifyiOS.framework/Headers/SPTSession.h +0 -33
  120. package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64_x86_64-simulator/SpotifyiOS.framework/Headers/SPTSessionManager.h +0 -170
  121. package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64_x86_64-simulator/SpotifyiOS.framework/Headers/SpotifyAppRemote.h +0 -28
  122. package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64_x86_64-simulator/SpotifyiOS.framework/Headers/SpotifyiOS.h +0 -7
  123. package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64_x86_64-simulator/SpotifyiOS.framework/Info.plist +0 -0
  124. package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64_x86_64-simulator/SpotifyiOS.framework/Modules/module.modulemap +0 -5
  125. package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64_x86_64-simulator/SpotifyiOS.framework/PrivacyInfo.xcprivacy +0 -14
  126. package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64_x86_64-simulator/SpotifyiOS.framework/SpotifyiOS +0 -0
package/README.md CHANGED
@@ -8,32 +8,55 @@ An Expo module that wraps the native [Spotify iOS SDK](https://github.com/spotif
8
8
 
9
9
  **Why this exists:** Spotify ships native SDKs for iOS and Android that enable authentication via the installed Spotify app (no browser redirect, better UX) but there is no maintained Expo module for them. This library fills that gap.
10
10
 
11
+ ## Table of contents
12
+
13
+ - [Platform support](#platform-support)
14
+ - [Versioning and Expo SDK lanes](#versioning-and-expo-sdk-lanes)
15
+ - [Public API (Auth + App Remote)](#public-api-auth--app-remote)
16
+ - [Quick start (Expo)](#quick-start-expo)
17
+ - [Installation in bare React Native](#installation-in-bare-react-native)
18
+ - [Configuration](#configuration)
19
+ - [Usage](#usage)
20
+ - [Spotify Premium and App Remote](#spotify-premium-and-app-remote)
21
+ - [Migration from v0.x](#migration-from-v0x)
22
+ - [Documentation](#documentation)
23
+ - [Troubleshooting](#troubleshooting)
24
+ - [Related docs](#related-docs)
25
+ - [Acknowledgements](#acknowledgements)
26
+ - [Contributing](#contributing)
27
+ - [License](#license)
28
+
11
29
  ## Platform support
12
30
 
13
31
  **iOS and Android only.** This module will not support Expo Web or any browser target — there is no web implementation and none is planned.
14
32
 
15
- | Feature | iOS | Android |
16
- | --- | --- | --- |
17
- | `Auth.*` | ✅ | ✅ |
18
- | `AppRemote.*` | ✅ | ✅ |
19
- | `Player.*` | ✅ | ✅ |
20
- | `User.*` | ✅ | ✅ |
21
- | `Content.*` | ✅ | ✅ |
22
- | `Images.*` | ✅ | ✅ |
33
+ | Feature | iOS | Android |
34
+ | ------------- | --- | ------- |
35
+ | `Auth.*` | ✅ | ✅ |
36
+ | `AppRemote.*` | ✅ | ✅ |
37
+ | `Player.*` | ✅ | ✅ |
38
+ | `User.*` | ✅ | ✅ |
39
+ | `Content.*` | ✅ | ✅ |
40
+ | `Images.*` | ✅ | ✅ |
23
41
 
24
42
  ## Versioning and Expo SDK lanes
25
43
 
26
44
  Install the major that matches your Expo SDK:
27
45
 
28
- | npm version | Expo SDK | iOS minimum | Branch |
29
- | --- | --- | --- | --- |
30
- | **`1.x`** | 55 | 15.1 | `v1` (long-lived maintenance) |
31
- | **`2.x`** | 56+ | 16.4 | `main` |
46
+ | npm version | Expo SDK | iOS minimum | Branch |
47
+ | ----------- | -------- | ----------- | ----------------------------- |
48
+ | **`1.x`** | 55 | 15.1 | `v1` (long-lived maintenance) |
49
+ | **`2.x`** | 56+ | 16.4 | `main` |
32
50
 
33
51
  Both lanes ship the same public API (Auth + App Remote namespaces and hooks). The major version signals **runtime lane**, not a different feature set. See [ADR-0005](./docs/adr/0005-sdk-lane-versioning.md).
34
52
 
35
53
  The current `main` branch targets **Expo SDK 56** and releases as **`2.x`**. For Expo SDK 55, install **`1.x`** from the `v1` branch ([ADR-0005](./docs/adr/0005-sdk-lane-versioning.md)).
36
54
 
55
+ Auth payload note by lane:
56
+
57
+ - **`2.x` (`main`)**: Android token swap/refresh requests are normalized to match iOS (`code` for swap, `refresh_token` for refresh).
58
+ - **`1.x` (`v1`)**: Android keeps the legacy payload shape that includes additional form fields for compatibility with existing backends.
59
+
37
60
  ## Public API (Auth + App Remote)
38
61
 
39
62
  ```ts
@@ -56,7 +79,7 @@ import {
56
79
  } from "@wwdrew/expo-spotify-sdk";
57
80
  ```
58
81
 
59
- Top-level v0-style functions (`authenticateAsync`, `isAvailable`, etc.) are still exported for backward compatibility but are deprecated — see [Migration from v0.x](#migration-from-v0x).
82
+ Top-level v0-style functions were removed in **`2.x`**. If you still use them, stay on **`1.x`** or migrate — see [Migration from v0.x](#migration-from-v0x).
60
83
 
61
84
  **Not wrapped:** [Spotify Web API](https://developer.spotify.com/documentation/web-api) (`api.spotify.com`). Use the access token from `Auth.authenticate()` and call REST yourself. See [CONTEXT.md](./CONTEXT.md) for terminology.
62
85
 
@@ -67,6 +90,7 @@ Top-level v0-style functions (`authenticateAsync`, `isAvailable`, etc.) are stil
67
90
  npx expo install @wwdrew/expo-spotify-sdk
68
91
 
69
92
  # Expo SDK 55 only: npx expo install @wwdrew/expo-spotify-sdk@1
93
+ # (or @sdk55 — npm dist-tag for the v1 maintenance lane)
70
94
 
71
95
  # 2. Add the config plugin to app.config.ts / app.json (see Configuration below)
72
96
  # 3. Regenerate native projects
@@ -75,6 +99,20 @@ npx expo prebuild
75
99
 
76
100
  For bare React Native (no Expo CLI), see [Installation in bare React Native](#installation-in-bare-react-native).
77
101
 
102
+ ## Native SDK binaries
103
+
104
+ | Platform | Distribution |
105
+ | --- | --- |
106
+ | **iOS** | `SpotifyiOS` resolved from Spotify's GitHub via SPM at `pod install` ([ADR-0008](./docs/adr/0008-ios-spotify-sdk-via-spm.md)). Uses React Native's `spm_dependency` (RN 0.75+). Supported Expo SDK lanes: [Platform support](#platform-support). |
107
+ | **Android** | App Remote AAR downloaded from Spotify's GitHub at Gradle build. Auth SDK from Maven Central. |
108
+
109
+ | You are… | Action |
110
+ | --- | --- |
111
+ | **Using the published npm package** | Network on first native build per platform (`pod install` / Gradle). |
112
+ | **Developing this repo from git** | Same — no manual fetch scripts. |
113
+
114
+ See [Native SDK distribution](./docs/guides/native-sdk-distribution.md).
115
+
78
116
  ## Installation in bare React Native
79
117
 
80
118
  This library is an [Expo Module](https://docs.expo.dev/modules/overview/) and therefore requires `expo-modules-core` as a peer. If your project does not use the Expo managed workflow you will need to set this up manually.
@@ -289,7 +327,11 @@ Avoid reconnect loops by gating on connection state and attempting at most once
289
327
 
290
328
  ```ts
291
329
  import { useEffect, useRef } from "react";
292
- import { AppRemote, useConnectionState, useSession } from "@wwdrew/expo-spotify-sdk";
330
+ import {
331
+ AppRemote,
332
+ useConnectionState,
333
+ useSession,
334
+ } from "@wwdrew/expo-spotify-sdk";
293
335
 
294
336
  /**
295
337
  * Connect when a session exists and App Remote is disconnected.
@@ -313,24 +355,37 @@ export function useAutoConnectAppRemote(options?: { once?: boolean }) {
313
355
  }
314
356
  ```
315
357
 
316
- For iOS, if `connect()` fails with `CONNECTION_FAILED`, foreground the Spotify app and retry manually see [Troubleshooting](#troubleshooting).
358
+ For iOS, if `connect()` fails with `CONNECTION_FAILED`, the Spotify app has likely been suspended in the background — `connect()` can only attach to an already-running Spotify. Instead of asking the user to foreground Spotify and retry manually, call `AppRemote.authorizeAndPlay(accessToken, uri?)`, which wakes Spotify (launching it if needed), starts playback, and then connects:
359
+
360
+ ```ts
361
+ try {
362
+ await AppRemote.connect(session.accessToken);
363
+ } catch (e) {
364
+ if (e instanceof AppRemoteError && e.code === "CONNECTION_FAILED") {
365
+ // Spotify was suspended — wake it, start playing, and connect.
366
+ await AppRemote.authorizeAndPlay(session.accessToken);
367
+ }
368
+ }
369
+ ```
370
+
371
+ Note that `authorizeAndPlay()` always starts (or resumes) playback and briefly switches to the Spotify app — see [Troubleshooting](#troubleshooting).
317
372
 
318
373
  ## Spotify Premium and App Remote
319
374
 
320
- | Concern | Auth (`Auth.*`) | App Remote (`AppRemote.*`, `Player.*`, …) |
321
- | --- | --- | --- |
322
- | Spotify app installed | Recommended (native UX) | **Required** |
323
- | Spotify app running | No | **Required** — connect talks to the running Spotify process over IPC |
324
- | Spotify Premium | No | **Required** for reliable on-demand playback and rich player state (track name, transport, browse) |
325
- | Free account | Auth works | `Player.play()` may fail with `PREMIUM_REQUIRED`; `User.getCapabilities().canPlayOnDemand` is `false` |
375
+ | Concern | Auth (`Auth.*`) | App Remote (`AppRemote.*`, `Player.*`, …) |
376
+ | --------------------- | ----------------------- | ----------------------------------------------------------------------------------------------------- |
377
+ | Spotify app installed | Recommended (native UX) | **Required** |
378
+ | Spotify app running | No | **Required** — connect talks to the running Spotify process over IPC |
379
+ | Spotify Premium | No | **Required** for reliable on-demand playback and rich player state (track name, transport, browse) |
380
+ | Free account | Auth works | `Player.play()` may fail with `PREMIUM_REQUIRED`; `User.getCapabilities().canPlayOnDemand` is `false` |
326
381
 
327
382
  **This library does not play audio.** It remote-controls the official Spotify app. On Android especially, Free accounts often see empty or incomplete now-playing metadata even when connected.
328
383
 
329
384
  ## Migration from v0.x
330
385
 
331
- v0.x top-level functions remain exported but are deprecated (scheduled for removal in a future major).
386
+ **Removed in `2.x`.** The flat top-level functions (`authenticateAsync`, `isAvailable`, `refreshSessionAsync`, `cancelPendingAuthAsync`, `addSessionChangeListener`) and legacy type aliases (`SpotifyConfig`, `SpotifyErrorCode`) are no longer exported on the **`2.x`** lane.
332
387
 
333
- | v0.x | v1 |
388
+ | v0.x | v2 |
334
389
  | --- | --- |
335
390
  | `isAvailable()` | `Auth.isAvailable()` |
336
391
  | `authenticateAsync(config)` | `Auth.authenticate(config)` |
@@ -340,329 +395,20 @@ v0.x top-level functions remain exported but are deprecated (scheduled for remov
340
395
  | `SpotifyError` (auth throws) | `AuthError` (or `instanceof SpotifyError` as catch-all) |
341
396
  | `SpotifyErrorCode` | `AuthErrorCode` |
342
397
 
343
- `SpotifySession`, `SpotifyScope`, and config shapes are unchanged. For auth-specific `e.code` narrowing, use `instanceof AuthError` instead of `instanceof SpotifyError`.
344
-
345
- ## API reference
346
-
347
- ### `Auth`
348
-
349
- #### `Auth.isAvailable(): boolean`
350
-
351
- Returns `true` if the Spotify app is installed. Does not throw (not available on unsupported platforms).
352
-
353
- #### `Auth.authenticate(config): Promise<SpotifySession>`
354
-
355
- Starts OAuth via the installed Spotify app (or web fallback). Throws [`AuthError`](#autherror) on failure.
356
-
357
- | Field | Type | Required | Description |
358
- | --- | --- | --- | --- |
359
- | `scopes` | `SpotifyScope[]` | ✅ | At least one scope. Include `app-remote-control` for App Remote. |
360
- | `tokenSwapURL` | `string` | — | Code flow + server swap (recommended). Required on Android for a `refreshToken`. |
361
- | `tokenRefreshURL` | `string` | — | Refresh endpoint for iOS SDK and `Auth.refresh()`. |
362
- | `showDialog` | `boolean` | — | Force the consent screen. Default `false`. |
363
-
364
- | Return field | Type | Description |
365
- | --- | --- | --- |
366
- | `accessToken` | `string` | Bearer token for Web API and `AppRemote.connect()`. |
367
- | `refreshToken` | `string \| null` | `null` on Android without `tokenSwapURL`. |
368
- | `expirationDate` | `number` | Expiry as Unix epoch **milliseconds**. |
369
- | `scopes` | `SpotifyScope[]` | Granted scopes (requested-only on Android TOKEN flow). |
398
+ Need the deprecated shims temporarily? Pin **`1.x`**: `npm install @wwdrew/expo-spotify-sdk@1`.
370
399
 
371
- #### `Auth.cancelPending(): Promise<void>`
372
-
373
- Cancels an in-flight `Auth.authenticate()`. On iOS, call before retrying if you see `AUTH_IN_PROGRESS` after a dropped redirect. No-op on Android.
374
-
375
- ```ts
376
- await Auth.cancelPending();
377
- const session = await Auth.authenticate({ scopes: ["streaming"] });
378
- ```
379
-
380
- A cancelled call rejects with `AuthError` `code: "USER_CANCELLED"`.
381
-
382
- #### `Auth.refresh(config): Promise<SpotifySession>`
383
-
384
- Exchanges a refresh token via your refresh server. Pass through `scopes` from the previous session when the refresh response omits `scope`.
385
-
386
- ```ts
387
- const refreshed = await Auth.refresh({
388
- refreshToken: previous.refreshToken!,
389
- tokenRefreshURL: "https://your-server.example.com/refresh",
390
- scopes: previous.scopes,
391
- });
392
- ```
393
-
394
- #### `Auth.addListener("sessionChange", listener): Subscription`
395
-
396
- Central place to persist tokens. Fires for every `authenticate` / `refresh` — including calls you did not `await`.
397
-
398
- | `type` | Payload | When |
399
- | --- | --- | --- |
400
- | `"didInitiate"` | `{ session }` | `Auth.authenticate()` succeeded |
401
- | `"didRenew"` | `{ session }` | `Auth.refresh()` succeeded |
402
- | `"didFail"` | `{ error: { code, message } }` | Either call failed |
403
-
404
- #### `AuthError`
405
-
406
- ```ts
407
- import { Auth, AuthError } from "@wwdrew/expo-spotify-sdk";
408
-
409
- try {
410
- await Auth.authenticate({ scopes: ["streaming"] });
411
- } catch (e) {
412
- if (e instanceof AuthError) {
413
- switch (e.code) {
414
- case "USER_CANCELLED":
415
- case "AUTH_IN_PROGRESS":
416
- return;
417
- case "INVALID_CONFIG":
418
- case "NETWORK_ERROR":
419
- case "TOKEN_SWAP_FAILED":
420
- case "TOKEN_SWAP_PARSE_ERROR":
421
- case "SPOTIFY_NOT_INSTALLED":
422
- case "AUTH_ERROR":
423
- case "UNKNOWN":
424
- reportError(e);
425
- }
426
- }
427
- }
428
- ```
429
-
430
- `e.message` is the native reason string. On iOS `UNKNOWN` auth failures, the message includes the full `NSError` chain (e.g. `NSURLErrorDomain` to your `tokenSwapURL`). `e.cause` retains the original error for logging.
431
-
432
- ### `AppRemote`
400
+ `SpotifySession`, `SpotifyScope`, and config shapes are unchanged. For auth-specific `e.code` narrowing, use `instanceof AuthError` instead of `instanceof SpotifyError`.
433
401
 
434
- Connects to the **running** Spotify app. All `Player`, `User`, `Content`, and `Images` calls require an active connection.
402
+ ## Documentation
435
403
 
436
- | Method | Description |
404
+ | Guide | Contents |
437
405
  | --- | --- |
438
- | `connect(accessToken)` | Open IPC to Spotify. Resolves when connected. |
439
- | `disconnect()` | Tear down connection. |
440
- | `isConnected()` | Synchronous snapshot. |
441
- | `getConnectionState()` | `"disconnected"` \| `"connecting"` \| `"connected"`. |
442
- | `addListener("connectionStateChange", cb)` | State transitions. |
443
- | `addListener("connectionError", cb)` | Failures and drops (`AppRemoteError` codes). |
444
-
445
- **Platform notes:**
446
-
447
- - **iOS:** pass the access token from `Auth.authenticate()`. If connect fails with `CONNECTION_FAILED` / `Connection refused` (`NSPOSIXErrorDomain` 61), bring Spotify to the foreground and retry — backgrounded Spotify often has no transport listener yet.
448
- - **Android:** `accessToken` is accepted for API parity; the SDK uses the session cached in the Spotify app from your prior `Auth.authenticate()` call.
449
-
450
- Calling `connect()` while already connected is a no-op.
451
-
452
- ### `Player`
453
-
454
- - `Player.play(uri)`
455
- - `Player.pause()`
456
- - `Player.resume()`
457
- - `Player.skipNext()`
458
- - `Player.skipPrevious()`
459
- - `Player.seekTo(positionMs)`
460
- - `Player.setShuffle(enabled)`
461
- - `Player.setRepeatMode(mode)`
462
- - `Player.setPodcastPlaybackSpeed(speed)`
463
- - `Player.queue(uri)`
464
- - `Player.getPlayerState()`
465
- - `Player.getCrossfadeState()`
466
- - `Player.addListener("playerStateChange", cb)`
467
-
468
- ### `User`
469
-
470
- - `User.getCapabilities()`
471
- - `User.getLibraryState(uri)`
472
- - `User.addToLibrary(uri)`
473
- - `User.removeFromLibrary(uri)`
474
- - `User.addListener("capabilitiesChange", cb)`
475
- - `User.addLibraryStateListener(uri, cb)`
476
-
477
- ### `Content`
478
-
479
- - `Content.getRecommendedContentItems(type)`
480
- - `Content.getChildren(item)`
481
-
482
- ### `Images`
483
-
484
- - `Images.load(item, size)`
485
-
486
- ### Hooks
487
-
488
- Built on `useSyncExternalStore` over the native event streams. Subscribe in any component; no manual `addListener` cleanup required.
489
-
490
- - `useSession()` — latest `SpotifySession` from auth events (or `null`)
491
- - `useConnectionState()` — `disconnected` \| `connecting` \| `connected`
492
- - `usePlayerState()` — full player snapshot
493
- - `useCurrentTrack()` / `useIsPlaying()` / `usePlaybackPosition()` — derived from player state
494
- - `useCapabilities()` — `canPlayOnDemand` and related flags
495
- - `useLibraryState(uri)` — save state for one URI
496
-
497
- ## Error codes by namespace
498
-
499
- Every rejection is an instance of a namespace-specific subclass (`AuthError`, `AppRemoteError`, …) extending the abstract `SpotifyError` base. Catch with `instanceof` for typed `code` narrowing.
500
-
501
- ### `AuthErrorCode`
502
-
503
- | Code | When | What to do |
504
- | --- | --- | --- |
505
- | `USER_CANCELLED` | User closed auth or `Auth.cancelPending()` ran | Benign — no action |
506
- | `AUTH_IN_PROGRESS` | Concurrent `Auth.authenticate()` or iOS stuck pending auth | `await Auth.cancelPending()` then retry |
507
- | `INVALID_CONFIG` | Missing `clientID`, empty `scopes`, or bad plugin setup | Fix config plugin / `app.config`; run `expo prebuild` |
508
- | `NETWORK_ERROR` | Device cannot reach `tokenSwapURL` / `tokenRefreshURL` | Check dev server URL, HTTPS, device network |
509
- | `TOKEN_SWAP_FAILED` | Swap server returned non-2xx | Fix server; read status + body in `e.message` |
510
- | `TOKEN_SWAP_PARSE_ERROR` | Swap response was not valid token JSON | Fix server response shape |
511
- | `SPOTIFY_NOT_INSTALLED` | Spotify app not found (rare — web fallback may still run) | Prompt install or use web auth |
512
- | `AUTH_ERROR` | Spotify rejected the authorization | Check Dashboard redirect URI, scopes, test users |
513
- | `UNKNOWN` | Unexpected native failure | Read `e.message` / `e.cause`; file an issue with logs |
514
-
515
- ### `AppRemoteErrorCode`
516
-
517
- | Code | When | What to do |
518
- | --- | --- | --- |
519
- | `CONNECTION_FAILED` | `connect()` failed (app missing, refused, handshake error) | Open Spotify foreground; re-auth if token stale; retry once |
520
- | `CONNECTION_LOST` | Connection dropped mid-session | `AppRemote.connect()` again with fresh token |
521
- | `NOT_CONNECTED` | `AppRemote.*` called before connect completed | `await AppRemote.connect()` first |
522
- | `UNKNOWN` | Unexpected IPC failure | Read `e.message`; retry connect |
523
-
524
- ### `PlayerErrorCode`
525
-
526
- | Code | When | What to do |
527
- | --- | --- | --- |
528
- | `NOT_CONNECTED` | `Player.*` before `AppRemote.connect()` | Connect App Remote first |
529
- | `CONNECTION_LOST` | Dropped during a player call | Reconnect, then retry |
530
- | `PREMIUM_REQUIRED` | `canPlayOnDemand === false` (Free tier) | Upgrade account or use shuffle/context play only |
531
- | `INVALID_URI` | URI failed validation | Use `SpotifyURI.from()` |
532
- | `INVALID_PARAMETER` | Bad argument (e.g. negative seek) | Fix caller |
533
- | `OPERATION_NOT_ALLOWED` | Player restriction (can't skip, etc.) | Check `PlayerState` restrictions |
534
- | `UNKNOWN` | Other native player error | Read `e.message` |
535
-
536
- ### `UserErrorCode`
537
-
538
- | Code | When | What to do |
539
- | --- | --- | --- |
540
- | `NOT_CONNECTED` | `User.*` before connect | Connect App Remote first |
541
- | `CONNECTION_LOST` | Dropped during user API call | Reconnect |
542
- | `INVALID_URI` | Bad library URI | Use `SpotifyURI.from()` |
543
- | `OPERATION_NOT_ALLOWED` | Save/remove blocked (tier, region) | Check `LibraryState.canAdd` / capabilities |
544
- | `UNKNOWN` | Other native user error | Read `e.message` |
545
-
546
- ### `ContentErrorCode`
547
-
548
- | Code | When | What to do |
549
- | --- | --- | --- |
550
- | `NOT_CONNECTED` | `Content.*` before connect | Connect App Remote first |
551
- | `CONNECTION_LOST` | Dropped during browse | Reconnect |
552
- | `CONTENT_API_UNAVAILABLE` | Spotify app too old for Content API | Update Spotify app |
553
- | `UNKNOWN` | Other content error | Read `e.message` |
554
-
555
- ### `ImagesErrorCode`
556
-
557
- | Code | When | What to do |
558
- | --- | --- | --- |
559
- | `NOT_CONNECTED` | `Images.load()` before connect | Connect App Remote first |
560
- | `INVALID_URI` | Item has no loadable image | Skip artwork or use placeholder |
561
- | `IMAGE_LOAD_FAILED` | Spotify or disk write failed | Retry; check free space |
562
- | `UNKNOWN` | Other image error | Read `e.message` |
563
-
564
- ## Platform differences (parity)
565
-
566
- | Topic | iOS | Android |
567
- | --- | --- | --- |
568
- | `AppRemote.connect(accessToken)` | Token passed to `SPTAppRemote` | Token accepted for API parity; SDK uses session cached in Spotify app from prior auth |
569
- | `Auth.cancelPending()` | Clears stuck `SPTSessionManager` state | No-op |
570
- | Refresh token without swap | Possible (iOS TOKEN flow) | Not available — use `tokenSwapURL` |
571
- | `session.scopes` without swap | Granted scopes returned | Requested scopes only (not granted list) |
572
- | Premium / player metadata | Full App Remote when Premium | Free accounts often lack track titles / on-demand play |
573
- | Content / Images | Requires recent Spotify app | Same |
574
-
575
- ## Android implicit (TOKEN) flow is not recommended
576
-
577
- When `Auth.authenticate()` is called on Android **without** a `tokenSwapURL`, the Spotify Android SDK uses the implicit (TOKEN) flow. This flow has two hard limitations that **will not be fixed** — Spotify has deprecated it:
578
-
579
- 1. **No `refreshToken`.** The Android SDK does not expose a refresh token for implicit grants. `session.refreshToken` will always be `null`.
580
- 2. **`scopes` reflects what was requested, not what was granted.** The Android SDK does not return the actual granted scope list for TOKEN responses.
581
-
582
- The library emits a one-time `console.warn` at runtime when this path is taken.
583
-
584
- **The fix:** provide a `tokenSwapURL` to use the Authorization Code flow, which returns a full `refreshToken` and the actual granted `scopes` on both platforms.
585
-
586
- See [Spotify's migration guide](https://developer.spotify.com/documentation/android/tutorials/migration-token-code) for context, and the [token swap server section](#token-swap-server) below for a reference implementation.
587
-
588
- ## Token swap server
589
-
590
- The `tokenSwapURL` / `tokenRefreshURL` endpoints must be a server you control — **never** put your Spotify `CLIENT_SECRET` in the app bundle.
591
-
592
- ### Swap endpoint (`POST {tokenSwapURL}`)
593
-
594
- The native module sends a `application/x-www-form-urlencoded` body:
595
-
596
- ```
597
- code=<authorization-code>&redirect_uri=<redirect-uri>&client_id=<client-id>
598
- ```
599
-
600
- Your server POSTs these to `https://accounts.spotify.com/api/token` with `grant_type=authorization_code` and your `CLIENT_SECRET` in the `Authorization` header, then returns Spotify's response verbatim as `application/json`:
601
-
602
- ```json
603
- {
604
- "access_token": "BQA...",
605
- "token_type": "Bearer",
606
- "expires_in": 3600,
607
- "refresh_token": "AQA...",
608
- "scope": "user-read-email streaming"
609
- }
610
- ```
611
-
612
- ### Refresh endpoint (`POST {tokenRefreshURL}`)
613
-
614
- The native module sends:
615
-
616
- ```
617
- refresh_token=<token>&client_id=<client-id>
618
- ```
619
-
620
- Your server POSTs to `https://accounts.spotify.com/api/token` with `grant_type=refresh_token`. Return Spotify's response verbatim. If Spotify rotates the refresh token the response will contain a new `refresh_token`; if not, the field is absent — the library handles both cases correctly.
621
-
622
- ### Error responses
623
-
624
- Return a non-2xx HTTP status with a JSON body for structured error propagation. The library will reject with `TOKEN_SWAP_FAILED` and include the status code and (truncated) response body in `e.message`.
625
-
626
- ### Reference implementation
627
-
628
- The example app uses [Expo Router API routes](https://docs.expo.dev/router/reference/api-routes/) for the swap and refresh endpoints — no separate server process needed.
629
-
630
- **Before running the example, you need a Spotify app:**
631
-
632
- > **Note (February 2026 onwards):** Spotify now requires the app owner to have an active **Spotify Premium** subscription to use Development Mode apps. Development Mode apps are also limited to **5 test users** — each user must be explicitly added in your Dashboard under **User Management**. See [Spotify's February 2026 migration guide](https://developer.spotify.com/documentation/web-api/tutorials/february-2026-migration-guide) for details.
633
-
634
- 1. Go to [developer.spotify.com/dashboard](https://developer.spotify.com/dashboard) and create an app (or use an existing one).
635
- 2. In the app settings, under **APIs used**, enable **Web API** (required for the `/v1/me` profile call).
636
- 3. Under **Redirect URIs**, add `expo-spotify-sdk-example://authenticate` exactly and save.
637
- 4. Under **User Management**, add the Spotify accounts that will test the app (up to 5 in Development Mode).
638
-
639
- **Then populate the credentials:**
640
-
641
- ```sh
642
- cd example
643
- cp .env.local.example .env.local
644
- ```
645
-
646
- Edit `.env.local` — all three values are required:
647
-
648
- ```sh
649
- # From your Spotify app's dashboard page
650
- SPOTIFY_CLIENT_ID=your_client_id_here
651
- SPOTIFY_CLIENT_SECRET=your_client_secret_here
652
-
653
- # Must match the redirect URI you registered in the Spotify Dashboard
654
- SPOTIFY_REDIRECT_URI=expo-spotify-sdk-example://authenticate
655
- ```
656
-
657
- `SPOTIFY_CLIENT_ID` and `SPOTIFY_CLIENT_SECRET` are shown on your app's dashboard page. `SPOTIFY_CLIENT_SECRET` is revealed by clicking **View client secret**.
658
-
659
- **Then run the app:**
660
-
661
- ```sh
662
- npx expo start
663
- ```
664
-
665
- The `/swap` and `/refresh` API routes are served by the Expo dev server alongside the app. `Constants.expoConfig.hostUri` auto-detects the correct server URL for any device or simulator — no manual IP configuration needed.
406
+ | [API reference](./docs/api-reference.md) | All namespaces, methods, hooks |
407
+ | [Error codes](./docs/error-codes.md) | Per-namespace codes with when/what-to-do |
408
+ | [App Remote error mapping](./docs/app-remote-error-mapping.md) | iOS/Android native → JS mapping matrix |
409
+ | [Platform differences](./docs/guides/platform-differences.md) | iOS vs Android parity |
410
+ | [Native SDK distribution](./docs/guides/native-sdk-distribution.md) | How iOS/Android binaries are fetched, packaged, and bumped |
411
+ | [Token swap server](./docs/guides/token-swap-server.md) | Swap/refresh endpoints + example app setup |
666
412
 
667
413
  ## Troubleshooting
668
414
 
@@ -678,18 +424,18 @@ Ensure your app's URL scheme is registered in Xcode under **Info → URL Types**
678
424
  **`AUTH_IN_PROGRESS`**
679
425
  `Auth.authenticate()` was called while a previous call was still pending. Usually a concurrent call — wait for the first to settle.
680
426
 
681
- On iOS this can also be a stuck state when Spotify never redirects back. Call [`Auth.cancelPending()`](#authcancelpending-promisevoid) before retrying.
427
+ On iOS this can also be a stuck state when Spotify never redirects back. Call `Auth.cancelPending()` before retrying.
682
428
 
683
429
  **App Remote: `CONNECTION_FAILED` / `Connection refused` (iOS code 61)**
684
430
  The Spotify app is installed but its App Remote transport is not accepting connections. Common causes:
685
431
 
686
- 1. **Spotify is not in the foreground** — switch to Spotify, then retry `AppRemote.connect()`.
432
+ 1. **Spotify is suspended / not in the foreground** — `connect()` only attaches to a running Spotify. Call `AppRemote.authorizeAndPlay(accessToken, uri?)` to wake it (it launches Spotify, starts playback, and connects), or have the user switch to Spotify and retry `AppRemote.connect()`.
687
433
  2. **Connect ran before auth finished** — call `AppRemote.connect()` only after `Auth.authenticate()` resolves.
688
434
  3. **Stale access token** — refresh or re-authenticate, then reconnect.
689
435
  4. **Retry loop on startup** — avoid calling `connect()` on every render while `connectionState === "disconnected"`; gate on a one-shot flag or user action.
690
436
 
691
437
  **Token swap: `NETWORK_ERROR` / `Could not connect to the server` to `http://127.0.0.1:…/swap`**
692
- The swap URL must be reachable from the device running your app. During local dev, the Expo dev server must be running and serving API routes. Omit `tokenSwapURL` to test auth without a swap server (iOS TOKEN flow only; see [Android implicit flow](#android-implicit-token-flow-is-not-recommended)).
438
+ The swap URL must be reachable from the device running your app. During local dev, the Expo dev server must be running and serving API routes. Omit `tokenSwapURL` to test auth without a swap server (iOS TOKEN flow only; see [Platform differences](./docs/guides/platform-differences.md)).
693
439
 
694
440
  **Now playing shows no track title (Android, Free account)**
695
441
  App Remote player state is limited for non-Premium users. Check `GET /v1/me` → `product` and `User.getCapabilities().canPlayOnDemand`.
@@ -697,7 +443,9 @@ App Remote player state is limited for non-Premium users. Check `GET /v1/me` →
697
443
  ## Related docs
698
444
 
699
445
  - [CONTEXT.md](./CONTEXT.md) — terminology (Auth SDK vs App Remote vs Web API)
700
- - [docs/V1_PLAN.md](./docs/V1_PLAN.md) — implementation plan and release criteria
446
+ - [docs/guides/native-sdk-distribution.md](./docs/guides/native-sdk-distribution.md) — how native binaries are fetched and packaged
447
+ - [docs/api-reference.md](./docs/api-reference.md) — method reference
448
+ - [docs/error-codes.md](./docs/error-codes.md) — error code tables
701
449
  - [docs/QA_CHECKLIST.md](./docs/QA_CHECKLIST.md) — manual QA before a `2.x` release on `main` (or `1.x` on `v1`)
702
450
  - [docs/RELEASE.md](./docs/RELEASE.md) — Release Please on `main` (`2.x`); maintenance releases from `v1` (`1.x`)
703
451
  - [ATTRIBUTION.md](./ATTRIBUTION.md) — third-party SDKs and scope boundaries
@@ -1,4 +1,5 @@
1
1
  apply plugin: 'com.android.library'
2
+ apply from: 'spotify-native-sdk.gradle'
2
3
 
3
4
  group = 'expo.modules.spotifysdk'
4
5
  version = '0.5.0'
@@ -49,11 +50,9 @@ repositories {
49
50
  dependencies {
50
51
  implementation "com.spotify.android:auth:4.0.1"
51
52
 
52
- // Spotify App Remote SDK — NOT on Maven Central.
53
- // Download spotify-app-remote-release-0.8.0.aar from
54
- // https://github.com/spotify/android-sdk/releases and place it in android/libs/.
55
- // See android/libs/SETUP.md for full instructions.
56
- implementation files('libs/spotify-app-remote-release-0.8.0.aar')
53
+ // Spotify App Remote SDK — NOT on Maven Central; downloaded at build time
54
+ // (see android/spotify-native-sdk.gradle and ios/spotify-native-sdk-versions.json).
55
+ implementation files(spotifyAppRemoteAarFile)
57
56
  // App Remote is consumed as a local AAR, so transitive deps like Gson must
58
57
  // be declared explicitly to avoid NoClassDefFoundError at runtime.
59
58
  implementation "com.google.code.gson:gson:2.11.0"
@@ -1,29 +1,14 @@
1
- # Android App Remote SDK setup
1
+ # Android App Remote SDK
2
2
 
3
- The Spotify App Remote SDK for Android is not published to Maven Central and must be
4
- downloaded manually.
3
+ The Spotify App Remote SDK for Android is **not** on Maven Central. This module
4
+ downloads `spotify-app-remote-release-<version>.aar` from Spotify's GitHub
5
+ releases at Gradle build time (`android/spotify-native-sdk.gradle`).
5
6
 
6
- ## Steps
7
+ Version pin: `ios/spotify-native-sdk-versions.json` (`android` section).
7
8
 
8
- 1. Go to <https://github.com/spotify/android-sdk/releases>
9
- 2. Download the latest release archive (e.g. `spotify-android-sdk-x.x.x.zip`)
10
- 3. Extract `spotify-app-remote-release-x.x.x.aar` from the archive
11
- 4. Rename it to `spotify-app-remote-release-0.8.0.aar` (or update the version in
12
- `android/build.gradle` to match the downloaded version)
13
- 5. Place the `.aar` file in this directory (`android/libs/`)
9
+ The downloaded `.aar` is gitignored under `android/libs/`.
14
10
 
15
- The `flatDir` repository in `android/build.gradle` will pick it up automatically.
11
+ ## Bumping the App Remote version
16
12
 
17
- ## Why not Maven?
18
-
19
- The Spotify Auth SDK (`com.spotify.android:auth`) is on Maven Central, but the App Remote
20
- SDK is distributed exclusively via GitHub releases. This directory follows the same pattern
21
- as the iOS side, where `SpotifyiOS.xcframework` is committed directly to `ios/SpotifySDK/`.
22
-
23
- ## Proguard
24
-
25
- `consumer-rules.pro` already keeps the App Remote classes:
26
-
27
- ```
28
- -keep class com.spotify.android.appremote.** { *; }
29
- ```
13
+ Update `appRemoteVersion`, `appRemoteReleaseTag`, and `appRemoteSha256` in
14
+ `ios/spotify-native-sdk-versions.json`, then ship a new release of this package.