···1+import getAccessCode from "./access";
2+import { SpotifyError, throws } from "./errors";
3+import { isNowPlaying, type nowPlaying } from "./types";
4+5+/**
6+ * Wrapper for authorizing a spotify API with default headers etc
7+ * @param url API endpoint to call. Pass a leading slash
8+ * @returns `Response`
9+ * @throws `SpotifyError<NO_AUTH>` when auth fails
10+ * @throws `Response` on non 200-299 status codes
11+ */
12+export async function getSpotifyApi(url: string) {
13+ // get the access code
14+ const accessToken = await getAccessCode();
15+ // check its valid
16+ if (!accessToken)
17+ throw new SpotifyError(
18+ "NO_AUTH",
19+ null,
20+ "Failed to get access code. try using src/pages/_callback",
21+ );
22+23+ // fetch the api and throw on non 2** code
24+ return fetch(`https://api.spotify.com/v1${url}`, {
25+ headers: {
26+ Authorization: `Bearer ${accessToken}`,
27+ },
28+ }).then((res) => (res.ok ? res : throws(res)));
29+}
30+/**
31+ * Get the current playing track
32+ * @returns `nowPlaying`
33+ * @throws `SpotifyError` of NO_AUTH | UNHANDLED_API_ERR | INVALID_AUTH_RES | RATE_LIMITED | NO_CONTENT | MALFORMED_SPOTIFY_RES
34+ */
35+export async function spotifyNowPlaying() {
36+ type success = nowPlaying["item"];
37+ let res: (v: success) => void, rej: (v: unknown) => void;
38+ const output = new Promise<success>((_res, _rej) => {
39+ (res = _res), (rej = _rej);
40+ });
41+ const nowPlaying = getSpotifyApi("/me/player/currently-playing");
42+43+ // auth failed
44+ nowPlaying.catch((err) => {
45+ if (err instanceof SpotifyError && err.code === "NO_AUTH") {
46+ console.error("Authentication failed:", err.human);
47+ rej(err);
48+ }
49+ });
50+51+ /**
52+ * request failed.
53+ * https://developer.spotify.com/documentation/web-api/concepts/api-calls
54+ * 400 Bad Request - The request could not be understood by the server due to malformed syntax. The message body will contain more information; see Response Schema.
55+ * 401 Unauthorized - The request requires user authentication or, if the request included authorization credentials, authorization has been refused for those credentials.
56+ * 403 Forbidden - The server understood the request, but is refusing to fulfill it.
57+ * 404 Not Found - The requested resource could not be found. This error can be due to a temporary or permanent condition.
58+ * 429 Too Many Requests - Rate limiting has been applied.
59+ * 500 Internal Server Error. You should never receive this error because our clever coders catch them all ... but if you are unlucky enough to get one, please report it to us through a comment at the bottom of this page.
60+ * 502 Bad Gateway - The server was acting as a gateway or proxy and received an invalid response from the upstream server.
61+ * 503 Service Unavailable - The server is currently unable to handle the request due to a temporary condition which will be alleviated after some delay. You can choose to resend the request again.
62+ */
63+ nowPlaying.catch((res) => {
64+ switch (res.status) {
65+ // handle req error
66+ case 400: {
67+ rej(new SpotifyError("UNHANDLED_API_ERR", res, "400: Bad request"));
68+ break;
69+ }
70+ case 401: {
71+ rej(new SpotifyError("INVALID_AUTH_RES", res, "401: Unauthorized"));
72+ break;
73+ }
74+ case 403: {
75+ rej(new SpotifyError("UNHANDLED_API_ERR", res, "403: Forbidden"));
76+ break;
77+ }
78+ case 404: {
79+ rej(new SpotifyError("UNHANDLED_API_ERR", res, "404: Not found"));
80+ break;
81+ }
82+ case 429: {
83+ rej(new SpotifyError("RATE_LIMITED", res, "429: Rate Limited"));
84+ break;
85+ }
86+ case 500: {
87+ rej(new SpotifyError("UNHANDLED_API_ERR", res, "500: Internal Error"));
88+ break;
89+ }
90+ case 502: {
91+ rej(new SpotifyError("UNHANDLED_API_ERR", res, "502: Bad Gateway"));
92+ break;
93+ }
94+ case 503: {
95+ rej(
96+ new SpotifyError(
97+ "UNHANDLED_API_ERR",
98+ res,
99+ "503: Service Unavaliable",
100+ ),
101+ );
102+ break;
103+ }
104+ }
105+ });
106+107+ /**
108+ * request succeeded
109+ * https://developer.spotify.com/documentation/web-api/concepts/api-calls
110+ * 200 OK - The request has succeeded. The client can read the result of the request in the body and the headers of the response.
111+ * 201 Created - The request has been fulfilled and resulted in a new resource being created.
112+ * 202 Accepted - The request has been accepted for processing, but the processing has not been completed.
113+ * 204 No Content - The request has succeeded but returns no message body.
114+ */
115+ nowPlaying
116+ .then((res) => {
117+ if (res instanceof Error) return;
118+ switch (res.status) {
119+ // handle 200 codes
120+ case 200: {
121+ return res;
122+ }
123+ case 201: {
124+ rej(new SpotifyError("UNHANDLED_API_ERR", res, "201: Created"));
125+ return;
126+ }
127+ case 202: {
128+ rej(new SpotifyError("UNHANDLED_API_ERR", res, "202: Accepted"));
129+ return;
130+ }
131+ case 204: {
132+ rej(new SpotifyError("NO_CONTENT", res, "204: No Content"));
133+ return;
134+ }
135+ }
136+ })
137+ .then(async (resp) => {
138+ // quit early if it rejected last time
139+ if (!resp) return;
140+ try {
141+ const json = await resp.json();
142+143+144+ // verify structure
145+ if (!isNowPlaying(json)) {
146+ rej(new SpotifyError("MALFORMED_SPOTIFY_RES", json, "Response missing required fields."));
147+ return;
148+ }
149+150+ res(json.item)
151+ } catch (e) {}
152+ });
153+154+ return output;
155+}