@wwdrew/expo-spotify-sdk 0.5.0 → 0.7.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 +346 -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 +133 -147
- package/android/src/main/java/expo/modules/spotifysdk/SpotifyAuthCoordinator.kt +36 -0
- package/android/src/main/java/expo/modules/spotifysdk/SpotifyAuthorizationContract.kt +46 -0
- package/android/src/main/java/expo/modules/spotifysdk/SpotifyConfig.kt +84 -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 +84 -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 +98 -26
- package/ios/SPTScopeSerializer.swift +40 -39
- package/ios/SpotifyAuthCoordinator.swift +186 -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
|
@@ -1,13 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Result of a successful Spotify authentication.
|
|
3
|
+
*
|
|
4
|
+
* The shape is identical across iOS and Android. On the Android implicit
|
|
5
|
+
* (TOKEN) flow `refreshToken` is `null` and `scopes` reflects what was
|
|
6
|
+
* *requested*, not granted — see the README's "Android implicit flow is not
|
|
7
|
+
* recommended" section.
|
|
8
|
+
*/
|
|
1
9
|
export interface SpotifySession {
|
|
10
|
+
/** OAuth access token. */
|
|
2
11
|
accessToken: string;
|
|
3
|
-
|
|
12
|
+
/**
|
|
13
|
+
* OAuth refresh token. `null` on Android when no `tokenSwapURL` is provided
|
|
14
|
+
* (the Spotify Android SDK does not expose a refresh token for implicit
|
|
15
|
+
* grants — see the README).
|
|
16
|
+
*/
|
|
17
|
+
refreshToken: string | null;
|
|
18
|
+
/** Expiration timestamp as Unix epoch milliseconds. */
|
|
4
19
|
expirationDate: number;
|
|
20
|
+
/** Scopes the access token was granted (or requested, on Android implicit). */
|
|
5
21
|
scopes: SpotifyScope[];
|
|
6
22
|
}
|
|
23
|
+
/**
|
|
24
|
+
* Configuration accepted by `refreshSessionAsync`.
|
|
25
|
+
*/
|
|
26
|
+
export interface SpotifyRefreshConfig {
|
|
27
|
+
/** The refresh token from a previous `authenticateAsync` call. */
|
|
28
|
+
refreshToken: string;
|
|
29
|
+
/** URL of your token refresh server endpoint. */
|
|
30
|
+
tokenRefreshURL: string;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Configuration accepted by `authenticateAsync`.
|
|
34
|
+
*/
|
|
7
35
|
export interface SpotifyConfig {
|
|
36
|
+
/** OAuth scopes to request. Must contain at least one entry. */
|
|
8
37
|
scopes: SpotifyScope[];
|
|
38
|
+
/**
|
|
39
|
+
* If supplied, requests an authorization code rather than an implicit
|
|
40
|
+
* token, then POSTs the code to this URL to exchange it for tokens.
|
|
41
|
+
* **Required on Android** to receive a usable `refreshToken`.
|
|
42
|
+
*/
|
|
9
43
|
tokenSwapURL?: string;
|
|
44
|
+
/**
|
|
45
|
+
* Used by the iOS SDK to refresh access tokens automatically, and by
|
|
46
|
+
* `refreshSessionAsync` on both platforms.
|
|
47
|
+
*/
|
|
10
48
|
tokenRefreshURL?: string;
|
|
49
|
+
/**
|
|
50
|
+
* If `true`, forces Spotify to show the authorization dialog even when
|
|
51
|
+
* the user already has an active session. Defaults to `false`.
|
|
52
|
+
*
|
|
53
|
+
* Maps to `SPTSessionManager.alwaysShowAuthorizationDialog` on iOS and
|
|
54
|
+
* `AuthorizationRequest.Builder.setShowDialog(true)` on Android.
|
|
55
|
+
*/
|
|
56
|
+
showDialog?: boolean;
|
|
11
57
|
}
|
|
58
|
+
/**
|
|
59
|
+
* Spotify OAuth scope identifiers that are valid through the iOS, Android
|
|
60
|
+
* and Web auth flows. See https://developer.spotify.com/documentation/web-api/concepts/scopes
|
|
61
|
+
*/
|
|
12
62
|
export type SpotifyScope = "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";
|
|
63
|
+
/**
|
|
64
|
+
* JS-side error code constants thrown via `Promise.reject(new Error(...))`
|
|
65
|
+
* by the native modules.
|
|
66
|
+
*/
|
|
67
|
+
export type SpotifyErrorCode = "USER_CANCELLED" | "AUTH_IN_PROGRESS" | "INVALID_CONFIG" | "NETWORK_ERROR" | "TOKEN_SWAP_FAILED" | "TOKEN_SWAP_PARSE_ERROR" | "SPOTIFY_NOT_INSTALLED" | "AUTH_ERROR" | "UNKNOWN";
|
|
68
|
+
/**
|
|
69
|
+
* Payload delivered to `addSessionChangeListener` subscribers.
|
|
70
|
+
*
|
|
71
|
+
* - `didInitiate` — a new session was created by `authenticateAsync`
|
|
72
|
+
* - `didRenew` — an existing session was refreshed by `refreshSessionAsync`
|
|
73
|
+
* - `didFail` — an auth or refresh attempt failed
|
|
74
|
+
*/
|
|
75
|
+
export type SpotifySessionChangeEvent = {
|
|
76
|
+
type: "didInitiate";
|
|
77
|
+
session: SpotifySession;
|
|
78
|
+
} | {
|
|
79
|
+
type: "didRenew";
|
|
80
|
+
session: SpotifySession;
|
|
81
|
+
} | {
|
|
82
|
+
type: "didFail";
|
|
83
|
+
error: {
|
|
84
|
+
code: SpotifyErrorCode;
|
|
85
|
+
message: string;
|
|
86
|
+
};
|
|
87
|
+
};
|
|
88
|
+
/**
|
|
89
|
+
* Error subclass thrown by `authenticateAsync` and `refreshSessionAsync`
|
|
90
|
+
* carrying a structured `code` field for branching.
|
|
91
|
+
*/
|
|
92
|
+
export declare class SpotifyError extends Error {
|
|
93
|
+
readonly code: SpotifyErrorCode;
|
|
94
|
+
constructor(code: SpotifyErrorCode, message: string);
|
|
95
|
+
}
|
|
13
96
|
//# sourceMappingURL=ExpoSpotifySDK.types.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ExpoSpotifySDK.types.d.ts","sourceRoot":"","sources":["../src/ExpoSpotifySDK.types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,cAAc;IAC7B,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"ExpoSpotifySDK.types.d.ts","sourceRoot":"","sources":["../src/ExpoSpotifySDK.types.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,MAAM,WAAW,cAAc;IAC7B,0BAA0B;IAC1B,WAAW,EAAE,MAAM,CAAC;IACpB;;;;OAIG;IACH,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,uDAAuD;IACvD,cAAc,EAAE,MAAM,CAAC;IACvB,+EAA+E;IAC/E,MAAM,EAAE,YAAY,EAAE,CAAC;CACxB;AAED;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC,kEAAkE;IAClE,YAAY,EAAE,MAAM,CAAC;IACrB,iDAAiD;IACjD,eAAe,EAAE,MAAM,CAAC;CACzB;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,gEAAgE;IAChE,MAAM,EAAE,YAAY,EAAE,CAAC;IACvB;;;;OAIG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;OAGG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB;;;;;;OAMG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAED;;;GAGG;AACH,MAAM,MAAM,YAAY,GACpB,kBAAkB,GAClB,0BAA0B,GAC1B,4BAA4B,GAC5B,6BAA6B,GAC7B,oBAAoB,GACpB,WAAW,GACX,uBAAuB,GACvB,6BAA6B,GAC7B,yBAAyB,GACzB,wBAAwB,GACxB,oBAAoB,GACpB,kBAAkB,GAClB,eAAe,GACf,2BAA2B,GAC3B,qBAAqB,GACrB,mBAAmB,GACnB,iBAAiB,GACjB,mBAAmB,CAAC;AAExB;;;GAGG;AACH,MAAM,MAAM,gBAAgB,GACxB,gBAAgB,GAChB,kBAAkB,GAClB,gBAAgB,GAChB,eAAe,GACf,mBAAmB,GACnB,wBAAwB,GACxB,uBAAuB,GACvB,YAAY,GACZ,SAAS,CAAC;AAEd;;;;;;GAMG;AACH,MAAM,MAAM,yBAAyB,GACjC;IAAE,IAAI,EAAE,aAAa,CAAC;IAAC,OAAO,EAAE,cAAc,CAAA;CAAE,GAChD;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,OAAO,EAAE,cAAc,CAAA;CAAE,GAC7C;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,KAAK,EAAE;QAAE,IAAI,EAAE,gBAAgB,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE,CAAC;AAE5E;;;GAGG;AACH,qBAAa,YAAa,SAAQ,KAAK;IACrC,SAAgB,IAAI,EAAE,gBAAgB,CAAC;gBAE3B,IAAI,EAAE,gBAAgB,EAAE,OAAO,EAAE,MAAM;CAKpD"}
|
|
@@ -1,2 +1,13 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Error subclass thrown by `authenticateAsync` and `refreshSessionAsync`
|
|
3
|
+
* carrying a structured `code` field for branching.
|
|
4
|
+
*/
|
|
5
|
+
export class SpotifyError extends Error {
|
|
6
|
+
code;
|
|
7
|
+
constructor(code, message) {
|
|
8
|
+
super(message);
|
|
9
|
+
this.name = "SpotifyError";
|
|
10
|
+
this.code = code;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
2
13
|
//# sourceMappingURL=ExpoSpotifySDK.types.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ExpoSpotifySDK.types.js","sourceRoot":"","sources":["../src/ExpoSpotifySDK.types.ts"],"names":[],"mappings":"","sourcesContent":["
|
|
1
|
+
{"version":3,"file":"ExpoSpotifySDK.types.js","sourceRoot":"","sources":["../src/ExpoSpotifySDK.types.ts"],"names":[],"mappings":"AA+GA;;;GAGG;AACH,MAAM,OAAO,YAAa,SAAQ,KAAK;IACrB,IAAI,CAAmB;IAEvC,YAAY,IAAsB,EAAE,OAAe;QACjD,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,cAAc,CAAC;QAC3B,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;IACnB,CAAC;CACF","sourcesContent":["/**\n * Result of a successful Spotify authentication.\n *\n * The shape is identical across iOS and Android. On the Android implicit\n * (TOKEN) flow `refreshToken` is `null` and `scopes` reflects what was\n * *requested*, not granted — see the README's \"Android implicit flow is not\n * recommended\" section.\n */\nexport interface SpotifySession {\n /** OAuth access token. */\n accessToken: string;\n /**\n * OAuth refresh token. `null` on Android when no `tokenSwapURL` is provided\n * (the Spotify Android SDK does not expose a refresh token for implicit\n * grants — see the README).\n */\n refreshToken: string | null;\n /** Expiration timestamp as Unix epoch milliseconds. */\n expirationDate: number;\n /** Scopes the access token was granted (or requested, on Android implicit). */\n scopes: SpotifyScope[];\n}\n\n/**\n * Configuration accepted by `refreshSessionAsync`.\n */\nexport interface SpotifyRefreshConfig {\n /** The refresh token from a previous `authenticateAsync` call. */\n refreshToken: string;\n /** URL of your token refresh server endpoint. */\n tokenRefreshURL: string;\n}\n\n/**\n * Configuration accepted by `authenticateAsync`.\n */\nexport interface SpotifyConfig {\n /** OAuth scopes to request. Must contain at least one entry. */\n scopes: SpotifyScope[];\n /**\n * If supplied, requests an authorization code rather than an implicit\n * token, then POSTs the code to this URL to exchange it for tokens.\n * **Required on Android** to receive a usable `refreshToken`.\n */\n tokenSwapURL?: string;\n /**\n * Used by the iOS SDK to refresh access tokens automatically, and by\n * `refreshSessionAsync` on both platforms.\n */\n tokenRefreshURL?: string;\n /**\n * If `true`, forces Spotify to show the authorization dialog even when\n * the user already has an active session. Defaults to `false`.\n *\n * Maps to `SPTSessionManager.alwaysShowAuthorizationDialog` on iOS and\n * `AuthorizationRequest.Builder.setShowDialog(true)` on Android.\n */\n showDialog?: boolean;\n}\n\n/**\n * Spotify OAuth scope identifiers that are valid through the iOS, Android\n * and Web auth flows. See https://developer.spotify.com/documentation/web-api/concepts/scopes\n */\nexport type SpotifyScope =\n | \"ugc-image-upload\"\n | \"user-read-playback-state\"\n | \"user-modify-playback-state\"\n | \"user-read-currently-playing\"\n | \"app-remote-control\"\n | \"streaming\"\n | \"playlist-read-private\"\n | \"playlist-read-collaborative\"\n | \"playlist-modify-private\"\n | \"playlist-modify-public\"\n | \"user-follow-modify\"\n | \"user-follow-read\"\n | \"user-top-read\"\n | \"user-read-recently-played\"\n | \"user-library-modify\"\n | \"user-library-read\"\n | \"user-read-email\"\n | \"user-read-private\";\n\n/**\n * JS-side error code constants thrown via `Promise.reject(new Error(...))`\n * by the native modules.\n */\nexport type SpotifyErrorCode =\n | \"USER_CANCELLED\"\n | \"AUTH_IN_PROGRESS\"\n | \"INVALID_CONFIG\"\n | \"NETWORK_ERROR\"\n | \"TOKEN_SWAP_FAILED\"\n | \"TOKEN_SWAP_PARSE_ERROR\"\n | \"SPOTIFY_NOT_INSTALLED\"\n | \"AUTH_ERROR\"\n | \"UNKNOWN\";\n\n/**\n * Payload delivered to `addSessionChangeListener` subscribers.\n *\n * - `didInitiate` — a new session was created by `authenticateAsync`\n * - `didRenew` — an existing session was refreshed by `refreshSessionAsync`\n * - `didFail` — an auth or refresh attempt failed\n */\nexport type SpotifySessionChangeEvent =\n | { type: \"didInitiate\"; session: SpotifySession }\n | { type: \"didRenew\"; session: SpotifySession }\n | { type: \"didFail\"; error: { code: SpotifyErrorCode; message: string } };\n\n/**\n * Error subclass thrown by `authenticateAsync` and `refreshSessionAsync`\n * carrying a structured `code` field for branching.\n */\nexport class SpotifyError extends Error {\n public readonly code: SpotifyErrorCode;\n\n constructor(code: SpotifyErrorCode, message: string) {\n super(message);\n this.name = \"SpotifyError\";\n this.code = code;\n }\n}\n"]}
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
declare const _default: {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
isAvailable(): boolean;
|
|
3
|
+
authenticateAsync(): Promise<never>;
|
|
4
|
+
refreshSessionAsync(): Promise<never>;
|
|
5
|
+
addListener(): {
|
|
6
|
+
remove(): void;
|
|
7
|
+
};
|
|
8
|
+
removeListeners(): void;
|
|
5
9
|
};
|
|
6
10
|
export default _default;
|
|
7
11
|
//# sourceMappingURL=ExpoSpotifySDKModule.web.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ExpoSpotifySDKModule.web.d.ts","sourceRoot":"","sources":["../src/ExpoSpotifySDKModule.web.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"ExpoSpotifySDKModule.web.d.ts","sourceRoot":"","sources":["../src/ExpoSpotifySDKModule.web.ts"],"names":[],"mappings":";mBAUiB,OAAO;yBAGD,OAAO,CAAC,KAAK,CAAC;2BAGZ,OAAO,CAAC,KAAK,CAAC;mBAGtB;QAAE,MAAM,IAAI,IAAI,CAAA;KAAE;uBAGd,IAAI;;AAbzB,wBAgBE"}
|
|
@@ -1,12 +1,22 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import { SpotifyError } from "./ExpoSpotifySDK.types";
|
|
2
|
+
function notImplementedReject(method) {
|
|
3
|
+
return new SpotifyError("INVALID_CONFIG", `expo-spotify-sdk: ${method} is not implemented on web yet (planned for v0.7.0).`);
|
|
4
|
+
}
|
|
3
5
|
export default {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
emitter.emit("onChange", { value });
|
|
6
|
+
isAvailable() {
|
|
7
|
+
return false;
|
|
7
8
|
},
|
|
8
|
-
|
|
9
|
-
return "
|
|
9
|
+
authenticateAsync() {
|
|
10
|
+
return Promise.reject(notImplementedReject("authenticateAsync"));
|
|
11
|
+
},
|
|
12
|
+
refreshSessionAsync() {
|
|
13
|
+
return Promise.reject(notImplementedReject("refreshSessionAsync"));
|
|
14
|
+
},
|
|
15
|
+
addListener() {
|
|
16
|
+
return { remove() { } };
|
|
17
|
+
},
|
|
18
|
+
removeListeners() {
|
|
19
|
+
/* no-op */
|
|
10
20
|
},
|
|
11
21
|
};
|
|
12
22
|
//# sourceMappingURL=ExpoSpotifySDKModule.web.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ExpoSpotifySDKModule.web.js","sourceRoot":"","sources":["../src/ExpoSpotifySDKModule.web.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"ExpoSpotifySDKModule.web.js","sourceRoot":"","sources":["../src/ExpoSpotifySDKModule.web.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAEtD,SAAS,oBAAoB,CAAC,MAAc;IAC1C,OAAO,IAAI,YAAY,CACrB,gBAAgB,EAChB,qBAAqB,MAAM,sDAAsD,CAClF,CAAC;AACJ,CAAC;AAED,eAAe;IACb,WAAW;QACT,OAAO,KAAK,CAAC;IACf,CAAC;IACD,iBAAiB;QACf,OAAO,OAAO,CAAC,MAAM,CAAC,oBAAoB,CAAC,mBAAmB,CAAC,CAAC,CAAC;IACnE,CAAC;IACD,mBAAmB;QACjB,OAAO,OAAO,CAAC,MAAM,CAAC,oBAAoB,CAAC,qBAAqB,CAAC,CAAC,CAAC;IACrE,CAAC;IACD,WAAW;QACT,OAAO,EAAE,MAAM,KAAI,CAAC,EAAE,CAAC;IACzB,CAAC;IACD,eAAe;QACb,WAAW;IACb,CAAC;CACF,CAAC","sourcesContent":["import { SpotifyError } from \"./ExpoSpotifySDK.types\";\n\nfunction notImplementedReject(method: string): SpotifyError {\n return new SpotifyError(\n \"INVALID_CONFIG\",\n `expo-spotify-sdk: ${method} is not implemented on web yet (planned for v0.7.0).`,\n );\n}\n\nexport default {\n isAvailable(): boolean {\n return false;\n },\n authenticateAsync(): Promise<never> {\n return Promise.reject(notImplementedReject(\"authenticateAsync\"));\n },\n refreshSessionAsync(): Promise<never> {\n return Promise.reject(notImplementedReject(\"refreshSessionAsync\"));\n },\n addListener(): { remove(): void } {\n return { remove() {} };\n },\n removeListeners(): void {\n /* no-op */\n },\n};\n"]}
|
package/build/index.d.ts
CHANGED
|
@@ -1,8 +1,45 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { EventSubscription } from "expo-modules-core";
|
|
2
|
+
import { SpotifyConfig, SpotifyError, SpotifyRefreshConfig, SpotifySession, SpotifySessionChangeEvent } from "./ExpoSpotifySDK.types";
|
|
3
|
+
/**
|
|
4
|
+
* Returns `true` if the Spotify app is installed on the device.
|
|
5
|
+
* Always returns `false` on web.
|
|
6
|
+
*/
|
|
2
7
|
declare function isAvailable(): boolean;
|
|
8
|
+
/**
|
|
9
|
+
* Starts a Spotify OAuth flow. Resolves with a {@link SpotifySession};
|
|
10
|
+
* rejects with a {@link SpotifyError} carrying a `code`.
|
|
11
|
+
*/
|
|
3
12
|
declare function authenticateAsync(config: SpotifyConfig): Promise<SpotifySession>;
|
|
13
|
+
/**
|
|
14
|
+
* Exchanges a refresh token for a new access token via your token refresh
|
|
15
|
+
* server. Resolves with a fresh {@link SpotifySession}; rejects with a
|
|
16
|
+
* {@link SpotifyError}.
|
|
17
|
+
*/
|
|
18
|
+
declare function refreshSessionAsync(config: SpotifyRefreshConfig): Promise<SpotifySession>;
|
|
19
|
+
/**
|
|
20
|
+
* Subscribes to session lifecycle events emitted by the native module.
|
|
21
|
+
*
|
|
22
|
+
* Events are fired for every `authenticateAsync` and `refreshSessionAsync`
|
|
23
|
+
* call, regardless of whether the call was awaited. Useful for persisting
|
|
24
|
+
* tokens in a central store without coupling the store to the call sites.
|
|
25
|
+
*
|
|
26
|
+
* Returns a {@link Subscription} — call `.remove()` to unsubscribe.
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* ```ts
|
|
30
|
+
* const sub = addSessionChangeListener((event) => {
|
|
31
|
+
* if (event.type === "didInitiate" || event.type === "didRenew") {
|
|
32
|
+
* store.setSession(event.session);
|
|
33
|
+
* }
|
|
34
|
+
* });
|
|
35
|
+
* // later:
|
|
36
|
+
* sub.remove();
|
|
37
|
+
* ```
|
|
38
|
+
*/
|
|
39
|
+
declare function addSessionChangeListener(listener: (event: SpotifySessionChangeEvent) => void): EventSubscription;
|
|
4
40
|
declare const Authenticate: {
|
|
5
41
|
authenticateAsync: typeof authenticateAsync;
|
|
6
42
|
};
|
|
7
|
-
export { isAvailable, Authenticate };
|
|
43
|
+
export { isAvailable, authenticateAsync, refreshSessionAsync, addSessionChangeListener, Authenticate, SpotifyError, };
|
|
44
|
+
export type { SpotifyConfig, SpotifyRefreshConfig, SpotifySession, SpotifySessionChangeEvent, SpotifyErrorCode, SpotifyScope, } from "./ExpoSpotifySDK.types";
|
|
8
45
|
//# sourceMappingURL=index.d.ts.map
|
package/build/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAY,MAAM,mBAAmB,CAAC;AAEhE,OAAO,EACL,aAAa,EACb,YAAY,EAEZ,oBAAoB,EACpB,cAAc,EACd,yBAAyB,EAC1B,MAAM,wBAAwB,CAAC;AAWhC;;;GAGG;AACH,iBAAS,WAAW,IAAI,OAAO,CAE9B;AAED;;;GAGG;AACH,iBAAS,iBAAiB,CAAC,MAAM,EAAE,aAAa,GAAG,OAAO,CAAC,cAAc,CAAC,CAqBzE;AAgCD;;;;GAIG;AACH,iBAAS,mBAAmB,CAC1B,MAAM,EAAE,oBAAoB,GAC3B,OAAO,CAAC,cAAc,CAAC,CAczB;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,iBAAS,wBAAwB,CAC/B,QAAQ,EAAE,CAAC,KAAK,EAAE,yBAAyB,KAAK,IAAI,GACnD,iBAAiB,CAKnB;AAgCD,QAAA,MAAM,YAAY;;CAEjB,CAAC;AAEF,OAAO,EACL,WAAW,EACX,iBAAiB,EACjB,mBAAmB,EACnB,wBAAwB,EACxB,YAAY,EACZ,YAAY,GACb,CAAC;AACF,YAAY,EACV,aAAa,EACb,oBAAoB,EACpB,cAAc,EACd,yBAAyB,EACzB,gBAAgB,EAChB,YAAY,GACb,MAAM,wBAAwB,CAAC"}
|
package/build/index.js
CHANGED
|
@@ -1,15 +1,128 @@
|
|
|
1
|
+
import { Platform } from "expo-modules-core";
|
|
2
|
+
import { SpotifyError, } from "./ExpoSpotifySDK.types";
|
|
1
3
|
import ExpoSpotifySDKModule from "./ExpoSpotifySDKModule";
|
|
4
|
+
const ANDROID_TOKEN_FLOW_WARNING = "[expo-spotify-sdk] You are using authenticateAsync on Android without a " +
|
|
5
|
+
"tokenSwapURL. The Spotify Android SDK does NOT return a refresh token or " +
|
|
6
|
+
"the actual granted scopes through this path; see the README's " +
|
|
7
|
+
"'Android implicit (TOKEN) flow is not recommended' section.";
|
|
8
|
+
let warnedAboutAndroidTokenFlow = false;
|
|
9
|
+
/**
|
|
10
|
+
* Returns `true` if the Spotify app is installed on the device.
|
|
11
|
+
* Always returns `false` on web.
|
|
12
|
+
*/
|
|
2
13
|
function isAvailable() {
|
|
3
14
|
return ExpoSpotifySDKModule.isAvailable();
|
|
4
15
|
}
|
|
16
|
+
/**
|
|
17
|
+
* Starts a Spotify OAuth flow. Resolves with a {@link SpotifySession};
|
|
18
|
+
* rejects with a {@link SpotifyError} carrying a `code`.
|
|
19
|
+
*/
|
|
5
20
|
function authenticateAsync(config) {
|
|
6
|
-
if (!config.scopes || config.scopes
|
|
7
|
-
|
|
21
|
+
if (!config.scopes || config.scopes.length === 0) {
|
|
22
|
+
return Promise.reject(new SpotifyError("INVALID_CONFIG", "scopes must contain at least one entry"));
|
|
8
23
|
}
|
|
9
|
-
|
|
24
|
+
if (Platform.OS === "android" &&
|
|
25
|
+
!config.tokenSwapURL &&
|
|
26
|
+
!warnedAboutAndroidTokenFlow) {
|
|
27
|
+
warnedAboutAndroidTokenFlow = true;
|
|
28
|
+
console.warn(ANDROID_TOKEN_FLOW_WARNING);
|
|
29
|
+
}
|
|
30
|
+
return ExpoSpotifySDKModule.authenticateAsync(config)
|
|
31
|
+
.then(normaliseSession)
|
|
32
|
+
.catch(rethrowAsSpotifyError);
|
|
33
|
+
}
|
|
34
|
+
function normaliseSession(raw) {
|
|
35
|
+
if (!raw || typeof raw !== "object") {
|
|
36
|
+
throw new SpotifyError("UNKNOWN", "Native module returned a non-object session");
|
|
37
|
+
}
|
|
38
|
+
const r = raw;
|
|
39
|
+
const accessToken = r.accessToken;
|
|
40
|
+
if (typeof accessToken !== "string" || accessToken.length === 0) {
|
|
41
|
+
throw new SpotifyError("UNKNOWN", "Session is missing accessToken");
|
|
42
|
+
}
|
|
43
|
+
const expirationDate = r.expirationDate;
|
|
44
|
+
if (typeof expirationDate !== "number") {
|
|
45
|
+
throw new SpotifyError("UNKNOWN", "Session is missing expirationDate");
|
|
46
|
+
}
|
|
47
|
+
const refreshTokenRaw = r.refreshToken;
|
|
48
|
+
const refreshToken = typeof refreshTokenRaw === "string" && refreshTokenRaw.length > 0
|
|
49
|
+
? refreshTokenRaw
|
|
50
|
+
: null;
|
|
51
|
+
const scopesRaw = r.scopes;
|
|
52
|
+
const scopes = Array.isArray(scopesRaw)
|
|
53
|
+
? scopesRaw.filter((s) => typeof s === "string")
|
|
54
|
+
: [];
|
|
55
|
+
return { accessToken, refreshToken, expirationDate, scopes };
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Exchanges a refresh token for a new access token via your token refresh
|
|
59
|
+
* server. Resolves with a fresh {@link SpotifySession}; rejects with a
|
|
60
|
+
* {@link SpotifyError}.
|
|
61
|
+
*/
|
|
62
|
+
function refreshSessionAsync(config) {
|
|
63
|
+
if (!config.refreshToken) {
|
|
64
|
+
return Promise.reject(new SpotifyError("INVALID_CONFIG", "refreshToken is required"));
|
|
65
|
+
}
|
|
66
|
+
if (!config.tokenRefreshURL) {
|
|
67
|
+
return Promise.reject(new SpotifyError("INVALID_CONFIG", "tokenRefreshURL is required"));
|
|
68
|
+
}
|
|
69
|
+
return ExpoSpotifySDKModule.refreshSessionAsync(config)
|
|
70
|
+
.then(normaliseSession)
|
|
71
|
+
.catch(rethrowAsSpotifyError);
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Subscribes to session lifecycle events emitted by the native module.
|
|
75
|
+
*
|
|
76
|
+
* Events are fired for every `authenticateAsync` and `refreshSessionAsync`
|
|
77
|
+
* call, regardless of whether the call was awaited. Useful for persisting
|
|
78
|
+
* tokens in a central store without coupling the store to the call sites.
|
|
79
|
+
*
|
|
80
|
+
* Returns a {@link Subscription} — call `.remove()` to unsubscribe.
|
|
81
|
+
*
|
|
82
|
+
* @example
|
|
83
|
+
* ```ts
|
|
84
|
+
* const sub = addSessionChangeListener((event) => {
|
|
85
|
+
* if (event.type === "didInitiate" || event.type === "didRenew") {
|
|
86
|
+
* store.setSession(event.session);
|
|
87
|
+
* }
|
|
88
|
+
* });
|
|
89
|
+
* // later:
|
|
90
|
+
* sub.remove();
|
|
91
|
+
* ```
|
|
92
|
+
*/
|
|
93
|
+
function addSessionChangeListener(listener) {
|
|
94
|
+
return ExpoSpotifySDKModule.addListener("onSessionChange", listener);
|
|
95
|
+
}
|
|
96
|
+
const ERROR_PREFIX_RE = /^([A-Z_][A-Z0-9_]*):\s*(.*)$/s;
|
|
97
|
+
const VALID_CODES = new Set([
|
|
98
|
+
"USER_CANCELLED",
|
|
99
|
+
"AUTH_IN_PROGRESS",
|
|
100
|
+
"INVALID_CONFIG",
|
|
101
|
+
"NETWORK_ERROR",
|
|
102
|
+
"TOKEN_SWAP_FAILED",
|
|
103
|
+
"TOKEN_SWAP_PARSE_ERROR",
|
|
104
|
+
"SPOTIFY_NOT_INSTALLED",
|
|
105
|
+
"AUTH_ERROR",
|
|
106
|
+
"UNKNOWN",
|
|
107
|
+
]);
|
|
108
|
+
function rethrowAsSpotifyError(err) {
|
|
109
|
+
if (err instanceof SpotifyError)
|
|
110
|
+
throw err;
|
|
111
|
+
if (err instanceof Error) {
|
|
112
|
+
const m = err.message.match(ERROR_PREFIX_RE);
|
|
113
|
+
if (m && VALID_CODES.has(m[1])) {
|
|
114
|
+
throw new SpotifyError(m[1], m[2]);
|
|
115
|
+
}
|
|
116
|
+
const maybeCode = err.code;
|
|
117
|
+
if (maybeCode && VALID_CODES.has(maybeCode)) {
|
|
118
|
+
throw new SpotifyError(maybeCode, err.message);
|
|
119
|
+
}
|
|
120
|
+
throw new SpotifyError("UNKNOWN", err.message);
|
|
121
|
+
}
|
|
122
|
+
throw new SpotifyError("UNKNOWN", String(err));
|
|
10
123
|
}
|
|
11
124
|
const Authenticate = {
|
|
12
125
|
authenticateAsync,
|
|
13
126
|
};
|
|
14
|
-
export { isAvailable, Authenticate };
|
|
127
|
+
export { isAvailable, authenticateAsync, refreshSessionAsync, addSessionChangeListener, Authenticate, SpotifyError, };
|
|
15
128
|
//# sourceMappingURL=index.js.map
|
package/build/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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","sourcesContent":["import { EventSubscription, Platform } from \"expo-modules-core\";\n\nimport {\n SpotifyConfig,\n SpotifyError,\n SpotifyErrorCode,\n SpotifyRefreshConfig,\n SpotifySession,\n SpotifySessionChangeEvent,\n} from \"./ExpoSpotifySDK.types\";\nimport ExpoSpotifySDKModule from \"./ExpoSpotifySDKModule\";\n\nconst ANDROID_TOKEN_FLOW_WARNING =\n \"[expo-spotify-sdk] You are using authenticateAsync on Android without a \" +\n \"tokenSwapURL. The Spotify Android SDK does NOT return a refresh token or \" +\n \"the actual granted scopes through this path; see the README's \" +\n \"'Android implicit (TOKEN) flow is not recommended' section.\";\n\nlet warnedAboutAndroidTokenFlow = false;\n\n/**\n * Returns `true` if the Spotify app is installed on the device.\n * Always returns `false` on web.\n */\nfunction isAvailable(): boolean {\n return ExpoSpotifySDKModule.isAvailable();\n}\n\n/**\n * Starts a Spotify OAuth flow. Resolves with a {@link SpotifySession};\n * rejects with a {@link SpotifyError} carrying a `code`.\n */\nfunction authenticateAsync(config: SpotifyConfig): Promise<SpotifySession> {\n if (!config.scopes || config.scopes.length === 0) {\n return Promise.reject(\n new SpotifyError(\n \"INVALID_CONFIG\",\n \"scopes must contain at least one entry\",\n ),\n );\n }\n if (\n Platform.OS === \"android\" &&\n !config.tokenSwapURL &&\n !warnedAboutAndroidTokenFlow\n ) {\n warnedAboutAndroidTokenFlow = true;\n\n console.warn(ANDROID_TOKEN_FLOW_WARNING);\n }\n return ExpoSpotifySDKModule.authenticateAsync(config)\n .then(normaliseSession)\n .catch(rethrowAsSpotifyError);\n}\n\nfunction normaliseSession(raw: unknown): SpotifySession {\n if (!raw || typeof raw !== \"object\") {\n throw new SpotifyError(\n \"UNKNOWN\",\n \"Native module returned a non-object session\",\n );\n }\n const r = raw as Record<string, unknown>;\n const accessToken = r.accessToken;\n if (typeof accessToken !== \"string\" || accessToken.length === 0) {\n throw new SpotifyError(\"UNKNOWN\", \"Session is missing accessToken\");\n }\n const expirationDate = r.expirationDate;\n if (typeof expirationDate !== \"number\") {\n throw new SpotifyError(\"UNKNOWN\", \"Session is missing expirationDate\");\n }\n const refreshTokenRaw = r.refreshToken;\n const refreshToken =\n typeof refreshTokenRaw === \"string\" && refreshTokenRaw.length > 0\n ? refreshTokenRaw\n : null;\n const scopesRaw = r.scopes;\n const scopes = Array.isArray(scopesRaw)\n ? (scopesRaw.filter(\n (s) => typeof s === \"string\",\n ) as SpotifySession[\"scopes\"])\n : [];\n return { accessToken, refreshToken, expirationDate, scopes };\n}\n\n/**\n * Exchanges a refresh token for a new access token via your token refresh\n * server. Resolves with a fresh {@link SpotifySession}; rejects with a\n * {@link SpotifyError}.\n */\nfunction refreshSessionAsync(\n config: SpotifyRefreshConfig,\n): Promise<SpotifySession> {\n if (!config.refreshToken) {\n return Promise.reject(\n new SpotifyError(\"INVALID_CONFIG\", \"refreshToken is required\"),\n );\n }\n if (!config.tokenRefreshURL) {\n return Promise.reject(\n new SpotifyError(\"INVALID_CONFIG\", \"tokenRefreshURL is required\"),\n );\n }\n return ExpoSpotifySDKModule.refreshSessionAsync(config)\n .then(normaliseSession)\n .catch(rethrowAsSpotifyError);\n}\n\n/**\n * Subscribes to session lifecycle events emitted by the native module.\n *\n * Events are fired for every `authenticateAsync` and `refreshSessionAsync`\n * call, regardless of whether the call was awaited. Useful for persisting\n * tokens in a central store without coupling the store to the call sites.\n *\n * Returns a {@link Subscription} — call `.remove()` to unsubscribe.\n *\n * @example\n * ```ts\n * const sub = addSessionChangeListener((event) => {\n * if (event.type === \"didInitiate\" || event.type === \"didRenew\") {\n * store.setSession(event.session);\n * }\n * });\n * // later:\n * sub.remove();\n * ```\n */\nfunction addSessionChangeListener(\n listener: (event: SpotifySessionChangeEvent) => void,\n): EventSubscription {\n return ExpoSpotifySDKModule.addListener(\n \"onSessionChange\",\n listener,\n ) as EventSubscription;\n}\n\nconst ERROR_PREFIX_RE = /^([A-Z_][A-Z0-9_]*):\\s*(.*)$/s;\n\nconst VALID_CODES: ReadonlySet<SpotifyErrorCode> = new Set<SpotifyErrorCode>([\n \"USER_CANCELLED\",\n \"AUTH_IN_PROGRESS\",\n \"INVALID_CONFIG\",\n \"NETWORK_ERROR\",\n \"TOKEN_SWAP_FAILED\",\n \"TOKEN_SWAP_PARSE_ERROR\",\n \"SPOTIFY_NOT_INSTALLED\",\n \"AUTH_ERROR\",\n \"UNKNOWN\",\n]);\n\nfunction rethrowAsSpotifyError(err: unknown): never {\n if (err instanceof SpotifyError) throw err;\n if (err instanceof Error) {\n const m = err.message.match(ERROR_PREFIX_RE);\n if (m && VALID_CODES.has(m[1] as SpotifyErrorCode)) {\n throw new SpotifyError(m[1] as SpotifyErrorCode, m[2]);\n }\n const maybeCode = (err as Error & { code?: string }).code;\n if (maybeCode && VALID_CODES.has(maybeCode as SpotifyErrorCode)) {\n throw new SpotifyError(maybeCode as SpotifyErrorCode, err.message);\n }\n throw new SpotifyError(\"UNKNOWN\", err.message);\n }\n throw new SpotifyError(\"UNKNOWN\", String(err));\n}\n\nconst Authenticate = {\n authenticateAsync,\n};\n\nexport {\n isAvailable,\n authenticateAsync,\n refreshSessionAsync,\n addSessionChangeListener,\n Authenticate,\n SpotifyError,\n};\nexport type {\n SpotifyConfig,\n SpotifyRefreshConfig,\n SpotifySession,\n SpotifySessionChangeEvent,\n SpotifyErrorCode,\n SpotifyScope,\n} from \"./ExpoSpotifySDK.types\";\n"]}
|
package/expo-module.config.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"platforms": ["ios", "android", "web"],
|
|
3
3
|
"ios": {
|
|
4
4
|
"modules": ["ExpoSpotifySDKModule"],
|
|
5
|
-
"appDelegateSubscribers": ["
|
|
5
|
+
"appDelegateSubscribers": ["ExpoSpotifyAppDelegate"]
|
|
6
6
|
},
|
|
7
7
|
"android": {
|
|
8
8
|
"modules": ["expo.modules.spotifysdk.ExpoSpotifySDKModule"]
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import ExpoModulesCore
|
|
2
|
+
import SpotifyiOS
|
|
3
|
+
|
|
4
|
+
/// Delivers Spotify auth redirect URLs received via the legacy
|
|
5
|
+
/// `application(_:open:url:)` AppDelegate path.
|
|
6
|
+
public class ExpoSpotifyAppDelegate: ExpoAppDelegateSubscriber {
|
|
7
|
+
public func application(
|
|
8
|
+
_ app: UIApplication,
|
|
9
|
+
open url: URL,
|
|
10
|
+
options: [UIApplication.OpenURLOptionsKey: Any] = [:]
|
|
11
|
+
) -> Bool {
|
|
12
|
+
return SpotifyAuthCoordinator.shared?.handleOpenURL(url, options: options) ?? false
|
|
13
|
+
}
|
|
14
|
+
}
|
|
@@ -1,20 +1,38 @@
|
|
|
1
|
+
import Foundation
|
|
1
2
|
import SpotifyiOS
|
|
2
3
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
4
|
+
/// Strongly-typed view of the `ExpoSpotifySDK` Info.plist dictionary written
|
|
5
|
+
/// by the Expo config plugin. Values are validated up-front; missing or empty
|
|
6
|
+
/// entries return nil so callers can fail with a structured error.
|
|
7
|
+
struct ExpoSpotifyConfiguration {
|
|
8
|
+
let clientID: String
|
|
9
|
+
let host: String
|
|
10
|
+
let scheme: String
|
|
11
|
+
|
|
12
|
+
var redirectURL: URL? {
|
|
13
|
+
return URL(string: "\(scheme)://\(host)")
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
var sptConfiguration: SPTConfiguration? {
|
|
17
|
+
guard let redirectURL = redirectURL else { return nil }
|
|
18
|
+
return SPTConfiguration(clientID: clientID, redirectURL: redirectURL)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
static func fromInfoPlist(bundle: Bundle = .main) -> ExpoSpotifyConfiguration? {
|
|
22
|
+
guard let dict = bundle.object(forInfoDictionaryKey: "ExpoSpotifySDK") as? [String: Any] else {
|
|
23
|
+
return nil
|
|
10
24
|
}
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
self.host = host
|
|
18
|
-
self.scheme = scheme
|
|
25
|
+
guard
|
|
26
|
+
let clientID = (dict["clientID"] as? String)?.nilIfEmpty,
|
|
27
|
+
let host = (dict["host"] as? String)?.nilIfEmpty,
|
|
28
|
+
let scheme = (dict["scheme"] as? String)?.nilIfEmpty
|
|
29
|
+
else {
|
|
30
|
+
return nil
|
|
19
31
|
}
|
|
32
|
+
return ExpoSpotifyConfiguration(clientID: clientID, host: host, scheme: scheme)
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
private extension String {
|
|
37
|
+
var nilIfEmpty: String? { isEmpty ? nil : self }
|
|
20
38
|
}
|
|
@@ -10,15 +10,13 @@ Pod::Spec.new do |s|
|
|
|
10
10
|
s.license = package['license']
|
|
11
11
|
s.author = package['author']
|
|
12
12
|
s.homepage = package['homepage']
|
|
13
|
-
s.platform = :ios, '
|
|
14
|
-
s.swift_version = '5.
|
|
13
|
+
s.platform = :ios, '15.1'
|
|
14
|
+
s.swift_version = '5.9'
|
|
15
15
|
s.source = { git: 'https://github.com/wwdrew/expo-spotify-sdk' }
|
|
16
16
|
s.static_framework = true
|
|
17
17
|
|
|
18
18
|
s.dependency 'ExpoModulesCore'
|
|
19
|
-
s.dependency 'PromiseKit', "~> 6.8"
|
|
20
19
|
|
|
21
|
-
# Swift/Objective-C compatibility
|
|
22
20
|
s.pod_target_xcconfig = {
|
|
23
21
|
'DEFINES_MODULE' => 'YES',
|
|
24
22
|
'SWIFT_COMPILATION_MODE' => 'wholemodule'
|