@wwdrew/expo-spotify-sdk 0.4.3 → 0.6.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 (81) hide show
  1. package/LICENSE +21 -3
  2. package/README.md +345 -96
  3. package/android/build.gradle +28 -22
  4. package/android/consumer-rules.pro +8 -0
  5. package/android/src/main/AndroidManifest.xml +3 -0
  6. package/android/src/main/java/expo/modules/spotifysdk/ExpoSpotifySDKModule.kt +130 -145
  7. package/android/src/main/java/expo/modules/spotifysdk/SpotifyAuthCoordinator.kt +36 -0
  8. package/android/src/main/java/expo/modules/spotifysdk/SpotifyAuthorizationContract.kt +45 -0
  9. package/android/src/main/java/expo/modules/spotifysdk/SpotifyConfig.kt +80 -0
  10. package/android/src/main/java/expo/modules/spotifysdk/SpotifyErrors.kt +34 -0
  11. package/android/src/main/java/expo/modules/spotifysdk/SpotifyTokenSwapClient.kt +154 -0
  12. package/build/ExpoSpotifySDK.types.d.ts +76 -1
  13. package/build/ExpoSpotifySDK.types.d.ts.map +1 -1
  14. package/build/ExpoSpotifySDK.types.js +12 -1
  15. package/build/ExpoSpotifySDK.types.js.map +1 -1
  16. package/build/ExpoSpotifySDKModule.web.d.ts +7 -3
  17. package/build/ExpoSpotifySDKModule.web.d.ts.map +1 -1
  18. package/build/ExpoSpotifySDKModule.web.js +17 -7
  19. package/build/ExpoSpotifySDKModule.web.js.map +1 -1
  20. package/build/index.d.ts +39 -2
  21. package/build/index.d.ts.map +1 -1
  22. package/build/index.js +117 -4
  23. package/build/index.js.map +1 -1
  24. package/expo-module.config.json +1 -1
  25. package/ios/ExpoSpotifyAppDelegate.swift +14 -0
  26. package/ios/ExpoSpotifyConfiguration.swift +33 -15
  27. package/ios/ExpoSpotifySDK.podspec +2 -4
  28. package/ios/ExpoSpotifySDKModule.swift +96 -26
  29. package/ios/SPTScopeSerializer.swift +40 -39
  30. package/ios/SpotifyAuthCoordinator.swift +133 -0
  31. package/ios/SpotifySDK/SpotifyiOS.xcframework/Info.plist +5 -9
  32. package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64/SpotifyiOS.framework/Headers/SPTAppRemote.h +43 -41
  33. package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64/SpotifyiOS.framework/Headers/SPTAppRemoteCommon.h +9 -8
  34. package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64/SpotifyiOS.framework/Headers/SPTAppRemoteConnectivityAPI.h +4 -3
  35. package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64/SpotifyiOS.framework/Headers/SPTAppRemoteContentAPI.h +8 -6
  36. package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64/SpotifyiOS.framework/Headers/SPTAppRemoteContentItem.h +3 -3
  37. package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64/SpotifyiOS.framework/Headers/SPTAppRemoteImageAPI.h +3 -3
  38. package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64/SpotifyiOS.framework/Headers/SPTAppRemotePlayerAPI.h +12 -7
  39. package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64/SpotifyiOS.framework/Headers/SPTAppRemoteUserAPI.h +4 -5
  40. package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64/SpotifyiOS.framework/Headers/SPTConfiguration.h +5 -5
  41. package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64/SpotifyiOS.framework/Headers/SPTError.h +7 -9
  42. package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64/SpotifyiOS.framework/Headers/SPTMacros.h +59 -23
  43. package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64/SpotifyiOS.framework/Headers/SPTScope.h +67 -63
  44. package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64/SpotifyiOS.framework/Headers/SPTSession.h +2 -1
  45. package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64/SpotifyiOS.framework/Headers/SPTSessionManager.h +59 -19
  46. package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64/SpotifyiOS.framework/Headers/SpotifyAppRemote.h +11 -15
  47. package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64/SpotifyiOS.framework/Headers/SpotifyiOS.h +1 -1
  48. package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64/SpotifyiOS.framework/Info.plist +0 -0
  49. package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64/SpotifyiOS.framework/Modules/module.modulemap +0 -1
  50. package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64/SpotifyiOS.framework/SpotifyiOS +0 -0
  51. package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64_x86_64-simulator/SpotifyiOS.framework/Headers/SPTAppRemote.h +43 -41
  52. package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64_x86_64-simulator/SpotifyiOS.framework/Headers/SPTAppRemoteCommon.h +9 -8
  53. package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64_x86_64-simulator/SpotifyiOS.framework/Headers/SPTAppRemoteConnectivityAPI.h +4 -3
  54. package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64_x86_64-simulator/SpotifyiOS.framework/Headers/SPTAppRemoteContentAPI.h +8 -6
  55. package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64_x86_64-simulator/SpotifyiOS.framework/Headers/SPTAppRemoteContentItem.h +3 -3
  56. package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64_x86_64-simulator/SpotifyiOS.framework/Headers/SPTAppRemoteImageAPI.h +3 -3
  57. package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64_x86_64-simulator/SpotifyiOS.framework/Headers/SPTAppRemotePlayerAPI.h +12 -7
  58. package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64_x86_64-simulator/SpotifyiOS.framework/Headers/SPTAppRemoteUserAPI.h +4 -5
  59. package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64_x86_64-simulator/SpotifyiOS.framework/Headers/SPTConfiguration.h +5 -5
  60. package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64_x86_64-simulator/SpotifyiOS.framework/Headers/SPTError.h +7 -9
  61. package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64_x86_64-simulator/SpotifyiOS.framework/Headers/SPTMacros.h +59 -23
  62. package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64_x86_64-simulator/SpotifyiOS.framework/Headers/SPTScope.h +67 -63
  63. package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64_x86_64-simulator/SpotifyiOS.framework/Headers/SPTSession.h +2 -1
  64. package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64_x86_64-simulator/SpotifyiOS.framework/Headers/SPTSessionManager.h +59 -19
  65. package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64_x86_64-simulator/SpotifyiOS.framework/Headers/SpotifyAppRemote.h +11 -15
  66. package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64_x86_64-simulator/SpotifyiOS.framework/Headers/SpotifyiOS.h +1 -1
  67. package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64_x86_64-simulator/SpotifyiOS.framework/Info.plist +0 -0
  68. package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64_x86_64-simulator/SpotifyiOS.framework/Modules/module.modulemap +0 -1
  69. package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64_x86_64-simulator/SpotifyiOS.framework/SpotifyiOS +0 -0
  70. package/ios/SpotifyTokenRefreshClient.swift +118 -0
  71. package/package.json +12 -11
  72. package/plugin/build/android/withSpotifyAndroidAppBuildGradle.js +15 -10
  73. package/plugin/build/ios/withSpotifyConfigValues.js +12 -7
  74. package/plugin/build/ios/withSpotifyURLScheme.d.ts +1 -1
  75. package/plugin/build/ios/withSpotifyURLScheme.js +9 -8
  76. package/plugin/build/types.d.ts +35 -0
  77. package/plugin/build/types.js +0 -19
  78. package/ios/ExpoSpotifyAuthDelegate.swift +0 -13
  79. package/ios/ExpoSpotifySessionManager+SessionManagerDelegate.swift +0 -16
  80. package/ios/ExpoSpotifySessionManager.swift +0 -90
  81. package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64_x86_64-simulator/SpotifyiOS.framework/_CodeSignature/CodeResources +0 -454
