Skip to content

Instantly share code, notes, and snippets.

@GregBrimble
Created February 27, 2021 11:14
Show Gist options
  • Save GregBrimble/a5cfdeb78b7c1b77e351ee32cf758d70 to your computer and use it in GitHub Desktop.
Save GregBrimble/a5cfdeb78b7c1b77e351ee32cf758d70 to your computer and use it in GitHub Desktop.
addEventListener('fetch', (event) => {
event.respondWith(handleErrors(handleRequest, event.request))
})
const handleRequest = async (request) => {
const url = new URL(request.url)
switch (url.pathname) {
case '/': {
const newReleases = await spotify('/new-releases')
// If we want to return just the data we get back from Spotify,
// we can just return the following:
// return new Response(JSON.stringify(newReleases, { headers: { 'Content-Type': 'application/json' }}))
// But instead, let's render it nicely-ish:
return new Response(`<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<title>Spotify's New Releases</title>
</head>
<body>
<h1>New Releases on Spotify</h1>
<ul>
${newReleases.albums.items.map((album) => `<li>${renderAlbum(album)}</li>`)}
</ul>
</body>
</html>`, { headers: { 'Content-Type': 'text/html' }})
}
default: {
return new Response("Not Found", { status: 404 })
}
}
}
/***
* A collection of helper functions for rendering out the page.
*/
const renderLinkable = (item) => `<a href="${item.external_urls.spotify}">${item.name}</a>`
const renderAlbumArtwork = (album) => `<img src="${album.images[0].url}" width="250" height="250" />`
const renderAlbum = (album) => `<div>${renderAlbumArtwork(album)}<p>${renderLinkable(album)} — ${album.artists.map((artist) => renderLinkable(artist)).join(', ')}</p></div>`
/***
* Since we might want to make multiple requests to Spotify,
* let's abstract out something which can call the Spotify API for us.
*/
const BASE_URL = 'https://api.spotify.com/v1/browse'
const spotify = async (path) => {
const url = `${BASE_URL}${path}`
const accessToken = await getAccessToken()
const response = await fetch(url, {
headers: {
'Authorization': `Bearer ${accessToken}`
}
})
return await response.json()
}
/***
* Authorization
*
* We're following the simplest 'client credentials' flow:
* https://developer.spotify.com/documentation/general/guides/authorization-guide/#client-credentials-flow
*/
const AUTHORIZE_URL = 'https://accounts.spotify.com/api/token'
const getAccessToken = async () => {
const clientCredentials = btoa(`${SPOTIFY_CLIENT_ID}:${SPOTIFY_CLIENT_SECRET}`)
const response = await fetch(AUTHORIZE_URL, {
method: 'POST',
headers: {
'Authorization': `Basic ${clientCredentials}`,
'Content-Type': 'application/x-www-form-urlencoded'
},
body: new URLSearchParams({
grant_type: "client_credentials"
})
})
const json = await response.json()
return json.access_token
}
/***
* Error handling
*
* In case there's a problem, let's return a readable error message.
*/
const handleErrors = (func, request) => {
try {
return func(request)
} catch (error) {
const { message, stack } = error
return new Response(JSON.stringify({ message, stack }), { headers: { 'Content-Type': 'application/json' }})
}
}
// Polyfill for btoa (ignore me!)
const btoa = (data) => {
const ascii = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='
const len = data.length - 1
let i = -1
let b64 = ''
while (i < len) {
const code = data.charCodeAt(++i) << 16 | data.charCodeAt(++i) << 8 | data.charCodeAt(++i);
b64 += ascii[(code >>> 18) & 63] + ascii[(code >>> 12) & 63] + ascii[(code >>> 6) & 63] + ascii[code & 63];
}
const pads = data.length % 3;
if (pads > 0) {
b64 = b64.slice(0, pads - 3);
while (b64.length % 4 !== 0) {
b64 += '=';
}
}
return b64;
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment