diff --git a/src/background/broken-page-report.js b/src/background/broken-page-report.js new file mode 100644 index 000000000..8dae8ba64 --- /dev/null +++ b/src/background/broken-page-report.js @@ -0,0 +1,104 @@ +/** + * Ghostery Browser Extension + * https://www.ghostery.com/ + * + * Copyright 2017-present Ghostery GmbH. All rights reserved. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0 + */ + +import { store } from 'hybrids'; + +import Options, { SYNC_OPTIONS } from '/store/options.js'; + +import getBrowserInfo from '/utils/browser-info.js'; +import { SUPPORT_PAGE_URL } from '/utils/urls.js'; + +import { tabStats } from './stats.js'; + +async function getMetadata(tab) { + let result = '\n\n------\n\n'; + + const { version } = chrome.runtime.getManifest(); + result += `Extension version: ${version}`; + + const trackers = tabStats.get(tab.id)?.trackers.map((t) => t.id); + if (trackers) { + result += `\nTrackers(${trackers.length}): ${trackers.join(', ')}`; + } + + // Send only not-private options + const options = Object.fromEntries( + Object.entries(await store.resolve(Options)).filter(([key]) => + SYNC_OPTIONS.includes(key), + ), + ); + + result += `\nOptions: ${JSON.stringify(options)}`; + + return result; +} + +chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => { + if (msg.action === 'report-broken-page') { + (async () => { + try { + const formData = new FormData(); + const browserInfo = await getBrowserInfo(); + + formData.append('support_ticket[user_name]', ''); + formData.append('support_ticket[user_email]', msg.email); + formData.append( + 'support_ticket[subject]', + `[GBE] Broken page report: ${msg.url}`, + ); + + formData.append( + 'support_ticket[message]', + msg.description + (await getMetadata(msg.tab)), + ); + + formData.append('support_ticket[selected_browser]', browserInfo.name); + formData.append('support_ticket[browser_version]', browserInfo.version); + + if (browserInfo.osVersion !== 'other') { + formData.append('support_ticket[selected_os]', browserInfo.osVersion); + formData.append('support_ticket[os_version]', ''); + } + + if (msg.screenshot) { + const screenshot = await chrome.tabs.captureVisibleTab(null, { + format: 'jpeg', + quality: 100, + }); + formData.append( + 'support_ticket[screenshot]', + await fetch(screenshot).then((res) => res.blob()), + 'screenshot.jpeg', + ); + } + + await fetch(SUPPORT_PAGE_URL, { + method: 'POST', + body: formData, + }).then((res) => { + if (!res.ok || res.status > 204) { + throw new Error( + `Sending report has failed with status: ${res.status}`, + ); + } + }); + + sendResponse(); + } catch (e) { + sendResponse(e.message); + } + })(); + + return true; + } + + return false; +}); diff --git a/src/background/index.js b/src/background/index.js index b9529cf73..f2e6334b1 100644 --- a/src/background/index.js +++ b/src/background/index.js @@ -22,6 +22,7 @@ import './session.js'; import './stats.js'; import './notifications.js'; import './serp.js'; +import './broken-page-report.js'; import './helpers.js'; import './external.js'; diff --git a/src/background/session.js b/src/background/session.js index 919e40d8c..7d78c3ca2 100644 --- a/src/background/session.js +++ b/src/background/session.js @@ -9,7 +9,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0 */ import { UPDATE_SESSION_ACTION_NAME } from '/store/session.js'; -import { HOME_PAGE_URL, ACCOUNT_PAGE_URL } from '/utils/api.js'; +import { HOME_PAGE_URL, ACCOUNT_PAGE_URL } from '/utils/urls.js'; // Observe cookie changes (login/logout actions) chrome.webNavigation.onDOMContentLoaded.addListener(async ({ url = '' }) => { diff --git a/src/background/sync.js b/src/background/sync.js index 36e1051e5..e12d041ea 100644 --- a/src/background/sync.js +++ b/src/background/sync.js @@ -15,7 +15,7 @@ import Session from '/store/session.js'; import { getUserOptions, setUserOptions } from '/utils/api.js'; import * as OptionsObserver from '/utils/options-observer.js'; -import { HOME_PAGE_URL, ACCOUNT_PAGE_URL } from '/utils/api.js'; +import { HOME_PAGE_URL, ACCOUNT_PAGE_URL } from '/utils/urls.js'; import debounce from '/utils/debounce.js'; const syncOptions = debounce( diff --git a/src/manifest.chromium.json b/src/manifest.chromium.json index c1ad8589c..0d673b904 100644 --- a/src/manifest.chromium.json +++ b/src/manifest.chromium.json @@ -14,6 +14,7 @@ "storage", "scripting", "tabs", + "activeTab", "webRequest", "offscreen" ], diff --git a/src/manifest.firefox.json b/src/manifest.firefox.json index cbdcc5066..513ee3eaa 100644 --- a/src/manifest.firefox.json +++ b/src/manifest.firefox.json @@ -11,6 +11,7 @@ "storage", "scripting", "tabs", + "activeTab", "webNavigation", "webRequest", "webRequestBlocking", diff --git a/src/manifest.safari.json b/src/manifest.safari.json index 834d85c07..5de06d278 100644 --- a/src/manifest.safari.json +++ b/src/manifest.safari.json @@ -13,7 +13,8 @@ "webNavigation", "storage", "scripting", - "tabs" + "tabs", + "activeTab" ], "host_permissions": [ "http://*/*", diff --git a/src/pages/panel/assets/contribution.svg b/src/pages/panel/assets/contribution.svg new file mode 100644 index 000000000..2c725ac8a --- /dev/null +++ b/src/pages/panel/assets/contribution.svg @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/pages/panel/components/pause.js b/src/pages/panel/components/pause.js index 3555f1188..ead59f1cf 100644 --- a/src/pages/panel/components/pause.js +++ b/src/pages/panel/components/pause.js @@ -89,7 +89,6 @@ export default { html` `} -
+ ${pauseList && html`
+ ${paused?.revokeAt && + html` +
+ + + Something wrong? + + Report a broken page + + + + +
+ `} ` : html` diff --git a/src/pages/panel/views/report-confirm.js b/src/pages/panel/views/report-confirm.js new file mode 100644 index 000000000..da69850c1 --- /dev/null +++ b/src/pages/panel/views/report-confirm.js @@ -0,0 +1,49 @@ +/** + * Ghostery Browser Extension + * https://www.ghostery.com/ + * + * Copyright 2017-present Ghostery GmbH. All rights reserved. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0 + */ + +import { html, router } from 'hybrids'; + +import contributionImage from '../assets/contribution.svg'; + +export default { + render: () => html` + + `, +}; diff --git a/src/pages/panel/views/report-form.js b/src/pages/panel/views/report-form.js new file mode 100644 index 000000000..cc12635f9 --- /dev/null +++ b/src/pages/panel/views/report-form.js @@ -0,0 +1,168 @@ +/** + * Ghostery Browser Extension + * https://www.ghostery.com/ + * + * Copyright 2017-present Ghostery GmbH. All rights reserved. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0 + */ + +import { html, router, store, msg } from 'hybrids'; + +import Session from '/store/session.js'; +import { getCurrentTab, openTabWithUrl } from '/utils/tabs.js'; +import { SUPPORT_PAGE_URL } from '/utils/urls.js'; + +import ReportConfirm from './report-confirm.js'; + +const Form = { + url: '', + email: '', + description: '', + screenshot: false, + [store.connect]: { + async get() { + const [currentTab, session] = await Promise.all([ + getCurrentTab(), + store.resolve(Session), + ]); + + const url = currentTab && new URL(currentTab.url); + + return { + url: url ? `${url.origin}${url.pathname}` : '', + email: session.email, + }; + }, + async set(_, values) { + const error = await chrome.runtime.sendMessage({ + action: 'report-broken-page', + tab: await getCurrentTab(), + ...values, + }); + + if (error) throw new Error(error); + + return values; + }, + }, +}; + +function submit(host, event) { + try { + router.resolve( + event, + store.submit(host.form).then(() => store.clear(Form)), + ); + } catch { + event.preventDefault(); + } +} + +export default { + form: store(Form, { draft: true }), + render: ({ form }) => html` + + `, +}; diff --git a/src/pages/settings/components/custom-filters.js b/src/pages/settings/components/custom-filters.js index e97f4c804..5514801e4 100644 --- a/src/pages/settings/components/custom-filters.js +++ b/src/pages/settings/components/custom-filters.js @@ -38,7 +38,7 @@ export default { render: ({ input, result, disabled }) => html`