@@ -1,71 +1,75 @@
1
+ #import <Foundation/Foundation.h>
2
+
1
3
  // (Objective-C identifier, Swift name, string value, bit flag)
2
- #define SPT_SCOPE_LIST \
3
- SPT_SCOPE(SPTPlaylistReadPrivateScope, playlistReadPrivate, "playlist-read-private", (1 << 0)) \
4
- SPT_SCOPE(SPTPlaylistReadCollaborativeScope, playlistReadCollaborative, "playlist-read-collaborative", (1 << 1)) \
5
- SPT_SCOPE(SPTPlaylistModifyPublicScope, playlistModifyPublic, "playlist-modify-public", (1 << 2)) \
6
- SPT_SCOPE(SPTPlaylistModifyPrivateScope, playlistModifyPrivate, "playlist-modify-private", (1 << 3)) \
7
- SPT_SCOPE(SPTUserFollowReadScope, userFollowRead, "user-follow-read", (1 << 4)) \
8
- SPT_SCOPE(SPTUserFollowModifyScope, userFollowModify, "user-follow-modify", (1 << 5)) \
9
- SPT_SCOPE(SPTUserLibraryReadScope, userLibraryRead, "user-library-read", (1 << 6)) \
10
- SPT_SCOPE(SPTUserLibraryModifyScope, userLibraryModify, "user-library-modify", (1 << 7)) \
11
- SPT_SCOPE(SPTUserReadBirthDateScope, userReadBirthDate, "user-read-birthdate", (1 << 8)) \
12
- SPT_SCOPE(SPTUserReadEmailScope, userReadEmail, "user-read-email", (1 << 9)) \
13
- SPT_SCOPE(SPTUserReadPrivateScope, userReadPrivate, "user-read-private", (1 << 10)) \
14
- SPT_SCOPE(SPTUserTopReadScope, userTopRead, "user-top-read", (1 << 11)) \
15
- SPT_SCOPE(SPTUGCImageUploadScope, ugcImageUpload, "ugc-image-upload", (1 << 12)) \
16
- SPT_SCOPE(SPTStreamingScope, streaming, "streaming", (1 << 13)) \
17
- SPT_SCOPE(SPTAppRemoteControlScope, appRemoteControl, "app-remote-control", (1 << 14)) \
18
- SPT_SCOPE(SPTUserReadPlaybackStateScope, userReadPlaybackState, "user-read-playback-state", (1 << 15)) \
19
- SPT_SCOPE(SPTUserModifyPlaybackStateScope, userModifyPlaybackState, "user-modify-playback-state", (1 << 16)) \
20
- SPT_SCOPE(SPTUserReadCurrentlyPlayingScope, userReadCurrentlyPlaying, "user-read-currently-playing", (1 << 17)) \
21
- SPT_SCOPE(SPTUserReadRecentlyPlayedScope, userReadRecentlyPlayed, "user-read-recently-played", (1 << 18)) \
22
- SPT_SCOPE(SPTOpenIdScope, openid, "openid", (1 << 19))
4
+ #define SPT_SCOPE_LIST \
5
+ SPT_SCOPE(SPTPlaylistReadPrivateScope, playlistReadPrivate, "playlist-read-private", (1 << 0)) \
6
+ SPT_SCOPE(SPTPlaylistReadCollaborativeScope, playlistReadCollaborative, "playlist-read-collaborative", (1 << 1)) \
7
+ SPT_SCOPE(SPTPlaylistModifyPublicScope, playlistModifyPublic, "playlist-modify-public", (1 << 2)) \
8
+ SPT_SCOPE(SPTPlaylistModifyPrivateScope, playlistModifyPrivate, "playlist-modify-private", (1 << 3)) \
9
+ SPT_SCOPE(SPTUserFollowReadScope, userFollowRead, "user-follow-read", (1 << 4)) \
10
+ SPT_SCOPE(SPTUserFollowModifyScope, userFollowModify, "user-follow-modify", (1 << 5)) \
11
+ SPT_SCOPE(SPTUserLibraryReadScope, userLibraryRead, "user-library-read", (1 << 6)) \
12
+ SPT_SCOPE(SPTUserLibraryModifyScope, userLibraryModify, "user-library-modify", (1 << 7)) \
13
+ SPT_SCOPE(SPTUserReadBirthDateScope, userReadBirthDate, "user-read-birthdate", (1 << 8)) \
14
+ SPT_SCOPE(SPTUserReadEmailScope, userReadEmail, "user-read-email", (1 << 9)) \
15
+ SPT_SCOPE(SPTUserReadPrivateScope, userReadPrivate, "user-read-private", (1 << 10)) \
16
+ SPT_SCOPE(SPTUserTopReadScope, userTopRead, "user-top-read", (1 << 11)) \
17
+ SPT_SCOPE(SPTUGCImageUploadScope, ugcImageUpload, "ugc-image-upload", (1 << 12)) \
18
+ SPT_SCOPE(SPTStreamingScope, streaming, "streaming", (1 << 13)) \
19
+ SPT_SCOPE(SPTAppRemoteControlScope, appRemoteControl, "app-remote-control", (1 << 14)) \
20
+ SPT_SCOPE(SPTUserReadPlaybackStateScope, userReadPlaybackState, "user-read-playback-state", (1 << 15)) \
21
+ SPT_SCOPE(SPTUserModifyPlaybackStateScope, userModifyPlaybackState, "user-modify-playback-state", (1 << 16)) \
22
+ SPT_SCOPE(SPTUserReadCurrentlyPlayingScope, userReadCurrentlyPlaying, "user-read-currently-playing", (1 << 17)) \
23
+ SPT_SCOPE(SPTUserReadRecentlyPlayedScope, userReadRecentlyPlayed, "user-read-recently-played", (1 << 18)) \
24
+ SPT_SCOPE(SPTOpenIdScope, openid, "openid", (1 << 19))
23
25
 
