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
This commit is contained in:
Kevin Gibbons 2023-02-19 19:24:27 -08:00 committed by GitHub
parent c7069d90cb
commit 9528a347e0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 142 additions and 77 deletions

View File

@ -84,44 +84,50 @@ async function getCookieState() {
return response; return response;
} }
// send ping to server, return response // send ping to server
async function verifyConnection() { async function verifyConnection() {
const path = 'api/ping/'; const path = 'api/ping/';
let response = await sendGet(path); let message = await sendGet(path);
console.log('verify connection: ' + JSON.stringify(response)); 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 // send youtube link from injected buttons
async function youtubeLink(youtubeMessage) { async function download(url) {
let path; return await sendData(
let payload; 'api/download/',
{
if (youtubeMessage.action === 'download') {
path = 'api/download/';
payload = {
data: [ data: [
{ {
youtube_id: youtubeMessage.url, youtube_id: url,
status: 'pending', status: 'pending',
}, },
], ],
}; },
} else if (youtubeMessage.action === 'subscribe') { 'POST'
path = 'api/channel/'; );
payload = { }
async function subscribe(url) {
return await sendData(
'api/channel/',
{
data: [ data: [
{ {
channel_id: youtubeMessage.url, channel_id: url,
channel_subscribed: true, channel_subscribed: true,
}, },
], ],
}; },
} 'POST'
);
let response = await sendData(path, payload, 'POST');
return response;
} }
async function cookieStr(cookieLines) { async function cookieStr(cookieLines) {
@ -172,33 +178,56 @@ async function sendCookies() {
return response; 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) { 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) { // this function must return the value `true` in chrome to signal the response will be async;
let response = verifyConnection(); // it cannot return a promise
response.then(message => { // so in order to use async/await, we need a wrapper
sendResponse(message); (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; return true;
} }

View File

@ -26,6 +26,7 @@
<div class="submit"> <div class="submit">
<button id="save-login">Save</button><span id="status-icon">&#9744;</span> <button id="save-login">Save</button><span id="status-icon">&#9744;</span>
</div> </div>
<div id="error-out"></div>
<hr> <hr>
<p>Options:</p> <p>Options:</p>
<div class="options"> <div class="options">

View File

@ -15,29 +15,52 @@ function getBrowser() {
return chrome; return chrome;
} }
} else { } else {
console.log('failed to dedect browser'); console.log('failed to detect browser');
throw 'browser detection error'; 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 // store access details
document.getElementById('save-login').addEventListener('click', function () { document.getElementById('save-login').addEventListener('click', function () {
let url = document.getElementById('full-url').value; let url = document.getElementById('full-url').value;
if (!url.includes('://')) { if (!url.includes('://')) {
url = 'http://' + url; url = 'http://' + url;
} }
let parsed = new URL(url); try {
let toStore = { clearError();
access: { let parsed = new URL(url);
url: `${parsed.protocol}//${parsed.hostname}`, let toStore = {
port: parsed.port || (parsed.protocol === 'https:' ? '443' : '80'), access: {
apiKey: document.getElementById('api-key').value, 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(); browserType.storage.local.set(toStore, function () {
}); console.log('Stored connection details: ' + JSON.stringify(toStore));
pingBackend();
});
} catch (e) {
setError(e.message);
}
}); });
// verify connection status // verify connection status
@ -52,6 +75,7 @@ document.getElementById('sendCookies').addEventListener('click', function () {
function sendCookie() { function sendCookie() {
console.log('popup send cookie'); console.log('popup send cookie');
clearError();
function handleResponse(message) { function handleResponse(message) {
console.log('handle cookie response: ' + JSON.stringify(message)); console.log('handle cookie response: ' + JSON.stringify(message));
@ -61,6 +85,7 @@ function sendCookie() {
function handleError(error) { function handleError(error) {
console.log(`Error: ${error}`); console.log(`Error: ${error}`);
setError(error);
} }
let checked = document.getElementById('sendCookies').checked; let checked = document.getElementById('sendCookies').checked;
@ -75,26 +100,26 @@ function sendCookie() {
if (checked === false) { if (checked === false) {
return; return;
} }
let sending = browserType.runtime.sendMessage({ sendCookie: true }); let sending = sendMessage({ type: 'sendCookie' });
sending.then(handleResponse, handleError); sending.then(handleResponse, handleError);
} }
// send ping message to TA backend // send ping message to TA backend
function pingBackend() { function pingBackend() {
function handleResponse(message) { clearError();
if (message.response === 'pong') { function handleResponse() {
setStatusIcon(true); console.log('connection validated');
console.log('connection validated'); setStatusIcon(true);
}
} }
function handleError(error) { function handleError(error) {
console.log(`Error: ${error}`); console.log(`Verify got error: ${error}`);
setStatusIcon(false); setStatusIcon(false);
setError(error);
} }
console.log('ping TA server'); console.log('ping TA server');
let sending = browserType.runtime.sendMessage({ verify: true }); let sending = sendMessage({ type: 'verify' });
sending.then(handleResponse, handleError); sending.then(handleResponse, handleError);
} }
@ -105,6 +130,7 @@ function addUrl(access) {
} }
function setCookieState() { function setCookieState() {
clearError();
function handleResponse(message) { function handleResponse(message) {
console.log(message); console.log(message);
document.getElementById('sendCookies').checked = message.cookie_enabled; document.getElementById('sendCookies').checked = message.cookie_enabled;
@ -115,10 +141,11 @@ function setCookieState() {
function handleError(error) { function handleError(error) {
console.log(`Error: ${error}`); console.log(`Error: ${error}`);
setError(error);
} }
console.log('set cookie state'); console.log('set cookie state');
let sending = browserType.runtime.sendMessage({ cookieState: true }); let sending = sendMessage({ type: 'cookieState' });
sending.then(handleResponse, handleError); sending.then(handleResponse, handleError);
document.getElementById('sendCookies').checked = true; document.getElementById('sendCookies').checked = true;
} }

View File

@ -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 = `<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" const downloadIcon = `<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 500 500" style="enable-background:new 0 0 500 500;" xml:space="preserve"> viewBox="0 0 500 500" style="enable-background:new 0 0 500 500;" xml:space="preserve">
<style type="text/css"> <style type="text/css">
@ -362,18 +370,14 @@ function sendUrl(url, action, button) {
function handleError(error) { function handleError(error) {
console.log('error'); console.log('error');
console.log(JSON.stringify(error)); console.log(JSON.stringify(error));
buttonError(button);
} }
let payload = { let message = { type: action, url };
youtube: {
url: url,
action: action,
},
};
console.log('youtube link: ' + JSON.stringify(payload)); console.log('youtube link: ' + JSON.stringify(message));
let sending = browserType.runtime.sendMessage(payload); let sending = sendMessage(message);
sending.then(handleResponse, handleError); sending.then(handleResponse, handleError);
} }

View File

@ -77,3 +77,7 @@ hr {
.icons img { .icons img {
width: 25px; width: 25px;
} }
#error-out {
color: red;
display: none; /* will be made visible when an error occurs */
}