diff --git a/README.md b/README.md index d6117ac..012d731 100644 --- a/README.md +++ b/README.md @@ -74,3 +74,4 @@ Build a repo which just shows the current time or a random number or something. - Replaced track ID web search lookup with a Spotify AppleScript command - Rewrote to use the Spotify API directly instead of via the Heroku app - Upgraded to latest Electron and started using ESM and TLA +- Implemented a re-authorization flow for obtaining the new token after a 401 diff --git a/index.js b/index.js index 2d24369..0415885 100644 --- a/index.js +++ b/index.js @@ -2,51 +2,14 @@ import electron from 'electron'; import timers from 'timers/promises'; import fs from 'fs'; import askSpotify from './askSpotify.js'; +import promptAuthorization from './promptAuthorization.js'; electron.app.on('ready', async () => { // Prevent "exited with signal SIGINT" to be printed to the console // Note that this must be in the `ready` event handler process.on("SIGINT", () => { }); - /** @type {string} */ - let authorization; - - const path = `token.json`; - try { - await fs.promises.access(path); - - console.log('Loading bearer token…'); - authorization = JSON.parse(await fs.promises.readFile(path)); - console.log('Loaded bearer token'); - } - catch (error) { - if (error.code !== 'ENOENT') { - console.log(`Failed to load bearer token: ${error}`); - } - - console.log('Obtaining bearer token…'); - const window = new electron.BrowserWindow({ width: 800, height: 600 }); - window.loadURL('https://open.spotify.com/'); - - authorization = await new Promise((resolve) => { - electron.session.defaultSession.webRequest.onSendHeaders( - { urls: ['https://gew4-spclient.spotify.com/*'] }, - (details) => { - if (authorization) { - return; - } - - if (details.requestHeaders['authorization']) { - resolve(details.requestHeaders['authorization']); - } - } - ); - }); - - window.close(); - await fs.promises.writeFile(path, JSON.stringify(authorization, null, 2)); - console.log('Obtained bearer token'); - } + let authorization = await promptAuthorization(); // Hide the Dock icon for the application electron.app.dock.hide(); @@ -167,7 +130,7 @@ electron.app.on('ready', async () => { } // Download new lyrics if we don't already have them - if (lyrics?.artist !== artist || lyrics?.song !== song) { + if (authorization && (lyrics?.artist !== artist || lyrics?.song !== song)) { const path = `lyrics/${artist} - ${song}.json`; try { await fs.promises.access(path); @@ -206,7 +169,17 @@ electron.app.on('ready', async () => { console.log(`Downloaded lyrics for ${artist} - ${song} (${id})`); } else { - lyrics = { artist, song, error: response.statusText }; + lyrics = { artist, song, error: response.status + ' ' + response.statusText }; + + // Force the user to re-authenticate if the token is invalid + if (response.status === 401) { + // Reset the field while re-authenticating to prevent multiple prompts + authorization = undefined; + authorization = await promptAuthorization(true); + + // Reset the lyrics so they are re-tried with the new token + lyrics = undefined; + } } } diff --git a/promptAuthorization.js b/promptAuthorization.js new file mode 100644 index 0000000..1e8f971 --- /dev/null +++ b/promptAuthorization.js @@ -0,0 +1,57 @@ +import fs from 'fs'; +import electron from 'electron'; + +export default async function promptAuthorization(force = false) { + /** @type {string} */ + let authorization; + + const path = `token.json`; + if (force) { + try { + await fs.promises.unlink(path); + } + catch (error) { + if (error.code !== 'ENOENT') { + console.log(`Failed to delete bearer token: ${error}`); + } + } + } + + try { + await fs.promises.access(path); + + console.log('Loading bearer token…'); + authorization = JSON.parse(await fs.promises.readFile(path)); + console.log('Loaded bearer token'); + } + catch (error) { + if (error.code !== 'ENOENT') { + console.log(`Failed to load bearer token: ${error}`); + } + + console.log('Obtaining bearer token…'); + const window = new electron.BrowserWindow({ width: 1024, height: 800 }); + window.loadURL('https://open.spotify.com/'); + + authorization = await new Promise((resolve) => { + electron.session.defaultSession.webRequest.onSendHeaders( + { urls: ['https://gew4-spclient.spotify.com/*'] }, + (details) => { + if (authorization) { + return; + } + + if (details.requestHeaders['authorization']) { + resolve(details.requestHeaders['authorization']); + } + } + ); + }); + + window.close(); + await fs.promises.writeFile(path, JSON.stringify(authorization, null, 2)); + console.log('Obtained bearer token'); + } + + return authorization; +}