hawk / commit
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);