@wwdrew/expo-spotify-sdk 0.8.0 → 1.0.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/README.md +378 -138
- package/android/build.gradle +10 -0
- package/android/consumer-rules.pro +5 -0
- package/android/libs/SETUP.md +29 -0
- package/android/src/main/java/expo/modules/spotifysdk/ExpoSpotifySDKModule.kt +146 -9
- package/android/src/main/java/expo/modules/spotifysdk/SpotifyAppRemoteCoordinator.kt +556 -0
- package/android/src/main/java/expo/modules/spotifysdk/SpotifyErrors.kt +112 -0
- package/build/ExpoSpotifySDKModule.web.d.ts +23 -0
- package/build/ExpoSpotifySDKModule.web.d.ts.map +1 -1
- package/build/ExpoSpotifySDKModule.web.js +74 -5
- package/build/ExpoSpotifySDKModule.web.js.map +1 -1
- package/build/app-remote/error.d.ts +8 -0
- package/build/app-remote/error.d.ts.map +1 -0
- package/build/app-remote/error.js +10 -0
- package/build/app-remote/error.js.map +1 -0
- package/build/app-remote/index.d.ts +79 -0
- package/build/app-remote/index.d.ts.map +1 -0
- package/build/app-remote/index.js +110 -0
- package/build/app-remote/index.js.map +1 -0
- package/build/auth/error.d.ts +8 -0
- package/build/auth/error.d.ts.map +1 -0
- package/build/auth/error.js +10 -0
- package/build/auth/error.js.map +1 -0
- package/build/auth/index.d.ts +124 -0
- package/build/auth/index.d.ts.map +1 -0
- package/build/auth/index.js +162 -0
- package/build/auth/index.js.map +1 -0
- package/build/content/error.d.ts +8 -0
- package/build/content/error.d.ts.map +1 -0
- package/build/content/error.js +10 -0
- package/build/content/error.js.map +1 -0
- package/build/content/index.d.ts +38 -0
- package/build/content/index.d.ts.map +1 -0
- package/build/content/index.js +56 -0
- package/build/content/index.js.map +1 -0
- package/build/error.d.ts +24 -0
- package/build/error.d.ts.map +1 -0
- package/build/error.js +25 -0
- package/build/error.js.map +1 -0
- package/build/hooks/index.d.ts +90 -0
- package/build/hooks/index.d.ts.map +1 -0
- package/build/hooks/index.js +326 -0
- package/build/hooks/index.js.map +1 -0
- package/build/images/error.d.ts +8 -0
- package/build/images/error.d.ts.map +1 -0
- package/build/images/error.js +10 -0
- package/build/images/error.js.map +1 -0
- package/build/images/index.d.ts +36 -0
- package/build/images/index.d.ts.map +1 -0
- package/build/images/index.js +59 -0
- package/build/images/index.js.map +1 -0
- package/build/index.d.ts +44 -56
- package/build/index.d.ts.map +1 -1
- package/build/index.js +49 -141
- package/build/index.js.map +1 -1
- package/build/player/error.d.ts +8 -0
- package/build/player/error.d.ts.map +1 -0
- package/build/player/error.js +10 -0
- package/build/player/error.js.map +1 -0
- package/build/player/index.d.ts +140 -0
- package/build/player/index.d.ts.map +1 -0
- package/build/player/index.js +136 -0
- package/build/player/index.js.map +1 -0
- package/build/uri/index.d.ts +55 -0
- package/build/uri/index.d.ts.map +1 -0
- package/build/uri/index.js +57 -0
- package/build/uri/index.js.map +1 -0
- package/build/user/error.d.ts +8 -0
- package/build/user/error.d.ts.map +1 -0
- package/build/user/error.js +10 -0
- package/build/user/error.js.map +1 -0
- package/build/user/index.d.ts +44 -0
- package/build/user/index.d.ts.map +1 -0
- package/build/user/index.js +97 -0
- package/build/user/index.js.map +1 -0
- package/ios/ExpoSpotifySDKModule.swift +227 -5
- package/ios/SpotifyAppRemoteCoordinator.swift +939 -0
- package/ios/SpotifyAuthCoordinator.swift +53 -13
- package/ios/SpotifyTokenRefreshClient.swift +28 -0
- package/package.json +4 -3
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
const URI_RE = /^spotify:(track|album|playlist|artist|show|episode):([A-Za-z0-9]+)$/;
|
|
2
|
+
/**
|
|
3
|
+
* Helpers for constructing, validating and decomposing {@link SpotifyURI}
|
|
4
|
+
* values.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```ts
|
|
8
|
+
* const uri = SpotifyURI.from("spotify:track:4uLU6hMCjMI75M1A2tKUQC");
|
|
9
|
+
* const { type, id } = SpotifyURI.parse(uri);
|
|
10
|
+
* const rebuilt = SpotifyURI.build("track", id);
|
|
11
|
+
* ```
|
|
12
|
+
*/
|
|
13
|
+
export const SpotifyURI = {
|
|
14
|
+
/**
|
|
15
|
+
* Cast `uri` to {@link SpotifyURI} after validation.
|
|
16
|
+
* Throws a plain `Error` if the URI does not match the `spotify:<type>:<id>`
|
|
17
|
+
* format — use at app-code call sites where you want early feedback.
|
|
18
|
+
*/
|
|
19
|
+
from(uri) {
|
|
20
|
+
if (!SpotifyURI.isValid(uri)) {
|
|
21
|
+
throw new Error(`Invalid Spotify URI: "${uri}". Expected spotify:<type>:<id> where ` +
|
|
22
|
+
`<type> is one of: track, album, playlist, artist, show, episode.`);
|
|
23
|
+
}
|
|
24
|
+
return uri;
|
|
25
|
+
},
|
|
26
|
+
/**
|
|
27
|
+
* Cast `uri` to {@link SpotifyURI} without validation.
|
|
28
|
+
* Use only for URIs that originate from the Spotify SDK itself (i.e. already
|
|
29
|
+
* known-valid values coming back over the bridge).
|
|
30
|
+
*/
|
|
31
|
+
unsafe(uri) {
|
|
32
|
+
return uri;
|
|
33
|
+
},
|
|
34
|
+
/**
|
|
35
|
+
* Decompose a {@link SpotifyURI} into its `{ type, id }` parts.
|
|
36
|
+
*/
|
|
37
|
+
parse(uri) {
|
|
38
|
+
const m = uri.match(URI_RE);
|
|
39
|
+
if (!m) {
|
|
40
|
+
throw new Error(`Cannot parse Spotify URI: "${uri}"`);
|
|
41
|
+
}
|
|
42
|
+
return { type: m[1], id: m[2] };
|
|
43
|
+
},
|
|
44
|
+
/**
|
|
45
|
+
* Build a {@link SpotifyURI} from a resource type and ID.
|
|
46
|
+
*/
|
|
47
|
+
build(type, id) {
|
|
48
|
+
return `spotify:${type}:${id}`;
|
|
49
|
+
},
|
|
50
|
+
/**
|
|
51
|
+
* Type-guard: returns `true` if `uri` is a valid Spotify URI string.
|
|
52
|
+
*/
|
|
53
|
+
isValid(uri) {
|
|
54
|
+
return URI_RE.test(uri);
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/uri/index.ts"],"names":[],"mappings":"AAoBA,MAAM,MAAM,GACV,qEAAqE,CAAC;AAExE;;;;;;;;;;GAUG;AACH,MAAM,CAAC,MAAM,UAAU,GAAG;IACxB;;;;OAIG;IACH,IAAI,CAAC,GAAW;QACd,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC;YAC7B,MAAM,IAAI,KAAK,CACb,yBAAyB,GAAG,wCAAwC;gBAClE,kEAAkE,CACrE,CAAC;QACJ,CAAC;QACD,OAAO,GAAiB,CAAC;IAC3B,CAAC;IAED;;;;OAIG;IACH,MAAM,CAAC,GAAW;QAChB,OAAO,GAAiB,CAAC;IAC3B,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,GAAe;QACnB,MAAM,CAAC,GAAG,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;QAC5B,IAAI,CAAC,CAAC,EAAE,CAAC;YACP,MAAM,IAAI,KAAK,CAAC,8BAA8B,GAAG,GAAG,CAAC,CAAC;QACxD,CAAC;QACD,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAwB,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IACzD,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,IAAyB,EAAE,EAAU;QACzC,OAAO,WAAW,IAAI,IAAI,EAAE,EAAgB,CAAC;IAC/C,CAAC;IAED;;OAEG;IACH,OAAO,CAAC,GAAW;QACjB,OAAO,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAC1B,CAAC;CACO,CAAC","sourcesContent":["/**\n * The six Spotify resource types accepted by App Remote APIs.\n */\nexport type SpotifyResourceType =\n | \"track\"\n | \"album\"\n | \"playlist\"\n | \"artist\"\n | \"show\"\n | \"episode\";\n\n/**\n * Branded string type for Spotify URIs (`spotify:<type>:<id>`).\n *\n * Construct via `SpotifyURI.from(str)` (validates) or\n * `SpotifyURI.unsafe(str)` (skips validation — use only when the source is\n * trusted, e.g. a URI that came back from the Spotify SDK itself).\n */\nexport type SpotifyURI = string & { readonly __brand: \"SpotifyURI\" };\n\nconst URI_RE =\n /^spotify:(track|album|playlist|artist|show|episode):([A-Za-z0-9]+)$/;\n\n/**\n * Helpers for constructing, validating and decomposing {@link SpotifyURI}\n * values.\n *\n * @example\n * ```ts\n * const uri = SpotifyURI.from(\"spotify:track:4uLU6hMCjMI75M1A2tKUQC\");\n * const { type, id } = SpotifyURI.parse(uri);\n * const rebuilt = SpotifyURI.build(\"track\", id);\n * ```\n */\nexport const SpotifyURI = {\n /**\n * Cast `uri` to {@link SpotifyURI} after validation.\n * Throws a plain `Error` if the URI does not match the `spotify:<type>:<id>`\n * format — use at app-code call sites where you want early feedback.\n */\n from(uri: string): SpotifyURI {\n if (!SpotifyURI.isValid(uri)) {\n throw new Error(\n `Invalid Spotify URI: \"${uri}\". Expected spotify:<type>:<id> where ` +\n `<type> is one of: track, album, playlist, artist, show, episode.`,\n );\n }\n return uri as SpotifyURI;\n },\n\n /**\n * Cast `uri` to {@link SpotifyURI} without validation.\n * Use only for URIs that originate from the Spotify SDK itself (i.e. already\n * known-valid values coming back over the bridge).\n */\n unsafe(uri: string): SpotifyURI {\n return uri as SpotifyURI;\n },\n\n /**\n * Decompose a {@link SpotifyURI} into its `{ type, id }` parts.\n */\n parse(uri: SpotifyURI): { type: SpotifyResourceType; id: string } {\n const m = uri.match(URI_RE);\n if (!m) {\n throw new Error(`Cannot parse Spotify URI: \"${uri}\"`);\n }\n return { type: m[1] as SpotifyResourceType, id: m[2] };\n },\n\n /**\n * Build a {@link SpotifyURI} from a resource type and ID.\n */\n build(type: SpotifyResourceType, id: string): SpotifyURI {\n return `spotify:${type}:${id}` as SpotifyURI;\n },\n\n /**\n * Type-guard: returns `true` if `uri` is a valid Spotify URI string.\n */\n isValid(uri: string): uri is SpotifyURI {\n return URI_RE.test(uri);\n },\n} as const;\n"]}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { SpotifyError } from "../error";
|
|
2
|
+
export type UserErrorCode = "NOT_CONNECTED" | "CONNECTION_LOST" | "INVALID_URI" | "OPERATION_NOT_ALLOWED" | "UNKNOWN";
|
|
3
|
+
export declare class UserError extends SpotifyError {
|
|
4
|
+
readonly namespace: "User";
|
|
5
|
+
readonly code: UserErrorCode;
|
|
6
|
+
constructor(code: UserErrorCode, message: string);
|
|
7
|
+
}
|
|
8
|
+
//# sourceMappingURL=error.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"error.d.ts","sourceRoot":"","sources":["../../src/user/error.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAExC,MAAM,MAAM,aAAa,GACrB,eAAe,GACf,iBAAiB,GACjB,aAAa,GACb,uBAAuB,GACvB,SAAS,CAAC;AAEd,qBAAa,SAAU,SAAQ,YAAY;IACzC,QAAQ,CAAC,SAAS,EAAG,MAAM,CAAU;IACrC,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAC;gBAEjB,IAAI,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM;CAIjD"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"error.js","sourceRoot":"","sources":["../../src/user/error.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AASxC,MAAM,OAAO,SAAU,SAAQ,YAAY;IAChC,SAAS,GAAG,MAAe,CAAC;IAC5B,IAAI,CAAgB;IAE7B,YAAY,IAAmB,EAAE,OAAe;QAC9C,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;IACnB,CAAC;CACF","sourcesContent":["import { SpotifyError } from \"../error\";\n\nexport type UserErrorCode =\n | \"NOT_CONNECTED\"\n | \"CONNECTION_LOST\"\n | \"INVALID_URI\"\n | \"OPERATION_NOT_ALLOWED\"\n | \"UNKNOWN\";\n\nexport class UserError extends SpotifyError {\n readonly namespace = \"User\" as const;\n readonly code: UserErrorCode;\n\n constructor(code: UserErrorCode, message: string) {\n super(message);\n this.code = code;\n }\n}\n"]}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
export type { UserErrorCode } from "./error";
|
|
2
|
+
export { UserError } from "./error";
|
|
3
|
+
import { EventSubscription } from "expo-modules-core";
|
|
4
|
+
import type { SpotifyURI as SpotifyURIType } from "../uri";
|
|
5
|
+
/** Capabilities of the current user in Spotify's App Remote context. */
|
|
6
|
+
export interface Capabilities {
|
|
7
|
+
/** `true` for Premium users with on-demand playback capability. */
|
|
8
|
+
canPlayOnDemand: boolean;
|
|
9
|
+
}
|
|
10
|
+
/** Library save state for a specific album/track URI. */
|
|
11
|
+
export interface LibraryState {
|
|
12
|
+
uri: string;
|
|
13
|
+
isAdded: boolean;
|
|
14
|
+
canAdd: boolean;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Spotify User namespace. Capabilities and library-state operations.
|
|
18
|
+
* Requires `AppRemote.connect()` to be resolved before any call.
|
|
19
|
+
*/
|
|
20
|
+
export declare const User: {
|
|
21
|
+
/** Returns the current user's capabilities. */
|
|
22
|
+
readonly getCapabilities: () => Promise<Capabilities>;
|
|
23
|
+
/** Returns the current library state for a track or album URI. */
|
|
24
|
+
readonly getLibraryState: (uri: SpotifyURIType) => Promise<LibraryState>;
|
|
25
|
+
/** Adds a track or album URI to the user's library. */
|
|
26
|
+
readonly addToLibrary: (uri: SpotifyURIType) => Promise<void>;
|
|
27
|
+
/** Removes a track or album URI from the user's library. */
|
|
28
|
+
readonly removeFromLibrary: (uri: SpotifyURIType) => Promise<void>;
|
|
29
|
+
/**
|
|
30
|
+
* Subscribes to user-scoped events.
|
|
31
|
+
* Supported event: `"capabilitiesChange"`.
|
|
32
|
+
*/
|
|
33
|
+
readonly addListener: (event: "capabilitiesChange", listener: (event: Capabilities) => void) => EventSubscription;
|
|
34
|
+
/**
|
|
35
|
+
* Subscribes to library state changes for a specific URI.
|
|
36
|
+
*
|
|
37
|
+
* There is no native push stream for per-URI library state updates on all
|
|
38
|
+
* platforms, so this listener is updated whenever library mutations are
|
|
39
|
+
* performed via this SDK and can be manually seeded by calling
|
|
40
|
+
* `User.getLibraryState(uri)` before subscribing.
|
|
41
|
+
*/
|
|
42
|
+
readonly addLibraryStateListener: (uri: SpotifyURIType, listener: (state: LibraryState) => void) => EventSubscription;
|
|
43
|
+
};
|
|
44
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/user/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAC7C,OAAO,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAMpC,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAGtD,OAAO,KAAK,EAAE,UAAU,IAAI,cAAc,EAAE,MAAM,QAAQ,CAAC;AAG3D,wEAAwE;AACxE,MAAM,WAAW,YAAY;IAC3B,mEAAmE;IACnE,eAAe,EAAE,OAAO,CAAC;CAC1B;AAED,yDAAyD;AACzD,MAAM,WAAW,YAAY;IAC3B,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,OAAO,CAAC;CACjB;AA2CD;;;GAGG;AACH,eAAO,MAAM,IAAI;IACf,+CAA+C;oCAC5B,OAAO,CAAC,YAAY,CAAC;IAIxC,kEAAkE;oCAC7C,cAAc,KAAG,OAAO,CAAC,YAAY,CAAC;IAI3D,uDAAuD;iCAC/B,cAAc,KAAG,OAAO,CAAC,IAAI,CAAC;IAOtD,4DAA4D;sCAC/B,cAAc,KAAG,OAAO,CAAC,IAAI,CAAC;IAO3D;;;OAGG;kCAEM,oBAAoB,YACjB,CAAC,KAAK,EAAE,YAAY,KAAK,IAAI,KACtC,iBAAiB;IAOpB;;;;;;;OAOG;4CAEI,cAAc,YACT,CAAC,KAAK,EAAE,YAAY,KAAK,IAAI,KACtC,iBAAiB;CAiBZ,CAAC"}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
export { UserError } from "./error";
|
|
2
|
+
import ExpoSpotifySDKModule from "../ExpoSpotifySDKModule";
|
|
3
|
+
import { UserError } from "./error";
|
|
4
|
+
// ---------------------------------------------------------------------------
|
|
5
|
+
// Internal helpers
|
|
6
|
+
// ---------------------------------------------------------------------------
|
|
7
|
+
const VALID_USER_CODES = new Set([
|
|
8
|
+
"NOT_CONNECTED",
|
|
9
|
+
"CONNECTION_LOST",
|
|
10
|
+
"INVALID_URI",
|
|
11
|
+
"OPERATION_NOT_ALLOWED",
|
|
12
|
+
"UNKNOWN",
|
|
13
|
+
]);
|
|
14
|
+
const CAUSE_SEPARATOR = "→ Caused by: ";
|
|
15
|
+
function unwrapReason(message) {
|
|
16
|
+
const idx = message.lastIndexOf(CAUSE_SEPARATOR);
|
|
17
|
+
return idx === -1 ? message : message.slice(idx + CAUSE_SEPARATOR.length);
|
|
18
|
+
}
|
|
19
|
+
function rethrowAsUserError(err) {
|
|
20
|
+
if (err instanceof UserError)
|
|
21
|
+
throw err;
|
|
22
|
+
if (err instanceof Error) {
|
|
23
|
+
const reason = unwrapReason(err.message);
|
|
24
|
+
const maybeCode = err.code;
|
|
25
|
+
if (maybeCode && VALID_USER_CODES.has(maybeCode)) {
|
|
26
|
+
throw new UserError(maybeCode, reason);
|
|
27
|
+
}
|
|
28
|
+
throw new UserError("UNKNOWN", reason);
|
|
29
|
+
}
|
|
30
|
+
throw new UserError("UNKNOWN", String(err));
|
|
31
|
+
}
|
|
32
|
+
const libraryListeners = new Map();
|
|
33
|
+
function notifyLibraryState(state) {
|
|
34
|
+
const set = libraryListeners.get(state.uri);
|
|
35
|
+
if (!set)
|
|
36
|
+
return;
|
|
37
|
+
set.forEach((listener) => listener(state));
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Spotify User namespace. Capabilities and library-state operations.
|
|
41
|
+
* Requires `AppRemote.connect()` to be resolved before any call.
|
|
42
|
+
*/
|
|
43
|
+
export const User = {
|
|
44
|
+
/** Returns the current user's capabilities. */
|
|
45
|
+
getCapabilities() {
|
|
46
|
+
return ExpoSpotifySDKModule.userGetCapabilities().catch(rethrowAsUserError);
|
|
47
|
+
},
|
|
48
|
+
/** Returns the current library state for a track or album URI. */
|
|
49
|
+
getLibraryState(uri) {
|
|
50
|
+
return ExpoSpotifySDKModule.userGetLibraryState(uri).catch(rethrowAsUserError);
|
|
51
|
+
},
|
|
52
|
+
/** Adds a track or album URI to the user's library. */
|
|
53
|
+
async addToLibrary(uri) {
|
|
54
|
+
const state = (await ExpoSpotifySDKModule.userAddToLibrary(uri).catch(rethrowAsUserError));
|
|
55
|
+
notifyLibraryState(state);
|
|
56
|
+
},
|
|
57
|
+
/** Removes a track or album URI from the user's library. */
|
|
58
|
+
async removeFromLibrary(uri) {
|
|
59
|
+
const state = (await ExpoSpotifySDKModule.userRemoveFromLibrary(uri).catch(rethrowAsUserError));
|
|
60
|
+
notifyLibraryState(state);
|
|
61
|
+
},
|
|
62
|
+
/**
|
|
63
|
+
* Subscribes to user-scoped events.
|
|
64
|
+
* Supported event: `"capabilitiesChange"`.
|
|
65
|
+
*/
|
|
66
|
+
addListener(event, listener) {
|
|
67
|
+
return ExpoSpotifySDKModule.addListener("onCapabilitiesChange", listener);
|
|
68
|
+
},
|
|
69
|
+
/**
|
|
70
|
+
* Subscribes to library state changes for a specific URI.
|
|
71
|
+
*
|
|
72
|
+
* There is no native push stream for per-URI library state updates on all
|
|
73
|
+
* platforms, so this listener is updated whenever library mutations are
|
|
74
|
+
* performed via this SDK and can be manually seeded by calling
|
|
75
|
+
* `User.getLibraryState(uri)` before subscribing.
|
|
76
|
+
*/
|
|
77
|
+
addLibraryStateListener(uri, listener) {
|
|
78
|
+
const key = String(uri);
|
|
79
|
+
let set = libraryListeners.get(key);
|
|
80
|
+
if (!set) {
|
|
81
|
+
set = new Set();
|
|
82
|
+
libraryListeners.set(key, set);
|
|
83
|
+
}
|
|
84
|
+
set.add(listener);
|
|
85
|
+
return {
|
|
86
|
+
remove() {
|
|
87
|
+
const current = libraryListeners.get(key);
|
|
88
|
+
if (!current)
|
|
89
|
+
return;
|
|
90
|
+
current.delete(listener);
|
|
91
|
+
if (current.size === 0)
|
|
92
|
+
libraryListeners.delete(key);
|
|
93
|
+
},
|
|
94
|
+
};
|
|
95
|
+
},
|
|
96
|
+
};
|
|
97
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/user/index.ts"],"names":[],"mappings":"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","sourcesContent":["export type { UserErrorCode } from \"./error\";\nexport { UserError } from \"./error\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nimport { EventSubscription } from \"expo-modules-core\";\n\nimport ExpoSpotifySDKModule from \"../ExpoSpotifySDKModule\";\nimport type { SpotifyURI as SpotifyURIType } from \"../uri\";\nimport { UserError, UserErrorCode } from \"./error\";\n\n/** Capabilities of the current user in Spotify's App Remote context. */\nexport interface Capabilities {\n /** `true` for Premium users with on-demand playback capability. */\n canPlayOnDemand: boolean;\n}\n\n/** Library save state for a specific album/track URI. */\nexport interface LibraryState {\n uri: string;\n isAdded: boolean;\n canAdd: boolean;\n}\n\n// ---------------------------------------------------------------------------\n// Internal helpers\n// ---------------------------------------------------------------------------\n\nconst VALID_USER_CODES = new Set<UserErrorCode>([\n \"NOT_CONNECTED\",\n \"CONNECTION_LOST\",\n \"INVALID_URI\",\n \"OPERATION_NOT_ALLOWED\",\n \"UNKNOWN\",\n]);\n\nconst CAUSE_SEPARATOR = \"→ Caused by: \";\n\nfunction unwrapReason(message: string): string {\n const idx = message.lastIndexOf(CAUSE_SEPARATOR);\n return idx === -1 ? message : message.slice(idx + CAUSE_SEPARATOR.length);\n}\n\nfunction rethrowAsUserError(err: unknown): never {\n if (err instanceof UserError) throw err;\n if (err instanceof Error) {\n const reason = unwrapReason(err.message);\n const maybeCode = (err as Error & { code?: string }).code;\n if (maybeCode && VALID_USER_CODES.has(maybeCode as UserErrorCode)) {\n throw new UserError(maybeCode as UserErrorCode, reason);\n }\n throw new UserError(\"UNKNOWN\", reason);\n }\n throw new UserError(\"UNKNOWN\", String(err));\n}\n\ntype LibraryListener = (state: LibraryState) => void;\nconst libraryListeners = new Map<string, Set<LibraryListener>>();\n\nfunction notifyLibraryState(state: LibraryState) {\n const set = libraryListeners.get(state.uri);\n if (!set) return;\n set.forEach((listener) => listener(state));\n}\n\n/**\n * Spotify User namespace. Capabilities and library-state operations.\n * Requires `AppRemote.connect()` to be resolved before any call.\n */\nexport const User = {\n /** Returns the current user's capabilities. */\n getCapabilities(): Promise<Capabilities> {\n return ExpoSpotifySDKModule.userGetCapabilities().catch(rethrowAsUserError);\n },\n\n /** Returns the current library state for a track or album URI. */\n getLibraryState(uri: SpotifyURIType): Promise<LibraryState> {\n return ExpoSpotifySDKModule.userGetLibraryState(uri).catch(rethrowAsUserError);\n },\n\n /** Adds a track or album URI to the user's library. */\n async addToLibrary(uri: SpotifyURIType): Promise<void> {\n const state = (await ExpoSpotifySDKModule.userAddToLibrary(uri).catch(\n rethrowAsUserError,\n )) as LibraryState;\n notifyLibraryState(state);\n },\n\n /** Removes a track or album URI from the user's library. */\n async removeFromLibrary(uri: SpotifyURIType): Promise<void> {\n const state = (await ExpoSpotifySDKModule.userRemoveFromLibrary(uri).catch(\n rethrowAsUserError,\n )) as LibraryState;\n notifyLibraryState(state);\n },\n\n /**\n * Subscribes to user-scoped events.\n * Supported event: `\"capabilitiesChange\"`.\n */\n addListener(\n event: \"capabilitiesChange\",\n listener: (event: Capabilities) => void,\n ): EventSubscription {\n return ExpoSpotifySDKModule.addListener(\n \"onCapabilitiesChange\",\n listener as (event: unknown) => void,\n ) as EventSubscription;\n },\n\n /**\n * Subscribes to library state changes for a specific URI.\n *\n * There is no native push stream for per-URI library state updates on all\n * platforms, so this listener is updated whenever library mutations are\n * performed via this SDK and can be manually seeded by calling\n * `User.getLibraryState(uri)` before subscribing.\n */\n addLibraryStateListener(\n uri: SpotifyURIType,\n listener: (state: LibraryState) => void,\n ): EventSubscription {\n const key = String(uri);\n let set = libraryListeners.get(key);\n if (!set) {\n set = new Set();\n libraryListeners.set(key, set);\n }\n set.add(listener);\n return {\n remove() {\n const current = libraryListeners.get(key);\n if (!current) return;\n current.delete(listener);\n if (current.size === 0) libraryListeners.delete(key);\n },\n } as EventSubscription;\n },\n} as const;\n"]}
|
|
@@ -1,13 +1,47 @@
|
|
|
1
1
|
import ExpoModulesCore
|
|
2
2
|
import SpotifyiOS
|
|
3
3
|
|
|
4
|
-
private let SDK_VERSION = "0.
|
|
4
|
+
private let SDK_VERSION = "1.0.0" // x-release-please-version
|
|
5
5
|
private let EVENT_SESSION_CHANGE = "onSessionChange"
|
|
6
|
+
private let EVENT_CONNECTION_STATE_CHANGE = "onConnectionStateChange"
|
|
7
|
+
private let EVENT_CONNECTION_ERROR = "onConnectionError"
|
|
8
|
+
private let EVENT_PLAYER_STATE_CHANGE = "onPlayerStateChange"
|
|
9
|
+
private let EVENT_CAPABILITIES_CHANGE = "onCapabilitiesChange"
|
|
6
10
|
|
|
7
11
|
public class ExpoSpotifySDKModule: Module {
|
|
8
12
|
public func definition() -> ModuleDefinition {
|
|
9
13
|
Name("ExpoSpotifySDK")
|
|
10
|
-
|
|
14
|
+
|
|
15
|
+
Events(
|
|
16
|
+
EVENT_SESSION_CHANGE,
|
|
17
|
+
EVENT_CONNECTION_STATE_CHANGE,
|
|
18
|
+
EVENT_CONNECTION_ERROR,
|
|
19
|
+
EVENT_PLAYER_STATE_CHANGE,
|
|
20
|
+
EVENT_CAPABILITIES_CHANGE
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
// Wire up App Remote coordinator event callbacks once the module is alive.
|
|
24
|
+
OnCreate {
|
|
25
|
+
Task {
|
|
26
|
+
guard let coordinator = SpotifyAppRemoteCoordinator.shared else { return }
|
|
27
|
+
await coordinator.setEventHandlers(
|
|
28
|
+
onConnectionStateChange: { [weak self] state in
|
|
29
|
+
self?.sendEvent(EVENT_CONNECTION_STATE_CHANGE, ["state": state])
|
|
30
|
+
},
|
|
31
|
+
onConnectionError: { [weak self] code, message in
|
|
32
|
+
self?.sendEvent(EVENT_CONNECTION_ERROR, ["code": code, "message": message])
|
|
33
|
+
},
|
|
34
|
+
onPlayerStateChange: { [weak self] stateMap in
|
|
35
|
+
self?.sendEvent(EVENT_PLAYER_STATE_CHANGE, stateMap)
|
|
36
|
+
},
|
|
37
|
+
onCapabilitiesChange: { [weak self] capabilities in
|
|
38
|
+
self?.sendEvent(EVENT_CAPABILITIES_CHANGE, capabilities)
|
|
39
|
+
}
|
|
40
|
+
)
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// MARK: — Auth
|
|
11
45
|
|
|
12
46
|
Function("isAvailable") { () -> Bool in
|
|
13
47
|
SpotifyAuthCoordinator.shared?.isSpotifyAppInstalled() ?? false
|
|
@@ -43,7 +77,7 @@ public class ExpoSpotifySDKModule: Module {
|
|
|
43
77
|
"type": "didFail",
|
|
44
78
|
"error": ["code": error.code, "message": error.message],
|
|
45
79
|
])
|
|
46
|
-
throw
|
|
80
|
+
throw SpotifyAuthException(error)
|
|
47
81
|
}
|
|
48
82
|
}
|
|
49
83
|
|
|
@@ -79,17 +113,203 @@ public class ExpoSpotifySDKModule: Module {
|
|
|
79
113
|
"type": "didFail",
|
|
80
114
|
"error": ["code": error.code, "message": error.message],
|
|
81
115
|
])
|
|
82
|
-
throw
|
|
116
|
+
throw SpotifyAuthException(error)
|
|
83
117
|
} catch let error as SpotifyRefreshError {
|
|
84
118
|
self.sendEvent(EVENT_SESSION_CHANGE, [
|
|
85
119
|
"type": "didFail",
|
|
86
120
|
"error": ["code": error.code, "message": error.message],
|
|
87
121
|
])
|
|
88
|
-
throw
|
|
122
|
+
throw SpotifyRefreshException(error)
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// MARK: — App Remote
|
|
127
|
+
|
|
128
|
+
AsyncFunction("appRemoteConnect") { (accessToken: String) async throws -> Void in
|
|
129
|
+
guard let coordinator = SpotifyAppRemoteCoordinator.shared else {
|
|
130
|
+
throw AppRemoteException(AppRemoteError.connectionFailed(
|
|
131
|
+
"Missing `ExpoSpotifySDK` configuration in Info.plist."
|
|
132
|
+
))
|
|
133
|
+
}
|
|
134
|
+
do {
|
|
135
|
+
try await coordinator.connect(accessToken: accessToken)
|
|
136
|
+
} catch let error as AppRemoteError {
|
|
137
|
+
throw AppRemoteException(error)
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
AsyncFunction("appRemoteDisconnect") { () async -> Void in
|
|
142
|
+
await SpotifyAppRemoteCoordinator.shared?.disconnect()
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
Function("appRemoteIsConnected") { () -> Bool in
|
|
146
|
+
SpotifyAppRemoteCoordinator.shared?.isConnected() ?? false
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
AsyncFunction("appRemoteGetConnectionState") { () async -> String in
|
|
150
|
+
await SpotifyAppRemoteCoordinator.shared?.getConnectionState() ?? "disconnected"
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// MARK: — Player
|
|
154
|
+
|
|
155
|
+
AsyncFunction("playerPlay") { (uri: String) async throws -> Void in
|
|
156
|
+
guard let coordinator = SpotifyAppRemoteCoordinator.shared else {
|
|
157
|
+
throw PlayerException(NativePlayerError.notConnected("Player.play: missing configuration"))
|
|
158
|
+
}
|
|
159
|
+
do { try await coordinator.playerPlay(uri: uri) } catch let e as NativePlayerError { throw PlayerException(e) }
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
AsyncFunction("playerPause") { () async throws -> Void in
|
|
163
|
+
guard let coordinator = SpotifyAppRemoteCoordinator.shared else {
|
|
164
|
+
throw PlayerException(NativePlayerError.notConnected("Player.pause: missing configuration"))
|
|
165
|
+
}
|
|
166
|
+
do { try await coordinator.playerPause() } catch let e as NativePlayerError { throw PlayerException(e) }
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
AsyncFunction("playerResume") { () async throws -> Void in
|
|
170
|
+
guard let coordinator = SpotifyAppRemoteCoordinator.shared else {
|
|
171
|
+
throw PlayerException(NativePlayerError.notConnected("Player.resume: missing configuration"))
|
|
172
|
+
}
|
|
173
|
+
do { try await coordinator.playerResume() } catch let e as NativePlayerError { throw PlayerException(e) }
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
AsyncFunction("playerSkipNext") { () async throws -> Void in
|
|
177
|
+
guard let coordinator = SpotifyAppRemoteCoordinator.shared else {
|
|
178
|
+
throw PlayerException(NativePlayerError.notConnected("Player.skipNext: missing configuration"))
|
|
179
|
+
}
|
|
180
|
+
do { try await coordinator.playerSkipNext() } catch let e as NativePlayerError { throw PlayerException(e) }
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
AsyncFunction("playerSkipPrevious") { () async throws -> Void in
|
|
184
|
+
guard let coordinator = SpotifyAppRemoteCoordinator.shared else {
|
|
185
|
+
throw PlayerException(NativePlayerError.notConnected("Player.skipPrevious: missing configuration"))
|
|
186
|
+
}
|
|
187
|
+
do { try await coordinator.playerSkipPrevious() } catch let e as NativePlayerError { throw PlayerException(e) }
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
AsyncFunction("playerSeekTo") { (positionMs: Int) async throws -> Void in
|
|
191
|
+
guard let coordinator = SpotifyAppRemoteCoordinator.shared else {
|
|
192
|
+
throw PlayerException(NativePlayerError.notConnected("Player.seekTo: missing configuration"))
|
|
193
|
+
}
|
|
194
|
+
do { try await coordinator.playerSeekTo(positionMs: positionMs) } catch let e as NativePlayerError { throw PlayerException(e) }
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
AsyncFunction("playerSetShuffle") { (enabled: Bool) async throws -> Void in
|
|
198
|
+
guard let coordinator = SpotifyAppRemoteCoordinator.shared else {
|
|
199
|
+
throw PlayerException(NativePlayerError.notConnected("Player.setShuffle: missing configuration"))
|
|
200
|
+
}
|
|
201
|
+
do { try await coordinator.playerSetShuffle(enabled: enabled) } catch let e as NativePlayerError { throw PlayerException(e) }
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
AsyncFunction("playerSetRepeatMode") { (mode: Int) async throws -> Void in
|
|
205
|
+
guard let coordinator = SpotifyAppRemoteCoordinator.shared else {
|
|
206
|
+
throw PlayerException(NativePlayerError.notConnected("Player.setRepeatMode: missing configuration"))
|
|
207
|
+
}
|
|
208
|
+
do { try await coordinator.playerSetRepeatMode(mode: mode) } catch let e as NativePlayerError { throw PlayerException(e) }
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
AsyncFunction("playerSetPodcastPlaybackSpeed") { (value: Double) async throws -> Void in
|
|
212
|
+
guard let coordinator = SpotifyAppRemoteCoordinator.shared else {
|
|
213
|
+
throw PlayerException(NativePlayerError.notConnected("Player.setPodcastPlaybackSpeed: missing configuration"))
|
|
214
|
+
}
|
|
215
|
+
do {
|
|
216
|
+
try await coordinator.playerSetPodcastPlaybackSpeed(value: Float(value))
|
|
217
|
+
} catch let e as NativePlayerError {
|
|
218
|
+
throw PlayerException(e)
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
AsyncFunction("playerQueue") { (uri: String) async throws -> Void in
|
|
223
|
+
guard let coordinator = SpotifyAppRemoteCoordinator.shared else {
|
|
224
|
+
throw PlayerException(NativePlayerError.notConnected("Player.queue: missing configuration"))
|
|
225
|
+
}
|
|
226
|
+
do { try await coordinator.playerQueue(uri: uri) } catch let e as NativePlayerError { throw PlayerException(e) }
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
AsyncFunction("playerGetPlayerState") { () async throws -> [String: Any] in
|
|
230
|
+
guard let coordinator = SpotifyAppRemoteCoordinator.shared else {
|
|
231
|
+
throw PlayerException(NativePlayerError.notConnected("Player.getPlayerState: missing configuration"))
|
|
232
|
+
}
|
|
233
|
+
do { return try await coordinator.playerGetPlayerState() } catch let e as NativePlayerError { throw PlayerException(e) }
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
AsyncFunction("playerGetCrossfadeState") { () async throws -> [String: Any] in
|
|
237
|
+
guard let coordinator = SpotifyAppRemoteCoordinator.shared else {
|
|
238
|
+
throw PlayerException(NativePlayerError.notConnected("Player.getCrossfadeState: missing configuration"))
|
|
239
|
+
}
|
|
240
|
+
do { return try await coordinator.playerGetCrossfadeState() } catch let e as NativePlayerError { throw PlayerException(e) }
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// MARK: — User
|
|
244
|
+
|
|
245
|
+
AsyncFunction("userGetCapabilities") { () async throws -> [String: Any] in
|
|
246
|
+
guard let coordinator = SpotifyAppRemoteCoordinator.shared else {
|
|
247
|
+
throw UserException(NativeUserError.notConnected("User.getCapabilities: missing configuration"))
|
|
248
|
+
}
|
|
249
|
+
do { return try await coordinator.userGetCapabilities() } catch let e as NativeUserError { throw UserException(e) }
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
AsyncFunction("userGetLibraryState") { (uri: String) async throws -> [String: Any] in
|
|
253
|
+
guard let coordinator = SpotifyAppRemoteCoordinator.shared else {
|
|
254
|
+
throw UserException(NativeUserError.notConnected("User.getLibraryState: missing configuration"))
|
|
255
|
+
}
|
|
256
|
+
do { return try await coordinator.userGetLibraryState(uri: uri) } catch let e as NativeUserError { throw UserException(e) }
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
AsyncFunction("userAddToLibrary") { (uri: String) async throws -> [String: Any] in
|
|
260
|
+
guard let coordinator = SpotifyAppRemoteCoordinator.shared else {
|
|
261
|
+
throw UserException(NativeUserError.notConnected("User.addToLibrary: missing configuration"))
|
|
262
|
+
}
|
|
263
|
+
do { return try await coordinator.userAddToLibrary(uri: uri) } catch let e as NativeUserError { throw UserException(e) }
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
AsyncFunction("userRemoveFromLibrary") { (uri: String) async throws -> [String: Any] in
|
|
267
|
+
guard let coordinator = SpotifyAppRemoteCoordinator.shared else {
|
|
268
|
+
throw UserException(NativeUserError.notConnected("User.removeFromLibrary: missing configuration"))
|
|
269
|
+
}
|
|
270
|
+
do { return try await coordinator.userRemoveFromLibrary(uri: uri) } catch let e as NativeUserError { throw UserException(e) }
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// MARK: — Content
|
|
274
|
+
|
|
275
|
+
AsyncFunction("contentGetRecommendedContentItems") { (type: String) async throws -> [[String: Any]] in
|
|
276
|
+
guard let coordinator = SpotifyAppRemoteCoordinator.shared else {
|
|
277
|
+
throw ContentException(NativeContentError.notConnected("Content.getRecommendedContentItems: missing configuration"))
|
|
278
|
+
}
|
|
279
|
+
do {
|
|
280
|
+
return try await coordinator.contentGetRecommendedContentItems(type: type)
|
|
281
|
+
} catch let e as NativeContentError {
|
|
282
|
+
throw ContentException(e)
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
AsyncFunction("contentGetChildren") { (item: [String: Any]) async throws -> [[String: Any]] in
|
|
287
|
+
guard let coordinator = SpotifyAppRemoteCoordinator.shared else {
|
|
288
|
+
throw ContentException(NativeContentError.notConnected("Content.getChildren: missing configuration"))
|
|
289
|
+
}
|
|
290
|
+
do {
|
|
291
|
+
return try await coordinator.contentGetChildren(itemMap: item)
|
|
292
|
+
} catch let e as NativeContentError {
|
|
293
|
+
throw ContentException(e)
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// MARK: — Images
|
|
298
|
+
|
|
299
|
+
AsyncFunction("imagesLoad") { (imageIdentifier: String, size: String) async throws -> [String: Any] in
|
|
300
|
+
guard let coordinator = SpotifyAppRemoteCoordinator.shared else {
|
|
301
|
+
throw ImagesException(NativeImagesError.notConnected("Images.load: missing configuration"))
|
|
302
|
+
}
|
|
303
|
+
do {
|
|
304
|
+
return try await coordinator.imagesLoad(imageIdentifier: imageIdentifier, size: size)
|
|
305
|
+
} catch let e as NativeImagesError {
|
|
306
|
+
throw ImagesException(e)
|
|
89
307
|
}
|
|
90
308
|
}
|
|
91
309
|
}
|
|
92
310
|
|
|
311
|
+
// MARK: — Helpers
|
|
312
|
+
|
|
93
313
|
private func sessionToMap(_ session: SPTSession) -> [String: Any?] {
|
|
94
314
|
return [
|
|
95
315
|
"accessToken": session.accessToken,
|
|
@@ -100,6 +320,8 @@ public class ExpoSpotifySDKModule: Module {
|
|
|
100
320
|
}
|
|
101
321
|
}
|
|
102
322
|
|
|
323
|
+
// MARK: — Record types
|
|
324
|
+
|
|
103
325
|
struct AuthenticateOptions: Record {
|
|
104
326
|
@Field var scopes: [String] = []
|
|
105
327
|
@Field var tokenSwapURL: String? = nil
|