Skip to content

Commit 7198198

Browse files
committed
feat: add a factory to start/stop nodes from node and browser
1 parent 6b04506 commit 7198198

File tree

12 files changed

+394
-4
lines changed

12 files changed

+394
-4
lines changed

.aegir.js

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
'use strict'
2+
3+
const tasks = require('./src/remote-factory/tasks')
4+
5+
module.exports = {
6+
hooks: {
7+
browser: {
8+
pre: tasks.start,
9+
post: tasks.stop
10+
}
11+
}
12+
}

.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -36,3 +36,5 @@ node_modules
3636

3737
dist
3838
docs
39+
40+
.idea

src/factory/index.js

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
'use strict'
2+
3+
const isNode = require('detect-node')
4+
const Local = require('./local-factory')
5+
const Remote = require('./remote-factory')
6+
7+
class Factory {
8+
constructor (opts) {
9+
this.factory = isNode ? new Local() : new Remote(opts)
10+
}
11+
12+
spawnNode (repoPath, opts, callback) {
13+
this.factory.spawnNode(repoPath, opts, callback)
14+
}
15+
16+
dismantle (callback) {
17+
this.factory.dismantle(callback)
18+
}
19+
}
20+
21+
module.exports = Factory

src/factory/local-factory/index.js

+39
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
'use strict'
2+
3+
const IpfsdCtl = require('../..')
4+
const each = require('async/each')
5+
const waterfall = require('async/waterfall')
6+
7+
class Factory {
8+
constructor () {
9+
this.nodes = []
10+
}
11+
12+
/* yields a new started node */
13+
spawnNode (repoPath, opts, callback) {
14+
if (typeof repoPath === 'function') {
15+
callback = repoPath
16+
repoPath = null
17+
}
18+
if (typeof opts === 'function') {
19+
callback = opts
20+
opts = {}
21+
}
22+
23+
opts = Object.assign({}, opts, { repoPath })
24+
25+
waterfall([
26+
(cb) => IpfsdCtl.disposable(opts, cb),
27+
(node, cb) => {
28+
this.nodes.push(node)
29+
node.startDaemon(['--enable-pubsub-experiment'], cb)
30+
}
31+
], callback)
32+
}
33+
34+
dismantle (callback) {
35+
each(this.nodes, (node, cb) => node.stopDaemon(cb), callback)
36+
}
37+
}
38+
39+
module.exports = Factory

src/factory/remote-factory/client.js

+68
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
'use strict'
2+
3+
const io = require('socket.io-client')
4+
const IpfsApi = require('ipfs-api')
5+
6+
const { toPayload, toRes } = require('./utils')
7+
8+
class Client {
9+
constructor (sio) {
10+
this._sio = sio
11+
}
12+
13+
_request () {
14+
const action = Array.from(arguments).shift()
15+
const args = Array.from(arguments).splice(1, arguments.length - 2)
16+
const cb = Array.from(arguments).splice(arguments.length - 1, 1).shift()
17+
this._sio.on(action, (data) => toRes(data, cb))
18+
this._sio.emit(action, toPayload(args))
19+
}
20+
21+
start (opts, cb) {
22+
if (typeof opts === 'function') {
23+
cb = opts
24+
opts = {}
25+
}
26+
27+
this._request('start', opts, (err, api) => {
28+
if (err) {
29+
return cb(err)
30+
}
31+
32+
cb(null, new IpfsApi(api.apiAddr))
33+
})
34+
}
35+
36+
stop (nodes, cb) {
37+
if (typeof nodes === 'function') {
38+
cb = nodes
39+
nodes = undefined
40+
}
41+
42+
cb = cb || (() => {})
43+
this._request('stop', nodes, cb)
44+
}
45+
}
46+
47+
function createClient (options, callback) {
48+
if (typeof options === 'function') {
49+
callback = options
50+
options = {}
51+
}
52+
53+
callback = callback || (() => {})
54+
options = options || {}
55+
const url = options.url ? (delete options.url) : 'http://localhost:55155'
56+
options = Object.assign({}, options, {
57+
transports: ['websocket'],
58+
'force new connection': true
59+
})
60+
61+
const sio = io.connect(url, options)
62+
sio.once('connect_error', (err) => { throw err })
63+
sio.once('connect', () => {
64+
callback(null, new Client(sio))
65+
})
66+
}
67+
68+
module.exports = createClient

src/factory/remote-factory/index.js

+49
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
'use strict'
2+
3+
const createClient = require('./client')
4+
5+
class Factory {
6+
constructor (options) {
7+
this._options = options || {}
8+
this._client = null
9+
}
10+
11+
_spawn (repoPath, opts, callback) {
12+
if (typeof repoPath === 'function') {
13+
callback = repoPath
14+
repoPath = null
15+
}
16+
if (typeof opts === 'function') {
17+
callback = opts
18+
opts = {}
19+
}
20+
21+
opts = Object.assign({}, opts, { repoPath })
22+
this._client.start(opts, callback)
23+
}
24+
25+
spawnNode (repoPath, opts, callback) {
26+
if (!this._client) {
27+
return createClient(this._options, (err, cl) => {
28+
if (err) {
29+
return callback(err)
30+
}
31+
32+
this._client = cl
33+
return this._spawn(repoPath, opts, callback)
34+
})
35+
}
36+
37+
return this._spawn(repoPath, opts, callback)
38+
}
39+
40+
dismantle (callback) {
41+
if (!this._client) {
42+
return callback()
43+
}
44+
45+
this._client.stop(callback)
46+
}
47+
}
48+
49+
module.exports = Factory

