Skip to content

Commit

Permalink
feat: improved experience on DNSLink websites (#826)
Browse files Browse the repository at this point in the history
  • Loading branch information
lidel authored Dec 6, 2019
2 parents 0ef6765 + c7c3221 commit df96a05
Show file tree
Hide file tree
Showing 13 changed files with 160 additions and 48 deletions.
24 changes: 22 additions & 2 deletions add-on/_locales/en/messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,10 @@
"message": "Copy Public Gateway URL",
"description": "A menu item in Browser Action pop-up and right-click context menu (panel_copyCurrentPublicGwUrl)"
},
"panel_contextMenuViewOnGateway": {
"message": "View on Gateway",
"description": "A menu item in Browser Action pop-up and right-click context menu (panel_contextMenuViewOnGateway)"
},
"pageAction_titleIpfsAtPublicGateway": {
"message": "IPFS resource loaded via Public Gateway",
"description": "A tooltip displayed over Page Action in location bar (pageAction_titleIpfsAtPublicGateway)"
Expand Down Expand Up @@ -275,6 +279,18 @@
"message": "Redirect requests for IPFS resources to the Custom gateway",
"description": "An option description on the Preferences screen (option_useCustomGateway_description)"
},
"option_dnslinkRedirect_title": {
"message": "Force page load from custom gateway",
"description": "An option title on the Preferences screen (option_dnslinkRedirect_title)"
},
"option_dnslinkRedirect_description": {
"message": "If global redirect is enabled, this will include DNSLink websites and redirect them to respective /ipns/{fqdn} paths at Custom Gateway",
"description": "An option description on the Preferences screen (option_dnslinkRedirect_description)"
},
"option_dnslinkRedirect_warning": {
"message": "Redirecting to a path-based gateway breaks Origin-based security isolation of DNSLink website! Please leave this disabled unless you are aware of (and ok with) related risks.",
"description": "A warning on the Preferences screen, displayed when URL does not belong to Secure Context (option_customGatewayUrl_warning)"
},
"option_noRedirectHostnames_title": {
"message": "Redirect Opt-Outs",
"description": "An option title on the Preferences screen (option_noRedirectHostnames_title)"
Expand Down Expand Up @@ -327,6 +343,10 @@
"message": "Toggle use of Custom Gateway when IPFS API availability changes",
"description": "An option description on the Preferences screen (option_automaticMode_description)"
},
"option_header_dnslink": {
"message": "DNSLink",
"description": "A section header on the Preferences screen (option_header_dnslink)"
},
"option_header_experiments": {
"message": "Experiments",
"description": "A section header on the Preferences screen (option_header_experiments)"
Expand Down Expand Up @@ -364,11 +384,11 @@
"description": "An option description on the Preferences screen (option_linkify_description)"
},
"option_dnslinkPolicy_title": {
"message": "DNSLink Support",
"message": "DNSLink Lookup",
"description": "An option title on the Preferences screen (option_dnslinkPolicy_title)"
},
"option_dnslinkPolicy_description": {
"message": "Select DNS TXT lookup policy to load IPFS hosted sites over IPFS where possible",
"message": "Lookup policy for displaying context actions on websites with DNSLink",
"description": "An option description on the Preferences screen (option_dnslinkPolicy_description)"
},
"option_dnslinkPolicy_disabled": {
Expand Down
2 changes: 2 additions & 0 deletions add-on/src/lib/context-menus.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,11 @@ const contextMenuImportToIpfsSelection = 'contextMenu_importToIpfsSelection'
const contextMenuCopyCanonicalAddress = 'panelCopy_currentIpfsAddress'
const contextMenuCopyRawCid = 'panelCopy_copyRawCid'
const contextMenuCopyAddressAtPublicGw = 'panel_copyCurrentPublicGwUrl'
const contextMenuViewOnGateway = 'panel_contextMenuViewOnGateway'
module.exports.contextMenuCopyCanonicalAddress = contextMenuCopyCanonicalAddress
module.exports.contextMenuCopyRawCid = contextMenuCopyRawCid
module.exports.contextMenuCopyAddressAtPublicGw = contextMenuCopyAddressAtPublicGw
module.exports.contextMenuViewOnGateway = contextMenuViewOnGateway

// menu items that are enabled only when API is online
const apiMenuItems = new Set()
Expand Down
21 changes: 21 additions & 0 deletions add-on/src/lib/inspector.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
'use strict'

const browser = require('webextension-polyfill')
const { findValueForContext } = require('./context-menus')
const { pathAtHttpGateway } = require('./ipfs-path')

function createInspector (notify, ipfsPathValidator, getState) {
return {
async viewOnGateway (context, contextType) {
const url = await findValueForContext(context, contextType)
const ipfsPath = ipfsPathValidator.resolveToIpfsPath(url)
const gateway = getState().pubGwURLString
const gatewayUrl = pathAtHttpGateway(ipfsPath, gateway)
await browser.tabs.create({ url: gatewayUrl })
}
// TODO: view in WebUI's Files
// TODO: view in WebUI's IPLD Explorer
}
}

module.exports = createInspector
7 changes: 6 additions & 1 deletion add-on/src/lib/ipfs-companion.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,9 @@ const { createIpfsUrlProtocolHandler } = require('./ipfs-protocol')
const createIpfsImportHandler = require('./ipfs-import')
const createNotifier = require('./notifier')
const createCopier = require('./copier')
const createInspector = require('./inspector')
const { createRuntimeChecks } = require('./runtime-checks')
const { createContextMenus, findValueForContext, contextMenuCopyAddressAtPublicGw, contextMenuCopyRawCid, contextMenuCopyCanonicalAddress } = require('./context-menus')
const { createContextMenus, findValueForContext, contextMenuCopyAddressAtPublicGw, contextMenuCopyRawCid, contextMenuCopyCanonicalAddress, contextMenuViewOnGateway } = require('./context-menus')
const createIpfsProxy = require('./ipfs-proxy')
const { showPendingLandingPages } = require('./on-installed')

Expand All @@ -34,6 +35,7 @@ module.exports = async function init () {
var modifyRequest
var notify
var copier
var inspector
var runtime
var contextMenus
var apiStatusUpdateInterval
Expand Down Expand Up @@ -69,6 +71,7 @@ module.exports = async function init () {
ipfsPathValidator = createIpfsPathValidator(getState, getIpfs, dnslinkResolver)
ipfsImportHandler = createIpfsImportHandler(getState, getIpfs, ipfsPathValidator, runtime)
copier = createCopier(notify, ipfsPathValidator)
inspector = createInspector(notify, ipfsPathValidator, getState)
contextMenus = createContextMenus(getState, runtime, ipfsPathValidator, {
onAddFromContext,
onCopyCanonicalAddress: copier.copyCanonicalAddress,
Expand Down Expand Up @@ -212,6 +215,7 @@ module.exports = async function init () {

const BrowserActionMessageHandlers = {
notification: (message) => notify(message.title, message.message),
[contextMenuViewOnGateway]: inspector.viewOnGateway,
[contextMenuCopyCanonicalAddress]: copier.copyCanonicalAddress,
[contextMenuCopyRawCid]: copier.copyRawCid,
[contextMenuCopyAddressAtPublicGw]: copier.copyAddressAtPublicGw
Expand Down Expand Up @@ -676,6 +680,7 @@ module.exports = async function init () {
case 'preloadAtPublicGateway':
case 'openViaWebUI':
case 'noRedirectHostnames':
case 'dnslinkRedirect':
state[key] = change.newValue
break
}
Expand Down
7 changes: 3 additions & 4 deletions add-on/src/lib/ipfs-request.js
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ function createRequestModifier (getState, dnslinkResolver, ipfsPathValidator, ru
return redirectToGateway(request.url, state, ipfsPathValidator)
}
// Detect dnslink using heuristics enabled in Preferences
if (state.dnslinkPolicy && dnslinkResolver.canLookupURL(request.url)) {
if (state.dnslinkRedirect && state.dnslinkPolicy && dnslinkResolver.canLookupURL(request.url)) {
const dnslinkRedirect = dnslinkResolver.dnslinkRedirect(request.url)
if (dnslinkRedirect && isSafeToRedirect(request, runtime)) {
// console.log('onBeforeRequest.dnslinkRedirect', dnslinkRedirect)
Expand Down Expand Up @@ -339,10 +339,9 @@ function createRequestModifier (getState, dnslinkResolver, ipfsPathValidator, ru
if (header.name.toLowerCase() === 'x-ipfs-path' && isSafeToRedirect(request, runtime)) {
// console.log('onHeadersReceived.request.responseHeaders', request.responseHeaders.length)
const xIpfsPath = header.value
log(`detected x-ipfs-path for ${request.url}: ${xIpfsPath}`)
// First: Check if dnslink heuristic yields any results
// Note: this depends on which dnslink lookup policy is selecten in Preferences
if (state.dnslinkPolicy && dnslinkResolver.canLookupURL(request.url)) {
if (state.dnslinkRedirect && state.dnslinkPolicy && dnslinkResolver.canLookupURL(request.url)) {
// x-ipfs-path is a strong indicator of IPFS support
// so we force dnslink lookup to pre-populate dnslink cache
// in a way that works even when state.dnslinkPolicy !== 'enabled'
Expand All @@ -358,7 +357,7 @@ function createRequestModifier (getState, dnslinkResolver, ipfsPathValidator, ru
if (IsIpfs.ipnsPath(xIpfsPath)) {
// Ignore unhandled IPNS path by this point
// (means DNSLink is disabled so we don't want to make a redirect that works like DNSLink)
log(`onHeadersReceived: ignoring x-ipfs-path=${xIpfsPath} (dnslinkPolicy=false or missing DNS TXT record)`)
// log(`onHeadersReceived: ignoring x-ipfs-path=${xIpfsPath} (dnslinkRedirect=false, dnslinkPolicy=false or missing DNS TXT record)`)
} else if (IsIpfs.ipfsPath(xIpfsPath)) {
// It is possible that someone exposed /ipfs/<cid>/ under /
// and our path-based onBeforeRequest heuristics were unable
Expand Down
1 change: 1 addition & 0 deletions add-on/src/lib/options.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ exports.optionDefaults = Object.freeze({
automaticMode: true,
linkify: false,
dnslinkPolicy: 'best-effort',
dnslinkRedirect: false,
recoverFailedHttpRequests: true,
detectIpfsPathHeader: true,
preloadAtPublicGateway: true,
Expand Down
70 changes: 70 additions & 0 deletions add-on/src/options/forms/dnslink-form.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
'use strict'
/* eslint-env browser, webextensions */

const browser = require('webextension-polyfill')
const html = require('choo/html')
const switchToggle = require('../../pages/components/switch-toggle')

function dnslinkForm ({
dnslinkPolicy,
dnslinkRedirect,
onOptionChange
}) {
const onDnslinkPolicyChange = onOptionChange('dnslinkPolicy')
const onDnslinkRedirectChange = onOptionChange('dnslinkRedirect')

return html`
<form>
<fieldset>
<legend>${browser.i18n.getMessage('option_header_dnslink')}</legend>
<div>
<label for="dnslinkPolicy">
<dl>
<dt>${browser.i18n.getMessage('option_dnslinkPolicy_title')}</dt>
<dd>
${browser.i18n.getMessage('option_dnslinkPolicy_description')}
<p><a href="https://github.com/ipfs-shipyard/ipfs-companion/blob/master/docs/dnslink.md#dnslink-support-in-ipfs-companion" target="_blank">
${browser.i18n.getMessage('option_legend_readMore')}
</a></p>
</dd>
</dl>
</label>
<select id="dnslinkPolicy" name='dnslinkPolicy' onchange=${onDnslinkPolicyChange}>
<option
value='false'
selected=${String(dnslinkPolicy) === 'false'}>
${browser.i18n.getMessage('option_dnslinkPolicy_disabled')}
</option>
<option
value='best-effort'
selected=${dnslinkPolicy === 'best-effort'}>
${browser.i18n.getMessage('option_dnslinkPolicy_bestEffort')}
</option>
<option
value='enabled'
selected=${dnslinkPolicy === 'enabled'}>
${browser.i18n.getMessage('option_dnslinkPolicy_enabled')}
</option>
</select>
</div>
<div>
<label for="dnslinkRedirect">
<dl>
<dt>${browser.i18n.getMessage('option_dnslinkRedirect_title')}</dt>
<dd>
${browser.i18n.getMessage('option_dnslinkRedirect_description')}
${dnslinkRedirect ? html`<p class="red i">${browser.i18n.getMessage('option_dnslinkRedirect_warning')}</p>` : null}
<p><a href="https://github.com/ipfs-shipyard/ipfs-companion/issues/667" target="_blank">
${browser.i18n.getMessage('option_legend_readMore')}
</a></p>
</dd>
</dl>
</label>
<div>${switchToggle({ id: 'dnslinkRedirect', checked: dnslinkRedirect, onchange: onDnslinkRedirectChange })}</div>
</div>
</fieldset>
</form>
`
}

module.exports = dnslinkForm
32 changes: 0 additions & 32 deletions add-on/src/options/forms/experiments-form.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ function experimentsForm ({
displayNotifications,
catchUnhandledProtocols,
linkify,
dnslinkPolicy,
recoverFailedHttpRequests,
detectIpfsPathHeader,
ipfsProxy,
Expand All @@ -20,7 +19,6 @@ function experimentsForm ({
const onDisplayNotificationsChange = onOptionChange('displayNotifications')
const onCatchUnhandledProtocolsChange = onOptionChange('catchUnhandledProtocols')
const onLinkifyChange = onOptionChange('linkify')
const onDnslinkPolicyChange = onOptionChange('dnslinkPolicy')
const onrecoverFailedHttpRequestsChange = onOptionChange('recoverFailedHttpRequests')
const onDetectIpfsPathHeaderChange = onOptionChange('detectIpfsPathHeader')
const onIpfsProxyChange = onOptionChange('ipfsProxy')
Expand Down Expand Up @@ -66,36 +64,6 @@ function experimentsForm ({
</label>
<div>${switchToggle({ id: 'linkify', checked: linkify, onchange: onLinkifyChange })}</div>
</div>
<div>
<label for="dnslinkPolicy">
<dl>
<dt>${browser.i18n.getMessage('option_dnslinkPolicy_title')}</dt>
<dd>
${browser.i18n.getMessage('option_dnslinkPolicy_description')}
<p><a href="https://github.com/ipfs-shipyard/ipfs-companion/blob/master/docs/dnslink.md#dnslink-support-in-ipfs-companion" target="_blank">
${browser.i18n.getMessage('option_legend_readMore')}
</a></p>
</dd>
</dl>
</label>
<select id="dnslinkPolicy" name='dnslinkPolicy' onchange=${onDnslinkPolicyChange}>
<option
value='false'
selected=${String(dnslinkPolicy) === 'false'}>
${browser.i18n.getMessage('option_dnslinkPolicy_disabled')}
</option>
<option
value='best-effort'
selected=${dnslinkPolicy === 'best-effort'}>
${browser.i18n.getMessage('option_dnslinkPolicy_bestEffort')}
</option>
<option
value='enabled'
selected=${dnslinkPolicy === 'enabled'}>
${browser.i18n.getMessage('option_dnslinkPolicy_enabled')}
</option>
</select>
</div>
<div>
<label for="detectIpfsPathHeader">
<dl>
Expand Down
7 changes: 6 additions & 1 deletion add-on/src/options/page.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ const html = require('choo/html')
const globalToggleForm = require('./forms/global-toggle-form')
const ipfsNodeForm = require('./forms/ipfs-node-form')
const fileImportForm = require('./forms/file-import-form')
const dnslinkForm = require('./forms/dnslink-form')
const gatewaysForm = require('./forms/gateways-form')
const apiForm = require('./forms/api-form')
const experimentsForm = require('./forms/experiments-form')
Expand Down Expand Up @@ -77,11 +78,15 @@ module.exports = function optionsPage (state, emit) {
preloadAtPublicGateway: state.options.preloadAtPublicGateway,
onOptionChange
})}
${dnslinkForm({
dnslinkPolicy: state.options.dnslinkPolicy,
dnslinkRedirect: state.options.dnslinkRedirect,
onOptionChange
})}
${experimentsForm({
displayNotifications: state.options.displayNotifications,
catchUnhandledProtocols: state.options.catchUnhandledProtocols,
linkify: state.options.linkify,
dnslinkPolicy: state.options.dnslinkPolicy,
recoverFailedHttpRequests: state.options.recoverFailedHttpRequests,
detectIpfsPathHeader: state.options.detectIpfsPathHeader,
ipfsProxy: state.options.ipfsProxy,
Expand Down
24 changes: 19 additions & 5 deletions add-on/src/popup/browser-action/context-actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,23 @@ const browser = require('webextension-polyfill')
const html = require('choo/html')
const navItem = require('./nav-item')
const navHeader = require('./nav-header')
const { contextMenuCopyAddressAtPublicGw, contextMenuCopyRawCid, contextMenuCopyCanonicalAddress } = require('../../lib/context-menus')
const {
contextMenuViewOnGateway,
contextMenuCopyAddressAtPublicGw,
contextMenuCopyRawCid,
contextMenuCopyCanonicalAddress
} = require('../../lib/context-menus')

// Context Actions are displayed in Browser Action and Page Action (FF only)
function contextActions ({
active,
redirect,
isRedirectContext,
pubGwURLString,
gwURLString,
currentTab,
currentFqdn,
currentDnslinkFqdn,
currentTabRedirectOptOut,
ipfsNodeType,
isIpfsContext,
Expand All @@ -22,16 +31,21 @@ function contextActions ({
isIpfsOnline,
isApiAvailable,
onToggleSiteRedirect,
onViewOnGateway,
onCopy,
onPin,
onUnPin
}) {
const activeCidResolver = active && isIpfsOnline && isApiAvailable
const activePinControls = active && isIpfsOnline && isApiAvailable

const activeViewOnGateway = currentTab && !(currentTab.url.startsWith(pubGwURLString) || currentTab.url.startsWith(gwURLString))
const renderIpfsContextItems = () => {
if (!isIpfsContext) return
return html`<div>
${activeViewOnGateway ? navItem({
text: browser.i18n.getMessage(contextMenuViewOnGateway),
onClick: () => onViewOnGateway(contextMenuViewOnGateway)
}) : null}
${navItem({
text: browser.i18n.getMessage(contextMenuCopyAddressAtPublicGw),
onClick: () => onCopy(contextMenuCopyAddressAtPublicGw)
Expand All @@ -55,7 +69,8 @@ function contextActions ({
</div>
`
}

/* TODO: change "redirect on {fqdn}" to "disable on {fqdn}" and disable all integrations
// removed per site toggle for now: ${renderSiteRedirectToggle()}
const renderSiteRedirectToggle = () => {
if (!isRedirectContext) return
return html`
Expand All @@ -69,10 +84,9 @@ function contextActions ({
})}
`
}

*/
return html`
<div class='fade-in pv1'>
${renderSiteRedirectToggle()}
${renderIpfsContextItems()}
</div>
`
Expand Down
Loading

0 comments on commit df96a05

Please sign in to comment.