Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support ipfs daemon --offline #936

Draft
wants to merge 5 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 >= 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)
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
17 changes: 12 additions & 5 deletions add-on/src/lib/precache.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 = []
Expand All @@ -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 <api-port>/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({
Expand All @@ -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}`)
Expand All @@ -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}`)
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 && isIpfsOnline && 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 && isIpfsOnline && isApiAvailable
const activeWebUI = active && isIpfsOnline && 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
Loading