diff --git a/packages/browser-extension/src/manifest.json b/packages/browser-extension/src/manifest.json index 629e6e8..5778eff 100644 --- a/packages/browser-extension/src/manifest.json +++ b/packages/browser-extension/src/manifest.json @@ -26,7 +26,7 @@ "*://*.facebook.com/*", "*://*.googleapis.com/embed/*", "*://*.messenger.com/*", - "*://*.office365.com*", + "*://*.office365.com/*", "*://*.officeapps.live.com/*", "*://*.sharepoint.com/*", "*://*.suite.office.com/*", diff --git a/packages/browser-extension/src/scripts/background.js b/packages/browser-extension/src/scripts/background.js index d3ddec5..ea4357d 100644 --- a/packages/browser-extension/src/scripts/background.js +++ b/packages/browser-extension/src/scripts/background.js @@ -2,54 +2,47 @@ * @description API URL * @type {string} */ - const apiUrl = 'https://api.cookie-dialog-monster.com/rest/v2'; /** * @description Context menu identifier * @type {string} */ - const extensionMenuItemId = 'CDM-MENU'; /** * @description Context menu identifier * @type {string} */ - const reportMenuItemId = 'CDM-REPORT'; /** * @description Context menu identifier * @type {string} */ - const settingsMenuItemId = 'CDM-SETTINGS'; /** * @description A shortcut for chrome.scripting * @type {chrome.scripting} */ - const script = chrome.scripting; /** * @description The storage to use * @type {chrome.storage.LocalStorageArea} */ - const storage = chrome.storage.local; /** - * @description Refreshes data + * @description Refresh data * @param {void?} callback */ - const refreshData = (callback) => { try { fetch(`${apiUrl}/data/`).then((result) => { result.json().then(({ data }) => { - chrome.storage.local.set({ data }); + chrome.storage.local.set({ data }, suppressLastError); callback?.(data); }); }); @@ -60,30 +53,39 @@ const refreshData = (callback) => { /** * @async - * @description Reports active tab URL + * @description Report active tab URL * @param {any} message * @param {chrome.tabs.Tab} tab */ - const report = async (message, tab) => { - const reason = message.reason; - const userAgent = message.userAgent; - const version = chrome.runtime.getManifest().version; - const body = JSON.stringify({ reason, url: tab.url, userAgent, version }); - const headers = { 'Content-type': 'application/json' }; - const url = `${apiUrl}/report/`; + try { + const reason = message.reason; + const userAgent = message.userAgent; + const version = chrome.runtime.getManifest().version; + const body = JSON.stringify({ reason, url: tab.url, userAgent, version }); + const headers = { 'Content-type': 'application/json' }; + const url = `${apiUrl}/report/`; - await fetch(url, { body, headers, method: 'POST' }); + await fetch(url, { body, headers, method: 'POST' }); + } catch { + console.error("Can't send report"); + } }; /** - * @description Listens to context menus clicked + * @description Supress `chrome.runtime.lastError` */ +const suppressLastError = () => void chrome.runtime.lastError; +/** + * @description Listen to context menus clicked + */ chrome.contextMenus.onClicked.addListener((info, tab) => { + const tabId = tab?.id; + switch (info.menuItemId) { case reportMenuItemId: - if (tab) chrome.tabs.sendMessage(tab.id, { type: 'SHOW_REPORT_DIALOG' }); + if (tabId) chrome.tabs.sendMessage(tabId, { type: 'SHOW_REPORT_DIALOG' }, suppressLastError); break; case settingsMenuItemId: chrome.runtime.openOptionsPage(); @@ -96,7 +98,6 @@ chrome.contextMenus.onClicked.addListener((info, tab) => { /** * @description Listens to messages */ - chrome.runtime.onMessage.addListener((message, sender, callback) => { const hostname = message.hostname; const isPage = sender.frameId === 0; @@ -105,17 +106,17 @@ chrome.runtime.onMessage.addListener((message, sender, callback) => { switch (message.type) { case 'DISABLE_ICON': if (isPage && tabId) { - chrome.action.setIcon({ path: '/assets/icons/disabled.png', tabId }); + chrome.action.setIcon({ path: '/assets/icons/disabled.png', tabId }, suppressLastError); } break; case 'ENABLE_ICON': if (isPage && tabId) { - chrome.action.setIcon({ path: '/assets/icons/enabled.png', tabId }); + chrome.action.setIcon({ path: '/assets/icons/enabled.png', tabId }, suppressLastError); } break; case 'ENABLE_POPUP': if (isPage && tabId) { - chrome.action.setPopup({ popup: '/popup.html', tabId }); + chrome.action.setPopup({ popup: '/popup.html', tabId }, suppressLastError); } break; case 'GET_DATA': @@ -172,34 +173,41 @@ chrome.runtime.onMessage.addListener((message, sender, callback) => { /** * @description Listens to extension installed */ - chrome.runtime.onInstalled.addListener(() => { - chrome.contextMenus.create({ - contexts: ['all'], - documentUrlPatterns: chrome.runtime.getManifest().content_scripts[0].matches, - id: extensionMenuItemId, - title: 'Cookie Dialog Monster', - }); - chrome.contextMenus.create({ - contexts: ['all'], - documentUrlPatterns: chrome.runtime.getManifest().content_scripts[0].matches, - id: settingsMenuItemId, - parentId: extensionMenuItemId, - title: chrome.i18n.getMessage('contextMenu_settingsOption'), - }); - chrome.contextMenus.create({ - contexts: ['all'], - documentUrlPatterns: chrome.runtime.getManifest().content_scripts[0].matches, - id: reportMenuItemId, - parentId: extensionMenuItemId, - title: chrome.i18n.getMessage('contextMenu_reportOption'), - }); + chrome.contextMenus.create( + { + contexts: ['all'], + documentUrlPatterns: chrome.runtime.getManifest().content_scripts[0].matches, + id: extensionMenuItemId, + title: 'Cookie Dialog Monster', + }, + suppressLastError + ); + chrome.contextMenus.create( + { + contexts: ['all'], + documentUrlPatterns: chrome.runtime.getManifest().content_scripts[0].matches, + id: settingsMenuItemId, + parentId: extensionMenuItemId, + title: chrome.i18n.getMessage('contextMenu_settingsOption'), + }, + suppressLastError + ); + chrome.contextMenus.create( + { + contexts: ['all'], + documentUrlPatterns: chrome.runtime.getManifest().content_scripts[0].matches, + id: reportMenuItemId, + parentId: extensionMenuItemId, + title: chrome.i18n.getMessage('contextMenu_reportOption'), + }, + suppressLastError + ); }); /** - * @description Listens to first start + * @description Listen to first start */ - chrome.runtime.onStartup.addListener(() => { refreshData(); }); diff --git a/packages/browser-extension/src/scripts/content.js b/packages/browser-extension/src/scripts/content.js index 3e86d54..3c559aa 100644 --- a/packages/browser-extension/src/scripts/content.js +++ b/packages/browser-extension/src/scripts/content.js @@ -2,49 +2,42 @@ * @description Data properties * @type {{ classes: string[], commonWords?: string[], fixes: string[], elements: string[], skips: string[], tags: string[] }?} */ - let data = null; /** * @description Shortcut to send messages to background script */ - const dispatch = chrome.runtime.sendMessage; /** * @description Current hostname * @type {string} */ - const hostname = getHostname(); /** * @description Options provided to observer * @type {MutationObserverInit} */ - const options = { childList: true, subtree: true }; /** * @description Is consent preview page? */ - const preview = hostname.startsWith('consent.') || hostname.startsWith('myprivacy.'); /** * @description Extension state * @type {{ enabled: boolean }} */ - let state = { enabled: true }; /** - * @description Cleans DOM + * @description Clean DOM * @param {Element[]} elements * @param {boolean?} skipMatch * @returns {void} */ - function clean(elements, skipMatch) { for (const element of elements) { if (match(element, skipMatch)) { @@ -58,11 +51,10 @@ function clean(elements, skipMatch) { } /** - * @description Forces a DOM clean in the specific element + * @description Force a DOM clean in the specific element * @param {HTMLElement} element * @returns {void} */ - function forceClean(element) { const elements = [...element.querySelectorAll(data.elements)]; @@ -71,20 +63,18 @@ function forceClean(element) { } /** - * @description Forces element to have these styles + * @description Force element to have these styles * @param {HTMLElement} element * @returns {void} */ - function forceElementStyles(element) { element.style.setProperty('display', 'none', 'important'); } /** - * @description Calculates current hostname + * @description Calculate current hostname * @returns {string} */ - function getHostname() { let hostname = document.location.hostname; const referrer = document.referrer; @@ -97,11 +87,10 @@ function getHostname() { } /** - * @description Checks if an element is visible in the viewport + * @description Check if an element is visible in the viewport * @param {HTMLElement} element * @returns {boolean} */ - function isInViewport(element) { const height = window.innerHeight || document.documentElement.clientHeight; const position = element.getBoundingClientRect(); @@ -114,12 +103,11 @@ function isInViewport(element) { } /** - * @description Checks if element element is removable + * @description Check if element element is removable * @param {Element} element * @param {boolean?} skipMatch * @returns {boolean} */ - function match(element, skipMatch) { if (!(element instanceof HTMLElement) || !element.tagName) { return false; @@ -152,10 +140,9 @@ function match(element, skipMatch) { } /** - * @description Fixes data, consent page and scroll issues + * @description Fix data, consent page and scroll issues * @returns {void} */ - function fix() { const backdrop = document.getElementsByClassName('modal-backdrop')[0]; const fixes = data?.fixes ?? []; @@ -200,10 +187,9 @@ function fix() { } /** - * @description Calculates reading time for the current page to avoid lags in large pages + * @description Calculate reading time for the current page to avoid lags in large pages * @returns {number} */ - function readingTime() { const text = document.body.innerText; const wpm = 225; @@ -215,10 +201,9 @@ function readingTime() { /** * @async - * @description Sets up everything + * @description Set up everything * @param {boolean} skipReadyStateHack */ - async function runSetup(skipReadyStateHack) { state = (await dispatch({ hostname, type: 'GET_HOSTNAME_STATE' })) ?? state; dispatch({ type: 'ENABLE_POPUP' }); @@ -240,7 +225,6 @@ async function runSetup(skipReadyStateHack) { * @description Mutation Observer instance * @type {MutationObserver} */ - const observer = new MutationObserver((mutations) => { const elements = mutations.map((mutation) => Array.from(mutation.addedNodes)).flat(); @@ -250,24 +234,22 @@ const observer = new MutationObserver((mutations) => { /** * @async - * @description Runs setup if the page wasn't focused yet + * @description Run setup if the page wasn't focused yet * @listens window#focus * @returns {void} */ - window.addEventListener('focus', async () => { - if (!data) { + if (document.body && !data) { await runSetup(true); clean([...document.body.children]); } }); /** - * @description Fixes still existing elements when page fully load + * @description Fix still existing elements when page fully load * @listens window#load * @returns {void} */ - window.addEventListener('load', () => { if (document.hasFocus()) { window.dispatchEvent(new Event('run')); @@ -275,11 +257,10 @@ window.addEventListener('load', () => { }); /** - * @description Fixes bfcache issues + * @description Fix bfcache issues * @listens window#pageshow * @returns {void} */ - window.addEventListener('pageshow', (event) => { if (document.hasFocus() && event.persisted) { window.dispatchEvent(new Event('run')); @@ -287,11 +268,10 @@ window.addEventListener('pageshow', (event) => { }); /** - * @description Forces a clean when this event is fired + * @description Force clean when this event is fired * @listens window#run * @returns {void} */ - window.addEventListener('run', () => { if (data?.elements.length && document.body && state.enabled && !preview) { if (readingTime() < 4) { @@ -303,10 +283,6 @@ window.addEventListener('run', () => { } }); -/** - * @description As this extension do really expensive work, it only runs if the user is on the page - */ - if (document.hasFocus()) { runSetup(); } diff --git a/packages/browser-extension/src/scripts/dialog.js b/packages/browser-extension/src/scripts/dialog.js index 96c1bf2..93849eb 100644 --- a/packages/browser-extension/src/scripts/dialog.js +++ b/packages/browser-extension/src/scripts/dialog.js @@ -2,7 +2,6 @@ * @description Report reasons * @type {string[]} */ - const reasons = [ 'Cannot click', 'Page contains visual glitches', @@ -15,13 +14,11 @@ const reasons = [ /** * @description Report dialog ID */ - const reportDialogId = 'report-dialog'; /** * @description Report dialog outer HTML */ - const reportDialogHtml = ` @@ -124,7 +121,6 @@ const reportDialogHtml = ` * @description Dialog close button click handler * @param {MouseEvent} event */ - const closeButtonClickHandler = (event) => { const dialog = document.getElementById(reportDialogId); @@ -133,9 +129,8 @@ const closeButtonClickHandler = (event) => { }; /** - * @description Hides report dialog + * @description Hide report dialog */ - const hideReportDialog = () => { document.getElementById(reportDialogId)?.remove(); }; @@ -144,7 +139,6 @@ const hideReportDialog = () => { * @description Dialog radio input click handler * @param {MouseEvent} event */ - const radioClickHandler = (event) => { const dialog = document.getElementById(reportDialogId); const radios = dialog.getElementsByTagName('report-dialog-radio'); @@ -161,9 +155,8 @@ const radioClickHandler = (event) => { }; /** - * @description Shows report dialog + * @description Show report dialog */ - const showReportDialog = () => { const parser = new DOMParser(); const result = parser.parseFromString(reportDialogHtml, 'text/html'); @@ -194,7 +187,6 @@ const showReportDialog = () => { * @description Dialog submit button click handler * @param {MouseEvent} event */ - const submitButtonClickHandler = (event) => { const dialog = document.getElementById(reportDialogId); const formView = dialog?.getElementsByTagName('report-dialog-form-view')[0]; @@ -211,9 +203,8 @@ const submitButtonClickHandler = (event) => { }; /** - * @description Listens to messages + * @description Listen to messages */ - chrome.runtime.onMessage.addListener((message) => { const isPage = window === window.top; diff --git a/packages/browser-extension/src/scripts/options.js b/packages/browser-extension/src/scripts/options.js index d648362..58facac 100644 --- a/packages/browser-extension/src/scripts/options.js +++ b/packages/browser-extension/src/scripts/options.js @@ -1,27 +1,23 @@ /** * @description Shortcut to send messages to background script */ - const dispatch = chrome.runtime.sendMessage; /** * @description Domain RegExp */ - const domainRx = /^([a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,}$/g; /** * @description Exclusion list, URLs where the user prefers to disable the extension * @type {string[]} */ - let exclusionList = []; /** - * @description Renders exclusion items into exclusion list + * @description Render exclusion items into exclusion list * @returns {void} */ - function createList() { const emptyItemElement = document.getElementById('exclusion-list-item-empty'); const exclusionListElement = document.getElementById('exclusion-list'); @@ -57,7 +53,6 @@ function createList() { * @description Add a new item to the exclusion list * @returns {Promise} */ - async function handleAddClick() { const exclusionValue = window.prompt(chrome.i18n.getMessage('options_addPrompt')); @@ -77,7 +72,6 @@ async function handleAddClick() { * @description Clear all items from the exclusion list * @returns {Promise} */ - async function handleClearClick() { const filterInputElement = document.getElementById('filter-input'); @@ -95,7 +89,6 @@ async function handleClearClick() { * @async * @description Setup handlers and items */ - async function handleContentLoaded() { exclusionList = await dispatch({ type: 'GET_EXCLUSION_LIST' }); createList(); @@ -123,11 +116,10 @@ async function handleContentLoaded() { /** * @async - * @description Deletes the clicked element from the exclusion list + * @description Delete the clicked element from the exclusion list * @param {MouseEvent} event * @returns {Promise} */ - async function handleDeleteClick(event) { const filterInputElement = document.getElementById('filter-input'); const { value } = event.currentTarget.parentElement.dataset; @@ -141,10 +133,9 @@ async function handleDeleteClick(event) { } /** - * @description Exports a file with the current exclusion list + * @description Export a file with the current exclusion list * @returns {void} */ - function handleExportClick() { const anchor = document.createElement('a'); const now = new Date(); @@ -164,11 +155,10 @@ function handleExportClick() { } /** - * @description Processes a file and sends the updates + * @description Process a file and send the updates * @param {InputEvent} event * @returns {void} */ - function handleFileChange(event) { const file = event.currentTarget.files[0]; const filterInputElement = document.getElementById('filter-input'); @@ -194,11 +184,10 @@ function handleFileChange(event) { } /** - * @description Applies filter to the exclusion list when the user presses ENTER key + * @description Apply filter to the exclusion list when the user presses ENTER key * @param {KeyboardEvent} event * @returns {void} */ - function handleFilterKeyDown(event) { if (event.key === 'Enter') { const filterValue = event.currentTarget.value.trim(); @@ -207,20 +196,18 @@ function handleFilterKeyDown(event) { } /** - * @description Shallow clicks an hidden input to open the file explorer + * @description Shallow click an hidden input to open the file explorer * @returns {void} */ - function handleImportClick() { const fileInputElement = document.getElementById('file-input'); fileInputElement.click(); } /** - * @description Applies translations to tags with i18n data attribute + * @description Apply translations to tags with i18n data attribute * @returns {void} */ - function translate() { const nodes = document.querySelectorAll('[data-i18n], [data-i18n-placeholder]'); @@ -239,11 +226,10 @@ function translate() { } /** - * @description Updates exclusion items in DOM + * @description Update exclusion items in DOM * @param {string | undefined} filterValue * @returns {void} */ - function updateList(filterValue) { const emptyItemElement = document.getElementById('exclusion-list-item-empty'); const exclusionListElement = document.getElementById('exclusion-list'); @@ -276,5 +262,4 @@ function updateList(filterValue) { * @description Listen to document ready * @listens document#DOMContentLoaded */ - document.addEventListener('DOMContentLoaded', handleContentLoaded); diff --git a/packages/browser-extension/src/scripts/popup.js b/packages/browser-extension/src/scripts/popup.js index 783f090..d836de9 100644 --- a/packages/browser-extension/src/scripts/popup.js +++ b/packages/browser-extension/src/scripts/popup.js @@ -2,20 +2,17 @@ * @description Chrome Web Store link * @type {string} */ - const chromeUrl = 'https://chrome.google.com/webstore/detail/djcbfpkdhdkaflcigibkbpboflaplabg'; /** * @description Shortcut to send messages to background script */ - const dispatch = chrome.runtime.sendMessage; /** * @description Edge Add-ons link * @type {string} */ - const edgeUrl = 'https://microsoftedge.microsoft.com/addons/detail/hbogodfciblakeneadpcolhmfckmjcii'; @@ -23,42 +20,36 @@ const edgeUrl = * @description Firefox Add-ons link * @type {string} */ - const firefoxUrl = 'https://addons.mozilla.org/firefox/addon/cookie-dialog-monster'; /** * @description Current hostname * @type {string} */ - let hostname = '?'; /** * @description Is current browser an instance of Chromium? * @type {boolean} */ - const isChromium = navigator.userAgent.indexOf('Chrome') !== -1; /** * @description Is current browser an instance of Edge? * @type {boolean} */ - const isEdge = navigator.userAgent.indexOf('Edg') !== -1; /** * @description Is current browser an instance of Firefox? * @type {boolean} */ - const isFirefox = navigator.userAgent.indexOf('Firefox') !== -1; /** * @description Extension state * @type {{ enabled: boolean }} */ - let state = { enabled: true }; /** @@ -66,7 +57,6 @@ let state = { enabled: true }; * @description Setup stars handlers and result message links * @returns {Promise} */ - async function handleContentLoaded() { const tab = await dispatch({ type: 'GET_TAB' }); @@ -103,11 +93,10 @@ async function handleContentLoaded() { /** * @async - * @description Opens a new tab + * @description Open a new tab * @param {MouseEvent} event * @returns {Promise} */ - async function handleLinkRedirect(event) { const { href } = event.currentTarget.dataset; @@ -118,11 +107,10 @@ async function handleLinkRedirect(event) { /** * @async - * @description Disables or enables extension on current page + * @description Disable or enable extension on current page * @param {MouseEvent} event * @returns {Promise} */ - async function handlePowerToggle(event) { state = { enabled: !state.enabled }; dispatch({ hostname, state, type: 'SET_HOSTNAME_STATE' }); @@ -133,19 +121,17 @@ async function handlePowerToggle(event) { } /** - * @description Opens options page + * @description Open options page * @returns {void} */ - function handleSettingsClick() { chrome.runtime.openOptionsPage(); } /** - * @description Applies translations to tags with i18n data attribute + * @description Apply translations to tags with i18n data attribute * @returns {void} */ - function translate() { const nodes = document.querySelectorAll('[data-i18n], [data-i18n-placeholder]'); @@ -167,5 +153,4 @@ function translate() { * @description Listen to document ready * @listens document#DOMContentLoaded */ - document.addEventListener('DOMContentLoaded', handleContentLoaded);