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

Fix port collisions in Brave #734

Merged
merged 3 commits into from
Jul 16, 2019
Merged
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
134 changes: 48 additions & 86 deletions add-on/src/lib/ipfs-client/embedded-chromesockets.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,32 +16,16 @@ const Ipfs = require('ipfs')
const HttpApi = require('ipfs/src/http')
const multiaddr = require('multiaddr')
const maToUri = require('multiaddr-to-uri')
const getPort = require('get-port')

const { optionDefaults } = require('../options')

// js-ipfs with embedded hapi HTTP server
let node = null
let nodeHttpApi = null

// additional servers for smoke-tests
// let httpServer = null
// let hapiServer = null

exports.init = function init (opts) {
/*
// TEST RAW require('http') SERVER
if (!httpServer) {
httpServer = startRawHttpServer(9091)
}
// TEST require('@hapi/hapi') HTTP SERVER (same as in js-ipfs)
if (!hapiServer) {
hapiServer = startRawHapiServer(9092)
}
*/
log('init embedded:chromesockets')

async function buildConfig (opts) {
const defaultOpts = JSON.parse(optionDefaults.ipfsNodeConfig)

defaultOpts.libp2p = {
config: {
dht: {
Expand All @@ -50,9 +34,31 @@ exports.init = function init (opts) {
}
}
}

const userOpts = JSON.parse(opts.ipfsNodeConfig)
const ipfsOpts = mergeOptions.call({ concatArrays: true }, defaultOpts, userOpts, { start: false })
const ipfsNodeConfig = mergeOptions.call({ concatArrays: true }, defaultOpts, userOpts, { start: false })

// Detect when API or Gateway port is not available (taken by something else)
// We find the next free port and update configuration to use it instead
const multiaddr2port = (ma) => parseInt(new URL(multiaddr2httpUrl(ma)).port, 10)
const gatewayPort = multiaddr2port(ipfsNodeConfig.config.Addresses.Gateway)
const apiPort = multiaddr2port(ipfsNodeConfig.config.Addresses.API)
log(`checking if ports are available: api: ${apiPort}, gateway: ${gatewayPort}`)
const freeGatewayPort = await getPort({ port: getPort.makeRange(gatewayPort, gatewayPort + 100) })
const freeApiPort = await getPort({ port: getPort.makeRange(apiPort, apiPort + 100) })
if (gatewayPort !== freeGatewayPort || apiPort !== freeApiPort) {
log(`updating config to available ports: api: ${freeApiPort}, gateway: ${freeGatewayPort}`)
const addrs = ipfsNodeConfig.config.Addresses
addrs.Gateway = addrs.Gateway.replace(gatewayPort.toString(), freeGatewayPort.toString())
addrs.API = addrs.API.replace(apiPort.toString(), freeApiPort.toString())
}

return ipfsNodeConfig
}

exports.init = async function init (opts) {
log('init embedded:chromesockets')

const ipfsOpts = await buildConfig(opts)
log('creating js-ipfs with opts: ', ipfsOpts)
node = new Ipfs(ipfsOpts)

Expand Down Expand Up @@ -95,7 +101,6 @@ async function updateConfigWithHttpEndpoints (ipfs, opts) {
const apiMa = await ipfs.config.get('Addresses.API')
const httpGateway = multiaddr2httpUrl(gwMa)
const httpApi = multiaddr2httpUrl(apiMa)
log(`updating extension configuration to Gateway=${httpGateway} and API=${httpApi}`)
// update ports in JSON configuration for embedded js-ipfs
const ipfsNodeConfig = JSON.parse(localConfig.ipfsNodeConfig)
ipfsNodeConfig.config.Addresses.Gateway = gwMa
Expand All @@ -105,87 +110,44 @@ async function updateConfigWithHttpEndpoints (ipfs, opts) {
ipfsApiUrl: httpApi,
ipfsNodeConfig: JSON.stringify(ipfsNodeConfig, null, 2)
}
// update current runtime config (in place, effective without restart)
// update current runtime config (in place)
Object.assign(opts, configChanges)
// update user config in storage (effective on next run)
// update user config in storage (triggers async client restart if ports changed)
log(`synchronizing ipfsNodeConfig with customGatewayUrl (${configChanges.customGatewayUrl}) and ipfsApiUrl (${configChanges.ipfsApiUrl})`)
await browser.storage.local.set(configChanges)
}
}

exports.destroy = async function () {
log('destroy: embedded:chromesockets')

/*
if (httpServer) {
httpServer.close()
httpServer = null
}
if (hapiServer) {
try {
await hapiServer.stop({ timeout: 1000 })
} catch (err) {
if (err) {
console.error(`[ipfs-companion] failed to stop hapi`, err)
} else {
console.log('[ipfs-companion] hapi server stopped')
}
}
hapiServer = null
}
*/

if (nodeHttpApi) {
try {
await nodeHttpApi.stop()
} catch (err) {
log.error('failed to stop HttpApi', err)
// TODO: needs upstream fix like https://github.com/ipfs/js-ipfs/issues/2257
if (err.message !== 'Cannot stop server while in stopping phase') {
log.error('failed to stop HttpApi', err)
}
}
nodeHttpApi = null
}
if (node) {
await node.stop()
node = null
}
}

/*
// Quick smoke-test to confirm require('http') works for MVP
function startRawHttpServer (port) {
const http = require('http') // courtesy of chrome-net
const httpServer = http.createServer(function (req, res) {
res.writeHead(200, { 'Content-Type': 'text/plain' })
res.end('Hello from ipfs-companion exposing HTTP via chrome.sockets in Brave :-)\n')
})
httpServer.listen(port, '127.0.0.1')
console.log(`[ipfs-companion] require('http') HTTP server on http://127.0.0.1:${port}`)
return httpServer
}

function startRawHapiServer (port) {
let options = {
host: '127.0.0.1',
port,
debug: {
log: ['*'],
request: ['*']
}
}
const initHapi = async () => {
// hapi v18 (js-ipfs >=v0.35.0-pre.0)
const Hapi = require('@hapi/hapi') // courtesy of js-ipfs
const hapiServer = new Hapi.Server(options)
await hapiServer.route({
method: 'GET',
path: '/',
handler: (request, h) => {
console.log('[ipfs-companion] hapiServer processing request', request)
return 'Hello from ipfs-companion+Hapi.js exposing HTTP via chrome.sockets in Brave :-)'
}
const stopped = new Promise((resolve, reject) => {
node.on('stop', resolve)
node.on('error', reject)
})
await hapiServer.start()
console.log(`[ipfs-companion] require('@hapi/hapi') HTTP server running at: ${hapiServer.info.uri}`)
try {
await node.stop()
} catch (err) {
// TODO: remove when fixed upstream: https://github.com/ipfs/js-ipfs/issues/2257
if (err.message === 'Not able to stop from state: stopping') {
log('destroy: embedded:chromesockets waiting for node.stop()')
await stopped
} else {
throw err
}
}
node = null
}
initHapi()
return hapiServer
}
*/
20 changes: 20 additions & 0 deletions add-on/src/lib/ipfs-companion.js
Original file line number Diff line number Diff line change
Expand Up @@ -616,6 +616,26 @@ module.exports = async function init () {
shouldStopIpfsClient = !state.active
break
case 'ipfsNodeType':
// Switching between External and Embeedded HTTP Gateway in Brave is tricky.
// For now we remove user confusion by persisting and restoring the External config.
// TODO: refactor as a part of https://github.com/ipfs-shipyard/ipfs-companion/issues/491
if (change.oldValue === 'external' && change.newValue === 'embedded:chromesockets') {
const oldGatewayUrl = (await browser.storage.local.get('customGatewayUrl')).customGatewayUrl
const oldApiUrl = (await browser.storage.local.get('ipfsApiUrl')).ipfsApiUrl
log(`storing externalNodeConfig: ipfsApiUrl=${oldApiUrl}, customGatewayUrl=${oldGatewayUrl}"`)
await browser.storage.local.set({ externalNodeConfig: [oldGatewayUrl, oldApiUrl] })
} else if (change.oldValue === 'embedded:chromesockets' && change.newValue === 'external') {
const [oldGatewayUrl, oldApiUrl] = (await browser.storage.local.get('externalNodeConfig')).externalNodeConfig
log(`restoring externalNodeConfig: ipfsApiUrl=${oldApiUrl}, customGatewayUrl=${oldGatewayUrl}"`)
await browser.storage.local.set({
ipfsApiUrl: oldApiUrl,
customGatewayUrl: oldGatewayUrl,
externalNodeConfig: null
})
}
shouldRestartIpfsClient = true
state[key] = change.newValue
break
case 'ipfsNodeConfig':
shouldRestartIpfsClient = true
state[key] = change.newValue
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@
"drag-and-drop-files": "0.0.1",
"file-type": "12.0.1",
"filesize": "4.1.2",
"get-port": "5.0.0",
"http-dns": "3.0.1",
"http-node": "1.2.0",
"ipfs": "https://github.com/ipfs/js-ipfs/tarball/2ae6b672c222555b1a068141f2acfe4b5f39b709/js-ipfs.tar.gz",
Expand Down
7 changes: 7 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -5786,6 +5786,13 @@ get-iterator@^1.0.2:
resolved "https://registry.yarnpkg.com/get-iterator/-/get-iterator-1.0.2.tgz#cd747c02b4c084461fac14f48f6b45a80ed25c82"
integrity sha512-v+dm9bNVfOYsY1OrhaCrmyOcYoSeVvbt+hHZ0Au+T+p1y+0Uyj9aMaGIeUTT6xdpRbWzDeYKvfOslPhggQMcsg==

get-port@5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/get-port/-/get-port-5.0.0.tgz#aa22b6b86fd926dd7884de3e23332c9f70c031a6"
integrity sha512-imzMU0FjsZqNa6BqOjbbW6w5BivHIuQKopjpPqcnx0AVHJQKCxK1O+Ab3OrVXhrekqfVMjwA9ZYu062R+KcIsQ==
dependencies:
type-fest "^0.3.0"

get-port@^4.0.0:
version "4.2.0"
resolved "https://registry.yarnpkg.com/get-port/-/get-port-4.2.0.tgz#e37368b1e863b7629c43c5a323625f95cf24b119"
Expand Down