24
26
  /**
25
27
  `SPTScope` represents the OAuth scopes that declare how your app wants to access a user's account.
26
28
  See https://developer.spotify.com/web-api/using-scopes/ for more information.
27
29
  */
28
- typedef NS_OPTIONS(NSUInteger, SPTScope)
29
- {
30
+ typedef NS_OPTIONS(NSUInteger, SPTScope) {
30
31
  /// Read access to user's private playlists.
31
32
  SPTPlaylistReadPrivateScope NS_SWIFT_NAME(playlistReadPrivate) = (1 << 0),
32
- /// Include collaborative playlists when requesting a user's playlists.
33
- SPTPlaylistReadCollaborativeScope NS_SWIFT_NAME(playlistReadCollaborative) = (1 << 1),
34
- /// Write access to a user's public playlists.
35
- SPTPlaylistModifyPublicScope NS_SWIFT_NAME(playlistModifyPublic) = (1 << 2),
36
- /// Write access to a user's private playlists.
37
- SPTPlaylistModifyPrivateScope NS_SWIFT_NAME(playlistModifyPrivate) = (1 << 3),
38
- /// Read access to the list of artists and other users that the user follows.
39
- SPTUserFollowReadScope NS_SWIFT_NAME(userFollowRead) = (1 << 4),
40
- /// Write/delete access to the list of artists and other users that the user follows.
41
- SPTUserFollowModifyScope NS_SWIFT_NAME(userFollowModify) = (1 << 5),
42
- /// Read access to a user's "Your Music" library.
43
- SPTUserLibraryReadScope NS_SWIFT_NAME(userLibraryRead) = (1 << 6),
44
- /// Write/delete access to a user's "Your Music" library.
45
- SPTUserLibraryModifyScope NS_SWIFT_NAME(userLibraryModify) = (1 << 7),
46
- /// Read access to the user's birthdate.
47
- SPTUserReadBirthDateScope NS_SWIFT_NAME(userReadBirthDate) = (1 << 8),
48
- /// Read access to user’s email address.
49
- SPTUserReadEmailScope NS_SWIFT_NAME(userReadEmail) = (1 << 9),
50
- /// Read access to user’s subscription details (type of user account).
51
- SPTUserReadPrivateScope NS_SWIFT_NAME(userReadPrivate) = (1 << 10),
52
- /// Read access to a user's top artists and tracks.
53
- SPTUserTopReadScope NS_SWIFT_NAME(userTopRead) = (1 << 11),
54
- /// Upload user generated content images
55
- SPTUGCImageUploadScope NS_SWIFT_NAME(ugcImageUpload) = (1 << 12),
56
- /// Control playback of a Spotify track.
57
- SPTStreamingScope NS_SWIFT_NAME(streaming) = (1 << 13),
58
- /// Use App Remote to control playback in the Spotify app
59
- SPTAppRemoteControlScope NS_SWIFT_NAME(appRemoteControl) = (1 << 14),
60
- /// Read access to a user’s player state.
61
- SPTUserReadPlaybackStateScope NS_SWIFT_NAME(userReadPlaybackState) = (1 << 15),
62
- /// Write access to a user’s playback state
63
- SPTUserModifyPlaybackStateScope NS_SWIFT_NAME(userModifyPlaybackState) = (1 << 16),
64
- /// Read access to a user’s currently playing track
65
- SPTUserReadCurrentlyPlayingScope NS_SWIFT_NAME(userReadCurrentlyPlaying) = (1 << 17),
66
- /// Read access to a user’s currently playing track
67
- SPTUserReadRecentlyPlayedScope NS_SWIFT_NAME(userReadRecentlyPlayed) = (1 << 18),
68
- /// Indicate that the application intends to use OIDC to verify the user's identity
69
- SPTOpenIdScope NS_SWIFT_NAME(openid) = (1 << 19),
70
- };
71
-
33
+ /// Include collaborative playlists when requesting a user's playlists.
34
+ SPTPlaylistReadCollaborativeScope NS_SWIFT_NAME(playlistReadCollaborative) = (1 << 1),
35
+ /// Write access to a user's public playlists.
36
+ SPTPlaylistModifyPublicScope NS_SWIFT_NAME(playlistModifyPublic) = (1 << 2),
37
+ /// Write access to a user's private playlists.
38
+ SPTPlaylistModifyPrivateScope NS_SWIFT_NAME(playlistModifyPrivate) = (1 << 3),
39
+ /// Read access to the list of artists and other users that the user follows.
40
+ SPTUserFollowReadScope NS_SWIFT_NAME(userFollowRead) = (1 << 4),
41
+ /// Write/delete access to the list of artists and other users that the user follows.
42
+ SPTUserFollowModifyScope NS_SWIFT_NAME(userFollowModify) = (1 << 5),
43
+ /// Read access to a user's "Your Music" library.
44
+ SPTUserLibraryReadScope NS_SWIFT_NAME(userLibraryRead) = (1 << 6),
45
+ /// Write/delete access to a user's "Your Music" library.
46
+ SPTUserLibraryModifyScope NS_SWIFT_NAME(userLibraryModify) = (1 << 7),
47
+ /// Read access to the user's birthdate.
48
+ SPTUserReadBirthDateScope NS_SWIFT_NAME(userReadBirthDate) = (1 << 8),
49
+ /// Read access to user’s email address.
50
+ SPTUserReadEmailScope NS_SWIFT_NAME(userReadEmail) = (1 << 9),
51
+ /// Read access to user’s subscription details (type of user account).
52
+ SPTUserReadPrivateScope NS_SWIFT_NAME(userReadPrivate) = (1 << 10),
53
+ /// Read access to a user's top artists and tracks.
54
+ SPTUserTopReadScope NS_SWIFT_NAME(userTopRead) = (1 << 11),
55
+ /// Upload user generated content images
56
+ SPTUGCImageUploadScope NS_SWIFT_NAME(ugcImageUpload) = (1 << 12),
57
+ /// Control playback of a Spotify track.
58
+ SPTStreamingScope NS_SWIFT_NAME(streaming) = (1 << 13),
59
+ /// Use App Remote to control playback in the Spotify app
60
+ SPTAppRemoteControlScope NS_SWIFT_NAME(appRemoteControl) = (1 << 14),
61
+ /// Read access to a user’s player state.
62
+ SPTUserReadPlaybackStateScope NS_SWIFT_NAME(userReadPlaybackState) = (1 << 15),
63
+ /// Write access to a user’s playback state
64
+ SPTUserModifyPlaybackStateScope NS_SWIFT_NAME(userModifyPlaybackState) = (1 << 16),
65
+ /// Read access to a user’s currently playing
66
+ /// track
67
+ SPTUserReadCurrentlyPlayingScope NS_SWIFT_NAME(userReadCurrentlyPlaying) = (1 << 17),
68
+ /// Read access to a user’s currently
69
+ /// playing track
70
+ SPTUserReadRecentlyPlayedScope NS_SWIFT_NAME(userReadRecentlyPlayed) = (1 << 18),
71
+ /// Indicate that the application
72
+ /// intends to use OIDC to verify the
73
+ /// user's identity
74
+ SPTOpenIdScope NS_SWIFT_NAME(openid) = (1 << 19),
75
+ };
@@ -20,7 +20,8 @@ NS_ASSUME_NONNULL_BEGIN
20
20
 
