diff --git a/add-on/_locales/en/messages.json b/add-on/_locales/en/messages.json index 1388416ba..08a07c917 100644 --- a/add-on/_locales/en/messages.json +++ b/add-on/_locales/en/messages.json @@ -786,5 +786,29 @@ "recovery_page_update_preferences": { "message": "Update your IPFS Companion preferences", "description": "Learn more link on the recovery screen (recovery_page_learn_more)" + }, + "request_permissions_page_title": { + "message": "Grant Host Permissions | IPFS Companion", + "description": "Title of the recovery page (recovery_page_title)" + }, + "request_permissions_page_sub_header": { + "message": "Just one more thing to do before you're all set! :)", + "description": "Sub-Header on the recovery screen (recovery_page_sub_header)" + }, + "request_permissions_page_message_p1": { + "message": "IPFS Companion needs permission to identify IPFS resources on the web.", + "description": "Message Para-1 on the recovery screen (recovery_page_message_p1)" + }, + "request_permissions_page_message_p2": { + "message": "The IPFS Companion requires Host permission to access data on all websites. This allows it to inspect all web requests, identify ones for content-addressed IPFS resources, and load them using your local IPFS node. Please click the button below to grant these permissions.", + "description": "Message Para-2 on the recovery screen (recovery_page_message_p2)" + }, + "request_permissions_page_button": { + "message": "Grant Permission", + "description": "Button on the recovery screen (recovery_page_button)" + }, + "request_permissions_page_learn_more": { + "message": "Learn more about host permissions", + "description": "Learn more link on the recovery screen (recovery_page_learn_more)" } } diff --git a/add-on/src/landing-pages/permissions/request.css b/add-on/src/landing-pages/permissions/request.css new file mode 100644 index 000000000..1a6fdc282 --- /dev/null +++ b/add-on/src/landing-pages/permissions/request.css @@ -0,0 +1,54 @@ +@import url('~tachyons/css/tachyons.css'); +@import url('~ipfs-css/ipfs.css'); + +#left-col { + background-image: url('../../../images/stars.png'), linear-gradient(to bottom, #041727 0%, #043b55 100%); + background-size: 100%; + background-repeat: repeat; +} + +a:hover { + text-decoration: none; +} + +a:visited { + color: inherit; +} + +/* + https://github.com/tachyons-css/tachyons-queries + Tachyons: $point == large +*/ +@media (min-width: 60em) { + #left-col { + position: fixed; + top: 0; + right: 55%; + width: 45%; + background-image: url('../../../images/stars.png'), linear-gradient(to bottom, #041727 0%, #043b55 100%); + background-size: 100%; + background-repeat: repeat; + } + + #right-col { + margin-left: 54%; + margin-right: 6%; + } +} + +@media (max-height: 800px) { + #left-col img { + width: 98px !important; + height: 98px !important; + } + + #left-col svg { + width: 60px; + } +} + +.recovery-root { + width: 100%; + height: 100%; + text-align: left; +} diff --git a/add-on/src/landing-pages/permissions/request.html b/add-on/src/landing-pages/permissions/request.html new file mode 100644 index 000000000..965d9e84e --- /dev/null +++ b/add-on/src/landing-pages/permissions/request.html @@ -0,0 +1,20 @@ + + + + IPFS Node is Offline + + + + + + + + +
+
+
+ + +
+ + diff --git a/add-on/src/landing-pages/permissions/request.js b/add-on/src/landing-pages/permissions/request.js new file mode 100644 index 000000000..242a2e920 --- /dev/null +++ b/add-on/src/landing-pages/permissions/request.js @@ -0,0 +1,56 @@ +'use strict' +/* eslint-env browser, webextensions */ + +import choo from 'choo' +import html from 'choo/html/index.js' +import { i18n, runtime, permissions } from 'webextension-polyfill' +import { nodeOffSvg } from '../welcome/page.js' +import createWelcomePageStore from '../welcome/store.js' +import { optionsPage } from '../../lib/constants.js' +import './request.css' + +const app = choo() + +const learnMoreLink = html`${i18n.getMessage('request_permissions_page_learn_more')}` + +const optionsPageLink = html`${i18n.getMessage('recovery_page_update_preferences')}` + +// TODO (whizzzkid): refactor base store to be more generic. +app.use(createWelcomePageStore(i18n, runtime)) +// Register our single route +app.route('*', () => { + runtime.sendMessage({ telemetry: { trackView: 'request-permissions' } }) + const requestPermission = async () => { + await permissions.request({ origins: [''] }) + runtime.reload() + } + + return html`
+
+
+ ${nodeOffSvg(200)} +

