Skip to content

Commit df96a05

Browse files
authored
feat: improved experience on DNSLink websites (#826)
2 parents 0ef6765 + c7c3221 commit df96a05

File tree

13 files changed

+160
-48
lines changed

13 files changed

+160
-48
lines changed

add-on/_locales/en/messages.json

+22-2
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,10 @@
9191
"message": "Copy Public Gateway URL",
9292
"description": "A menu item in Browser Action pop-up and right-click context menu (panel_copyCurrentPublicGwUrl)"
9393
},
94+
"panel_contextMenuViewOnGateway": {
95+
"message": "View on Gateway",
96+
"description": "A menu item in Browser Action pop-up and right-click context menu (panel_contextMenuViewOnGateway)"
97+
},
9498
"pageAction_titleIpfsAtPublicGateway": {
9599
"message": "IPFS resource loaded via Public Gateway",
96100
"description": "A tooltip displayed over Page Action in location bar (pageAction_titleIpfsAtPublicGateway)"
@@ -275,6 +279,18 @@
275279
"message": "Redirect requests for IPFS resources to the Custom gateway",
276280
"description": "An option description on the Preferences screen (option_useCustomGateway_description)"
277281
},
282+
"option_dnslinkRedirect_title": {
283+
"message": "Force page load from custom gateway",
284+
"description": "An option title on the Preferences screen (option_dnslinkRedirect_title)"
285+
},
286+
"option_dnslinkRedirect_description": {
287+
"message": "If global redirect is enabled, this will include DNSLink websites and redirect them to respective /ipns/{fqdn} paths at Custom Gateway",
288+
"description": "An option description on the Preferences screen (option_dnslinkRedirect_description)"
289+
},
290+
"option_dnslinkRedirect_warning": {
291+
"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.",
292+
"description": "A warning on the Preferences screen, displayed when URL does not belong to Secure Context (option_customGatewayUrl_warning)"
293+
},
278294
"option_noRedirectHostnames_title": {
279295
"message": "Redirect Opt-Outs",
280296
"description": "An option title on the Preferences screen (option_noRedirectHostnames_title)"
@@ -327,6 +343,10 @@
327343
"message": "Toggle use of Custom Gateway when IPFS API availability changes",
328344
"description": "An option description on the Preferences screen (option_automaticMode_description)"
329345
},
346+
"option_header_dnslink": {
347+
"message": "DNSLink",
348+
"description": "A section header on the Preferences screen (option_header_dnslink)"
349+
},
330350
"option_header_experiments": {
331351
"message": "Experiments",
332352
"description": "A section header on the Preferences screen (option_header_experiments)"
@@ -364,11 +384,11 @@
364384
"description": "An option description on the Preferences screen (option_linkify_description)"
365385
},
366386
"option_dnslinkPolicy_title": {
367-
"message": "DNSLink Support",
387+
"message": "DNSLink Lookup",
368388
"description": "An option title on the Preferences screen (option_dnslinkPolicy_title)"
369389
},
370390
"option_dnslinkPolicy_description": {
371-
"message": "Select DNS TXT lookup policy to load IPFS hosted sites over IPFS where possible",
391+
"message": "Lookup policy for displaying context actions on websites with DNSLink",
372392
"description": "An option description on the Preferences screen (option_dnslinkPolicy_description)"
373393
},
374394
"option_dnslinkPolicy_disabled": {

add-on/src/lib/context-menus.js

+2
Original file line numberDiff line numberDiff line change
@@ -58,9 +58,11 @@ const contextMenuImportToIpfsSelection = 'contextMenu_importToIpfsSelection'
5858
const contextMenuCopyCanonicalAddress = 'panelCopy_currentIpfsAddress'
5959
const contextMenuCopyRawCid = 'panelCopy_copyRawCid'
6060
const contextMenuCopyAddressAtPublicGw = 'panel_copyCurrentPublicGwUrl'
61+
const contextMenuViewOnGateway = 'panel_contextMenuViewOnGateway'
6162
module.exports.contextMenuCopyCanonicalAddress = contextMenuCopyCanonicalAddress
6263
module.exports.contextMenuCopyRawCid = contextMenuCopyRawCid
6364
module.exports.contextMenuCopyAddressAtPublicGw = contextMenuCopyAddressAtPublicGw
65+
module.exports.contextMenuViewOnGateway = contextMenuViewOnGateway
6466

6567
// menu items that are enabled only when API is online
6668
const apiMenuItems = new Set()

add-on/src/lib/inspector.js

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
'use strict'
2+
3+
const browser = require('webextension-polyfill')
4+
const { findValueForContext } = require('./context-menus')
5+
const { pathAtHttpGateway } = require('./ipfs-path')
6+
7+
function createInspector (notify, ipfsPathValidator, getState) {
8+
return {
9+
async viewOnGateway (context, contextType) {
10+
const url = await findValueForContext(context, contextType)
11+
const ipfsPath = ipfsPathValidator.resolveToIpfsPath(url)
12+
const gateway = getState().pubGwURLString
13+
const gatewayUrl = pathAtHttpGateway(ipfsPath, gateway)
14+
await browser.tabs.create({ url: gatewayUrl })
15+
}
16+
// TODO: view in WebUI's Files
17+
// TODO: view in WebUI's IPLD Explorer
18+
}
19+
}
20+
21+
module.exports = createInspector

add-on/src/lib/ipfs-companion.js

+6-1
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,9 @@ const { createIpfsUrlProtocolHandler } = require('./ipfs-protocol')
1818
const createIpfsImportHandler = require('./ipfs-import')
1919
const createNotifier = require('./notifier')
2020
const createCopier = require('./copier')
21+
const createInspector = require('./inspector')
2122
const { createRuntimeChecks } = require('./runtime-checks')
22-
const { createContextMenus, findValueForContext, contextMenuCopyAddressAtPublicGw, contextMenuCopyRawCid, contextMenuCopyCanonicalAddress } = require('./context-menus')
23+
const { createContextMenus, findValueForContext, contextMenuCopyAddressAtPublicGw, contextMenuCopyRawCid, contextMenuCopyCanonicalAddress, contextMenuViewOnGateway } = require('./context-menus')
2324
const createIpfsProxy = require('./ipfs-proxy')
2425
const { showPendingLandingPages } = require('./on-installed')
2526

@@ -34,6 +35,7 @@ module.exports = async function init () {
3435
var modifyRequest
3536
var notify
3637
var copier
38+
var inspector
3739
var runtime
3840
var contextMenus
3941
var apiStatusUpdateInterval
@@ -69,6 +71,7 @@ module.exports = async function init () {
6971
ipfsPathValidator = createIpfsPathValidator(getState, getIpfs, dnslinkResolver)
7072
ipfsImportHandler = createIpfsImportHandler(getState, getIpfs, ipfsPathValidator, runtime)
7173
copier = createCopier(notify, ipfsPathValidator)
74+
inspector = createInspector(notify, ipfsPathValidator, getState)
7275
contextMenus = createContextMenus(getState, runtime, ipfsPathValidator, {
7376
onAddFromContext,
7477
onCopyCanonicalAddress: copier.copyCanonicalAddress,
@@ -212,6 +215,7 @@ module.exports = async function init () {
212215

213216
const BrowserActionMessageHandlers = {
214217
notification: (message) => notify(message.title, message.message),
218+
[contextMenuViewOnGateway]: inspector.viewOnGateway,
215219
[contextMenuCopyCanonicalAddress]: copier.copyCanonicalAddress,
216220
[contextMenuCopyRawCid]: copier.copyRawCid,
217221
[contextMenuCopyAddressAtPublicGw]: copier.copyAddressAtPublicGw
@@ -676,6 +680,7 @@ module.exports = async function init () {
676680
case 'preloadAtPublicGateway':
677681
case 'openViaWebUI':
678682
case 'noRedirectHostnames':
683+
case 'dnslinkRedirect':
679684
state[key] = change.newValue
680685
break
681686
}

add-on/src/lib/ipfs-request.js

+3-4
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ function createRequestModifier (getState, dnslinkResolver, ipfsPathValidator, ru
142142
return redirectToGateway(request.url, state, ipfsPathValidator)
143143
}
144144
// Detect dnslink using heuristics enabled in Preferences
145-
if (state.dnslinkPolicy && dnslinkResolver.canLookupURL(request.url)) {
145+
if (state.dnslinkRedirect && state.dnslinkPolicy && dnslinkResolver.canLookupURL(request.url)) {
146146
const dnslinkRedirect = dnslinkResolver.dnslinkRedirect(request.url)
147147
if (dnslinkRedirect && isSafeToRedirect(request, runtime)) {
148148
// console.log('onBeforeRequest.dnslinkRedirect', dnslinkRedirect)
@@ -339,10 +339,9 @@ function createRequestModifier (getState, dnslinkResolver, ipfsPathValidator, ru
339339
if (header.name.toLowerCase() === 'x-ipfs-path' && isSafeToRedirect(request, runtime)) {
340340
// console.log('onHeadersReceived.request.responseHeaders', request.responseHeaders.length)
341341
const xIpfsPath = header.value
342-
log(`detected x-ipfs-path for ${request.url}: ${xIpfsPath}`)
343342
// First: Check if dnslink heuristic yields any results
344343
// Note: this depends on which dnslink lookup policy is selecten in Preferences
345-
if (state.dnslinkPolicy && dnslinkResolver.canLookupURL(request.url)) {
344+
if (state.dnslinkRedirect && state.dnslinkPolicy && dnslinkResolver.canLookupURL(request.url)) {
346345
// x-ipfs-path is a strong indicator of IPFS support
347346
// so we force dnslink lookup to pre-populate dnslink cache
348347
// in a way that works even when state.dnslinkPolicy !== 'enabled'
@@ -358,7 +357,7 @@ function createRequestModifier (getState, dnslinkResolver, ipfsPathValidator, ru
358357
if (IsIpfs.ipnsPath(xIpfsPath)) {
359358
// Ignore unhandled IPNS path by this point
360359
// (means DNSLink is disabled so we don't want to make a redirect that works like DNSLink)
361-
log(`onHeadersReceived: ignoring x-ipfs-path=${xIpfsPath} (dnslinkPolicy=false or missing DNS TXT record)`)
360+
// log(`onHeadersReceived: ignoring x-ipfs-path=${xIpfsPath} (dnslinkRedirect=false, dnslinkPolicy=false or missing DNS TXT record)`)
362361
} else if (IsIpfs.ipfsPath(xIpfsPath)) {
363362
// It is possible that someone exposed /ipfs/<cid>/ under /
364363
// and our path-based onBeforeRequest heuristics were unable

add-on/src/lib/options.js

+1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ exports.optionDefaults = Object.freeze({
1717
automaticMode: true,
1818
linkify: false,
1919
dnslinkPolicy: 'best-effort',
20+
dnslinkRedirect: false,
2021
recoverFailedHttpRequests: true,
2122
detectIpfsPathHeader: true,
2223
preloadAtPublicGateway: true,
+70
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
'use strict'
2+
/* eslint-env browser, webextensions */
3+
4+
const browser = require('webextension-polyfill')
5+
const html = require('choo/html')
6+
const switchToggle = require('../../pages/components/switch-toggle')
7+
8+
function dnslinkForm ({
9+
dnslinkPolicy,
10+
dnslinkRedirect,
11+
onOptionChange
12+
}) {
13+
const onDnslinkPolicyChange = onOptionChange('dnslinkPolicy')
14+
const onDnslinkRedirectChange = onOptionChange('dnslinkRedirect')
15+
16+
return html`
17+
<form>
18+
<fieldset>
19+
<legend>${browser.i18n.getMessage('option_header_dnslink')}</legend>
20+
<div>
21+
<label for="dnslinkPolicy">
22+
<dl>
23+
<dt>${browser.i18n.getMessage('option_dnslinkPolicy_title')}</dt>
24+
<dd>
25+
${browser.i18n.getMessage('option_dnslinkPolicy_description')}
26+
<p><a href="https://github.com/ipfs-shipyard/ipfs-companion/blob/master/docs/dnslink.md#dnslink-support-in-ipfs-companion" target="_blank">
27+
${browser.i18n.getMessage('option_legend_readMore')}
28+
</a></p>
29+
</dd>
30+
</dl>
31+
</label>
32+
<select id="dnslinkPolicy" name='dnslinkPolicy' onchange=${onDnslinkPolicyChange}>
33+
<option
34+
value='false'
35+
selected=${String(dnslinkPolicy) === 'false'}>
36+
${browser.i18n.getMessage('option_dnslinkPolicy_disabled')}
37+
</option>
38+
<option
39+
value='best-effort'
40+
selected=${dnslinkPolicy === 'best-effort'}>
41+
${browser.i18n.getMessage('option_dnslinkPolicy_bestEffort')}
42+
</option>
43+
<option
44+
value='enabled'
45+
selected=${dnslinkPolicy === 'enabled'}>
46+
${browser.i18n.getMessage('option_dnslinkPolicy_enabled')}
47+
</option>
48+
</select>
49+
</div>
50+
<div>
51+
<label for="dnslinkRedirect">
52+
<dl>
53+
<dt>${browser.i18n.getMessage('option_dnslinkRedirect_title')}</dt>
54+
<dd>
55+
${browser.i18n.getMessage('option_dnslinkRedirect_description')}
56+
${dnslinkRedirect ? html`<p class="red i">${browser.i18n.getMessage('option_dnslinkRedirect_warning')}</p>` : null}
57+
<p><a href="https://github.com/ipfs-shipyard/ipfs-companion/issues/667" target="_blank">
58+
${browser.i18n.getMessage('option_legend_readMore')}
59+
</a></p>
60+
</dd>
61+
</dl>
62+
</label>
63+
<div>${switchToggle({ id: 'dnslinkRedirect', checked: dnslinkRedirect, onchange: onDnslinkRedirectChange })}</div>
64+
</div>
65+
</fieldset>
66+
</form>
67+
`
68+
}
69+
70+
module.exports = dnslinkForm

add-on/src/options/forms/experiments-form.js

-32
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ function experimentsForm ({
99
displayNotifications,
1010
catchUnhandledProtocols,
1111
linkify,
12-
dnslinkPolicy,
1312
recoverFailedHttpRequests,
1413
detectIpfsPathHeader,
1514
ipfsProxy,
@@ -20,7 +19,6 @@ function experimentsForm ({
2019
const onDisplayNotificationsChange = onOptionChange('displayNotifications')
2120
const onCatchUnhandledProtocolsChange = onOptionChange('catchUnhandledProtocols')
2221
const onLinkifyChange = onOptionChange('linkify')
23-
const onDnslinkPolicyChange = onOptionChange('dnslinkPolicy')
2422
const onrecoverFailedHttpRequestsChange = onOptionChange('recoverFailedHttpRequests')
2523
const onDetectIpfsPathHeaderChange = onOptionChange('detectIpfsPathHeader')
2624
const onIpfsProxyChange = onOptionChange('ipfsProxy')
@@ -66,36 +64,6 @@ function experimentsForm ({
6664
</label>
6765
<div>${switchToggle({ id: 'linkify', checked: linkify, onchange: onLinkifyChange })}</div>
6866
</div>
69-
<div>
70-
<label for="dnslinkPolicy">
71-
<dl>
72-
<dt>${browser.i18n.getMessage('option_dnslinkPolicy_title')}</dt>
73-
<dd>
74-
${browser.i18n.getMessage('option_dnslinkPolicy_description')}
75-
<p><a href="https://github.com/ipfs-shipyard/ipfs-companion/blob/master/docs/dnslink.md#dnslink-support-in-ipfs-companion" target="_blank">
76-
${browser.i18n.getMessage('option_legend_readMore')}
77-
</a></p>
78-
</dd>
79-
</dl>
80-
</label>
81-
<select id="dnslinkPolicy" name='dnslinkPolicy' onchange=${onDnslinkPolicyChange}>
82-
<option
83-
value='false'
84-
selected=${String(dnslinkPolicy) === 'false'}>
85-
${browser.i18n.getMessage('option_dnslinkPolicy_disabled')}
86-
</option>
87-
<option
88-
value='best-effort'
89-
selected=${dnslinkPolicy === 'best-effort'}>
90-
${browser.i18n.getMessage('option_dnslinkPolicy_bestEffort')}
91-
</option>
92-
<option
93-
value='enabled'
94-
selected=${dnslinkPolicy === 'enabled'}>
95-
${browser.i18n.getMessage('option_dnslinkPolicy_enabled')}
96-
</option>
97-
</select>
98-
</div>
9967
<div>
10068
<label for="detectIpfsPathHeader">
10169
<dl>

add-on/src/options/page.js

+6-1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ const html = require('choo/html')
55
const globalToggleForm = require('./forms/global-toggle-form')
66
const ipfsNodeForm = require('./forms/ipfs-node-form')
77
const fileImportForm = require('./forms/file-import-form')
8+
const dnslinkForm = require('./forms/dnslink-form')
89
const gatewaysForm = require('./forms/gateways-form')
910
const apiForm = require('./forms/api-form')
1011
const experimentsForm = require('./forms/experiments-form')
@@ -77,11 +78,15 @@ module.exports = function optionsPage (state, emit) {
7778
preloadAtPublicGateway: state.options.preloadAtPublicGateway,
7879
onOptionChange
7980
})}
81+
${dnslinkForm({
82+
dnslinkPolicy: state.options.dnslinkPolicy,
83+
dnslinkRedirect: state.options.dnslinkRedirect,
84+
onOptionChange
85+
})}
8086
${experimentsForm({
8187
displayNotifications: state.options.displayNotifications,
8288
catchUnhandledProtocols: state.options.catchUnhandledProtocols,
8389
linkify: state.options.linkify,
84-
dnslinkPolicy: state.options.dnslinkPolicy,
8590
recoverFailedHttpRequests: state.options.recoverFailedHttpRequests,
8691
detectIpfsPathHeader: state.options.detectIpfsPathHeader,
8792
ipfsProxy: state.options.ipfsProxy,

add-on/src/popup/browser-action/context-actions.js

+19-5
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,23 @@ const browser = require('webextension-polyfill')
55
const html = require('choo/html')
66
const navItem = require('./nav-item')
77
const navHeader = require('./nav-header')
8-
const { contextMenuCopyAddressAtPublicGw, contextMenuCopyRawCid, contextMenuCopyCanonicalAddress } = require('../../lib/context-menus')
8+
const {
9+
contextMenuViewOnGateway,
10+
contextMenuCopyAddressAtPublicGw,
11+
contextMenuCopyRawCid,
12+
contextMenuCopyCanonicalAddress
13+
} = require('../../lib/context-menus')
914

1015
// Context Actions are displayed in Browser Action and Page Action (FF only)
1116
function contextActions ({
1217
active,
1318
redirect,
1419
isRedirectContext,
20+
pubGwURLString,
21+
gwURLString,
22+
currentTab,
1523
currentFqdn,
24+
currentDnslinkFqdn,
1625
currentTabRedirectOptOut,
1726
ipfsNodeType,
1827
isIpfsContext,
@@ -22,16 +31,21 @@ function contextActions ({
2231
isIpfsOnline,
2332
isApiAvailable,
2433
onToggleSiteRedirect,
34+
onViewOnGateway,
2535
onCopy,
2636
onPin,
2737
onUnPin
2838
}) {
2939
const activeCidResolver = active && isIpfsOnline && isApiAvailable
3040
const activePinControls = active && isIpfsOnline && isApiAvailable
31-
41+
const activeViewOnGateway = currentTab && !(currentTab.url.startsWith(pubGwURLString) || currentTab.url.startsWith(gwURLString))
3242
const renderIpfsContextItems = () => {
3343
if (!isIpfsContext) return
3444
return html`<div>
45+
${activeViewOnGateway ? navItem({
46+
text: browser.i18n.getMessage(contextMenuViewOnGateway),
47+
onClick: () => onViewOnGateway(contextMenuViewOnGateway)
48+
}) : null}
3549
${navItem({
3650
text: browser.i18n.getMessage(contextMenuCopyAddressAtPublicGw),
3751
onClick: () => onCopy(contextMenuCopyAddressAtPublicGw)
@@ -55,7 +69,8 @@ function contextActions ({
5569
</div>
5670
`
5771
}
58-
72+
/* TODO: change "redirect on {fqdn}" to "disable on {fqdn}" and disable all integrations
73+
// removed per site toggle for now: ${renderSiteRedirectToggle()}
5974
const renderSiteRedirectToggle = () => {
6075
if (!isRedirectContext) return
6176
return html`
@@ -69,10 +84,9 @@ function contextActions ({
6984
})}
7085
`
7186
}
72-
87+
*/
7388
return html`
7489
<div class='fade-in pv1'>
75-
${renderSiteRedirectToggle()}
7690
${renderIpfsContextItems()}
7791
</div>
7892
`

0 commit comments

Comments
 (0)