@wwdrew/expo-spotify-sdk 0.8.0 → 2.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 +404 -140
- package/android/build.gradle +12 -2
- 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/app.plugin.js +1 -1
- 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 +137 -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/ExpoSpotifySDK.podspec +1 -1
- 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 +29 -16
- package/plugin/build/index.d.ts +20 -3
- package/plugin/build/index.js +22 -25
- package/plugin/build/types.d.ts +17 -1
- package/plugin/build/withSpotifySdkConfig.d.ts +4 -0
- package/plugin/build/withSpotifySdkConfig.js +27 -0
package/build/index.js
CHANGED
|
@@ -1,147 +1,55 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// Hooks (v1 public API)
|
|
3
|
+
// ---------------------------------------------------------------------------
|
|
4
|
+
export { useSession, useConnectionState, usePlayerState, useCurrentTrack, useIsPlaying, usePlaybackPosition, useCapabilities, useLibraryState, } from "./hooks";
|
|
5
|
+
// ---------------------------------------------------------------------------
|
|
6
|
+
// Namespaces (v1 public API)
|
|
7
|
+
// ---------------------------------------------------------------------------
|
|
8
|
+
export { Auth } from "./auth";
|
|
9
|
+
export { AppRemote } from "./app-remote";
|
|
10
|
+
export { Player } from "./player";
|
|
11
|
+
export { User } from "./user";
|
|
12
|
+
export { Content } from "./content";
|
|
13
|
+
export { Images } from "./images";
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
// Error hierarchy
|
|
16
|
+
// ---------------------------------------------------------------------------
|
|
17
|
+
export { SpotifyError } from "./error";
|
|
18
|
+
export { AuthError } from "./auth";
|
|
19
|
+
export { AppRemoteError } from "./app-remote";
|
|
20
|
+
export { PlayerError } from "./player";
|
|
21
|
+
export { UserError } from "./user";
|
|
22
|
+
export { ContentError } from "./content";
|
|
23
|
+
export { ImagesError } from "./images";
|
|
24
|
+
// ---------------------------------------------------------------------------
|
|
25
|
+
// URI helpers
|
|
26
|
+
// ---------------------------------------------------------------------------
|
|
27
|
+
export { SpotifyURI } from "./uri";
|
|
28
|
+
// ---------------------------------------------------------------------------
|
|
29
|
+
// v0.x backward-compatible exports (deprecated — remove at v2.0.0)
|
|
30
|
+
//
|
|
31
|
+
// These shims let existing callers continue to compile after upgrading to v1.
|
|
32
|
+
// Migrate to the namespaced API: see docs/V1_PLAN.md §8 (Migration).
|
|
33
|
+
// ---------------------------------------------------------------------------
|
|
34
|
+
import { Auth } from "./auth";
|
|
35
|
+
/** @deprecated Use `Auth.isAvailable()` */
|
|
36
|
+
export function isAvailable() {
|
|
37
|
+
return Auth.isAvailable();
|
|
15
38
|
}
|
|
16
|
-
/**
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
*/
|
|
20
|
-
function authenticateAsync(config) {
|
|
21
|
-
if (!config.scopes || config.scopes.length === 0) {
|
|
22
|
-
return Promise.reject(new SpotifyError("INVALID_CONFIG", "scopes must contain at least one entry"));
|
|
23
|
-
}
|
|
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);
|
|
39
|
+
/** @deprecated Use `Auth.authenticate(config)` */
|
|
40
|
+
export function authenticateAsync(config) {
|
|
41
|
+
return Auth.authenticate(config);
|
|
33
42
|
}
|
|
34
|
-
/**
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
* nothing is in flight, and a no-op on Android (the Android coordinator
|
|
38
|
-
* self-cleans via structured concurrency).
|
|
39
|
-
*
|
|
40
|
-
* Recovery hatch for the iOS coordinator's stuck-state class of bugs: the
|
|
41
|
-
* SPTSessionManager delegate callbacks are not guaranteed to fire — e.g. when
|
|
42
|
-
* Spotify never redirects back to the host app — leaving the coordinator's
|
|
43
|
-
* `pending` continuation set forever and every subsequent `authenticateAsync`
|
|
44
|
-
* rejecting with `AUTH_IN_PROGRESS` until the process restarts. Call this
|
|
45
|
-
* before `authenticateAsync` to defensively clear any leaked state.
|
|
46
|
-
*/
|
|
47
|
-
function cancelPendingAuthAsync() {
|
|
48
|
-
if (Platform.OS !== "ios") {
|
|
49
|
-
return Promise.resolve();
|
|
50
|
-
}
|
|
51
|
-
return ExpoSpotifySDKModule.cancelPendingAuthAsync();
|
|
43
|
+
/** @deprecated Use `Auth.cancelPending()` */
|
|
44
|
+
export function cancelPendingAuthAsync() {
|
|
45
|
+
return Auth.cancelPending();
|
|
52
46
|
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
}
|
|
57
|
-
const r = raw;
|
|
58
|
-
const accessToken = r.accessToken;
|
|
59
|
-
if (typeof accessToken !== "string" || accessToken.length === 0) {
|
|
60
|
-
throw new SpotifyError("UNKNOWN", "Session is missing accessToken");
|
|
61
|
-
}
|
|
62
|
-
const expirationDate = r.expirationDate;
|
|
63
|
-
if (typeof expirationDate !== "number") {
|
|
64
|
-
throw new SpotifyError("UNKNOWN", "Session is missing expirationDate");
|
|
65
|
-
}
|
|
66
|
-
const refreshTokenRaw = r.refreshToken;
|
|
67
|
-
const refreshToken = typeof refreshTokenRaw === "string" && refreshTokenRaw.length > 0
|
|
68
|
-
? refreshTokenRaw
|
|
69
|
-
: null;
|
|
70
|
-
const scopesRaw = r.scopes;
|
|
71
|
-
const scopes = Array.isArray(scopesRaw)
|
|
72
|
-
? scopesRaw.filter((s) => typeof s === "string")
|
|
73
|
-
: [];
|
|
74
|
-
return { accessToken, refreshToken, expirationDate, scopes };
|
|
47
|
+
/** @deprecated Use `Auth.refresh(config)` */
|
|
48
|
+
export function refreshSessionAsync(config) {
|
|
49
|
+
return Auth.refresh(config);
|
|
75
50
|
}
|
|
76
|
-
/**
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
* {@link SpotifyError}.
|
|
80
|
-
*/
|
|
81
|
-
function refreshSessionAsync(config) {
|
|
82
|
-
if (!config.refreshToken) {
|
|
83
|
-
return Promise.reject(new SpotifyError("INVALID_CONFIG", "refreshToken is required"));
|
|
84
|
-
}
|
|
85
|
-
if (!config.tokenRefreshURL) {
|
|
86
|
-
return Promise.reject(new SpotifyError("INVALID_CONFIG", "tokenRefreshURL is required"));
|
|
87
|
-
}
|
|
88
|
-
return ExpoSpotifySDKModule.refreshSessionAsync(config)
|
|
89
|
-
.then(normaliseSession)
|
|
90
|
-
.catch(rethrowAsSpotifyError);
|
|
51
|
+
/** @deprecated Use `Auth.addListener("sessionChange", cb)` */
|
|
52
|
+
export function addSessionChangeListener(listener) {
|
|
53
|
+
return Auth.addListener("sessionChange", listener);
|
|
91
54
|
}
|
|
92
|
-
/**
|
|
93
|
-
* Subscribes to session lifecycle events emitted by the native module.
|
|
94
|
-
*
|
|
95
|
-
* Events are fired for every `authenticateAsync` and `refreshSessionAsync`
|
|
96
|
-
* call, regardless of whether the call was awaited. Useful for persisting
|
|
97
|
-
* tokens in a central store without coupling the store to the call sites.
|
|
98
|
-
*
|
|
99
|
-
* Returns a {@link Subscription} — call `.remove()` to unsubscribe.
|
|
100
|
-
*
|
|
101
|
-
* @example
|
|
102
|
-
* ```ts
|
|
103
|
-
* const sub = addSessionChangeListener((event) => {
|
|
104
|
-
* if (event.type === "didInitiate" || event.type === "didRenew") {
|
|
105
|
-
* store.setSession(event.session);
|
|
106
|
-
* }
|
|
107
|
-
* });
|
|
108
|
-
* // later:
|
|
109
|
-
* sub.remove();
|
|
110
|
-
* ```
|
|
111
|
-
*/
|
|
112
|
-
function addSessionChangeListener(listener) {
|
|
113
|
-
return ExpoSpotifySDKModule.addListener("onSessionChange", listener);
|
|
114
|
-
}
|
|
115
|
-
const ERROR_PREFIX_RE = /^([A-Z_][A-Z0-9_]*):\s*(.*)$/s;
|
|
116
|
-
const VALID_CODES = new Set([
|
|
117
|
-
"USER_CANCELLED",
|
|
118
|
-
"AUTH_IN_PROGRESS",
|
|
119
|
-
"INVALID_CONFIG",
|
|
120
|
-
"NETWORK_ERROR",
|
|
121
|
-
"TOKEN_SWAP_FAILED",
|
|
122
|
-
"TOKEN_SWAP_PARSE_ERROR",
|
|
123
|
-
"SPOTIFY_NOT_INSTALLED",
|
|
124
|
-
"AUTH_ERROR",
|
|
125
|
-
"UNKNOWN",
|
|
126
|
-
]);
|
|
127
|
-
function rethrowAsSpotifyError(err) {
|
|
128
|
-
if (err instanceof SpotifyError)
|
|
129
|
-
throw err;
|
|
130
|
-
if (err instanceof Error) {
|
|
131
|
-
const m = err.message.match(ERROR_PREFIX_RE);
|
|
132
|
-
if (m && VALID_CODES.has(m[1])) {
|
|
133
|
-
throw new SpotifyError(m[1], m[2]);
|
|
134
|
-
}
|
|
135
|
-
const maybeCode = err.code;
|
|
136
|
-
if (maybeCode && VALID_CODES.has(maybeCode)) {
|
|
137
|
-
throw new SpotifyError(maybeCode, err.message);
|
|
138
|
-
}
|
|
139
|
-
throw new SpotifyError("UNKNOWN", err.message);
|
|
140
|
-
}
|
|
141
|
-
throw new SpotifyError("UNKNOWN", String(err));
|
|
142
|
-
}
|
|
143
|
-
const Authenticate = {
|
|
144
|
-
authenticateAsync,
|
|
145
|
-
};
|
|
146
|
-
export { isAvailable, authenticateAsync, cancelPendingAuthAsync, refreshSessionAsync, addSessionChangeListener, Authenticate, SpotifyError, };
|
|
147
55
|
//# 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":"AAAA,OAAO,EAAqB,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAEhE,OAAO,EAEL,YAAY,GAKb,MAAM,wBAAwB,CAAC;AAChC,OAAO,oBAAoB,MAAM,wBAAwB,CAAC;AAE1D,MAAM,0BAA0B,GAC9B,0EAA0E;IAC1E,2EAA2E;IAC3E,gEAAgE;IAChE,6DAA6D,CAAC;AAEhE,IAAI,2BAA2B,GAAG,KAAK,CAAC;AAExC;;;GAGG;AACH,SAAS,WAAW;IAClB,OAAO,oBAAoB,CAAC,WAAW,EAAE,CAAC;AAC5C,CAAC;AAED;;;GAGG;AACH,SAAS,iBAAiB,CAAC,MAAqB;IAC9C,IAAI,CAAC,MAAM,CAAC,MAAM,IAAI,MAAM,CAAC,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACjD,OAAO,OAAO,CAAC,MAAM,CACnB,IAAI,YAAY,CACd,gBAAgB,EAChB,wCAAwC,CACzC,CACF,CAAC;IACJ,CAAC;IACD,IACE,QAAQ,CAAC,EAAE,KAAK,SAAS;QACzB,CAAC,MAAM,CAAC,YAAY;QACpB,CAAC,2BAA2B,EAC5B,CAAC;QACD,2BAA2B,GAAG,IAAI,CAAC;QAEnC,OAAO,CAAC,IAAI,CAAC,0BAA0B,CAAC,CAAC;IAC3C,CAAC;IACD,OAAO,oBAAoB,CAAC,iBAAiB,CAAC,MAAM,CAAC;SAClD,IAAI,CAAC,gBAAgB,CAAC;SACtB,KAAK,CAAC,qBAAqB,CAAC,CAAC;AAClC,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,SAAS,sBAAsB;IAC7B,IAAI,QAAQ,CAAC,EAAE,KAAK,KAAK,EAAE,CAAC;QAC1B,OAAO,OAAO,CAAC,OAAO,EAAE,CAAC;IAC3B,CAAC;IACD,OAAO,oBAAoB,CAAC,sBAAsB,EAAE,CAAC;AACvD,CAAC;AAED,SAAS,gBAAgB,CAAC,GAAY;IACpC,IAAI,CAAC,GAAG,IAAI,OAAO,GAAG,KAAK,QAAQ,EAAE,CAAC;QACpC,MAAM,IAAI,YAAY,CACpB,SAAS,EACT,6CAA6C,CAC9C,CAAC;IACJ,CAAC;IACD,MAAM,CAAC,GAAG,GAA8B,CAAC;IACzC,MAAM,WAAW,GAAG,CAAC,CAAC,WAAW,CAAC;IAClC,IAAI,OAAO,WAAW,KAAK,QAAQ,IAAI,WAAW,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAChE,MAAM,IAAI,YAAY,CAAC,SAAS,EAAE,gCAAgC,CAAC,CAAC;IACtE,CAAC;IACD,MAAM,cAAc,GAAG,CAAC,CAAC,cAAc,CAAC;IACxC,IAAI,OAAO,cAAc,KAAK,QAAQ,EAAE,CAAC;QACvC,MAAM,IAAI,YAAY,CAAC,SAAS,EAAE,mCAAmC,CAAC,CAAC;IACzE,CAAC;IACD,MAAM,eAAe,GAAG,CAAC,CAAC,YAAY,CAAC;IACvC,MAAM,YAAY,GAChB,OAAO,eAAe,KAAK,QAAQ,IAAI,eAAe,CAAC,MAAM,GAAG,CAAC;QAC/D,CAAC,CAAC,eAAe;QACjB,CAAC,CAAC,IAAI,CAAC;IACX,MAAM,SAAS,GAAG,CAAC,CAAC,MAAM,CAAC;IAC3B,MAAM,MAAM,GAAG,KAAK,CAAC,OAAO,CAAC,SAAS,CAAC;QACrC,CAAC,CAAE,SAAS,CAAC,MAAM,CACf,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,QAAQ,CACA;QAChC,CAAC,CAAC,EAAE,CAAC;IACP,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,EAAE,CAAC;AAC/D,CAAC;AAED;;;;GAIG;AACH,SAAS,mBAAmB,CAC1B,MAA4B;IAE5B,IAAI,CAAC,MAAM,CAAC,YAAY,EAAE,CAAC;QACzB,OAAO,OAAO,CAAC,MAAM,CACnB,IAAI,YAAY,CAAC,gBAAgB,EAAE,0BAA0B,CAAC,CAC/D,CAAC;IACJ,CAAC;IACD,IAAI,CAAC,MAAM,CAAC,eAAe,EAAE,CAAC;QAC5B,OAAO,OAAO,CAAC,MAAM,CACnB,IAAI,YAAY,CAAC,gBAAgB,EAAE,6BAA6B,CAAC,CAClE,CAAC;IACJ,CAAC;IACD,OAAO,oBAAoB,CAAC,mBAAmB,CAAC,MAAM,CAAC;SACpD,IAAI,CAAC,gBAAgB,CAAC;SACtB,KAAK,CAAC,qBAAqB,CAAC,CAAC;AAClC,CAAC;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,SAAS,wBAAwB,CAC/B,QAAoD;IAEpD,OAAO,oBAAoB,CAAC,WAAW,CACrC,iBAAiB,EACjB,QAAQ,CACY,CAAC;AACzB,CAAC;AAED,MAAM,eAAe,GAAG,+BAA+B,CAAC;AAExD,MAAM,WAAW,GAAkC,IAAI,GAAG,CAAmB;IAC3E,gBAAgB;IAChB,kBAAkB;IAClB,gBAAgB;IAChB,eAAe;IACf,mBAAmB;IACnB,wBAAwB;IACxB,uBAAuB;IACvB,YAAY;IACZ,SAAS;CACV,CAAC,CAAC;AAEH,SAAS,qBAAqB,CAAC,GAAY;IACzC,IAAI,GAAG,YAAY,YAAY;QAAE,MAAM,GAAG,CAAC;IAC3C,IAAI,GAAG,YAAY,KAAK,EAAE,CAAC;QACzB,MAAM,CAAC,GAAG,GAAG,CAAC,OAAO,CAAC,KAAK,CAAC,eAAe,CAAC,CAAC;QAC7C,IAAI,CAAC,IAAI,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAqB,CAAC,EAAE,CAAC;YACnD,MAAM,IAAI,YAAY,CAAC,CAAC,CAAC,CAAC,CAAqB,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QACzD,CAAC;QACD,MAAM,SAAS,GAAI,GAAiC,CAAC,IAAI,CAAC;QAC1D,IAAI,SAAS,IAAI,WAAW,CAAC,GAAG,CAAC,SAA6B,CAAC,EAAE,CAAC;YAChE,MAAM,IAAI,YAAY,CAAC,SAA6B,EAAE,GAAG,CAAC,OAAO,CAAC,CAAC;QACrE,CAAC;QACD,MAAM,IAAI,YAAY,CAAC,SAAS,EAAE,GAAG,CAAC,OAAO,CAAC,CAAC;IACjD,CAAC;IACD,MAAM,IAAI,YAAY,CAAC,SAAS,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;AACjD,CAAC;AAED,MAAM,YAAY,GAAG;IACnB,iBAAiB;CAClB,CAAC;AAEF,OAAO,EACL,WAAW,EACX,iBAAiB,EACjB,sBAAsB,EACtB,mBAAmB,EACnB,wBAAwB,EACxB,YAAY,EACZ,YAAY,GACb,CAAC","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\n/**\n * Forcibly cancel any in-flight `authenticateAsync` call. Resolves once the\n * native coordinator's pending continuation has been cleared. No-op when\n * nothing is in flight, and a no-op on Android (the Android coordinator\n * self-cleans via structured concurrency).\n *\n * Recovery hatch for the iOS coordinator's stuck-state class of bugs: the\n * SPTSessionManager delegate callbacks are not guaranteed to fire — e.g. when\n * Spotify never redirects back to the host app — leaving the coordinator's\n * `pending` continuation set forever and every subsequent `authenticateAsync`\n * rejecting with `AUTH_IN_PROGRESS` until the process restarts. Call this\n * before `authenticateAsync` to defensively clear any leaked state.\n */\nfunction cancelPendingAuthAsync(): Promise<void> {\n if (Platform.OS !== \"ios\") {\n return Promise.resolve();\n }\n return ExpoSpotifySDKModule.cancelPendingAuthAsync();\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 cancelPendingAuthAsync,\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"]}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAC9E,wBAAwB;AACxB,8EAA8E;AAE9E,OAAO,EACL,UAAU,EACV,kBAAkB,EAClB,cAAc,EACd,eAAe,EACf,YAAY,EACZ,mBAAmB,EACnB,eAAe,EACf,eAAe,GAChB,MAAM,SAAS,CAAC;AAEjB,8EAA8E;AAC9E,6BAA6B;AAC7B,8EAA8E;AAE9E,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAC9B,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AACzC,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAClC,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAC9B,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAElC,8EAA8E;AAC9E,kBAAkB;AAClB,8EAA8E;AAE9E,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAC;AACnC,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AACvC,OAAO,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAC;AACnC,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAavC,8EAA8E;AAC9E,cAAc;AACd,8EAA8E;AAE9E,OAAO,EAAE,UAAU,EAAE,MAAM,OAAO,CAAC;AA2DnC,8EAA8E;AAC9E,mEAAmE;AACnE,EAAE;AACF,8EAA8E;AAC9E,qEAAqE;AACrE,8EAA8E;AAE9E,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAM9B,2CAA2C;AAC3C,MAAM,UAAU,WAAW;IACzB,OAAO,IAAI,CAAC,WAAW,EAAE,CAAC;AAC5B,CAAC;AAED,kDAAkD;AAClD,MAAM,UAAU,iBAAiB,CAAC,MAAqB;IACrD,OAAO,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC;AACnC,CAAC;AAED,6CAA6C;AAC7C,MAAM,UAAU,sBAAsB;IACpC,OAAO,IAAI,CAAC,aAAa,EAAE,CAAC;AAC9B,CAAC;AAED,6CAA6C;AAC7C,MAAM,UAAU,mBAAmB,CAAC,MAA4B;IAC9D,OAAO,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;AAC9B,CAAC;AAED,8DAA8D;AAC9D,MAAM,UAAU,wBAAwB,CACtC,QAAgD;IAEhD,OAAO,IAAI,CAAC,WAAW,CAAC,eAAe,EAAE,QAAQ,CAAC,CAAC;AACrD,CAAC","sourcesContent":["// ---------------------------------------------------------------------------\n// Hooks (v1 public API)\n// ---------------------------------------------------------------------------\n\nexport {\n useSession,\n useConnectionState,\n usePlayerState,\n useCurrentTrack,\n useIsPlaying,\n usePlaybackPosition,\n useCapabilities,\n useLibraryState,\n} from \"./hooks\";\n\n// ---------------------------------------------------------------------------\n// Namespaces (v1 public API)\n// ---------------------------------------------------------------------------\n\nexport { Auth } from \"./auth\";\nexport { AppRemote } from \"./app-remote\";\nexport { Player } from \"./player\";\nexport { User } from \"./user\";\nexport { Content } from \"./content\";\nexport { Images } from \"./images\";\n\n// ---------------------------------------------------------------------------\n// Error hierarchy\n// ---------------------------------------------------------------------------\n\nexport { SpotifyError } from \"./error\";\nexport { AuthError } from \"./auth\";\nexport { AppRemoteError } from \"./app-remote\";\nexport { PlayerError } from \"./player\";\nexport { UserError } from \"./user\";\nexport { ContentError } from \"./content\";\nexport { ImagesError } from \"./images\";\n\n// ---------------------------------------------------------------------------\n// Error code types\n// ---------------------------------------------------------------------------\n\nexport type { AuthErrorCode } from \"./auth\";\nexport type { AppRemoteErrorCode } from \"./app-remote\";\nexport type { PlayerErrorCode } from \"./player\";\nexport type { UserErrorCode } from \"./user\";\nexport type { ContentErrorCode } from \"./content\";\nexport type { ImagesErrorCode } from \"./images\";\n\n// ---------------------------------------------------------------------------\n// URI helpers\n// ---------------------------------------------------------------------------\n\nexport { SpotifyURI } from \"./uri\";\nexport type { SpotifyResourceType, SpotifyURI as SpotifyURIType } from \"./uri\";\n\n// ---------------------------------------------------------------------------\n// Auth types\n// ---------------------------------------------------------------------------\n\nexport type {\n SpotifySession,\n SpotifyScope,\n AuthenticateConfig,\n RefreshConfig,\n SessionChangeEvent,\n} from \"./auth\";\n\n// ---------------------------------------------------------------------------\n// App Remote types\n// ---------------------------------------------------------------------------\n\nexport type {\n ConnectionState,\n ConnectionStateChangeEvent,\n ConnectionErrorEvent,\n} from \"./app-remote\";\n\n// ---------------------------------------------------------------------------\n// Player types\n// ---------------------------------------------------------------------------\n\nexport type {\n RepeatMode,\n PodcastPlaybackSpeed,\n Artist,\n Album,\n Track,\n PlaybackOptions,\n PlaybackRestrictions,\n PlayerState,\n CrossfadeState,\n} from \"./player\";\n\n// ---------------------------------------------------------------------------\n// User types\n// ---------------------------------------------------------------------------\n\nexport type { Capabilities, LibraryState } from \"./user\";\n\n// ---------------------------------------------------------------------------\n// Content types\n// ---------------------------------------------------------------------------\n\nexport type { ContentType, ContentItem } from \"./content\";\n\n// ---------------------------------------------------------------------------\n// Images types\n// ---------------------------------------------------------------------------\n\nexport type { ImageSize, ImageResult, ImageRepresentable } from \"./images\";\n\n// ---------------------------------------------------------------------------\n// v0.x backward-compatible exports (deprecated — remove at v2.0.0)\n//\n// These shims let existing callers continue to compile after upgrading to v1.\n// Migrate to the namespaced API: see docs/V1_PLAN.md §8 (Migration).\n// ---------------------------------------------------------------------------\n\nimport { Auth } from \"./auth\";\nimport type {\n AuthenticateConfig as SpotifyConfig,\n RefreshConfig as SpotifyRefreshConfig,\n} from \"./auth\";\n\n/** @deprecated Use `Auth.isAvailable()` */\nexport function isAvailable(): boolean {\n return Auth.isAvailable();\n}\n\n/** @deprecated Use `Auth.authenticate(config)` */\nexport function authenticateAsync(config: SpotifyConfig) {\n return Auth.authenticate(config);\n}\n\n/** @deprecated Use `Auth.cancelPending()` */\nexport function cancelPendingAuthAsync() {\n return Auth.cancelPending();\n}\n\n/** @deprecated Use `Auth.refresh(config)` */\nexport function refreshSessionAsync(config: SpotifyRefreshConfig) {\n return Auth.refresh(config);\n}\n\n/** @deprecated Use `Auth.addListener(\"sessionChange\", cb)` */\nexport function addSessionChangeListener(\n listener: Parameters<typeof Auth.addListener>[1],\n) {\n return Auth.addListener(\"sessionChange\", listener);\n}\n\n// Re-export legacy type aliases so existing `import type { SpotifyConfig }`\n// consumers don't break.\nexport type { SpotifyConfig, SpotifyRefreshConfig };\n\n/**\n * @deprecated `SpotifyErrorCode` is now `AuthErrorCode`. Import via:\n * `import type { AuthErrorCode } from \"@wwdrew/expo-spotify-sdk\"`\n */\nexport type { AuthErrorCode as SpotifyErrorCode } from \"./auth\";\n"]}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { SpotifyError } from "../error";
|
|
2
|
+
export type PlayerErrorCode = "NOT_CONNECTED" | "CONNECTION_LOST" | "PREMIUM_REQUIRED" | "INVALID_URI" | "INVALID_PARAMETER" | "OPERATION_NOT_ALLOWED" | "UNKNOWN";
|
|
3
|
+
export declare class PlayerError extends SpotifyError {
|
|
4
|
+
readonly namespace: "Player";
|
|
5
|
+
readonly code: PlayerErrorCode;
|
|
6
|
+
constructor(code: PlayerErrorCode, message: string);
|
|
7
|
+
}
|
|
8
|
+
//# sourceMappingURL=error.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"error.d.ts","sourceRoot":"","sources":["../../src/player/error.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAExC,MAAM,MAAM,eAAe,GACvB,eAAe,GACf,iBAAiB,GACjB,kBAAkB,GAClB,aAAa,GACb,mBAAmB,GACnB,uBAAuB,GACvB,SAAS,CAAC;AAEd,qBAAa,WAAY,SAAQ,YAAY;IAC3C,QAAQ,CAAC,SAAS,EAAG,QAAQ,CAAU;IACvC,QAAQ,CAAC,IAAI,EAAE,eAAe,CAAC;gBAEnB,IAAI,EAAE,eAAe,EAAE,OAAO,EAAE,MAAM;CAInD"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"error.js","sourceRoot":"","sources":["../../src/player/error.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAWxC,MAAM,OAAO,WAAY,SAAQ,YAAY;IAClC,SAAS,GAAG,QAAiB,CAAC;IAC9B,IAAI,CAAkB;IAE/B,YAAY,IAAqB,EAAE,OAAe;QAChD,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;IACnB,CAAC;CACF","sourcesContent":["import { SpotifyError } from \"../error\";\n\nexport type PlayerErrorCode =\n | \"NOT_CONNECTED\"\n | \"CONNECTION_LOST\"\n | \"PREMIUM_REQUIRED\"\n | \"INVALID_URI\"\n | \"INVALID_PARAMETER\"\n | \"OPERATION_NOT_ALLOWED\"\n | \"UNKNOWN\";\n\nexport class PlayerError extends SpotifyError {\n readonly namespace = \"Player\" as const;\n readonly code: PlayerErrorCode;\n\n constructor(code: PlayerErrorCode, message: string) {\n super(message);\n this.code = code;\n }\n}\n"]}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import type { EventSubscription } from "expo-modules-core";
|
|
2
|
+
import { SpotifyURI } from "../uri";
|
|
3
|
+
export type { PlayerErrorCode } from "./error";
|
|
4
|
+
export { PlayerError } from "./error";
|
|
5
|
+
/** Repeat mode for the Spotify player. */
|
|
6
|
+
export type RepeatMode = 0 /** off */ | 1 /** repeat current track */ | 2; /** repeat current context */
|
|
7
|
+
/** Valid podcast playback speed multipliers. */
|
|
8
|
+
export type PodcastPlaybackSpeed = 0.5 | 0.8 | 1.0 | 1.2 | 1.5 | 2.0 | 3.0;
|
|
9
|
+
/** A Spotify artist. */
|
|
10
|
+
export interface Artist {
|
|
11
|
+
name: string;
|
|
12
|
+
uri: string;
|
|
13
|
+
}
|
|
14
|
+
/** A Spotify album. */
|
|
15
|
+
export interface Album {
|
|
16
|
+
name: string;
|
|
17
|
+
uri: string;
|
|
18
|
+
}
|
|
19
|
+
/** A track currently loaded in the Spotify player. */
|
|
20
|
+
export interface Track {
|
|
21
|
+
uri: string;
|
|
22
|
+
name: string;
|
|
23
|
+
/** Identifier used by `Images.load(...)`. */
|
|
24
|
+
imageIdentifier?: string;
|
|
25
|
+
/** Duration in milliseconds. */
|
|
26
|
+
duration: number;
|
|
27
|
+
artist: Artist;
|
|
28
|
+
album: Album;
|
|
29
|
+
isSaved: boolean;
|
|
30
|
+
isEpisode: boolean;
|
|
31
|
+
isPodcast: boolean;
|
|
32
|
+
isAdvertisement: boolean;
|
|
33
|
+
}
|
|
34
|
+
/** Current playback options (shuffle / repeat). */
|
|
35
|
+
export interface PlaybackOptions {
|
|
36
|
+
isShuffling: boolean;
|
|
37
|
+
/** 0 = off, 1 = repeat track, 2 = repeat context. */
|
|
38
|
+
repeatMode: RepeatMode;
|
|
39
|
+
}
|
|
40
|
+
/** Actions currently permitted by Spotify (gate UI buttons on these). */
|
|
41
|
+
export interface PlaybackRestrictions {
|
|
42
|
+
canSkipNext: boolean;
|
|
43
|
+
canSkipPrevious: boolean;
|
|
44
|
+
canRepeatTrack: boolean;
|
|
45
|
+
canRepeatContext: boolean;
|
|
46
|
+
canToggleShuffle: boolean;
|
|
47
|
+
canSeek: boolean;
|
|
48
|
+
}
|
|
49
|
+
/** Full snapshot of the Spotify player at a point in time. */
|
|
50
|
+
export interface PlayerState {
|
|
51
|
+
track: Track;
|
|
52
|
+
/** Current playback position in milliseconds. */
|
|
53
|
+
playbackPosition: number;
|
|
54
|
+
playbackSpeed: number;
|
|
55
|
+
isPaused: boolean;
|
|
56
|
+
playbackOptions: PlaybackOptions;
|
|
57
|
+
playbackRestrictions: PlaybackRestrictions;
|
|
58
|
+
contextTitle: string;
|
|
59
|
+
contextUri: string;
|
|
60
|
+
}
|
|
61
|
+
/** Crossfade configuration from the Spotify app. */
|
|
62
|
+
export interface CrossfadeState {
|
|
63
|
+
isEnabled: boolean;
|
|
64
|
+
/** Crossfade duration in milliseconds (only meaningful when enabled). */
|
|
65
|
+
duration: number;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Spotify Player namespace. Transport controls, queue management, and
|
|
69
|
+
* player-state subscriptions. Requires `AppRemote.connect()` to be resolved
|
|
70
|
+
* before any call.
|
|
71
|
+
*
|
|
72
|
+
* @example
|
|
73
|
+
* ```ts
|
|
74
|
+
* import { Player, SpotifyURI } from "@wwdrew/expo-spotify-sdk";
|
|
75
|
+
*
|
|
76
|
+
* await Player.play(SpotifyURI.from("spotify:track:4uLU6hMCjMI75M1A2tKUQC"));
|
|
77
|
+
* const state = await Player.getPlayerState();
|
|
78
|
+
*
|
|
79
|
+
* const sub = Player.addListener("playerStateChange", (state) => {
|
|
80
|
+
* console.log("now playing:", state.track.name, "paused:", state.isPaused);
|
|
81
|
+
* });
|
|
82
|
+
* ```
|
|
83
|
+
*/
|
|
84
|
+
export declare const Player: {
|
|
85
|
+
/**
|
|
86
|
+
* Asks Spotify to play the entity identified by the given URI.
|
|
87
|
+
* Requires Spotify Premium for on-demand track playback; throws
|
|
88
|
+
* `PlayerError("PREMIUM_REQUIRED", ...)` for Free users.
|
|
89
|
+
*/
|
|
90
|
+
readonly play: (uri: SpotifyURI) => Promise<void>;
|
|
91
|
+
/** Pauses playback. */
|
|
92
|
+
readonly pause: () => Promise<void>;
|
|
93
|
+
/** Resumes paused playback. */
|
|
94
|
+
readonly resume: () => Promise<void>;
|
|
95
|
+
/** Skips to the next track in the queue or context. */
|
|
96
|
+
readonly skipNext: () => Promise<void>;
|
|
97
|
+
/** Skips to the previous track. */
|
|
98
|
+
readonly skipPrevious: () => Promise<void>;
|
|
99
|
+
/**
|
|
100
|
+
* Seeks to the given position in milliseconds.
|
|
101
|
+
* Only valid when `PlaybackRestrictions.canSeek` is `true`.
|
|
102
|
+
*/
|
|
103
|
+
readonly seekTo: (positionMs: number) => Promise<void>;
|
|
104
|
+
/** Enables or disables shuffle. */
|
|
105
|
+
readonly setShuffle: (enabled: boolean) => Promise<void>;
|
|
106
|
+
/**
|
|
107
|
+
* Sets the repeat mode.
|
|
108
|
+
* @param mode 0 = off, 1 = repeat track, 2 = repeat context.
|
|
109
|
+
*/
|
|
110
|
+
readonly setRepeatMode: (mode: RepeatMode) => Promise<void>;
|
|
111
|
+
/**
|
|
112
|
+
* Sets the podcast playback speed. Only takes effect when a podcast episode
|
|
113
|
+
* is playing; valid speeds are `0.5 | 0.8 | 1.0 | 1.2 | 1.5 | 2.0 | 3.0`.
|
|
114
|
+
*/
|
|
115
|
+
readonly setPodcastPlaybackSpeed: (speed: PodcastPlaybackSpeed) => Promise<void>;
|
|
116
|
+
/** Adds a track URI to the end of the current playback queue. */
|
|
117
|
+
readonly queue: (uri: SpotifyURI) => Promise<void>;
|
|
118
|
+
/** Returns the current {@link PlayerState} as a one-shot pull. */
|
|
119
|
+
readonly getPlayerState: () => Promise<PlayerState>;
|
|
120
|
+
/** Returns the current {@link CrossfadeState} as a one-shot pull. */
|
|
121
|
+
readonly getCrossfadeState: () => Promise<CrossfadeState>;
|
|
122
|
+
/**
|
|
123
|
+
* Subscribes to player state changes. The callback fires whenever the
|
|
124
|
+
* Spotify app reports a state update (track change, pause/resume, seek,
|
|
125
|
+
* shuffle/repeat change, etc.).
|
|
126
|
+
*
|
|
127
|
+
* Returns a `Subscription` — call `.remove()` to unsubscribe.
|
|
128
|
+
*
|
|
129
|
+
* @example
|
|
130
|
+
* ```ts
|
|
131
|
+
* const sub = Player.addListener("playerStateChange", (state) => {
|
|
132
|
+
* console.log("track:", state.track.name, "paused:", state.isPaused);
|
|
133
|
+
* });
|
|
134
|
+
* // ...later:
|
|
135
|
+
* sub.remove();
|
|
136
|
+
* ```
|
|
137
|
+
*/
|
|
138
|
+
readonly addListener: (event: "playerStateChange", listener: (state: PlayerState) => void) => EventSubscription;
|
|
139
|
+
};
|
|
140
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/player/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import ExpoSpotifySDKModule from "../ExpoSpotifySDKModule";
|
|
2
|
+
import { SpotifyURI } from "../uri";
|
|
3
|
+
import { PlayerError } from "./error";
|
|
4
|
+
export { PlayerError } from "./error";
|
|
5
|
+
// ---------------------------------------------------------------------------
|
|
6
|
+
// Internal helpers
|
|
7
|
+
// ---------------------------------------------------------------------------
|
|
8
|
+
const VALID_PLAYER_CODES = new Set([
|
|
9
|
+
"NOT_CONNECTED",
|
|
10
|
+
"CONNECTION_LOST",
|
|
11
|
+
"PREMIUM_REQUIRED",
|
|
12
|
+
"INVALID_URI",
|
|
13
|
+
"INVALID_PARAMETER",
|
|
14
|
+
"OPERATION_NOT_ALLOWED",
|
|
15
|
+
"UNKNOWN",
|
|
16
|
+
]);
|
|
17
|
+
const CAUSE_SEPARATOR = "→ Caused by: ";
|
|
18
|
+
function unwrapReason(message) {
|
|
19
|
+
const idx = message.lastIndexOf(CAUSE_SEPARATOR);
|
|
20
|
+
return idx === -1 ? message : message.slice(idx + CAUSE_SEPARATOR.length);
|
|
21
|
+
}
|
|
22
|
+
function rethrowAsPlayerError(err) {
|
|
23
|
+
if (err instanceof PlayerError)
|
|
24
|
+
throw err;
|
|
25
|
+
if (err instanceof Error) {
|
|
26
|
+
const reason = unwrapReason(err.message);
|
|
27
|
+
const maybeCode = err.code;
|
|
28
|
+
if (maybeCode && VALID_PLAYER_CODES.has(maybeCode)) {
|
|
29
|
+
throw new PlayerError(maybeCode, reason);
|
|
30
|
+
}
|
|
31
|
+
throw new PlayerError("UNKNOWN", reason);
|
|
32
|
+
}
|
|
33
|
+
throw new PlayerError("UNKNOWN", String(err));
|
|
34
|
+
}
|
|
35
|
+
// ---------------------------------------------------------------------------
|
|
36
|
+
// Player namespace
|
|
37
|
+
// ---------------------------------------------------------------------------
|
|
38
|
+
/**
|
|
39
|
+
* Spotify Player namespace. Transport controls, queue management, and
|
|
40
|
+
* player-state subscriptions. Requires `AppRemote.connect()` to be resolved
|
|
41
|
+
* before any call.
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* ```ts
|
|
45
|
+
* import { Player, SpotifyURI } from "@wwdrew/expo-spotify-sdk";
|
|
46
|
+
*
|
|
47
|
+
* await Player.play(SpotifyURI.from("spotify:track:4uLU6hMCjMI75M1A2tKUQC"));
|
|
48
|
+
* const state = await Player.getPlayerState();
|
|
49
|
+
*
|
|
50
|
+
* const sub = Player.addListener("playerStateChange", (state) => {
|
|
51
|
+
* console.log("now playing:", state.track.name, "paused:", state.isPaused);
|
|
52
|
+
* });
|
|
53
|
+
* ```
|
|
54
|
+
*/
|
|
55
|
+
export const Player = {
|
|
56
|
+
/**
|
|
57
|
+
* Asks Spotify to play the entity identified by the given URI.
|
|
58
|
+
* Requires Spotify Premium for on-demand track playback; throws
|
|
59
|
+
* `PlayerError("PREMIUM_REQUIRED", ...)` for Free users.
|
|
60
|
+
*/
|
|
61
|
+
play(uri) {
|
|
62
|
+
return ExpoSpotifySDKModule.playerPlay(uri).catch(rethrowAsPlayerError);
|
|
63
|
+
},
|
|
64
|
+
/** Pauses playback. */
|
|
65
|
+
pause() {
|
|
66
|
+
return ExpoSpotifySDKModule.playerPause().catch(rethrowAsPlayerError);
|
|
67
|
+
},
|
|
68
|
+
/** Resumes paused playback. */
|
|
69
|
+
resume() {
|
|
70
|
+
return ExpoSpotifySDKModule.playerResume().catch(rethrowAsPlayerError);
|
|
71
|
+
},
|
|
72
|
+
/** Skips to the next track in the queue or context. */
|
|
73
|
+
skipNext() {
|
|
74
|
+
return ExpoSpotifySDKModule.playerSkipNext().catch(rethrowAsPlayerError);
|
|
75
|
+
},
|
|
76
|
+
/** Skips to the previous track. */
|
|
77
|
+
skipPrevious() {
|
|
78
|
+
return ExpoSpotifySDKModule.playerSkipPrevious().catch(rethrowAsPlayerError);
|
|
79
|
+
},
|
|
80
|
+
/**
|
|
81
|
+
* Seeks to the given position in milliseconds.
|
|
82
|
+
* Only valid when `PlaybackRestrictions.canSeek` is `true`.
|
|
83
|
+
*/
|
|
84
|
+
seekTo(positionMs) {
|
|
85
|
+
return ExpoSpotifySDKModule.playerSeekTo(positionMs).catch(rethrowAsPlayerError);
|
|
86
|
+
},
|
|
87
|
+
/** Enables or disables shuffle. */
|
|
88
|
+
setShuffle(enabled) {
|
|
89
|
+
return ExpoSpotifySDKModule.playerSetShuffle(enabled).catch(rethrowAsPlayerError);
|
|
90
|
+
},
|
|
91
|
+
/**
|
|
92
|
+
* Sets the repeat mode.
|
|
93
|
+
* @param mode 0 = off, 1 = repeat track, 2 = repeat context.
|
|
94
|
+
*/
|
|
95
|
+
setRepeatMode(mode) {
|
|
96
|
+
return ExpoSpotifySDKModule.playerSetRepeatMode(mode).catch(rethrowAsPlayerError);
|
|
97
|
+
},
|
|
98
|
+
/**
|
|
99
|
+
* Sets the podcast playback speed. Only takes effect when a podcast episode
|
|
100
|
+
* is playing; valid speeds are `0.5 | 0.8 | 1.0 | 1.2 | 1.5 | 2.0 | 3.0`.
|
|
101
|
+
*/
|
|
102
|
+
setPodcastPlaybackSpeed(speed) {
|
|
103
|
+
return ExpoSpotifySDKModule.playerSetPodcastPlaybackSpeed(speed).catch(rethrowAsPlayerError);
|
|
104
|
+
},
|
|
105
|
+
/** Adds a track URI to the end of the current playback queue. */
|
|
106
|
+
queue(uri) {
|
|
107
|
+
return ExpoSpotifySDKModule.playerQueue(uri).catch(rethrowAsPlayerError);
|
|
108
|
+
},
|
|
109
|
+
/** Returns the current {@link PlayerState} as a one-shot pull. */
|
|
110
|
+
getPlayerState() {
|
|
111
|
+
return ExpoSpotifySDKModule.playerGetPlayerState().catch(rethrowAsPlayerError);
|
|
112
|
+
},
|
|
113
|
+
/** Returns the current {@link CrossfadeState} as a one-shot pull. */
|
|
114
|
+
getCrossfadeState() {
|
|
115
|
+
return ExpoSpotifySDKModule.playerGetCrossfadeState().catch(rethrowAsPlayerError);
|
|
116
|
+
},
|
|
117
|
+
/**
|
|
118
|
+
* Subscribes to player state changes. The callback fires whenever the
|
|
119
|
+
* Spotify app reports a state update (track change, pause/resume, seek,
|
|
120
|
+
* shuffle/repeat change, etc.).
|
|
121
|
+
*
|
|
122
|
+
* Returns a `Subscription` — call `.remove()` to unsubscribe.
|
|
123
|
+
*
|
|
124
|
+
* @example
|
|
125
|
+
* ```ts
|
|
126
|
+
* const sub = Player.addListener("playerStateChange", (state) => {
|
|
127
|
+
* console.log("track:", state.track.name, "paused:", state.isPaused);
|
|
128
|
+
* });
|
|
129
|
+
* // ...later:
|
|
130
|
+
* sub.remove();
|
|
131
|
+
* ```
|
|
132
|
+
*/
|
|
133
|
+
addListener(event, listener) {
|
|
134
|
+
return ExpoSpotifySDKModule.addListener("onPlayerStateChange", listener);
|
|
135
|
+
},
|
|
136
|
+
};
|
|
137
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/player/index.ts"],"names":[],"mappings":"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","sourcesContent":["import type { EventSubscription } from \"expo-modules-core\";\n\nimport ExpoSpotifySDKModule from \"../ExpoSpotifySDKModule\";\nimport { SpotifyURI } from \"../uri\";\nimport { PlayerError, type PlayerErrorCode } from \"./error\";\n\nexport type { PlayerErrorCode } from \"./error\";\nexport { PlayerError } from \"./error\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/** Repeat mode for the Spotify player. */\nexport type RepeatMode =\n | 0 /** off */\n | 1 /** repeat current track */\n | 2; /** repeat current context */\n\n/** Valid podcast playback speed multipliers. */\nexport type PodcastPlaybackSpeed = 0.5 | 0.8 | 1.0 | 1.2 | 1.5 | 2.0 | 3.0;\n\n/** A Spotify artist. */\nexport interface Artist {\n name: string;\n uri: string;\n}\n\n/** A Spotify album. */\nexport interface Album {\n name: string;\n uri: string;\n}\n\n/** A track currently loaded in the Spotify player. */\nexport interface Track {\n uri: string;\n name: string;\n /** Identifier used by `Images.load(...)`. */\n imageIdentifier?: string;\n /** Duration in milliseconds. */\n duration: number;\n artist: Artist;\n album: Album;\n isSaved: boolean;\n isEpisode: boolean;\n isPodcast: boolean;\n isAdvertisement: boolean;\n}\n\n/** Current playback options (shuffle / repeat). */\nexport interface PlaybackOptions {\n isShuffling: boolean;\n /** 0 = off, 1 = repeat track, 2 = repeat context. */\n repeatMode: RepeatMode;\n}\n\n/** Actions currently permitted by Spotify (gate UI buttons on these). */\nexport interface PlaybackRestrictions {\n canSkipNext: boolean;\n canSkipPrevious: boolean;\n canRepeatTrack: boolean;\n canRepeatContext: boolean;\n canToggleShuffle: boolean;\n canSeek: boolean;\n}\n\n/** Full snapshot of the Spotify player at a point in time. */\nexport interface PlayerState {\n track: Track;\n /** Current playback position in milliseconds. */\n playbackPosition: number;\n playbackSpeed: number;\n isPaused: boolean;\n playbackOptions: PlaybackOptions;\n playbackRestrictions: PlaybackRestrictions;\n contextTitle: string;\n contextUri: string;\n}\n\n/** Crossfade configuration from the Spotify app. */\nexport interface CrossfadeState {\n isEnabled: boolean;\n /** Crossfade duration in milliseconds (only meaningful when enabled). */\n duration: number;\n}\n\n// ---------------------------------------------------------------------------\n// Internal helpers\n// ---------------------------------------------------------------------------\n\nconst VALID_PLAYER_CODES = new Set<PlayerErrorCode>([\n \"NOT_CONNECTED\",\n \"CONNECTION_LOST\",\n \"PREMIUM_REQUIRED\",\n \"INVALID_URI\",\n \"INVALID_PARAMETER\",\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 rethrowAsPlayerError(err: unknown): never {\n if (err instanceof PlayerError) 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_PLAYER_CODES.has(maybeCode as PlayerErrorCode)) {\n throw new PlayerError(maybeCode as PlayerErrorCode, reason);\n }\n throw new PlayerError(\"UNKNOWN\", reason);\n }\n throw new PlayerError(\"UNKNOWN\", String(err));\n}\n\n// ---------------------------------------------------------------------------\n// Player namespace\n// ---------------------------------------------------------------------------\n\n/**\n * Spotify Player namespace. Transport controls, queue management, and\n * player-state subscriptions. Requires `AppRemote.connect()` to be resolved\n * before any call.\n *\n * @example\n * ```ts\n * import { Player, SpotifyURI } from \"@wwdrew/expo-spotify-sdk\";\n *\n * await Player.play(SpotifyURI.from(\"spotify:track:4uLU6hMCjMI75M1A2tKUQC\"));\n * const state = await Player.getPlayerState();\n *\n * const sub = Player.addListener(\"playerStateChange\", (state) => {\n * console.log(\"now playing:\", state.track.name, \"paused:\", state.isPaused);\n * });\n * ```\n */\nexport const Player = {\n /**\n * Asks Spotify to play the entity identified by the given URI.\n * Requires Spotify Premium for on-demand track playback; throws\n * `PlayerError(\"PREMIUM_REQUIRED\", ...)` for Free users.\n */\n play(uri: SpotifyURI): Promise<void> {\n return ExpoSpotifySDKModule.playerPlay(uri).catch(rethrowAsPlayerError);\n },\n\n /** Pauses playback. */\n pause(): Promise<void> {\n return ExpoSpotifySDKModule.playerPause().catch(rethrowAsPlayerError);\n },\n\n /** Resumes paused playback. */\n resume(): Promise<void> {\n return ExpoSpotifySDKModule.playerResume().catch(rethrowAsPlayerError);\n },\n\n /** Skips to the next track in the queue or context. */\n skipNext(): Promise<void> {\n return ExpoSpotifySDKModule.playerSkipNext().catch(rethrowAsPlayerError);\n },\n\n /** Skips to the previous track. */\n skipPrevious(): Promise<void> {\n return ExpoSpotifySDKModule.playerSkipPrevious().catch(rethrowAsPlayerError);\n },\n\n /**\n * Seeks to the given position in milliseconds.\n * Only valid when `PlaybackRestrictions.canSeek` is `true`.\n */\n seekTo(positionMs: number): Promise<void> {\n return ExpoSpotifySDKModule.playerSeekTo(positionMs).catch(rethrowAsPlayerError);\n },\n\n /** Enables or disables shuffle. */\n setShuffle(enabled: boolean): Promise<void> {\n return ExpoSpotifySDKModule.playerSetShuffle(enabled).catch(rethrowAsPlayerError);\n },\n\n /**\n * Sets the repeat mode.\n * @param mode 0 = off, 1 = repeat track, 2 = repeat context.\n */\n setRepeatMode(mode: RepeatMode): Promise<void> {\n return ExpoSpotifySDKModule.playerSetRepeatMode(mode).catch(rethrowAsPlayerError);\n },\n\n /**\n * Sets the podcast playback speed. Only takes effect when a podcast episode\n * is playing; valid speeds are `0.5 | 0.8 | 1.0 | 1.2 | 1.5 | 2.0 | 3.0`.\n */\n setPodcastPlaybackSpeed(speed: PodcastPlaybackSpeed): Promise<void> {\n return ExpoSpotifySDKModule.playerSetPodcastPlaybackSpeed(speed).catch(\n rethrowAsPlayerError,\n );\n },\n\n /** Adds a track URI to the end of the current playback queue. */\n queue(uri: SpotifyURI): Promise<void> {\n return ExpoSpotifySDKModule.playerQueue(uri).catch(rethrowAsPlayerError);\n },\n\n /** Returns the current {@link PlayerState} as a one-shot pull. */\n getPlayerState(): Promise<PlayerState> {\n return ExpoSpotifySDKModule.playerGetPlayerState().catch(rethrowAsPlayerError);\n },\n\n /** Returns the current {@link CrossfadeState} as a one-shot pull. */\n getCrossfadeState(): Promise<CrossfadeState> {\n return ExpoSpotifySDKModule.playerGetCrossfadeState().catch(rethrowAsPlayerError);\n },\n\n /**\n * Subscribes to player state changes. The callback fires whenever the\n * Spotify app reports a state update (track change, pause/resume, seek,\n * shuffle/repeat change, etc.).\n *\n * Returns a `Subscription` — call `.remove()` to unsubscribe.\n *\n * @example\n * ```ts\n * const sub = Player.addListener(\"playerStateChange\", (state) => {\n * console.log(\"track:\", state.track.name, \"paused:\", state.isPaused);\n * });\n * // ...later:\n * sub.remove();\n * ```\n */\n addListener(\n event: \"playerStateChange\",\n listener: (state: PlayerState) => void,\n ): EventSubscription {\n return ExpoSpotifySDKModule.addListener(\n \"onPlayerStateChange\",\n listener as (event: unknown) => void,\n ) as EventSubscription;\n },\n} as const;\n"]}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The six Spotify resource types accepted by App Remote APIs.
|
|
3
|
+
*/
|
|
4
|
+
export type SpotifyResourceType = "track" | "album" | "playlist" | "artist" | "show" | "episode";
|
|
5
|
+
/**
|
|
6
|
+
* Branded string type for Spotify URIs (`spotify:<type>:<id>`).
|
|
7
|
+
*
|
|
8
|
+
* Construct via `SpotifyURI.from(str)` (validates) or
|
|
9
|
+
* `SpotifyURI.unsafe(str)` (skips validation — use only when the source is
|
|
10
|
+
* trusted, e.g. a URI that came back from the Spotify SDK itself).
|
|
11
|
+
*/
|
|
12
|
+
export type SpotifyURI = string & {
|
|
13
|
+
readonly __brand: "SpotifyURI";
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* Helpers for constructing, validating and decomposing {@link SpotifyURI}
|
|
17
|
+
* values.
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* ```ts
|
|
21
|
+
* const uri = SpotifyURI.from("spotify:track:4uLU6hMCjMI75M1A2tKUQC");
|
|
22
|
+
* const { type, id } = SpotifyURI.parse(uri);
|
|
23
|
+
* const rebuilt = SpotifyURI.build("track", id);
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
export declare const SpotifyURI: {
|
|
27
|
+
/**
|
|
28
|
+
* Cast `uri` to {@link SpotifyURI} after validation.
|
|
29
|
+
* Throws a plain `Error` if the URI does not match the `spotify:<type>:<id>`
|
|
30
|
+
* format — use at app-code call sites where you want early feedback.
|
|
31
|
+
*/
|
|
32
|
+
readonly from: (uri: string) => SpotifyURI;
|
|
33
|
+
/**
|
|
34
|
+
* Cast `uri` to {@link SpotifyURI} without validation.
|
|
35
|
+
* Use only for URIs that originate from the Spotify SDK itself (i.e. already
|
|
36
|
+
* known-valid values coming back over the bridge).
|
|
37
|
+
*/
|
|
38
|
+
readonly unsafe: (uri: string) => SpotifyURI;
|
|
39
|
+
/**
|
|
40
|
+
* Decompose a {@link SpotifyURI} into its `{ type, id }` parts.
|
|
41
|
+
*/
|
|
42
|
+
readonly parse: (uri: SpotifyURI) => {
|
|
43
|
+
type: SpotifyResourceType;
|
|
44
|
+
id: string;
|
|
45
|
+
};
|
|
46
|
+
/**
|
|
47
|
+
* Build a {@link SpotifyURI} from a resource type and ID.
|
|
48
|
+
*/
|
|
49
|
+
readonly build: (type: SpotifyResourceType, id: string) => SpotifyURI;
|
|
50
|
+
/**
|
|
51
|
+
* Type-guard: returns `true` if `uri` is a valid Spotify URI string.
|
|
52
|
+
*/
|
|
53
|
+
readonly isValid: (uri: string) => uri is SpotifyURI;
|
|
54
|
+
};
|
|
55
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/uri/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,MAAM,mBAAmB,GAC3B,OAAO,GACP,OAAO,GACP,UAAU,GACV,QAAQ,GACR,MAAM,GACN,SAAS,CAAC;AAEd;;;;;;GAMG;AACH,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG;IAAE,QAAQ,CAAC,OAAO,EAAE,YAAY,CAAA;CAAE,CAAC;AAKrE;;;;;;;;;;GAUG;AACH,eAAO,MAAM,UAAU;IACrB;;;;OAIG;yBACO,MAAM,KAAG,UAAU;IAU7B;;;;OAIG;2BACS,MAAM,KAAG,UAAU;IAI/B;;OAEG;0BACQ,UAAU,KAAG;QAAE,IAAI,EAAE,mBAAmB,CAAC;QAAC,EAAE,EAAE,MAAM,CAAA;KAAE;IAQjE;;OAEG;2BACS,mBAAmB,MAAM,MAAM,KAAG,UAAU;IAIxD;;OAEG;4BACU,MAAM,KAAG,GAAG,IAAI,UAAU;CAG/B,CAAC"}
|