mirror of
https://github.com/fluencelabs/js-libp2p
synced 2025-07-13 15:51:34 +00:00
Compare commits
17 Commits
Author | SHA1 | Date | |
---|---|---|---|
10a8ec3f31 | |||
41d202c4ba | |||
a5fd05875c | |||
379febb610 | |||
99873e877b | |||
4e01c094bc | |||
8fcafe2d90 | |||
947eaf166b | |||
1ebf725ac4 | |||
0c543b7180 | |||
beeb36c10c | |||
0acc7e5d72 | |||
9fd94b98a1 | |||
5c3037037a | |||
362217c8da | |||
7733ba5cd7 | |||
52bf826ec6 |
11
.travis.yml
11
.travis.yml
@ -4,25 +4,16 @@ language: node_js
|
||||
|
||||
matrix:
|
||||
include:
|
||||
- node_js: 6
|
||||
- node_js: 'stable'
|
||||
env: CXX=g++-4.8
|
||||
- node_js: 8
|
||||
env: CXX=g++-4.8
|
||||
# - node_js: stable
|
||||
# env: CXX=g++-4.8
|
||||
|
||||
script:
|
||||
- npm run lint
|
||||
- npm run test
|
||||
- npm run coverage
|
||||
|
||||
before_script:
|
||||
- export DISPLAY=:99.0
|
||||
- sh -e /etc/init.d/xvfb start
|
||||
|
||||
after_success:
|
||||
- npm run coverage-publish
|
||||
|
||||
addons:
|
||||
firefox: 'latest'
|
||||
apt:
|
||||
|
30
CHANGELOG.md
30
CHANGELOG.md
@ -1,3 +1,33 @@
|
||||
<a name="0.17.0"></a>
|
||||
# [0.17.0](https://github.com/libp2p/js-libp2p/compare/v0.16.5...v0.17.0) (2018-02-16)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* use correct reference to floodSub ([947eaf1](https://github.com/libp2p/js-libp2p/commit/947eaf1))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add pubsub to libp2p ([0c543b7](https://github.com/libp2p/js-libp2p/commit/0c543b7))
|
||||
|
||||
|
||||
|
||||
<a name="0.16.5"></a>
|
||||
## [0.16.5](https://github.com/libp2p/js-libp2p/compare/v0.16.4...v0.16.5) (2018-02-14)
|
||||
|
||||
|
||||
|
||||
<a name="0.16.4"></a>
|
||||
## [0.16.4](https://github.com/libp2p/js-libp2p/compare/v0.16.3...v0.16.4) (2018-02-09)
|
||||
|
||||
|
||||
|
||||
<a name="0.16.3"></a>
|
||||
## [0.16.3](https://github.com/libp2p/js-libp2p/compare/v0.16.2...v0.16.3) (2018-02-08)
|
||||
|
||||
|
||||
|
||||
<a name="0.16.2"></a>
|
||||
## [0.16.2](https://github.com/libp2p/js-libp2p/compare/v0.16.1...v0.16.2) (2018-02-07)
|
||||
|
||||
|
@ -249,9 +249,13 @@ class Node extends libp2p {
|
||||
|
||||
> PeerInfo instance of the node
|
||||
|
||||
#### `libp2p.pubsub`
|
||||
|
||||
> Same API as IPFS PubSub, defined in the [CORE API Spec](https://github.com/ipfs/interface-ipfs-core/blob/master/SPEC/PUBSUB.md). Just replace `ipfs` by `libp2p` and you are golden.
|
||||
|
||||
---------------------
|
||||
|
||||
`DHT methods exposed`
|
||||
`DHT methods also exposed for the time being`
|
||||
|
||||
#### `libp2p.dht.put(key, value, callback)`
|
||||
|
||||
|
@ -2,6 +2,12 @@ machine:
|
||||
node:
|
||||
version: stable
|
||||
|
||||
test:
|
||||
pre:
|
||||
- npm run lint
|
||||
post:
|
||||
- npm run coverage -- --upload --providers coveralls
|
||||
|
||||
dependencies:
|
||||
pre:
|
||||
- google-chrome --version
|
||||
@ -15,4 +21,4 @@ dependencies:
|
||||
- sudo apt-get install -f
|
||||
- sudo apt-get install --only-upgrade lsb-base
|
||||
- sudo dpkg -i google-chrome.deb
|
||||
- google-chrome --version
|
||||
- google-chrome --version
|
||||
|
@ -6,7 +6,6 @@ const Multiplex = require('libp2p-multiplex')
|
||||
const SECIO = require('libp2p-secio')
|
||||
const PeerInfo = require('peer-info')
|
||||
const MulticastDNS = require('libp2p-mdns')
|
||||
const FloodSub = require('libp2p-floodsub')
|
||||
const waterfall = require('async/waterfall')
|
||||
const parallel = require('async/parallel')
|
||||
const series = require('async/series')
|
||||
@ -19,7 +18,9 @@ class MyBundle extends libp2p {
|
||||
muxer: [Multiplex],
|
||||
crypto: [SECIO]
|
||||
},
|
||||
discovery: [new MulticastDNS(peerInfo, { interval: 2000 })]
|
||||
discovery: [
|
||||
new MulticastDNS(peerInfo, { interval: 2000 })
|
||||
]
|
||||
}
|
||||
super(modules, peerInfo)
|
||||
}
|
||||
@ -47,22 +48,19 @@ parallel([
|
||||
const node1 = nodes[0]
|
||||
const node2 = nodes[1]
|
||||
|
||||
const fs1 = new FloodSub(node1)
|
||||
const fs2 = new FloodSub(node2)
|
||||
|
||||
series([
|
||||
(cb) => fs1.start(cb),
|
||||
(cb) => fs2.start(cb),
|
||||
(cb) => node1.once('peer:discovery', (peer) => node1.dial(peer, cb)),
|
||||
(cb) => setTimeout(cb, 500)
|
||||
], (err) => {
|
||||
if (err) { throw err }
|
||||
|
||||
fs2.on('news', (msg) => console.log(msg.from, msg.data.toString()))
|
||||
fs2.subscribe('news')
|
||||
|
||||
setInterval(() => {
|
||||
fs1.publish('news', Buffer.from('Bird bird bird, bird is the word!'))
|
||||
}, 1000)
|
||||
node1.pubsub.subscribe('news',
|
||||
(msg) => console.log(msg.from, msg.data.toString()),
|
||||
() => {
|
||||
setInterval(() => {
|
||||
node2.pubsub.publish('news', Buffer.from('Bird bird bird, bird is the word!'))
|
||||
}, 1000)
|
||||
}
|
||||
)
|
||||
})
|
||||
})
|
||||
|
@ -1,8 +1,6 @@
|
||||
# Publish Subscribe
|
||||
|
||||
Publish Subscribe is something out of scope for the modular networking stack that is libp2p, however, it is something that is enabled through the primitives that libp2p offers and so it has become one of the most interesting use cases for libp2p.
|
||||
|
||||
Currently, we have a PubSub implementation, [libp2p-floodsub](https://github.com/libp2p/js-libp2p-floodsub) and many more being researched at [research-pubsub](https://github.com/libp2p/research-pubsub).
|
||||
Publish Subscribe is also included on the stack. Currently, we have on PubSub implementation which we ship by default [libp2p-floodsub](https://github.com/libp2p/js-libp2p-floodsub), with many more being researched at [research-pubsub](https://github.com/libp2p/research-pubsub).
|
||||
|
||||
We've seen many interesting use cases appear with this, here are some highlights:
|
||||
|
||||
@ -12,29 +10,22 @@ We've seen many interesting use cases appear with this, here are some highlights
|
||||
|
||||
## 1. Setting up a simple PubSub network on top of libp2p
|
||||
|
||||
For this example, we will use MulticastDNS for automatic Peer Discovery and libp2p-floodsub to give us the PubSub primitives we are looking for. This example is based the previous examples found in [Discovery Mechanisms](../discovery-mechanisms). You can find the complete version at [1.js](./1.js).
|
||||
For this example, we will use MulticastDNS for automatic Peer Discovery. This example is based the previous examples found in [Discovery Mechanisms](../discovery-mechanisms). You can find the complete version at [1.js](./1.js).
|
||||
|
||||
Getting PubSub is super simple, all you have to do is require the FloodSub module and pass it in a libp2p node, once you have done that you can start subscribing and publishing in any topic.
|
||||
Using PubSub is super simple, all you have to do is start a libp2p node, PubSub will be enabled by default.
|
||||
|
||||
```JavaScript
|
||||
const FloodSub = require('libp2p-floodsub')
|
||||
|
||||
const fs1 = new FloodSub(node1)
|
||||
const fs2 = new FloodSub(node2)
|
||||
|
||||
series([
|
||||
(cb) => fs1.start(cb),
|
||||
(cb) => fs2.start(cb),
|
||||
(cb) => node1.once('peer:discovery', (peer) => node1.dial(peer, cb)),
|
||||
(cb) => setTimeout(cb, 500)
|
||||
], (err) => {
|
||||
if (err) { throw err }
|
||||
|
||||
fs2.on('news', (msg) => console.log(msg.from, msg.data.toString()))
|
||||
fs2.subscribe('news')
|
||||
node1.pubsub.on('news', (msg) => console.log(msg.from, msg.data.toString()))
|
||||
node1.pubsub.subscribe('news')
|
||||
|
||||
setInterval(() => {
|
||||
fs1.publish('news', Buffer.from('Bird bird bird, bird is the word!'))
|
||||
node2.pubsub.publish('news', Buffer.from('Bird bird bird, bird is the word!'))
|
||||
}, 1000)
|
||||
})
|
||||
```
|
||||
|
25
package.json
25
package.json
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "libp2p",
|
||||
"version": "0.16.2",
|
||||
"version": "0.17.0",
|
||||
"description": "JavaScript base class for libp2p bundles",
|
||||
"main": "src/index.js",
|
||||
"scripts": {
|
||||
@ -38,40 +38,41 @@
|
||||
"homepage": "https://github.com/libp2p/js-libp2p",
|
||||
"dependencies": {
|
||||
"async": "^2.6.0",
|
||||
"libp2p-ping": "~0.6.0",
|
||||
"libp2p-switch": "~0.36.0",
|
||||
"mafmt": "^3.0.2",
|
||||
"libp2p-floodsub": "^0.14.1",
|
||||
"libp2p-ping": "~0.6.1",
|
||||
"libp2p-switch": "~0.36.1",
|
||||
"mafmt": "^4.0.0",
|
||||
"multiaddr": "^3.0.2",
|
||||
"peer-book": "~0.5.4",
|
||||
"peer-id": "~0.10.5",
|
||||
"peer-id": "~0.10.6",
|
||||
"peer-info": "~0.11.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"aegir": "^12.4.0",
|
||||
"aegir": "^13.0.0",
|
||||
"chai": "^4.1.2",
|
||||
"cids": "~0.5.2",
|
||||
"dirty-chai": "^2.0.1",
|
||||
"electron-webrtc": "~0.3.0",
|
||||
"libp2p-circuit": "~0.1.4",
|
||||
"libp2p-kad-dht": "~0.6.3",
|
||||
"libp2p-kad-dht": "~0.8.0",
|
||||
"libp2p-mdns": "~0.9.2",
|
||||
"libp2p-multiplex": "~0.5.1",
|
||||
"libp2p-railing": "~0.7.1",
|
||||
"libp2p-secio": "~0.9.1",
|
||||
"libp2p-secio": "~0.9.2",
|
||||
"libp2p-spdy": "~0.11.0",
|
||||
"libp2p-tcp": "~0.11.5",
|
||||
"libp2p-webrtc-star": "~0.13.3",
|
||||
"libp2p-websockets": "~0.10.4",
|
||||
"libp2p-websocket-star": "~0.7.2",
|
||||
"libp2p-websocket-star-rendezvous": "~0.2.2",
|
||||
"libp2p-websocket-star": "~0.7.6",
|
||||
"libp2p-websocket-star-rendezvous": "~0.2.3",
|
||||
"lodash.times": "^4.3.2",
|
||||
"pre-commit": "^1.2.2",
|
||||
"pull-goodbye": "0.0.2",
|
||||
"pull-serializer": "~0.3.2",
|
||||
"pull-stream": "^3.6.1",
|
||||
"safe-buffer": "^5.1.1",
|
||||
"sinon": "^4.2.2",
|
||||
"wrtc": "0.0.65"
|
||||
"sinon": "^4.3.0",
|
||||
"wrtc": "0.0.66"
|
||||
},
|
||||
"contributors": [
|
||||
"Chris Bratlien <chrisbratlien@gmail.com>",
|
||||
|
3
src/error-messages.js
Normal file
3
src/error-messages.js
Normal file
@ -0,0 +1,3 @@
|
||||
'use strict'
|
||||
|
||||
exports.NOT_STARTED_YET = 'The libp2p node is not started yet'
|
25
src/index.js
25
src/index.js
@ -14,6 +14,7 @@ const Ping = require('libp2p-ping')
|
||||
const peerRouting = require('./peer-routing')
|
||||
const contentRouting = require('./content-routing')
|
||||
const dht = require('./dht')
|
||||
const pubsub = require('./pubsub')
|
||||
const getPeerInfo = require('./get-peer-info')
|
||||
|
||||
exports = module.exports
|
||||
@ -89,6 +90,7 @@ class Node extends EventEmitter {
|
||||
this.peerRouting = peerRouting(this)
|
||||
this.contentRouting = contentRouting(this)
|
||||
this.dht = dht(this)
|
||||
this.pubsub = pubsub(this)
|
||||
|
||||
this._getPeerInfo = getPeerInfo(this)
|
||||
|
||||
@ -149,17 +151,29 @@ class Node extends EventEmitter {
|
||||
cb()
|
||||
},
|
||||
(cb) => {
|
||||
// TODO: chicken-and-egg problem:
|
||||
// TODO: chicken-and-egg problem #1:
|
||||
// have to set started here because DHT requires libp2p is already started
|
||||
this._isStarted = true
|
||||
if (this._dht) {
|
||||
return this._dht.start(cb)
|
||||
this._dht.start(cb)
|
||||
} else {
|
||||
cb()
|
||||
}
|
||||
cb()
|
||||
},
|
||||
(cb) => {
|
||||
// TODO: chicken-and-egg problem #2:
|
||||
// have to set started here because FloodSub requires libp2p is already started
|
||||
if (this._options !== false) {
|
||||
this._floodSub.start(cb)
|
||||
} else {
|
||||
cb()
|
||||
}
|
||||
},
|
||||
|
||||
(cb) => {
|
||||
// detect which multiaddrs we don't have a transport for and remove them
|
||||
const multiaddrs = this.peerInfo.multiaddrs.toArray()
|
||||
|
||||
transports.forEach((transport) => {
|
||||
multiaddrs.forEach((multiaddr) => {
|
||||
if (!multiaddr.toString().match(/\/p2p-circuit($|\/)/) &&
|
||||
@ -188,6 +202,11 @@ class Node extends EventEmitter {
|
||||
}
|
||||
|
||||
series([
|
||||
(cb) => {
|
||||
if (this._floodSub.started) {
|
||||
this._floodSub.stop(cb)
|
||||
}
|
||||
},
|
||||
(cb) => {
|
||||
if (this._dht) {
|
||||
return this._dht.stop(cb)
|
||||
|
93
src/pubsub.js
Normal file
93
src/pubsub.js
Normal file
@ -0,0 +1,93 @@
|
||||
'use strict'
|
||||
|
||||
const setImmediate = require('async/setImmediate')
|
||||
const NOT_STARTED_YET = require('./error-messages').NOT_STARTED_YET
|
||||
const FloodSub = require('libp2p-floodsub')
|
||||
|
||||
module.exports = (node) => {
|
||||
const floodSub = new FloodSub(node)
|
||||
|
||||
node._floodSub = floodSub
|
||||
|
||||
return {
|
||||
subscribe: (topic, options, handler, callback) => {
|
||||
if (typeof options === 'function') {
|
||||
callback = handler
|
||||
handler = options
|
||||
options = {}
|
||||
}
|
||||
|
||||
if (!node.isStarted() && !floodSub.started) {
|
||||
return setImmediate(() => callback(new Error(NOT_STARTED_YET)))
|
||||
}
|
||||
|
||||
function subscribe (cb) {
|
||||
if (floodSub.listenerCount(topic) === 0) {
|
||||
floodSub.subscribe(topic)
|
||||
}
|
||||
|
||||
floodSub.on(topic, handler)
|
||||
setImmediate(cb)
|
||||
}
|
||||
|
||||
subscribe(callback)
|
||||
},
|
||||
|
||||
unsubscribe: (topic, handler) => {
|
||||
if (!node.isStarted() && !floodSub.started) {
|
||||
throw new Error(NOT_STARTED_YET)
|
||||
}
|
||||
|
||||
floodSub.removeListener(topic, handler)
|
||||
|
||||
if (floodSub.listenerCount(topic) === 0) {
|
||||
floodSub.unsubscribe(topic)
|
||||
}
|
||||
},
|
||||
|
||||
publish: (topic, data, callback) => {
|
||||
if (!node.isStarted() && !floodSub.started) {
|
||||
return setImmediate(() => callback(new Error(NOT_STARTED_YET)))
|
||||
}
|
||||
|
||||
if (!Buffer.isBuffer(data)) {
|
||||
return setImmediate(() => callback(new Error('data must be a Buffer')))
|
||||
}
|
||||
|
||||
floodSub.publish(topic, data)
|
||||
|
||||
setImmediate(() => callback())
|
||||
},
|
||||
|
||||
ls: (callback) => {
|
||||
if (!node.isStarted() && !floodSub.started) {
|
||||
return setImmediate(() => callback(new Error(NOT_STARTED_YET)))
|
||||
}
|
||||
|
||||
const subscriptions = Array.from(floodSub.subscriptions)
|
||||
|
||||
setImmediate(() => callback(null, subscriptions))
|
||||
},
|
||||
|
||||
peers: (topic, callback) => {
|
||||
if (!node.isStarted() && !floodSub.started) {
|
||||
return setImmediate(() => callback(new Error(NOT_STARTED_YET)))
|
||||
}
|
||||
|
||||
if (typeof topic === 'function') {
|
||||
callback = topic
|
||||
topic = null
|
||||
}
|
||||
|
||||
const peers = Array.from(floodSub.peers.values())
|
||||
.filter((peer) => topic ? peer.topics.has(topic) : true)
|
||||
.map((peer) => peer.info.id.toB58String())
|
||||
|
||||
setImmediate(() => callback(null, peers))
|
||||
},
|
||||
|
||||
setMaxListeners (n) {
|
||||
return floodSub.setMaxListeners(n)
|
||||
}
|
||||
}
|
||||
}
|
@ -4,6 +4,7 @@ require('./base')
|
||||
require('./transports.node')
|
||||
require('./stream-muxing.node')
|
||||
require('./peer-discovery.node')
|
||||
require('./pubsub.node')
|
||||
require('./peer-routing.node')
|
||||
require('./content-routing.node')
|
||||
require('./circuit-relay.node')
|
||||
|
87
test/pubsub.node.js
Normal file
87
test/pubsub.node.js
Normal file
@ -0,0 +1,87 @@
|
||||
/* eslint-env mocha */
|
||||
/* eslint max-nested-callbacks: ["error", 8] */
|
||||
|
||||
'use strict'
|
||||
|
||||
const chai = require('chai')
|
||||
chai.use(require('dirty-chai'))
|
||||
const expect = chai.expect
|
||||
const parallel = require('async/parallel')
|
||||
const waterfall = require('async/waterfall')
|
||||
const _times = require('lodash.times')
|
||||
const utils = require('./utils/node')
|
||||
const createNode = utils.createNode
|
||||
|
||||
function startTwo (callback) {
|
||||
const tasks = _times(2, () => (cb) => {
|
||||
createNode('/ip4/0.0.0.0/tcp/0', {
|
||||
mdns: false
|
||||
}, (err, node) => {
|
||||
expect(err).to.not.exist()
|
||||
node.start((err) => cb(err, node))
|
||||
})
|
||||
})
|
||||
|
||||
parallel(tasks, (err, nodes) => {
|
||||
expect(err).to.not.exist()
|
||||
|
||||
nodes[0].dial(nodes[1].peerInfo, (err) => callback(err, nodes))
|
||||
})
|
||||
}
|
||||
|
||||
function stopTwo (nodes, callback) {
|
||||
parallel([
|
||||
(cb) => nodes[0].stop(cb),
|
||||
(cb) => nodes[1].stop(cb)
|
||||
], callback)
|
||||
}
|
||||
|
||||
// There is a vast test suite on PubSub through js-ipfs
|
||||
// https://github.com/ipfs/interface-ipfs-core/blob/master/js/src/pubsub.js
|
||||
// and libp2p-floodsub itself
|
||||
// https://github.com/libp2p/js-libp2p-floodsub/tree/master/test
|
||||
// TODO: consider if all or some of those should come here
|
||||
describe('.pubsub', () => {
|
||||
describe('.pubsub on (default)', (done) => {
|
||||
it('start two nodes and send one message', (done) => {
|
||||
waterfall([
|
||||
(cb) => startTwo(cb),
|
||||
(nodes, cb) => {
|
||||
const data = Buffer.from('test')
|
||||
nodes[0].pubsub.subscribe('pubsub',
|
||||
(msg) => {
|
||||
expect(msg.data).to.eql(data)
|
||||
cb(null, nodes)
|
||||
},
|
||||
(err) => {
|
||||
expect(err).to.not.exist()
|
||||
setTimeout(() => nodes[1].pubsub.publish('pubsub', data, (err) => {
|
||||
expect(err).to.not.exist()
|
||||
}), 500)
|
||||
}
|
||||
)
|
||||
},
|
||||
(nodes, cb) => stopTwo(nodes, cb)
|
||||
], done)
|
||||
})
|
||||
})
|
||||
|
||||
describe('.pubsub off', () => {
|
||||
it('fail to use pubsub if disabled', (done) => {
|
||||
createNode('/ip4/0.0.0.0/tcp/0', {
|
||||
mdns: false,
|
||||
pubsub: false
|
||||
}, (err, node) => {
|
||||
expect(err).to.not.exist()
|
||||
|
||||
node.pubsub.subscribe('news',
|
||||
(msg) => {},
|
||||
(err) => {
|
||||
expect(err).to.exist()
|
||||
done()
|
||||
}
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
@ -22,7 +22,7 @@ function createNode (multiaddrs, options, callback) {
|
||||
}
|
||||
|
||||
waterfall([
|
||||
(cb) => PeerId.create({ bits: 1024 }, cb),
|
||||
(cb) => PeerId.create({ bits: 512 }, cb),
|
||||
(peerId, cb) => PeerInfo.create(peerId, cb),
|
||||
(peerInfo, cb) => {
|
||||
multiaddrs.map((ma) => peerInfo.multiaddrs.add(ma))
|
||||
|
Reference in New Issue
Block a user