diff --git a/add-on/src/landing-pages/welcome/page.js b/add-on/src/landing-pages/welcome/page.js index 7d4db4bc8..eda66fb6b 100644 --- a/add-on/src/landing-pages/welcome/page.js +++ b/add-on/src/landing-pages/welcome/page.js @@ -16,7 +16,7 @@ const colorYellow = '#f39021' function createWelcomePage (i18n) { return function welcomePage (state, emit) { - const { isIpfsOnline, peerCount } = state + const { apiAvailable, peerCount } = state const openWebUi = (page) => () => emit('openWebUi', page) // Set translated title @@ -25,8 +25,8 @@ function createWelcomePage (i18n) { return html`
- ${renderCompanionLogo(i18n, isIpfsOnline)} - ${isIpfsOnline ? renderWelcome(i18n, peerCount, openWebUi) : renderInstallSteps(i18n, isIpfsOnline)} + ${renderCompanionLogo(i18n, apiAvailable)} + ${apiAvailable ? renderWelcome(i18n, peerCount, openWebUi) : renderInstallSteps(i18n, apiAvailable)}
@@ -43,14 +43,14 @@ function createWelcomePage (i18n) { Render functions for the left side ======================================================== */ -const renderCompanionLogo = (i18n, isIpfsOnline) => { +const renderCompanionLogo = (i18n, apiAvailable) => { const logoPath = '../../../icons' const logoSize = 128 - const stateUnknown = isIpfsOnline === null + const stateUnknown = apiAvailable === null return html`
- ${logo({ path: logoPath, size: logoSize, isIpfsOnline: isIpfsOnline })} + ${logo({ path: logoPath, size: logoSize, apiAvailable: apiAvailable })}

${i18n.getMessage('page_landingWelcome_logo_title')}

` @@ -83,10 +83,10 @@ const renderWelcome = (i18n, peerCount, openWebUi) => { ` } -const renderInstallSteps = (i18n, isIpfsOnline) => { +const renderInstallSteps = (i18n, apiAvailable) => { const copyClass = 'mv0 white f5 lh-copy' const anchorClass = 'aqua hover-white' - const stateUnknown = isIpfsOnline === null + const stateUnknown = apiAvailable == null const svgWidth = 130 const nodeOffSvg = () => html` diff --git a/add-on/src/landing-pages/welcome/store.js b/add-on/src/landing-pages/welcome/store.js index 19833abc2..6d5614e29 100644 --- a/add-on/src/landing-pages/welcome/store.js +++ b/add-on/src/landing-pages/welcome/store.js @@ -4,7 +4,7 @@ const browser = require('webextension-polyfill') function createWelcomePageStore (i18n, runtime) { return function welcomePageStore (state, emitter) { - state.isIpfsOnline = null + state.apiAvailable = null state.peerCount = null state.webuiRootUrl = null let port @@ -13,12 +13,10 @@ function createWelcomePageStore (i18n, runtime) { port = runtime.connect({ name: 'browser-action-port' }) port.onMessage.addListener(async (message) => { if (message.statusUpdate) { - const webuiRootUrl = message.statusUpdate.webuiRootUrl - const peerCount = message.statusUpdate.peerCount - const isIpfsOnline = peerCount > -1 - if (isIpfsOnline !== state.isIpfsOnline || peerCount !== state.peerCount || webuiRootUrl !== state.webuiRootUrl) { + const { webuiRootUrl, peerCount, apiAvailable } = message.statusUpdate + if (apiAvailable !== state.apiAvailable || peerCount !== state.peerCount || webuiRootUrl !== state.webuiRootUrl) { state.webuiRootUrl = webuiRootUrl - state.isIpfsOnline = isIpfsOnline + state.apiAvailable = apiAvailable state.peerCount = peerCount emitter.emit('render') } diff --git a/add-on/src/lib/context-menus.js b/add-on/src/lib/context-menus.js index b38291d23..d35e156ae 100644 --- a/add-on/src/lib/context-menus.js +++ b/add-on/src/lib/context-menus.js @@ -163,15 +163,15 @@ function createContextMenus (getState, runtime, ipfsPathValidator, { onAddFromCo ipfsContext = ipfsPathValidator.isIpfsPageActionsContext(currentTab.url) } } - const ifApi = getState().peerCount >= 0 + const { apiAvailable } = getState() for (const item of apiMenuItems) { - await browser.contextMenus.update(item, { enabled: ifApi }) + await browser.contextMenus.update(item, { enabled: apiAvailable }) } for (const item of ipfsContextItems) { await browser.contextMenus.update(item, { enabled: ipfsContext }) } for (const item of apiAndIpfsContextItems) { - await browser.contextMenus.update(item, { enabled: (ifApi && ipfsContext) }) + await browser.contextMenus.update(item, { enabled: (apiAvailable && ipfsContext) }) } } catch (err) { log.error('Error updating context menus', err) diff --git a/add-on/src/lib/dnslink.js b/add-on/src/lib/dnslink.js index b27736f44..f91ef519d 100644 --- a/add-on/src/lib/dnslink.js +++ b/add-on/src/lib/dnslink.js @@ -8,7 +8,6 @@ log.error = debug('ipfs-companion:dnslink:error') const IsIpfs = require('is-ipfs') const LRU = require('lru-cache') const { default: PQueue } = require('p-queue') -const { offlinePeerCount } = require('./state') const { ipfsContentPath, sameGateway, pathAtHttpGateway } = require('./ipfs-path') module.exports = function createDnslinkResolver (getState) { @@ -123,7 +122,7 @@ module.exports = function createDnslinkResolver (getState) { const state = getState() let apiProvider // TODO: fix DNS resolver for ipfsNodeType='embedded:chromesockets', for now use ipfs.io - if (!state.ipfsNodeType.startsWith('embedded') && state.peerCount !== offlinePeerCount) { + if (state.localGwAvailable && state.apiAvailable) { // Use gw port so it can be a GET: // Chromium does not execute onBeforeSendHeaders for synchronous calls // made from the same extension context as onBeforeSendHeaders diff --git a/add-on/src/lib/ipfs-companion.js b/add-on/src/lib/ipfs-companion.js index 2ef04a551..e5698a44e 100644 --- a/add-on/src/lib/ipfs-companion.js +++ b/add-on/src/lib/ipfs-companion.js @@ -11,7 +11,7 @@ const pMemoize = require('p-memoize') const LRU = require('lru-cache') const all = require('it-all') const { optionDefaults, storeMissingOptions, migrateOptions, guiURLString, safeURL } = require('./options') -const { initState, offlinePeerCount } = require('./state') +const { initState, offlinePeerCount, apiDownPeerCount } = require('./state') const { createIpfsPathValidator, sameGateway } = require('./ipfs-path') const createDnslinkResolver = require('./dnslink') const { createRequestModifier } = require('./ipfs-request') @@ -257,6 +257,7 @@ module.exports = async function init () { importDir: state.importDir, openViaWebUI: state.openViaWebUI, apiURLString: dropSlash(state.apiURLString), + apiAvailable: state.apiAvailable, redirect: state.redirect, enabledOn: state.enabledOn, disabledOn: state.disabledOn, @@ -462,13 +463,18 @@ module.exports = async function init () { } async function getSwarmPeerCount () { - if (!ipfs) return offlinePeerCount + if (!ipfs) return apiDownPeerCount try { const peerInfos = await ipfs.swarm.peers({ timeout: 2500 }) return peerInfos.length } catch (error) { + if (error.message.includes('action must be run in online mode')) { + // node is running in offline mode (ipfs daemon --offline) + // https://github.com/ipfs-shipyard/ipfs-companion/issues/790 + return offlinePeerCount // ipfs daemon --offline + } console.error(`Error while ipfs.swarm.peers: ${error}`) - return offlinePeerCount + return apiDownPeerCount } } @@ -495,11 +501,11 @@ module.exports = async function init () { let badgeText, badgeColor, badgeIcon badgeText = state.peerCount.toString() - if (state.peerCount > 0) { + if (state.peerCount > offlinePeerCount) { // All is good (online with peers) badgeColor = '#418B8E' badgeIcon = '/icons/ipfs-logo-on.svg' - } else if (state.peerCount === 0) { + } else if (state.peerCount === offlinePeerCount) { // API is online but no peers badgeColor = 'red' badgeIcon = '/icons/ipfs-logo-on.svg' @@ -578,13 +584,11 @@ module.exports = async function init () { function updateAutomaticModeRedirectState (oldPeerCount, newPeerCount) { // enable/disable gw redirect based on API going online or offline - // newPeerCount === -1 currently implies node is offline. - // TODO: use `node.isOnline()` if available (js-ipfs) if (state.automaticMode && state.localGwAvailable) { - if (oldPeerCount === offlinePeerCount && newPeerCount > offlinePeerCount && !state.redirect) { + if (oldPeerCount === apiDownPeerCount && newPeerCount > apiDownPeerCount && !state.redirect) { browser.storage.local.set({ useCustomGateway: true }) .then(() => notify('notify_apiOnlineTitle', 'notify_apiOnlineAutomaticModeMsg')) - } else if (newPeerCount === offlinePeerCount && state.redirect) { + } else if (newPeerCount === apiDownPeerCount && state.redirect) { browser.storage.local.set({ useCustomGateway: false }) .then(() => notify('notify_apiOfflineTitle', 'notify_apiOfflineAutomaticModeMsg')) } diff --git a/add-on/src/lib/precache.js b/add-on/src/lib/precache.js index 24ccf15a0..481fe0066 100644 --- a/add-on/src/lib/precache.js +++ b/add-on/src/lib/precache.js @@ -21,7 +21,7 @@ module.exports.braveJsIpfsWebuiCid = 'bafybeigkbbjnltbd4ewfj7elajsbnjwinyk6tiilc module.exports.precache = async (ipfs, state) => { // simplified prefetch over HTTP when in Brave if (state.ipfsNodeType === 'embedded:chromesockets') { - return preloadOverHTTP(log, ipfs, state, module.exports.braveJsIpfsWebuiCid) + return preloadOverHTTP(log, ipfs, state, module.exports.braveJsIpfsWebuiCid, 'webui for embedded:chromesockets') } const roots = [] @@ -32,7 +32,8 @@ module.exports.precache = async (ipfs, state) => { cid = await ipfs.dns('webui.ipfs.io', { recursive: true }) name = 'latest webui from DNSLink at webui.ipfs.io' } else { // find out safelisted path behind /webui - cid = new URL((await fetch(`${state.apiURLString}webui`)).url).pathname + const { pathname } = new URL((await fetch(`${state.apiURLString}webui`)).url) + cid = pathname.split('/')[2] name = `stable webui hardcoded at ${state.apiURLString}webui` } roots.push({ @@ -53,12 +54,18 @@ module.exports.precache = async (ipfs, state) => { } log(`importing ${name} (${cid}) to local ipfs repo`) + // prefetch over HTTP when node runs in offline mode + if (state.peerCount < 1) { + await preloadOverHTTP(log, ipfs, state, cid, name) + continue + } + // prefetch over IPFS try { for await (const ref of ipfs.refs(cid, { recursive: true })) { if (ref.err) { log.error(`error while preloading ${name} (${cid})`, ref.err) - continue + throw new Error(ref.err) } } log(`${name} successfully cached under CID ${cid}`) @@ -81,10 +88,10 @@ async function inRepo (ipfs, cid) { // Downloads CID from a public gateway // (alternative to ipfs.refs -r) -async function preloadOverHTTP (log, ipfs, state, cid) { +async function preloadOverHTTP (log, ipfs, state, cid, name) { const url = `${state.pubGwURLString}api/v0/get?arg=${cid}&archive=true` try { - log(`importing ${url} (${cid}) to local ipfs repo`) + log(`importing ${url} to local ipfs repo`) const { body } = await fetch(url) await importTar(ipfs, body.getReader(), cid) log(`successfully fetched TAR from ${url} and cached under CID ${cid}`) diff --git a/add-on/src/lib/state.js b/add-on/src/lib/state.js index 0a6174449..769354574 100644 --- a/add-on/src/lib/state.js +++ b/add-on/src/lib/state.js @@ -4,7 +4,8 @@ const isFQDN = require('is-fqdn') const { safeURL } = require('./options') const { braveJsIpfsWebuiCid } = require('./precache') -const offlinePeerCount = -1 +const offlinePeerCount = 0 // always the case when running: ipfs daemon --offline +const apiDownPeerCount = -1 // unable to read, most likely API is down function initState (options, overrides) { // we store options and some pregenerated values to avoid async storage @@ -48,6 +49,12 @@ function initState (options, overrides) { // TODO: make quick fetch to confirm it works? get: function () { return this.ipfsNodeType !== 'embedded' } }) + Object.defineProperty(state, 'apiAvailable', { + // TODO: when we move away from constantly polling in the backgroun, + // this can be replaced with ipfs.id check to confirm it works + memoize for ipfsApiPollMs + // that way there is no need for polling, api check would execute only when user actualy needs it + get: function () { return this.peerCount > apiDownPeerCount } + }) Object.defineProperty(state, 'webuiRootUrl', { get: function () { // Did user opt-in for rolling release published on DNSLink? @@ -67,3 +74,4 @@ function initState (options, overrides) { exports.initState = initState exports.offlinePeerCount = offlinePeerCount +exports.apiDownPeerCount = apiDownPeerCount diff --git a/add-on/src/popup/browser-action/context-actions.js b/add-on/src/popup/browser-action/context-actions.js index 29d5a8a3f..8957e9466 100644 --- a/add-on/src/popup/browser-action/context-actions.js +++ b/add-on/src/popup/browser-action/context-actions.js @@ -32,16 +32,15 @@ function contextActions ({ isPinning, isUnPinning, isPinned, - isIpfsOnline, - isApiAvailable, + apiAvailable, onToggleSiteIntegrations, onViewOnGateway, onCopy, onPin, onUnPin }) { - const activeCidResolver = active && isIpfsOnline && isApiAvailable && currentTabCid - const activePinControls = active && isIpfsOnline && isApiAvailable + const activeCidResolver = active && apiAvailable && currentTabCid + const activePinControls = active && apiAvailable const activeViewOnGateway = (currentTab) => { if (!currentTab) return false const { url } = currentTab diff --git a/add-on/src/popup/browser-action/gateway-status.js b/add-on/src/popup/browser-action/gateway-status.js index 2711294bb..b268ac9b5 100644 --- a/add-on/src/popup/browser-action/gateway-status.js +++ b/add-on/src/popup/browser-action/gateway-status.js @@ -8,7 +8,7 @@ function statusEntry ({ label, labelLegend, value, check, itemClass = '', valueC const offline = browser.i18n.getMessage('panel_statusOffline') label = label ? browser.i18n.getMessage(label) : null labelLegend = labelLegend ? browser.i18n.getMessage(labelLegend) : label - value = value || value === 0 ? value : offline + value = value || offline return html`
${label} diff --git a/add-on/src/popup/browser-action/header.js b/add-on/src/popup/browser-action/header.js index e5fcbfe04..ab7e04927 100644 --- a/add-on/src/popup/browser-action/header.js +++ b/add-on/src/popup/browser-action/header.js @@ -9,7 +9,7 @@ const ipfsVersion = require('./ipfs-version') const gatewayStatus = require('./gateway-status') module.exports = function header (props) { - const { ipfsNodeType, active, onToggleActive, onOpenPrefs, isIpfsOnline, onOpenWelcomePage } = props + const { ipfsNodeType, active, onToggleActive, onOpenPrefs, apiAvailable, onOpenWelcomePage } = props return html`
@@ -22,7 +22,7 @@ module.exports = function header (props) { size: 54, path: '../../../icons', ipfsNodeType, - isIpfsOnline: (active && isIpfsOnline) + apiAvailable })}
diff --git a/add-on/src/popup/browser-action/store.js b/add-on/src/popup/browser-action/store.js index 6fa955ed1..acc437c1e 100644 --- a/add-on/src/popup/browser-action/store.js +++ b/add-on/src/popup/browser-action/store.js @@ -22,14 +22,13 @@ module.exports = (state, emitter) => { isPinned: false, // IPFS details ipfsNodeType: 'external', - isIpfsOnline: false, + apiAvailable: false, ipfsApiUrl: null, publicGatewayUrl: null, publicSubdomainGatewayUrl: null, gatewayAddress: null, swarmPeers: null, gatewayVersion: null, - isApiAvailable: false, // isRedirectContext currentTab: null, currentFqdn: null, @@ -229,7 +228,7 @@ module.exports = (state, emitter) => { state.ipfsApiUrl = null state.gatewayVersion = null state.swarmPeers = null - state.isIpfsOnline = false + state.apiAvailable = false } try { await browser.storage.local.set({ active: state.active }) @@ -257,7 +256,7 @@ module.exports = (state, emitter) => { // Note: access to background page will be denied in Private Browsing mode const ipfs = await getIpfsApi() // There is no point in displaying actions that require API interaction if API is down - const apiIsUp = ipfs && status && status.peerCount >= 0 + const apiIsUp = ipfs && status && status.apiAvailable if (apiIsUp) await updatePinnedState(ipfs, status) } } @@ -273,15 +272,14 @@ module.exports = (state, emitter) => { state.gatewayAddress = status.pubGwURLString } // Import requires access to the background page (https://github.com/ipfs-shipyard/ipfs-companion/issues/477) - state.isApiAvailable = state.active && !!(await getBackgroundPage()) && !browser.extension.inIncognitoContext // https://github.com/ipfs-shipyard/ipfs-companion/issues/243 - state.swarmPeers = !state.active || status.peerCount === -1 ? null : status.peerCount - state.isIpfsOnline = state.active && status.peerCount > -1 + state.apiAvailable = state.active && status.apiAvailable && !!(await getBackgroundPage()) && !browser.extension.inIncognitoContext // https://github.com/ipfs-shipyard/ipfs-companion/issues/243 + state.swarmPeers = state.apiAvailable ? status.peerCount : null state.gatewayVersion = state.active && status.gatewayVersion ? status.gatewayVersion : null state.ipfsApiUrl = state.active ? status.apiURLString : null } else { state.ipfsNodeType = 'external' state.swarmPeers = null - state.isIpfsOnline = false + state.apiAvailable = false state.gatewayVersion = null state.isIpfsContext = false state.isRedirectContext = false diff --git a/add-on/src/popup/browser-action/tools.js b/add-on/src/popup/browser-action/tools.js index 82378eceb..60c495d1a 100644 --- a/add-on/src/popup/browser-action/tools.js +++ b/add-on/src/popup/browser-action/tools.js @@ -8,13 +8,13 @@ const navItem = require('./nav-item') module.exports = function tools ({ active, ipfsNodeType, - isIpfsOnline, - isApiAvailable, + apiAvailable, onQuickImport, onOpenWebUi }) { - const activeQuickImport = active && isIpfsOnline && isApiAvailable - const activeWebUI = active && isIpfsOnline && ipfsNodeType !== 'embedded' + const localGwAvailable = ipfsNodeType !== 'embedded' + const activeQuickImport = active && apiAvailable + const activeWebUI = active && apiAvailable && localGwAvailable return html`
diff --git a/add-on/src/popup/logo.js b/add-on/src/popup/logo.js index e2ee0da41..9853e99b0 100644 --- a/add-on/src/popup/logo.js +++ b/add-on/src/popup/logo.js @@ -3,14 +3,14 @@ const html = require('choo/html') -function logo ({ path, size = 52, ipfsNodeType = 'external', isIpfsOnline = true, heartbeat = true }) { +function logo ({ path, size = 52, ipfsNodeType = 'external', apiAvailable = true, heartbeat = true }) { const logoTypePrefix = ipfsNodeType.startsWith('embedded') ? 'js-' : '' - const logoFileName = `${logoTypePrefix}ipfs-logo-${isIpfsOnline ? 'on' : 'off'}.svg` + const logoFileName = `${logoTypePrefix}ipfs-logo-${apiAvailable ? 'on' : 'off'}.svg` return html` IPFS ` } diff --git a/add-on/src/popup/page-action/header.js b/add-on/src/popup/page-action/header.js index a660a3c92..7dd9c5923 100644 --- a/add-on/src/popup/page-action/header.js +++ b/add-on/src/popup/page-action/header.js @@ -13,7 +13,7 @@ module.exports = function header ({ isIpfsContext, pageActionTitle }) { size: 20, path: '../../../icons', ipfsNodeType: 'external', - isIpfsOnline: true, + apiAvailable: true, heartbeat: false })} ${pageActionTitle || '…'} diff --git a/add-on/src/popup/quick-import.js b/add-on/src/popup/quick-import.js index 9feac1a39..38cbc8acf 100644 --- a/add-on/src/popup/quick-import.js +++ b/add-on/src/popup/quick-import.js @@ -8,6 +8,7 @@ const choo = require('choo') const html = require('choo/html') const logo = require('./logo') const externalApiClient = require('../lib/ipfs-client/external') +const { offlinePeerCount } = require('../lib/state') const all = require('it-all') const drop = require('drag-and-drop-files') const filesize = require('filesize') @@ -22,7 +23,7 @@ app.mount('#root') function quickImportStore (state, emitter) { state.message = '' - state.peerCount = '' + state.peerCount = offlinePeerCount state.ipfsNodeType = 'external' state.expandOptions = false state.openViaWebUI = true