diff --git a/packages/browser-extension/src/scripts/background.js b/packages/browser-extension/src/scripts/background.js
index 110c5fb..e1f5df2 100644
--- a/packages/browser-extension/src/scripts/background.js
+++ b/packages/browser-extension/src/scripts/background.js
@@ -3,230 +3,120 @@
* @type {string}
*/
-const apiUrl = 'https://api.cookie-dialog-monster.com/rest/v1';
+const apiUrl = 'https://api.cookie-dialog-monster.com/rest/v2';
/**
- * @description Base data URL
- * @type {string}
- */
-
-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}
- */
-
-const contextMenuId = 'CDM_MENU';
-
-/**
- * @description Cache initial state
+ * @description Initial state
* @type {{ enabled: boolean }}
*/
const initial = { enabled: true };
/**
- * @description Disables icon
- * @param {string} tabId
+ * @description Context menu identifier
+ * @type {string}
*/
-const disableIcon = (tabId) =>
- chrome.browserAction.setIcon({ path: 'assets/icons/disabled.png', tabId });
+const reportMenuItemId = 'REPORT';
/**
- * @description Enables icon
- * @param {string} tabId
+ * @description Refreshes data
+ * @param {void?} callback
*/
-const enableIcon = (tabId) =>
- chrome.browserAction.setIcon({ path: 'assets/icons/enabled.png', tabId });
-
-/**
- * @description Enables popup
- * @param {string} tabId
- */
-
-const enablePopup = (tabId) => chrome.browserAction.setPopup({ popup: 'popup.html', tabId });
-
-/**
- * @description Retrieves store
- * @param {string} hostname
- * @param {void} callback
- * @returns {{ enabled: boolean }}
- */
-
-const getStore = (hostname, callback) => {
- chrome.storage.local.get(null, (store) => {
- callback(store[hostname] ?? initial);
- });
-};
-
-/**
- * @async
- * @description Get all data from GitHub
- * @param {void} callback
- * @returns {Promise<{ attributes: string[], classes: string[], fixes: string[], selectors: string[], skips: string[] }>}
- */
-
-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);
-};
-
-/**
- * @description Retrieves current tab information
- * @param {void} [callback]
- * @returns {Promise<{ id: string, location: string }>}
- */
-
-const getTab = (callback) => {
- chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
- const tab = tabs[0];
-
- callback({
- id: tab?.id,
- hostname: tab ? new URL(tab.url).hostname.split('.').slice(-2).join('.') : undefined,
+const refreshData = (callback) => {
+ fetch(`${apiUrl}/data/`).then((result) => {
+ result.json().then(({ data }) => {
+ chrome.storage.local.set({ data });
+ callback(data);
});
});
};
/**
* @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
+ * @param {chrome.tabs.Tab} tab
*/
-const report = () => {
- chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
- const tab = tabs[0];
- const userAgent = window.navigator.userAgent;
- const version = chrome.runtime.getManifest().version;
+const report = async (tab) => {
+ const version = chrome.runtime.getManifest().version;
+ const body = JSON.stringify({ url: tab?.url, version });
+ const headers = { 'Content-type': 'application/json' };
+ const url = `${apiUrl}/report/`;
- if (tab) {
- fetch(`${apiUrl}/report/`, {
- body: JSON.stringify({
- html: `Browser: ${userAgent}
Site: ${tab.url}
Version: ${version}`,
- to: 'hello@wanhose.dev',
- subject: 'Cookie Dialog Monster Report',
- }),
- headers: {
- 'Content-type': 'application/json',
- },
- method: 'POST',
- });
- }
- });
+ await fetch(url, { body, headers, method: 'POST' });
+ chrome.tabs.sendMessage(tab.id, { type: 'SHOW_REPORT_CONFIRMATION' });
};
/**
- * @description Update store
- * @param {string} [hostname]
- * @param {object} [state]
+ * @description Listens to context menus
*/
-const updateStore = (hostname, state) => {
- chrome.storage.local.get(null, (cache) => {
- const current = cache[hostname];
+chrome.contextMenus.onClicked.addListener((info, tab) => {
+ switch (info.menuItemId) {
+ case reportMenuItemId:
+ if (tab) report(tab);
+ break;
+ default:
+ break;
+ }
+});
- chrome.storage.local.set({
- [hostname]: {
- enabled: typeof state.enabled === 'undefined' ? current.enabled : state.enabled,
- },
- });
+/**
+ * @description Listens to extension installed/updated
+ */
+
+chrome.runtime.onInstalled.addListener(() => {
+ chrome.contextMenus.create({
+ contexts: ['all'],
+ documentUrlPatterns: chrome.runtime.getManifest().content_scripts[0].matches,
+ id: reportMenuItemId,
+ title: chrome.i18n.getMessage('contextMenuText'),
});
-};
+});
+
+/**
+ * @description Listens to first start
+ */
+
+chrome.runtime.onStartup.addListener(() => {
+ refreshData();
+});
/**
* @description Listens to messages
*/
-chrome.runtime.onMessage.addListener((request, sender, callback) => {
- const hostname = request.hostname;
- const state = request.state;
+chrome.runtime.onMessage.addListener((message, sender, callback) => {
+ const hostname = message.hostname;
const tabId = sender.tab?.id;
- switch (request.type) {
+ switch (message.type) {
case 'DISABLE_ICON':
- if (tabId) disableIcon(tabId);
+ if (tabId) chrome.browserAction.setIcon({ path: 'assets/icons/disabled.png', tabId });
break;
case 'ENABLE_ICON':
- if (tabId) enableIcon(tabId);
+ if (tabId) chrome.browserAction.setIcon({ path: 'assets/icons/enabled.png', tabId });
break;
case 'ENABLE_POPUP':
- if (tabId) enablePopup(tabId);
+ if (tabId) chrome.browserAction.setPopup({ popup: 'popup.html', tabId });
break;
case 'GET_DATA':
- getData(callback);
+ chrome.storage.local.get('data', ({ data }) => {
+ if (data) callback(data);
+ else refreshData(callback);
+ });
break;
- case 'GET_STORE':
- getStore(hostname, callback);
+ case 'GET_STATE':
+ // prettier-ignore
+ if (hostname) chrome.storage.local.get(hostname, (state) => callback(state[hostname] ?? initial));
break;
case 'GET_TAB':
- getTab(callback);
+ chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => callback(tabs[0]));
break;
- case 'UPDATE_STORE':
- updateStore(hostname, state);
+ case 'UPDATE_STATE':
+ if (hostname) chrome.storage.local.set({ [hostname]: message.state });
break;
default:
break;
@@ -234,23 +124,3 @@ chrome.runtime.onMessage.addListener((request, sender, callback) => {
return true;
});
-
-/**
- * @description Creates context menu
- */
-
-chrome.contextMenus.create({
- contexts: ['all'],
- documentUrlPatterns: chrome.runtime.getManifest().content_scripts[0].matches,
- id: contextMenuId,
- title: chrome.i18n.getMessage('contextMenuText'),
-});
-
-/**
- * @description Listens to context menus
- */
-
-chrome.contextMenus.onClicked.addListener((info) => {
- if (info.menuItemId !== contextMenuId) return;
- report();
-});
diff --git a/packages/browser-extension/src/scripts/content.js b/packages/browser-extension/src/scripts/content.js
index da2e93f..76ee91e 100644
--- a/packages/browser-extension/src/scripts/content.js
+++ b/packages/browser-extension/src/scripts/content.js
@@ -1,36 +1,28 @@
/**
- * @description Array of selectors
- * @type {string[]}
+ * @description Data properties
+ * @type {{ classes: string[], fixes: string[], elements: string[], skips: string[] }?}
*/
-const classes = [];
+let data = null;
/**
* @description Shortcut to send messages to background script
- * @type {void}
*/
const dispatch = chrome.runtime.sendMessage;
/**
- * @description Array of skips to skip
- * @type {string[]}
+ * @description Forbidden tags to ignore in the DOM
*/
-const skips = [];
+const forbiddenTags = ['BASE', 'BODY', 'HEAD', 'HTML', 'LINK', 'META', 'SCRIPT', 'STYLE', 'TITLE'];
/**
- * @description Array of instructions
- * @type {string[]}
+ * @description Current hostname
+ * @type {string}
*/
-const fixes = [];
-
-/**
- * @description Hostname
- */
-
-const hostname = document.location.hostname.split('.').slice(-2).join('.');
+const hostname = document.location.hostname.split('.').slice(-3).join('.').replace('www.', '');
/**
* @description Options provided to observer
@@ -46,56 +38,43 @@ const options = { childList: true, subtree: true };
const preview = hostname.startsWith('consent.') || hostname.startsWith('myprivacy.');
/**
- * @description Selectors list
- * @type {string[]}
- */
-
-const selectors = [];
-
-/**
- * @description Target provided to observer
- */
-
-const target = document.body || document.documentElement;
-
-/**
- * @description Checks if node element is removable
- * @param {any} node
- * @param {boolean} skipMatch
+ * @description Matches if node element is removable
+ * @param {Element} node
* @returns {boolean}
*/
-const check = (node, skipMatch) =>
+const match = (node) =>
node instanceof HTMLElement &&
node.parentElement &&
- !['BODY', 'HTML'].includes(node.tagName) &&
- !(node.id && ['APP', 'ROOT'].includes(node.id.toUpperCase?.())) &&
- (skipMatch || node.matches(selectors));
+ !forbiddenTags.includes(node.tagName?.toUpperCase?.()) &&
+ node.matches(data?.elements ?? []);
/**
* @description Cleans DOM
* @param {HTMLElement[]} nodes
- * @param {boolean} skipMatch
+ * @param {boolean?} skipMatch
* @returns {void}
*/
const clean = (nodes, skipMatch) =>
- nodes.filter((node) => check(node, skipMatch)).forEach((node) => (node.outerHTML = ''));
+ nodes.filter((node) => skipMatch || match(node)).forEach((node) => node.remove());
/**
* @description Fixes scroll issues
*/
const fix = () => {
- if (skips.length && !skips.includes(hostname)) {
+ document.getElementsByClassName('_31e')[0]?.classList.remove('_31e');
+
+ if (data?.skips.length && !data.skips.includes(hostname)) {
for (const item of [document.body, document.documentElement]) {
- item?.classList.remove(...classes);
+ item?.classList.remove(...(data?.classes ?? []));
item?.style.setProperty('position', 'initial', 'important');
item?.style.setProperty('overflow-y', 'initial', 'important');
}
}
- for (const fix of fixes) {
+ for (const fix of data?.fixes ?? []) {
const [match, selector, action, property] = fix.split('##');
if (hostname.includes(match)) {
@@ -132,54 +111,40 @@ const fix = () => {
* @type {MutationObserver}
*/
-const observer = new MutationObserver((mutations, instance) => {
+const observer = new MutationObserver((mutations) => {
const nodes = mutations.map((mutation) => Array.from(mutation.addedNodes)).flat();
- instance.disconnect();
fix();
- if (!preview && selectors.length) clean(nodes);
- instance.observe(target, options);
+ if (data?.elements.length && !preview) clean(nodes);
});
/**
- * @description Cleans DOM again after all
- * @listens document#readystatechange
+ * @description Fixes bfcache issues
+ * @listens window#pageshow
*/
-document.addEventListener('readystatechange', () => {
- 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, true);
- setTimeout(() => clean(nodes, true), 2000);
- }
- });
+window.addEventListener('pageshow', (event) => {
+ if (event.persisted) {
+ dispatch({ hostname, type: 'GET_STATE' }, null, (state) => {
+ if (data?.elements.length && state?.enabled && !preview) {
+ fix();
+ clean(Array.from(document.querySelectorAll(data.elements)), true);
+ }
+ });
+ }
});
/**
- * @description Fix bfcache issues
- * @listens window#unload
+ * @description Sets up everything
*/
-window.addEventListener('unload', () => {});
-
-/**
- * @description Setups everything and starts to observe if enabled
- */
-
-dispatch({ hostname, type: 'GET_STORE' }, null, ({ enabled }) => {
+dispatch({ hostname, type: 'GET_STATE' }, null, (state) => {
dispatch({ type: 'ENABLE_POPUP' });
- if (enabled) {
- 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);
+ if (state?.enabled) {
+ dispatch({ hostname, type: 'GET_DATA' }, null, (result) => {
+ data = result;
+ observer.observe(document.body ?? document.documentElement, options);
dispatch({ type: 'ENABLE_ICON' });
});
}
diff --git a/packages/browser-extension/src/scripts/popup.js b/packages/browser-extension/src/scripts/popup.js
index df0379c..32355a9 100644
--- a/packages/browser-extension/src/scripts/popup.js
+++ b/packages/browser-extension/src/scripts/popup.js
@@ -1,5 +1,4 @@
/**
- * @constant chromeUrl
* @description Chrome Web Store link
* @type {string}
*/
@@ -7,15 +6,12 @@
const chromeUrl = 'https://chrome.google.com/webstore/detail/djcbfpkdhdkaflcigibkbpboflaplabg';
/**
- * @constant dispatch
* @description Shortcut to send messages to background script
- * @type {void}
*/
const dispatch = chrome.runtime.sendMessage;
/**
- * @constant edgeUrl
* @description Edge Add-ons link
* @type {string}
*/
@@ -24,7 +20,6 @@ const edgeUrl =
'https://microsoftedge.microsoft.com/addons/detail/hbogodfciblakeneadpcolhmfckmjcii';
/**
- * @constant firefoxUrl
* @description Firefox Add-ons link
* @type {string}
*/
@@ -32,7 +27,13 @@ const edgeUrl =
const firefoxUrl = 'https://addons.mozilla.org/es/firefox/addon/cookie-dialog-monster/';
/**
- * @constant isChromium
+ * @description Current hostname
+ * @type {string}
+ */
+
+let hostname = '?';
+
+/**
* @description Is current browser an instance of Chromium?
* @type {boolean}
*/
@@ -40,7 +41,6 @@ const firefoxUrl = 'https://addons.mozilla.org/es/firefox/addon/cookie-dialog-mo
const isChromium = navigator.userAgent.indexOf('Chrome') !== -1;
/**
- * @constant isEdge
* @description Is current browser an instance of Edge?
* @type {boolean}
*/
@@ -48,7 +48,6 @@ const isChromium = navigator.userAgent.indexOf('Chrome') !== -1;
const isEdge = navigator.userAgent.indexOf('Edg') !== -1;
/**
- * @constant isFirefox
* @description Is current browser an instance of Firefox?
* @type {boolean}
*/
@@ -59,22 +58,10 @@ const isFirefox = navigator.userAgent.indexOf('Firefox') !== -1;
* @description Disables or enables extension on current page
*/
-const handlePowerChange = () => {
- dispatch({ type: 'GET_TAB' }, null, ({ hostname, id }) => {
- dispatch({ hostname, type: 'GET_STORE' }, null, ({ enabled }) => {
- dispatch({ hostname, state: { enabled: !enabled }, type: 'UPDATE_STORE' });
- chrome.tabs.reload(id, { bypassCache: true });
- });
- });
-};
-
-/**
- * @description Reload current page
- */
-
-const handleReload = () => {
- dispatch({ type: 'GET_TAB' }, null, ({ id }) => {
- chrome.tabs.reload(id, { bypassCache: true });
+const handlePowerChange = async () => {
+ dispatch({ hostname, type: 'GET_STATE' }, null, (state) => {
+ dispatch({ hostname, state: { enabled: !state?.enabled }, type: 'UPDATE_STATE' });
+ chrome.tabs.reload({ bypassCache: true });
});
};
@@ -106,8 +93,12 @@ const handleRate = (event) => {
*/
const handleContentLoaded = () => {
- dispatch({ type: 'GET_TAB' }, null, ({ hostname }) => {
- dispatch({ hostname, type: 'GET_STORE' }, null, ({ enabled }) => {
+ dispatch({ type: 'GET_TAB' }, null, (tab) => {
+ hostname = tab?.url
+ ? new URL(tab.url).hostname.split('.').slice(-3).join('.').replace('www.', '')
+ : undefined;
+
+ dispatch({ hostname, type: 'GET_STATE' }, null, (state) => {
translate();
const host = document.getElementById('host');
@@ -119,13 +110,14 @@ const handleContentLoaded = () => {
like.addEventListener('click', handleRate);
power.addEventListener('change', handlePowerChange);
- reload.addEventListener('click', handleReload);
- if (isEdge) store.setAttribute('href', edgeUrl);
- else if (isChromium) store.setAttribute('href', chromeUrl);
- else if (isFirefox) store.setAttribute('href', firefoxUrl);
+ reload.addEventListener('click', () => chrome.tabs.reload({ bypassCache: true }));
unlike.addEventListener('click', handleRate);
- if (location) host.innerText = hostname.replace('www.', '');
- if (!enabled) power.removeAttribute('checked');
+
+ host.innerText = hostname ?? 'unknown';
+ if (isEdge) store?.setAttribute('href', edgeUrl);
+ else if (isChromium) store?.setAttribute('href', chromeUrl);
+ else if (isFirefox) store?.setAttribute('href', firefoxUrl);
+ if (!state.enabled) power.removeAttribute('checked');
});
});
};