From 9528a347e0d77da16aefd518e45d4969ea8ec987 Mon Sep 17 00:00:00 2001 From: Kevin Gibbons Date: Sun, 19 Feb 2023 19:24:27 -0800 Subject: [PATCH] Refactor message passing and surface errors to users (#18) * refactor message passing between popup/background * surface errors to user in popup.js * move logic into background.js * split youtube message * handle errors from URL constructor --- extension/background.js | 121 +++++++++++++++++++++++++--------------- extension/index.html | 1 + extension/popup.js | 71 +++++++++++++++-------- extension/script.js | 20 ++++--- extension/style.css | 6 +- 5 files changed, 142 insertions(+), 77 deletions(-) diff --git a/extension/background.js b/extension/background.js index 745d6ef..43a6cd6 100644 --- a/extension/background.js +++ b/extension/background.js @@ -84,44 +84,50 @@ async function getCookieState() { return response; } -// send ping to server, return response +// send ping to server async function verifyConnection() { const path = 'api/ping/'; - let response = await sendGet(path); - console.log('verify connection: ' + JSON.stringify(response)); + let message = await sendGet(path); + console.log('verify connection: ' + JSON.stringify(message)); - return response; + if (message?.response === 'pong') { + return true; + } else if (message?.detail) { + throw new Error(message.detail); + } else { + throw new Error(`got unknown message ${JSON.stringify(message)}`); + } } // send youtube link from injected buttons -async function youtubeLink(youtubeMessage) { - let path; - let payload; - - if (youtubeMessage.action === 'download') { - path = 'api/download/'; - payload = { +async function download(url) { + return await sendData( + 'api/download/', + { data: [ { - youtube_id: youtubeMessage.url, + youtube_id: url, status: 'pending', }, ], - }; - } else if (youtubeMessage.action === 'subscribe') { - path = 'api/channel/'; - payload = { + }, + 'POST' + ); +} + +async function subscribe(url) { + return await sendData( + 'api/channel/', + { data: [ { - channel_id: youtubeMessage.url, + channel_id: url, channel_subscribed: true, }, ], - }; - } - - let response = await sendData(path, payload, 'POST'); - return response; + }, + 'POST' + ); } async function cookieStr(cookieLines) { @@ -172,33 +178,56 @@ async function sendCookies() { return response; } -// process and return message if needed +/* +process and return message if needed +the following messages are supported: +type Message = + | { type: 'verify' } + | { type: 'cookieState' } + | { type: 'sendCookie' } + | { type: 'download', url: string } + | { type: 'subscribe', url: string } +*/ function handleMessage(request, sender, sendResponse) { - console.log('message background.js listener: ' + JSON.stringify(request)); + console.log('message background.js listener got message', request); - if (request.verify === true) { - let response = verifyConnection(); - response.then(message => { - sendResponse(message); + // this function must return the value `true` in chrome to signal the response will be async; + // it cannot return a promise + // so in order to use async/await, we need a wrapper + (async () => { + switch (request.type) { + case 'verify': { + return await verifyConnection(); + } + case 'cookieState': { + return await getCookieState(); + } + case 'sendCookie': { + return await sendCookies(); + } + case 'download': { + return await download(request.url); + } + case 'subscribe': { + return await subscribe(request.url); + } + default: { + let err = new Error(`unknown message type ${JSON.stringify(request.type)}`); + console.log(err); + throw err; + } + } + })() + .then(value => sendResponse({ success: true, value })) + .catch(e => { + console.error(e); + let message = e?.message ?? e; + if (message === 'Failed to fetch') { + // chrome's error message for failed `fetch` is not very user-friendly + message = 'Could not connect to server'; + } + sendResponse({ success: false, value: message }); }); - } else if (request.youtube) { - let response = youtubeLink(request.youtube); - response.then(message => { - sendResponse(message); - }); - } else if (request.cookieState) { - let response = getCookieState(); - response.then(message => { - sendResponse(message); - }); - } else if (request.sendCookie) { - console.log('backgound: ' + JSON.stringify(request)); - let response = sendCookies(); - response.then(message => { - sendResponse(message); - }); - } - return true; } diff --git a/extension/index.html b/extension/index.html index f3f52dd..5dea72a 100644 --- a/extension/index.html +++ b/extension/index.html @@ -26,6 +26,7 @@
+

Options:

diff --git a/extension/popup.js b/extension/popup.js index 9e591d5..a3c60ec 100644 --- a/extension/popup.js +++ b/extension/popup.js @@ -15,29 +15,52 @@ function getBrowser() { return chrome; } } else { - console.log('failed to dedect browser'); + console.log('failed to detect browser'); throw 'browser detection error'; } } +async function sendMessage(message) { + let { success, value } = await browserType.runtime.sendMessage(message); + if (!success) { + throw value; + } + return value; +} + +let errorOut = document.getElementById('error-out'); +function setError(message) { + errorOut.style.display = 'initial'; + errorOut.innerText = message; +} + +function clearError() { + errorOut.style.display = 'none'; +} + // store access details document.getElementById('save-login').addEventListener('click', function () { let url = document.getElementById('full-url').value; if (!url.includes('://')) { url = 'http://' + url; } - let parsed = new URL(url); - let toStore = { - access: { - url: `${parsed.protocol}//${parsed.hostname}`, - port: parsed.port || (parsed.protocol === 'https:' ? '443' : '80'), - apiKey: document.getElementById('api-key').value, - }, - }; - browserType.storage.local.set(toStore, function () { - console.log('Stored connection details: ' + JSON.stringify(toStore)); - pingBackend(); - }); + try { + clearError(); + let parsed = new URL(url); + let toStore = { + access: { + url: `${parsed.protocol}//${parsed.hostname}`, + port: parsed.port || (parsed.protocol === 'https:' ? '443' : '80'), + apiKey: document.getElementById('api-key').value, + }, + }; + browserType.storage.local.set(toStore, function () { + console.log('Stored connection details: ' + JSON.stringify(toStore)); + pingBackend(); + }); + } catch (e) { + setError(e.message); + } }); // verify connection status @@ -52,6 +75,7 @@ document.getElementById('sendCookies').addEventListener('click', function () { function sendCookie() { console.log('popup send cookie'); + clearError(); function handleResponse(message) { console.log('handle cookie response: ' + JSON.stringify(message)); @@ -61,6 +85,7 @@ function sendCookie() { function handleError(error) { console.log(`Error: ${error}`); + setError(error); } let checked = document.getElementById('sendCookies').checked; @@ -75,26 +100,26 @@ function sendCookie() { if (checked === false) { return; } - let sending = browserType.runtime.sendMessage({ sendCookie: true }); + let sending = sendMessage({ type: 'sendCookie' }); sending.then(handleResponse, handleError); } // send ping message to TA backend function pingBackend() { - function handleResponse(message) { - if (message.response === 'pong') { - setStatusIcon(true); - console.log('connection validated'); - } + clearError(); + function handleResponse() { + console.log('connection validated'); + setStatusIcon(true); } function handleError(error) { - console.log(`Error: ${error}`); + console.log(`Verify got error: ${error}`); setStatusIcon(false); + setError(error); } console.log('ping TA server'); - let sending = browserType.runtime.sendMessage({ verify: true }); + let sending = sendMessage({ type: 'verify' }); sending.then(handleResponse, handleError); } @@ -105,6 +130,7 @@ function addUrl(access) { } function setCookieState() { + clearError(); function handleResponse(message) { console.log(message); document.getElementById('sendCookies').checked = message.cookie_enabled; @@ -115,10 +141,11 @@ function setCookieState() { function handleError(error) { console.log(`Error: ${error}`); + setError(error); } console.log('set cookie state'); - let sending = browserType.runtime.sendMessage({ cookieState: true }); + let sending = sendMessage({ type: 'cookieState' }); sending.then(handleResponse, handleError); document.getElementById('sendCookies').checked = true; } diff --git a/extension/script.js b/extension/script.js index 7cbaf55..c9a511b 100644 --- a/extension/script.js +++ b/extension/script.js @@ -22,6 +22,14 @@ function getBrowser() { } } +async function sendMessage(message) { + let { success, value } = await browserType.runtime.sendMessage(message); + if (!success) { + throw value; + } + return value; +} + const downloadIcon = `