@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,162 @@
|
|
|
1
|
+
import { Platform } from "expo-modules-core";
|
|
2
|
+
import ExpoSpotifySDKModule from "../ExpoSpotifySDKModule";
|
|
3
|
+
import { AuthError } from "./error";
|
|
4
|
+
export { AuthError } from "./error";
|
|
5
|
+
// ---------------------------------------------------------------------------
|
|
6
|
+
// Internal helpers
|
|
7
|
+
// ---------------------------------------------------------------------------
|
|
8
|
+
const ANDROID_TOKEN_FLOW_WARNING = "[expo-spotify-sdk] You are using Auth.authenticate on Android without a " +
|
|
9
|
+
"tokenSwapURL. The Spotify Android SDK does NOT return a refresh token or " +
|
|
10
|
+
"the actual granted scopes through this path; see the README's " +
|
|
11
|
+
"'Android implicit (TOKEN) flow is not recommended' section.";
|
|
12
|
+
let warnedAboutAndroidTokenFlow = false;
|
|
13
|
+
const VALID_AUTH_CODES = new Set([
|
|
14
|
+
"USER_CANCELLED",
|
|
15
|
+
"AUTH_IN_PROGRESS",
|
|
16
|
+
"INVALID_CONFIG",
|
|
17
|
+
"NETWORK_ERROR",
|
|
18
|
+
"TOKEN_SWAP_FAILED",
|
|
19
|
+
"TOKEN_SWAP_PARSE_ERROR",
|
|
20
|
+
"SPOTIFY_NOT_INSTALLED",
|
|
21
|
+
"AUTH_ERROR",
|
|
22
|
+
"UNKNOWN",
|
|
23
|
+
]);
|
|
24
|
+
const CAUSE_SEPARATOR = "→ Caused by: ";
|
|
25
|
+
const LEGACY_CODE_PREFIX_RE = /^([A-Z_][A-Z0-9_]*):\s*(.*)$/s;
|
|
26
|
+
function unwrapReason(message) {
|
|
27
|
+
const idx = message.lastIndexOf(CAUSE_SEPARATOR);
|
|
28
|
+
return idx === -1 ? message : message.slice(idx + CAUSE_SEPARATOR.length);
|
|
29
|
+
}
|
|
30
|
+
function rethrowAsAuthError(err) {
|
|
31
|
+
if (err instanceof AuthError)
|
|
32
|
+
throw err;
|
|
33
|
+
if (err instanceof Error) {
|
|
34
|
+
const reason = unwrapReason(err.message);
|
|
35
|
+
const maybeCode = err.code;
|
|
36
|
+
if (maybeCode && VALID_AUTH_CODES.has(maybeCode)) {
|
|
37
|
+
throw new AuthError(maybeCode, reason);
|
|
38
|
+
}
|
|
39
|
+
const m = reason.match(LEGACY_CODE_PREFIX_RE);
|
|
40
|
+
if (m && VALID_AUTH_CODES.has(m[1])) {
|
|
41
|
+
throw new AuthError(m[1], m[2]);
|
|
42
|
+
}
|
|
43
|
+
throw new AuthError("UNKNOWN", reason);
|
|
44
|
+
}
|
|
45
|
+
throw new AuthError("UNKNOWN", String(err));
|
|
46
|
+
}
|
|
47
|
+
function normaliseSession(raw) {
|
|
48
|
+
if (!raw || typeof raw !== "object") {
|
|
49
|
+
throw new AuthError("UNKNOWN", "Native module returned a non-object session");
|
|
50
|
+
}
|
|
51
|
+
const r = raw;
|
|
52
|
+
const accessToken = r.accessToken;
|
|
53
|
+
if (typeof accessToken !== "string" || accessToken.length === 0) {
|
|
54
|
+
throw new AuthError("UNKNOWN", "Session is missing accessToken");
|
|
55
|
+
}
|
|
56
|
+
const expirationDate = r.expirationDate;
|
|
57
|
+
if (typeof expirationDate !== "number") {
|
|
58
|
+
throw new AuthError("UNKNOWN", "Session is missing expirationDate");
|
|
59
|
+
}
|
|
60
|
+
const refreshTokenRaw = r.refreshToken;
|
|
61
|
+
const refreshToken = typeof refreshTokenRaw === "string" && refreshTokenRaw.length > 0
|
|
62
|
+
? refreshTokenRaw
|
|
63
|
+
: null;
|
|
64
|
+
const scopesRaw = r.scopes;
|
|
65
|
+
const scopes = Array.isArray(scopesRaw)
|
|
66
|
+
? scopesRaw.filter((s) => typeof s === "string")
|
|
67
|
+
: [];
|
|
68
|
+
return { accessToken, refreshToken, expirationDate, scopes };
|
|
69
|
+
}
|
|
70
|
+
// ---------------------------------------------------------------------------
|
|
71
|
+
// Auth namespace
|
|
72
|
+
// ---------------------------------------------------------------------------
|
|
73
|
+
/**
|
|
74
|
+
* Spotify Auth namespace. Handles OAuth authentication and session lifecycle.
|
|
75
|
+
*
|
|
76
|
+
* @example
|
|
77
|
+
* ```ts
|
|
78
|
+
* import { Auth } from "@wwdrew/expo-spotify-sdk";
|
|
79
|
+
*
|
|
80
|
+
* const session = await Auth.authenticate({ scopes: ["streaming"] });
|
|
81
|
+
* ```
|
|
82
|
+
*/
|
|
83
|
+
export const Auth = {
|
|
84
|
+
/**
|
|
85
|
+
* Returns `true` if the Spotify app is installed on the device.
|
|
86
|
+
*/
|
|
87
|
+
isAvailable() {
|
|
88
|
+
return ExpoSpotifySDKModule.isAvailable();
|
|
89
|
+
},
|
|
90
|
+
/**
|
|
91
|
+
* Starts a Spotify OAuth flow. Resolves with a {@link SpotifySession};
|
|
92
|
+
* rejects with an {@link AuthError} carrying a `code`.
|
|
93
|
+
*/
|
|
94
|
+
authenticate(config) {
|
|
95
|
+
if (!config.scopes || config.scopes.length === 0) {
|
|
96
|
+
return Promise.reject(new AuthError("INVALID_CONFIG", "scopes must contain at least one entry"));
|
|
97
|
+
}
|
|
98
|
+
if (Platform.OS === "android" &&
|
|
99
|
+
!config.tokenSwapURL &&
|
|
100
|
+
!warnedAboutAndroidTokenFlow) {
|
|
101
|
+
warnedAboutAndroidTokenFlow = true;
|
|
102
|
+
console.warn(ANDROID_TOKEN_FLOW_WARNING);
|
|
103
|
+
}
|
|
104
|
+
return ExpoSpotifySDKModule.authenticateAsync(config)
|
|
105
|
+
.then(normaliseSession)
|
|
106
|
+
.catch(rethrowAsAuthError);
|
|
107
|
+
},
|
|
108
|
+
/**
|
|
109
|
+
* Exchanges a refresh token for a new access token via your token refresh
|
|
110
|
+
* server. Resolves with a fresh {@link SpotifySession}; rejects with an
|
|
111
|
+
* {@link AuthError}.
|
|
112
|
+
*/
|
|
113
|
+
refresh(config) {
|
|
114
|
+
if (!config.refreshToken) {
|
|
115
|
+
return Promise.reject(new AuthError("INVALID_CONFIG", "refreshToken is required"));
|
|
116
|
+
}
|
|
117
|
+
if (!config.tokenRefreshURL) {
|
|
118
|
+
return Promise.reject(new AuthError("INVALID_CONFIG", "tokenRefreshURL is required"));
|
|
119
|
+
}
|
|
120
|
+
return ExpoSpotifySDKModule.refreshSessionAsync(config)
|
|
121
|
+
.then(normaliseSession)
|
|
122
|
+
.catch(rethrowAsAuthError);
|
|
123
|
+
},
|
|
124
|
+
/**
|
|
125
|
+
* Forcibly cancel any in-flight `Auth.authenticate()` call. No-op on
|
|
126
|
+
* Android (the Android coordinator self-cleans via structured concurrency).
|
|
127
|
+
*
|
|
128
|
+
* Use before `Auth.authenticate()` to defensively clear any leaked iOS
|
|
129
|
+
* coordinator state (the `SPTSessionManager` delegate callbacks are not
|
|
130
|
+
* guaranteed to fire).
|
|
131
|
+
*/
|
|
132
|
+
cancelPending() {
|
|
133
|
+
if (Platform.OS !== "ios") {
|
|
134
|
+
return Promise.resolve();
|
|
135
|
+
}
|
|
136
|
+
return ExpoSpotifySDKModule.cancelPendingAuthAsync();
|
|
137
|
+
},
|
|
138
|
+
/**
|
|
139
|
+
* Subscribes to session lifecycle events.
|
|
140
|
+
*
|
|
141
|
+
* Events fire for every `Auth.authenticate()` and `Auth.refresh()` call,
|
|
142
|
+
* regardless of whether the call was awaited. Useful for persisting tokens
|
|
143
|
+
* in a central store without coupling the store to the call sites.
|
|
144
|
+
*
|
|
145
|
+
* Returns a `Subscription` — call `.remove()` to unsubscribe.
|
|
146
|
+
*
|
|
147
|
+
* @example
|
|
148
|
+
* ```ts
|
|
149
|
+
* const sub = Auth.addListener("sessionChange", (event) => {
|
|
150
|
+
* if (event.type === "didInitiate" || event.type === "didRenew") {
|
|
151
|
+
* store.setSession(event.session);
|
|
152
|
+
* }
|
|
153
|
+
* });
|
|
154
|
+
* // later:
|
|
155
|
+
* sub.remove();
|
|
156
|
+
* ```
|
|
157
|
+
*/
|
|
158
|
+
addListener(event, listener) {
|
|
159
|
+
return ExpoSpotifySDKModule.addListener("onSessionChange", listener);
|
|
160
|
+
},
|
|
161
|
+
};
|
|
162
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/auth/index.ts"],"names":[],"mappings":"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","sourcesContent":["import { EventSubscription, Platform } from \"expo-modules-core\";\n\nimport ExpoSpotifySDKModule from \"../ExpoSpotifySDKModule\";\nimport { AuthError, AuthErrorCode } from \"./error\";\n\nexport type { AuthErrorCode } from \"./error\";\nexport { AuthError } from \"./error\";\n\n// ---------------------------------------------------------------------------\n// Auth-specific types\n// ---------------------------------------------------------------------------\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).\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\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\nexport interface AuthenticateConfig {\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 * `Auth.refresh()` on both platforms.\n */\n tokenRefreshURL?: string;\n /**\n * If `true`, forces Spotify to show the authorization dialog even when the\n * user already has an active session. Defaults to `false`.\n */\n showDialog?: boolean;\n}\n\nexport interface RefreshConfig {\n /** The refresh token from a previous `Auth.authenticate()` call. */\n refreshToken: string;\n /** URL of your token refresh server endpoint. */\n tokenRefreshURL: string;\n /**\n * Scopes that were granted by the previous session. Used as a fallback when\n * the refresh response omits the `scope` field.\n */\n scopes?: SpotifyScope[];\n}\n\n/**\n * Payload delivered to `Auth.addListener(\"sessionChange\", ...)` subscribers.\n */\nexport type SessionChangeEvent =\n | { type: \"didInitiate\"; session: SpotifySession }\n | { type: \"didRenew\"; session: SpotifySession }\n | { type: \"didFail\"; error: { code: AuthErrorCode; message: string } };\n\n// ---------------------------------------------------------------------------\n// Internal helpers\n// ---------------------------------------------------------------------------\n\nconst ANDROID_TOKEN_FLOW_WARNING =\n \"[expo-spotify-sdk] You are using Auth.authenticate 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\nconst VALID_AUTH_CODES = new Set<AuthErrorCode>([\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\nconst CAUSE_SEPARATOR = \"→ Caused by: \";\nconst LEGACY_CODE_PREFIX_RE = /^([A-Z_][A-Z0-9_]*):\\s*(.*)$/s;\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 rethrowAsAuthError(err: unknown): never {\n if (err instanceof AuthError) 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_AUTH_CODES.has(maybeCode as AuthErrorCode)) {\n throw new AuthError(maybeCode as AuthErrorCode, reason);\n }\n const m = reason.match(LEGACY_CODE_PREFIX_RE);\n if (m && VALID_AUTH_CODES.has(m[1] as AuthErrorCode)) {\n throw new AuthError(m[1] as AuthErrorCode, m[2]);\n }\n throw new AuthError(\"UNKNOWN\", reason);\n }\n throw new AuthError(\"UNKNOWN\", String(err));\n}\n\nfunction normaliseSession(raw: unknown): SpotifySession {\n if (!raw || typeof raw !== \"object\") {\n throw new AuthError(\"UNKNOWN\", \"Native module returned a non-object session\");\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 AuthError(\"UNKNOWN\", \"Session is missing accessToken\");\n }\n const expirationDate = r.expirationDate;\n if (typeof expirationDate !== \"number\") {\n throw new AuthError(\"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((s) => typeof s === \"string\") as SpotifyScope[])\n : [];\n return { accessToken, refreshToken, expirationDate, scopes };\n}\n\n// ---------------------------------------------------------------------------\n// Auth namespace\n// ---------------------------------------------------------------------------\n\n/**\n * Spotify Auth namespace. Handles OAuth authentication and session lifecycle.\n *\n * @example\n * ```ts\n * import { Auth } from \"@wwdrew/expo-spotify-sdk\";\n *\n * const session = await Auth.authenticate({ scopes: [\"streaming\"] });\n * ```\n */\nexport const Auth = {\n /**\n * Returns `true` if the Spotify app is installed on the device.\n */\n isAvailable(): boolean {\n return ExpoSpotifySDKModule.isAvailable();\n },\n\n /**\n * Starts a Spotify OAuth flow. Resolves with a {@link SpotifySession};\n * rejects with an {@link AuthError} carrying a `code`.\n */\n authenticate(config: AuthenticateConfig): Promise<SpotifySession> {\n if (!config.scopes || config.scopes.length === 0) {\n return Promise.reject(\n new AuthError(\"INVALID_CONFIG\", \"scopes must contain at least one entry\"),\n );\n }\n if (\n Platform.OS === \"android\" &&\n !config.tokenSwapURL &&\n !warnedAboutAndroidTokenFlow\n ) {\n warnedAboutAndroidTokenFlow = true;\n console.warn(ANDROID_TOKEN_FLOW_WARNING);\n }\n return ExpoSpotifySDKModule.authenticateAsync(config)\n .then(normaliseSession)\n .catch(rethrowAsAuthError);\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 an\n * {@link AuthError}.\n */\n refresh(config: RefreshConfig): Promise<SpotifySession> {\n if (!config.refreshToken) {\n return Promise.reject(\n new AuthError(\"INVALID_CONFIG\", \"refreshToken is required\"),\n );\n }\n if (!config.tokenRefreshURL) {\n return Promise.reject(\n new AuthError(\"INVALID_CONFIG\", \"tokenRefreshURL is required\"),\n );\n }\n return ExpoSpotifySDKModule.refreshSessionAsync(config)\n .then(normaliseSession)\n .catch(rethrowAsAuthError);\n },\n\n /**\n * Forcibly cancel any in-flight `Auth.authenticate()` call. No-op on\n * Android (the Android coordinator self-cleans via structured concurrency).\n *\n * Use before `Auth.authenticate()` to defensively clear any leaked iOS\n * coordinator state (the `SPTSessionManager` delegate callbacks are not\n * guaranteed to fire).\n */\n cancelPending(): Promise<void> {\n if (Platform.OS !== \"ios\") {\n return Promise.resolve();\n }\n return ExpoSpotifySDKModule.cancelPendingAuthAsync();\n },\n\n /**\n * Subscribes to session lifecycle events.\n *\n * Events fire for every `Auth.authenticate()` and `Auth.refresh()` call,\n * regardless of whether the call was awaited. Useful for persisting tokens\n * in a central store without coupling the store to the call sites.\n *\n * Returns a `Subscription` — call `.remove()` to unsubscribe.\n *\n * @example\n * ```ts\n * const sub = Auth.addListener(\"sessionChange\", (event) => {\n * if (event.type === \"didInitiate\" || event.type === \"didRenew\") {\n * store.setSession(event.session);\n * }\n * });\n * // later:\n * sub.remove();\n * ```\n */\n addListener(\n event: \"sessionChange\",\n listener: (event: SessionChangeEvent) => void,\n ): EventSubscription {\n return ExpoSpotifySDKModule.addListener(\n \"onSessionChange\",\n listener,\n ) as EventSubscription;\n },\n} as const;\n"]}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { SpotifyError } from "../error";
|
|
2
|
+
export type ContentErrorCode = "NOT_CONNECTED" | "CONNECTION_LOST" | "CONTENT_API_UNAVAILABLE" | "UNKNOWN";
|
|
3
|
+
export declare class ContentError extends SpotifyError {
|
|
4
|
+
readonly namespace: "Content";
|
|
5
|
+
readonly code: ContentErrorCode;
|
|
6
|
+
constructor(code: ContentErrorCode, message: string);
|
|
7
|
+
}
|
|
8
|
+
//# sourceMappingURL=error.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"error.d.ts","sourceRoot":"","sources":["../../src/content/error.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAExC,MAAM,MAAM,gBAAgB,GACxB,eAAe,GACf,iBAAiB,GACjB,yBAAyB,GACzB,SAAS,CAAC;AAEd,qBAAa,YAAa,SAAQ,YAAY;IAC5C,QAAQ,CAAC,SAAS,EAAG,SAAS,CAAU;IACxC,QAAQ,CAAC,IAAI,EAAE,gBAAgB,CAAC;gBAEpB,IAAI,EAAE,gBAAgB,EAAE,OAAO,EAAE,MAAM;CAIpD"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"error.js","sourceRoot":"","sources":["../../src/content/error.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAQxC,MAAM,OAAO,YAAa,SAAQ,YAAY;IACnC,SAAS,GAAG,SAAkB,CAAC;IAC/B,IAAI,CAAmB;IAEhC,YAAY,IAAsB,EAAE,OAAe;QACjD,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;IACnB,CAAC;CACF","sourcesContent":["import { SpotifyError } from \"../error\";\n\nexport type ContentErrorCode =\n | \"NOT_CONNECTED\"\n | \"CONNECTION_LOST\"\n | \"CONTENT_API_UNAVAILABLE\"\n | \"UNKNOWN\";\n\nexport class ContentError extends SpotifyError {\n readonly namespace = \"Content\" as const;\n readonly code: ContentErrorCode;\n\n constructor(code: ContentErrorCode, message: string) {\n super(message);\n this.code = code;\n }\n}\n"]}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
export type { ContentErrorCode } from "./error";
|
|
2
|
+
export { ContentError } from "./error";
|
|
3
|
+
export type ContentType = "default" | "navigation" | "fitness" | "gaming";
|
|
4
|
+
export interface ContentItem {
|
|
5
|
+
title: string | null;
|
|
6
|
+
subtitle: string | null;
|
|
7
|
+
contentDescription: string | null;
|
|
8
|
+
identifier: string;
|
|
9
|
+
uri: string;
|
|
10
|
+
imageIdentifier: string | null;
|
|
11
|
+
isAvailableOffline: boolean;
|
|
12
|
+
isPlayable: boolean;
|
|
13
|
+
isContainer: boolean;
|
|
14
|
+
isPinned: boolean;
|
|
15
|
+
children?: ContentItem[];
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Spotify Content namespace. Browse Spotify's curated content tree.
|
|
19
|
+
* Requires `AppRemote.connect()` to be resolved before any call.
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* ```ts
|
|
23
|
+
* import { Content } from "@wwdrew/expo-spotify-sdk";
|
|
24
|
+
*
|
|
25
|
+
* const items = await Content.getRecommendedContentItems("default");
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
export declare const Content: {
|
|
29
|
+
/**
|
|
30
|
+
* Returns Spotify-curated recommended content for the given feed type.
|
|
31
|
+
*/
|
|
32
|
+
readonly getRecommendedContentItems: (type: ContentType) => Promise<ContentItem[]>;
|
|
33
|
+
/**
|
|
34
|
+
* Returns children of a browsable (container) content item.
|
|
35
|
+
*/
|
|
36
|
+
readonly getChildren: (item: ContentItem) => Promise<ContentItem[]>;
|
|
37
|
+
};
|
|
38
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/content/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAChD,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AASvC,MAAM,MAAM,WAAW,GAAG,SAAS,GAAG,YAAY,GAAG,SAAS,GAAG,QAAQ,CAAC;AAE1E,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,kBAAkB,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,UAAU,EAAE,MAAM,CAAC;IACnB,GAAG,EAAE,MAAM,CAAC;IACZ,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,kBAAkB,EAAE,OAAO,CAAC;IAC5B,UAAU,EAAE,OAAO,CAAC;IACpB,WAAW,EAAE,OAAO,CAAC;IACrB,QAAQ,EAAE,OAAO,CAAC;IAClB,QAAQ,CAAC,EAAE,WAAW,EAAE,CAAC;CAC1B;AA6BD;;;;;;;;;;GAUG;AACH,eAAO,MAAM,OAAO;IAClB;;OAEG;gDAC8B,WAAW,KAAG,OAAO,CAAC,WAAW,EAAE,CAAC;IAMrE;;OAEG;iCACe,WAAW,KAAG,OAAO,CAAC,WAAW,EAAE,CAAC;CAG9C,CAAC"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
export { ContentError } from "./error";
|
|
2
|
+
// ---------------------------------------------------------------------------
|
|
3
|
+
// Types
|
|
4
|
+
// ---------------------------------------------------------------------------
|
|
5
|
+
import ExpoSpotifySDKModule from "../ExpoSpotifySDKModule";
|
|
6
|
+
import { ContentError } from "./error";
|
|
7
|
+
const VALID_CONTENT_CODES = new Set([
|
|
8
|
+
"NOT_CONNECTED",
|
|
9
|
+
"CONNECTION_LOST",
|
|
10
|
+
"CONTENT_API_UNAVAILABLE",
|
|
11
|
+
"UNKNOWN",
|
|
12
|
+
]);
|
|
13
|
+
const CAUSE_SEPARATOR = "→ Caused by: ";
|
|
14
|
+
function unwrapReason(message) {
|
|
15
|
+
const idx = message.lastIndexOf(CAUSE_SEPARATOR);
|
|
16
|
+
return idx === -1 ? message : message.slice(idx + CAUSE_SEPARATOR.length);
|
|
17
|
+
}
|
|
18
|
+
function rethrowAsContentError(err) {
|
|
19
|
+
if (err instanceof ContentError)
|
|
20
|
+
throw err;
|
|
21
|
+
if (err instanceof Error) {
|
|
22
|
+
const reason = unwrapReason(err.message);
|
|
23
|
+
const maybeCode = err.code;
|
|
24
|
+
if (maybeCode && VALID_CONTENT_CODES.has(maybeCode)) {
|
|
25
|
+
throw new ContentError(maybeCode, reason);
|
|
26
|
+
}
|
|
27
|
+
throw new ContentError("UNKNOWN", reason);
|
|
28
|
+
}
|
|
29
|
+
throw new ContentError("UNKNOWN", String(err));
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Spotify Content namespace. Browse Spotify's curated content tree.
|
|
33
|
+
* Requires `AppRemote.connect()` to be resolved before any call.
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* ```ts
|
|
37
|
+
* import { Content } from "@wwdrew/expo-spotify-sdk";
|
|
38
|
+
*
|
|
39
|
+
* const items = await Content.getRecommendedContentItems("default");
|
|
40
|
+
* ```
|
|
41
|
+
*/
|
|
42
|
+
export const Content = {
|
|
43
|
+
/**
|
|
44
|
+
* Returns Spotify-curated recommended content for the given feed type.
|
|
45
|
+
*/
|
|
46
|
+
getRecommendedContentItems(type) {
|
|
47
|
+
return ExpoSpotifySDKModule.contentGetRecommendedContentItems(type).catch(rethrowAsContentError);
|
|
48
|
+
},
|
|
49
|
+
/**
|
|
50
|
+
* Returns children of a browsable (container) content item.
|
|
51
|
+
*/
|
|
52
|
+
getChildren(item) {
|
|
53
|
+
return ExpoSpotifySDKModule.contentGetChildren(item).catch(rethrowAsContentError);
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/content/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAEvC,8EAA8E;AAC9E,QAAQ;AACR,8EAA8E;AAE9E,OAAO,oBAAoB,MAAM,yBAAyB,CAAC;AAC3D,OAAO,EAAE,YAAY,EAAoB,MAAM,SAAS,CAAC;AAkBzD,MAAM,mBAAmB,GAAG,IAAI,GAAG,CAAmB;IACpD,eAAe;IACf,iBAAiB;IACjB,yBAAyB;IACzB,SAAS;CACV,CAAC,CAAC;AAEH,MAAM,eAAe,GAAG,eAAe,CAAC;AAExC,SAAS,YAAY,CAAC,OAAe;IACnC,MAAM,GAAG,GAAG,OAAO,CAAC,WAAW,CAAC,eAAe,CAAC,CAAC;IACjD,OAAO,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,GAAG,GAAG,eAAe,CAAC,MAAM,CAAC,CAAC;AAC5E,CAAC;AAED,SAAS,qBAAqB,CAAC,GAAY;IACzC,IAAI,GAAG,YAAY,YAAY;QAAE,MAAM,GAAG,CAAC;IAC3C,IAAI,GAAG,YAAY,KAAK,EAAE,CAAC;QACzB,MAAM,MAAM,GAAG,YAAY,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QACzC,MAAM,SAAS,GAAI,GAAiC,CAAC,IAAI,CAAC;QAC1D,IAAI,SAAS,IAAI,mBAAmB,CAAC,GAAG,CAAC,SAA6B,CAAC,EAAE,CAAC;YACxE,MAAM,IAAI,YAAY,CAAC,SAA6B,EAAE,MAAM,CAAC,CAAC;QAChE,CAAC;QACD,MAAM,IAAI,YAAY,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC;IAC5C,CAAC;IACD,MAAM,IAAI,YAAY,CAAC,SAAS,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;AACjD,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,CAAC,MAAM,OAAO,GAAG;IACrB;;OAEG;IACH,0BAA0B,CAAC,IAAiB;QAC1C,OAAO,oBAAoB,CAAC,iCAAiC,CAAC,IAAI,CAAC,CAAC,KAAK,CACvE,qBAAqB,CACtB,CAAC;IACJ,CAAC;IAED;;OAEG;IACH,WAAW,CAAC,IAAiB;QAC3B,OAAO,oBAAoB,CAAC,kBAAkB,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,qBAAqB,CAAC,CAAC;IACpF,CAAC;CACO,CAAC","sourcesContent":["export type { ContentErrorCode } from \"./error\";\nexport { ContentError } from \"./error\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nimport ExpoSpotifySDKModule from \"../ExpoSpotifySDKModule\";\nimport { ContentError, ContentErrorCode } from \"./error\";\n\nexport type ContentType = \"default\" | \"navigation\" | \"fitness\" | \"gaming\";\n\nexport interface ContentItem {\n title: string | null;\n subtitle: string | null;\n contentDescription: string | null;\n identifier: string;\n uri: string;\n imageIdentifier: string | null;\n isAvailableOffline: boolean;\n isPlayable: boolean;\n isContainer: boolean;\n isPinned: boolean;\n children?: ContentItem[];\n}\n\nconst VALID_CONTENT_CODES = new Set<ContentErrorCode>([\n \"NOT_CONNECTED\",\n \"CONNECTION_LOST\",\n \"CONTENT_API_UNAVAILABLE\",\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 rethrowAsContentError(err: unknown): never {\n if (err instanceof ContentError) 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_CONTENT_CODES.has(maybeCode as ContentErrorCode)) {\n throw new ContentError(maybeCode as ContentErrorCode, reason);\n }\n throw new ContentError(\"UNKNOWN\", reason);\n }\n throw new ContentError(\"UNKNOWN\", String(err));\n}\n\n/**\n * Spotify Content namespace. Browse Spotify's curated content tree.\n * Requires `AppRemote.connect()` to be resolved before any call.\n *\n * @example\n * ```ts\n * import { Content } from \"@wwdrew/expo-spotify-sdk\";\n *\n * const items = await Content.getRecommendedContentItems(\"default\");\n * ```\n */\nexport const Content = {\n /**\n * Returns Spotify-curated recommended content for the given feed type.\n */\n getRecommendedContentItems(type: ContentType): Promise<ContentItem[]> {\n return ExpoSpotifySDKModule.contentGetRecommendedContentItems(type).catch(\n rethrowAsContentError,\n );\n },\n\n /**\n * Returns children of a browsable (container) content item.\n */\n getChildren(item: ContentItem): Promise<ContentItem[]> {\n return ExpoSpotifySDKModule.contentGetChildren(item).catch(rethrowAsContentError);\n },\n} as const;\n"]}
|
package/build/error.d.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Abstract base class for every error thrown by this library. Carry a
|
|
3
|
+
* structured `code` and a `namespace` so catch-all handlers can branch
|
|
4
|
+
* without importing concrete subclasses.
|
|
5
|
+
*
|
|
6
|
+
* Always thrown as one of the per-namespace subclasses:
|
|
7
|
+
* `AuthError`, `AppRemoteError`, `PlayerError`, `UserError`,
|
|
8
|
+
* `ContentError`, `ImagesError`.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```ts
|
|
12
|
+
* try { ... } catch (e) {
|
|
13
|
+
* if (e instanceof SpotifyError) {
|
|
14
|
+
* console.error(e.namespace, e.code, e.message);
|
|
15
|
+
* }
|
|
16
|
+
* }
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
export declare abstract class SpotifyError extends Error {
|
|
20
|
+
abstract readonly code: string;
|
|
21
|
+
abstract readonly namespace: string;
|
|
22
|
+
constructor(message: string);
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=error.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"error.d.ts","sourceRoot":"","sources":["../src/error.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,8BAAsB,YAAa,SAAQ,KAAK;IAC9C,QAAQ,CAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;gBAExB,OAAO,EAAE,MAAM;CAI5B"}
|
package/build/error.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Abstract base class for every error thrown by this library. Carry a
|
|
3
|
+
* structured `code` and a `namespace` so catch-all handlers can branch
|
|
4
|
+
* without importing concrete subclasses.
|
|
5
|
+
*
|
|
6
|
+
* Always thrown as one of the per-namespace subclasses:
|
|
7
|
+
* `AuthError`, `AppRemoteError`, `PlayerError`, `UserError`,
|
|
8
|
+
* `ContentError`, `ImagesError`.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```ts
|
|
12
|
+
* try { ... } catch (e) {
|
|
13
|
+
* if (e instanceof SpotifyError) {
|
|
14
|
+
* console.error(e.namespace, e.code, e.message);
|
|
15
|
+
* }
|
|
16
|
+
* }
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
export class SpotifyError extends Error {
|
|
20
|
+
constructor(message) {
|
|
21
|
+
super(message);
|
|
22
|
+
this.name = this.constructor.name;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=error.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"error.js","sourceRoot":"","sources":["../src/error.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,OAAgB,YAAa,SAAQ,KAAK;IAI9C,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC;IACpC,CAAC;CACF","sourcesContent":["/**\n * Abstract base class for every error thrown by this library. Carry a\n * structured `code` and a `namespace` so catch-all handlers can branch\n * without importing concrete subclasses.\n *\n * Always thrown as one of the per-namespace subclasses:\n * `AuthError`, `AppRemoteError`, `PlayerError`, `UserError`,\n * `ContentError`, `ImagesError`.\n *\n * @example\n * ```ts\n * try { ... } catch (e) {\n * if (e instanceof SpotifyError) {\n * console.error(e.namespace, e.code, e.message);\n * }\n * }\n * ```\n */\nexport abstract class SpotifyError extends Error {\n abstract readonly code: string;\n abstract readonly namespace: string;\n\n constructor(message: string) {\n super(message);\n this.name = this.constructor.name;\n }\n}\n"]}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { ConnectionState } from "../app-remote";
|
|
2
|
+
import { SpotifySession } from "../auth";
|
|
3
|
+
import { PlayerState, Track } from "../player";
|
|
4
|
+
import { Capabilities, LibraryState } from "../user";
|
|
5
|
+
import type { SpotifyURI as SpotifyURIType } from "../uri";
|
|
6
|
+
/**
|
|
7
|
+
* Returns the current Spotify OAuth session, or `null` when not authenticated.
|
|
8
|
+
* Updates automatically whenever `Auth.authenticate()` or `Auth.refresh()`
|
|
9
|
+
* resolves, and on session failure.
|
|
10
|
+
*
|
|
11
|
+
* Built on `useSyncExternalStore` for tearing-free React rendering.
|
|
12
|
+
*/
|
|
13
|
+
export declare function useSession(): SpotifySession | null;
|
|
14
|
+
/**
|
|
15
|
+
* Returns the current App Remote {@link ConnectionState}
|
|
16
|
+
* (`"disconnected"` | `"connecting"` | `"connected"`). Updates automatically
|
|
17
|
+
* on every state transition driven by `AppRemote.connect()` and `disconnect()`.
|
|
18
|
+
*
|
|
19
|
+
* Built on `useSyncExternalStore` for tearing-free React rendering.
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* ```tsx
|
|
23
|
+
* function ConnectionBanner() {
|
|
24
|
+
* const state = useConnectionState();
|
|
25
|
+
* return <Text>{state === "connected" ? "Connected" : "Disconnected"}</Text>;
|
|
26
|
+
* }
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
export declare function useConnectionState(): ConnectionState;
|
|
30
|
+
/**
|
|
31
|
+
* Returns the latest {@link PlayerState} from the Spotify app, or `null`
|
|
32
|
+
* before the first update arrives (i.e., before `AppRemote.connect()` resolves
|
|
33
|
+
* and the native subscription emits its first event).
|
|
34
|
+
*
|
|
35
|
+
* Updates on every state change reported by the Spotify app (track change,
|
|
36
|
+
* pause/resume, seek, shuffle/repeat toggle, etc.).
|
|
37
|
+
*
|
|
38
|
+
* Built on `useSyncExternalStore` for tearing-free React rendering.
|
|
39
|
+
*
|
|
40
|
+
* @example
|
|
41
|
+
* ```tsx
|
|
42
|
+
* function NowPlaying() {
|
|
43
|
+
* const state = usePlayerState();
|
|
44
|
+
* if (!state) return <Text>Not playing</Text>;
|
|
45
|
+
* return <Text>{state.track.name} — {state.isPaused ? "Paused" : "Playing"}</Text>;
|
|
46
|
+
* }
|
|
47
|
+
* ```
|
|
48
|
+
*/
|
|
49
|
+
export declare function usePlayerState(): PlayerState | null;
|
|
50
|
+
/**
|
|
51
|
+
* Returns the currently playing {@link Track}, or `null` when nothing is
|
|
52
|
+
* playing or before the first state update arrives.
|
|
53
|
+
*
|
|
54
|
+
* Derived from `usePlayerState`.
|
|
55
|
+
*/
|
|
56
|
+
export declare function useCurrentTrack(): Track | null;
|
|
57
|
+
/**
|
|
58
|
+
* Returns `true` when the Spotify player is actively playing (not paused),
|
|
59
|
+
* and `false` otherwise (including before the first state update arrives).
|
|
60
|
+
*
|
|
61
|
+
* Derived from `usePlayerState`.
|
|
62
|
+
*/
|
|
63
|
+
export declare function useIsPlaying(): boolean;
|
|
64
|
+
/**
|
|
65
|
+
* Returns the current playback position in milliseconds. Returns `0` before
|
|
66
|
+
* the first state update arrives.
|
|
67
|
+
*
|
|
68
|
+
* **Note:** This value updates whenever the native side emits a player state
|
|
69
|
+
* change (track transitions, pause/resume, seek, etc.) — not on a fixed timer.
|
|
70
|
+
* For a progress bar that ticks smoothly, combine this with a local `Date.now`
|
|
71
|
+
* offset and `requestAnimationFrame`.
|
|
72
|
+
*
|
|
73
|
+
* Derived from `usePlayerState`.
|
|
74
|
+
*/
|
|
75
|
+
export declare function usePlaybackPosition(): number;
|
|
76
|
+
/**
|
|
77
|
+
* Returns the latest Spotify user capabilities, or `null` before the first
|
|
78
|
+
* snapshot arrives.
|
|
79
|
+
*
|
|
80
|
+
* Derived from `User.getCapabilities()` + `User.addListener("capabilitiesChange")`.
|
|
81
|
+
*/
|
|
82
|
+
export declare function useCapabilities(): Capabilities | null;
|
|
83
|
+
/**
|
|
84
|
+
* Returns the library state for a specific URI, or `null` before the first
|
|
85
|
+
* snapshot arrives.
|
|
86
|
+
*
|
|
87
|
+
* Derived from `User.getLibraryState(uri)` + `User.addLibraryStateListener(uri, ...)`.
|
|
88
|
+
*/
|
|
89
|
+
export declare function useLibraryState(uri: SpotifyURIType): LibraryState | null;
|
|
90
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/hooks/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAa,eAAe,EAAE,MAAM,eAAe,CAAC;AAC3D,OAAO,EAAQ,cAAc,EAAE,MAAM,SAAS,CAAC;AAC/C,OAAO,EAAU,WAAW,EAAE,KAAK,EAAE,MAAM,WAAW,CAAC;AACvD,OAAO,EAAE,YAAY,EAAE,YAAY,EAAQ,MAAM,SAAS,CAAC;AAC3D,OAAO,KAAK,EAAE,UAAU,IAAI,cAAc,EAAE,MAAM,QAAQ,CAAC;AAmR3D;;;;;;GAMG;AACH,wBAAgB,UAAU,IAAI,cAAc,GAAG,IAAI,CAElD;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,kBAAkB,IAAI,eAAe,CAMpD;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,cAAc,IAAI,WAAW,GAAG,IAAI,CAEnD;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,IAAI,KAAK,GAAG,IAAI,CAE9C;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,IAAI,OAAO,CAGtC;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,mBAAmB,IAAI,MAAM,CAE5C;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,IAAI,YAAY,GAAG,IAAI,CAMrD;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,cAAc,GAAG,YAAY,GAAG,IAAI,CAMxE"}
|