Skip to content

Commit

Permalink
feat(options): UI for editing redirect opt-outs
Browse files Browse the repository at this point in the history
This adds a textarea for editing per-site opt-outs.
Array is converted into multi-line text.
Entries that are not valid FQDNs are dropped.
The list is sorted lexicographically.
  • Loading branch information
lidel committed Feb 24, 2019
1 parent 1f939d6 commit 86f5fcf
Show file tree
Hide file tree
Showing 9 changed files with 96 additions and 24 deletions.
8 changes: 8 additions & 0 deletions add-on/_locales/en/messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,14 @@
"message": "Redirect requests for IPFS resources to the Custom gateway",
"description": "An option description on the Preferences screen (option_useCustomGateway_description)"
},
"option_noRedirectHostnames_title": {
"message": "Redirect Opt-Outs",
"description": "An option title on the Preferences screen (option_noRedirectHostnames_title)"
},
"option_noRedirectHostnames_description": {
"message": "List of websites that should not be redirected to the Custom Gateway (includes subresources from other domains). One hostname per line.",
"description": "An option description on the Preferences screen (option_noRedirectHostnames_description)"
},
"option_publicGatewayUrl_title": {
"message": "Default Public Gateway",
"description": "An option title on the Preferences screen (option_publicGatewayUrl_title)"
Expand Down
21 changes: 21 additions & 0 deletions add-on/src/lib/options.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
'use strict'

const isFQDN = require('is-fqdn')

exports.optionDefaults = Object.freeze({
active: true, // global ON/OFF switch, overrides everything else
ipfsNodeType: 'external', // or 'embedded'
Expand Down Expand Up @@ -65,6 +67,25 @@ function normalizeGatewayURL (url) {
exports.normalizeGatewayURL = normalizeGatewayURL
exports.safeURL = (url) => new URL(normalizeGatewayURL(url))

// convert JS array to multiline textarea
function hostArrayToText (array) {
array = array.map(host => host.trim().toLowerCase())
array = [...new Set(array)] // dedup
array = array.filter(Boolean).filter(isFQDN)
array.sort()
return array.join('\n')
}
// convert JS array to multiline textarea
function hostTextToArray (text) {
let array = text.split('\n').map(host => host.trim().toLowerCase())
array = [...new Set(array)] // dedup
array = array.filter(Boolean).filter(isFQDN)
array.sort()
return array
}
exports.hostArrayToText = hostArrayToText
exports.hostTextToArray = hostTextToArray

exports.migrateOptions = async (storage) => {
// <= v2.4.4
// DNSLINK: convert old on/off 'dnslink' flag to text-based 'dnslinkPolicy'
Expand Down
61 changes: 40 additions & 21 deletions add-on/src/options/forms/gateways-form.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

const browser = require('webextension-polyfill')
const html = require('choo/html')
const { normalizeGatewayURL } = require('../../lib/options')
const { normalizeGatewayURL, hostTextToArray, hostArrayToText } = require('../../lib/options')

// Warn about mixed content issues when changing the gateway
// https://github.com/ipfs-shipyard/ipfs-companion/issues/648
Expand All @@ -13,19 +13,40 @@ function gatewaysForm ({
ipfsNodeType,
customGatewayUrl,
useCustomGateway,
noRedirectHostnames,
publicGatewayUrl,
onOptionChange
}) {
const onCustomGatewayUrlChange = onOptionChange('customGatewayUrl', normalizeGatewayURL)
const onUseCustomGatewayChange = onOptionChange('useCustomGateway')
const onPublicGatewayUrlChange = onOptionChange('publicGatewayUrl', normalizeGatewayURL)
const onNoRedirectHostnamesChange = onOptionChange('noRedirectHostnames', hostTextToArray)
const mixedContentWarning = !secureContextUrl.test(customGatewayUrl)
const supportRedirectToCustomGateway = ipfsNodeType === 'external'

return html`
<form>
<fieldset>
<legend>${browser.i18n.getMessage('option_header_gateways')}</legend>
${ipfsNodeType === 'external' ? html`
<div>
<label for="publicGatewayUrl">
<dl>
<dt>${browser.i18n.getMessage('option_publicGatewayUrl_title')}</dt>
<dd>${browser.i18n.getMessage('option_publicGatewayUrl_description')}</dd>
</dl>
</label>
<input
id="publicGatewayUrl"
type="url"
inputmode="url"
required
pattern="^https?://[^/]+/?$"
spellcheck="false"
title="Enter URL without any sub-path"
onchange=${onPublicGatewayUrlChange}
value=${publicGatewayUrl} />
</div>
${supportRedirectToCustomGateway ? html`
<div>
<label for="customGatewayUrl">
<dl>
Expand All @@ -48,7 +69,7 @@ function gatewaysForm ({
</div>
` : null}
${ipfsNodeType === 'external' ? html`
${supportRedirectToCustomGateway ? html`
<div>
<label for="useCustomGateway">
<dl>
Expand All @@ -63,24 +84,22 @@ function gatewaysForm ({
checked=${useCustomGateway} />
</div>
` : null}
<div>
<label for="publicGatewayUrl">
<dl>
<dt>${browser.i18n.getMessage('option_publicGatewayUrl_title')}</dt>
<dd>${browser.i18n.getMessage('option_publicGatewayUrl_description')}</dd>
</dl>
</label>
<input
id="publicGatewayUrl"
type="url"
inputmode="url"
required
pattern="^https?://[^/]+/?$"
spellcheck="false"
title="Enter URL without any sub-path"
onchange=${onPublicGatewayUrlChange}
value=${publicGatewayUrl} />
</div>
${supportRedirectToCustomGateway ? html`
<div>
<label for="noRedirectHostnames">
<dl>
<dt>${browser.i18n.getMessage('option_noRedirectHostnames_title')}</dt>
<dd>${browser.i18n.getMessage('option_noRedirectHostnames_description')}</dd>
</dl>
</label>
<textarea
id="noRedirectHostnames"
spellcheck="false"
onchange=${onNoRedirectHostnamesChange}
rows="4"
>${hostArrayToText(noRedirectHostnames)}</textarea>
</div>
` : null}
</fieldset>
</form>
`
Expand Down
2 changes: 1 addition & 1 deletion add-on/src/options/forms/ipfs-node-form.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ function ipfsNodeForm ({ ipfsNodeType, ipfsNodeConfig, onOptionChange }) {
<dd>${browser.i18n.getMessage('option_ipfsNodeConfig_description')}</dd>
</dl>
</label>
<textarea id="ipfsNodeConfig" rows="4" onchange=${onIpfsNodeConfigChange}>${ipfsNodeConfig}</textarea>
<textarea id="ipfsNodeConfig" rows="7" onchange=${onIpfsNodeConfigChange}>${ipfsNodeConfig}</textarea>
</div>
` : null}
</fieldset>
Expand Down
1 change: 1 addition & 0 deletions add-on/src/options/page.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ module.exports = function optionsPage (state, emit) {
customGatewayUrl: state.options.customGatewayUrl,
useCustomGateway: state.options.useCustomGateway,
publicGatewayUrl: state.options.publicGatewayUrl,
noRedirectHostnames: state.options.noRedirectHostnames,
onOptionChange
})}
${state.options.ipfsNodeType === 'external' ? apiForm({
Expand Down
2 changes: 1 addition & 1 deletion add-on/src/popup/browser-action/store.js
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ module.exports = (state, emitter) => {
} else {
noRedirectHostnames.push(fqdn)
}
console.dir('toggleSiteRedirect', state)
// console.dir('toggleSiteRedirect', state)
await browser.storage.local.set({ noRedirectHostnames })

// Reload the current tab to apply updated redirect preference
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@
"ipfs-http-response": "0.2.2",
"ipfs-postmsg-proxy": "3.1.1",
"ipfsx": "0.17.0",
"is-fqdn": "1.0.1",
"is-ipfs": "0.4.8",
"is-svg": "3.0.0",
"lru-cache": "5.1.1",
Expand Down
19 changes: 18 additions & 1 deletion test/functional/lib/options.test.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
'use strict'
const { describe, it, beforeEach, after } = require('mocha')
const { expect } = require('chai')
const sinon = require('sinon')
const browser = require('sinon-chrome')
const { storeMissingOptions, optionDefaults } = require('../../../add-on/src/lib/options')
const { storeMissingOptions, optionDefaults, hostTextToArray, hostArrayToText } = require('../../../add-on/src/lib/options')

describe('storeMissingOptions()', function () {
beforeEach(() => {
Expand Down Expand Up @@ -66,3 +67,19 @@ describe('storeMissingOptions()', function () {
browser.flush()
})
})

describe('hostTextToArray()', function () {
it('should sort, dedup hostnames, drop non-FQDNs and produce an array', () => {
const text = `zombo.com\n two.com \n totally not a FQDN \none.pl \nTWO.com\n\n`
const array = ['one.pl', 'two.com', 'zombo.com']
expect(hostTextToArray(text)).to.be.an('array').to.have.ordered.members(array)
})
})

describe('hostArrayToText()', function () {
it('should sort, deduplicate, drop non-FQDNs and produce multiline string', () => {
const array = ['zombo.com ', 'two.com ', 'ONE.pl ', 'one.pl', 'totall not a FQDN', 'zombo.com']
const text = `one.pl\ntwo.com\nzombo.com`
expect(hostArrayToText(array)).to.be.a('string').equal(text)
})
})
5 changes: 5 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -6664,6 +6664,11 @@ is-finite@^1.0.0:
dependencies:
number-is-nan "^1.0.0"

is-fqdn@1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/is-fqdn/-/is-fqdn-1.0.1.tgz#f3ed9cd5a20238449ae510e10d81258dafca9b70"
integrity sha1-8+2c1aICOESa5RDhDYElja/Km3A=

is-fullwidth-code-point@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz#ef9e31386f031a7f0d643af82fde50c457ef00cb"
Expand Down

0 comments on commit 86f5fcf

Please sign in to comment.