hawk / commit
Added refresh capability, consolidated access token lookups, moved some code around.
public/api.js | 53 +++-------------------------------------------- public/main.js | 24 +++++++++++++++++++++ public/signin.js | 63 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 90 insertions(+), 50 deletions(-) diff --git a/public/api.js b/public/api.js index 615efe6..0402d53 100644 --- a/public/api.js +++ b/public/api.js @@ -1,10 +1,5 @@ async function getAlbums() { - let accessToken = localStorage.getItem('access_token'); - if (!accessToken) { - console.error('No access token found'); - return; - } - + let accessToken = await getAccessToken(); const response = await fetch('https://api.spotify.com/v1/me/albums', { headers: { Authorization: 'Bearer ' + accessToken @@ -16,12 +11,7 @@ async function getAlbums() { } async function getTracks(albumId) { - let accessToken = localStorage.getItem('access_token'); - if (!accessToken) { - console.error('No access token found'); - return; - } - + let accessToken = await getAccessToken(); const response = await fetch(`https://api.spotify.com/v1/albums/${albumId}/tracks`, { headers: { Authorization: 'Bearer ' + accessToken @@ -33,12 +23,7 @@ async function getTracks(albumId) { } async function playTrack(trackId) { - let accessToken = localStorage.getItem('access_token'); - if (!accessToken) { - console.error('No access token found'); - return; - } - + let accessToken = await getAccessToken(); console.info(`Playing track ${trackId}`); await fetch(`https://api.spotify.com/v1/me/player/play`, { method: 'PUT', @@ -50,35 +35,3 @@ async function playTrack(trackId) { }) }); } - -async function search(minutes, seconds) { - let accessToken = localStorage.getItem('access_token'); - if (!accessToken) { - console.error('No access token found'); - return; - } - - const albums = await getAlbums(); - // console.log(albums); - - const foundTracks = []; - for (let i = 0; i < albums.items.length; i++) { - const album = albums.items[i]; - const tracks = await getTracks(album.album.id); - // console.log(tracks); - - for (let j = 0; j < tracks.items.length; j++) { - const track = tracks.items[j]; - const duration = track.duration_ms; - const durationInSeconds = Math.floor(duration / 1000); - const durationInMinutes = Math.floor(durationInSeconds / 60); - const durationInRemainingSeconds = durationInSeconds % 60; - - if (durationInMinutes == minutes && durationInRemainingSeconds == seconds) { - console.log(track); - foundTracks.push(track); - } - } - } - return foundTracks; -} \ No newline at end of file diff --git a/public/main.js b/public/main.js index 61f8b79..db6dc58 100644 --- a/public/main.js +++ b/public/main.js @@ -66,3 +66,27 @@ function formatDuration(ms) { // Return formatted string return `${minutes}:${formattedSeconds}`; } + +async function search(minutes, seconds) { + const albums = await getAlbums(); + + const foundTracks = []; + for (let i = 0; i < albums.items.length; i++) { + const album = albums.items[i]; + const tracks = await getTracks(album.album.id); + + for (let j = 0; j < tracks.items.length; j++) { + const track = tracks.items[j]; + const duration = track.duration_ms; + const durationInSeconds = Math.floor(duration / 1000); + const durationInMinutes = Math.floor(durationInSeconds / 60); + const durationInRemainingSeconds = durationInSeconds % 60; + + if (durationInMinutes == minutes && durationInRemainingSeconds == seconds) { + console.log(track); + foundTracks.push(track); + } + } + } + return foundTracks; +} \ No newline at end of file diff --git a/public/signin.js b/public/signin.js index f6f2136..82e1de1 100644 --- a/public/signin.js +++ b/public/signin.js @@ -1,5 +1,7 @@ const clientId = '6c4c04a3751b4e409ddad7d71aad9949'; const scope = 'user-library-read user-modify-playback-state'; +const prematureRefreshSeconds = 60 * 5; // 5 minutes before expiration + function getSpotifyRedirectURL() { // If the current hostname is 'localhost' if (window.location.hostname === 'localhost') { @@ -13,6 +15,8 @@ function getSpotifyRedirectURL() { async function signOut() { localStorage.removeItem('access_token'); + localStorage.removeItem('refresh_token'); + localStorage.removeItem('expires_at'); window.location = getSpotifyRedirectURL(); } @@ -64,7 +68,11 @@ async function getToken(code) { throw new Error('HTTP status ' + response.status); } const data = await response.json(); + console.log(data); + const expiresInSeconds = data.expires_in; localStorage.setItem('access_token', data.access_token); + localStorage.setItem('refresh_token', data.access_token); + localStorage.setItem('expires_at', Date.now() + expiresInSeconds * 1000); } catch (error) { console.error('Error:', error); } @@ -94,3 +102,58 @@ async function generateCodeChallenge(codeVerifier) { return base64encode(digest); } + +async function getAccessToken() { + const accessToken = localStorage.getItem('access_token'); + if (!accessToken) { + throw 'No access token found'; + } + + const expiresAtText = localStorage.getItem('expires_at'); + if (!expiresAtText) { + throw 'No expiration found'; + } + const expiresAt = parseInt(expiresAtText); + const shouldRefresh = Date.now() > expiresAt - prematureRefreshSeconds * 1000; + if (shouldRefresh) { + console.log('Refreshing access token'); + accessToken = await refreshToken(); + } + + return accessToken; +} + +async function refreshToken() { + const refreshToken = localStorage.getItem('refresh_token'); + if (!refreshToken) { + throw 'No refresh token found'; + } + + let body = new URLSearchParams({ + grant_type: 'refresh_token', + refresh_token: refreshToken, + client_id: clientId + }); + + try { + const response = await fetch('https://accounts.spotify.com/api/token', { + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded' + }, + body: body + }); + if (!response.ok) { + console.error('HTTP status ' + response.status); + throw new Error('HTTP status ' + response.status); + } + const data = await response.json(); + console.log(data); + const expiresInSeconds = data.expires_in; + localStorage.setItem('access_token', data.access_token); + localStorage.setItem('expires_at', Date.now() + expiresInSeconds * 1000); + return data.access_token; + } catch (error) { + console.error('Error:', error); + } +} \ No newline at end of file