@wwdrew/expo-spotify-sdk 0.5.0 → 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.
- package/LICENSE +21 -3
- package/README.md +345 -96
- package/android/build.gradle +28 -22
- package/android/consumer-rules.pro +8 -0
- package/android/src/main/AndroidManifest.xml +3 -0
- package/android/src/main/java/expo/modules/spotifysdk/ExpoSpotifySDKModule.kt +130 -145
- package/android/src/main/java/expo/modules/spotifysdk/SpotifyAuthCoordinator.kt +36 -0
- package/android/src/main/java/expo/modules/spotifysdk/SpotifyAuthorizationContract.kt +45 -0
- package/android/src/main/java/expo/modules/spotifysdk/SpotifyConfig.kt +80 -0
- package/android/src/main/java/expo/modules/spotifysdk/SpotifyErrors.kt +34 -0
- package/android/src/main/java/expo/modules/spotifysdk/SpotifyTokenSwapClient.kt +154 -0
- package/build/ExpoSpotifySDK.types.d.ts +76 -1
- package/build/ExpoSpotifySDK.types.d.ts.map +1 -1
- package/build/ExpoSpotifySDK.types.js +12 -1
- package/build/ExpoSpotifySDK.types.js.map +1 -1
- package/build/ExpoSpotifySDKModule.web.d.ts +7 -3
- package/build/ExpoSpotifySDKModule.web.d.ts.map +1 -1
- package/build/ExpoSpotifySDKModule.web.js +17 -7
- package/build/ExpoSpotifySDKModule.web.js.map +1 -1
- package/build/index.d.ts +39 -2
- package/build/index.d.ts.map +1 -1
- package/build/index.js +117 -4
- package/build/index.js.map +1 -1
- package/expo-module.config.json +1 -1
- package/ios/ExpoSpotifyAppDelegate.swift +14 -0
- package/ios/ExpoSpotifyConfiguration.swift +33 -15
- package/ios/ExpoSpotifySDK.podspec +2 -4
- package/ios/ExpoSpotifySDKModule.swift +96 -26
- package/ios/SPTScopeSerializer.swift +40 -39
- package/ios/SpotifyAuthCoordinator.swift +133 -0
- package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64/SpotifyiOS.framework/Headers/SPTConfiguration.h +1 -1
- package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64/SpotifyiOS.framework/Headers/SPTError.h +6 -7
- package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64/SpotifyiOS.framework/Headers/SPTScope.h +43 -39
- package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64/SpotifyiOS.framework/Headers/SPTSessionManager.h +8 -4
- package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64/SpotifyiOS.framework/Headers/SpotifyiOS.h +1 -1
- package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64/SpotifyiOS.framework/Info.plist +0 -0
- package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64/SpotifyiOS.framework/SpotifyiOS +0 -0
- package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64_x86_64-simulator/SpotifyiOS.framework/Headers/SPTConfiguration.h +1 -1
- package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64_x86_64-simulator/SpotifyiOS.framework/Headers/SPTError.h +6 -7
- package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64_x86_64-simulator/SpotifyiOS.framework/Headers/SPTScope.h +43 -39
- package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64_x86_64-simulator/SpotifyiOS.framework/Headers/SPTSessionManager.h +8 -4
- package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64_x86_64-simulator/SpotifyiOS.framework/Headers/SpotifyiOS.h +1 -1
- package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64_x86_64-simulator/SpotifyiOS.framework/Info.plist +0 -0
- package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64_x86_64-simulator/SpotifyiOS.framework/SpotifyiOS +0 -0
- package/ios/SpotifyTokenRefreshClient.swift +118 -0
- package/package.json +12 -11
- package/plugin/build/android/withSpotifyAndroidAppBuildGradle.js +15 -10
- package/plugin/build/ios/withSpotifyConfigValues.js +12 -7
- package/plugin/build/ios/withSpotifyURLScheme.d.ts +1 -1
- package/plugin/build/ios/withSpotifyURLScheme.js +9 -8
- package/plugin/build/types.d.ts +35 -0
- package/plugin/build/types.js +0 -19
- package/ios/ExpoSpotifyAuthDelegate.swift +0 -13
- package/ios/ExpoSpotifySessionManager+SessionManagerDelegate.swift +0 -16
- package/ios/ExpoSpotifySessionManager.swift +0 -90
- package/ios/SpotifySDK/SpotifyiOS.xcframework/_CodeSignature/CodeDirectory +0 -0
- package/ios/SpotifySDK/SpotifyiOS.xcframework/_CodeSignature/CodeRequirements +0 -0
- package/ios/SpotifySDK/SpotifyiOS.xcframework/_CodeSignature/CodeRequirements-1 +0 -0
- package/ios/SpotifySDK/SpotifyiOS.xcframework/_CodeSignature/CodeResources +0 -1118
- package/ios/SpotifySDK/SpotifyiOS.xcframework/_CodeSignature/CodeSignature +0 -0
|
@@ -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
|
-
"description": "Expo
|
|
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
|
|
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
|
-
"
|
|
47
|
-
"expo-
|
|
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
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
7
|
-
|
|
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
|
-
|
|
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,18 +1,19 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.withSpotifyURLScheme = void 0;
|
|
4
|
-
const config_plugins_1 = require("expo/config-plugins");
|
|
4
|
+
const config_plugins_1 = require("@expo/config-plugins");
|
|
5
5
|
const withSpotifyURLScheme = (config, { scheme }) => {
|
|
6
6
|
return (0, config_plugins_1.withInfoPlist)(config, (config) => {
|
|
7
7
|
const bundleId = config.ios?.bundleIdentifier;
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
8
|
+
const urlTypes = config.modResults.CFBundleURLTypes ?? [];
|
|
9
|
+
const alreadyDeclared = urlTypes.some((entry) => entry.CFBundleURLSchemes?.includes(scheme));
|
|
10
|
+
if (!alreadyDeclared) {
|
|
11
|
+
urlTypes.push({
|
|
12
|
+
CFBundleURLName: bundleId ?? "",
|
|
13
|
+
CFBundleURLSchemes: [scheme],
|
|
14
|
+
});
|
|
15
|
+
config.modResults.CFBundleURLTypes = urlTypes;
|
|
14
16
|
}
|
|
15
|
-
config.modResults.CFBundleURLTypes.push(urlType);
|
|
16
17
|
return config;
|
|
17
18
|
});
|
|
18
19
|
};
|
package/plugin/build/types.d.ts
CHANGED
|
@@ -1,6 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Spotify scopes that are valid in Spotify's iOS, Android and Web auth APIs.
|
|
3
|
+
*
|
|
4
|
+
* See: https://developer.spotify.com/documentation/web-api/concepts/scopes
|
|
5
|
+
*/
|
|
1
6
|
export type SpotifyScopes = "ugc-image-upload" | "user-read-playback-state" | "user-modify-playback-state" | "user-read-currently-playing" | "app-remote-control" | "streaming" | "playlist-read-private" | "playlist-read-collaborative" | "playlist-modify-private" | "playlist-modify-public" | "user-follow-modify" | "user-follow-read" | "user-top-read" | "user-read-recently-played" | "user-library-modify" | "user-library-read" | "user-read-email" | "user-read-private";
|
|
7
|
+
/**
|
|
8
|
+
* Configuration accepted by the `@wwdrew/expo-spotify-sdk` Expo config plugin.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* // app.config.ts
|
|
12
|
+
* export default {
|
|
13
|
+
* plugins: [
|
|
14
|
+
* ["@wwdrew/expo-spotify-sdk", {
|
|
15
|
+
* clientID: "<spotify-client-id>",
|
|
16
|
+
* scheme: "myapp",
|
|
17
|
+
* host: "spotify-auth",
|
|
18
|
+
* redirectPathPattern: "/.*"
|
|
19
|
+
* }]
|
|
20
|
+
* ]
|
|
21
|
+
* }
|
|
22
|
+
*/
|
|
2
23
|
export interface SpotifyConfig {
|
|
24
|
+
/** Spotify Client ID for your application. */
|
|
3
25
|
clientID: string;
|
|
26
|
+
/** Path component of the redirect URI (e.g. `"spotify-auth"`). */
|
|
4
27
|
host: string;
|
|
28
|
+
/**
|
|
29
|
+
* URL scheme registered for your app, used as the redirect URI scheme
|
|
30
|
+
* (e.g. `"myapp"`). Must match your Expo app's `scheme`.
|
|
31
|
+
*/
|
|
5
32
|
scheme: string;
|
|
33
|
+
/**
|
|
34
|
+
* Path pattern Spotify will accept on the redirect URI. Required by the
|
|
35
|
+
* Spotify Android Auth SDK from version 3.0.0 onwards. Defaults to `"/.*"`
|
|
36
|
+
* which matches any path (preserving pre-3.0.0 SDK behaviour).
|
|
37
|
+
*
|
|
38
|
+
* See: https://developer.android.com/guide/topics/manifest/data-element#path
|
|
39
|
+
*/
|
|
40
|
+
redirectPathPattern?: string;
|
|
6
41
|
}
|
package/plugin/build/types.js
CHANGED
|
@@ -1,21 +1,2 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
/*
|
|
3
|
-
|
|
4
|
-
List of Scopes: https://developer.spotify.com/documentation/web-api/concepts/scopes
|
|
5
|
-
|
|
6
|
-
Note: these scopes are not currently available in the iOS SDK:
|
|
7
|
-
|
|
8
|
-
- user-read-playback-position
|
|
9
|
-
- user-soa-link
|
|
10
|
-
- user-soa-unlink
|
|
11
|
-
- user-manage-entitlements
|
|
12
|
-
- user-manage-partner
|
|
13
|
-
- user-create-partner
|
|
14
|
-
|
|
15
|
-
Also, although these scopes exist in the iOS SDK, they are not valid:
|
|
16
|
-
|
|
17
|
-
- user-read-birthdate
|
|
18
|
-
- openid
|
|
19
|
-
|
|
20
|
-
*/
|
|
21
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import ExpoModulesCore
|
|
2
|
-
import SpotifyiOS
|
|
3
|
-
|
|
4
|
-
public class ExpoSpotifyAuthDelegate: ExpoAppDelegateSubscriber {
|
|
5
|
-
let sessionManager = ExpoSpotifySessionManager.shared
|
|
6
|
-
|
|
7
|
-
public func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool {
|
|
8
|
-
if let canHandleURL = sessionManager.sessionManager?.application(app, open: url, options: options) {
|
|
9
|
-
return canHandleURL
|
|
10
|
-
}
|
|
11
|
-
return false
|
|
12
|
-
}
|
|
13
|
-
}
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import Foundation
|
|
2
|
-
import SpotifyiOS
|
|
3
|
-
|
|
4
|
-
extension ExpoSpotifySessionManager: SPTSessionManagerDelegate {
|
|
5
|
-
public func sessionManager(manager _: SPTSessionManager, didInitiate session: SPTSession) {
|
|
6
|
-
authPromiseSeal?.fulfill(session)
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
public func sessionManager(manager _: SPTSessionManager, didFailWith error: Error) {
|
|
10
|
-
authPromiseSeal?.reject(error)
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
public func sessionManager(manager _: SPTSessionManager, didRenew session: SPTSession) {
|
|
14
|
-
authPromiseSeal?.fulfill(session)
|
|
15
|
-
}
|
|
16
|
-
}
|
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
import ExpoModulesCore
|
|
2
|
-
import SpotifyiOS
|
|
3
|
-
import PromiseKit
|
|
4
|
-
|
|
5
|
-
enum SessionManagerError: Error {
|
|
6
|
-
case notInitialized
|
|
7
|
-
case invalidConfiguration
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
final class ExpoSpotifySessionManager: NSObject {
|
|
11
|
-
weak var module: ExpoSpotifySDKModule?
|
|
12
|
-
var authPromiseSeal: Resolver<SPTSession>?
|
|
13
|
-
|
|
14
|
-
static let shared = ExpoSpotifySessionManager()
|
|
15
|
-
|
|
16
|
-
private var expoSpotifyConfiguration: ExpoSpotifyConfiguration? {
|
|
17
|
-
guard let expoSpotifySdkDict = Bundle.main.object(forInfoDictionaryKey: "ExpoSpotifySDK") as? [String: String],
|
|
18
|
-
let clientID = expoSpotifySdkDict["clientID"],
|
|
19
|
-
let host = expoSpotifySdkDict["host"],
|
|
20
|
-
let scheme = expoSpotifySdkDict["scheme"] else
|
|
21
|
-
{
|
|
22
|
-
return nil
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
return ExpoSpotifyConfiguration(clientID: clientID, host: host, scheme: scheme)
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
lazy var configuration: SPTConfiguration? = {
|
|
29
|
-
guard let clientID = expoSpotifyConfiguration?.clientID,
|
|
30
|
-
let redirectURL = expoSpotifyConfiguration?.redirectURL else {
|
|
31
|
-
NSLog("Invalid Spotify configuration")
|
|
32
|
-
return nil
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
return SPTConfiguration(clientID: clientID, redirectURL: redirectURL)
|
|
36
|
-
}()
|
|
37
|
-
|
|
38
|
-
lazy var sessionManager: SPTSessionManager? = {
|
|
39
|
-
guard let configuration = configuration else {
|
|
40
|
-
return nil
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
return SPTSessionManager(configuration: configuration, delegate: self)
|
|
44
|
-
}()
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
func authenticate(scopes: [String], tokenSwapURL: String?, tokenRefreshURL: String?) -> PromiseKit.Promise<SPTSession> {
|
|
48
|
-
return Promise { seal in
|
|
49
|
-
guard let clientID = self.expoSpotifyConfiguration?.clientID,
|
|
50
|
-
let redirectURL = self.expoSpotifyConfiguration?.redirectURL else {
|
|
51
|
-
NSLog("Invalid Spotify configuration")
|
|
52
|
-
seal.reject(SessionManagerError.invalidConfiguration)
|
|
53
|
-
return
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
let configuration = SPTConfiguration(clientID: clientID, redirectURL: redirectURL)
|
|
57
|
-
|
|
58
|
-
if (tokenSwapURL != nil) {
|
|
59
|
-
configuration.tokenSwapURL = URL(string: tokenSwapURL ?? "")
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
if (tokenRefreshURL != nil) {
|
|
63
|
-
configuration.tokenRefreshURL = URL(string: tokenRefreshURL ?? "")
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
self.authPromiseSeal = seal
|
|
67
|
-
self.configuration = configuration
|
|
68
|
-
self.sessionManager = SPTSessionManager(configuration: configuration, delegate: self)
|
|
69
|
-
|
|
70
|
-
DispatchQueue.main.sync {
|
|
71
|
-
sessionManager?.initiateSession(with: SPTScopeSerializer.deserializeScopes(scopes), options: .default, campaign: nil)
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
func spotifyAppInstalled() -> Bool {
|
|
77
|
-
guard let sessionManager = sessionManager else {
|
|
78
|
-
NSLog("SPTSessionManager not initialized")
|
|
79
|
-
return false
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
var isInstalled = false
|
|
83
|
-
|
|
84
|
-
DispatchQueue.main.sync {
|
|
85
|
-
isInstalled = sessionManager.isSpotifyAppInstalled
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
return isInstalled
|
|
89
|
-
}
|
|
90
|
-
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|