Skip to content

Commit

Permalink
feat: add a factory to start/stop nodes from node and browser
Browse files Browse the repository at this point in the history
  • Loading branch information
dryajov committed Nov 22, 2017
1 parent 6b04506 commit 7198198
Show file tree
Hide file tree
Showing 12 changed files with 394 additions and 4 deletions.
12 changes: 12 additions & 0 deletions .aegir.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
'use strict'

const tasks = require('./src/remote-factory/tasks')

module.exports = {
hooks: {
browser: {
pre: tasks.start,
post: tasks.stop
}
}
}
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,5 @@ node_modules

dist
docs

.idea
21 changes: 21 additions & 0 deletions src/factory/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
'use strict'

const isNode = require('detect-node')
const Local = require('./local-factory')
const Remote = require('./remote-factory')

class Factory {
constructor (opts) {
this.factory = isNode ? new Local() : new Remote(opts)
}

spawnNode (repoPath, opts, callback) {
this.factory.spawnNode(repoPath, opts, callback)
}

dismantle (callback) {
this.factory.dismantle(callback)
}
}

module.exports = Factory
39 changes: 39 additions & 0 deletions src/factory/local-factory/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
'use strict'

const IpfsdCtl = require('../..')
const each = require('async/each')
const waterfall = require('async/waterfall')

class Factory {
constructor () {
this.nodes = []
}

/* yields a new started node */
spawnNode (repoPath, opts, callback) {
if (typeof repoPath === 'function') {
callback = repoPath
repoPath = null
}
if (typeof opts === 'function') {
callback = opts
opts = {}
}

opts = Object.assign({}, opts, { repoPath })

waterfall([
(cb) => IpfsdCtl.disposable(opts, cb),
(node, cb) => {
this.nodes.push(node)
node.startDaemon(['--enable-pubsub-experiment'], cb)
}
], callback)
}

dismantle (callback) {
each(this.nodes, (node, cb) => node.stopDaemon(cb), callback)
}
}

module.exports = Factory
68 changes: 68 additions & 0 deletions src/factory/remote-factory/client.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
'use strict'

const io = require('socket.io-client')
const IpfsApi = require('ipfs-api')

const { toPayload, toRes } = require('./utils')

class Client {
constructor (sio) {
this._sio = sio
}

_request () {
const action = Array.from(arguments).shift()
const args = Array.from(arguments).splice(1, arguments.length - 2)
const cb = Array.from(arguments).splice(arguments.length - 1, 1).shift()
this._sio.on(action, (data) => toRes(data, cb))
this._sio.emit(action, toPayload(args))
}

start (opts, cb) {
if (typeof opts === 'function') {
cb = opts
opts = {}
}

this._request('start', opts, (err, api) => {
if (err) {
return cb(err)
}

cb(null, new IpfsApi(api.apiAddr))
})
}

stop (nodes, cb) {
if (typeof nodes === 'function') {
cb = nodes
nodes = undefined
}

cb = cb || (() => {})
this._request('stop', nodes, cb)
}
}

function createClient (options, callback) {
if (typeof options === 'function') {
callback = options
options = {}
}

callback = callback || (() => {})
options = options || {}
const url = options.url ? (delete options.url) : 'http://localhost:55155'
options = Object.assign({}, options, {
transports: ['websocket'],
'force new connection': true
})

const sio = io.connect(url, options)
sio.once('connect_error', (err) => { throw err })
sio.once('connect', () => {
callback(null, new Client(sio))
})
}

module.exports = createClient
49 changes: 49 additions & 0 deletions src/factory/remote-factory/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
'use strict'

const createClient = require('./client')

