diff --git a/packages/browser-extension/src/manifest.json b/packages/browser-extension/src/manifest.json index 77a5feb..2e999fd 100644 --- a/packages/browser-extension/src/manifest.json +++ b/packages/browser-extension/src/manifest.json @@ -1,7 +1,7 @@ { "manifest_version": 2, "name": "Cookie Dialog Monster", - "version": "5.5.2", + "version": "5.5.3", "default_locale": "en", "description": "__MSG_appDesc__", "icons": { diff --git a/packages/browser-extension/src/scripts/background.js b/packages/browser-extension/src/scripts/background.js index 8a7cba7..1ee567c 100644 --- a/packages/browser-extension/src/scripts/background.js +++ b/packages/browser-extension/src/scripts/background.js @@ -12,6 +12,13 @@ const apiUrl = 'https://api.cookie-dialog-monster.com/rest/v1'; const baseDataUrl = 'https://raw.githubusercontent.com/wanhose/cookie-dialog-monster/main/data'; +/** + * @description Cache data + * @type {{ attributes: string[], classes: string[], fixes: string[], selectors: string[], skips: string[] }} + */ + +let cache = undefined; + /** * @description Context menu identifier * @type {string} @@ -50,13 +57,13 @@ const enableIcon = (tabId) => const enablePopup = (tabId) => chrome.browserAction.setPopup({ popup: 'popup.html', tabId }); /** - * @description Retrieves cache state + * @description Retrieves store * @param {string} hostname * @param {void} callback * @returns {{ enabled: boolean }} */ -const getCache = (hostname, callback) => { +const getStore = (hostname, callback) => { chrome.storage.local.get(null, (store) => { callback(store[hostname] ?? initial); }); @@ -64,24 +71,48 @@ const getCache = (hostname, callback) => { /** * @async - * @description Retrieves data from GitHub - * @param {string} key + * @description Get all data from GitHub * @param {void} callback - * @returns {Promise<{ any: string[] }>} + * @returns {Promise<{ attributes: string[], classes: string[], fixes: string[], selectors: string[], skips: string[] }>} */ -const query = async (key, callback) => { - try { - const url = `${baseDataUrl}/${key}.txt`; - const response = await fetch(url); - const data = await response.text(); - - if (response.status !== 200) throw new Error(); - - callback({ [key]: data.split('\n') }); - } catch { - callback({ [key]: [] }); +const getData = async (callback) => { + if (cache) { + callback(cache); + return; } + + const data = await Promise.all([ + query('classes'), + query('elements'), + query('fixes'), + query('skips'), + ]); + + const result = { + attributes: [ + ...new Set( + data[1].elements.flatMap((element) => { + const attributes = element.match(/(?<=\[)[^(){}[\]]+(?=\])/g); + + return attributes?.length + ? [ + ...attributes.flatMap((attribute) => { + return attribute ? [attribute.replace(/\".*\"|(=|\^|\*|\$)/g, '')] : []; + }), + ] + : []; + }) + ), + ], + classes: data[0].classes, + fixes: data[2].fixes, + selectors: data[1].elements, + skips: data[3].skips, + }; + + if (Object.keys(result).every((key) => result[key].length > 0)) cache = result; + callback(result); }; /** @@ -90,7 +121,7 @@ const query = async (key, callback) => { * @returns {Promise<{ id: string, location: string }>} */ -const queryTab = (callback) => { +const getTab = (callback) => { chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => { callback({ id: tabs[0]?.id, @@ -99,6 +130,27 @@ const queryTab = (callback) => { }); }; +/** + * @async + * @description Retrieves data from GitHub + * @param {string} key + * @returns {Promise<{ [key]: string[] }>} + */ + +const query = async (key) => { + try { + const url = `${baseDataUrl}/${key}.txt`; + const response = await fetch(url); + const data = await response.text(); + + if (response.status !== 200) throw new Error(); + + return { [key]: [...new Set(data.split('\n'))] }; + } catch { + return { [key]: [] }; + } +}; + /** * @description Reports active tab URL */ @@ -112,7 +164,7 @@ const report = () => { if (tab) { fetch(`${apiUrl}/report/`, { body: JSON.stringify({ - html: `Browser: ${userAgent}
Site: ${tab.url}
Version: ${version}`, + html: `Browser: ${userAgent}
Site: ${tab.url}
Version: ${version}`, to: 'wanhose.development@gmail.com', subject: 'Cookie Dialog Monster Report', }), @@ -126,12 +178,12 @@ const report = () => { }; /** - * @description Update cache state + * @description Update store * @param {string} [hostname] * @param {object} [state] */ -const updateCache = (hostname, state) => { +const updateStore = (hostname, state) => { chrome.storage.local.get(null, (cache) => { const current = cache[hostname]; @@ -162,26 +214,17 @@ chrome.runtime.onMessage.addListener((request, sender, callback) => { case 'ENABLE_POPUP': if (tabId) enablePopup(tabId); break; - case 'GET_CACHE': - getCache(hostname, callback); + case 'GET_DATA': + getData(callback); break; - case 'GET_CLASSES': - query('classes', callback); - break; - case 'GET_SKIPS': - query('skips', callback); - break; - case 'GET_FIXES': - query('fixes', callback); - break; - case 'GET_SELECTORS': - query('elements', callback); + case 'GET_STORE': + getStore(hostname, callback); break; case 'GET_TAB': - queryTab(callback); + getTab(callback); break; - case 'UPDATE_CACHE': - updateCache(hostname, state); + case 'UPDATE_STORE': + updateStore(hostname, state); break; default: break; @@ -196,6 +239,7 @@ chrome.runtime.onMessage.addListener((request, sender, callback) => { chrome.contextMenus.create({ contexts: ['all'], + documentUrlPatterns: chrome.runtime.getManifest().content_scripts[0].matches, id: contextMenuId, title: chrome.i18n.getMessage('contextMenuText'), }); diff --git a/packages/browser-extension/src/scripts/content.js b/packages/browser-extension/src/scripts/content.js index f54d039..da2e93f 100644 --- a/packages/browser-extension/src/scripts/content.js +++ b/packages/browser-extension/src/scripts/content.js @@ -32,18 +32,18 @@ const fixes = []; const hostname = document.location.hostname.split('.').slice(-2).join('.'); -/** - * @description Is consent preview page? - */ - -const preview = hostname.startsWith('consent.') || hostname.startsWith('myprivacy.'); - /** * @description Options provided to observer * @type {MutationObserverInit} */ -const options = { attributes: true, childList: true, subtree: true }; +const options = { childList: true, subtree: true }; + +/** + * @description Is consent preview page? + */ + +const preview = hostname.startsWith('consent.') || hostname.startsWith('myprivacy.'); /** * @description Selectors list @@ -61,23 +61,26 @@ const target = document.body || document.documentElement; /** * @description Checks if node element is removable * @param {any} node + * @param {boolean} skipMatch * @returns {boolean} */ -const check = (node) => +const check = (node, skipMatch) => node instanceof HTMLElement && node.parentElement && !['BODY', 'HTML'].includes(node.tagName) && !(node.id && ['APP', 'ROOT'].includes(node.id.toUpperCase?.())) && - node.matches(selectors); + (skipMatch || node.matches(selectors)); /** * @description Cleans DOM * @param {HTMLElement[]} nodes + * @param {boolean} skipMatch * @returns {void} */ -const clean = (nodes) => nodes.filter(check).forEach((node) => (node.outerHTML = '')); +const clean = (nodes, skipMatch) => + nodes.filter((node) => check(node, skipMatch)).forEach((node) => (node.outerHTML = '')); /** * @description Fixes scroll issues @@ -138,32 +141,19 @@ const observer = new MutationObserver((mutations, instance) => { instance.observe(target, options); }); -/** - * @description Gets data - * @returns {Promise} - */ - -const promiseAll = () => - Promise.all([ - new Promise((resolve) => dispatch({ type: 'GET_CLASSES' }, null, resolve)), - new Promise((resolve) => dispatch({ type: 'GET_FIXES' }, null, resolve)), - new Promise((resolve) => dispatch({ type: 'GET_SELECTORS' }, null, resolve)), - new Promise((resolve) => dispatch({ type: 'GET_SKIPS' }, null, resolve)), - ]); - /** * @description Cleans DOM again after all * @listens document#readystatechange */ document.addEventListener('readystatechange', () => { - dispatch({ hostname, type: 'GET_CACHE' }, null, async ({ enabled }) => { + dispatch({ hostname, type: 'GET_STORE' }, null, async ({ enabled }) => { if (document.readyState === 'complete' && enabled && !preview) { const nodes = selectors.length ? Array.from(document.querySelectorAll(selectors)) : []; fix(); - clean(nodes); - setTimeout(() => clean(nodes), 2000); + clean(nodes, true); + setTimeout(() => clean(nodes, true), 2000); } }); }); @@ -179,17 +169,18 @@ window.addEventListener('unload', () => {}); * @description Setups everything and starts to observe if enabled */ -dispatch({ hostname, type: 'GET_CACHE' }, null, async ({ enabled }) => { +dispatch({ hostname, type: 'GET_STORE' }, null, ({ enabled }) => { dispatch({ type: 'ENABLE_POPUP' }); if (enabled) { - const results = await promiseAll(); - - classes.push(...(results[0]?.classes ?? [])); - fixes.push(...(results[1]?.fixes ?? [])); - selectors.push(...(results[2]?.elements ?? [])); - skips.push(...(results[3]?.skips ?? [])); - observer.observe(target, options); - dispatch({ type: 'ENABLE_ICON' }); + dispatch({ type: 'GET_DATA' }, null, (data) => { + classes.push(...data.classes); + fixes.push(...data.fixes); + options.attributeFilter = data.attributes; + selectors.push(...data.selectors); + skips.push(...data.skips); + observer.observe(target, options); + dispatch({ type: 'ENABLE_ICON' }); + }); } }); diff --git a/packages/browser-extension/src/scripts/popup.js b/packages/browser-extension/src/scripts/popup.js index 0e2db4f..2d1d375 100644 --- a/packages/browser-extension/src/scripts/popup.js +++ b/packages/browser-extension/src/scripts/popup.js @@ -36,14 +36,10 @@ const isChromium = chrome.runtime.getURL('').startsWith('chrome-extension://'); const handlePowerChange = () => { dispatch({ type: 'GET_TAB' }, null, ({ hostname, id }) => { - dispatch({ hostname, type: 'GET_CACHE' }, null, ({ enabled }) => { + dispatch({ hostname, type: 'GET_STORE' }, null, ({ enabled }) => { const power = document.getElementById('power'); - dispatch({ - hostname, - state: { enabled: !enabled }, - type: 'UPDATE_CACHE', - }); + dispatch({ hostname, state: { enabled: !enabled }, type: 'UPDATE_STORE' }); if (!enabled === false) power.removeAttribute('checked'); if (!enabled === true) power.setAttribute('checked', 'checked'); chrome.tabs.reload(id, { bypassCache: true }); @@ -90,7 +86,7 @@ const handleRate = (event) => { const handleContentLoaded = () => { dispatch({ type: 'GET_TAB' }, null, ({ hostname }) => { - dispatch({ hostname, type: 'GET_CACHE' }, null, ({ enabled }) => { + dispatch({ hostname, type: 'GET_STORE' }, null, ({ enabled }) => { translate(); const host = document.getElementById('host');