Skip to content

Commit

Permalink
refactor: separate 'api down' from 'offline' state
Browse files Browse the repository at this point in the history
This changes the meaning of  offlinePeerCount to be the state when
peerCount is 0, and adds explicit apiDownPeerCount equal -1 to indicate
state where extension is unable to read peerCount and has to assume API
is down.
  • Loading branch information
lidel committed Oct 23, 2020
1 parent 0f51c6b commit a7793c7
Show file tree
Hide file tree
Showing 14 changed files with 60 additions and 53 deletions.
16 changes: 8 additions & 8 deletions add-on/src/landing-pages/welcome/page.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -25,8 +25,8 @@ function createWelcomePage (i18n) {
return html`
<div class="flex flex-column flex-row-l">
<div id="left-col" class="min-vh-100 flex flex-column justify-center items-center bg-navy white">
${renderCompanionLogo(i18n, isIpfsOnline)}
${isIpfsOnline ? renderWelcome(i18n, peerCount, openWebUi) : renderInstallSteps(i18n, isIpfsOnline)}
${renderCompanionLogo(i18n, apiAvailable)}
${apiAvailable ? renderWelcome(i18n, peerCount, openWebUi) : renderInstallSteps(i18n, apiAvailable)}
</div>
<div id="right-col" class="min-vh-100 w-100 flex flex-column justify-around items-center">
Expand All @@ -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`
<div class="mt4 mb2 flex flex-column justify-center items-center transition-all ${stateUnknown && 'state-unknown'}">
${logo({ path: logoPath, size: logoSize, isIpfsOnline: isIpfsOnline })}
${logo({ path: logoPath, size: logoSize, apiAvailable: apiAvailable })}
<p class="montserrat mt3 mb0 f2">${i18n.getMessage('page_landingWelcome_logo_title')}</p>
</div>
`
Expand Down Expand Up @@ -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`
Expand Down
10 changes: 4 additions & 6 deletions add-on/src/landing-pages/welcome/store.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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')
}
Expand Down
6 changes: 3 additions & 3 deletions add-on/src/lib/context-menus.js
Original file line number Diff line number Diff line change
Expand Up @@ -163,15 +163,15 @@ function createContextMenus (getState, runtime, ipfsPathValidator, { onAddFromCo
ipfsContext = ipfsPathValidator.isIpfsPageActionsContext(currentTab.url)
}
}
const ifApi = getState().peerCount > -1
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)
Expand Down
3 changes: 1 addition & 2 deletions add-on/src/lib/dnslink.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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
Expand Down
22 changes: 13 additions & 9 deletions add-on/src/lib/ipfs-companion.js
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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
}
}

Expand All @@ -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'
Expand Down Expand Up @@ -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'))
}
Expand Down
10 changes: 9 additions & 1 deletion add-on/src/lib/state.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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?
Expand All @@ -67,3 +74,4 @@ function initState (options, overrides) {

exports.initState = initState
exports.offlinePeerCount = offlinePeerCount
exports.apiDownPeerCount = apiDownPeerCount
7 changes: 3 additions & 4 deletions add-on/src/popup/browser-action/context-actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 && isApiAvailable
const activeCidResolver = active && apiAvailable && currentTabCid
const activePinControls = active && apiAvailable
const activeViewOnGateway = (currentTab) => {
if (!currentTab) return false
const { url } = currentTab
Expand Down
2 changes: 1 addition & 1 deletion add-on/src/popup/browser-action/gateway-status.js
Original file line number Diff line number Diff line change
Expand Up @@ -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`
<div class="flex mb1 ${check ? '' : 'o-60'} ${itemClass}" title="${labelLegend}">
<span class="w-40 f7 ttu no-user-select">${label}</span>
Expand Down
4 changes: 2 additions & 2 deletions add-on/src/popup/browser-action/header.js
Original file line number Diff line number Diff line change
Expand Up @@ -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`
<div class="br2 br--top ba bw1 b--white ipfs-gradient-0">
<div class="pt3 pr3 pb2 pl3 no-user-select flex justify-between items-center">
Expand All @@ -22,7 +22,7 @@ module.exports = function header (props) {
size: 54,
path: '../../../icons',
ipfsNodeType,
isIpfsOnline: (active && isIpfsOnline)
apiAvailable
})}
</div>
<div class="flex flex-column ml2 white ${active ? '' : 'o-40'}">
Expand Down
14 changes: 6 additions & 8 deletions add-on/src/popup/browser-action/store.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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 })
Expand Down Expand Up @@ -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)
}
}
Expand All @@ -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
Expand Down
8 changes: 4 additions & 4 deletions add-on/src/popup/browser-action/tools.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@ const navItem = require('./nav-item')
module.exports = function tools ({
active,
ipfsNodeType,
isIpfsOnline,
isApiAvailable,
apiAvailable,
onQuickImport,
onOpenWebUi
}) {
const activeQuickImport = active && isApiAvailable
const activeWebUI = active && isApiAvailable && ipfsNodeType !== 'embedded'
const localGwAvailable = ipfsNodeType !== 'embedded'
const activeQuickImport = active && apiAvailable
const activeWebUI = active && apiAvailable && localGwAvailable

return html`
<div class="fade-in pv1">
Expand Down
6 changes: 3 additions & 3 deletions add-on/src/popup/logo.js
Original file line number Diff line number Diff line change
Expand Up @@ -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`
<img
alt="IPFS"
src="${path}/${logoFileName}"
class="v-mid ${isIpfsOnline ? '' : 'o-40'} ${isIpfsOnline && heartbeat ? 'heartbeat' : ''}"
class="v-mid ${apiAvailable ? '' : 'o-40'} ${apiAvailable && heartbeat ? 'heartbeat' : ''}"
style="width:${size}px; height:${size}px" />
`
}
Expand Down
2 changes: 1 addition & 1 deletion add-on/src/popup/page-action/header.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ module.exports = function header ({ isIpfsContext, pageActionTitle }) {
size: 20,
path: '../../../icons',
ipfsNodeType: 'external',
isIpfsOnline: true,
apiAvailable: true,
heartbeat: false
})} <span class="pl1 f6 fw4 v-mid">${pageActionTitle || '…'}</span>
</h2>
Expand Down
3 changes: 2 additions & 1 deletion add-on/src/popup/quick-import.js
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand All @@ -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
Expand Down

0 comments on commit a7793c7

Please sign in to comment.