diff --git a/.github/funding.yml b/.github/funding.yml index cb9f753..95bc633 100644 --- a/.github/funding.yml +++ b/.github/funding.yml @@ -1,2 +1,2 @@ -github: bellingcat -custom: https://www.patreon.com/bellingcat +# github: bellingcat +# custom: https://www.patreon.com/bellingcat diff --git a/.gitignore b/.gitignore index b06d57e..b5cc7c2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ node_modules distribution .parcel-cache +distribution.zip \ No newline at end of file diff --git a/media/screenshot-01.png b/media/screenshot-01.png new file mode 100644 index 0000000..9d97c16 Binary files /dev/null and b/media/screenshot-01.png differ diff --git a/package.json b/package.json index 1ec83b1..9f4bb91 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "private": true, "scripts": { - "build": "rm -rf distribution && parcel build source/manifest.json --no-content-hash --no-source-maps --dist-dir distribution --no-cache --detailed-report 0", + "build": "rm -rf distribution && rm distribution.zip && parcel build source/manifest.json --no-content-hash --no-source-maps --dist-dir distribution --no-cache --detailed-report 0 && zip -r distribution.zip distribution", "lint": "run-p lint:*", "lint-fix": "run-p 'lint:* -- --fix'", "lint:css": "stylelint source/**/*.css", diff --git a/source/css/popup.css b/source/css/popup.css index 9779273..720dd51 100644 --- a/source/css/popup.css +++ b/source/css/popup.css @@ -1,33 +1,33 @@ body { - font-size: 100%; + font-size: 100%; } #app { - min-width: 40em; - margin: 15px; + min-width: 40em; + margin: 15px; } #icon { - max-height: 26px; - vertical-align: middle; + max-height: 26px; + vertical-align: middle; } -#archiveResults .row{ - /* table-layout: fixed; */ - width:90%; - max-width:100px; +#archive-results .row { + /* table-layout: fixed; */ + width: 90%; + max-width: 100px; } -/* #archiveResults td { +/* #archive-results td { width: auto; } -#archiveResults td:nth-child(2) { +#archive-results td:nth-child(2) { width: 150px; } */ table td { - word-wrap: break-word; - overflow-wrap: break-word; - padding: 5px; -} \ No newline at end of file + word-wrap: break-word; + overflow-wrap: break-word; + padding: 5px; +} diff --git a/source/img/ben-archiver-128.png b/source/img/ben-archiver-128.png new file mode 100644 index 0000000..b67af84 Binary files /dev/null and b/source/img/ben-archiver-128.png differ diff --git a/source/img/ben-archiver.png b/source/img/ben-archiver.png new file mode 100644 index 0000000..d85e672 Binary files /dev/null and b/source/img/ben-archiver.png differ diff --git a/source/img/icon.png b/source/img/icon.png deleted file mode 100644 index a490506..0000000 Binary files a/source/img/icon.png and /dev/null differ diff --git a/source/js/background.js b/source/js/background.js index 0a03ed6..373bf35 100644 --- a/source/js/background.js +++ b/source/js/background.js @@ -1,76 +1,86 @@ -// eslint-disable-next-line import/no-unassigned-import -// import './options-storage.js'; + +// Import './options-storage.js'; import optionsStorage from './options-storage.js'; // TODO: stable ID https://developer.chrome.com/docs/extensions/mv3/tut_oauth/ -// TODO: API_ENDPOINT depending on deployment -const API_ENDPOINT = 'http://localhost:8000/tasks' +const API_ENDPOINT = 'http://localhost:8004/tasks' +// const API_ENDPOINT = 'http://134.122.58.133:8004/tasks'; chrome.runtime.onMessage.addListener(((r, s, sR) => { - processMessages(r, s, sR) - return true; // needed for sendResponse to be async + processMessages(r, s, sR); + return true; // Needed for sendResponse to be async })); async function processMessages(request, sender, sendResponse) { - console.info(`action {${request.action}} from ${sender.tab ? 'content-script (' + sender.tab.url + ')' : 'the extension'}`) - chrome.identity.getAuthToken({ interactive: true }, async function (access_token) { - console.log(access_token); - if (request.action === "archive") { - archiveUrl(sendResponse, access_token); - } else if (request.action === "search") { - const tasks = await search(request.query, access_token); - sendResponse(tasks); - } else if (request.action === "status") { - const task_db = await getTaskById(request.task.task_id); - if (task_db?.status == "SUCCESS" || task_db?.status == 'FAILURE') { - console.log("ALREADY FINSIHED, NO REQS") - sendResponse(task_db) + console.info(`action {${request.action}} from ${sender.tab ? 'content-script (' + sender.tab.url + ')' : 'the extension'}`); + chrome.identity.getAuthToken({ interactive: true }, async accessToken => { + switch (request.action) { + case 'archive': { + archiveUrl(sendResponse, accessToken); + break; } - const task_fresh = await checkTaskStatus(request.task, access_token) - sendResponse(task_fresh) - } else if (request.action === "getTasks") { - sendResponse(await getAllTasks()); + case 'search': { + const tasks = await search(request.query, accessToken); + sendResponse(tasks); + break; + } + case 'status': { + const taskDb = await getTaskById(request.task.task_id); + if (taskDb?.status === 'SUCCESS' || taskDb?.status === 'FAILURE'|| taskDb?.status === 'REVOKED') { + console.log('ALREADY FINSIHED, NO REQS'); + sendResponse(taskDb); + } else { + const taskFresh = await checkTaskStatus(request.task, accessToken); + sendResponse(taskFresh); + } + break; + } + case 'getTasks': { + sendResponse(await getAllTasks()); + break; + } + // No default } }); } -function archiveUrl(sendResponse, access_token) { +function archiveUrl(sendResponse, accessToken) { chrome.tabs.query({ active: true, - lastFocusedWindow: true - }, async (tabs) => { - let url = tabs[0].url; + lastFocusedWindow: true, + }, async tabs => { + const url = tabs[0].url; console.log(`url=${url}`); - const response = await searchTask(url, access_token); - const new_archive = { url, task_id: response.task_id, status: 'PENDING', result: {} }; - await upsertTask(new_archive); - sendResponse(new_archive); + const response = await searchTask(url, accessToken); + const newArchive = { url, task_id: response.task_id, status: 'PENDING', result: {} }; + await upsertTask(newArchive); + sendResponse(newArchive); }); } -function searchTask(url, access_token) { - console.log(`API: SUBMIT`) +function searchTask(url, accessToken) { + console.log('API: SUBMIT'); return new Promise((resolve, reject) => { fetch(API_ENDPOINT, { method: 'POST', headers: { 'Content-Type': 'application/json', }, - body: JSON.stringify({ url, access_token }), + body: JSON.stringify({ url, access_token: accessToken }), }).then( response => response.json(), - ).then(response => resolve(response) - ).catch(err => { - console.log(`There was an error: ${err}`) - reject(err) + ).then(response => resolve(response), + ).catch(error => { + console.log(`There was an error: ${error}`); + reject(error); }); - }) + }); } -function checkTaskStatus(task, access_token) { - console.log(`API: STATUS`) +function checkTaskStatus(task, accessToken) { + console.log('API: STATUS'); return new Promise((resolve, reject) => { - fetch(`${API_ENDPOINT}/${task.task_id}?` + new URLSearchParams({ access_token }), { + fetch(`${API_ENDPOINT}/${task.task_id}?` + new URLSearchParams({ access_token: accessToken }), { method: 'GET', headers: { 'Content-Type': 'application/json', @@ -82,47 +92,47 @@ function checkTaskStatus(task, access_token) { url: task.url, task_id: response.task_id, status: response.task_status, - result: JSON.parse(response.task_result), - } - console.log(new_task); + result: typeof response.task_result == "object" ? response.task_result : JSON.parse(response.task_result), + }; + console.log(`status ${new_task.url}: ${new_task.task_id}`); upsertTask(new_task); - resolve(new_task) - } - ).catch(err => reject(err)); - }) + resolve(new_task); + }, + ).catch(error => reject(error)); + }); } -function search(query, access_token) { - console.log(`API: SEARCH`) +function search(query, accessToken) { + console.log('API: SEARCH'); return new Promise((resolve, reject) => { - fetch(`${API_ENDPOINT}/search?` + new URLSearchParams({ access_token, query }), { + fetch(`${API_ENDPOINT}/search?` + new URLSearchParams({ access_token: accessToken, query }), { method: 'GET', headers: { 'Content-Type': 'application/json', }, }).then( response => response.json(), - ).then(response => resolve(response) - ).catch(err => { - console.log(`There was an error: ${err}`) - reject(err) + ).then(response => resolve(response), + ).catch(error => { + console.log(`There was an error: ${error}`); + reject(error); }); - }) + }); } async function getAllTasks() { const storage = await optionsStorage.getAll(); - return storage.archived_urls; + return storage.archivedUrls; } -//TODO: improve with less reads from storage +// TODO: improve with less reads from storage async function upsertTask(task) { const storage = await optionsStorage.getAll(); - storage.archived_urls[task.task_id] = task; + storage.archivedUrls[task.task_id] = task; await optionsStorage.set(storage); } async function getTaskById(task) { const storage = await optionsStorage.getAll(); - return storage.archived_urls[task.task_id]; -} \ No newline at end of file + return storage.archivedUrls[task.task_id]; +} diff --git a/source/js/options-storage.js b/source/js/options-storage.js index 5452186..1ac93e2 100644 --- a/source/js/options-storage.js +++ b/source/js/options-storage.js @@ -2,7 +2,7 @@ import OptionsSync from 'webext-options-sync'; export default new OptionsSync({ defaults: { - archived_urls: {}, + archivedUrls: {}, }, migrations: [ OptionsSync.migrations.removeUnused, diff --git a/source/js/popup.js b/source/js/popup.js index 364cc3a..00ca795 100644 --- a/source/js/popup.js +++ b/source/js/popup.js @@ -1,11 +1,10 @@ -import { createApp } from "vue"; -import Popup from "../vue/Popup.vue"; -import 'materialize-css/dist/css/materialize.min.css' -import 'material-design-icons/iconfont/material-icons.css' - +import {createApp} from 'vue'; +import Popup from '../vue/Popup.vue'; +import 'materialize-css/dist/css/materialize.min.css'; +import 'material-design-icons/iconfont/material-icons.css'; const app = createApp(Popup); -app.mount("#app"); +app.mount('#app'); // Import browser from 'webextension-polyfill'; // import optionsStorage from './options-storage.js'; @@ -19,4 +18,4 @@ document.addEventListener('DOMContentLoaded', async () => { // document.querySelector('#optionsBtn').addEventListener('click', () => { // browser.runtime.openOptionsPage(); // }); -// } \ No newline at end of file +// } diff --git a/source/manifest.json b/source/manifest.json index c3a6225..8d93d7f 100644 --- a/source/manifest.json +++ b/source/manifest.json @@ -12,13 +12,12 @@ } }, "icons": { - "128": "img/icon.png" + "128": "img/ben-archiver.png" }, "permissions": [ "storage", "tabs", "identity" ], "host_permissions": [ - "*://*/*" ], "background": { "service_worker": "js/background.js", @@ -35,5 +34,6 @@ "oauth2": { "client_id": "572076445849-4cb2a8be1nfi46l80jm741k56s7cjkd0.apps.googleusercontent.com", "scopes": ["https://www.googleapis.com/auth/userinfo.email"] - } + }, + "key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAlKYc35vN+NAqABtFbBU60XoTzAQynotX2H/kF14m/hfUdBXUtndQN0HYdqyseTojHkcmQul8esbo1hztgNnuNW12wJ5wwJqjzYcXHjeRCwmWA0Ohld1Xh+HAwAWDpRfcWCB8S06G79SvVvk4C8WvpVYvO5MX6twBGZy/FpsFI+RodgHPK4doefo9iYRoeR47Zw20EYG+W4z8ehWR2xlpf56yq4IlsVVL78RSYqwwG4vLggXWb1MaSvnwjyVpGlcLZMoRfBgmzQrltbNCPZWS/QQ8SHk90s1kkuoiCHUgMlQIDAQAB" } \ No newline at end of file diff --git a/source/vue/Popup.vue b/source/vue/Popup.vue index 201e6cb..e33b511 100644 --- a/source/vue/Popup.vue +++ b/source/vue/Popup.vue @@ -1,16 +1,16 @@ @@ -54,16 +55,16 @@ export default { }.bind(this), 2500); }, taskFinished: function (task) { - return task.status == 'SUCCESS' || task.status == 'FAILURE'; + return task.status == 'SUCCESS' || task.status == 'FAILURE' || task.status == 'REVOKED'; } }, computed: { archiveUrl() { // return this.task?.result?.media?.urls.at(0) || ''; console.log(this.task?.result?.media); - console.log(this.task?.result?.media?.filter(m=>m?.properties?.id=="_final_media")); - console.log(this.task?.result?.media?.filter(m=>m?.properties?.id=="_final_media")?.urls?.at(0)); - return this.task?.result?.media?.filter(m=>m?.properties?.id=="_final_media")?.at(0)?.urls?.at(0) || ''; + console.log(this.task?.result?.media?.filter(m => m?.properties?.id == "_final_media")); + console.log(this.task?.result?.media?.filter(m => m?.properties?.id == "_final_media")?.urls?.at(0)); + return this.task?.result?.media?.filter(m => m?.properties?.id == "_final_media")?.at(0)?.urls?.at(0) || ''; }, readbleDate() { if (this.task?.result?._processed_at) {