21
21
  /**
22
22
  Check whether the session has expired. `YES` if expired; `NO` otherwise.
23
- Note: The session is considered expired once the current date and time is equal to or greater than the expiration date and time.
23
+ Note: The session is considered expired once the current date and time is equal to or greater than the expiration date
24
+ and time.
24
25
  */
25
26
  @property (nonatomic, readonly, getter=isExpired) BOOL expired;
26
27
 
@@ -7,15 +7,18 @@ NS_ASSUME_NONNULL_BEGIN
7
7
  @protocol SPTSessionManagerDelegate;
8
8
 
9
9
  /// Options for determining the most suitable method for authorization
10
- typedef NS_OPTIONS(NSUInteger, SPTAuthorizationOptions)
11
- {
12
- /// Authorize using a suitable method. If Spotify is installed the app will be used instead of a web view
10
+ typedef NS_OPTIONS(NSUInteger, SPTAuthorizationOptions) {
11
+ /// Authorize using a suitable method. If Spotify is installed the app will be used instead of ASWebAuthentication.
13
12
  SPTDefaultAuthorizationOption NS_SWIFT_NAME(default) = (0),
14
- /// Authorize using only the Spotify client. If Spotify is not installed authorization will fail.
15
- SPTClientAuthorizationOption NS_SWIFT_NAME(clientOnly) = (1 << 0),
16
- } NS_SWIFT_NAME(AuthorizationOptions);
13
+ /// Authorize using only the Spotify client. If Spotify is not installed authorization will fail.
14
+ SPTClientAuthorizationOption NS_SWIFT_NAME(clientOnly) = (1 << 0),
15
+ /// Use this option if you have reached the limit of `LSApplicationQueriesSchemes` in your Info.plist
16
+ /// Attempt to open Spotify app directly without using `canOpenURL`. Falls back to ASWebAuthentication if
17
+ /// opening fails.
18
+ SPTSpotifySchemeNotRegisteredAuthorizationOption NS_SWIFT_NAME(spotifySchemeNotRegistered) = (1 << 1),
19
+ } NS_SWIFT_NAME(AuthorizationOptions);
17
20
 
18
- typedef NSString * const SPTAuthorizationCode;
21
+ typedef NSString *const SPTAuthorizationCode;
19
22
 
20
23
 
21
24
  /// This class manages a Spotify user session, in the form of `SPTSession`.
@@ -24,14 +27,16 @@ typedef NSString * const SPTAuthorizationCode;
24
27
  /// The `SPTSession` for the `SPTSessionManager` to manage. If no user has been authenticated this will be nil
