fuzzyworld

hawk / commit

Added refresh capability, consolidated access token lookups, moved some code around.

3bf5fcbbf5c93bcf3978a3546a7afb2569119bd1 · Pavel Safronov · 2023-10-13 09:17:32 -0700 · parents 42c1ee822a8c

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