diff --git a/add-on/_locales/en/messages.json b/add-on/_locales/en/messages.json index e94a84f29..7149b8fbe 100644 --- a/add-on/_locales/en/messages.json +++ b/add-on/_locales/en/messages.json @@ -351,6 +351,14 @@ "message": "Turn plaintext /ipfs/ paths into clickable links", "description": "An option description on the Preferences screen (option_linkify_description)" }, + "option_webuiFromDNSLink_title": { + "message": "Load the latest Web UI", + "description": "An option title on the Preferences screen (option_webuiFromDNSLink_title)" + }, + "option_webuiFromDNSLink_description": { + "message": "Replaces stable version provided by your node with one at /ipns/webui.ipfs.io (requires working DNS and a compatible backend)", + "description": "An option description on the Preferences screen (option_webuiFromDNSLink_description)" + }, "option_dnslinkPolicy_title": { "message": "DNSLink Support", "description": "An option title on the Preferences screen (option_dnslinkPolicy_title)" diff --git a/add-on/src/lib/ipfs-client/index.js b/add-on/src/lib/ipfs-client/index.js index a18ade5e4..d65811d21 100644 --- a/add-on/src/lib/ipfs-client/index.js +++ b/add-on/src/lib/ipfs-client/index.js @@ -47,13 +47,13 @@ async function destroyIpfsClient () { function preloadWebui (instance, opts) { // run only when client still exists and async fetch is possible - if (!(client && instance && opts.webuiRootUrl && typeof fetch === 'function')) return + if (!(client && instance && opts.webuiURLString && typeof fetch === 'function')) return // Optimization: preload the root CID to speed up the first time // Web UI is opened. If embedded js-ipfs is used it will trigger // remote (always recursive) preload of entire DAG to one of preload nodes. // This way when embedded node wants to load resource related to webui // it will get it fast from preload nodes. - const webuiUrl = opts.webuiRootUrl + const webuiUrl = opts.webuiURLString log(`preloading webui root at ${webuiUrl}`) return fetch(webuiUrl, { redirect: 'follow' }) .then(response => { diff --git a/add-on/src/lib/ipfs-companion.js b/add-on/src/lib/ipfs-companion.js index 8f350e397..a39287b40 100644 --- a/add-on/src/lib/ipfs-companion.js +++ b/add-on/src/lib/ipfs-companion.js @@ -7,7 +7,7 @@ log.error = debug('ipfs-companion:main:error') const browser = require('webextension-polyfill') const { optionDefaults, storeMissingOptions, migrateOptions } = require('./options') -const { initState, offlinePeerCount } = require('./state') +const { initState, offlinePeerCount, buildWebuiURLString } = require('./state') const { createIpfsPathValidator } = require('./ipfs-path') const createDnslinkResolver = require('./dnslink') const { createRequestModifier, redirectOptOutHint } = require('./ipfs-request') @@ -223,7 +223,7 @@ module.exports = async function init () { peerCount: state.peerCount, gwURLString: dropSlash(state.gwURLString), pubGwURLString: dropSlash(state.pubGwURLString), - webuiRootUrl: state.webuiRootUrl, + webuiURLString: state.webuiURLString, apiURLString: dropSlash(state.apiURLString), redirect: state.redirect, noRedirectHostnames: state.noRedirectHostnames, @@ -633,7 +633,7 @@ module.exports = async function init () { case 'ipfsApiUrl': state.apiURL = new URL(change.newValue) state.apiURLString = state.apiURL.toString() - state.webuiRootUrl = `${state.apiURLString}webui` + state.webuiURLString = buildWebuiURLString(state) shouldRestartIpfsClient = true break case 'ipfsApiPollMs': @@ -664,6 +664,10 @@ module.exports = async function init () { shouldReloadExtension = true state[key] = localStorage.debug = change.newValue break + case 'webuiFromDNSLink': + state[key] = change.newValue + state.webuiURLString = buildWebuiURLString(state) + break case 'linkify': case 'catchUnhandledProtocols': case 'displayNotifications': diff --git a/add-on/src/lib/ipfs-request.js b/add-on/src/lib/ipfs-request.js index 16e471775..1fa0ccd73 100644 --- a/add-on/src/lib/ipfs-request.js +++ b/add-on/src/lib/ipfs-request.js @@ -8,6 +8,7 @@ log.error = debug('ipfs-companion:request:error') const LRU = require('lru-cache') const IsIpfs = require('is-ipfs') const { pathAtHttpGateway } = require('./ipfs-path') +const { buildWebuiURLString } = require('./state') const redirectOptOutHint = 'x-ipfs-companion-no-redirect' const recoverableErrors = new Set([ // Firefox @@ -229,6 +230,17 @@ function createRequestModifier (getState, dnslinkResolver, ipfsPathValidator, ru return } + // Recover from a broken DNSLink webui by redirecting back to CID one + // TODO: remove when both GO and JS ship support for /ipns/webui.ipfs.io on the API port + if (request.statusCode === 404 && request.url === state.webuiURLString && state.webuiFromDNSLink) { + const stableWebui = buildWebuiURLString({ + apiURLString: state.apiURLString, + webuiFromDNSLink: false + }) + log(`opening webui via ${state.webuiURLString} is not supported yet, opening stable webui from ${stableWebui} instead`) + return { redirectUrl: stableWebui } + } + // Skip if request is marked as ignored if (isIgnored(request.requestId)) { return diff --git a/add-on/src/lib/options.js b/add-on/src/lib/options.js index b2dd54b22..9a0fd3deb 100644 --- a/add-on/src/lib/options.js +++ b/add-on/src/lib/options.js @@ -24,6 +24,7 @@ exports.optionDefaults = Object.freeze({ ipfsApiUrl: buildIpfsApiUrl(), ipfsApiPollMs: 3000, ipfsProxy: true, // window.ipfs + webuiFromDNSLink: false, logNamespaces: 'jsipfs*,ipfs*,-*:ipns*,-ipfs:preload*,-ipfs-http-client:request*' }) diff --git a/add-on/src/lib/state.js b/add-on/src/lib/state.js index 92d5a0d24..8c1d4802b 100644 --- a/add-on/src/lib/state.js +++ b/add-on/src/lib/state.js @@ -2,6 +2,7 @@ /* eslint-env browser, webextensions */ const { safeURL } = require('./options') + const offlinePeerCount = -1 function initState (options) { @@ -22,9 +23,17 @@ function initState (options) { state.gwURLString = state.gwURL.toString() delete state.customGatewayUrl state.dnslinkPolicy = String(options.dnslinkPolicy) === 'false' ? false : options.dnslinkPolicy - state.webuiRootUrl = `${state.apiURLString}webui` + state.webuiURLString = buildWebuiURLString(state) return state } +function buildWebuiURLString ({ apiURLString, webuiFromDNSLink }) { + if (!apiURLString) throw new Error('Missing apiURLString') + return webuiFromDNSLink + ? `${apiURLString}ipns/webui.ipfs.io/` + : `${apiURLString}webui/` +} + exports.initState = initState exports.offlinePeerCount = offlinePeerCount +exports.buildWebuiURLString = buildWebuiURLString diff --git a/add-on/src/options/forms/experiments-form.js b/add-on/src/options/forms/experiments-form.js index ccd03a9a9..09b57aed5 100644 --- a/add-on/src/options/forms/experiments-form.js +++ b/add-on/src/options/forms/experiments-form.js @@ -10,6 +10,7 @@ function experimentsForm ({ preloadAtPublicGateway, catchUnhandledProtocols, linkify, + webuiFromDNSLink, dnslinkPolicy, detectIpfsPathHeader, ipfsProxy, @@ -24,6 +25,7 @@ function experimentsForm ({ const onDnslinkPolicyChange = onOptionChange('dnslinkPolicy') const onDetectIpfsPathHeaderChange = onOptionChange('detectIpfsPathHeader') const onIpfsProxyChange = onOptionChange('ipfsProxy') + const onWebuiFromDNSLinkChange = onOptionChange('webuiFromDNSLink') return html`
@@ -66,6 +68,15 @@ function experimentsForm ({
${switchToggle({ id: 'linkify', checked: linkify, onchange: onLinkifyChange })}
+
+ +
${switchToggle({ id: 'webuiFromDNSLink', checked: webuiFromDNSLink, onchange: onWebuiFromDNSLinkChange })}
+