25
28
  @property (nullable, nonatomic, strong) SPTSession *session;
26
29
 
27
- /// The `SPTSessionManagerDelegate` to notify of initiating a session, renewing a session, and/or errors related to a session
28
- @property (nullable, nonatomic, weak) id <SPTSessionManagerDelegate> delegate;
30
+ /// The `SPTSessionManagerDelegate` to notify of initiating a session, renewing a session, and/or errors related to a
31
+ /// session
32
+ @property (nullable, nonatomic, weak) id<SPTSessionManagerDelegate> delegate;
29
33
 
30
34
  /**
31
35
  Determine if the Spotify app is installed.
32
36
 
33
37
  Will be `YES` if the Spotify app is installed (and the URL scheme is whitelisted), otherwise is `NO`.
34
- Note: You must whitelist the "spotify" URL scheme in your info.plist LSApplicationQueriesSchemes or this will always be NO
38
+ Note: You must whitelist the "spotify" URL scheme in your info.plist LSApplicationQueriesSchemes or this will always be
39
+ NO
35
40
  */
36
41
  @property (nonatomic, readonly, getter=isSpotifyAppInstalled) BOOL spotifyAppInstalled;
37
42
 
@@ -46,7 +51,8 @@ typedef NSString * const SPTAuthorizationCode;
46
51
  /**
47
52
  Initiate the authorization process
48
53
 
49
- @param scope The scope to request, e.g. `SPTPlaylistReadPrivateScope`|`SPTUserReadEmailScope` if you wish to request read access to private playlists, and read access to the user's email address.
54
+ @param scope The scope to request, e.g. `SPTPlaylistReadPrivateScope`|`SPTUserReadEmailScope` if you wish to request
55
+ read access to private playlists, and read access to the user's email address.
50
56
  @param options Options bitmask that informs authorization behavior.
51
57
  @param campaign The campaign identifier, to help attribute where the account linking was initiated from.
52
58
  See `SPTSessionManagerDelegate` for messages regarding changes in session state.
@@ -55,33 +61,62 @@ typedef NSString * const SPTAuthorizationCode;
55
61
  options:(SPTAuthorizationOptions)options
56
62
  campaign:(nullable NSString *)campaign;
57
63
 
64
+ /**
65
+ Initiate the authorization process
66
+ @note Prefer `initiateSessionWithScope:options:campaign` instead, unless you need additional scopes that aren't listed
67
+ in `SPTScope`
68
+ @param scope The scope to request, e.g. `"playlist-read-private user-read-email"` if you wish to request read access to
69
+ private playlists, and read access to the user's email address.
70
+ @param options Options bitmask that informs authorization behavior.
71
+ @param campaign The campaign identifier, to help attribute where the account linking was initiated from.
72
+ See `SPTSessionManagerDelegate` for messages regarding changes in session state.
73
+ */
74
+ - (void)initiateSessionWithRawScope:(NSString *)scope
75
+ options:(SPTAuthorizationOptions)options
76
+ campaign:(nullable NSString *)campaign;
77
+
58
78
  /// Attempt to renew the access token, using the refresh token in the current `SPTSession` which must be valid.
59
79
  - (void)renewSession;
60
80
 
61
81
  /**
62
82
  Create an `SPTSessionManager` with the provided configuration.
63
-
83
+
64
84
  @param configuration An `SPTConfiguration` object.
65
85
  @param delegate An optional delegate conforming to `SPTSessionManagerDelegate`.
66
86
  @return An `SPTSessionManager` with the desired configuration.
67
87
  */
68
88
  - (instancetype)initWithConfiguration:(SPTConfiguration *)configuration
69
- delegate:(nullable id <SPTSessionManagerDelegate>)delegate;
89
+ delegate:(nullable id<SPTSessionManagerDelegate>)delegate;
70
90
 
71
91
  + (instancetype)sessionManagerWithConfiguration:(SPTConfiguration *)configuration
72
- delegate:(nullable id <SPTSessionManagerDelegate>)delegate;
92
+ delegate:(nullable id<SPTSessionManagerDelegate>)delegate;
73
93
  /**
74
94
  Handle openURL callbacks from the `AppDelegate`
75
95
 
76
96
  @param application The `UIApplication` passed into the matching `AppDelegate` method
77
97
  @param URL The URL to attempt to parse the access token from
78
98
  @param options The options passed in to the matching `AppDelegate` method
79
- @return Returns `YES` if `SPTSessionManager` recognizes the URL and will attempt to parse an access token, otherwise returns `NO`.
99
+ @return Returns `YES` if `SPTSessionManager` recognizes the URL and will attempt to parse an access token, otherwise
100
+ returns `NO`.
80
101
  */
81
102
  - (BOOL)application:(UIApplication *)application
82
103
  openURL:(NSURL *)URL
83
104
  options:(NSDictionary<UIApplicationOpenURLOptionsKey, id> *)options;
84
105
 
106
+ /**
107
+ Handle continueUserActivity callbacks from the `AppDelegate`
108
+
109
+ @param application The `UIApplication` passed into the matching `AppDelegate` method
110
+ @param userActivity An object encapsulating a user activity supported by this responder.
111
+ @param restorationHandler A block to execute if your app creates objects to perform the task the user was performing
112
+ @return Returns `YES` if `SPTSessionManager` recognizes the URL and will attempt to parse an access token, otherwise
113
+ returns `NO`.
114
+ */
115
+ - (BOOL)application:(UIApplication *)application
116
+ continueUserActivity:(NSUserActivity *)userActivity
117
+ restorationHandler:
118
+ (void (^)(NSArray<id<UIUserActivityRestoring>> *__nullable restorableObjects))restorationHandler;
119
+
85
120
  @end
86
121
 
87
122
  /// The `SPTSessionManagerDelegate` to use for monitoring state changes of a `SPTSessionManager`
