Compare commits

...

23 Commits

Author SHA1 Message Date
2803e64969 chore: release version v0.1.7 2019-12-15 16:57:40 +01:00
e979bc9d4e chore: update contributors 2019-12-15 16:57:39 +01:00
bdbd58e897 feat: export connection status' (#15)
* feat: export the connection status'

* docs(fix): correct status listing
2019-12-15 16:54:43 +01:00
79bfcacb61 chore: add lead maintainer property (#14) 2019-12-12 13:05:12 +01:00
148f3c9f43 docs: corrected reference to delegated content routing (#13) 2019-12-04 17:19:16 +01:00
6641a5b0b4 chore: release version v0.1.6 2019-12-02 16:09:12 +01:00
34372e07ce chore: update contributors 2019-12-02 16:09:12 +01:00
d5dd256a21 fix: multicodec topology disconnect with peer param (#12) 2019-12-02 16:06:26 +01:00
3f6c1cbee1 chore: release version v0.1.5 2019-11-15 14:51:53 +01:00
64c79d92e0 chore: update contributors 2019-11-15 14:51:52 +01:00
a67abccabf feat: add class-is to topology (#11) 2019-11-15 14:49:39 +01:00
21d8ae6d96 fix: multicodec topology update peers with multicodec (#10) 2019-11-15 14:48:43 +01:00
5969270ce6 nit :) 2019-11-14 12:37:20 +00:00
432da545b4 chore: release version v0.1.4 2019-11-14 11:54:38 +01:00
b00bcd453d chore: update contributors 2019-11-14 11:54:37 +01:00
8bee747c7c feat: add topology interfaces (#7)
* feat: topology

* feat: multicodec-topology

* chore: address review

Co-Authored-By: Jacob Heun <jacobheun@gmail.com>

* chore: remove error from disconnect

* docs: topology

* chore: apply suggestions from code review

Co-Authored-By: Jacob Heun <jacobheun@gmail.com>
2019-11-14 11:52:06 +01:00
b960f29757 chore: release version v0.1.3 2019-10-30 17:10:37 +01:00
5206862504 chore: update contributors 2019-10-30 17:10:37 +01:00
749a8d035d fix: localAddr should be optional (#6)
The local address of a connection will not always be known, such as a browser client, so it should not be required.
2019-10-30 16:56:28 +01:00
763187beb1 chore: release version v0.1.2 2019-10-29 12:15:58 +01:00
44e08c9007 chore: update contributors 2019-10-29 12:15:57 +01:00
d2fe2d1b36 feat: crypto errors (#4)
* chore: ignore docs folder

* feat: add invalid crypto exchange error
2019-10-29 12:12:09 +01:00
bcb52ae709 docs: add crypto to readme (#5) 2019-10-29 11:24:28 +01:00
19 changed files with 619 additions and 20 deletions

1
.gitignore vendored
View File

@ -6,6 +6,7 @@
build build
dist dist
docs
# Dependency directory # Dependency directory
# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git

View File

@ -1,3 +1,68 @@
<a name="0.1.7"></a>
## [0.1.7](https://github.com/libp2p/js-interfaces/compare/v0.1.6...v0.1.7) (2019-12-15)
### Features
* export connection status' ([#15](https://github.com/libp2p/js-interfaces/issues/15)) ([bdbd58e](https://github.com/libp2p/js-interfaces/commit/bdbd58e))
<a name="0.1.6"></a>
## [0.1.6](https://github.com/libp2p/js-interfaces/compare/v0.1.5...v0.1.6) (2019-12-02)
### Bug Fixes
* multicodec topology disconnect with peer param ([#12](https://github.com/libp2p/js-interfaces/issues/12)) ([d5dd256](https://github.com/libp2p/js-interfaces/commit/d5dd256))
<a name="0.1.5"></a>
## [0.1.5](https://github.com/libp2p/js-interfaces/compare/v0.1.4...v0.1.5) (2019-11-15)
### Bug Fixes
* multicodec topology update peers with multicodec ([#10](https://github.com/libp2p/js-interfaces/issues/10)) ([21d8ae6](https://github.com/libp2p/js-interfaces/commit/21d8ae6))
### Features
* add class-is to topology ([#11](https://github.com/libp2p/js-interfaces/issues/11)) ([a67abcc](https://github.com/libp2p/js-interfaces/commit/a67abcc))
<a name="0.1.4"></a>
## [0.1.4](https://github.com/libp2p/js-interfaces/compare/v0.1.3...v0.1.4) (2019-11-14)
### Features
* add topology interfaces ([#7](https://github.com/libp2p/js-interfaces/issues/7)) ([8bee747](https://github.com/libp2p/js-interfaces/commit/8bee747))
<a name="0.1.3"></a>
## [0.1.3](https://github.com/libp2p/js-interfaces/compare/v0.1.2...v0.1.3) (2019-10-30)
### Bug Fixes
* localAddr should be optional ([#6](https://github.com/libp2p/js-interfaces/issues/6)) ([749a8d0](https://github.com/libp2p/js-interfaces/commit/749a8d0))
<a name="0.1.2"></a>
## [0.1.2](https://github.com/libp2p/js-interfaces/compare/v0.1.1...v0.1.2) (2019-10-29)
### Features
* crypto errors ([#4](https://github.com/libp2p/js-interfaces/issues/4)) ([d2fe2d1](https://github.com/libp2p/js-interfaces/commit/d2fe2d1))
<a name="0.1.1"></a> <a name="0.1.1"></a>
## [0.1.1](https://github.com/libp2p/js-interfaces/compare/v0.1.0...v0.1.1) (2019-10-21) ## [0.1.1](https://github.com/libp2p/js-interfaces/compare/v0.1.0...v0.1.1) (2019-10-21)

View File

@ -1,4 +1,4 @@
# JS Libp2p Interfaces # JS libp2p Interfaces
[![](https://img.shields.io/badge/made%20by-Protocol%20Labs-blue.svg?style=flat-square)](http://protocol.ai) [![](https://img.shields.io/badge/made%20by-Protocol%20Labs-blue.svg?style=flat-square)](http://protocol.ai)
[![](https://img.shields.io/badge/project-libp2p-yellow.svg?style=flat-square)](http://libp2p.io/) [![](https://img.shields.io/badge/project-libp2p-yellow.svg?style=flat-square)](http://libp2p.io/)
@ -15,9 +15,11 @@
- [Connection](./src/connection) - [Connection](./src/connection)
- [Content Routing](./src/content-routing) - [Content Routing](./src/content-routing)
- [Crypto](./src/crypto)
- [Peer Discovery](./src/peer-discovery) - [Peer Discovery](./src/peer-discovery)
- [Peer Routing](./src/peer-routing) - [Peer Routing](./src/peer-routing)
- [Stream Muxer](./src/stream-muxer) - [Stream Muxer](./src/stream-muxer)
- [Topology](./src/topology)
- [Transport](./src/transport) - [Transport](./src/transport)
### Origin Repositories ### Origin Repositories

View File

@ -1,7 +1,8 @@
{ {
"name": "libp2p-interfaces", "name": "libp2p-interfaces",
"version": "0.1.1", "version": "0.1.7",
"description": "Interfaces for JS Libp2p", "description": "Interfaces for JS Libp2p",
"leadMaintainer": "Jacob Heun <jacobheun@gmail.com>",
"main": "src/index.js", "main": "src/index.js",
"files": [ "files": [
"src", "src",

View File

@ -109,7 +109,7 @@ const conn = new Connection({
Creates a new Connection instance. Creates a new Connection instance.
`localAddr` is the [multiaddr](https://github.com/multiformats/multiaddr) address used by the local peer to reach the remote. `localAddr` is the optional [multiaddr](https://github.com/multiformats/multiaddr) address used by the local peer to reach the remote.
`remoteAddr` is the [multiaddr](https://github.com/multiformats/multiaddr) address used to communicate with the remote peer. `remoteAddr` is the [multiaddr](https://github.com/multiformats/multiaddr) address used to communicate with the remote peer.
`localPeer` is the [PeerId](https://github.com/libp2p/js-peer-id) of the local peer. `localPeer` is the [PeerId](https://github.com/libp2p/js-peer-id) of the local peer.
`remotePeer` is the [PeerId](https://github.com/libp2p/js-peer-id) of the remote peer. `remotePeer` is the [PeerId](https://github.com/libp2p/js-peer-id) of the remote peer.
@ -121,6 +121,7 @@ Creates a new Connection instance.
- `timeline` is an `object` with the relevant events timestamps of the connection (`open`, `upgraded` and `closed`; the `closed` will be added when the connection is closed). - `timeline` is an `object` with the relevant events timestamps of the connection (`open`, `upgraded` and `closed`; the `closed` will be added when the connection is closed).
- `multiplexer` is a `string` with the connection multiplexing codec (optional). - `multiplexer` is a `string` with the connection multiplexing codec (optional).
- `encryption` is a `string` with the connection encryption method identifier (optional). - `encryption` is a `string` with the connection encryption method identifier (optional).
- `status` is a `string` indicating the overall status of the connection. It is one of [`'open'`, `'closing'`, `'closed'`]
#### Create a new stream #### Create a new stream
@ -220,7 +221,17 @@ This getter returns an `Object` with the metadata of the connection, as follows:
- `status`: - `status`:
This property contains the status of the connection. It can be either `open`, `closing` or `closed`. Once the connection is created it is in an `open` status. When a `conn.close()` happens, the status will change to `closing` and finally, after all the connection streams are properly closed, the status will be `closed`. This property contains the status of the connection. It can be either `open`, `closing` or `closed`. Once the connection is created it is in an `open` status. When a `conn.close()` happens, the status will change to `closing` and finally, after all the connection streams are properly closed, the status will be `closed`. These values can also be directly referenced by importing the `status` file:
```js
const {
OPEN, CLOSING, CLOSED
} = require('libp2p-interfaces/src/connection/status')
if (connection.stat.status === OPEN) {
// ...
}
```
- `timeline`: - `timeline`:

View File

@ -7,6 +7,7 @@ const withIs = require('class-is')
const assert = require('assert') const assert = require('assert')
const errCode = require('err-code') const errCode = require('err-code')
const Status = require('./status')
/** /**
* An implementation of the js-libp2p connection. * An implementation of the js-libp2p connection.
@ -16,7 +17,7 @@ class Connection {
/** /**
* Creates an instance of Connection. * Creates an instance of Connection.
* @param {object} properties properties of the connection. * @param {object} properties properties of the connection.
* @param {multiaddr} properties.localAddr local multiaddr of the connection. * @param {multiaddr} [properties.localAddr] local multiaddr of the connection if known.
* @param {multiaddr} properties.remoteAddr remote multiaddr of the connection. * @param {multiaddr} properties.remoteAddr remote multiaddr of the connection.
* @param {PeerId} properties.localPeer local peer-id. * @param {PeerId} properties.localPeer local peer-id.
* @param {PeerId} properties.remotePeer remote peer-id. * @param {PeerId} properties.remotePeer remote peer-id.
@ -32,7 +33,7 @@ class Connection {
* @param {string} [properties.stat.encryption] connection encryption method identifier. * @param {string} [properties.stat.encryption] connection encryption method identifier.
*/ */
constructor ({ localAddr, remoteAddr, localPeer, remotePeer, newStream, close, getStreams, stat }) { constructor ({ localAddr, remoteAddr, localPeer, remotePeer, newStream, close, getStreams, stat }) {
assert(multiaddr.isMultiaddr(localAddr), 'localAddr must be an instance of multiaddr') localAddr && assert(multiaddr.isMultiaddr(localAddr), 'localAddr must be an instance of multiaddr')
assert(multiaddr.isMultiaddr(remoteAddr), 'remoteAddr must be an instance of multiaddr') assert(multiaddr.isMultiaddr(remoteAddr), 'remoteAddr must be an instance of multiaddr')
assert(PeerId.isPeerId(localPeer), 'localPeer must be an instance of peer-id') assert(PeerId.isPeerId(localPeer), 'localPeer must be an instance of peer-id')
assert(PeerId.isPeerId(remotePeer), 'remotePeer must be an instance of peer-id') assert(PeerId.isPeerId(remotePeer), 'remotePeer must be an instance of peer-id')
@ -75,7 +76,7 @@ class Connection {
*/ */
this._stat = { this._stat = {
...stat, ...stat,
status: 'open' status: Status.OPEN
} }
/** /**
@ -126,11 +127,11 @@ class Connection {
* @return {Promise<object>} with muxed+multistream-selected stream and selected protocol * @return {Promise<object>} with muxed+multistream-selected stream and selected protocol
*/ */
async newStream (protocols) { async newStream (protocols) {
if (this.stat.status === 'closing') { if (this.stat.status === Status.CLOSING) {
throw errCode(new Error('the connection is being closed'), 'ERR_CONNECTION_BEING_CLOSED') throw errCode(new Error('the connection is being closed'), 'ERR_CONNECTION_BEING_CLOSED')
} }
if (this.stat.status === 'closed') { if (this.stat.status === Status.CLOSED) {
throw errCode(new Error('the connection is closed'), 'ERR_CONNECTION_CLOSED') throw errCode(new Error('the connection is closed'), 'ERR_CONNECTION_CLOSED')
} }
@ -175,7 +176,7 @@ class Connection {
* @return {Promise} * @return {Promise}
*/ */
async close () { async close () {
if (this.stat.status === 'closed') { if (this.stat.status === Status.CLOSED) {
return return
} }
@ -183,13 +184,13 @@ class Connection {
return this._closing return this._closing
} }
this.stat.status = 'closing' this.stat.status = Status.CLOSING
// Close raw connection // Close raw connection
this._closing = await this._close() this._closing = await this._close()
this._stat.timeline.close = Date.now() this._stat.timeline.close = Date.now()
this.stat.status = 'closed' this.stat.status = Status.CLOSED
} }
} }

7
src/connection/status.js Normal file
View File

@ -0,0 +1,7 @@
'use strict'
module.exports = {
OPEN: 'open',
CLOSING: 'closing',
CLOSED: 'closed'
}

View File

@ -6,6 +6,7 @@ const chai = require('chai')
const expect = chai.expect const expect = chai.expect
chai.use(require('dirty-chai')) chai.use(require('dirty-chai'))
const sinon = require('sinon') const sinon = require('sinon')
const Status = require('../status')
module.exports = (test) => { module.exports = (test) => {
describe('connection', () => { describe('connection', () => {
@ -28,7 +29,7 @@ module.exports = (test) => {
expect(connection.remotePeer).to.exist() expect(connection.remotePeer).to.exist()
expect(connection.localAddr).to.exist() expect(connection.localAddr).to.exist()
expect(connection.remoteAddr).to.exist() expect(connection.remoteAddr).to.exist()
expect(connection.stat.status).to.equal('open') expect(connection.stat.status).to.equal(Status.OPEN)
expect(connection.stat.timeline.open).to.exist() expect(connection.stat.timeline.open).to.exist()
expect(connection.stat.timeline.upgraded).to.exist() expect(connection.stat.timeline.upgraded).to.exist()
expect(connection.stat.timeline.close).to.not.exist() expect(connection.stat.timeline.close).to.not.exist()
@ -40,7 +41,7 @@ module.exports = (test) => {
it('should get the metadata of an open connection', () => { it('should get the metadata of an open connection', () => {
const stat = connection.stat const stat = connection.stat
expect(stat.status).to.equal('open') expect(stat.status).to.equal(Status.OPEN)
expect(stat.direction).to.exist() expect(stat.direction).to.exist()
expect(stat.timeline.open).to.exist() expect(stat.timeline.open).to.exist()
expect(stat.timeline.upgraded).to.exist() expect(stat.timeline.upgraded).to.exist()
@ -103,7 +104,7 @@ module.exports = (test) => {
await connection.close() await connection.close()
expect(connection.stat.timeline.close).to.exist() expect(connection.stat.timeline.close).to.exist()
expect(connection.stat.status).to.equal('closed') expect(connection.stat.status).to.equal(Status.CLOSED)
}) })
it('should be able to close the connection after opening a stream', async () => { it('should be able to close the connection after opening a stream', async () => {
@ -116,7 +117,7 @@ module.exports = (test) => {
await connection.close() await connection.close()
expect(connection.stat.timeline.close).to.exist() expect(connection.stat.timeline.close).to.exist()
expect(connection.stat.status).to.equal('closed') expect(connection.stat.status).to.equal(Status.CLOSED)
}) })
it('should support a proxy on the timeline', async () => { it('should support a proxy on the timeline', async () => {

View File

@ -12,7 +12,7 @@ Publishing a test suite as a module lets multiple modules all ensure compatibili
# Modules that implement the interface # Modules that implement the interface
- [JavaScript libp2p-kad-dht](https://github.com/libp2p/js-libp2p-kad-dht) - [JavaScript libp2p-kad-dht](https://github.com/libp2p/js-libp2p-kad-dht)
- [JavaScript libp2p-delegated-peer-routing](https://github.com/libp2p/js-libp2p-delegated-peer-routing) - [JavaScript libp2p-delegated-content-routing](https://github.com/libp2p/js-libp2p-delegated-content-routing)
# Badge # Badge

View File

@ -84,6 +84,7 @@ Common crypto errors come with the interface, and can be imported directly. All
```js ```js
const { const {
InvalidCryptoExchangeError,
UnexpectedPeerError UnexpectedPeerError
} = require('libp2p-interfaces/src/crypto/errors') } = require('libp2p-interfaces/src/crypto/errors')
@ -93,4 +94,5 @@ console.log(error.code === UnexpectedPeerError.code) // true
### Error Types ### Error Types
- `UnexpectedPeerError` - Should be thrown when the expected peer id does not match the peer id determined via the crypto exchange - `InvalidCryptoExchangeError` - Should be thrown when a peer provides data that is insufficient to finish the crypto exchange.
- `UnexpectedPeerError` - Should be thrown when the expected peer id does not match the peer id determined via the crypto exchange.

View File

@ -11,6 +11,18 @@ class UnexpectedPeerError extends Error {
} }
} }
module.exports = { class InvalidCryptoExchangeError extends Error {
UnexpectedPeerError constructor (message = 'Invalid crypto exchange') {
super(message)
this.code = InvalidCryptoExchangeError.code
}
static get code () {
return 'ERR_INVALID_CRYPTO_EXCHANGE'
}
}
module.exports = {
UnexpectedPeerError,
InvalidCryptoExchangeError
} }

141
src/topology/README.md Normal file
View File

@ -0,0 +1,141 @@
interface-topology
========================
> Implementation of the topology interface used by the `js-libp2p` registrar.
Topologies can be used in conjunction with `js-libp2p` to help shape its network and the overlays of its subsystems, such as pubsub and the DHT.
## Table of Contents
- [Implementations](#implementations)
- [Install](#install)
- [Modules using the interface](#modulesUsingTheInterface)
- [Usage](#usage)
- [Api](#api)
## Implementations
### Topology
A libp2p topology with a group of common peers.
### Multicodec Topology
A libp2p topology with a group of peers that support the same protocol.
## Install
```sh
$ npm install libp2p-interfaces
```
## Modules using the interface
TBA
## Usage
### Topology
```js
const Topology = require('libp2p-interfaces/src/topology')
const toplogy = new Topology({
min: 0,
max: 50
})
```
### Multicodec Topology
```js
const MulticodecTopology = require('libp2p-interfaces/src/topology/multicodec-topology')
const toplogy = new MulticodecTopology({
min: 0,
max: 50,
multicodecs: ['/echo/1.0.0'],
handlers: {
onConnect: (peerInfo, conn) => {},
onDisconnect: (peerInfo) => {}
}
})
```
## API
The `MulticodecTopology` extends the `Topology`, which makes the `Topology` API a subset of the `MulticodecTopology` API.
### Topology
- `Topology`
- `peers<Map<string, PeerInfo>>`: A Map of peers belonging to the topology.
- `disconnect<function(PeerInfo)>`: Called when a peer has been disconnected
#### Constructor
```js
const toplogy = new Topology({
min: 0,
max: 50,
handlers: {
onConnect: (peerInfo, conn) => {},
onDisconnect: (peerInfo) => {}
}
})
```
**Parameters**
- `properties` is an `Object` containing the properties of the topology.
- `min` is a `number` with the minimum needed connections (default: 0)
- `max` is a `number` with the maximum needed connections (default: Infinity)
- `handlers` is an optional `Object` containing the handler called when a peer is connected or disconnected.
- `onConnect` is a `function` called everytime a peer is connected in the topology context.
- `onDisconnect` is a `function` called everytime a peer is disconnected in the topology context.
#### Set a peer
- `topology.peers.set(id, peerInfo)`
Add a peer to the topology.
**Parameters**
- `id` is the `string` that identifies the peer to add.
- `peerInfo` is the [PeerInfo][peer-info] of the peer to add.
#### Notify about a peer disconnected event
- `topology.disconnect(peerInfo)`
**Parameters**
- `peerInfo` is the [PeerInfo][peer-info] of the peer disconnected.
### Multicodec Topology
- `MulticodecTopology`
- `registrar<Registrar>`: The `Registrar` of the topology. This is set by the `Registrar` during registration.
- `peers<Map<string, PeerInfo>>`: The Map of peers that belong to the topology
- `disconnect<function(PeerInfo)>`: Disconnects a peer from the topology.
#### Constructor
```js
const toplogy = new MulticodecTopology({
min: 0,
max: 50,
multicodecs: ['/echo/1.0.0'],
handlers: {
onConnect: (peerInfo, conn) => {},
onDisconnect: (peerInfo) => {}
}
})
```
**Parameters**
- `properties` is an `Object` containing the properties of the topology.
- `min` is a `number` with the minimum needed connections (default: 0)
- `max` is a `number` with the maximum needed connections (default: Infinity)
- `multicodecs` is a `Array<String>` with the multicodecs associated with the topology.
- `handlers` is an optional `Object` containing the handler called when a peer is connected or disconnected.
- `onConnect` is a `function` called everytime a peer is connected in the topology context.
- `onDisconnect` is a `function` called everytime a peer is disconnected in the topology context.

46
src/topology/index.js Normal file
View File

@ -0,0 +1,46 @@
'use strict'
const withIs = require('class-is')
const noop = () => {}
class Topology {
/**
* @param {Object} props
* @param {number} props.min minimum needed connections (default: 0)
* @param {number} props.max maximum needed connections (default: Infinity)
* @param {Object} [props.handlers]
* @param {function} [props.handlers.onConnect] protocol "onConnect" handler
* @param {function} [props.handlers.onDisconnect] protocol "onDisconnect" handler
* @constructor
*/
constructor ({
min = 0,
max = Infinity,
handlers = {}
}) {
this.min = min
this.max = max
// Handlers
this._onConnect = handlers.onConnect || noop
this._onDisconnect = handlers.onDisconnect || noop
this.peers = new Map()
}
set registrar (registrar) {
this._registrar = registrar
}
/**
* Notify about peer disconnected event.
* @param {PeerInfo} peerInfo
* @returns {void}
*/
disconnect (peerInfo) {
this._onDisconnect(peerInfo)
}
}
module.exports = withIs(Topology, { className: 'Topology', symbolName: '@libp2p/js-interfaces/topology' })

View File

@ -0,0 +1,93 @@
'use strict'
const assert = require('assert')
const withIs = require('class-is')
const Topology = require('./index')
class MulticodecTopology extends Topology {
/**
* @param {Object} props
* @param {number} props.min minimum needed connections (default: 0)
* @param {number} props.max maximum needed connections (default: Infinity)
* @param {Array<string>} props.multicodecs protocol multicodecs
* @param {Object} props.handlers
* @param {function} props.handlers.onConnect protocol "onConnect" handler
* @param {function} props.handlers.onDisconnect protocol "onDisconnect" handler
* @constructor
*/
constructor ({
min,
max,
multicodecs,
handlers
}) {
super({ min, max, handlers })
assert(multicodecs, 'one or more multicodec should be provided')
assert(handlers, 'the handlers should be provided')
assert(handlers.onConnect && typeof handlers.onConnect === 'function',
'the \'onConnect\' handler must be provided')
assert(handlers.onDisconnect && typeof handlers.onDisconnect === 'function',
'the \'onDisconnect\' handler must be provided')
this.multicodecs = Array.isArray(multicodecs) ? multicodecs : [multicodecs]
this._registrar = undefined
this._onProtocolChange = this._onProtocolChange.bind(this)
}
set registrar (registrar) {
this._registrar = registrar
this._registrar.peerStore.on('change:protocols', this._onProtocolChange)
// Update topology peers
this._updatePeers(this._registrar.peerStore.peers.values())
}
/**
* Update topology.
* @param {Array<PeerInfo>} peerInfoIterable
* @returns {void}
*/
_updatePeers (peerInfoIterable) {
for (const peerInfo of peerInfoIterable) {
if (this.multicodecs.filter(multicodec => peerInfo.protocols.has(multicodec)).length) {
// Add the peer regardless of whether or not there is currently a connection
this.peers.set(peerInfo.id.toB58String(), peerInfo)
// If there is a connection, call _onConnect
const connection = this._registrar.getConnection(peerInfo)
connection && this._onConnect(peerInfo, connection)
} else {
// Remove any peers we might be tracking that are no longer of value to us
this.peers.delete(peerInfo.id.toB58String())
}
}
}
/**
* Check if a new peer support the multicodecs for this topology.
* @param {Object} props
* @param {PeerInfo} props.peerInfo
* @param {Array<string>} props.protocols
*/
_onProtocolChange ({ peerInfo, protocols }) {
const existingPeer = this.peers.get(peerInfo.id.toB58String())
const hasProtocol = protocols.filter(protocol => this.multicodecs.includes(protocol))
// Not supporting the protocol anymore?
if (existingPeer && hasProtocol.length === 0) {
this._onDisconnect(peerInfo)
}
// New to protocol support
for (const protocol of protocols) {
if (this.multicodecs.includes(protocol)) {
this._updatePeers([peerInfo])
return
}
}
}
}
module.exports = withIs(MulticodecTopology, { className: 'MulticodecTopology', symbolName: '@libp2p/js-interfaces/topology/multicodec-topology' })

View File

@ -0,0 +1,92 @@
/* eslint-env mocha */
'use strict'
const chai = require('chai')
const expect = chai.expect
chai.use(require('dirty-chai'))
const sinon = require('sinon')
const PeerId = require('peer-id')
const PeerInfo = require('peer-info')
const peers = require('../../utils/peers')
module.exports = (test) => {
describe('multicodec topology', () => {
let topology, peer
beforeEach(async () => {
topology = await test.setup()
if (!topology) throw new Error('missing multicodec topology')
const id = await PeerId.createFromJSON(peers[0])
peer = await PeerInfo.create(id)
})
afterEach(async () => {
sinon.restore()
await test.teardown()
})
it('should have properties set', () => {
expect(topology.multicodecs).to.exist()
expect(topology._onConnect).to.exist()
expect(topology._onDisconnect).to.exist()
expect(topology.peers).to.exist()
expect(topology._registrar).to.exist()
})
it('should trigger "onDisconnect" on peer disconnected', () => {
sinon.spy(topology, '_onDisconnect')
topology.disconnect(peer)
expect(topology._onDisconnect.callCount).to.equal(1)
})
it('should update peers on protocol change', async () => {
sinon.spy(topology, '_updatePeers')
expect(topology.peers.size).to.eql(0)
const id2 = await PeerId.createFromJSON(peers[1])
const peer2 = await PeerInfo.create(id2)
topology.multicodecs.forEach((m) => peer2.protocols.add(m))
const peerStore = topology._registrar.peerStore
peerStore.emit('change:protocols', {
peerInfo: peer2,
protocols: Array.from(topology.multicodecs)
})
expect(topology._updatePeers.callCount).to.equal(1)
expect(topology.peers.size).to.eql(1)
})
it('should disconnect if peer no longer supports a protocol', async () => {
sinon.spy(topology, '_onDisconnect')
expect(topology.peers.size).to.eql(0)
const id2 = await PeerId.createFromJSON(peers[1])
const peer2 = await PeerInfo.create(id2)
topology.multicodecs.forEach((m) => peer2.protocols.add(m))
const peerStore = topology._registrar.peerStore
peerStore.emit('change:protocols', {
peerInfo: peer2,
protocols: Array.from(topology.multicodecs)
})
expect(topology.peers.size).to.eql(1)
topology.multicodecs.forEach((m) => peer2.protocols.delete(m))
// Peer does not support the protocol anymore
peerStore.emit('change:protocols', {
peerInfo: peer2,
protocols: []
})
expect(topology.peers.size).to.eql(1)
expect(topology._onDisconnect.callCount).to.equal(1)
expect(topology._onDisconnect.calledWith(peer2)).to.equal(true)
})
})
}

View File

@ -0,0 +1,46 @@
/* eslint-env mocha */
'use strict'
const chai = require('chai')
const expect = chai.expect
chai.use(require('dirty-chai'))
const sinon = require('sinon')
const PeerId = require('peer-id')
const PeerInfo = require('peer-info')
const peers = require('../../utils/peers')
module.exports = (test) => {
describe('topology', () => {
let topology, peer
beforeEach(async () => {
topology = await test.setup()
if (!topology) throw new Error('missing multicodec topology')
const id = await PeerId.createFromJSON(peers[0])
peer = await PeerInfo.create(id)
})
afterEach(async () => {
sinon.restore()
await test.teardown()
})
it('should have properties set', () => {
expect(topology.min).to.exist()
expect(topology.max).to.exist()
expect(topology._onConnect).to.exist()
expect(topology._onDisconnect).to.exist()
expect(topology.peers).to.exist()
})
it('should trigger "onDisconnect" on peer disconnected', () => {
sinon.spy(topology, '_onDisconnect')
topology.disconnect(peer)
expect(topology._onDisconnect.callCount).to.equal(1)
})
})
}

View File

@ -0,0 +1,12 @@
'use strict'
const { EventEmitter } = require('events')
class MockPeerStore extends EventEmitter {
constructor (peers) {
super()
this.peers = peers
}
}
module.exports = MockPeerStore

View File

@ -0,0 +1,40 @@
/* eslint-env mocha */
'use strict'
const tests = require('../../src/topology/tests/multicodec-topology')
const MulticodecTopology = require('../../src/topology/multicodec-topology')
const MockPeerStore = require('./mock-peer-store')
describe('multicodec topology compliance tests', () => {
tests({
setup (properties, registrar) {
const multicodecs = ['/echo/1.0.0']
const handlers = {
onConnect: () => { },
onDisconnect: () => { }
}
const topology = new MulticodecTopology({
multicodecs,
handlers,
...properties
})
if (!registrar) {
const peerStore = new MockPeerStore([])
registrar = {
peerStore,
getConnection: () => { }
}
}
topology.registrar = registrar
return topology
},
teardown () {
// cleanup resources created by setup()
}
})
})

View File

@ -0,0 +1,26 @@
/* eslint-env mocha */
'use strict'
const tests = require('../../src/topology/tests/topology')
const Topology = require('../../src/topology')
describe('topology compliance tests', () => {
tests({
setup (properties) {
const handlers = {
onConnect: () => { },
onDisconnect: () => { }
}
const topology = new Topology({
handlers,
...properties
})
return topology
},
teardown () {
// cleanup resources created by setup()
}
})
})