diff --git a/background/api/classifier.js b/background/api/classifier.js new file mode 100644 index 0000000..6d75ad7 --- /dev/null +++ b/background/api/classifier.js @@ -0,0 +1,13 @@ +import { ApiPost } from "./request"; + +export const classifyText = (text, callback) => { + ApiPost('/classify/text', { text }, callback, () => { + console.error('api/calliope: Could not classify cookie banner text'); + }); +}; + +export const classifyImage = (imageData, callback) => { + ApiPost('/classify/image', { image_data: imageData }, callback, () => { + console.error('api/janus: Could not classify cookie banner screenshot'); + }); +}; diff --git a/background/api/request.js b/background/api/request.js new file mode 100644 index 0000000..2466381 --- /dev/null +++ b/background/api/request.js @@ -0,0 +1,35 @@ +const baseUrl = import.meta.env.VITE_API_URL + '/api/v1'; + +export const ApiGet = (endpoint, callback, errorCallback) => { + if (typeof callback !== 'function') { + console.error('api/request(ApiGet): Expected function callback, got ' + typeof callback); + } + + fetch(baseUrl + endpoint, { + method: 'GET' + }).then((response) => response.json()) + .then((data) => callback(data)) + .catch((error) => { + console.error('api/request(ApiGet) Error fetching ' + endpoint + ':', error); + if (typeof errorCallback === 'function') errorCallback(error); + }); +} + +export const ApiPost = (endpoint, body, callback, errorCallback) => { + if (typeof callback !== 'function') { + console.error('api/request(ApiPost): Expected function callback, got ' + typeof callback); + } + + fetch(baseUrl + endpoint, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify(body) + }).then((response) => response.json()) + .then((data) => callback(data)) + .catch((error) => { + console.error('api/request(ApiPost) Error fetching ' + endpoint + ':', error); + if (typeof errorCallback === 'function') errorCallback(error); + }); +} diff --git a/background/api/stats.js b/background/api/stats.js new file mode 100644 index 0000000..c17b461 --- /dev/null +++ b/background/api/stats.js @@ -0,0 +1,16 @@ +import { ApiPost } from './request'; +import { updateBadgeText } from '../browser/badge'; +import { setTransaction } from '../browser/storage'; + +export const getStats = (tabUrl, successCallback, errorCallback) => { + ApiPost('/reports/by-url', {'page_url': tabUrl}, (data) => { + console.log('api/stats: Got stats for URL ' + tabUrl + ':', data); + setTransaction('stats', { + url: tabUrl, + stats: data + }).then(() => { + updateBadgeText(data); + if (successCallback !== undefined) successCallback(data); + }).catch((e) => errorCallback(e)) + }, errorCallback); +} diff --git a/background/background.js b/background/background.js index 91e8be2..83ce549 100644 --- a/background/background.js +++ b/background/background.js @@ -1,175 +1,10 @@ import * as browser from 'webextension-polyfill'; - -class AriadneBackground { - constructor() { - this._tabStates = []; - this._reportStats = {}; - this._API_URL = import.meta.env.VITE_API_URL + '/api/v1'; - - // Alias if browser.action is defined, i.e. in Manifest V3 - this.BrowserAction = browser.browserAction; - if (browser.action !== undefined) { - this.BrowserAction = browser.action; - } - } - - addListeners() { - // Update badge on tab change - browser.tabs.onActivated.addListener((activeInfo) => { - // Get URL of active tab - browser.tabs.get(activeInfo.tabId) - .then((tab) => { - console.log('[bg] Tab changed to', tab.url); - this.toggleBadge(this._tabStates[activeInfo.tabId]); - this.updateBadgeText(this._reportStats[tab.url]); - this.getStats(tab.url); - }); - }); - - // Tab URL change listener - browser.tabs.onUpdated.addListener((tabId, changeInfo, _) => { - if (changeInfo.url) { - // Request fresh stats - console.log('[bg] Tab ' + tabId + ' URL changed to', changeInfo.url); - this.getStats(changeInfo.url); - } - }); - - // Message listeners - browser.runtime.onMessage.addListener((request, sender, sendResponse) => { - console.log("[bg] Received message with action", request.action); - if (request.action === "updateBadge") { - // Listen to updateBadge requests - this.toggleBadge(request.args.enabled); - - // Update the tab state - this._tabStates[sender.tab.id] = request.args.enabled; - } else if (request.action === "detection") { - // Listen to detection requests from content scripts - const cookieBannerText = request.args.body; - console.log('[bg] Detection request received from tab', sender.tab.id, 'with body:', cookieBannerText); - - // POST to API - fetch(this._API_URL + '/classify/text', { - method: 'POST', - body: JSON.stringify({ - text: cookieBannerText - }), - headers: { - 'Content-Type': 'application/json' - } - }).then((response) => response.json()) - .then((data) => { - console.log('[bg] Detection result from API:', data); - sendResponse(data); - }); - } else if (request.action === "visualDetection") { - // Listen to visual detection requests from content scripts - const imageData = request.args.screenshot; - console.log('[bg] Visual detection request received from tab', sender.tab.id); - - // POST to API - fetch(this._API_URL + '/classify/image', { - method: 'POST', - body: JSON.stringify({ - image_data: imageData - }), - headers: { - 'Content-Type': 'application/json' - } - }).then((response) => response.json()) - .then((data) => { - console.log('[bg] Detection result from API:', data); - sendResponse(data); - }); - } else if (request.action === "requestStats") { - console.log("[bg] Received stats request from popup", request, sender); - - // If we have cached stats, send them before requesting new ones - const tabUrl = request.args.url; - let deferSending = false; - if (this._reportStats[tabUrl]) { - console.log("[bg] Sending cached stats to tab", tabUrl, this._reportStats[tabUrl]); - sendResponse(this._reportStats[tabUrl]); - deferSending = true; - } - - this.getStats(tabUrl, (stats) => { - if (!deferSending) { - console.log('[bg] Sending stats to tab', tabUrl, this._reportStats[tabUrl]) - sendResponse(stats); - } else { - console.log('[bg] Revalidated cache for tab', tabUrl, this._reportStats[tabUrl]) - } - }, (error) => { - sendResponse({ - success: false, - error - }); - }); - } - - return true; - }); - } - - toggleBadge(state) { - if (state) { - this.BrowserAction.setBadgeBackgroundColor({ - color: "#B677FA", - }); - } else { - this.BrowserAction.setBadgeBackgroundColor({ - color: "#2A272A", - }); - } - } - - updateBadgeText(stats) { - console.log('[bg] Updating badge text with stats:', stats); - if (stats !== undefined && stats.hasOwnProperty("success") && - stats.hasOwnProperty("specific_reports") && stats["success"]) { - const count = stats.specific_reports.count; - console.log('[bg] Badge count:', count) - if (count > 0) { - this.BrowserAction.setBadgeText({ - text: count.toString(), - }); - return; - } - } - this.BrowserAction.setBadgeText({ - text: "0", - }); - } - - getStats(tabUrl, successCallback, errorCallback) { - fetch(this._API_URL + '/reports/by-url', { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ - 'page_url': tabUrl - }) - }) - .then((response) => response.json()) - .then((data) => { - console.log('[bg] Report stats from API:', data); - this._reportStats[tabUrl] = data; - - // Update badge text - this.updateBadgeText(data); - - if (successCallback !== undefined) successCallback(data); - }) - .catch((error) => { - console.error('[bg] Error fetching report stats:', error); - if (errorCallback !== undefined) errorCallback(error); - } - ); - } -} - -const ariadneBackground = new AriadneBackground(); -ariadneBackground.addListeners(); +import { + messageListener, + tabChangeListener, + tabUrlChangeListener +} from './listeners'; + +browser.runtime.onMessage.addListener(messageListener); +browser.tabs.onActivated.addListener(tabChangeListener); +browser.tabs.onUpdated.addListener(tabUrlChangeListener); diff --git a/background/browser/badge.js b/background/browser/badge.js new file mode 100644 index 0000000..b921dc7 --- /dev/null +++ b/background/browser/badge.js @@ -0,0 +1,33 @@ +import { getAction } from './getAction'; + +const action = getAction(); + +export const toggleBadge = (state) => { + if (state) { + action.setBadgeBackgroundColor({ + color: "#B677FA", + }); + } else { + action.setBadgeBackgroundColor({ + color: "#2A272A", + }); + } +} + +export const updateBadgeText = (stats) => { + console.log('browser/badge: Updating badge text with stats:', stats); + if (stats !== undefined && stats.hasOwnProperty("success") && + stats.hasOwnProperty("specific_reports") && stats["success"]) { + const count = stats.specific_reports.count; + console.log('browser/badge: Badge count:', count) + if (count > 0) { + action.setBadgeText({ + text: count.toString(), + }); + return; + } + } + action.setBadgeText({ + text: "0", + }); +} diff --git a/background/browser/getAction.js b/background/browser/getAction.js new file mode 100644 index 0000000..59b0244 --- /dev/null +++ b/background/browser/getAction.js @@ -0,0 +1,8 @@ +import * as browser from 'webextension-polyfill'; + +export const getAction = () => { + if (chrome.runtime.getManifest().manifest_version === 3) { + return browser.action; + } + return browser.browserAction; +} diff --git a/background/browser/storage.js b/background/browser/storage.js new file mode 100644 index 0000000..36fb244 --- /dev/null +++ b/background/browser/storage.js @@ -0,0 +1,80 @@ +const openDatabase = () => new Promise((res, rej) => { + const request = indexedDB.open('ariadne', 1); + request.onerror = (event) => { + console.error(`browser/storage: Error ${event.target.errorCode}`) + rej(); + }; + + // Declare schema + request.onupgradeneeded = (event) => { + const db = event.target.result; + + // Create storage for stats per URL + const urlStatsStore = db.createObjectStore('stats', { keyPath: 'url' }) + urlStatsStore.createIndex('url', 'url', { unique: true }) + + // Create storage for badge states per tab + const badgeStateStore = db.createObjectStore('badgeStates', { keyPath: 'tabId' }) + badgeStateStore.createIndex('tabId', 'tabId', { unique: true }) + + // Create storage for Calliope results per URL + const calliopeStore = db.createObjectStore('calliope', { keyPath: 'url' }) + calliopeStore.createIndex('url', 'url', { unique: true }) + + // Create storage for Janus results per URL + const janusStore = db.createObjectStore('janus', { keyPath: 'url' }) + janusStore.createIndex('url', 'url', { unique: true }) + } + + request.onsuccess = (event) => { + console.log('browser/storage: Database ready'); + res(event.target.result); + } +}); + + +export const setTransaction = async (store, data) => { + console.log('browser/storage(set): Opening DB'); + + const db = await openDatabase(); + const t = db.transaction(store, 'readwrite'); + const s = t.objectStore(store); + return await new Promise((res, rej) => { + t.oncomplete = () => console.log('browser/storage(set): Complete'); + t.onerror = (e) => rej(e); + + const r = s.put(data); + r.onsuccess = () => res(); + }); +} + +export const getTransaction = async (store, key) => { + console.log('browser/storage(get): Opening DB'); + + const db = await openDatabase(); + const t = db.transaction(store, 'readonly'); + const s = t.objectStore(store); + return await new Promise((res, rej) => { + t.oncomplete = () => console.log('browser/storage(get): Complete'); + t.onerror = (ev) => rej(ev); + + const r = s.get(key); + r.onsuccess = (e) => res(e.target.result); + }); +} + +export const deleteTransaction = async (store, key) => { + console.log('browser/storage(delete): Opening DB'); + + const db = await openDatabase(); + const t = db.transaction(store, 'readwrite'); + const s = t.objectStore(store); + return await new Promise((res, rej) => { + t.oncomplete = () => { + console.log('browser/storage(delete): Complete'); + res(); + }; + t.onerror = (e) => rej(e); + s.delete(key); + }); +} diff --git a/background/listeners/index.js b/background/listeners/index.js new file mode 100644 index 0000000..7cdb280 --- /dev/null +++ b/background/listeners/index.js @@ -0,0 +1,9 @@ +import messageListener from './message'; +import tabChangeListener from './tabChange'; +import tabUrlChangeListener from './tabUrlChange'; + +export { + messageListener, + tabChangeListener, + tabUrlChangeListener +} diff --git a/background/listeners/message.js b/background/listeners/message.js new file mode 100644 index 0000000..7d2d482 --- /dev/null +++ b/background/listeners/message.js @@ -0,0 +1,108 @@ +import { toggleBadge } from "../browser/badge"; +import { getTransaction, setTransaction } from "../browser/storage"; +import { getStats } from "../api/stats"; +import { classifyImage, classifyText } from "../api/classifier"; +import * as browser from "webextension-polyfill"; + +export default (request, sender, sendResponse) => { + if (request.action === "updateBadge") { + // Save and apply the badge state + setTransaction('badgeStates', { + tabId: sender.tab.id, + enabled: request.args.enabled + }).then(() => toggleBadge(request.args.enabled)); + } else if (request.action === "detection") { + // Listen to detection requests from content scripts + const cookieBannerText = request.args.body; + console.log('listeners/message: Calliope request received from tab', sender.tab.id, 'with body:', cookieBannerText); + + // POST to API + classifyText(cookieBannerText, (data) => { + console.log('listeners/message: Calliope result from API:', data); + + // Save result + browser.tabs.get(sender.tab.id) + .then((tab) => tab.url) + .then((url) => setTransaction('calliope', { + url, + cookieBannerText, + tripped: !data.is_good + })) + .then(() => sendResponse(data)); + }); + } else if (request.action === "visualDetection") { + // Listen to visual detection requests from content scripts + const imageData = request.args.screenshot; + console.log('listeners/message: Janus request received from tab', sender.tab.id); + + // POST to API + classifyImage(imageData, (data) => { + console.log('listeners/message: Janus result from API:', data); + + // Save result + browser.tabs.get(sender.tab.id) + .then((tab) => tab.url) + .then((url) => setTransaction('janus', { + url, + imageData, + result: data.classification + })) + .then(() => sendResponse(data)); + }); + } else if (request.action === "requestStats") { + console.log("listeners/message: Received stats request from popup", request, sender); + + (async () => { + const tabUrl = request.args.url; + let calliopeResult = ''; + let janusResult = ''; + let cookieBannerText = ''; + let imageData = ''; + + // Try fetching results from DB + try { + const cachedCalliope = await getTransaction('calliope', tabUrl); + calliopeResult = JSON.stringify(cachedCalliope.tripped); + cookieBannerText = cachedCalliope.cookieBannerText; + } catch (_) { + // do nothing + } + try { + const cachedJanus = await getTransaction('janus', tabUrl); + janusResult = cachedJanus.result; + imageData = cachedJanus.imageData; + } catch (_) { + // do nothing + } + + // If we have cached stats, send them before requesting new ones + let deferSending = false; + let stats = { calliopeResult, janusResult, cookieBannerText, imageData }; + try { + stats = { ...stats, ...await getTransaction('stats', tabUrl) }; + console.log('listeners/message: Sending stats to tab', tabUrl, stats) + sendResponse(stats); + deferSending = true; + } catch (e) { + console.error(e); + } finally { + getStats(tabUrl, (newStats) => { + stats = { ...stats, ...newStats }; + if (!deferSending) { + console.log('listeners/message: Sending stats to tab', tabUrl, stats) + sendResponse(stats); + } else { + console.log('listeners/message: Revalidated cache for tab', tabUrl) + } + }, (error) => { + sendResponse({ + success: false, + error + }); + }) + } + })(); + } + + return true; +} diff --git a/background/listeners/tabChange.js b/background/listeners/tabChange.js new file mode 100644 index 0000000..f562b87 --- /dev/null +++ b/background/listeners/tabChange.js @@ -0,0 +1,29 @@ +import * as browser from 'webextension-polyfill'; +import { toggleBadge, updateBadgeText } from "../browser/badge"; +import { getStats } from "../api/stats"; +import { getTransaction } from '../browser/storage'; + +export default (activeInfo) => { + // Get URL of active tab + browser.tabs.get(activeInfo.tabId) + .then((tab) => { + console.log('listeners/tabChange: Tab changed to', tab.url); + + // Fetch latest stats + return new Promise((res, rej) => { + getStats(tab.url, () => res(tab.url), rej); + }) + }) + .then(async (url) => { + console.log('listeners/tabChange: Stats refreshed'); + let enabled = false; + try { + enabled = (await getTransaction('badgeStates', activeInfo.tabId)).enabled; + } catch (_) { + // do nothing + } + const { stats } = await getTransaction('stats', url); + toggleBadge(enabled); + updateBadgeText(stats); + }); +} diff --git a/background/listeners/tabUrlChange.js b/background/listeners/tabUrlChange.js new file mode 100644 index 0000000..f4bce50 --- /dev/null +++ b/background/listeners/tabUrlChange.js @@ -0,0 +1,9 @@ +import { getStats } from "../api/stats"; + +export default (tabId, changeInfo, _) => { + if (changeInfo.url) { + // Request fresh stats + console.log('listeners/tabUrlChange: Tab ' + tabId + ' changed to', changeInfo.url); + getStats(changeInfo.url); + } +} diff --git a/content/content.js b/content/content.js index 3218f95..9564858 100644 --- a/content/content.js +++ b/content/content.js @@ -1,6 +1,28 @@ import html2canvas from 'html2canvas'; import * as browser from 'webextension-polyfill'; +function canvasHandler(canvas) { + const screenshot = canvas.toDataURL("image/png"); + console.log("[content] Screenshot taken, sending to service worker"); + + // Send screenshot to service worker for detection + browser.runtime.sendMessage({ + action: "visualDetection", + args: { screenshot } + }) + .then((response) => { + console.log("[content] Result from Janus API:", response); + + // Update badge + return browser.runtime.sendMessage({ + action: "updateBadge", + args: { + enabled: response["classification"] == "weighted" + } + }); + }); +} + let bannerDiv = null; function performDetection() { // Look for floating divs, i.e. divs with position: fixed|absolute|sticky @@ -43,42 +65,17 @@ function performDetection() { .then((response) => { console.log("[content] Badge updated:", response); }); + break; } } - // No banner found - if (bannerDiv === null) { - console.log("[content] No banner found"); - - // Take screenshot of viewport - html2canvas(document.body, { - x: window.scrollX, - y: window.scrollY, - width: window.innerWidth, - height: window.innerHeight, - }).then((canvas) => { - const screenshot = canvas.toDataURL("image/png"); - console.log("[content] Screenshot taken, sending to service worker"); - - // Send screenshot to service worker for detection - browser.runtime.sendMessage({ - action: "visualDetection", - args: { screenshot } - }) - .then((response) => { - console.log("[content] Result from Janus API:", response); - - // Update badge - return browser.runtime.sendMessage({ - action: "updateBadge", - args: { - enabled: response["classification"] == "weighted" - } - }); - }); - }); - return; + // Perform visual detection + if (bannerDiv !== null) { + console.log("[content] Taking screenshot of banner"); + html2canvas(bannerDiv).then(canvasHandler); + } else { + console.log("[content] No banner div found, skipping visual detection"); } } diff --git a/manifest/v2.json b/manifest/v2.json index 1c8b575..43c851d 100644 --- a/manifest/v2.json +++ b/manifest/v2.json @@ -3,7 +3,7 @@ "short_name": "__MSG_appShortName__", "description": "__MSG_appDescription__", "homepage_url": "https://ariadne.dantis.me", - "version": "0.1.0", + "version": "0.1.1", "manifest_version": 2, "default_locale": "en", "permissions": [ @@ -30,7 +30,7 @@ }, "content_scripts": [ { - "js": ["./content.global.js"], + "js": ["./content.js"], "matches": ["http://*/*", "https://*/*"], "run_at": "document_idle" } diff --git a/manifest/v3.json b/manifest/v3.json index 2b04971..f4c4790 100644 --- a/manifest/v3.json +++ b/manifest/v3.json @@ -3,8 +3,8 @@ "short_name": "__MSG_appShortName__", "description": "__MSG_appDescription__", "homepage_url": "https://ariadne.dantis.me", - "version": "0.1.0", - "version_name": "0.1.0", + "version": "0.1.1", + "version_name": "0.1.1", "manifest_version": 3, "default_locale": "en", "minimum_chrome_version": "88", @@ -15,7 +15,7 @@ "128": "assets/img/Button@8x.png" }, "background": { - "service_worker": "./background.global.js" + "service_worker": "./background.js" }, "action": { "default_icon": { @@ -28,7 +28,7 @@ }, "content_scripts": [ { - "js": ["./content.global.js"], + "js": ["./content.js"], "matches": ["http://*/*", "https://*/*"], "run_at": "document_idle" } diff --git a/package-lock.json b/package-lock.json index 569bc47..3c750c7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,14 +1,15 @@ { "name": "ariadne", - "version": "0.0.1", + "version": "0.1.1", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "ariadne", - "version": "0.0.1", + "version": "0.1.1", "dependencies": { "@heroicons/vue": "^2.0.16", + "flowbite": "^1.6.5", "html2canvas": "^1.4.1", "pinia": "^2.0.32", "vue": "^3.2.47", @@ -809,6 +810,15 @@ "node": ">=12" } }, + "node_modules/@popperjs/core": { + "version": "2.11.7", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.7.tgz", + "integrity": "sha512-Cr4OjIkipTtcXKjAsm8agyleBuDHvxzeBoa1v543lbv1YaIwQjESsVcmjiWiPEbC1FIeHOG/Op9kdCmAmiS3Kw==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, "node_modules/@sindresorhus/is": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-5.3.0.tgz", @@ -3058,6 +3068,15 @@ "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", "dev": true }, + "node_modules/flowbite": { + "version": "1.6.5", + "resolved": "https://registry.npmjs.org/flowbite/-/flowbite-1.6.5.tgz", + "integrity": "sha512-eI4h3pIRI9d7grlYq14r0A01KUtw7189sPLLx/O2i7JyPEWpbleScfYuEc48XTeNjk1xxm/JHgZkD9kjyOWAlA==", + "dependencies": { + "@popperjs/core": "^2.9.3", + "mini-svg-data-uri": "^1.4.3" + } + }, "node_modules/forever-agent": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", @@ -4348,6 +4367,14 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/mini-svg-data-uri": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/mini-svg-data-uri/-/mini-svg-data-uri-1.4.4.tgz", + "integrity": "sha512-r9deDe9p5FJUPZAk3A59wGH7Ii9YrjjWw0jmw/liSbHl2CHiyXj6FcDXDu2K3TjVAXqiJdaw3xxwlZZr9E6nHg==", + "bin": { + "mini-svg-data-uri": "cli.js" + } + }, "node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -7483,6 +7510,11 @@ "config-chain": "^1.1.11" } }, + "@popperjs/core": { + "version": "2.11.7", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.7.tgz", + "integrity": "sha512-Cr4OjIkipTtcXKjAsm8agyleBuDHvxzeBoa1v543lbv1YaIwQjESsVcmjiWiPEbC1FIeHOG/Op9kdCmAmiS3Kw==" + }, "@sindresorhus/is": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-5.3.0.tgz", @@ -9142,6 +9174,15 @@ "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", "dev": true }, + "flowbite": { + "version": "1.6.5", + "resolved": "https://registry.npmjs.org/flowbite/-/flowbite-1.6.5.tgz", + "integrity": "sha512-eI4h3pIRI9d7grlYq14r0A01KUtw7189sPLLx/O2i7JyPEWpbleScfYuEc48XTeNjk1xxm/JHgZkD9kjyOWAlA==", + "requires": { + "@popperjs/core": "^2.9.3", + "mini-svg-data-uri": "^1.4.3" + } + }, "forever-agent": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", @@ -10126,6 +10167,11 @@ "integrity": "sha512-e5ISH9xMYU0DzrT+jl8q2ze9D6eWBto+I8CNpe+VI+K2J/F/k3PdkdTdz4wvGVH4NTpo+NRYTVIuMQEMMcsLqg==", "dev": true }, + "mini-svg-data-uri": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/mini-svg-data-uri/-/mini-svg-data-uri-1.4.4.tgz", + "integrity": "sha512-r9deDe9p5FJUPZAk3A59wGH7Ii9YrjjWw0jmw/liSbHl2CHiyXj6FcDXDu2K3TjVAXqiJdaw3xxwlZZr9E6nHg==" + }, "minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", diff --git a/package.json b/package.json index 041fef7..6c38460 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "ariadne", "description": "Detect deceptive design on the Web", - "version": "0.1.0", + "version": "0.1.1", "homepage": "https://ariadne.dantis.me", "author": "Juris Hannah Adorna & Aurel Jared Dantis", "repository": { @@ -24,6 +24,7 @@ }, "dependencies": { "@heroicons/vue": "^2.0.16", + "flowbite": "^1.6.5", "html2canvas": "^1.4.1", "pinia": "^2.0.32", "vue": "^3.2.47", diff --git a/src/components/Checkbox.vue b/src/components/Checkbox.vue index 0d4b61a..1c198a6 100644 --- a/src/components/Checkbox.vue +++ b/src/components/Checkbox.vue @@ -7,10 +7,12 @@ import { MinusIcon, PlusIcon } from '@heroicons/vue/24/outline' @click="handleChange" class="group rounded-lg box-border border-2 px-4 py-3 flex flex-row justify-between items-start - transition-colors cursor-pointer select-none" + transition-colors select-none" :class="{ 'bg-adn-dark border-adn-dark': isChecked, - 'bg-white border-adn-border': !isChecked + 'bg-white border-adn-border': !isChecked, + 'cursor-not-allowed opacity-30': disabled, + 'cursor-pointer': !disabled }" >
@@ -42,11 +44,16 @@ export default defineComponent({ checkboxKey: { type: String, required: true + }, + disabled: { + type: Boolean, + default: false } }, emits: ['update:value'], methods: { handleChange(e) { + if (this.disabled) { return } this.isChecked = !this.isChecked this.$emit('update:value', { key: this.checkboxKey, diff --git a/src/components/Header.vue b/src/components/Header.vue index 1d92d1b..db50dec 100644 --- a/src/components/Header.vue +++ b/src/components/Header.vue @@ -31,7 +31,7 @@ import { Cog6ToothIcon } from '@heroicons/vue/24/outline'
-
-
+
--> ALPHA diff --git a/src/components/PillCount.vue b/src/components/PillCount.vue index 02f5396..a25eea3 100644 --- a/src/components/PillCount.vue +++ b/src/components/PillCount.vue @@ -2,7 +2,7 @@
+ +
+ + + +
+ + + \ No newline at end of file diff --git a/src/router/index.js b/src/router/index.js index f1c6690..7fdd872 100644 --- a/src/router/index.js +++ b/src/router/index.js @@ -17,15 +17,22 @@ const router = createRouter({ }, }, { - path: '/report-positive', - name: 'report-positive', + path: '/report-manual', + name: 'report-manual', meta: { title: 'Report Deceptive Design' }, - // route level code-splitting - // this generates a separate chunk (About.[hash].js) for this route - // which is lazy-loaded when the route is visited. - component: () => import('../views/ReportPositiveView.vue') + component: () => import('../views/ManualReportView.vue') + }, + { + path: '/report-auto', + name: 'report-auto', + meta: { title: 'Submit Detection Feedback' }, + component: () => import('../views/AutoReportView.vue') } - ] + ], + scrollBehavior(to, from, savedPosition) { + // always scroll to top + return { top: 0 } + }, }) export default router diff --git a/src/stores/ariadne.js b/src/stores/ariadne.js index dc3cacc..e9dc815 100644 --- a/src/stores/ariadne.js +++ b/src/stores/ariadne.js @@ -6,7 +6,11 @@ export const useAriadneStore = defineStore('ariadne', { currentFavicon: '', currentPath: '', currentURL: '', - isRunningInExtension: false + isRunningInExtension: false, + calliopeTripped: '', + calliopeText: '', + janusResult: '', + janusScreenshot: '' }), actions: { setDomain(domain) { @@ -23,6 +27,12 @@ export const useAriadneStore = defineStore('ariadne', { }, setRunningInExtension(isRunningInExtension) { this.isRunningInExtension = isRunningInExtension; + }, + setDetectionData({ calliopeTripped, calliopeText, janusResult, janusScreenshot }) { + this.calliopeTripped = calliopeTripped; + this.calliopeText = calliopeText; + this.janusResult = janusResult; + this.janusScreenshot = janusScreenshot; } } }) diff --git a/src/views/AutoReportView.vue b/src/views/AutoReportView.vue new file mode 100644 index 0000000..98d8f2a --- /dev/null +++ b/src/views/AutoReportView.vue @@ -0,0 +1,191 @@ + + + \ No newline at end of file diff --git a/src/views/HomeView.vue b/src/views/HomeView.vue index 3f56804..69858bb 100644 --- a/src/views/HomeView.vue +++ b/src/views/HomeView.vue @@ -24,6 +24,73 @@ + +
+
+ +

{{ tripped ? 'D' : 'No d' }}eceptive design
detected here

+ + +
+ + + + + + + + +
+
+

+ The cookie banner on this page might be making use of + {{ trippedText }}. +

+

+ The cookie banner on this page, if any, is not making use of deceptive design. +

+
+
@@ -64,27 +131,23 @@
- - - There is deceptive design on this page + + + Report deceptive design on this page - - - - No deceptive design on this page -