@@ -93,7 +128,8 @@ typedef NSString * const SPTAuthorizationCode;
93
128
  @param manager The `SPTSessionManager` that initiated the session.
94
129
  @param session The initiated `SPTSession` object.
95
130
  */
96
- - (void)sessionManager:(SPTSessionManager *)manager didInitiateSession:(SPTSession *)session NS_SWIFT_NAME(sessionManager(manager:didInitiate:));
131
+ - (void)sessionManager:(SPTSessionManager *)manager
132
+ didInitiateSession:(SPTSession *)session NS_SWIFT_NAME(sessionManager(manager:didInitiate:));
97
133
 
98
134
  /**
99
135
  This message is sent when the manager failed to initiate or renew a session.
@@ -101,7 +137,8 @@ typedef NSString * const SPTAuthorizationCode;
101
137
  @param manager The `SPTSessionManager` instance.
102
138
  @param error The `NSError` that occured.
103
139
  */
104
- - (void)sessionManager:(SPTSessionManager *)manager didFailWithError:(NSError *)error NS_SWIFT_NAME(sessionManager(manager:didFailWith:));
140
+ - (void)sessionManager:(SPTSessionManager *)manager
141
+ didFailWithError:(NSError *)error NS_SWIFT_NAME(sessionManager(manager:didFailWith:));
105
142
 
106
143
  @optional
107
144
 
@@ -111,7 +148,8 @@ typedef NSString * const SPTAuthorizationCode;
111
148
  @param manager The `SPTSessionManager` instance.
112
149
  @param session The renewed `SPTSession` object.
113
150
  */
114
- - (void)sessionManager:(SPTSessionManager *)manager didRenewSession:(SPTSession *)session NS_SWIFT_NAME(sessionManager(manager:didRenew:));
151
+ - (void)sessionManager:(SPTSessionManager *)manager
152
+ didRenewSession:(SPTSession *)session NS_SWIFT_NAME(sessionManager(manager:didRenew:));
115
153
 
116
154
  /**
117
155
  Sent when the `SPTSessionManager` has obtained an authorization code,
@@ -122,7 +160,9 @@ typedef NSString * const SPTAuthorizationCode;
122
160
  @param code An OAuth authorization code.
123
161
  @return `NO` to request the access token yourself; `YES` to let `SPTSessionManager` handle it.
124
162
  */
125
- - (BOOL)sessionManager:(SPTSessionManager *)manager shouldRequestAccessTokenWithAuthorizationCode:(SPTAuthorizationCode)code NS_SWIFT_NAME(sessionManager(manager:shouldRequestAccessTokenWith:));
163
+ - (BOOL)sessionManager:(SPTSessionManager *)manager
164
+ shouldRequestAccessTokenWithAuthorizationCode:(SPTAuthorizationCode)code
165
+ NS_SWIFT_NAME(sessionManager(manager:shouldRequestAccessTokenWith:));
126
166
 
127
167
  @end
128
168
 
@@ -4,29 +4,25 @@
4
4
  #define _SPOTIFYAPPREMOTE_
5
5
 
6
6
  #import "SPTAppRemote.h"
7
+ #import "SPTAppRemoteAlbum.h"
8
+ #import "SPTAppRemoteArtist.h"
7
9
  #import "SPTAppRemoteCommon.h"
8
-
9
10
  #import "SPTAppRemoteConnectionParams.h"
10
-
11
- #import "SPTAppRemoteImageAPI.h"
12
- #import "SPTAppRemotePlayerAPI.h"
13
- #import "SPTAppRemoteUserAPI.h"
11
+ #import "SPTAppRemoteConnectivityAPI.h"
12
+ #import "SPTAppRemoteConnectivityState.h"
14
13
  #import "SPTAppRemoteContentAPI.h"
15
-
16
- #import "SPTAppRemoteAlbum.h"
17
- #import "SPTAppRemoteArtist.h"
14
+ #import "SPTAppRemoteContentItem.h"
15
+ #import "SPTAppRemoteCrossfadeState.h"
16
+ #import "SPTAppRemoteImageAPI.h"
17
+ #import "SPTAppRemoteLibraryState.h"
18
18
  #import "SPTAppRemotePlaybackOptions.h"
19
19
  #import "SPTAppRemotePlaybackRestrictions.h"
20
+ #import "SPTAppRemotePlayerAPI.h"
20
21
  #import "SPTAppRemotePlayerState.h"
21
- #import "SPTAppRemoteCrossfadeState.h"
22
+ #import "SPTAppRemotePodcastPlaybackSpeed.h"
22
23
  #import "SPTAppRemoteTrack.h"
24
+ #import "SPTAppRemoteUserAPI.h"
23
25
  #import "SPTAppRemoteUserCapabilities.h"
24
- #import "SPTAppRemoteLibraryState.h"
25
- #import "SPTAppRemoteContentItem.h"
26
- #import "SPTAppRemotePodcastPlaybackSpeed.h"
27
- #import "SPTAppRemoteConnectivityAPI.h"
28
- #import "SPTAppRemoteConnectivityState.h"
29
-
30
26
  #import "SPTConfiguration.h"
31
27
 
32
28
  #endif /* _SPOTIFYAPPREMOTE_ */
@@ -1,7 +1,7 @@
1
1
  #ifndef _SPOTIFYIOS_
2
2
  #define _SPOTIFYIOS_
3
3
 
4
- #import "SpotifyAppRemote.h"
5
4
  #import "SPTLogin.h"
5
+ #import "SpotifyAppRemote.h"
6
6
 
7
7
  #endif /* _SPOTIFYIOS_ */
@@ -1,6 +1,5 @@
1
1
  framework module SpotifyiOS {
2
2
  umbrella header "SpotifyiOS.h"
3
3
  export *
4
-
5
4
  module * { export * }
6
5
  }
