fuzzyworld

hawk / commit

Added API cache with a 1 hour expiry. The cache is cleared when the user logs out.

6e3f55a5e9c2cd4591ec02f70ae37ddcd8ec1c6b · Pavel Safronov · 2023-10-13 10:00:28 -0700 · parents 3bf5fcbbf5c9

Added API cache with a 1 hour expiry. The cache is cleared when the user logs out.
 public/api.js     | 18 +++++++++++++
 public/db.js      | 81 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
 public/index.html |  1 +
 public/main.js    |  1 +
 public/signin.js  |  5 +---
 5 files changed, 102 insertions(+), 4 deletions(-)

diff --git a/public/api.js b/public/api.js
index 0402d53..3015aa5 100644
--- a/public/api.js
+++ b/public/api.js
@@ -1,4 +1,13 @@
+const apiExpiry = 3600 * 1000; // 1 hour
+
 async function getAlbums() {
+    const db = await openDb();
+    const cacheKey = 'albums';
+    const albums = await getWithExpiry(db, cacheKey);
+    if (albums) {
+        return albums;
+    }
+
     let accessToken = await getAccessToken();
     const response = await fetch('https://api.spotify.com/v1/me/albums', {
         headers: {
@@ -7,10 +16,18 @@ async function getAlbums() {
     });
 
     const data = await response.json();
+    await setWithExpiry(db, cacheKey, data, apiExpiry);
     return data;
 }
 
 async function getTracks(albumId) {
+    const db = await openDb();
+    const cacheKey = `tracks-${albumId}`;
+    const tracks = await getWithExpiry(db, cacheKey);
+    if (tracks) {
+        return tracks;
+    }
+
     let accessToken = await getAccessToken();
     const response = await fetch(`https://api.spotify.com/v1/albums/${albumId}/tracks`, {
         headers: {
@@ -19,6 +36,7 @@ async function getTracks(albumId) {
     });
 
     const data = await response.json();
+    await setWithExpiry(db, cacheKey, data, apiExpiry);
     return data;
 }
 
diff --git a/public/db.js b/public/db.js
new file mode 100644
index 0000000..e0cf69a
--- /dev/null
+++ b/public/db.js
@@ -0,0 +1,81 @@
+async function openDb() {
+    const promise = new Promise((resolve, reject) => {
+        const openRequest = indexedDB.open("hawkDb", 1);
+
+        openRequest.onupgradeneeded = function (e) {
+            const db = e.target.result;
+            if (!db.objectStoreNames.contains("spotify")) {
+                db.createObjectStore("spotify");
+            }
+        };
+
+        openRequest.onsuccess = function (e) {
+            const db = e.target.result;
+            resolve(db);
+        };
+
+        openRequest.onerror = function (e) {
+            console.error("Error opening IndexedDB:", e);
+            reject(e);
+        };
+    });
+    const db = await promise;
+    return db;
+}
+
+async function deleteDb() {
+    const promise = new Promise((resolve, reject) => {
+        const deleteRequest = indexedDB.deleteDatabase("hawkDb");
+
+        deleteRequest.onsuccess = function (e) {
+            resolve();
+        };
+
+        deleteRequest.onerror = function (e) {
+            console.error("Error deleting IndexedDB:", e);
+            reject(e);
+        };
+    });
+    await promise;
+}
+
+async function setWithExpiry(db, key, value, ttl) {
+    const item = {
+        value: value,
+        expiry: Date.now() + ttl
+    };
+    const transaction = db.transaction("spotify", "readwrite");
+    const store = transaction.objectStore("spotify");
+    store.put(item, key);
+    await transaction.complete;
+}
+
+async function getWithExpiry(db, key) {
+    const transaction = db.transaction("spotify", "readonly");
+    const store = transaction.objectStore("spotify");
+    const promise = new Promise((resolve, reject) => {
+        const request = store.get(key);
+        request.onsuccess = function (e) {
+            resolve(e.target.result);
+        };
+        request.onerror = function (e) {
+            console.error("Error getting from IndexedDB:", e);
+            reject(e);
+        };
+    });
+    const item = await promise;
+    if (!item) {
+        return null;
+    }
+    console.debug(`Cache hit for ${key}`);
+    console.debug(JSON.stringify(item))
+    console.debug(item)
+
+    if (!item) {
+        return null;
+    }
+    if (Date.now() > item.expiry) {
+        return null;
+    }
+    return item.value;
+}
\ No newline at end of file
diff --git a/public/index.html b/public/index.html
index 3670d07..79648ed 100644
--- a/public/index.html
+++ b/public/index.html
@@ -107,6 +107,7 @@
         </div>
     </div>
     <script src="signin.js"></script>
+    <script src="db.js"></script>
     <script src="api.js"></script>
     <script src="template.js"></script>
     <script src="main.js"></script>
diff --git a/public/main.js b/public/main.js
index db6dc58..70e0542 100644
--- a/public/main.js
+++ b/public/main.js
@@ -37,6 +37,7 @@ searchButton.addEventListener('click', async () => {
         const seconds = document.getElementById('seconds-input').value;
         const tracks = await search(minutes, seconds);
         const resultsDiv = document.getElementById('results');
+        resultsDiv.innerHTML = '';
         tracks.forEach(track => {
             const songItem = document.querySelector('#song-item');
             const html = interpolate(songItem.innerHTML, {
diff --git a/public/signin.js b/public/signin.js
index 82e1de1..af87b95 100644
--- a/public/signin.js
+++ b/public/signin.js
@@ -17,6 +17,7 @@ async function signOut() {
     localStorage.removeItem('access_token');
     localStorage.removeItem('refresh_token');
     localStorage.removeItem('expires_at');
+    await deleteDb();
     window.location = getSpotifyRedirectURL();
 }
 
@@ -28,7 +29,6 @@ async function signIn() {
     localStorage.setItem('code_verifier', codeVerifier);
 
     const redirectUrl = getSpotifyRedirectURL();
-    console.log(redirectUrl);
     let args = new URLSearchParams({
         response_type: 'code',
         client_id: clientId,
@@ -46,7 +46,6 @@ async function getToken(code) {
     let codeVerifier = localStorage.getItem('code_verifier');
 
     const redirectUrl = getSpotifyRedirectURL();
-    console.log(redirectUrl);
     let body = new URLSearchParams({
         grant_type: 'authorization_code',
         code: code,
@@ -68,7 +67,6 @@ 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);
@@ -148,7 +146,6 @@ async function refreshToken() {
             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);