src/factory/remote-factory/routes.js

+82
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
'use strict'
2+
3+
const IpfsdCtl = require('..')
4+
const SocketIO = require('socket.io')
5+
const each = require('async/each')
6+
const waterfall = require('async/waterfall')
7+
const eachSeries = require('async/eachSeries')
8+
const series = require('async/series')
9+
const parsePayload = require('./utils').parsePayload
10+
11+
const nodes = new Map()
12+
13+
function start (opts, callback) {
14+
if (typeof opts === 'function') {
15+
callback = opts
16+
opts = null
17+
}
18+
19+
let node = null
20+
waterfall([
21+
(cb) => IpfsdCtl.disposable(opts, cb),
22+
(n, cb) => {
23+
node = n
24+
series([
25+
(pCb) => {
26+
const configValues = {
27+
Bootstrap: [],
28+
Discovery: {},
29+
'API.HTTPHeaders.Access-Control-Allow-Origin': ['*'],
30+
'API.HTTPHeaders.Access-Control-Allow-Methods': [
31+
'PUT',
32+
'POST',
33+
'GET'
34+
]
35+
}
36+
eachSeries(Object.keys(configValues), (configKey, cb) => {
37+
const configVal = JSON.stringify(configValues[configKey])
38+
node.setConfig(configKey, configVal, cb)
39+
}, pCb)
40+
},
41+
(pCb) => node.startDaemon(['--enable-pubsub-experiment'], cb)
42+
], cb)
43+
},
44+
(api, cb) => api.id(cb),
45+
(id, cb) => cb(null, nodes.set(id.id, node))
46+
], (err) => {
47+
callback(err, {
48+
apiAddr: node.apiAddr.toString()
49+
})
50+
})
51+
}
52+
53+
function stop (node, callback) {
54+
if (typeof node === 'function') {
55+
callback = node
56+
node = undefined
57+
}
58+
59+
if (node) {
60+
return nodes.get(node).stopDaemon(callback)
61+
}
62+
63+
each(nodes, (node, cb) => node[1].stopDaemon(cb), callback)
64+
}
65+
66+
module.exports = (http) => {
67+
const io = new SocketIO(http.listener)
68+
io.on('connection', handle)
69+
70+
function handle (socket) {
71+
const response = (action) => (err, data) => {
72+
if (err) {
73+
return socket.emit(action, JSON.stringify({ err }))
74+
}
75+
76+
socket.emit(action, JSON.stringify({ data }))
77+
}
78+
79+
socket.on('start', (data) => start.apply(null, parsePayload(data).concat(response('start'))))
80+
socket.on('stop', (data) => stop.apply(null, parsePayload(data).concat(response('stop'))))
81+
}
82+
}

src/factory/remote-factory/server.js

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
'use strict'
2+
3+
const Hapi = require('hapi')
4+
const routes = require('./routes')
5+
6+
const port = Number(process.env.PORT) || 55155
7+
const options = {
8+
connections: {
9+
routes: {
10+
cors: true
11+
}
12+
}
13+
}
14+
15+
function server (callback) {
16+
const http = new Hapi.Server(options)
17+
18+
http.connection({ port: port })
19+
20+
http.start((err) => {
21+
if (err) {
22+
return callback(err)
23+
}
24+
25+
routes(http)
26+
27+
callback(null, http)
28+
})
29+
}
30+
31+
module.exports = server

src/factory/remote-factory/tasks.js

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
'use strict'
2+
3+
const factoryServer = require('./server')
4+
5+
let factory
6+
module.exports = {
7+
start (done) {
8+
factoryServer((err, http) => {
9+
if (err) {
10+
return done(err)
11+
}
12+
factory = http
13+
done()
14+
})
15+
},
16+
stop (done) {
17+
factory.stop(done)
18+
}
19+
}

src/factory/remote-factory/utils.js

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
'use strict'
2+
3+
exports.toPayload = (args) => JSON.stringify({ args })
4+
5+
exports.toRes = (payload, cb) => {
6+
payload = JSON.parse(payload)
7+
if (payload.err) {
8+
return cb(payload.err)
9+
}
10+
11+
return cb(null, payload.data)
12+
}
13+
14+
exports.parsePayload = (data) => {
15+
const args = JSON.parse(data).args
16+
if (!Array.isArray(args)) {
17+
throw new Error('args field should be an array')
18+
}
19+
20+
return args
21+
}

0 commit comments

Comments
 (0)