class Factory {
constructor (options) {
this._options = options || {}
this._client = null
}

_spawn (repoPath, opts, callback) {
if (typeof repoPath === 'function') {
callback = repoPath
repoPath = null
}
if (typeof opts === 'function') {
callback = opts
opts = {}
}

opts = Object.assign({}, opts, { repoPath })
this._client.start(opts, callback)
}

spawnNode (repoPath, opts, callback) {
if (!this._client) {
return createClient(this._options, (err, cl) => {
if (err) {
return callback(err)
}

this._client = cl
return this._spawn(repoPath, opts, callback)
})
}

return this._spawn(repoPath, opts, callback)
}

dismantle (callback) {
if (!this._client) {
return callback()
}

this._client.stop(callback)
}
}

module.exports = Factory
82 changes: 82 additions & 0 deletions src/factory/remote-factory/routes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
'use strict'

const IpfsdCtl = require('..')
const SocketIO = require('socket.io')
const each = require('async/each')
const waterfall = require('async/waterfall')
const eachSeries = require('async/eachSeries')
const series = require('async/series')
const parsePayload = require('./utils').parsePayload

const nodes = new Map()

function start (opts, callback) {
if (typeof opts === 'function') {
callback = opts
opts = null
}

let node = null
waterfall([
(cb) => IpfsdCtl.disposable(opts, cb),
(n, cb) => {
node = n
series([
(pCb) => {
const configValues = {
Bootstrap: [],
Discovery: {},
'API.HTTPHeaders.Access-Control-Allow-Origin': ['*'],
'API.HTTPHeaders.Access-Control-Allow-Methods': [
'PUT',
'POST',
'GET'
]
}
eachSeries(Object.keys(configValues), (configKey, cb) => {
const configVal = JSON.stringify(configValues[configKey])
node.setConfig(configKey, configVal, cb)
}, pCb)
},
(pCb) => node.startDaemon(['--enable-pubsub-experiment'], cb)
], cb)
},
(api, cb) => api.id(cb),
(id, cb) => cb(null, nodes.set(id.id, node))
], (err) => {
callback(err, {
apiAddr: node.apiAddr.toString()
})
})
}

function stop (node, callback) {
if (typeof node === 'function') {
callback = node
node = undefined
}

if (node) {
return nodes.get(node).stopDaemon(callback)
}

each(nodes, (node, cb) => node[1].stopDaemon(cb), callback)
}

module.exports = (http) => {
const io = new SocketIO(http.listener)
io.on('connection', handle)

function handle (socket) {
const response = (action) => (err, data) => {
if (err) {
return socket.emit(action, JSON.stringify({ err }))
}

socket.emit(action, JSON.stringify({ data }))
}

socket.on('start', (data) => start.apply(null, parsePayload(data).concat(response('start'))))
socket.on('stop', (data) => stop.apply(null, parsePayload(data).concat(response('stop'))))
}
}
31 changes: 31 additions & 0 deletions src/factory/remote-factory/server.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
'use strict'

const Hapi = require('hapi')
const routes = require('./routes')

const port = Number(process.env.PORT) || 55155
const options = {
connections: {
routes: {
cors: true
}
}
}

function server (callback) {
const http = new Hapi.Server(options)

http.connection({ port: port })

http.start((err) => {
if (err) {
return callback(err)
}

routes(http)

callback(null, http)
})
}

module.exports = server
19 changes: 19 additions & 0 deletions src/factory/remote-factory/tasks.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
'use strict'

const factoryServer = require('./server')

let factory
module.exports = {
start (done) {
factoryServer((err, http) => {
if (err) {
return done(err)
}
factory = http
done()
})
},
stop (done) {
factory.stop(done)
}
}
21 changes: 21 additions & 0 deletions src/factory/remote-factory/utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
'use strict'

exports.toPayload = (args) => JSON.stringify({ args })

exports.toRes = (payload, cb) => {
payload = JSON.parse(payload)
if (payload.err) {
return cb(payload.err)
}

return cb(null, payload.data)
}

exports.parsePayload = (data) => {
const args = JSON.parse(data).args
if (!Array.isArray(args)) {
throw new Error('args field should be an array')
}

return args
}
Loading

0 comments on commit 7198198

Please sign in to comment.