Personal Site

Create instance of the sdk object.

In order to ensure that this works even if the server crashes and the token expires before the server reboots, after each request the refresh token is written to `./.refreshToken`.
When the sdk is required, first the refresh token is loaded from the file, before being used to make a raw API request to get a new access token. This response is validated to make sure all required fields are present before being passed to the sdk.
If any of this fails, an unhandled exception is thrown since this is unrecoverable but should only run at server start

vielle.dev 862b22fa c3ab9fd8

verified
+75
+75
src/components/home/playing/spotify/index.ts
··· 2 2 * types and logic for getting now playing information 3 3 */ 4 4 5 + import { SpotifyApi, type AccessToken } from "@spotify/web-api-ts-sdk"; 6 + import { SPOTIFY_CLIENT_ID, SPOTIFY_CLIENT_SECRET } from "astro:env/server"; 7 + 8 + import fs from "node:fs/promises"; 9 + import { isObj } from "/utils"; 10 + 11 + /** 12 + * the refresh_token field is not checked as 13 + */ 14 + const isSpotifyAccessToken = (token: any): token is AccessToken => 15 + isObj(token) && 16 + "access_token" in token && 17 + typeof token.access_token === "string" && 18 + "token_type" in token && 19 + token.token_type === "Bearer" && 20 + "expires_in" in token && 21 + typeof token.expires_in === "number"; 22 + 23 + // try load last known refresh token from file 24 + const refreshToken = await fs 25 + .readFile("./.refreshToken", { encoding: "utf-8" }) 26 + .then((token) => (token ? token : console.log("invalid `./.refreshToken`"))) 27 + // if anything errors its undefined 28 + .catch((err) => console.error(err)); 29 + 30 + // if refreshToken is undefined then we dont have a valid one saved, and can request the user obtain one 31 + // (this could be corruption, failed save, or missing file) 32 + if (!refreshToken) 33 + throw new Error( 34 + "No access token is stored in `./.refreshToken`. Please generate one using the `/callback` endpoint in a dev server.", 35 + ); 36 + 37 + const accessToken = await fetch("https://accounts.spotify.com/api/token", { 38 + method: "post", 39 + 40 + headers: { 41 + "content-type": "application/x-www-form-urlencoded", 42 + Authorization: 43 + "Basic " + 44 + Buffer.from(SPOTIFY_CLIENT_ID + ":" + SPOTIFY_CLIENT_SECRET).toString( 45 + "base64", 46 + ), 47 + }, 48 + 49 + body: new URLSearchParams({ 50 + grant_type: "refresh_token", 51 + refresh_token: refreshToken, 52 + }).toString(), 53 + }) 54 + .then((res) => res.json()) 55 + .then((token) => 56 + isSpotifyAccessToken(token) 57 + ? { 58 + ...token, 59 + // if no refresh_token is provided then insert it 60 + ...(token.refresh_token ? {} : { refresh_token: refreshToken }), 61 + } 62 + : console.error("Response was not a valid access token:", token), 63 + ) 64 + .catch((err) => console.error(err)); 65 + 66 + if (!accessToken) 67 + throw new Error( 68 + "Could not generate a new access token from the refresh token", 69 + ); 70 + 71 + export const sdk = SpotifyApi.withAccessToken(SPOTIFY_CLIENT_ID, accessToken, { 72 + async afterRequest() { 73 + const token = await sdk.getAccessToken(); 74 + if (!token) return; 75 + fs.writeFile("./.refreshToken", token.refresh_token, { 76 + encoding: "utf-8", 77 + }); 78 + }, 79 + });