Skip to content

Commit 2b63636

Browse files
committed
fix: cleanup boot on clean install
The very first time extension started was broken, node got restarted multiple times due to config updates happening in lib/options.js This change simplifies boot process to the point no restarts are triggered by config updates on the first run. Landing page is also fixed to properly receive notification about new peers being available. We switched to ipfs.io for DNS resolution until DNSLink support lands in js-ipfs and we get better understanding how to operate chrome.sockets.udp API Tests for relevant code paths are updated and embedded js-ipfs with chrome.sockets now listens on custom ports, removing the need of changing configuration if someone is already running go-ipfs or js-ipfs
1 parent 5c0b495 commit 2b63636

File tree

12 files changed

+133
-104
lines changed

12 files changed

+133
-104
lines changed

add-on/src/background/background.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
'use strict'
22
/* eslint-env browser, webextensions */
3+
4+
// Enable some debug output from js-ipfs
5+
// (borrowed from https://github.com/ipfs-shipyard/ipfs-companion/pull/557)
6+
// to include everything (mplex, libp2p, mss): localStorage.debug = '*'
7+
localStorage.debug = 'jsipfs*,ipfs*,-*:mfs*,-*:ipns*,-ipfs:preload*'
8+
39
const browser = require('webextension-polyfill')
410
const createIpfsCompanion = require('../lib/ipfs-companion')
511
const { onInstalled } = require('../lib/on-installed')

add-on/src/landing-pages/welcome/store.js

Lines changed: 14 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -5,26 +5,22 @@ function createWelcomePageStore (i18n, runtime) {
55
return function welcomePageStore (state, emitter) {
66
state.isIpfsOnline = null
77
state.peerCount = null
8-
9-
const port = runtime.connect({ name: 'browser-action-port' })
10-
11-
const onMessage = (message) => {
12-
if (message.statusUpdate) {
13-
const peerCount = message.statusUpdate.peerCount
14-
const isIpfsOnline = peerCount > -1
15-
16-
if (isIpfsOnline !== state.isIpfsOnline || peerCount !== state.peerCount) {
17-
state.isIpfsOnline = isIpfsOnline
18-
state.peerCount = peerCount
19-
emitter.emit('render')
20-
}
21-
}
22-
}
23-
24-
port.onMessage.addListener(onMessage)
25-
8+
let port
269
emitter.on('DOMContentLoaded', async () => {
2710
emitter.emit('render')
11+
port = runtime.connect({ name: 'browser-action-port' })
12+
port.onMessage.addListener(async (message) => {
13+
console.log('port.onMessage', message)
14+
if (message.statusUpdate) {
15+
const peerCount = message.statusUpdate.peerCount
16+
const isIpfsOnline = peerCount > -1
17+
if (isIpfsOnline !== state.isIpfsOnline || peerCount !== state.peerCount) {
18+
state.isIpfsOnline = isIpfsOnline
19+
state.peerCount = peerCount
20+
emitter.emit('render')
21+
}
22+
}
23+
})
2824
})
2925
}
3026
}

