Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: ON / OFF Toggle #500

Merged
merged 10 commits into from
Jun 20, 2018
32 changes: 14 additions & 18 deletions add-on/_locales/en/messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,16 @@
"description": "A pop-up title when user hovers on Browser Action button (browserAction_title)"
},
"panel_headerIpfsNodeIconLabel": {
"message": "IPFS node",
"message": "IPFS Companion",
"description": "Label for IPFS icon (panel_headerIpfsNodeIconLabel)"
},
"panel_headerIpfsNodeEmbedded": {
"message": "Embedded",
"description": "Label for an embedded IPFS node (panel_headerIpfsNodeEmbedded)"
},
"panel_headerIpfsNodeEmbeddedTitle": {
"message": "Experimental: Use IPFS embedded in your browser via js-ipfs",
"description": "Label for an embedded IPFS node (panel_headerIpfsNodeEmbeddedTitle)"
"panel_headerActiveToggleTitle": {
"message": "Global toggle: activate or suspend all IPFS integrations",
"description": "Label for an embedded IPFS node (panel_headerActiveToggleTitle)"
},
"panel_headerIpfsNodeExternal": {
"message": "External",
"description": "Label for an external IPFS node (panel_headerIpfsNodeExternal)"
},
"panel_headerIpfsNodeExternalTitle": {
"message": "Connect to an IPFS node over HTTP",
"description": "Label for an external IPFS node (panel_headerIpfsNodeExternalTitle)"
"panel_statusOffline": {
"message": "offline",
"description": "A label in Node status section of Browser Action pop-up (panel_statusOffline)"
},
"panel_statusGatewayAddress": {
"message": "Gateway",
Expand Down Expand Up @@ -52,7 +44,7 @@
"description": "A menu item in Browser Action pop-up (panel_openWebui)"
},
"panel_openPreferences": {
"message": "Open Preferences",
"message": "Open Preferences of Browser Extension",
"description": "A menu item in Browser Action pop-up (panel_openPreferences)"
},
"panel_switchToCustomGateway": {
Expand Down Expand Up @@ -189,8 +181,12 @@
"message": "IPFS Node Type",
"description": "An option title on the Preferences screen (option_ipfsNodeType_title)"
},
"option_ipfsNodeType_description": {
"message": "External: Connect to an IPFS daemon over HTTP. \n\n Embedded: Run an IPFS node in your browser via ipfs-js *Experimental*",
"option_ipfsNodeType_external_description": {
"message": "External (suggested): connect to an IPFS daemon over HTTP.",
"description": "An option description on the Preferences screen (option_ipfsNodeType_description)"
},
"option_ipfsNodeType_embedded_description": {
"message": "Embedded (experimental): run a js-ipfs node in your browser (YMMV, may drain your battery etc).",
"description": "An option description on the Preferences screen (option_ipfsNodeType_description)"
},
"option_ipfsNodeConfig_title": {
Expand Down
41 changes: 29 additions & 12 deletions add-on/src/lib/ipfs-companion.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,15 +40,17 @@ module.exports = async function init () {
state = initState(options)
notify = createNotifier(getState)

// It's ok for this to fail, node might be unavailable or mis-configured
try {
ipfs = await initIpfsClient(state)
} catch (err) {
console.error('[ipfs-companion] Failed to init IPFS client', err)
notify(
'notify_startIpfsNodeErrorTitle',
err.name === 'ValidationError' ? err.details[0].message : err.message
)
if (state.active) {
// It's ok for this to fail, node might be unavailable or mis-configured
try {
ipfs = await initIpfsClient(state)
} catch (err) {
console.error('[ipfs-companion] Failed to init IPFS client', err)
notify(
'notify_startIpfsNodeErrorTitle',
err.name === 'ValidationError' ? err.details[0].message : err.message
)
}
}

copier = createCopier(getState, notify)
Expand All @@ -60,7 +62,7 @@ module.exports = async function init () {
onCopyAddressAtPublicGw: () => copier.copyAddressAtPublicGw()
})
modifyRequest = createRequestModifier(getState, dnsLink, ipfsPathValidator, runtime)
ipfsProxy = createIpfsProxy(() => ipfs, getState)
ipfsProxy = createIpfsProxy(getIpfs, getState)
ipfsProxyContentScript = await registerIpfsProxyContentScript()
registerListeners()
await setApiStatusUpdateInterval(options.ipfsApiPollMs)
Expand All @@ -79,6 +81,12 @@ module.exports = async function init () {
return state
}

function getIpfs () {
if (state.active && ipfs) return ipfs
console.error('[ipfs-companion] Refused access to IPFS API client, check if extension is enabled')
throw new Error('IPFS Companion: API client is disabled')
}

function registerListeners () {
browser.webRequest.onBeforeSendHeaders.addListener(onBeforeSendHeaders, {urls: ['<all_urls>']}, ['blocking', 'requestHeaders'])
browser.webRequest.onBeforeRequest.addListener(onBeforeRequest, {urls: ['<all_urls>']}, ['blocking'])
Expand Down Expand Up @@ -107,8 +115,8 @@ module.exports = async function init () {
if (previousHandle) {
previousHandle.unregister()
}
if (!state.ipfsProxy || !browser.contentScripts) {
// no-op if window.ipfs is disabled in Preferences
if (!state.active || !state.ipfsProxy || !browser.contentScripts) {
// no-op if global toggle is off, window.ipfs is disabled in Preferences
// or if runtime has no contentScript API
// (Chrome loads content script via manifest)
return
Expand Down Expand Up @@ -200,6 +208,7 @@ module.exports = async function init () {
async function sendStatusUpdateToBrowserAction () {
if (!browserActionPort) return
const info = {
active: state.active,
ipfsNodeType: state.ipfsNodeType,
peerCount: state.peerCount,
gwURLString: state.gwURLString,
Expand Down Expand Up @@ -353,6 +362,7 @@ module.exports = async function init () {
}

async function onUpdatedTab (tabId, changeInfo, tab) {
if (!state.active) return // skip content script injection when off
if (changeInfo.status && changeInfo.status === 'complete' && tab.url && tab.url.startsWith('http')) {
if (state.linkify) {
console.info(`[ipfs-companion] Running linkfyDOM for ${tab.url}`)
Expand Down Expand Up @@ -549,6 +559,11 @@ module.exports = async function init () {
// debug info
// console.info(`Storage key "${key}" in namespace "${area}" changed. Old value was "${change.oldValue}", new value is "${change.newValue}".`)
switch (key) {
case 'active':
state[key] = change.newValue
ipfsProxyContentScript = await registerIpfsProxyContentScript()
shouldRestartIpfsClient = true
break
case 'ipfsNodeType':
case 'ipfsNodeConfig':
shouldRestartIpfsClient = true
Expand Down Expand Up @@ -598,6 +613,8 @@ module.exports = async function init () {
ipfs = null
}

if (!state.active) return

try {
ipfs = await initIpfsClient(state)
} catch (err) {
Expand Down
2 changes: 1 addition & 1 deletion add-on/src/lib/ipfs-request.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ function createRequestModifier (getState, dnsLink, ipfsPathValidator, runtime) {
}

// handle redirects to custom gateway
if (state.redirect) {
if (state.active && state.redirect) {
// Ignore preload requests
if (request.method === 'HEAD') {
return
Expand Down
3 changes: 2 additions & 1 deletion add-on/src/lib/options.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
'use strict'

const optionDefaults = Object.freeze({
ipfsNodeType: 'external',
active: true, // global ON/OFF switch, overrides everything else
ipfsNodeType: 'external', // or 'embedded'
ipfsNodeConfig: JSON.stringify({
config: {
Addresses: {
Expand Down
1 change: 1 addition & 0 deletions add-on/src/lib/state.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ function initState (options) {
const state = {}
// we store the most used values in optimized form
// to minimize performance impact on overall browsing experience
state.active = options.active
state.peerCount = offlinePeerCount
state.ipfsNodeType = options.ipfsNodeType
state.ipfsNodeConfig = options.ipfsNodeConfig
Expand Down
67 changes: 36 additions & 31 deletions add-on/src/options/forms/gateways-form.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ const html = require('choo/html')
const { normalizeGatewayURL } = require('../../lib/options')

function gatewaysForm ({
ipfsNodeType,
customGatewayUrl,
useCustomGateway,
publicGatewayUrl,
Expand All @@ -19,37 +20,41 @@ function gatewaysForm ({
<form>
<fieldset>
<legend>${browser.i18n.getMessage('option_header_gateways')}</legend>
<div>
<label for="customGatewayUrl">
<dl>
<dt>${browser.i18n.getMessage('option_customGatewayUrl_title')}</dt>
<dd>${browser.i18n.getMessage('option_customGatewayUrl_description')}</dd>
</dl>
</label>
<input
id="customGatewayUrl"
type="url"
inputmode="url"
required
pattern="^https?://[^/]+$"
spellcheck="false"
title="Enter URL without any sub-path"
onchange=${onCustomGatewayUrlChange}
value=${customGatewayUrl} />
</div>
<div>
<label for="useCustomGateway">
<dl>
<dt>${browser.i18n.getMessage('option_useCustomGateway_title')}</dt>
<dd>${browser.i18n.getMessage('option_useCustomGateway_description')}</dd>
</dl>
</label>
<input
id="useCustomGateway"
type="checkbox"
onchange=${onUseCustomGatewayChange}
checked=${useCustomGateway} />
</div>
${ipfsNodeType === 'external' ? html`
<div>
<label for="customGatewayUrl">
<dl>
<dt>${browser.i18n.getMessage('option_customGatewayUrl_title')}</dt>
<dd>${browser.i18n.getMessage('option_customGatewayUrl_description')}</dd>
</dl>
</label>
<input
id="customGatewayUrl"
type="url"
inputmode="url"
required
pattern="^https?://[^/]+$"
spellcheck="false"
title="Enter URL without any sub-path"
onchange=${onCustomGatewayUrlChange}
value=${customGatewayUrl} />
</div>
` : null}
${ipfsNodeType === 'external' ? html`
<div>
<label for="useCustomGateway">
<dl>
<dt>${browser.i18n.getMessage('option_useCustomGateway_title')}</dt>
<dd>${browser.i18n.getMessage('option_useCustomGateway_description')}</dd>
</dl>
</label>
<input
id="useCustomGateway"
type="checkbox"
onchange=${onUseCustomGatewayChange}
checked=${useCustomGateway} />
</div>
` : null}
<div>
<label for="publicGatewayUrl">
<dl>
Expand Down
19 changes: 19 additions & 0 deletions add-on/src/options/forms/global-toggle-form.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
'use strict'
/* eslint-env browser, webextensions */

const browser = require('webextension-polyfill')
const html = require('choo/html')

function globalToggleForm ({ active, onOptionChange }) {
const toggle = onOptionChange('active')
return html`
<form class="dib mb3">
<label for="active" class="dib pa3 pointer ${!active ? 'bg-aqua-muted br2' : ''}">
<input class="mr2 pointer" id="active" type="checkbox" onchange=${toggle} checked=${active} />
${browser.i18n.getMessage('panel_headerActiveToggleTitle')}
</label>
</form>
`
}

module.exports = globalToggleForm
3 changes: 2 additions & 1 deletion add-on/src/options/forms/ipfs-node-form.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ function ipfsNodeForm ({ ipfsNodeType, ipfsNodeConfig, onOptionChange }) {
<dl>
<dt>${browser.i18n.getMessage('option_ipfsNodeType_title')}</dt>
<dd>
${browser.i18n.getMessage('option_ipfsNodeType_description')}
<p>${browser.i18n.getMessage('option_ipfsNodeType_external_description')}</p>
<p>${browser.i18n.getMessage('option_ipfsNodeType_embedded_description')}</p>
<p><a href="https://github.com/ipfs-shipyard/ipfs-companion/blob/master/docs/node-types.md" target="_blank">
${browser.i18n.getMessage('option_legend_readMore')}
</a></p>
Expand Down
4 changes: 3 additions & 1 deletion add-on/src/options/options.css
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
html.is-chrome {
@import url('/ui-kit/tachyons.css');
@import url('/ui-kit/ipfs.css');
html {
overflow: hidden;
}
.is-chrome body {
Expand Down
24 changes: 21 additions & 3 deletions add-on/src/options/page.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
/* eslint-env browser, webextensions */

const html = require('choo/html')
const globalToggleForm = require('./forms/global-toggle-form')
const ipfsNodeForm = require('./forms/ipfs-node-form')
const gatewaysForm = require('./forms/gateways-form')
const apiForm = require('./forms/api-form')
Expand All @@ -28,25 +29,42 @@ module.exports = function optionsPage (state, emit) {
emit('optionsReset')
}

if (!state.options.active) {
// we don't want to confuse users by showing "active" checkboxes
// when global toggle is in "suspended" state
return html`
<div class="sans-serif">
${globalToggleForm({
active: state.options.active,
onOptionChange
})}
</div>
`
}
return html`
<div>
<div class="sans-serif">
${globalToggleForm({
active: state.options.active,
onOptionChange
})}
${ipfsNodeForm({
ipfsNodeType: state.options.ipfsNodeType,
ipfsNodeConfig: state.options.ipfsNodeConfig,
onOptionChange
})}
${gatewaysForm({
ipfsNodeType: state.options.ipfsNodeType,
customGatewayUrl: state.options.customGatewayUrl,
useCustomGateway: state.options.useCustomGateway,
publicGatewayUrl: state.options.publicGatewayUrl,
onOptionChange
})}
${apiForm({
${state.options.ipfsNodeType === 'external' ? apiForm({
ipfsApiUrl: state.options.ipfsApiUrl,
ipfsApiPollMs: state.options.ipfsApiPollMs,
automaticMode: state.options.automaticMode,
onOptionChange
})}
}) : null}
${experimentsForm({
displayNotifications: state.options.displayNotifications,
preloadAtPublicGateway: state.options.preloadAtPublicGateway,
Expand Down
18 changes: 16 additions & 2 deletions add-on/src/popup/browser-action/browser-action.css
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
@import url('../../../ui-kit/tachyons.css');
@import url('/ui-kit/tachyons.css');
@import url('/ui-kit/ipfs.css');
@import url('../heartbeat.css');
@import url('mdc.switch.css');

.bg-near-white--hover:hover {
background-color: #F4F4F4;
Expand All @@ -10,6 +10,20 @@
outline: 0;
}

.no-user-select {
-webkit-user-select: none; /* Old Chrome, Safari */
-moz-user-select: none; /* Firefox */
-ms-user-select: none; /* Internet Explorer/Edge */
user-select: none; /* Non-prefixed version, Chrome and Opera */
}

.force-select-all {
-webkit-user-select: all; /* Chrome 49+ */
-moz-user-select: all; /* Firefox 43+ */
-ms-user-select: all; /* No support yet */
user-select: all; /* Likely future */
}

.fade-in {
animation: fade-in 600ms;
}
Expand Down
Loading