@wwdrew/expo-spotify-sdk 2.0.0 → 2.2.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 +94 -346
- package/android/build.gradle +4 -5
- package/android/libs/SETUP.md +9 -24
- package/android/spotify-native-sdk.gradle +118 -0
- package/android/src/main/java/expo/modules/spotifysdk/ExpoSpotifySDKModule.kt +14 -5
- package/android/src/main/java/expo/modules/spotifysdk/SpotifyAppRemoteCoordinator.kt +38 -64
- package/android/src/main/java/expo/modules/spotifysdk/SpotifyAppRemoteErrorMapping.kt +106 -0
- package/android/src/main/java/expo/modules/spotifysdk/SpotifyTokenSwapClient.kt +2 -7
- package/build/app-remote/index.d.ts +34 -0
- package/build/app-remote/index.d.ts.map +1 -1
- package/build/app-remote/index.js +47 -24
- package/build/app-remote/index.js.map +1 -1
- package/build/auth/index.d.ts.map +1 -1
- package/build/auth/index.js +17 -34
- package/build/auth/index.js.map +1 -1
- package/build/content/index.d.ts.map +1 -1
- package/build/content/index.js +12 -25
- package/build/content/index.js.map +1 -1
- package/build/hooks/index.d.ts +1 -1
- package/build/hooks/index.d.ts.map +1 -1
- package/build/hooks/index.js +38 -115
- package/build/hooks/index.js.map +1 -1
- package/build/images/index.d.ts +2 -2
- package/build/images/index.d.ts.map +1 -1
- package/build/images/index.js +12 -25
- package/build/images/index.js.map +1 -1
- package/build/index.d.ts +0 -18
- package/build/index.d.ts.map +1 -1
- package/build/index.js +0 -27
- package/build/index.js.map +1 -1
- package/build/internal/native-errors.d.ts +14 -0
- package/build/internal/native-errors.d.ts.map +1 -0
- package/build/internal/native-errors.js +29 -0
- package/build/internal/native-errors.js.map +1 -0
- package/build/internal/sync-external-store.d.ts +14 -0
- package/build/internal/sync-external-store.d.ts.map +1 -0
- package/build/internal/sync-external-store.js +36 -0
- package/build/internal/sync-external-store.js.map +1 -0
- package/build/player/index.d.ts.map +1 -1
- package/build/player/index.js +14 -27
- package/build/player/index.js.map +1 -1
- package/build/user/index.d.ts +2 -2
- package/build/user/index.d.ts.map +1 -1
- package/build/user/index.js +16 -26
- package/build/user/index.js.map +1 -1
- package/ios/ExpoSpotifyAppDelegate.swift +8 -1
- package/ios/ExpoSpotifySDK.podspec +26 -2
- package/ios/ExpoSpotifySDKModule.swift +16 -1
- package/ios/SpotifyAppRemoteCoordinator.swift +137 -404
- package/ios/SpotifyAppRemoteErrorMapping.swift +110 -0
- package/ios/SpotifyAppRemoteErrors.swift +221 -0
- package/ios/SpotifyAppRemoteMappers.swift +88 -0
- package/ios/spotify-native-sdk-versions.json +12 -0
- package/package.json +13 -5
- package/plugin/index.d.ts +2 -0
- package/plugin/index.js +1 -0
- package/ios/SpotifySDK/Licenses/MPMessagePack-LICENSE.md +0 -20
- package/ios/SpotifySDK/SpotifyiOS.xcframework/Info.plist +0 -40
- package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64/SpotifyiOS.framework/Headers/SPTAppRemote.h +0 -287
- package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64/SpotifyiOS.framework/Headers/SPTAppRemoteAlbum.h +0 -18
- package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64/SpotifyiOS.framework/Headers/SPTAppRemoteArtist.h +0 -18
- package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64/SpotifyiOS.framework/Headers/SPTAppRemoteCommon.h +0 -37
- package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64/SpotifyiOS.framework/Headers/SPTAppRemoteConnectionParams.h +0 -65
- package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64/SpotifyiOS.framework/Headers/SPTAppRemoteConnectivityAPI.h +0 -55
- package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64/SpotifyiOS.framework/Headers/SPTAppRemoteConnectivityState.h +0 -15
- package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64/SpotifyiOS.framework/Headers/SPTAppRemoteContentAPI.h +0 -78
- package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64/SpotifyiOS.framework/Headers/SPTAppRemoteContentItem.h +0 -49
- package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64/SpotifyiOS.framework/Headers/SPTAppRemoteCrossfadeState.h +0 -18
- package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64/SpotifyiOS.framework/Headers/SPTAppRemoteImageAPI.h +0 -25
- package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64/SpotifyiOS.framework/Headers/SPTAppRemoteImageRepresentable.h +0 -16
- package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64/SpotifyiOS.framework/Headers/SPTAppRemoteLibraryState.h +0 -19
- package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64/SpotifyiOS.framework/Headers/SPTAppRemotePlaybackOptions.h +0 -32
- package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64/SpotifyiOS.framework/Headers/SPTAppRemotePlaybackRestrictions.h +0 -32
- package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64/SpotifyiOS.framework/Headers/SPTAppRemotePlayerAPI.h +0 -267
- package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64/SpotifyiOS.framework/Headers/SPTAppRemotePlayerState.h +0 -40
- package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64/SpotifyiOS.framework/Headers/SPTAppRemotePodcastPlaybackSpeed.h +0 -15
- package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64/SpotifyiOS.framework/Headers/SPTAppRemoteTrack.h +0 -52
- package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64/SpotifyiOS.framework/Headers/SPTAppRemoteUserAPI.h +0 -102
- package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64/SpotifyiOS.framework/Headers/SPTAppRemoteUserCapabilities.h +0 -15
- package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64/SpotifyiOS.framework/Headers/SPTConfiguration.h +0 -66
- package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64/SpotifyiOS.framework/Headers/SPTError.h +0 -31
- package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64/SpotifyiOS.framework/Headers/SPTLogin.h +0 -10
- package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64/SpotifyiOS.framework/Headers/SPTMacros.h +0 -125
- package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64/SpotifyiOS.framework/Headers/SPTScope.h +0 -75
- package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64/SpotifyiOS.framework/Headers/SPTSession.h +0 -33
- package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64/SpotifyiOS.framework/Headers/SPTSessionManager.h +0 -170
- package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64/SpotifyiOS.framework/Headers/SpotifyAppRemote.h +0 -28
- package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64/SpotifyiOS.framework/Headers/SpotifyiOS.h +0 -7
- package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64/SpotifyiOS.framework/Info.plist +0 -0
- package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64/SpotifyiOS.framework/Modules/module.modulemap +0 -5
- package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64/SpotifyiOS.framework/PrivacyInfo.xcprivacy +0 -14
- package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64/SpotifyiOS.framework/SpotifyiOS +0 -0
- package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64_x86_64-simulator/SpotifyiOS.framework/Headers/SPTAppRemote.h +0 -287
- package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64_x86_64-simulator/SpotifyiOS.framework/Headers/SPTAppRemoteAlbum.h +0 -18
- package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64_x86_64-simulator/SpotifyiOS.framework/Headers/SPTAppRemoteArtist.h +0 -18
- package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64_x86_64-simulator/SpotifyiOS.framework/Headers/SPTAppRemoteCommon.h +0 -37
- package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64_x86_64-simulator/SpotifyiOS.framework/Headers/SPTAppRemoteConnectionParams.h +0 -65
- package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64_x86_64-simulator/SpotifyiOS.framework/Headers/SPTAppRemoteConnectivityAPI.h +0 -55
- package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64_x86_64-simulator/SpotifyiOS.framework/Headers/SPTAppRemoteConnectivityState.h +0 -15
- package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64_x86_64-simulator/SpotifyiOS.framework/Headers/SPTAppRemoteContentAPI.h +0 -78
- package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64_x86_64-simulator/SpotifyiOS.framework/Headers/SPTAppRemoteContentItem.h +0 -49
- package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64_x86_64-simulator/SpotifyiOS.framework/Headers/SPTAppRemoteCrossfadeState.h +0 -18
- package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64_x86_64-simulator/SpotifyiOS.framework/Headers/SPTAppRemoteImageAPI.h +0 -25
- package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64_x86_64-simulator/SpotifyiOS.framework/Headers/SPTAppRemoteImageRepresentable.h +0 -16
- package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64_x86_64-simulator/SpotifyiOS.framework/Headers/SPTAppRemoteLibraryState.h +0 -19
- package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64_x86_64-simulator/SpotifyiOS.framework/Headers/SPTAppRemotePlaybackOptions.h +0 -32
- package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64_x86_64-simulator/SpotifyiOS.framework/Headers/SPTAppRemotePlaybackRestrictions.h +0 -32
- package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64_x86_64-simulator/SpotifyiOS.framework/Headers/SPTAppRemotePlayerAPI.h +0 -267
- package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64_x86_64-simulator/SpotifyiOS.framework/Headers/SPTAppRemotePlayerState.h +0 -40
- package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64_x86_64-simulator/SpotifyiOS.framework/Headers/SPTAppRemotePodcastPlaybackSpeed.h +0 -15
- package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64_x86_64-simulator/SpotifyiOS.framework/Headers/SPTAppRemoteTrack.h +0 -52
- package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64_x86_64-simulator/SpotifyiOS.framework/Headers/SPTAppRemoteUserAPI.h +0 -102
- package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64_x86_64-simulator/SpotifyiOS.framework/Headers/SPTAppRemoteUserCapabilities.h +0 -15
- package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64_x86_64-simulator/SpotifyiOS.framework/Headers/SPTConfiguration.h +0 -66
- package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64_x86_64-simulator/SpotifyiOS.framework/Headers/SPTError.h +0 -31
- package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64_x86_64-simulator/SpotifyiOS.framework/Headers/SPTLogin.h +0 -10
- package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64_x86_64-simulator/SpotifyiOS.framework/Headers/SPTMacros.h +0 -125
- package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64_x86_64-simulator/SpotifyiOS.framework/Headers/SPTScope.h +0 -75
- package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64_x86_64-simulator/SpotifyiOS.framework/Headers/SPTSession.h +0 -33
- package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64_x86_64-simulator/SpotifyiOS.framework/Headers/SPTSessionManager.h +0 -170
- package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64_x86_64-simulator/SpotifyiOS.framework/Headers/SpotifyAppRemote.h +0 -28
- package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64_x86_64-simulator/SpotifyiOS.framework/Headers/SpotifyiOS.h +0 -7
- package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64_x86_64-simulator/SpotifyiOS.framework/Info.plist +0 -0
- package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64_x86_64-simulator/SpotifyiOS.framework/Modules/module.modulemap +0 -5
- package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64_x86_64-simulator/SpotifyiOS.framework/PrivacyInfo.xcprivacy +0 -14
- package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64_x86_64-simulator/SpotifyiOS.framework/SpotifyiOS +0 -0
package/build/auth/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Platform } from "expo-modules-core";
|
|
2
2
|
import ExpoSpotifySDKModule from "../ExpoSpotifySDKModule";
|
|
3
3
|
import { AuthError } from "./error";
|
|
4
|
+
import { createNativeErrorRethrow } from "../internal/native-errors";
|
|
4
5
|
export { AuthError } from "./error";
|
|
5
6
|
// ---------------------------------------------------------------------------
|
|
6
7
|
// Internal helpers
|
|
@@ -10,40 +11,22 @@ const ANDROID_TOKEN_FLOW_WARNING = "[expo-spotify-sdk] You are using Auth.authen
|
|
|
10
11
|
"the actual granted scopes through this path; see the README's " +
|
|
11
12
|
"'Android implicit (TOKEN) flow is not recommended' section.";
|
|
12
13
|
let warnedAboutAndroidTokenFlow = false;
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
"
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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
|
-
}
|
|
14
|
+
const rethrowAsAuthError = createNativeErrorRethrow({
|
|
15
|
+
ErrorClass: AuthError,
|
|
16
|
+
unknownCode: "UNKNOWN",
|
|
17
|
+
legacyCodePrefix: true,
|
|
18
|
+
validCodes: new Set([
|
|
19
|
+
"USER_CANCELLED",
|
|
20
|
+
"AUTH_IN_PROGRESS",
|
|
21
|
+
"INVALID_CONFIG",
|
|
22
|
+
"NETWORK_ERROR",
|
|
23
|
+
"TOKEN_SWAP_FAILED",
|
|
24
|
+
"TOKEN_SWAP_PARSE_ERROR",
|
|
25
|
+
"SPOTIFY_NOT_INSTALLED",
|
|
26
|
+
"AUTH_ERROR",
|
|
27
|
+
"UNKNOWN",
|
|
28
|
+
]),
|
|
29
|
+
});
|
|
47
30
|
function normaliseSession(raw) {
|
|
48
31
|
if (!raw || typeof raw !== "object") {
|
|
49
32
|
throw new AuthError("UNKNOWN", "Native module returned a non-object session");
|
package/build/auth/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/auth/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAE7C,OAAO,oBAAoB,MAAM,yBAAyB,CAAC;AAC3D,OAAO,EAAE,SAAS,EAAsB,MAAM,SAAS,CAAC;AAGxD,OAAO,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAkFpC,8EAA8E;AAC9E,mBAAmB;AACnB,8EAA8E;AAE9E,MAAM,0BAA0B,GAC9B,0EAA0E;IAC1E,2EAA2E;IAC3E,gEAAgE;IAChE,6DAA6D,CAAC;AAEhE,IAAI,2BAA2B,GAAG,KAAK,CAAC;AAExC,MAAM,gBAAgB,GAAG,IAAI,GAAG,CAAgB;IAC9C,gBAAgB;IAChB,kBAAkB;IAClB,gBAAgB;IAChB,eAAe;IACf,mBAAmB;IACnB,wBAAwB;IACxB,uBAAuB;IACvB,YAAY;IACZ,SAAS;CACV,CAAC,CAAC;AAEH,MAAM,eAAe,GAAG,eAAe,CAAC;AACxC,MAAM,qBAAqB,GAAG,+BAA+B,CAAC;AAE9D,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,kBAAkB,CAAC,GAAY;IACtC,IAAI,GAAG,YAAY,SAAS;QAAE,MAAM,GAAG,CAAC;IACxC,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,gBAAgB,CAAC,GAAG,CAAC,SAA0B,CAAC,EAAE,CAAC;YAClE,MAAM,IAAI,SAAS,CAAC,SAA0B,EAAE,MAAM,CAAC,CAAC;QAC1D,CAAC;QACD,MAAM,CAAC,GAAG,MAAM,CAAC,KAAK,CAAC,qBAAqB,CAAC,CAAC;QAC9C,IAAI,CAAC,IAAI,gBAAgB,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAkB,CAAC,EAAE,CAAC;YACrD,MAAM,IAAI,SAAS,CAAC,CAAC,CAAC,CAAC,CAAkB,EAAE,CAAC,CAAC,CAAC,CAAE,CAAC,CAAC;QACpD,CAAC;QACD,MAAM,IAAI,SAAS,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC;IACzC,CAAC;IACD,MAAM,IAAI,SAAS,CAAC,SAAS,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;AAC9C,CAAC;AAED,SAAS,gBAAgB,CAAC,GAAY;IACpC,IAAI,CAAC,GAAG,IAAI,OAAO,GAAG,KAAK,QAAQ,EAAE,CAAC;QACpC,MAAM,IAAI,SAAS,CAAC,SAAS,EAAE,6CAA6C,CAAC,CAAC;IAChF,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,SAAS,CAAC,SAAS,EAAE,gCAAgC,CAAC,CAAC;IACnE,CAAC;IACD,MAAM,cAAc,GAAG,CAAC,CAAC,cAAc,CAAC;IACxC,IAAI,OAAO,cAAc,KAAK,QAAQ,EAAE,CAAC;QACvC,MAAM,IAAI,SAAS,CAAC,SAAS,EAAE,mCAAmC,CAAC,CAAC;IACtE,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,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,QAAQ,CAAoB;QACpE,CAAC,CAAC,EAAE,CAAC;IACP,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,EAAE,CAAC;AAC/D,CAAC;AAED,8EAA8E;AAC9E,iBAAiB;AACjB,8EAA8E;AAE9E;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,IAAI,GAAG;IAClB;;OAEG;IACH,WAAW;QACT,OAAO,oBAAoB,CAAC,WAAW,EAAE,CAAC;IAC5C,CAAC;IAED;;;OAGG;IACH,YAAY,CAAC,MAA0B;QACrC,IAAI,CAAC,MAAM,CAAC,MAAM,IAAI,MAAM,CAAC,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACjD,OAAO,OAAO,CAAC,MAAM,CACnB,IAAI,SAAS,CAAC,gBAAgB,EAAE,wCAAwC,CAAC,CAC1E,CAAC;QACJ,CAAC;QACD,IACE,QAAQ,CAAC,EAAE,KAAK,SAAS;YACzB,CAAC,MAAM,CAAC,YAAY;YACpB,CAAC,2BAA2B,EAC5B,CAAC;YACD,2BAA2B,GAAG,IAAI,CAAC;YACnC,OAAO,CAAC,IAAI,CAAC,0BAA0B,CAAC,CAAC;QAC3C,CAAC;QACD,OAAO,oBAAoB,CAAC,iBAAiB,CAAC,MAAM,CAAC;aAClD,IAAI,CAAC,gBAAgB,CAAC;aACtB,KAAK,CAAC,kBAAkB,CAAC,CAAC;IAC/B,CAAC;IAED;;;;OAIG;IACH,OAAO,CAAC,MAAqB;QAC3B,IAAI,CAAC,MAAM,CAAC,YAAY,EAAE,CAAC;YACzB,OAAO,OAAO,CAAC,MAAM,CACnB,IAAI,SAAS,CAAC,gBAAgB,EAAE,0BAA0B,CAAC,CAC5D,CAAC;QACJ,CAAC;QACD,IAAI,CAAC,MAAM,CAAC,eAAe,EAAE,CAAC;YAC5B,OAAO,OAAO,CAAC,MAAM,CACnB,IAAI,SAAS,CAAC,gBAAgB,EAAE,6BAA6B,CAAC,CAC/D,CAAC;QACJ,CAAC;QACD,OAAO,oBAAoB,CAAC,mBAAmB,CAAC,MAAM,CAAC;aACpD,IAAI,CAAC,gBAAgB,CAAC;aACtB,KAAK,CAAC,kBAAkB,CAAC,CAAC;IAC/B,CAAC;IAED;;;;;;;OAOG;IACH,aAAa;QACX,IAAI,QAAQ,CAAC,EAAE,KAAK,KAAK,EAAE,CAAC;YAC1B,OAAO,OAAO,CAAC,OAAO,EAAE,CAAC;QAC3B,CAAC;QACD,OAAO,oBAAoB,CAAC,sBAAsB,EAAE,CAAC;IACvD,CAAC;IAED;;;;;;;;;;;;;;;;;;;OAmBG;IACH,WAAW,CACT,KAAsB,EACtB,QAA6C;QAE7C,OAAO,oBAAoB,CAAC,WAAW,CACrC,iBAAiB,EACjB,QAAQ,CACY,CAAC;IACzB,CAAC;CACO,CAAC","sourcesContent":["import type { EventSubscription } from \"expo-modules-core\";\nimport { Platform } from \"expo-modules-core\";\n\nimport ExpoSpotifySDKModule from \"../ExpoSpotifySDKModule\";\nimport { AuthError, type 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"]}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/auth/index.ts"],"names":[],"mappings":"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","sourcesContent":["import type { EventSubscription } from \"expo-modules-core\";\nimport { Platform } from \"expo-modules-core\";\n\nimport ExpoSpotifySDKModule from \"../ExpoSpotifySDKModule\";\nimport { AuthError, type AuthErrorCode } from \"./error\";\nimport { createNativeErrorRethrow } from \"../internal/native-errors\";\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 rethrowAsAuthError = createNativeErrorRethrow({\n ErrorClass: AuthError,\n unknownCode: \"UNKNOWN\",\n legacyCodePrefix: true,\n validCodes: 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});\n\nfunction normaliseSession(raw: unknown): SpotifySession {\n if (!raw || typeof raw !== \"object\") {\n throw new AuthError(\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 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(\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 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"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/content/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/content/index.ts"],"names":[],"mappings":"AAQA,YAAY,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAChD,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAEvC,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;AAaD;;;;;;;;;;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;CAK9C,CAAC"}
|
package/build/content/index.js
CHANGED
|
@@ -1,33 +1,20 @@
|
|
|
1
|
-
export { ContentError } from "./error";
|
|
2
1
|
// ---------------------------------------------------------------------------
|
|
3
2
|
// Types
|
|
4
3
|
// ---------------------------------------------------------------------------
|
|
5
4
|
import ExpoSpotifySDKModule from "../ExpoSpotifySDKModule";
|
|
6
5
|
import { ContentError } from "./error";
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
"UNKNOWN",
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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
|
-
}
|
|
6
|
+
import { createNativeErrorRethrow } from "../internal/native-errors";
|
|
7
|
+
export { ContentError } from "./error";
|
|
8
|
+
const rethrowAsContentError = createNativeErrorRethrow({
|
|
9
|
+
ErrorClass: ContentError,
|
|
10
|
+
unknownCode: "UNKNOWN",
|
|
11
|
+
validCodes: new Set([
|
|
12
|
+
"NOT_CONNECTED",
|
|
13
|
+
"CONNECTION_LOST",
|
|
14
|
+
"CONTENT_API_UNAVAILABLE",
|
|
15
|
+
"UNKNOWN",
|
|
16
|
+
]),
|
|
17
|
+
});
|
|
31
18
|
/**
|
|
32
19
|
* Spotify Content namespace. Browse Spotify's curated content tree.
|
|
33
20
|
* Requires `AppRemote.connect()` to be resolved before any call.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/content/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/content/index.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAC9E,QAAQ;AACR,8EAA8E;AAE9E,OAAO,oBAAoB,MAAM,yBAAyB,CAAC;AAC3D,OAAO,EAAE,YAAY,EAAyB,MAAM,SAAS,CAAC;AAC9D,OAAO,EAAE,wBAAwB,EAAE,MAAM,2BAA2B,CAAC;AAGrE,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAkBvC,MAAM,qBAAqB,GAAG,wBAAwB,CAAC;IACrD,UAAU,EAAE,YAAY;IACxB,WAAW,EAAE,SAAS;IACtB,UAAU,EAAE,IAAI,GAAG,CAAmB;QACpC,eAAe;QACf,iBAAiB;QACjB,yBAAyB;QACzB,SAAS;KACV,CAAC;CACH,CAAC,CAAC;AAEH;;;;;;;;;;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,CACxD,qBAAqB,CACtB,CAAC;IACJ,CAAC;CACO,CAAC","sourcesContent":["// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nimport ExpoSpotifySDKModule from \"../ExpoSpotifySDKModule\";\nimport { ContentError, type ContentErrorCode } from \"./error\";\nimport { createNativeErrorRethrow } from \"../internal/native-errors\";\n\nexport type { ContentErrorCode } from \"./error\";\nexport { ContentError } 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 rethrowAsContentError = createNativeErrorRethrow({\n ErrorClass: ContentError,\n unknownCode: \"UNKNOWN\",\n validCodes: new Set<ContentErrorCode>([\n \"NOT_CONNECTED\",\n \"CONNECTION_LOST\",\n \"CONTENT_API_UNAVAILABLE\",\n \"UNKNOWN\",\n ]),\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(\n rethrowAsContentError,\n );\n },\n} as const;\n"]}
|
package/build/hooks/index.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { type ConnectionState } from "../app-remote";
|
|
2
2
|
import { type SpotifySession } from "../auth";
|
|
3
3
|
import { type PlayerState, type Track } from "../player";
|
|
4
|
-
import { type Capabilities, type LibraryState } from "../user";
|
|
5
4
|
import type { SpotifyURI as SpotifyURIType } from "../uri";
|
|
5
|
+
import { type Capabilities, type LibraryState } from "../user";
|
|
6
6
|
/**
|
|
7
7
|
* Returns the current Spotify OAuth session, or `null` when not authenticated.
|
|
8
8
|
* Updates automatically whenever `Auth.authenticate()` or `Auth.refresh()`
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/hooks/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAa,KAAK,eAAe,EAAE,MAAM,eAAe,CAAC;AAChE,OAAO,EAAQ,KAAK,cAAc,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/hooks/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAa,KAAK,eAAe,EAAE,MAAM,eAAe,CAAC;AAChE,OAAO,EAAQ,KAAK,cAAc,EAAE,MAAM,SAAS,CAAC;AAEpD,OAAO,EAAU,KAAK,WAAW,EAAE,KAAK,KAAK,EAAE,MAAM,WAAW,CAAC;AACjE,OAAO,KAAK,EAAE,UAAU,IAAI,cAAc,EAAE,MAAM,QAAQ,CAAC;AAC3D,OAAO,EAAE,KAAK,YAAY,EAAE,KAAK,YAAY,EAAQ,MAAM,SAAS,CAAC;AAsMrE;;;;;;GAMG;AACH,wBAAgB,UAAU,IAAI,cAAc,GAAG,IAAI,CAMlD;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,kBAAkB,IAAI,eAAe,CAMpD;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,cAAc,IAAI,WAAW,GAAG,IAAI,CAMnD;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"}
|
package/build/hooks/index.js
CHANGED
|
@@ -1,80 +1,36 @@
|
|
|
1
1
|
import { useSyncExternalStore } from "react";
|
|
2
2
|
import { AppRemote } from "../app-remote";
|
|
3
3
|
import { Auth } from "../auth";
|
|
4
|
+
import { createSyncExternalStore } from "../internal/sync-external-store";
|
|
4
5
|
import { Player } from "../player";
|
|
5
6
|
import { User } from "../user";
|
|
6
7
|
// ---------------------------------------------------------------------------
|
|
7
8
|
// Connection-state store
|
|
8
|
-
//
|
|
9
|
-
// A module-level singleton that caches the latest ConnectionState, exposes a
|
|
10
|
-
// subscribe function for useSyncExternalStore, and stays in sync via the
|
|
11
|
-
// native onConnectionStateChange event. Initialised lazily on first use.
|
|
12
9
|
// ---------------------------------------------------------------------------
|
|
13
|
-
|
|
14
|
-
const _connectionListeners = new Set();
|
|
15
|
-
let _connectionStoreInitialised = false;
|
|
16
|
-
function initConnectionStore() {
|
|
17
|
-
if (_connectionStoreInitialised)
|
|
18
|
-
return;
|
|
19
|
-
_connectionStoreInitialised = true;
|
|
20
|
-
// Seed with the current native state so the first snapshot is accurate.
|
|
10
|
+
const connectionStore = createSyncExternalStore("disconnected", (store) => {
|
|
21
11
|
AppRemote.getConnectionState().then((state) => {
|
|
22
|
-
|
|
23
|
-
_connectionState = state;
|
|
24
|
-
_connectionListeners.forEach((l) => l());
|
|
25
|
-
}
|
|
12
|
+
store.update(state);
|
|
26
13
|
});
|
|
27
14
|
AppRemote.addListener("connectionStateChange", ({ state }) => {
|
|
28
|
-
|
|
29
|
-
_connectionListeners.forEach((l) => l());
|
|
15
|
+
store.update(state);
|
|
30
16
|
});
|
|
31
|
-
}
|
|
32
|
-
function subscribeConnectionState(listener) {
|
|
33
|
-
initConnectionStore();
|
|
34
|
-
_connectionListeners.add(listener);
|
|
35
|
-
return () => _connectionListeners.delete(listener);
|
|
36
|
-
}
|
|
37
|
-
function getConnectionSnapshot() {
|
|
38
|
-
return _connectionState;
|
|
39
|
-
}
|
|
17
|
+
});
|
|
40
18
|
// ---------------------------------------------------------------------------
|
|
41
19
|
// Session store
|
|
42
20
|
// ---------------------------------------------------------------------------
|
|
43
|
-
|
|
44
|
-
const _sessionListeners = new Set();
|
|
45
|
-
let _sessionStoreInitialised = false;
|
|
46
|
-
function initSessionStore() {
|
|
47
|
-
if (_sessionStoreInitialised)
|
|
48
|
-
return;
|
|
49
|
-
_sessionStoreInitialised = true;
|
|
21
|
+
const sessionStore = createSyncExternalStore(null, (store) => {
|
|
50
22
|
Auth.addListener("sessionChange", (event) => {
|
|
51
23
|
if (event.type === "didInitiate" || event.type === "didRenew") {
|
|
52
|
-
|
|
24
|
+
store.update(event.session);
|
|
53
25
|
}
|
|
54
26
|
else {
|
|
55
|
-
|
|
27
|
+
store.update(null);
|
|
56
28
|
}
|
|
57
|
-
_sessionListeners.forEach((l) => l());
|
|
58
29
|
});
|
|
59
|
-
}
|
|
60
|
-
function subscribeSession(listener) {
|
|
61
|
-
initSessionStore();
|
|
62
|
-
_sessionListeners.add(listener);
|
|
63
|
-
return () => _sessionListeners.delete(listener);
|
|
64
|
-
}
|
|
65
|
-
function getSessionSnapshot() {
|
|
66
|
-
return _session;
|
|
67
|
-
}
|
|
30
|
+
});
|
|
68
31
|
// ---------------------------------------------------------------------------
|
|
69
32
|
// Player-state store
|
|
70
|
-
//
|
|
71
|
-
// Seeded from the first playerStateChange event after subscription. The
|
|
72
|
-
// native side automatically starts streaming player state updates once the
|
|
73
|
-
// App Remote connection is established.
|
|
74
33
|
// ---------------------------------------------------------------------------
|
|
75
|
-
let _playerState = null;
|
|
76
|
-
const _playerListeners = new Set();
|
|
77
|
-
let _playerStoreInitialised = false;
|
|
78
34
|
function normalizePlayerState(nextState, previousState) {
|
|
79
35
|
const nextName = nextState.track.name?.trim() ?? "";
|
|
80
36
|
const previousName = previousState?.track.name?.trim() ?? "";
|
|
@@ -93,95 +49,62 @@ function normalizePlayerState(nextState, previousState) {
|
|
|
93
49
|
}
|
|
94
50
|
return nextState;
|
|
95
51
|
}
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
}
|
|
99
|
-
async function hydratePlayerState() {
|
|
52
|
+
let playerHydrationVersion = 0;
|
|
53
|
+
async function hydratePlayerState(store, version) {
|
|
100
54
|
try {
|
|
101
55
|
const state = await Player.getPlayerState();
|
|
102
|
-
|
|
103
|
-
|
|
56
|
+
if (version !== playerHydrationVersion)
|
|
57
|
+
return;
|
|
58
|
+
store.update((previous) => normalizePlayerState(state, previous));
|
|
104
59
|
}
|
|
105
60
|
catch {
|
|
106
61
|
// Ignore one-shot hydration failures (e.g., connection races). Event stream
|
|
107
62
|
// updates still populate this store once available.
|
|
108
63
|
}
|
|
109
64
|
}
|
|
110
|
-
|
|
111
|
-
if (_playerStoreInitialised)
|
|
112
|
-
return;
|
|
113
|
-
_playerStoreInitialised = true;
|
|
114
|
-
// Hydrate immediately if the module is already connected before any consumer
|
|
115
|
-
// subscribes to player events.
|
|
65
|
+
const playerStore = createSyncExternalStore(null, (store) => {
|
|
116
66
|
AppRemote.getConnectionState().then((state) => {
|
|
117
67
|
if (state === "connected") {
|
|
118
|
-
|
|
68
|
+
const version = ++playerHydrationVersion;
|
|
69
|
+
hydratePlayerState(store, version).catch(() => { });
|
|
119
70
|
}
|
|
120
71
|
});
|
|
121
|
-
// Refresh the one-shot snapshot whenever App Remote reconnects, and clear on
|
|
122
|
-
// disconnect so stale "now playing" data is not retained.
|
|
123
72
|
AppRemote.addListener("connectionStateChange", ({ state }) => {
|
|
124
73
|
if (state === "connected") {
|
|
125
|
-
|
|
74
|
+
const version = ++playerHydrationVersion;
|
|
75
|
+
hydratePlayerState(store, version).catch(() => { });
|
|
126
76
|
return;
|
|
127
77
|
}
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
notifyPlayerListeners();
|
|
131
|
-
}
|
|
78
|
+
++playerHydrationVersion;
|
|
79
|
+
store.update((current) => (current === null ? current : null));
|
|
132
80
|
});
|
|
133
81
|
Player.addListener("playerStateChange", (state) => {
|
|
134
|
-
|
|
135
|
-
notifyPlayerListeners();
|
|
82
|
+
store.update((previous) => normalizePlayerState(state, previous));
|
|
136
83
|
});
|
|
137
|
-
}
|
|
138
|
-
function subscribePlayerState(listener) {
|
|
139
|
-
initPlayerStore();
|
|
140
|
-
_playerListeners.add(listener);
|
|
141
|
-
return () => _playerListeners.delete(listener);
|
|
142
|
-
}
|
|
143
|
-
function getPlayerSnapshot() {
|
|
144
|
-
return _playerState;
|
|
145
|
-
}
|
|
84
|
+
});
|
|
146
85
|
// ---------------------------------------------------------------------------
|
|
147
86
|
// Capabilities store
|
|
148
87
|
// ---------------------------------------------------------------------------
|
|
149
|
-
|
|
150
|
-
const _capabilitiesListeners = new Set();
|
|
151
|
-
let _capabilitiesStoreInitialised = false;
|
|
152
|
-
function initCapabilitiesStore() {
|
|
153
|
-
if (_capabilitiesStoreInitialised)
|
|
154
|
-
return;
|
|
155
|
-
_capabilitiesStoreInitialised = true;
|
|
88
|
+
const capabilitiesStore = createSyncExternalStore(null, (store) => {
|
|
156
89
|
User.getCapabilities()
|
|
157
90
|
.then((capabilities) => {
|
|
158
|
-
|
|
159
|
-
_capabilitiesListeners.forEach((l) => l());
|
|
91
|
+
store.update(capabilities);
|
|
160
92
|
})
|
|
161
93
|
.catch(() => {
|
|
162
94
|
// Swallow initial read failures (e.g., not connected yet). The event
|
|
163
95
|
// stream will hydrate this later.
|
|
164
96
|
});
|
|
165
97
|
User.addListener("capabilitiesChange", (capabilities) => {
|
|
166
|
-
|
|
167
|
-
_capabilitiesListeners.forEach((l) => l());
|
|
98
|
+
store.update(capabilities);
|
|
168
99
|
});
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
initCapabilitiesStore();
|
|
172
|
-
_capabilitiesListeners.add(listener);
|
|
173
|
-
return () => _capabilitiesListeners.delete(listener);
|
|
174
|
-
}
|
|
175
|
-
function getCapabilitiesSnapshot() {
|
|
176
|
-
return _capabilities;
|
|
177
|
-
}
|
|
178
|
-
const _libraryStores = new Map();
|
|
100
|
+
});
|
|
101
|
+
const libraryStores = new Map();
|
|
179
102
|
function getOrCreateLibraryStore(uri) {
|
|
180
103
|
const key = String(uri);
|
|
181
|
-
let store =
|
|
104
|
+
let store = libraryStores.get(key);
|
|
182
105
|
if (!store) {
|
|
183
106
|
store = { state: null, listeners: new Set(), initialised: false };
|
|
184
|
-
|
|
107
|
+
libraryStores.set(key, store);
|
|
185
108
|
}
|
|
186
109
|
return store;
|
|
187
110
|
}
|
|
@@ -193,7 +116,7 @@ function initLibraryStore(uri) {
|
|
|
193
116
|
User.getLibraryState(uri)
|
|
194
117
|
.then((state) => {
|
|
195
118
|
store.state = state;
|
|
196
|
-
store.listeners.forEach((
|
|
119
|
+
store.listeners.forEach((listener) => listener());
|
|
197
120
|
})
|
|
198
121
|
.catch(() => {
|
|
199
122
|
// Not connected / unavailable yet; listener updates can still arrive later.
|
|
@@ -201,7 +124,7 @@ function initLibraryStore(uri) {
|
|
|
201
124
|
User.addLibraryStateListener(uri, (state) => {
|
|
202
125
|
const next = getOrCreateLibraryStore(uri);
|
|
203
126
|
next.state = state;
|
|
204
|
-
next.listeners.forEach((
|
|
127
|
+
next.listeners.forEach((listener) => listener());
|
|
205
128
|
});
|
|
206
129
|
}
|
|
207
130
|
function subscribeLibraryState(uri, listener) {
|
|
@@ -210,14 +133,14 @@ function subscribeLibraryState(uri, listener) {
|
|
|
210
133
|
const store = getOrCreateLibraryStore(uri);
|
|
211
134
|
store.listeners.add(listener);
|
|
212
135
|
return () => {
|
|
213
|
-
const current =
|
|
136
|
+
const current = libraryStores.get(key);
|
|
214
137
|
if (!current)
|
|
215
138
|
return;
|
|
216
139
|
current.listeners.delete(listener);
|
|
217
140
|
};
|
|
218
141
|
}
|
|
219
142
|
function getLibrarySnapshot(uri) {
|
|
220
|
-
return
|
|
143
|
+
return libraryStores.get(String(uri))?.state ?? null;
|
|
221
144
|
}
|
|
222
145
|
// ---------------------------------------------------------------------------
|
|
223
146
|
// Public hooks
|
|
@@ -230,7 +153,7 @@ function getLibrarySnapshot(uri) {
|
|
|
230
153
|
* Built on `useSyncExternalStore` for tearing-free React rendering.
|
|
231
154
|
*/
|
|
232
155
|
export function useSession() {
|
|
233
|
-
return useSyncExternalStore(
|
|
156
|
+
return useSyncExternalStore(sessionStore.subscribe, sessionStore.getSnapshot, sessionStore.getSnapshot);
|
|
234
157
|
}
|
|
235
158
|
/**
|
|
236
159
|
* Returns the current App Remote {@link ConnectionState}
|
|
@@ -248,7 +171,7 @@ export function useSession() {
|
|
|
248
171
|
* ```
|
|
249
172
|
*/
|
|
250
173
|
export function useConnectionState() {
|
|
251
|
-
return useSyncExternalStore(
|
|
174
|
+
return useSyncExternalStore(connectionStore.subscribe, connectionStore.getSnapshot, connectionStore.getSnapshot);
|
|
252
175
|
}
|
|
253
176
|
/**
|
|
254
177
|
* Returns the latest {@link PlayerState} from the Spotify app, or `null`
|
|
@@ -270,7 +193,7 @@ export function useConnectionState() {
|
|
|
270
193
|
* ```
|
|
271
194
|
*/
|
|
272
195
|
export function usePlayerState() {
|
|
273
|
-
return useSyncExternalStore(
|
|
196
|
+
return useSyncExternalStore(playerStore.subscribe, playerStore.getSnapshot, playerStore.getSnapshot);
|
|
274
197
|
}
|
|
275
198
|
/**
|
|
276
199
|
* Returns the currently playing {@link Track}, or `null` when nothing is
|
|
@@ -312,7 +235,7 @@ export function usePlaybackPosition() {
|
|
|
312
235
|
* Derived from `User.getCapabilities()` + `User.addListener("capabilitiesChange")`.
|
|
313
236
|
*/
|
|
314
237
|
export function useCapabilities() {
|
|
315
|
-
return useSyncExternalStore(
|
|
238
|
+
return useSyncExternalStore(capabilitiesStore.subscribe, capabilitiesStore.getSnapshot, capabilitiesStore.getSnapshot);
|
|
316
239
|
}
|
|
317
240
|
/**
|
|
318
241
|
* Returns the library state for a specific URI, or `null` before the first
|
package/build/hooks/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/hooks/index.ts"],"names":[],"mappings":"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","sourcesContent":["import { useSyncExternalStore } from \"react\";\n\nimport { AppRemote, type ConnectionState } from \"../app-remote\";\nimport { Auth, type SpotifySession } from \"../auth\";\nimport { Player, type PlayerState, type Track } from \"../player\";\nimport { type Capabilities, type LibraryState, User } from \"../user\";\nimport type { SpotifyURI as SpotifyURIType } from \"../uri\";\n\n// ---------------------------------------------------------------------------\n// Shared utilities\n// ---------------------------------------------------------------------------\n\ntype Listener = () => void;\n\n// ---------------------------------------------------------------------------\n// Connection-state store\n//\n// A module-level singleton that caches the latest ConnectionState, exposes a\n// subscribe function for useSyncExternalStore, and stays in sync via the\n// native onConnectionStateChange event. Initialised lazily on first use.\n// ---------------------------------------------------------------------------\n\nlet _connectionState: ConnectionState = \"disconnected\";\nconst _connectionListeners = new Set<Listener>();\nlet _connectionStoreInitialised = false;\n\nfunction initConnectionStore() {\n if (_connectionStoreInitialised) return;\n _connectionStoreInitialised = true;\n\n // Seed with the current native state so the first snapshot is accurate.\n AppRemote.getConnectionState().then((state) => {\n if (state !== _connectionState) {\n _connectionState = state;\n _connectionListeners.forEach((l) => l());\n }\n });\n\n AppRemote.addListener(\"connectionStateChange\", ({ state }) => {\n _connectionState = state;\n _connectionListeners.forEach((l) => l());\n });\n}\n\nfunction subscribeConnectionState(listener: Listener): () => void {\n initConnectionStore();\n _connectionListeners.add(listener);\n return () => _connectionListeners.delete(listener);\n}\n\nfunction getConnectionSnapshot(): ConnectionState {\n return _connectionState;\n}\n\n// ---------------------------------------------------------------------------\n// Session store\n// ---------------------------------------------------------------------------\n\nlet _session: SpotifySession | null = null;\nconst _sessionListeners = new Set<Listener>();\nlet _sessionStoreInitialised = false;\n\nfunction initSessionStore() {\n if (_sessionStoreInitialised) return;\n _sessionStoreInitialised = true;\n\n Auth.addListener(\"sessionChange\", (event) => {\n if (event.type === \"didInitiate\" || event.type === \"didRenew\") {\n _session = event.session;\n } else {\n _session = null;\n }\n _sessionListeners.forEach((l) => l());\n });\n}\n\nfunction subscribeSession(listener: Listener): () => void {\n initSessionStore();\n _sessionListeners.add(listener);\n return () => _sessionListeners.delete(listener);\n}\n\nfunction getSessionSnapshot(): SpotifySession | null {\n return _session;\n}\n\n// ---------------------------------------------------------------------------\n// Player-state store\n//\n// Seeded from the first playerStateChange event after subscription. The\n// native side automatically starts streaming player state updates once the\n// App Remote connection is established.\n// ---------------------------------------------------------------------------\n\nlet _playerState: PlayerState | null = null;\nconst _playerListeners = new Set<Listener>();\nlet _playerStoreInitialised = false;\n\nfunction normalizePlayerState(\n nextState: PlayerState,\n previousState: PlayerState | null,\n): PlayerState {\n const nextName = nextState.track.name?.trim() ?? \"\";\n const previousName = previousState?.track.name?.trim() ?? \"\";\n const sameTrack =\n previousState != null && previousState.track.uri === nextState.track.uri;\n\n // App Remote can occasionally emit a transient blank title between valid\n // snapshots for the same URI; keep the last non-empty title to avoid UI\n // flicker/regression in hooks consumers.\n if (sameTrack && nextName.length === 0 && previousName.length > 0) {\n return {\n ...nextState,\n track: {\n ...nextState.track,\n name: previousState.track.name,\n },\n };\n }\n\n return nextState;\n}\n\nfunction notifyPlayerListeners() {\n _playerListeners.forEach((l) => l());\n}\n\nasync function hydratePlayerState() {\n try {\n const state = await Player.getPlayerState();\n _playerState = normalizePlayerState(state, _playerState);\n notifyPlayerListeners();\n } catch {\n // Ignore one-shot hydration failures (e.g., connection races). Event stream\n // updates still populate this store once available.\n }\n}\n\nfunction initPlayerStore() {\n if (_playerStoreInitialised) return;\n _playerStoreInitialised = true;\n\n // Hydrate immediately if the module is already connected before any consumer\n // subscribes to player events.\n AppRemote.getConnectionState().then((state) => {\n if (state === \"connected\") {\n void hydratePlayerState();\n }\n });\n\n // Refresh the one-shot snapshot whenever App Remote reconnects, and clear on\n // disconnect so stale \"now playing\" data is not retained.\n AppRemote.addListener(\"connectionStateChange\", ({ state }) => {\n if (state === \"connected\") {\n void hydratePlayerState();\n return;\n }\n\n if (_playerState !== null) {\n _playerState = null;\n notifyPlayerListeners();\n }\n });\n\n Player.addListener(\"playerStateChange\", (state) => {\n _playerState = normalizePlayerState(state, _playerState);\n notifyPlayerListeners();\n });\n}\n\nfunction subscribePlayerState(listener: Listener): () => void {\n initPlayerStore();\n _playerListeners.add(listener);\n return () => _playerListeners.delete(listener);\n}\n\nfunction getPlayerSnapshot(): PlayerState | null {\n return _playerState;\n}\n\n// ---------------------------------------------------------------------------\n// Capabilities store\n// ---------------------------------------------------------------------------\n\nlet _capabilities: Capabilities | null = null;\nconst _capabilitiesListeners = new Set<Listener>();\nlet _capabilitiesStoreInitialised = false;\n\nfunction initCapabilitiesStore() {\n if (_capabilitiesStoreInitialised) return;\n _capabilitiesStoreInitialised = true;\n\n User.getCapabilities()\n .then((capabilities) => {\n _capabilities = capabilities;\n _capabilitiesListeners.forEach((l) => l());\n })\n .catch(() => {\n // Swallow initial read failures (e.g., not connected yet). The event\n // stream will hydrate this later.\n });\n\n User.addListener(\"capabilitiesChange\", (capabilities) => {\n _capabilities = capabilities;\n _capabilitiesListeners.forEach((l) => l());\n });\n}\n\nfunction subscribeCapabilities(listener: Listener): () => void {\n initCapabilitiesStore();\n _capabilitiesListeners.add(listener);\n return () => _capabilitiesListeners.delete(listener);\n}\n\nfunction getCapabilitiesSnapshot(): Capabilities | null {\n return _capabilities;\n}\n\n// ---------------------------------------------------------------------------\n// Per-URI library-state store\n// ---------------------------------------------------------------------------\n\ninterface LibraryStore {\n state: LibraryState | null;\n listeners: Set<Listener>;\n initialised: boolean;\n}\n\nconst _libraryStores = new Map<string, LibraryStore>();\n\nfunction getOrCreateLibraryStore(uri: SpotifyURIType): LibraryStore {\n const key = String(uri);\n let store = _libraryStores.get(key);\n if (!store) {\n store = { state: null, listeners: new Set(), initialised: false };\n _libraryStores.set(key, store);\n }\n return store;\n}\n\nfunction initLibraryStore(uri: SpotifyURIType) {\n const store = getOrCreateLibraryStore(uri);\n if (store.initialised) return;\n store.initialised = true;\n\n User.getLibraryState(uri)\n .then((state) => {\n store.state = state;\n store.listeners.forEach((l) => l());\n })\n .catch(() => {\n // Not connected / unavailable yet; listener updates can still arrive later.\n });\n\n User.addLibraryStateListener(uri, (state) => {\n const next = getOrCreateLibraryStore(uri);\n next.state = state;\n next.listeners.forEach((l) => l());\n });\n}\n\nfunction subscribeLibraryState(uri: SpotifyURIType, listener: Listener): () => void {\n const key = String(uri);\n initLibraryStore(uri);\n const store = getOrCreateLibraryStore(uri);\n store.listeners.add(listener);\n return () => {\n const current = _libraryStores.get(key);\n if (!current) return;\n current.listeners.delete(listener);\n };\n}\n\nfunction getLibrarySnapshot(uri: SpotifyURIType): LibraryState | null {\n return _libraryStores.get(String(uri))?.state ?? null;\n}\n\n// ---------------------------------------------------------------------------\n// Public hooks\n// ---------------------------------------------------------------------------\n\n/**\n * Returns the current Spotify OAuth session, or `null` when not authenticated.\n * Updates automatically whenever `Auth.authenticate()` or `Auth.refresh()`\n * resolves, and on session failure.\n *\n * Built on `useSyncExternalStore` for tearing-free React rendering.\n */\nexport function useSession(): SpotifySession | null {\n return useSyncExternalStore(subscribeSession, getSessionSnapshot, getSessionSnapshot);\n}\n\n/**\n * Returns the current App Remote {@link ConnectionState}\n * (`\"disconnected\"` | `\"connecting\"` | `\"connected\"`). Updates automatically\n * on every state transition driven by `AppRemote.connect()` and `disconnect()`.\n *\n * Built on `useSyncExternalStore` for tearing-free React rendering.\n *\n * @example\n * ```tsx\n * function ConnectionBanner() {\n * const state = useConnectionState();\n * return <Text>{state === \"connected\" ? \"Connected\" : \"Disconnected\"}</Text>;\n * }\n * ```\n */\nexport function useConnectionState(): ConnectionState {\n return useSyncExternalStore(\n subscribeConnectionState,\n getConnectionSnapshot,\n getConnectionSnapshot,\n );\n}\n\n/**\n * Returns the latest {@link PlayerState} from the Spotify app, or `null`\n * before the first update arrives (i.e., before `AppRemote.connect()` resolves\n * and the native subscription emits its first event).\n *\n * Updates on every state change reported by the Spotify app (track change,\n * pause/resume, seek, shuffle/repeat toggle, etc.).\n *\n * Built on `useSyncExternalStore` for tearing-free React rendering.\n *\n * @example\n * ```tsx\n * function NowPlaying() {\n * const state = usePlayerState();\n * if (!state) return <Text>Not playing</Text>;\n * return <Text>{state.track.name} — {state.isPaused ? \"Paused\" : \"Playing\"}</Text>;\n * }\n * ```\n */\nexport function usePlayerState(): PlayerState | null {\n return useSyncExternalStore(subscribePlayerState, getPlayerSnapshot, getPlayerSnapshot);\n}\n\n/**\n * Returns the currently playing {@link Track}, or `null` when nothing is\n * playing or before the first state update arrives.\n *\n * Derived from `usePlayerState`.\n */\nexport function useCurrentTrack(): Track | null {\n return usePlayerState()?.track ?? null;\n}\n\n/**\n * Returns `true` when the Spotify player is actively playing (not paused),\n * and `false` otherwise (including before the first state update arrives).\n *\n * Derived from `usePlayerState`.\n */\nexport function useIsPlaying(): boolean {\n const state = usePlayerState();\n return state !== null && !state.isPaused;\n}\n\n/**\n * Returns the current playback position in milliseconds. Returns `0` before\n * the first state update arrives.\n *\n * **Note:** This value updates whenever the native side emits a player state\n * change (track transitions, pause/resume, seek, etc.) — not on a fixed timer.\n * For a progress bar that ticks smoothly, combine this with a local `Date.now`\n * offset and `requestAnimationFrame`.\n *\n * Derived from `usePlayerState`.\n */\nexport function usePlaybackPosition(): number {\n return usePlayerState()?.playbackPosition ?? 0;\n}\n\n/**\n * Returns the latest Spotify user capabilities, or `null` before the first\n * snapshot arrives.\n *\n * Derived from `User.getCapabilities()` + `User.addListener(\"capabilitiesChange\")`.\n */\nexport function useCapabilities(): Capabilities | null {\n return useSyncExternalStore(\n subscribeCapabilities,\n getCapabilitiesSnapshot,\n getCapabilitiesSnapshot,\n );\n}\n\n/**\n * Returns the library state for a specific URI, or `null` before the first\n * snapshot arrives.\n *\n * Derived from `User.getLibraryState(uri)` + `User.addLibraryStateListener(uri, ...)`.\n */\nexport function useLibraryState(uri: SpotifyURIType): LibraryState | null {\n return useSyncExternalStore(\n (listener) => subscribeLibraryState(uri, listener),\n () => getLibrarySnapshot(uri),\n () => getLibrarySnapshot(uri),\n );\n}\n"]}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/hooks/index.ts"],"names":[],"mappings":"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","sourcesContent":["import { useSyncExternalStore } from \"react\";\n\nimport { AppRemote, type ConnectionState } from \"../app-remote\";\nimport { Auth, type SpotifySession } from \"../auth\";\nimport { createSyncExternalStore } from \"../internal/sync-external-store\";\nimport { Player, type PlayerState, type Track } from \"../player\";\nimport type { SpotifyURI as SpotifyURIType } from \"../uri\";\nimport { type Capabilities, type LibraryState, User } from \"../user\";\n\ntype Listener = () => void;\n\n// ---------------------------------------------------------------------------\n// Connection-state store\n// ---------------------------------------------------------------------------\n\nconst connectionStore = createSyncExternalStore<ConnectionState>(\n \"disconnected\",\n (store) => {\n AppRemote.getConnectionState().then((state) => {\n store.update(state);\n });\n\n AppRemote.addListener(\"connectionStateChange\", ({ state }) => {\n store.update(state);\n });\n },\n);\n\n// ---------------------------------------------------------------------------\n// Session store\n// ---------------------------------------------------------------------------\n\nconst sessionStore = createSyncExternalStore<SpotifySession | null>(\n null,\n (store) => {\n Auth.addListener(\"sessionChange\", (event) => {\n if (event.type === \"didInitiate\" || event.type === \"didRenew\") {\n store.update(event.session);\n } else {\n store.update(null);\n }\n });\n },\n);\n\n// ---------------------------------------------------------------------------\n// Player-state store\n// ---------------------------------------------------------------------------\n\nfunction normalizePlayerState(\n nextState: PlayerState,\n previousState: PlayerState | null,\n): PlayerState {\n const nextName = nextState.track.name?.trim() ?? \"\";\n const previousName = previousState?.track.name?.trim() ?? \"\";\n const sameTrack =\n previousState != null && previousState.track.uri === nextState.track.uri;\n\n // App Remote can occasionally emit a transient blank title between valid\n // snapshots for the same URI; keep the last non-empty title to avoid UI\n // flicker/regression in hooks consumers.\n if (sameTrack && nextName.length === 0 && previousName.length > 0) {\n return {\n ...nextState,\n track: {\n ...nextState.track,\n name: previousState.track.name,\n },\n };\n }\n\n return nextState;\n}\n\nlet playerHydrationVersion = 0;\n\nasync function hydratePlayerState(\n store: ReturnType<typeof createSyncExternalStore<PlayerState | null>>,\n version: number,\n) {\n try {\n const state = await Player.getPlayerState();\n if (version !== playerHydrationVersion) return;\n store.update((previous) => normalizePlayerState(state, previous));\n } catch {\n // Ignore one-shot hydration failures (e.g., connection races). Event stream\n // updates still populate this store once available.\n }\n}\n\nconst playerStore = createSyncExternalStore<PlayerState | null>(\n null,\n (store) => {\n AppRemote.getConnectionState().then((state) => {\n if (state === \"connected\") {\n const version = ++playerHydrationVersion;\n hydratePlayerState(store, version).catch(() => {});\n }\n });\n\n AppRemote.addListener(\"connectionStateChange\", ({ state }) => {\n if (state === \"connected\") {\n const version = ++playerHydrationVersion;\n hydratePlayerState(store, version).catch(() => {});\n return;\n }\n\n ++playerHydrationVersion;\n store.update((current) => (current === null ? current : null));\n });\n\n Player.addListener(\"playerStateChange\", (state) => {\n store.update((previous) => normalizePlayerState(state, previous));\n });\n },\n);\n\n// ---------------------------------------------------------------------------\n// Capabilities store\n// ---------------------------------------------------------------------------\n\nconst capabilitiesStore = createSyncExternalStore<Capabilities | null>(\n null,\n (store) => {\n User.getCapabilities()\n .then((capabilities) => {\n store.update(capabilities);\n })\n .catch(() => {\n // Swallow initial read failures (e.g., not connected yet). The event\n // stream will hydrate this later.\n });\n\n User.addListener(\"capabilitiesChange\", (capabilities) => {\n store.update(capabilities);\n });\n },\n);\n\n// ---------------------------------------------------------------------------\n// Per-URI library-state store\n// ---------------------------------------------------------------------------\n\ninterface LibraryStore {\n state: LibraryState | null;\n listeners: Set<Listener>;\n initialised: boolean;\n}\n\nconst libraryStores = new Map<string, LibraryStore>();\n\nfunction getOrCreateLibraryStore(uri: SpotifyURIType): LibraryStore {\n const key = String(uri);\n let store = libraryStores.get(key);\n if (!store) {\n store = { state: null, listeners: new Set(), initialised: false };\n libraryStores.set(key, store);\n }\n return store;\n}\n\nfunction initLibraryStore(uri: SpotifyURIType) {\n const store = getOrCreateLibraryStore(uri);\n if (store.initialised) return;\n store.initialised = true;\n\n User.getLibraryState(uri)\n .then((state) => {\n store.state = state;\n store.listeners.forEach((listener) => listener());\n })\n .catch(() => {\n // Not connected / unavailable yet; listener updates can still arrive later.\n });\n\n User.addLibraryStateListener(uri, (state) => {\n const next = getOrCreateLibraryStore(uri);\n next.state = state;\n next.listeners.forEach((listener) => listener());\n });\n}\n\nfunction subscribeLibraryState(\n uri: SpotifyURIType,\n listener: Listener,\n): () => void {\n const key = String(uri);\n initLibraryStore(uri);\n const store = getOrCreateLibraryStore(uri);\n store.listeners.add(listener);\n return () => {\n const current = libraryStores.get(key);\n if (!current) return;\n current.listeners.delete(listener);\n };\n}\n\nfunction getLibrarySnapshot(uri: SpotifyURIType): LibraryState | null {\n return libraryStores.get(String(uri))?.state ?? null;\n}\n\n// ---------------------------------------------------------------------------\n// Public hooks\n// ---------------------------------------------------------------------------\n\n/**\n * Returns the current Spotify OAuth session, or `null` when not authenticated.\n * Updates automatically whenever `Auth.authenticate()` or `Auth.refresh()`\n * resolves, and on session failure.\n *\n * Built on `useSyncExternalStore` for tearing-free React rendering.\n */\nexport function useSession(): SpotifySession | null {\n return useSyncExternalStore(\n sessionStore.subscribe,\n sessionStore.getSnapshot,\n sessionStore.getSnapshot,\n );\n}\n\n/**\n * Returns the current App Remote {@link ConnectionState}\n * (`\"disconnected\"` | `\"connecting\"` | `\"connected\"`). Updates automatically\n * on every state transition driven by `AppRemote.connect()` and `disconnect()`.\n *\n * Built on `useSyncExternalStore` for tearing-free React rendering.\n *\n * @example\n * ```tsx\n * function ConnectionBanner() {\n * const state = useConnectionState();\n * return <Text>{state === \"connected\" ? \"Connected\" : \"Disconnected\"}</Text>;\n * }\n * ```\n */\nexport function useConnectionState(): ConnectionState {\n return useSyncExternalStore(\n connectionStore.subscribe,\n connectionStore.getSnapshot,\n connectionStore.getSnapshot,\n );\n}\n\n/**\n * Returns the latest {@link PlayerState} from the Spotify app, or `null`\n * before the first update arrives (i.e., before `AppRemote.connect()` resolves\n * and the native subscription emits its first event).\n *\n * Updates on every state change reported by the Spotify app (track change,\n * pause/resume, seek, shuffle/repeat toggle, etc.).\n *\n * Built on `useSyncExternalStore` for tearing-free React rendering.\n *\n * @example\n * ```tsx\n * function NowPlaying() {\n * const state = usePlayerState();\n * if (!state) return <Text>Not playing</Text>;\n * return <Text>{state.track.name} — {state.isPaused ? \"Paused\" : \"Playing\"}</Text>;\n * }\n * ```\n */\nexport function usePlayerState(): PlayerState | null {\n return useSyncExternalStore(\n playerStore.subscribe,\n playerStore.getSnapshot,\n playerStore.getSnapshot,\n );\n}\n\n/**\n * Returns the currently playing {@link Track}, or `null` when nothing is\n * playing or before the first state update arrives.\n *\n * Derived from `usePlayerState`.\n */\nexport function useCurrentTrack(): Track | null {\n return usePlayerState()?.track ?? null;\n}\n\n/**\n * Returns `true` when the Spotify player is actively playing (not paused),\n * and `false` otherwise (including before the first state update arrives).\n *\n * Derived from `usePlayerState`.\n */\nexport function useIsPlaying(): boolean {\n const state = usePlayerState();\n return state !== null && !state.isPaused;\n}\n\n/**\n * Returns the current playback position in milliseconds. Returns `0` before\n * the first state update arrives.\n *\n * **Note:** This value updates whenever the native side emits a player state\n * change (track transitions, pause/resume, seek, etc.) — not on a fixed timer.\n * For a progress bar that ticks smoothly, combine this with a local `Date.now`\n * offset and `requestAnimationFrame`.\n *\n * Derived from `usePlayerState`.\n */\nexport function usePlaybackPosition(): number {\n return usePlayerState()?.playbackPosition ?? 0;\n}\n\n/**\n * Returns the latest Spotify user capabilities, or `null` before the first\n * snapshot arrives.\n *\n * Derived from `User.getCapabilities()` + `User.addListener(\"capabilitiesChange\")`.\n */\nexport function useCapabilities(): Capabilities | null {\n return useSyncExternalStore(\n capabilitiesStore.subscribe,\n capabilitiesStore.getSnapshot,\n capabilitiesStore.getSnapshot,\n );\n}\n\n/**\n * Returns the library state for a specific URI, or `null` before the first\n * snapshot arrives.\n *\n * Derived from `User.getLibraryState(uri)` + `User.addLibraryStateListener(uri, ...)`.\n */\nexport function useLibraryState(uri: SpotifyURIType): LibraryState | null {\n return useSyncExternalStore(\n (listener) => subscribeLibraryState(uri, listener),\n () => getLibrarySnapshot(uri),\n () => getLibrarySnapshot(uri),\n );\n}\n"]}
|
package/build/images/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
export type { ImagesErrorCode } from "./error";
|
|
2
|
-
export { ImagesError } from "./error";
|
|
3
1
|
import type { ContentItem } from "../content";
|
|
4
2
|
import type { Track } from "../player";
|
|
3
|
+
export type { ImagesErrorCode } from "./error";
|
|
4
|
+
export { ImagesError } from "./error";
|
|
5
5
|
export type ImageSize = "small" | "medium" | "large";
|
|
6
6
|
export interface ImageResult {
|
|
7
7
|
uri: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/images/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/images/index.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE9C,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,WAAW,CAAC;AAGvC,YAAY,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAC/C,OAAO,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAEtC,MAAM,MAAM,SAAS,GAAG,OAAO,GAAG,QAAQ,GAAG,OAAO,CAAC;AAErD,MAAM,WAAW,WAAW;IAC1B,GAAG,EAAE,MAAM,CAAC;CACb;AAED,8DAA8D;AAC9D,UAAU,kBAAkB;IAC1B,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACjC;AAED,6DAA6D;AAC7D,UAAU,gBAAgB;IACxB,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACjC;AAED,MAAM,MAAM,kBAAkB,GAC1B,KAAK,GACL,WAAW,GACX,gBAAgB,GAChB,kBAAkB,CAAC;AAwBvB;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,MAAM;IACjB;;OAEG;0BACQ,kBAAkB,QAAQ,SAAS,KAAG,OAAO,CAAC,WAAW,CAAC;CAM7D,CAAC"}
|