add-on/src/lib/dnslink.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,8 @@ module.exports = function createDnslinkResolver (getState) {
105105
readDnslinkFromTxtRecord (fqdn) {
106106
const state = getState()
107107
let apiProvider
108-
if (state.ipfsNodeType !== 'embedded' && state.peerCount !== offlinePeerCount) {
108+
// TODO: fix DNS resolver for ipfsNodeType='embedded:chromesockets', for now use ipfs.io
109+
if (!state.ipfsNodeType.startsWith('embedded') && state.peerCount !== offlinePeerCount) {
109110
apiProvider = state.apiURLString
110111
} else {
111112
// fallback to resolver at public gateway

add-on/src/lib/ipfs-client/embedded-chromesockets.js

Lines changed: 33 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,6 @@ let nodeHttpApi = null
2424
// let httpServer = null
2525
// let hapiServer = null
2626

27-
// Enable some debug output from js-ipfs
28-
// (borrowed from https://github.com/ipfs-shipyard/ipfs-companion/pull/557)
29-
// to include everything (mplex, libp2p, mss): localStorage.debug = '*'
30-
localStorage.debug = 'jsipfs*,ipfs*,-*:mfs*,-*:ipns*,-ipfs:preload*'
31-
3227
const log = debug('ipfs-companion:client:embedded')
3328
log.error = debug('ipfs-companion:client:embedded:error')
3429

@@ -43,9 +38,11 @@ exports.init = function init (opts) {
4338
hapiServer = startRawHapiServer(9092)
4439
}
4540
*/
46-
log('init: embedded js-ipfs+chrome.sockets')
41+
log('init embedded:chromesockets')
4742

4843
const defaultOpts = JSON.parse(optionDefaults.ipfsNodeConfig)
44+
45+
// TODO: check if below is needed after js-ipfs is released with DHT disabled
4946
defaultOpts.libp2p = {
5047
config: {
5148
dht: {
@@ -65,21 +62,21 @@ exports.init = function init (opts) {
6562
reject(error)
6663
})
6764
node.once('ready', async () => {
68-
node.on('start', async () => {
65+
node.once('start', async () => {
6966
// HttpApi is off in browser context and needs to be started separately
7067
try {
7168
const httpServers = new HttpApi(node, ipfsOpts)
7269
nodeHttpApi = await httpServers.start()
73-
await updateConfigWithHttpEndpoints(node)
70+
await updateConfigWithHttpEndpoints(node, opts)
7471
resolve(node)
7572
} catch (err) {
7673
reject(err)
7774
}
7875
})
79-
node.on('error', error => {
80-
log.error('something went terribly wrong in embedded js-ipfs!', error)
81-
})
8276
try {
77+
node.on('error', error => {
78+
log.error('something went terribly wrong in embedded js-ipfs!', error)
79+
})
8380
await node.start()
8481
} catch (err) {
8582
reject(err)
@@ -88,21 +85,35 @@ exports.init = function init (opts) {
8885
})
8986
}
9087

88+
const multiaddr2httpUrl = (ma) => maToUri(ma.includes('/http') ? ma : multiaddr(ma).encapsulate('/http'))
89+
9190
// Update internal configuration to HTTP Endpoints from js-ipfs instance
92-
async function updateConfigWithHttpEndpoints (ipfs) {
93-
const ma = await ipfs.config.get('Addresses.Gateway')
94-
log(`synchronizing Addresses.Gateway=${ma} to customGatewayUrl and ipfsNodeConfig`)
95-
const httpGateway = maToUri(ma.includes('/http') ? ma : multiaddr(ma).encapsulate('/http'))
96-
const ipfsNodeConfig = JSON.parse((await browser.storage.local.get('ipfsNodeConfig')).ipfsNodeConfig)
97-
ipfsNodeConfig.config.Addresses.Gateway = ma
98-
await browser.storage.local.set({
99-
customGatewayUrl: httpGateway,
100-
ipfsNodeConfig: JSON.stringify(ipfsNodeConfig, null, 2)
101-
})
91+
async function updateConfigWithHttpEndpoints (ipfs, opts) {
92+
const localConfig = await browser.storage.local.get('ipfsNodeConfig')
93+
if (localConfig && localConfig.ipfsNodeConfig) {
94+
const gwMa = await ipfs.config.get('Addresses.Gateway')
95+
const apiMa = await ipfs.config.get('Addresses.API')
96+
const httpGateway = multiaddr2httpUrl(gwMa)
97+
const httpApi = multiaddr2httpUrl(apiMa)
98+
log(`updating extension configuration to Gateway=${httpGateway} and API=${httpApi}`)
99+
// update ports in JSON configuration for embedded js-ipfs
100+
const ipfsNodeConfig = JSON.parse(localConfig.ipfsNodeConfig)
101+
ipfsNodeConfig.config.Addresses.Gateway = gwMa
102+
ipfsNodeConfig.config.Addresses.API = apiMa
103+
const configChanges = {
104+
customGatewayUrl: httpGateway,
105+
ipfsApiUrl: httpApi,
106+
ipfsNodeConfig: JSON.stringify(ipfsNodeConfig, null, 2)
107+
}
108+
// update current runtime config (in place, effective without restart)
109+
Object.assign(opts, configChanges)
110+
// update user config in storage (effective on next run)
111+
await browser.storage.local.set(configChanges)
112+
}
102113
}
103114

104115
exports.destroy = async function () {
105-
log('destroy: embedded js-ipfs+chrome.sockets')
116+
log('destroy: embedded:chromesockets')
106117

107118
/*
108119
if (httpServer) {

add-on/src/lib/ipfs-client/embedded.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ exports.init = function init (opts) {
2121
reject(error)
2222
})
2323
node.once('ready', async () => {
24-
node.on('start', () => {
24+
node.once('start', () => {
2525
resolve(node)
2626
})
2727
node.on('error', error => {

add-on/src/lib/ipfs-client/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ const embeddedWithChromeSockets = require('./embedded-chromesockets')
1414
let client
1515

1616
async function initIpfsClient (opts) {
17+
log('init ipfs client')
1718
await destroyIpfsClient()
1819
switch (opts.ipfsNodeType) {
1920
case 'embedded':

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

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
'use strict'
22
/* eslint-env browser, webextensions */
33

4+
const debug = require('debug')
5+
const log = debug('ipfs-companion:main')
6+
log.error = debug('ipfs-companion:main:error')
7+
48
const browser = require('webextension-polyfill')
59
const toMultiaddr = require('uri-to-multiaddr')
610
const { optionDefaults, storeMissingOptions, migrateOptions } = require('./options')
@@ -37,7 +41,9 @@ module.exports = async function init () {
3741
const browserActionPortName = 'browser-action-port'
3842

3943
try {
44+
log('init')
4045
await migrateOptions(browser.storage.local)
46+
await storeMissingOptions(await browser.storage.local.get(), optionDefaults, browser.storage.local)
4147
const options = await browser.storage.local.get(optionDefaults)
4248
runtime = await createRuntimeChecks(browser)
4349
state = initState(options)
@@ -68,16 +74,13 @@ module.exports = async function init () {
6874
modifyRequest = createRequestModifier(getState, dnslinkResolver, ipfsPathValidator, runtime)
6975
ipfsProxy = createIpfsProxy(getIpfs, getState)
7076
ipfsProxyContentScript = await registerIpfsProxyContentScript()
77+
log('register all listeners')
7178
registerListeners()
7279
await setApiStatusUpdateInterval(options.ipfsApiPollMs)
73-
await storeMissingOptions(
74-
await browser.storage.local.get(),
75-
optionDefaults,
76-
browser.storage.local
77-
)
80+
log('init done')
7881
await showPendingLandingPages()
7982
} catch (error) {
80-
console.error('Unable to initialize addon due to error', error)
83+
log.error('Unable to initialize addon due to error', error)
8184
if (notify) notify('notify_addonIssueTitle', 'notify_addonIssueMsg')
8285
throw error
8386
}
@@ -596,6 +599,7 @@ module.exports = async function init () {
596599

597600
async function onStorageChange (changes, area) {
598601
let shouldRestartIpfsClient = false
602+
let shouldStopIpfsClient = false
599603

600604
for (let key in changes) {
601605
const change = changes[key]
@@ -608,6 +612,7 @@ module.exports = async function init () {
608612
state[key] = change.newValue
609613
ipfsProxyContentScript = await registerIpfsProxyContentScript()
610614
shouldRestartIpfsClient = true
615+
shouldStopIpfsClient = !state.active
611616
break
612617
case 'ipfsNodeType':
613618
case 'ipfsNodeConfig':
@@ -656,8 +661,9 @@ module.exports = async function init () {
656661
}
657662
}
658663

659-
if (shouldRestartIpfsClient) {
664+
if ((state.active && shouldRestartIpfsClient) || shouldStopIpfsClient) {
660665
try {
666+
log('stoping ipfs client due to config changes', changes)
661667
await destroyIpfsClient()
662668
} catch (err) {
663669
console.error('[ipfs-companion] Failed to destroy IPFS client', err)
@@ -669,6 +675,7 @@ module.exports = async function init () {
669675
if (!state.active) return
670676

671677
try {
678+
log('starting ipfs client with the new config')
672679
ipfs = await initIpfsClient(state)
673680
} catch (err) {
674681
console.error('[ipfs-companion] Failed to init IPFS client', err)

add-on/src/lib/on-installed.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ exports.showPendingLandingPages = async () => {
1515
const hint = await browser.storage.local.get('showLandingPage')
1616
switch (hint.showLandingPage) {
1717
case 'onInstallWelcome':
18-
await browser.storage.local.remove('showLandingPage')
18+
// TODO:restore await browser.storage.local.remove('showLandingPage')
1919
return browser.tabs.create({
2020
url: '/dist/landing-pages/welcome/index.html'
2121
})

add-on/src/lib/options.js

Lines changed: 41 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,24 @@ exports.optionDefaults = Object.freeze({
1717
preloadAtPublicGateway: true,
1818
catchUnhandledProtocols: true,
1919
displayNotifications: true,
20-
customGatewayUrl: 'http://127.0.0.1:8080',
21-
ipfsApiUrl: 'http://127.0.0.1:5001',
20+
customGatewayUrl: buildCustomGatewayUrl(),
21+
ipfsApiUrl: buildIpfsApiUrl(),
2222
ipfsApiPollMs: 3000,
2323
ipfsProxy: true // window.ipfs
2424
})
2525

26+
function buildCustomGatewayUrl () {
27+
// TODO: make more robust (sync with buildDefaultIpfsNodeConfig)
28+
const port = hasChromeSocketsForTcp() ? 9091 : 8080
29+
return `http://127.0.0.1:${port}`
30+
}
31+
32+
function buildIpfsApiUrl () {
33+
// TODO: make more robust (sync with buildDefaultIpfsNodeConfig)
34+
const port = hasChromeSocketsForTcp() ? 5003 : 5001
35+
return `http://127.0.0.1:${port}`
36+
}
37+
2638
function buildDefaultIpfsNodeType () {
2739
// Right now Brave is the only vendor giving us access to chrome.sockets
2840
return hasChromeSocketsForTcp() ? 'embedded:chromesockets' : 'external'
@@ -37,36 +49,40 @@ function buildDefaultIpfsNodeConfig () {
3749
}
3850
}
3951
if (hasChromeSocketsForTcp()) {
40-
// config.config.Addresses.API = '/ip4/127.0.0.1/tcp/5002'
41-
config.config.Addresses.API = '' // disable API port
42-
config.config.Addresses.Gateway = '/ip4/127.0.0.1/tcp/8080'
52+
// TODO: make more robust (sync with buildCustomGatewayUrl and buildIpfsApiUrl)
53+
// embedded node should use different ports to make it easier
54+
// for people already running regular go-ipfs and js-ipfs on standard ports
55+
config.config.Addresses.API = '/ip4/127.0.0.1/tcp/5003'
56+
config.config.Addresses.Gateway = '/ip4/127.0.0.1/tcp/9091'
57+
/*
58+
(Sidenote on why we need API for Web UI)
59+
Gateway can run without API port,
60+
but Web UI does not use window.ipfs due to sandboxing atm.
61+
62+
If Web UI is able to use window.ipfs, then we can remove API port.
63+
Disabling API is as easy as:
64+
config.config.Addresses.API = ''
65+
*/
4366
}
4467
return JSON.stringify(config, null, 2)
4568
}
4669

4770
// `storage` should be a browser.storage.local or similar
48-
exports.storeMissingOptions = (read, defaults, storage) => {
71+
exports.storeMissingOptions = async (read, defaults, storage) => {
4972
const requiredKeys = Object.keys(defaults)
50-
const changes = new Set()
51-
requiredKeys.map(key => {
52-
// limit work to defaults and missing values
73+
const changes = {}
74+
for (let key of requiredKeys) {
75+
// limit work to defaults and missing values, skip values other than defaults
5376
if (!read.hasOwnProperty(key) || read[key] === defaults[key]) {
54-
changes.add(new Promise((resolve, reject) => {
55-
storage.get(key).then(data => {
56-
if (!data[key]) { // detect and fix key without value in storage
57-
let option = {}
58-
option[key] = defaults[key]
59-
storage.set(option)
60-
.then(data => { resolve(`updated:${key}`) })
61-
.catch(error => { reject(error) })
62-
} else {
63-
resolve(`nochange:${key}`)
64-
}
65-
})
66-
}))
77+
const data = await storage.get(key)
78+
if (!data.hasOwnProperty(key)) { // detect and fix key without value in storage
79+
changes[key] = defaults[key]
80+
}
6781
}
68-
})
69-
return Promise.all(changes)
82+
}
83+
// save all in bulk
84+
await storage.set(changes)
85+
return changes
7086
}
7187

7288
function normalizeGatewayURL (url) {
@@ -118,10 +134,9 @@ exports.migrateOptions = async (storage) => {
118134
if (ipfsNodeType === 'embedded' && hasChromeSocketsForTcp()) {
119135
console.log(`[ipfs-companion] migrating ipfsNodeType to 'embedded:chromesockets'`)
120136
// Overwrite old config
121-
const ipfsNodeConfig = JSON.parse(exports.optionDefaults.ipfsNodeConfig)
122137
await storage.set({
123138
ipfsNodeType: 'embedded:chromesockets',
124-
ipfsNodeConfig: JSON.stringify(ipfsNodeConfig, null, 2)
139+
ipfsNodeConfig: buildDefaultIpfsNodeConfig()
125140
})
126141
}
127142
}

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ function gatewaysForm ({
2424
const onNoRedirectHostnamesChange = onOptionChange('noRedirectHostnames', hostTextToArray)
2525
const mixedContentWarning = !secureContextUrl.test(customGatewayUrl)
2626
const supportRedirectToCustomGateway = ipfsNodeType !== 'embedded'
27+
const allowChangeOfCustomGateway = ipfsNodeType !== 'embedded:chromesockets'
2728

2829
return html`
2930
<form>
@@ -47,7 +48,7 @@ function gatewaysForm ({
4748
onchange=${onPublicGatewayUrlChange}
4849
value=${publicGatewayUrl} />
4950
</div>
50-
${supportRedirectToCustomGateway ? html`
51+
${supportRedirectToCustomGateway && allowChangeOfCustomGateway ? html`
5152
<div>
5253
<label for="customGatewayUrl">
5354
<dl>
@@ -66,7 +67,7 @@ function gatewaysForm ({
6667
spellcheck="false"
6768
title="Enter URL without any sub-path"
6869
onchange=${onCustomGatewayUrlChange}
69-
${ipfsNodeType !== 'external' ? 'disabled' : ''}
70+
${allowChangeOfCustomGateway ? '' : 'disabled'}
7071
value=${customGatewayUrl} />
7172
7273
</div>

0 commit comments

Comments
 (0)