@@ -0,0 +1,118 @@
1
+ import Foundation
2
+
3
+ enum SpotifyRefreshError: Error {
4
+ case invalidURL(String)
5
+ case network(Error)
6
+ case http(status: Int, body: String?)
7
+ case parse(String)
8
+
9
+ var code: String {
10
+ switch self {
11
+ case .invalidURL: return "INVALID_CONFIG"
12
+ case .network: return "NETWORK_ERROR"
13
+ case .http: return "TOKEN_SWAP_FAILED"
14
+ case .parse: return "TOKEN_SWAP_PARSE_ERROR"
15
+ }
16
+ }
17
+
18
+ var message: String {
19
+ switch self {
20
+ case .invalidURL(let s):
21
+ return "Invalid token refresh URL: \(s)"
22
+ case .network(let err):
23
+ return err.localizedDescription
24
+ case .http(let status, let body):
25
+ let trimmed = body.map { String($0.prefix(512)) } ?? ""
26
+ return "Token refresh server returned HTTP \(status)\(trimmed.isEmpty ? "" : ": \(trimmed)")"
27
+ case .parse(let m):
28
+ return m
29
+ }
30
+ }
31
+ }
32
+
33
+ struct SpotifyRefreshResult {
34
+ let accessToken: String
35
+ let refreshToken: String?
36
+ let expirationDate: Int64
37
+ let scopes: [String]
38
+ }
39
+
40
+ struct SpotifyTokenRefreshClient {
41
+ let sdkVersion: String
42
+ let clientID: String
43
+ let session: URLSession
44
+
45
+ init(sdkVersion: String, clientID: String, session: URLSession = .shared) {
46
+ self.sdkVersion = sdkVersion
47
+ self.clientID = clientID
48
+ self.session = session
49
+ }
50
+
51
+ func refresh(
52
+ refreshToken: String,
53
+ tokenRefreshURL urlString: String,
54
+ previousScopes: [String]
55
+ ) async throws -> SpotifyRefreshResult {
56
+ guard let url = URL(string: urlString) else {
57
+ throw SpotifyRefreshError.invalidURL(urlString)
58
+ }
59
+ var request = URLRequest(url: url)
60
+ request.httpMethod = "POST"
61
+ request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
62
+ request.setValue("expo-spotify-sdk/\(sdkVersion)", forHTTPHeaderField: "User-Agent")
63
+ request.httpBody = formURLEncoded([
64
+ "refresh_token": refreshToken,
65
+ "client_id": clientID,
66
+ ]).data(using: .utf8)
67
+
68
+ let data: Data
69
+ let response: URLResponse
70
+ do {
71
+ (data, response) = try await session.data(for: request)
72
+ } catch {
73
+ throw SpotifyRefreshError.network(error)
74
+ }
75
+
76
+ guard let http = response as? HTTPURLResponse else {
77
+ throw SpotifyRefreshError.parse("Non-HTTP response from refresh endpoint")
78
+ }
79
+ guard (200..<300).contains(http.statusCode) else {
80
+ let body = String(data: data, encoding: .utf8)
81
+ throw SpotifyRefreshError.http(status: http.statusCode, body: body)
82
+ }
83
+
84
+ let json: [String: Any]
85
+ do {
86
+ guard let parsed = try JSONSerialization.jsonObject(with: data) as? [String: Any] else {
87
+ throw SpotifyRefreshError.parse("Refresh response was not a JSON object")
88
+ }
89
+ json = parsed
90
+ } catch {
91
+ throw SpotifyRefreshError.parse("Refresh response was not valid JSON: \(error.localizedDescription)")
92
+ }
93
+
94
+ guard let accessToken = (json["access_token"] as? String), !accessToken.isEmpty else {
95
+ throw SpotifyRefreshError.parse("Refresh response missing required field: access_token")
96
+ }
97
+ guard let expiresIn = json["expires_in"] as? Int else {
98
+ throw SpotifyRefreshError.parse("Refresh response missing required field: expires_in")
99
+ }
100
+ let rotatedRefreshToken = (json["refresh_token"] as? String).flatMap { $0.isEmpty ? nil : $0 }
101
+ let scopeString = json["scope"] as? String
102
+ let scopes = scopeString?.split(separator: " ").map(String.init) ?? previousScopes
103
+ let expirationDate = Int64(Date().timeIntervalSince1970 * 1000) + Int64(expiresIn) * 1000
104
+
105
+ return SpotifyRefreshResult(
106
+ accessToken: accessToken,
107
+ refreshToken: rotatedRefreshToken ?? refreshToken,
108
+ expirationDate: expirationDate,
109
+ scopes: scopes
110
+ )
111
+ }
112
+
113
+ private func formURLEncoded(_ pairs: [String: String]) -> String {
114
+ var components = URLComponents()
115
+ components.queryItems = pairs.map { URLQueryItem(name: $0.key, value: $0.value) }
116
+ return components.percentEncodedQuery ?? ""
117
+ }
118
+ }
package/package.json CHANGED
@@ -1,16 +1,17 @@
1
1
  {
2
2
  "name": "@wwdrew/expo-spotify-sdk",
3
- "version": "0.4.3",
4
- "description": "Expo Module for native Spotify SDK",
3
+ "version": "0.6.0",
4
+ "description": "Expo module wrapping the native Spotify iOS (v5) and Android (v4) SDKs for OAuth authentication",
5
5
  "main": "build/index.js",
6
6
  "types": "build/index.d.ts",
7
7
  "scripts": {
8
8
  "build": "expo-module build",
9
9
  "clean": "expo-module clean",
10
10
  "lint": "expo-module lint",
11
- "test": "expo-module test",
11
+ "test": "expo-module test plugin",
12
12
  "prepare": "expo-module prepare",
13
13
  "prepublishOnly": "expo-module prepublishOnly",
14
+ "typecheck": "expo-module typecheck",
14
15
  "expo-module": "expo-module",
15
16
  "open:ios": "open -a \"Xcode\" example/ios",
16
17
  "open:android": "open -a \"Android Studio\" example/android"
@@ -28,23 +29,23 @@
28
29
  "android",
29
30
  "ios",
30
31
  "app.plugin.js",
31
- "expo-module.config.json",
32
- "server.js"
32
+ "expo-module.config.json"
33
33
  ],
34
- "jest": {
35
- "preset": "expo-module-scripts"
36
- },
37
34
  "repository": "https://github.com/wwdrew/expo-spotify-sdk",
38
35
  "bugs": {
39
36
  "url": "https://github.com/wwdrew/expo-spotify-sdk/issues"
40
37
  },
41
- "author": "Drew Miller <49833875+wwdrew@users.noreply.github.com> (https:/github.com/wwdrew/)",
38
+ "author": "Drew Miller <49833875+wwdrew@users.noreply.github.com> (https://github.com/wwdrew/)",
42
39
  "license": "MIT",
43
40
  "homepage": "https://github.com/wwdrew/expo-spotify-sdk#readme",
44
41
  "devDependencies": {
42
+ "@expo/config-plugins": "^55.0.8",
45
43
  "@types/react": "^18.0.25",
46
- "expo-module-scripts": "^3.1.0",
47
- "expo-modules-core": "^1.5.11"
44
+ "eslint": "^9.0.0",
45
+ "expo-module-scripts": "^55.0.2",
46
+ "expo-modules-core": "^3.0.30",
47
+ "jest": "^29.7.0",
48
+ "prettier": "^3.0.0"
48
49
  },
49
50
  "peerDependencies": {
50
51
  "expo": "*",
@@ -2,27 +2,32 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.withSpotifyAndroidAppBuildGradle = void 0;
4
4
  const config_plugins_1 = require("@expo/config-plugins");
5
+ const SENTINEL_KEY = "spotifyClientId";
6
+ const DEFAULT_REDIRECT_PATH_PATTERN = "/.*";
5
7
  const withSpotifyAndroidAppBuildGradle = (config, spotifyConfig) => {
6
8
  return (0, config_plugins_1.withAppBuildGradle)(config, (config) => {
7
- const defaultConfigPattern = /(defaultConfig\s*{[\s\S]*?)(})/s;
8
- const manifestPlaceholders = `
9
- manifestPlaceholders = [
9
+ if (config.modResults.contents.includes(SENTINEL_KEY)) {
10
+ // Already injected on a previous prebuild; bail to keep this modifier
11
+ // idempotent. Run `expo prebuild --clean` to force a refresh.
12
+ return config;
13
+ }
14
+ const redirectPathPattern = spotifyConfig.redirectPathPattern ?? DEFAULT_REDIRECT_PATH_PATTERN;
15
+ const placeholders = ` manifestPlaceholders = [
10
16
  spotifyClientId: "${spotifyConfig.clientID}",
11
17
  spotifyRedirectUri: "${spotifyConfig.scheme}://${spotifyConfig.host}",
12
18
  redirectSchemeName: "${spotifyConfig.scheme}",
13
- redirectHostName: "${spotifyConfig.host}"
14
- ]
15
- `;
19
+ redirectHostName: "${spotifyConfig.host}",
20
+ redirectPathPattern: "${redirectPathPattern}"
21
+ ]`;
22
+ const defaultConfigPattern = /(defaultConfig\s*\{[\s\S]*?)(\n\s*})/m;
16
23
  if (defaultConfigPattern.test(config.modResults.contents)) {
17
- // If the defaultConfig block exists, add the manifestPlaceholders to it
18
- config.modResults.contents = config.modResults.contents.replace(defaultConfigPattern, `$1${manifestPlaceholders}$2`);
24
+ config.modResults.contents = config.modResults.contents.replace(defaultConfigPattern, `$1\n${placeholders}$2`);
19
25
  }
20
26
  else {
21
- // If the defaultConfig block doesn't exist, add it to the android block
22
27
  config.modResults.contents += `
23
28
  android {
24
29
  defaultConfig {
25
- ${manifestPlaceholders}
30
+ ${placeholders}
26
31
  }
27
32
  }`;
28
33
  }
@@ -2,15 +2,20 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.withSpotifyConfigValues = void 0;
4
4
  const config_plugins_1 = require("@expo/config-plugins");
5
+ const IOS_KEYS = [
6
+ "clientID",
7
+ "host",
8
+ "scheme",
9
+ ];
5
10
  const withSpotifyConfigValues = (config, spotifyConfig) => (0, config_plugins_1.withInfoPlist)(config, (config) => {
6
- if (!config.modResults.ExpoSpotifySDK) {
7
- config.modResults.ExpoSpotifySDK = {};
11
+ const existing = (config.modResults.ExpoSpotifySDK ?? {});
12
+ for (const key of IOS_KEYS) {
13
+ const value = spotifyConfig[key];
14
+ if (typeof value === "string" && value.length > 0) {
15
+ existing[key] = value;
16
+ }
8
17
  }
9
- const spotifySDKConfig = config.modResults
10
- .ExpoSpotifySDK;
11
- Object.entries(spotifyConfig).forEach(([key, value]) => {
12
- spotifySDKConfig[key] = value;
13
- });
18
+ config.modResults.ExpoSpotifySDK = existing;
14
19
  return config;
15
20
  });
16
21
  exports.withSpotifyConfigValues = withSpotifyConfigValues;
@@ -1,3 +1,3 @@
1
- import { ConfigPlugin } from "expo/config-plugins";
1
+ import { ConfigPlugin } from "@expo/config-plugins";
2
2
  import { SpotifyConfig } from "../types";
3
3
  export declare const withSpotifyURLScheme: ConfigPlugin<SpotifyConfig>;