${i18n.getMessage('request_permissions_page_sub_header')}

+
+
+ +
+

${i18n.getMessage('request_permissions_page_message_p1')}

+

${i18n.getMessage('request_permissions_page_message_p2')}

+ +

+ ${learnMoreLink} | ${optionsPageLink} + +

+
` +}) + +// Start the application and render it to the given querySelector +app.mount('#root') + +// Set page title and header translation +document.title = i18n.getMessage('request_permissions_page_title') diff --git a/add-on/src/lib/constants.js b/add-on/src/lib/constants.js index ef2f7c61a..179f80943 100644 --- a/add-on/src/lib/constants.js +++ b/add-on/src/lib/constants.js @@ -4,4 +4,5 @@ export const welcomePage = '/dist/landing-pages/welcome/index.html' export const optionsPage = '/dist/options/options.html' export const recoveryPagePath = '/dist/recovery/recovery.html' +export const requestRequiredPermissionsPage = '/dist/landing-pages/permissions/request.html' export const tickMs = 250 // no CPU spike, but still responsive enough diff --git a/add-on/src/lib/ipfs-request.js b/add-on/src/lib/ipfs-request.js index 2a400d1f7..8ae67d078 100644 --- a/add-on/src/lib/ipfs-request.js +++ b/add-on/src/lib/ipfs-request.js @@ -240,7 +240,7 @@ export function createRequestModifier (getState, dnslinkResolver, ipfsPathValida const { requestHeaders } = request if (isCompanionRequest(request)) { - // '403 - Forbidden' fix for Chrome and Firefox + // '403 - Forbidden' fix for browsers that support blocking webRequest API (e.g. Firefox) // -------------------------------------------- // We update "Origin: *-extension://" HTTP headers in requests made to API // by js-kubo-rpc-client running in the background page of browser diff --git a/add-on/src/lib/on-installed.js b/add-on/src/lib/on-installed.js index 11dae33fe..b40d88a35 100644 --- a/add-on/src/lib/on-installed.js +++ b/add-on/src/lib/on-installed.js @@ -3,7 +3,7 @@ import browser from 'webextension-polyfill' import debug from 'debug' -import { welcomePage } from './constants.js' +import { requestRequiredPermissionsPage, welcomePage } from './constants.js' import { brave, braveNodeType } from './ipfs-client/brave.js' const { version } = browser.runtime.getManifest() @@ -21,6 +21,15 @@ export async function onInstalled (details) { export async function runPendingOnInstallTasks () { const { onInstallTasks, displayReleaseNotes } = await browser.storage.local.get(['onInstallTasks', 'displayReleaseNotes']) await browser.storage.local.remove('onInstallTasks') + // this is needed because `permissions.request` cannot be called from a script. If that happens the browser will + // throws: Error: permissions.request may only be called from a user input handler + // To avoid this, we open a new tab with the permissions page and ask the user to grant the permissions. + // That makes the request valid and allows us to gain access to the permissions. + if (!(await browser.permissions.contains({ origins: [''] }))) { + return browser.tabs.create({ + url: requestRequiredPermissionsPage + }) + } switch (onInstallTasks) { case 'onFirstInstall': await useNativeNodeIfFeasible(browser) diff --git a/add-on/src/recovery/recovery.js b/add-on/src/recovery/recovery.js index 1cc3447e3..7339e7d41 100644 --- a/add-on/src/recovery/recovery.js +++ b/add-on/src/recovery/recovery.js @@ -19,7 +19,7 @@ const optionsPageLink = html` { - browser.runtime.sendMessage({ telemetry: { trackView: 'recovery' } }) + runtime.sendMessage({ telemetry: { trackView: 'recovery' } }) const { hash } = window.location const { href: publicURI } = new URL(decodeURIComponent(hash.slice(1))) diff --git a/webpack.config.js b/webpack.config.js index 82fa0f5ec..37ee165b0 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -190,7 +190,8 @@ const uiConfig = merge(commonConfig, { importPage: './add-on/src/popup/quick-import.js', optionsPage: './add-on/src/options/options.js', recoveryPage: './add-on/src/recovery/recovery.js', - welcomePage: './add-on/src/landing-pages/welcome/index.js' + welcomePage: './add-on/src/landing-pages/welcome/index.js', + requestPermissionsPage: './add-on/src/landing-pages/permissions/request.js', }, optimization: { splitChunks: {