mirror of
https://github.com/fluencelabs/js-libp2p-interfaces
synced 2025-04-24 16:52:22 +00:00
refactor: API changes and switch to async iterators (#29)
BREAKING CHANGE: all the callbacks in the provided API were removed and each function uses async/await. Additionally, pull-streams are no longer being used. See the README for new usage.
This commit is contained in:
parent
e06af8c4aa
commit
bf5c646441
24
.travis.yml
24
.travis.yml
@ -2,17 +2,39 @@ language: node_js
|
||||
cache: npm
|
||||
stages:
|
||||
- check
|
||||
- test
|
||||
- cov
|
||||
|
||||
node_js:
|
||||
- '10'
|
||||
- '12'
|
||||
|
||||
os:
|
||||
- linux
|
||||
- osx
|
||||
- windows
|
||||
|
||||
script: npx nyc -s npm run test:node -- --bail
|
||||
after_success: npx nyc report --reporter=text-lcov > coverage.lcov && npx codecov
|
||||
|
||||
jobs:
|
||||
include:
|
||||
- stage: check
|
||||
script:
|
||||
- npx aegir commitlint --travis
|
||||
- npx aegir dep-check
|
||||
- npm run lint
|
||||
|
||||
- stage: test
|
||||
name: chrome
|
||||
addons:
|
||||
chrome: stable
|
||||
script: npx aegir test -t browser -t webworker
|
||||
|
||||
- stage: test
|
||||
name: firefox
|
||||
addons:
|
||||
firefox: latest
|
||||
script: npx aegir test -t browser -t webworker -- --browsers FirefoxHeadless
|
||||
|
||||
notifications:
|
||||
email: false
|
||||
|
272
README.md
272
README.md
@ -10,101 +10,253 @@ interface-connection
|
||||
[](https://david-dm.org/libp2p/interface-connection)
|
||||
[](https://github.com/feross/standard)
|
||||
|
||||
This is a test suite and interface you can use to implement a connection. A connection is understood as something that offers mechanism for writing and reading data, back pressure, half and full duplex streams. This module and test suite were heavily inspired by abstract-blob-store and interface-stream-muxer.
|
||||
This is a test suite and interface you can use to implement a connection. The connection interface contains all the metadata associated with it, as well as an array of the streams opened through this connection. In the same way as the connection, a stream contains properties with its metadata, plus an iterable duplex object that offers a mechanism for writing and reading data, with back pressure. This module and test suite were heavily inspired by abstract-blob-store and interface-stream-muxer.
|
||||
|
||||
The primary goal of this module is to enable developers to pick, swap or upgrade their connection without losing the same API expectations and mechanisms such as back pressure and the ability to half close a connection.
|
||||
|
||||
Publishing a test suite as a module lets multiple modules all ensure compatibility since they use the same test suite.
|
||||
|
||||
The API is presented with both Node.js and Go primitives, however, there is no actual limitations for it to be extended to any other language, pushing forward the cross compatibility and interop through diferent stacks.
|
||||
Publishing a test suite as a module lets multiple modules ensure compatibility since they use the same test suite.
|
||||
|
||||
## Lead Maintainer
|
||||
|
||||
[Jacob Heun](https://github.com/jacobheun/)
|
||||
|
||||
# Modules that implement the interface
|
||||
## Usage
|
||||
|
||||
- [js-libp2p-tcp](https://github.com/libp2p/js-libp2p-tcp)
|
||||
- [js-libp2p-webrtc-star](https://github.com/libp2p/js-libp2p-webrtc-star)
|
||||
- [js-libp2p-websockets](https://github.com/libp2p/js-libp2p-websockets)
|
||||
- [js-libp2p-utp](https://github.com/libp2p/js-libp2p-utp)
|
||||
- [webrtc-explorer](https://github.com/diasdavid/webrtc-explorer)
|
||||
- [js-libp2p-spdy](https://github.com/libp2p/js-libp2p-spdy)
|
||||
- [js-libp2p-multiplex](https://github.com/libp2p/js-libp2p-multiplex)
|
||||
- [js-libp2p-secio](https://github.com/libp2p/js-libp2p-secio)
|
||||
### Connection
|
||||
|
||||
# Badge
|
||||
Before creating a connection from a transport compatible with `libp2p` it is important to understand some concepts:
|
||||
|
||||
Include this badge in your readme if you make a module that is compatible with the interface-connection API. You can validate this by running the tests.
|
||||
- **socket**: the underlying raw duplex connection between two nodes. It is created by the transports during a dial/listen.
|
||||
- **[multiaddr connection](https://github.com/libp2p/interface-transport#multiaddrconnection)**: an abstraction over the socket to allow it to work with multiaddr addresses. It is a duplex connection that transports create to wrap the socket before passing to an upgrader that turns it into a standard connection (see below).
|
||||
- **connection**: a connection between two _peers_ that has built in multiplexing and info about the connected peer. It is created from a [multiaddr connection](https://github.com/libp2p/interface-transport#multiaddrconnection) by an upgrader. The upgrader uses multistream-select to add secio and multiplexing and returns this object.
|
||||
- **stream**: a muxed duplex channel of the `connection`. Each connection may have many streams.
|
||||
|
||||

|
||||
A connection stands for the libp2p communication duplex layer between two nodes. It is **not** the underlying raw transport duplex layer (socket), such as a TCP socket, but an abstracted layer that sits on top of the raw socket.
|
||||
|
||||
# How to use the battery of tests
|
||||
This helps ensuring that the transport is responsible for socket management, while also allowing the application layer to handle the connection management.
|
||||
|
||||
## Node.js
|
||||
### Test suite
|
||||
|
||||
```
|
||||
var tape = require('tape')
|
||||
var tests = require('interface-connection/tests')
|
||||
var YourConnectionHandler = require('../src')
|
||||
#### JS
|
||||
|
||||
var common = {
|
||||
setup: function (t, cb) {
|
||||
cb(null, YourConnectionHandler)
|
||||
},
|
||||
teardown: function (t, cb) {
|
||||
cb()
|
||||
}
|
||||
}
|
||||
|
||||
tests(tape, common)
|
||||
```js
|
||||
describe('your connection', () => {
|
||||
require('interface-connection/src/tests')({
|
||||
async setup () {
|
||||
return YourConnection
|
||||
},
|
||||
async teardown () {
|
||||
// cleanup resources created by setup()
|
||||
}
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
## Go
|
||||
#### Go
|
||||
|
||||
> WIP
|
||||
|
||||
# API
|
||||
## API
|
||||
|
||||
A valid (read: that follows this abstraction) connection, must implement the following API.
|
||||
### Connection
|
||||
|
||||
**Table of contents:**
|
||||
A valid connection (one that follows this abstraction), must implement the following API:
|
||||
|
||||
- type: `Connection`
|
||||
- `conn.getObservedAddrs(callback)`
|
||||
- `conn.getPeerInfo(callback)`
|
||||
- `conn.setPeerInfo(peerInfo)`
|
||||
- `...`
|
||||
```js
|
||||
new Connection({
|
||||
localAddr,
|
||||
remoteAddr,
|
||||
localPeer,
|
||||
remotePeer,
|
||||
newStream,
|
||||
close,
|
||||
getStreams,
|
||||
stat: {
|
||||
direction,
|
||||
timeline: {
|
||||
open,
|
||||
upgraded
|
||||
},
|
||||
multiplexer,
|
||||
encryption
|
||||
}
|
||||
})
|
||||
```
|
||||
- `<Multiaddr> conn.localAddr`
|
||||
- `<Multiaddr> conn.remoteAddr`
|
||||
- `<PeerId> conn.localPeer`
|
||||
- `<PeerId> conn.remotePeer`
|
||||
- `<Object> conn.stat`
|
||||
- `<Map> conn.registry`
|
||||
- `Array<Stream> conn.streams`
|
||||
- `Promise<object> conn.newStream(Array<protocols>)`
|
||||
- `<void> conn.removeStream(id)`
|
||||
- `<Stream> conn.addStream(stream, protocol, metadata)`
|
||||
- `Promise<> conn.close()`
|
||||
|
||||
### Get the Observed Addresses of the peer in the other end
|
||||
It can be obtained as follows:
|
||||
|
||||
- `JavaScript` - `conn.getObservedAddrs(callback)`
|
||||
```js
|
||||
const { Connection } = require('interface-connection')
|
||||
|
||||
This method retrieves the observed addresses we get from the underlying transport, if any.
|
||||
const conn = new Connection({
|
||||
localAddr: maConn.localAddr,
|
||||
remoteAddr: maConn.remoteAddr,
|
||||
localPeer: this._peerId,
|
||||
remotePeer,
|
||||
newStream,
|
||||
close: err => maConn.close(err),
|
||||
getStreams,
|
||||
stats: {
|
||||
direction: 'outbound',
|
||||
timeline: {
|
||||
open: maConn.timeline.open,
|
||||
upgraded: Date.now()
|
||||
},
|
||||
multiplexer,
|
||||
encryption
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
`callback` should follow the follow `function (err, multiaddrs) {}`, where `multiaddrs` is an array of [multiaddr](https://github.com/multiformats/multiaddr).
|
||||
#### Creating a connection instance
|
||||
|
||||
### Get the PeerInfo
|
||||
- `JavaScript` - `const conn = new Connection({localAddr, remoteAddr, localPeer, remotePeer, newStream, close, getStreams, direction, multiplexer, encryption})`
|
||||
|
||||
- `JavaScript` - `conn.getPeerInfo(callback)`
|
||||
Creates a new Connection instance.
|
||||
|
||||
This method retrieves the a Peer Info object that contains information about the peer that this conn connects to.
|
||||
`localAddr` is the [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.
|
||||
`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.
|
||||
`newStream` is the `function` responsible for getting a new muxed+multistream-selected stream.
|
||||
`close` is the `function` responsible for closing the raw connection.
|
||||
`getStreams` is the `function` responsible for getting the streams muxed within the connection.
|
||||
`stats` is an `object` with the metadata of the connection. It contains:
|
||||
- `direction` is a `string` indicating whether the connection is `inbound` or `outbound`.
|
||||
- `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).
|
||||
- `encryption` is a `string` with the connection encryption method identifier (optional).
|
||||
|
||||
`callback` should follow the `function (err, peerInfo) {}` signature, where peerInfo is a object of type [Peer Info](https://github.com/libp2p/js-peer-info)
|
||||
#### Create a new stream
|
||||
|
||||
### Set the PeerInfo
|
||||
- `JavaScript` - `conn.newStream(protocols)`
|
||||
|
||||
- `JavaScript` - `conn.setPeerInfo(peerInfo)`
|
||||
j
|
||||
This method stores a reference to the peerInfo Object that contains information about the peer that this conn connects to.
|
||||
Create a new stream within the connection.
|
||||
|
||||
`peerInfo` is a object of type [Peer Info](https://github.com/diasdavid/js-peer-info)
|
||||
`protocols` is an array of the intended protocol to use (by order of preference). Example: `[/echo/1.0.0]`
|
||||
|
||||
---
|
||||
It returns a `Promise` with an object with the following properties:
|
||||
|
||||
notes:
|
||||
- should follow the remaining Duplex stream operations
|
||||
- should have backpressure into account
|
||||
- should enable half duplex streams (close from one side, but still open for the other)
|
||||
- should support full duplex
|
||||
- tests should be performed by passing two streams
|
||||
```js
|
||||
{
|
||||
stream,
|
||||
protocol
|
||||
}
|
||||
```
|
||||
|
||||
The stream property contains the muxed stream, while the protocol contains the protocol codec used by the stream.
|
||||
|
||||
#### Add stream metadata
|
||||
|
||||
- `JavaScript` - `conn.addStream(stream, { protocol, ...metadata })`
|
||||
|
||||
Add a new stream to the connection registry.
|
||||
|
||||
`stream` is a muxed stream.
|
||||
`protocol` is the string codec for the protocol used by the stream. Example: `/echo/1.0.0`
|
||||
`metadata` is an object containing any additional, optional, stream metadata that you wish to track (such as its `tags`).
|
||||
|
||||
#### Remove a from the registry
|
||||
|
||||
- `JavaScript` - `conn.removeStream(id)`
|
||||
|
||||
Removes the stream with the given id from the connection registry.
|
||||
|
||||
`id` is the unique id of the stream for this connection.
|
||||
|
||||
|
||||
#### Close connection
|
||||
|
||||
- `JavaScript` - `conn.close()`
|
||||
|
||||
This method closes the connection to the remote peer, as well as all the streams muxed within the connection.
|
||||
|
||||
It returns a `Promise`.
|
||||
|
||||
#### Connection identifier
|
||||
|
||||
- `JavaScript` - `conn.id`
|
||||
|
||||
This property contains the identifier of the connection.
|
||||
|
||||
#### Connection streams registry
|
||||
|
||||
- `JavaScript` - `conn.registry`
|
||||
|
||||
This property contains a map with the muxed streams indexed by their id. This registry contains the protocol used by the stream, as well as its metadata.
|
||||
|
||||
#### Remote peer
|
||||
|
||||
- `JavaScript` - `conn.remotePeer`
|
||||
|
||||
This property contains the remote `peer-id` of this connection.
|
||||
|
||||
#### Local peer
|
||||
|
||||
- `JavaScript` - `conn.localPeer`
|
||||
|
||||
This property contains the local `peer-id` of this connection.
|
||||
|
||||
#### Get the connection Streams
|
||||
|
||||
- `JavaScript` - `conn.streams`
|
||||
|
||||
This getter returns all the muxed streams within the connection.
|
||||
|
||||
It returns an `Array`.
|
||||
|
||||
#### Remote address
|
||||
|
||||
- `JavaScript` - `conn.remoteAddr`
|
||||
|
||||
This getter returns the `remote` [multiaddr](https://github.com/multiformats/multiaddr) address.
|
||||
|
||||
#### Local address
|
||||
|
||||
- `JavaScript` - `conn.localAddr`
|
||||
|
||||
This getter returns the `local` [multiaddr](https://github.com/multiformats/multiaddr) address.
|
||||
|
||||
#### Stat
|
||||
|
||||
- `JavaScript` - `conn.stat`
|
||||
|
||||
This getter returns an `Object` with the metadata of the connection, as follows:
|
||||
|
||||
- `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`.
|
||||
|
||||
- `timeline`:
|
||||
|
||||
This property contains an object with the `open`, `upgraded` and `close` timestamps of the connection. Note that, the `close` timestamp is `undefined` until the connection is closed.
|
||||
|
||||
- `direction`:
|
||||
|
||||
This property contains the direction of the peer in the connection. It can be `inbound` or `outbound`.
|
||||
|
||||
- `multiplexer`:
|
||||
|
||||
This property contains the `multiplexing` codec being used in the connection.
|
||||
|
||||
- `encryption`:
|
||||
|
||||
This property contains the encryption method being used in the connection. It is `undefined` if the connection is not encrypted.
|
||||
|
||||
#### Tags
|
||||
|
||||
- `JavaScript` - `conn.tags`
|
||||
|
||||
This property contains an array of tags associated with the connection. New tags can be pushed to this array during the connection's lifetime.
|
||||
|
28
package.json
28
package.json
@ -11,12 +11,14 @@
|
||||
"scripts": {
|
||||
"lint": "aegir lint",
|
||||
"build": "aegir build",
|
||||
"test": "node -e 'process.exit()'",
|
||||
"release": "aegir release --no-test",
|
||||
"release-minor": "aegir release --type minor --no-test",
|
||||
"release-major": "aegir release --type major --no-test"
|
||||
"test": "aegir test",
|
||||
"test:node": "aegir test -t node",
|
||||
"test:browser": "aegir test -t browser -t webworker",
|
||||
"release": "aegir release",
|
||||
"release-minor": "aegir release --type minor",
|
||||
"release-major": "aegir release --type major"
|
||||
},
|
||||
"pre-commit": [
|
||||
"pre-push": [
|
||||
"lint"
|
||||
],
|
||||
"repository": {
|
||||
@ -32,14 +34,22 @@
|
||||
},
|
||||
"homepage": "https://github.com/libp2p/interface-connection",
|
||||
"dependencies": {
|
||||
"pull-defer": "~0.2.3"
|
||||
"abortable-iterator": "^2.1.0",
|
||||
"chai": "^4.2.0",
|
||||
"class-is": "^1.1.0",
|
||||
"dirty-chai": "^2.0.1",
|
||||
"err-code": "^2.0.0",
|
||||
"multiaddr": "^7.1.0",
|
||||
"peer-id": "~0.13.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"aegir": "^18.2.2",
|
||||
"timed-tape": "~0.1.1"
|
||||
"aegir": "^20.2.0",
|
||||
"it-pair": "^1.0.0",
|
||||
"it-pipe": "^1.0.1",
|
||||
"mocha": "^6.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.0.0",
|
||||
"node": ">=10.0.0",
|
||||
"npm": ">=6.0.0"
|
||||
},
|
||||
"contributors": [
|
||||
|
@ -1,60 +1,200 @@
|
||||
'use strict'
|
||||
|
||||
const defer = require('pull-defer/duplex')
|
||||
const PeerId = require('peer-id')
|
||||
const multiaddr = require('multiaddr')
|
||||
|
||||
module.exports = class Connection {
|
||||
constructor (conn, info) {
|
||||
this.peerInfo = null
|
||||
this.conn = defer()
|
||||
const withIs = require('class-is')
|
||||
|
||||
if (conn) {
|
||||
this.setInnerConn(conn, info)
|
||||
} else if (info) {
|
||||
this.info = info
|
||||
const assert = require('assert')
|
||||
const errCode = require('err-code')
|
||||
|
||||
/**
|
||||
* An implementation of the js-libp2p connection.
|
||||
* Any libp2p transport should use an upgrader to return this connection.
|
||||
*/
|
||||
class Connection {
|
||||
/**
|
||||
* Creates an instance of Connection.
|
||||
* @param {object} properties properties of the connection.
|
||||
* @param {multiaddr} properties.localAddr local multiaddr of the connection.
|
||||
* @param {multiaddr} properties.remoteAddr remote multiaddr of the connection.
|
||||
* @param {PeerId} properties.localPeer local peer-id.
|
||||
* @param {PeerId} properties.remotePeer remote peer-id.
|
||||
* @param {function} properties.newStream new stream muxer function.
|
||||
* @param {function} properties.close close raw connection function.
|
||||
* @param {function} properties.getStreams get streams from muxer function.
|
||||
* @param {object} properties.stat metadata of the connection.
|
||||
* @param {string} properties.stat.direction connection establishment direction ("inbound" or "outbound").
|
||||
* @param {object} properties.stat.timeline connection relevant events timestamp.
|
||||
* @param {string} properties.stat.timeline.open connection opening timestamp.
|
||||
* @param {string} properties.stat.timeline.upgraded connection upgraded timestamp.
|
||||
* @param {string} [properties.stat.multiplexer] connection multiplexing identifier.
|
||||
* @param {string} [properties.stat.encryption] connection encryption method identifier.
|
||||
*/
|
||||
constructor ({ localAddr, remoteAddr, localPeer, remotePeer, newStream, close, getStreams, stat }) {
|
||||
assert(multiaddr.isMultiaddr(localAddr), 'localAddr 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(remotePeer), 'remotePeer must be an instance of peer-id')
|
||||
assert(typeof newStream === 'function', 'new stream must be a function')
|
||||
assert(typeof close === 'function', 'close must be a function')
|
||||
assert(typeof getStreams === 'function', 'getStreams must be a function')
|
||||
assert(stat, 'connection metadata object must be provided')
|
||||
assert(stat.direction === 'inbound' || stat.direction === 'outbound', 'direction must be "inbound" or "outbound"')
|
||||
assert(stat.timeline, 'connection timeline object must be provided in the stat object')
|
||||
assert(stat.timeline.open, 'connection open timestamp must be provided')
|
||||
assert(stat.timeline.upgraded, 'connection upgraded timestamp must be provided')
|
||||
|
||||
/**
|
||||
* Connection identifier.
|
||||
*/
|
||||
this.id = (parseInt(Math.random() * 1e9)).toString(36) + Date.now()
|
||||
|
||||
/**
|
||||
* Observed multiaddr of the local peer
|
||||
*/
|
||||
this.localAddr = localAddr
|
||||
|
||||
/**
|
||||
* Observed multiaddr of the remote peer
|
||||
*/
|
||||
this.remoteAddr = remoteAddr
|
||||
|
||||
/**
|
||||
* Local peer id.
|
||||
*/
|
||||
this.localPeer = localPeer
|
||||
|
||||
/**
|
||||
* Remote peer id.
|
||||
*/
|
||||
this.remotePeer = remotePeer
|
||||
|
||||
/**
|
||||
* Connection metadata.
|
||||
*/
|
||||
this._stat = {
|
||||
...stat,
|
||||
timeline: {
|
||||
...stat.timeline,
|
||||
close: undefined
|
||||
},
|
||||
status: 'open'
|
||||
}
|
||||
|
||||
/**
|
||||
* Reference to the new stream function of the multiplexer
|
||||
*/
|
||||
this._newStream = newStream
|
||||
|
||||
/**
|
||||
* Reference to the close function of the raw connection
|
||||
*/
|
||||
this._close = close
|
||||
|
||||
/**
|
||||
* Reference to the getStreams function of the muxer
|
||||
*/
|
||||
this._getStreams = getStreams
|
||||
|
||||
/**
|
||||
* Connection streams registry
|
||||
*/
|
||||
this.registry = new Map()
|
||||
|
||||
/**
|
||||
* User provided tags
|
||||
*/
|
||||
this.tags = []
|
||||
}
|
||||
|
||||
/**
|
||||
* Get connection metadata
|
||||
* @return {Object}
|
||||
*/
|
||||
get stat () {
|
||||
return this._stat
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all the streams of the muxer.
|
||||
* @return {Array<*>}
|
||||
*/
|
||||
get streams () {
|
||||
return this._getStreams()
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new stream from this connection
|
||||
* @param {string[]} protocols intended protocol for the stream
|
||||
* @return {Promise<object>} with muxed+multistream-selected stream and selected protocol
|
||||
*/
|
||||
async newStream (protocols) {
|
||||
if (this.stat.status === 'closing') {
|
||||
throw errCode(new Error('the connection is being closed'), 'ERR_CONNECTION_BEING_CLOSED')
|
||||
}
|
||||
|
||||
if (this.stat.status === 'closed') {
|
||||
throw errCode(new Error('the connection is closed'), 'ERR_CONNECTION_CLOSED')
|
||||
}
|
||||
|
||||
if (!Array.isArray(protocols)) protocols = [protocols]
|
||||
|
||||
const { stream, protocol } = await this._newStream(protocols)
|
||||
|
||||
this.addStream(stream, protocol)
|
||||
|
||||
return {
|
||||
stream,
|
||||
protocol
|
||||
}
|
||||
}
|
||||
|
||||
get source () {
|
||||
return this.conn.source
|
||||
/**
|
||||
* Add a stream when it is opened to the registry.
|
||||
* @param {*} muxedStream a muxed stream
|
||||
* @param {object} properties the stream properties to be registered
|
||||
* @param {string} properties.protocol the protocol used by the stream
|
||||
* @param {object} properties.metadata metadata of the stream
|
||||
* @return {void}
|
||||
*/
|
||||
addStream (muxedStream, { protocol, metadata = {} }) {
|
||||
// Add metadata for the stream
|
||||
this.registry.set(muxedStream.id, {
|
||||
protocol,
|
||||
...metadata
|
||||
})
|
||||
}
|
||||
|
||||
get sink () {
|
||||
return this.conn.sink
|
||||
/**
|
||||
* Remove stream registry after it is closed.
|
||||
* @param {string} id identifier of the stream
|
||||
*/
|
||||
removeStream (id) {
|
||||
this.registry.delete(id)
|
||||
}
|
||||
|
||||
getPeerInfo (callback) {
|
||||
if (this.info && this.info.getPeerInfo) {
|
||||
return this.info.getPeerInfo(callback)
|
||||
/**
|
||||
* Close the connection.
|
||||
* @return {Promise}
|
||||
*/
|
||||
async close () {
|
||||
if (this.stat.status === 'closed') {
|
||||
return
|
||||
}
|
||||
|
||||
if (!this.peerInfo) {
|
||||
return callback(new Error('Peer Info not set yet'))
|
||||
if (this._closing) {
|
||||
return this._closing
|
||||
}
|
||||
|
||||
callback(null, this.peerInfo)
|
||||
}
|
||||
this.stat.status = 'closing'
|
||||
|
||||
setPeerInfo (peerInfo) {
|
||||
if (this.info && this.info.setPeerInfo) {
|
||||
return this.info.setPeerInfo(peerInfo)
|
||||
}
|
||||
// Close raw connection
|
||||
this._closing = await this._close()
|
||||
|
||||
this.peerInfo = peerInfo
|
||||
}
|
||||
|
||||
getObservedAddrs (callback) {
|
||||
if (this.info && this.info.getObservedAddrs) {
|
||||
return this.info.getObservedAddrs(callback)
|
||||
}
|
||||
callback(null, [])
|
||||
}
|
||||
|
||||
setInnerConn (conn, info) {
|
||||
this.conn.resolve(conn)
|
||||
if (info) {
|
||||
this.info = info
|
||||
} else {
|
||||
this.info = conn
|
||||
}
|
||||
this._stat.timeline.close = Date.now()
|
||||
this.stat.status = 'closed'
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = withIs(Connection, { className: 'Connection', symbolName: '@libp2p/interface-connection/connection' })
|
||||
|
9
src/tests.js
Normal file
9
src/tests.js
Normal file
@ -0,0 +1,9 @@
|
||||
/* eslint-env mocha */
|
||||
|
||||
'use strict'
|
||||
|
||||
const connectionSuite = require('../test/connection')
|
||||
|
||||
module.exports = (test) => {
|
||||
connectionSuite(test)
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
'use strict'
|
||||
|
||||
module.exports.all = function (test, common) {
|
||||
test('a test', function (t) {
|
||||
common.setup(test, function (err, conn) {
|
||||
t.ifError(err)
|
||||
t.end()
|
||||
})
|
||||
})
|
||||
}
|
59
test/compliance.spec.js
Normal file
59
test/compliance.spec.js
Normal file
@ -0,0 +1,59 @@
|
||||
/* eslint-env mocha */
|
||||
'use strict'
|
||||
|
||||
const tests = require('../src/tests')
|
||||
const Connection = require('../src/connection')
|
||||
const peers = require('./utils/peers')
|
||||
const PeerId = require('peer-id')
|
||||
const multiaddr = require('multiaddr')
|
||||
const pair = require('it-pair')
|
||||
|
||||
describe('compliance tests', () => {
|
||||
tests({
|
||||
async setup () {
|
||||
const localAddr = multiaddr('/ip4/127.0.0.1/tcp/8080')
|
||||
const remoteAddr = multiaddr('/ip4/127.0.0.1/tcp/8081')
|
||||
const [localPeer, remotePeer] = await Promise.all([
|
||||
PeerId.createFromJSON(peers[0]),
|
||||
PeerId.createFromJSON(peers[1])
|
||||
])
|
||||
const openStreams = []
|
||||
let streamId = 0
|
||||
|
||||
return new Connection({
|
||||
localPeer,
|
||||
remotePeer,
|
||||
localAddr,
|
||||
remoteAddr,
|
||||
stat: {
|
||||
timeline: {
|
||||
open: Date.now() - 10,
|
||||
upgraded: Date.now()
|
||||
},
|
||||
direction: 'outbound',
|
||||
encryption: '/secio/1.0.0',
|
||||
multiplexer: '/mplex/6.7.0'
|
||||
},
|
||||
newStream: (protocols) => {
|
||||
const id = streamId++
|
||||
const stream = pair()
|
||||
|
||||
stream.close = () => stream.sink([])
|
||||
stream.id = id
|
||||
|
||||
openStreams.push(stream)
|
||||
|
||||
return {
|
||||
stream,
|
||||
protocol: protocols[0]
|
||||
}
|
||||
},
|
||||
close: () => {},
|
||||
getStreams: () => openStreams
|
||||
})
|
||||
},
|
||||
async teardown () {
|
||||
// cleanup resources created by setup()
|
||||
}
|
||||
})
|
||||
})
|
135
test/connection.js
Normal file
135
test/connection.js
Normal file
@ -0,0 +1,135 @@
|
||||
/* eslint-env mocha */
|
||||
|
||||
'use strict'
|
||||
|
||||
const chai = require('chai')
|
||||
const expect = chai.expect
|
||||
chai.use(require('dirty-chai'))
|
||||
|
||||
module.exports = (test) => {
|
||||
describe('connection', () => {
|
||||
describe('open connection', () => {
|
||||
let connection
|
||||
|
||||
beforeEach(async () => {
|
||||
connection = await test.setup()
|
||||
if (!connection) throw new Error('missing connection')
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
await connection.close()
|
||||
await test.teardown()
|
||||
})
|
||||
|
||||
it('should have properties set', () => {
|
||||
expect(connection.id).to.exist()
|
||||
expect(connection.localPeer).to.exist()
|
||||
expect(connection.remotePeer).to.exist()
|
||||
expect(connection.localAddr).to.exist()
|
||||
expect(connection.remoteAddr).to.exist()
|
||||
expect(connection.stat.status).to.equal('open')
|
||||
expect(connection.stat.timeline.open).to.exist()
|
||||
expect(connection.stat.timeline.upgraded).to.exist()
|
||||
expect(connection.stat.timeline.close).to.not.exist()
|
||||
expect(connection.stat.direction).to.exist()
|
||||
expect(connection.streams).to.eql([])
|
||||
expect(connection.tags).to.eql([])
|
||||
})
|
||||
|
||||
it('should get the metadata of an open connection', () => {
|
||||
const stat = connection.stat
|
||||
|
||||
expect(stat.status).to.equal('open')
|
||||
expect(stat.direction).to.exist()
|
||||
expect(stat.timeline.open).to.exist()
|
||||
expect(stat.timeline.upgraded).to.exist()
|
||||
expect(stat.timeline.close).to.not.exist()
|
||||
})
|
||||
|
||||
it('should return an empty array of streams', () => {
|
||||
const streams = connection.streams
|
||||
|
||||
expect(streams).to.eql([])
|
||||
})
|
||||
|
||||
it('should be able to create a new stream', async () => {
|
||||
const protocolToUse = '/echo/0.0.1'
|
||||
const { stream, protocol } = await connection.newStream(protocolToUse)
|
||||
|
||||
expect(protocol).to.equal(protocolToUse)
|
||||
|
||||
const connStreams = await connection.streams
|
||||
|
||||
expect(stream).to.exist()
|
||||
expect(connStreams).to.exist()
|
||||
expect(connStreams).to.have.lengthOf(1)
|
||||
expect(connStreams[0]).to.equal(stream)
|
||||
})
|
||||
})
|
||||
|
||||
describe('close connection', () => {
|
||||
let connection
|
||||
|
||||
beforeEach(async () => {
|
||||
connection = await test.setup()
|
||||
if (!connection) throw new Error('missing connection')
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
await test.teardown()
|
||||
})
|
||||
|
||||
it('should be able to close the connection after being created', async () => {
|
||||
expect(connection.stat.timeline.close).to.not.exist()
|
||||
await connection.close()
|
||||
|
||||
expect(connection.stat.timeline.close).to.exist()
|
||||
expect(connection.stat.status).to.equal('closed')
|
||||
})
|
||||
|
||||
it('should be able to close the connection after opening a stream', async () => {
|
||||
// Open stream
|
||||
const protocol = '/echo/0.0.1'
|
||||
await connection.newStream(protocol)
|
||||
|
||||
// Close connection
|
||||
expect(connection.stat.timeline.close).to.not.exist()
|
||||
await connection.close()
|
||||
|
||||
expect(connection.stat.timeline.close).to.exist()
|
||||
expect(connection.stat.status).to.equal('closed')
|
||||
})
|
||||
|
||||
it('should fail to create a new stream if the connection is closing', async () => {
|
||||
expect(connection.stat.timeline.close).to.not.exist()
|
||||
connection.close()
|
||||
|
||||
try {
|
||||
const protocol = '/echo/0.0.1'
|
||||
await connection.newStream(protocol)
|
||||
} catch (err) {
|
||||
expect(err).to.exist()
|
||||
return
|
||||
}
|
||||
|
||||
throw new Error('should fail to create a new stream if the connection is closing')
|
||||
})
|
||||
|
||||
it('should fail to create a new stream if the connection is closed', async () => {
|
||||
expect(connection.stat.timeline.close).to.not.exist()
|
||||
await connection.close()
|
||||
|
||||
try {
|
||||
const protocol = '/echo/0.0.1'
|
||||
await connection.newStream(protocol)
|
||||
} catch (err) {
|
||||
expect(err).to.exist()
|
||||
expect(err.code).to.equal('ERR_CONNECTION_CLOSED')
|
||||
return
|
||||
}
|
||||
|
||||
throw new Error('should fail to create a new stream if the connection is closing')
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
@ -1,8 +0,0 @@
|
||||
'use strict'
|
||||
|
||||
var timed = require('timed-tape')
|
||||
|
||||
module.exports = function (test, common) {
|
||||
test = timed(test)
|
||||
require('./base-test.js').all(test, common)
|
||||
}
|
27
test/utils/peers.js
Normal file
27
test/utils/peers.js
Normal file
@ -0,0 +1,27 @@
|
||||
'use strict'
|
||||
|
||||
module.exports = [{
|
||||
id: 'QmNMMAqSxPetRS1cVMmutW5BCN1qQQyEr4u98kUvZjcfEw',
|
||||
privKey: 'CAASpQkwggShAgEAAoIBAQDPek2aeHMa0blL42RTKd6xgtkk4Zkldvq4LHxzcag5uXepiQzWANEUvoD3KcUTmMRmx14PvsxdLCNst7S2JSa0R2n5wSRs14zGy6892lx4H4tLBD1KSpQlJ6vabYM1CJhIQRG90BtzDPrJ/X1iJ2HA0PPDz0Mflam2QUMDDrU0IuV2m7gSCJ5r4EmMs3U0xnH/1gShkVx4ir0WUdoWf5KQUJOmLn1clTRHYPv4KL9A/E38+imNAXfkH3c2T7DrCcYRkZSpK+WecjMsH1dCX15hhhggNqfp3iulO1tGPxHjm7PDGTPUjpCWKpD5e50sLqsUwexac1ja6ktMfszIR+FPAgMBAAECggEAB2H2uPRoRCAKU+T3gO4QeoiJaYKNjIO7UCplE0aMEeHDnEjAKC1HQ1G0DRdzZ8sb0fxuIGlNpFMZv5iZ2ZFg2zFfV//DaAwTek9tIOpQOAYHUtgHxkj5FIlg2BjlflGb+ZY3J2XsVB+2HNHkUEXOeKn2wpTxcoJE07NmywkO8Zfr1OL5oPxOPlRN1gI4ffYH2LbfaQVtRhwONR2+fs5ISfubk5iKso6BX4moMYkxubYwZbpucvKKi/rIjUA3SK86wdCUnno1KbDfdXSgCiUlvxt/IbRFXFURQoTV6BOi3sP5crBLw8OiVubMr9/8WE6KzJ0R7hPd5+eeWvYiYnWj4QKBgQD6jRlAFo/MgPO5NZ/HRAk6LUG+fdEWexA+GGV7CwJI61W/Dpbn9ZswPDhRJKo3rquyDFVZPdd7+RlXYg1wpmp1k54z++L1srsgj72vlg4I8wkZ4YLBg0+zVgHlQ0kxnp16DvQdOgiRFvMUUMEgetsoIx1CQWTd67hTExGsW+WAZQKBgQDT/WaHWvwyq9oaZ8G7F/tfeuXvNTk3HIJdfbWGgRXB7lJ7Gf6FsX4x7PeERfL5a67JLV6JdiLLVuYC2CBhipqLqC2DB962aKMvxobQpSljBBZvZyqP1IGPoKskrSo+2mqpYkeCLbDMuJ1nujgMP7gqVjabs2zj6ACKmmpYH/oNowJ/T0ZVtvFsjkg+1VsiMupUARRQuPUWMwa9HOibM1NIZcoQV2NGXB5Z++kR6JqxQO0DZlKArrviclderUdY+UuuY4VRiSEprpPeoW7ZlbTku/Ap8QZpWNEzZorQDro7bnfBW91fX9/81ets/gCPGrfEn+58U3pdb9oleCOQc/ifpQKBgBTYGbi9bYbd9vgZs6bd2M2um+VFanbMytS+g5bSIn2LHXkVOT2UEkB+eGf9KML1n54QY/dIMmukA8HL1oNAyalpw+/aWj+9Ui5kauUhGEywHjSeBEVYM9UXizxz+m9rsoktLLLUI0o97NxCJzitG0Kub3gn0FEogsUeIc7AdinZAoGBANnM1vcteSQDs7x94TDEnvvqwSkA2UWyLidD2jXgE0PG4V6tTkK//QPBmC9eq6TIqXkzYlsErSw4XeKO91knFofmdBzzVh/ddgx/NufJV4tXF+a2iTpqYBUJiz9wpIKgf43/Ob+P1EA99GAhSdxz1ess9O2aTqf3ANzn6v6g62Pv',
|
||||
pubKey: 'CAASpgIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDPek2aeHMa0blL42RTKd6xgtkk4Zkldvq4LHxzcag5uXepiQzWANEUvoD3KcUTmMRmx14PvsxdLCNst7S2JSa0R2n5wSRs14zGy6892lx4H4tLBD1KSpQlJ6vabYM1CJhIQRG90BtzDPrJ/X1iJ2HA0PPDz0Mflam2QUMDDrU0IuV2m7gSCJ5r4EmMs3U0xnH/1gShkVx4ir0WUdoWf5KQUJOmLn1clTRHYPv4KL9A/E38+imNAXfkH3c2T7DrCcYRkZSpK+WecjMsH1dCX15hhhggNqfp3iulO1tGPxHjm7PDGTPUjpCWKpD5e50sLqsUwexac1ja6ktMfszIR+FPAgMBAAE='
|
||||
}, {
|
||||
id: 'QmW8rAgaaA6sRydK1k6vonShQME47aDxaFidbtMevWs73t',
|
||||
privKey: 'CAASpwkwggSjAgEAAoIBAQCTU3gVDv3SRXLOsFln9GEf1nJ/uCEDhOG10eC0H9l9IPpVxjuPT1ep+ykFUdvefq3D3q+W3hbmiHm81o8dYv26RxZIEioToUWp7Ec5M2B/niYoE93za9/ZDwJdl7eh2hNKwAdxTmdbXUPjkIU4vLyHKRFbJIn9X8w9djldz8hoUvC1BK4L1XrT6F2l0ruJXErH2ZwI1youfSzo87TdXIoFKdrQLuW6hOtDCGKTiS+ab/DkMODc6zl8N47Oczv7vjzoWOJMUJs1Pg0ZsD1zmISY38P0y/QyEhatZn0B8BmSWxlLQuukatzOepQI6k+HtfyAAjn4UEqnMaXTP1uwLldVAgMBAAECggEAHq2f8MqpYjLiAFZKl9IUs3uFZkEiZsgx9BmbMAb91Aec+WWJG4OLHrNVTG1KWp+IcaQablEa9bBvoToQnS7y5OpOon1d066egg7Ymfmv24NEMM5KRpktCNcOSA0CySpPIB6yrg6EiUr3ixiaFUGABKkxmwgVz/Q15IqM0ZMmCUsC174PMAz1COFZxD0ZX0zgHblOJQW3dc0X3XSzhht8vU02SMoVObQHQfeXEHv3K/RiVj/Ax0bTc5JVkT8dm8xksTtsFCNOzRBqFS6MYqX6U/u0Onz3Jm5Jt7fLWb5n97gZR4SleyGrqxYNb46d9X7mP0ie7E6bzFW0DsWBIeAqVQKBgQDW0We2L1n44yOvJaMs3evpj0nps13jWidt2I3RlZXjWzWHiYQfvhWUWqps/xZBnAYgnN/38xbKzHZeRNhrqOo+VB0WK1IYl0lZVE4l6TNKCsLsUfQzsb1pePkd1eRZA+TSqsi+I/IOQlQU7HA0bMrah/5FYyUBP0jYvCOvYTlZuwKBgQCvkcVRydVlzjUgv7lY5lYvT8IHV5iYO4Qkk2q6Wjv9VUKAJZauurMdiy05PboWfs5kbETdwFybXMBcknIvZO4ihxmwL8mcoNwDVZHI4bXapIKMTCyHgUKvJ9SeTcKGC7ZuQJ8mslRmYox/HloTOXEJgQgPRxXcwa3amzvdZI+6LwKBgQCLsnQqgxKUi0m6bdR2qf7vzTH4258z6X34rjpT0F5AEyF1edVFOz0XU/q+lQhpNEi7zqjLuvbYfSyA026WXKuwSsz7jMJ/oWqev/duKgAjp2npesY/E9gkjfobD+zGgoS9BzkyhXe1FCdP0A6L2S/1+zg88WOwMvJxl6/xLl24XwKBgCm60xSajX8yIQyUpWBM9yUtpueJ2Xotgz4ST+bVNbcEAddll8gWFiaqgug9FLLuFu5lkYTHiPtgc1RNdphvO+62/9MRuLDixwh/2TPO+iNqwKDKJjda8Nei9vVddCPaOtU/xNQ0xLzFJbG9LBmvqH9izOCcu8SJwGHaTcNUeJj/AoGADCJ26cY30c13F/8awAAmFYpZWCuTP5ppTsRmjd63ixlrqgkeLGpJ7kYb5fXkcTycRGYgP0e1kssBGcmE7DuG955fx3ZJESX3GQZ+XfMHvYGONwF1EiK1f0p6+GReC2VlQ7PIkoD9o0hojM6SnWvv9EXNjCPALEbfPFFvcniKVsE=',
|
||||
pubKey: 'CAASpgIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCTU3gVDv3SRXLOsFln9GEf1nJ/uCEDhOG10eC0H9l9IPpVxjuPT1ep+ykFUdvefq3D3q+W3hbmiHm81o8dYv26RxZIEioToUWp7Ec5M2B/niYoE93za9/ZDwJdl7eh2hNKwAdxTmdbXUPjkIU4vLyHKRFbJIn9X8w9djldz8hoUvC1BK4L1XrT6F2l0ruJXErH2ZwI1youfSzo87TdXIoFKdrQLuW6hOtDCGKTiS+ab/DkMODc6zl8N47Oczv7vjzoWOJMUJs1Pg0ZsD1zmISY38P0y/QyEhatZn0B8BmSWxlLQuukatzOepQI6k+HtfyAAjn4UEqnMaXTP1uwLldVAgMBAAE='
|
||||
}, {
|
||||
id: 'QmZqCdSzgpsmB3Qweb9s4fojAoqELWzqku21UVrqtVSKi4',
|
||||
privKey: 'CAASpgkwggSiAgEAAoIBAQCdbSEsTmw7lp5HagRcx57DaLiSUEkh4iBcKc7Y+jHICEIA8NIVi9FlfGEZj9G21FpiTR4Cy+BLVEuf8Nm90bym4iV+cSumeS21fvD8xGTEbeKGljs6OYHy3M45JhWF85gqHQJOqZufI2NRDuRgMZEO2+qGEXmSlv9mMXba/+9ecze8nSpB7bG2Z2pnKDeYwhF9Cz+ElMyn7TBWDjJERGVgFbTpdM3rBnbhB/TGpvs732QqZmIBlxnDb/Jn0l1gNZCgkEDcJ/0NDMBJTQ8vbvcdmaw3eaMPLkn1ix4wdu9QWCA0IBtuY1R7vSUtf4irnLJG7DnAw2GfM5QrF3xF1GLXAgMBAAECggEAQ1N0qHoxl5pmvqv8iaFlqLSUmx5y6GbI6CGJMQpvV9kQQU68yjItr3VuIXx8d/CBZyEMAK4oko7OeOyMcr3MLKLy3gyQWnXgsopDjhZ/8fH8uwps8g2+IZuFJrO+6LaxEPGvFu06fOiphPUVfn40R2KN/iBjGeox+AaXijmCqaV2vEdNJJPpMfz6VKZBDLTrbiqvo/3GN1U99PUqfPWpOWR29oAhh/Au6blSqvqTUPXB2+D/X6e1JXv31mxMPK68atDHSUjZWKB9lE4FMK1bkSKJRbyXmNIlbZ9V8X4/0r8/6T7JnW7ZT8ugRkquohmwgG7KkDXB1YsOCKXYUqzVYQKBgQDtnopFXWYl7XUyePJ/2MA5i7eoko9jmF44L31irqmHc5unNf6JlNBjlxTNx3WyfzhUzrn3c18psnGkqtow0tkBj5hmqn8/WaPbc5UA/5R1FNaNf8W5khn7MDm6KtYRPjN9djqTDiVHyC6ljONYd+5S+MqyKVWZ3t/xvG60sw85qwKBgQCpmpDtL+2JBwkfeUr3LyDcQxvbfzcv8lXj2otopWxWiLiZF1HzcqgAa2CIwu9kCGEt9Zr+9E4uINbe1To0b01/FhvR6xKO/ukceGA/mBB3vsKDcRmvpBUp+3SmnhY0nOk+ArQl4DhJ34k8pDM3EDPrixPf8SfVdU/8IM32lsdHhQKBgHLgpvCKCwxjFLnmBzcPzz8C8TOqR3BbBZIcQ34l+wflOGdKj1hsfaLoM8KYn6pAHzfBCd88A9Hg11hI0VuxVACRL5jS7NnvuGwsIOluppNEE8Ys86aXn7/0vLPoab3EWJhbRE48FIHzobmft3nZ4XpzlWs02JGfUp1IAC2UM9QpAoGAeWy3pZhSr2/iEC5+hUmwdQF2yEbj8+fDpkWo2VrVnX506uXPPkQwE1zM2Bz31t5I9OaJ+U5fSpcoPpDaAwBMs1fYwwlRWB8YNdHY1q6/23svN3uZsC4BGPV2JnO34iMUudilsRg+NGVdk5TbNejbwx7nM8Urh59djFzQGGMKeSECgYA0QMCARPpdMY50Mf2xQaCP7HfMJhESSPaBq9V3xY6ToEOEnXgAR5pNjnU85wnspHp+82r5XrKfEQlFxGpj2YA4DRRmn239sjDa29qP42UNAFg1+C3OvXTht1d5oOabaGhU0udwKmkEKUbb0bG5xPQJ5qeSJ5T1gLzLk3SIP0GlSw==',
|
||||
pubKey: 'CAASpgIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCdbSEsTmw7lp5HagRcx57DaLiSUEkh4iBcKc7Y+jHICEIA8NIVi9FlfGEZj9G21FpiTR4Cy+BLVEuf8Nm90bym4iV+cSumeS21fvD8xGTEbeKGljs6OYHy3M45JhWF85gqHQJOqZufI2NRDuRgMZEO2+qGEXmSlv9mMXba/+9ecze8nSpB7bG2Z2pnKDeYwhF9Cz+ElMyn7TBWDjJERGVgFbTpdM3rBnbhB/TGpvs732QqZmIBlxnDb/Jn0l1gNZCgkEDcJ/0NDMBJTQ8vbvcdmaw3eaMPLkn1ix4wdu9QWCA0IBtuY1R7vSUtf4irnLJG7DnAw2GfM5QrF3xF1GLXAgMBAAE='
|
||||
}, {
|
||||
id: 'QmR5VwgsL7jyfZHAGyp66tguVrQhCRQuRc3NokocsCZ3fA',
|
||||
privKey: 'CAASpwkwggSjAgEAAoIBAQCGXYU+uc2nn1zuJhfdFOl34upztnrD1gpHu58ousgHdGlGgYgbqLBAvIAauXdEL0+e30HofjA634SQxE+9nV+0FQBam1DDzHQlXsuwHV+2SKvSDkk4bVllMFpu2SJtts6VH+OXC/2ANJOm+eTALykQPYXgLIBxrhp/eD+Jz5r6wW2nq3k6OmYyK/4pgGzFjo5UyX+fa/171AJ68UPboFpDy6BZCcUjS0ondxPvD7cv5jMNqqMKIB/7rpi8n+Q3oeccRqVL56wH+FE3/QLjwYHwY6ILNRyvNXRqHjwBEXB2R5moXN0AFUWTw9rt3KhFiEjR1U81BTw5/xS7W2Iu0FgZAgMBAAECggEAS64HK8JZfE09eYGJNWPe8ECmD1C7quw21BpwVe+GVPSTizvQHswPohbKDMNj0srXDMPxCnNw1OgqcaOwyjsGuZaOoXoTroTM8nOHRIX27+PUqzaStS6aCG2IsiCozKUHjGTuupftS7XRaF4eIsUtWtFcQ1ytZ9pJYHypRQTi5NMSrTze5ThjnWxtHilK7gnBXik+aR0mYEVfSn13czQEC4rMOs+b9RAc/iibDNoLopfIdvmCCvfxzmySnR7Cu1iSUAONkir7PB+2Mt/qRFCH6P+jMamtCgQ8AmifXgVmDUlun+4MnKg3KrPd6ZjOEKhVe9mCHtGozk65RDREShfDdQKBgQDi+x2MuRa9peEMOHnOyXTS+v+MFcfmG0InsO08rFNBKZChLB+c9UHBdIvexpfBHigSyERfuDye4z6lxi8ZnierWMYJP30nxmrnxwTGTk1MQquhfs1A0kpmDnPsjlOS/drEIEIssNx2WbfJ7YtMxLWBtp+BJzGpQmr0LKC+NHRSrwKBgQCXiy2kJESIUkIs2ihV55hhT6/bZo1B1O5DPA2nkjOBXqXF6fvijzMDX82JjLd07lQZlI0n1Q/Hw0p4iYi9YVd2bLkLXF5UIb2qOeHj76enVFOrPHUSkC9Y2g/0Xs+60Ths2xRd8RrrfQU3kl5iVpBywkCIrb2M5+wRnNTk1W3TtwKBgQCvplyrteAfSurpJhs9JzE8w/hWU9SqAZYkWQp91W1oE95Um2yrbjBAoQxMjaqKS+f/APPIjy56Vqj4aHGyhW11b/Fw3qzfxvCcBKtxOs8eoMlo5FO6QgJJEA4tlcafDcvp0nzjUMqK28safLU7503+33B35fjMXxWdd5u9FaKfCQKBgC4W6j6tuRosymuRvgrCcRnHfpify/5loEFallyMnpWOD6Tt0OnK25z/GifnYDRz96gAAh5HMpFy18dpLOlMHamqz2yhHx8/U8vd5tHIJZlCkF/X91M5/uxrBccwvsT2tM6Got8fYSyVzWxlW8dUxIHiinYHQUsFjkqdBDLEpq5pAoGASoTw5RBEWFM0GuAZdXsyNyxU+4S+grkTS7WdW/Ymkukh+bJZbnvF9a6MkSehqXnknthmufonds2AFNS//63gixENsoOhzT5+2cdfc6tJECvJ9xXVXkf85AoQ6T/RrXF0W4m9yQyCngNJUrKUOIH3oDIfdZITlYzOC3u1ojj7VuQ=',
|
||||
pubKey: 'CAASpgIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCGXYU+uc2nn1zuJhfdFOl34upztnrD1gpHu58ousgHdGlGgYgbqLBAvIAauXdEL0+e30HofjA634SQxE+9nV+0FQBam1DDzHQlXsuwHV+2SKvSDkk4bVllMFpu2SJtts6VH+OXC/2ANJOm+eTALykQPYXgLIBxrhp/eD+Jz5r6wW2nq3k6OmYyK/4pgGzFjo5UyX+fa/171AJ68UPboFpDy6BZCcUjS0ondxPvD7cv5jMNqqMKIB/7rpi8n+Q3oeccRqVL56wH+FE3/QLjwYHwY6ILNRyvNXRqHjwBEXB2R5moXN0AFUWTw9rt3KhFiEjR1U81BTw5/xS7W2Iu0FgZAgMBAAE='
|
||||
}, {
|
||||
id: 'QmScLDqRg7H6ipCYxm9fVk152UWavQFKscTdoT4YNHxgqp',
|
||||
privKey: 'CAASpwkwggSjAgEAAoIBAQCWEHaTZ6LBLFP5OPrUqjDM/cF4b2zrfh1Zm3kd02ZtgQB3iYtZqRPJT5ctT3A7WdVF/7dCxPGOCkJlLekTx4Y4gD8JtjA+EfN9fR/2RBKbti2N3CD4vkGp9ss4hbBFcXIhl8zuD/ELHutbV6b8b4QXJGnxfp/B+1kNPnyd7SJznS0QyvI8OLI1nAkVKdYLDRW8kPKeHyx1xhdNDuTQVTFyAjRGQ4e3UYFB7bYIHW3E6kCtCoJDlj+JPC02Yt1LHzIzZVLvPvNFnYY2mag6OiGFuh/oMBIqvnPc1zRZ3eLUqeGZjQVaoR0kdgZUKz7Q2TBeNldxK/s6XO0DnkQTlelNAgMBAAECggEAdmt1dyswR2p4tdIeNpY7Pnj9JNIhTNDPznefI0dArCdBvBMhkVaYk6MoNIxcj6l7YOrDroAF8sXr0TZimMY6B/pERKCt/z1hPWTxRQBBAvnHhwvwRPq2jK6BfhAZoyM8IoBNKowP9mum5QUNdGV4Al8s73KyFX0IsCfgZSvNpRdlt+DzPh+hu/CyoZaMpRchJc1UmK8Fyk3KfO+m0DZNfHP5P08lXNfM6MZLgTJVVgERHyG+vBOzTd2RElMe19nVCzHwb3dPPRZSQ7Fnz3rA+GeLqsM2Zi4HNhfbD1OcD9C4wDj5tYL6hWTkdz4IlfVcjCeUHxgIOhdDV2K+OwbuAQKBgQD0FjUZ09UW2FQ/fitbvIB5f1SkXWPxTF9l6mAeuXhoGv2EtQUO4vq/PK6N08RjrZdWQy6UsqHgffi7lVQ8o3hvCKdbtf4sP+cM92OrY0WZV89os79ndj4tyvmnP8WojwRjt/2XEfgdoWcgWxW9DiYINTOQVimZX+X/3on4s8hEgQKBgQCdY3kOMbyQeLTRkqHXjVTY4ddO+v4S4wOUa1l4rTqAbq1W3JYWwoDQgFuIu3limIHmjnSJpCD4EioXFsM7p6csenoc20sHxsaHnJ6Mn5Te41UYmY9EW0otkQ0C3KbXM0hwQkjyplnEmZawGKmjEHW8DJ3vRYTv9TUCgYKxDHgOzQKBgB4A/NYH7BG61eBYKgxEx6YnuMfbkwV+Vdu5S8d7FQn3B2LgvZZu4FPRqcNVXLbEB+5ao8czjiKCWaj1Wj15+rvrXGcxn+Tglg5J+r5+nXeUC7LbJZQaPNp0MOwWMr3dlrSLUWjYlJ9Pz9VyXOG4c4Rexc/gR4zK9QLW4C7qKpwBAoGAZzyUb0cYlPtYQA+asTU3bnvVKy1f8yuNcZFowst+EDiI4u0WVh+HNzy6zdmLKa03p+/RaWeLaK0hhrubnEnAUmCUMNF3ScaM+u804LDcicc8TkKLwx7ObU0z56isl4RAA8K27tNHFrpYKXJD834cfBkaj5ReOrfw6Y/iFhhDuBECgYEA8gbC76uz7LSHhW30DSRTcqOzTyoe2oYKQaxuxYNp7vSSOkcdRen+mrdflDvud2q/zN2QdL4pgqdldHlR35M/lJ0f0B6zp74jlzbO9700wzsOqreezGc5eWiroDL100U9uIZ50BKb8CKtixIHpinUSPIUcVDkSAZ2y7mbfCxQwqQ=',
|
||||
pubKey: 'CAASpgIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCWEHaTZ6LBLFP5OPrUqjDM/cF4b2zrfh1Zm3kd02ZtgQB3iYtZqRPJT5ctT3A7WdVF/7dCxPGOCkJlLekTx4Y4gD8JtjA+EfN9fR/2RBKbti2N3CD4vkGp9ss4hbBFcXIhl8zuD/ELHutbV6b8b4QXJGnxfp/B+1kNPnyd7SJznS0QyvI8OLI1nAkVKdYLDRW8kPKeHyx1xhdNDuTQVTFyAjRGQ4e3UYFB7bYIHW3E6kCtCoJDlj+JPC02Yt1LHzIzZVLvPvNFnYY2mag6OiGFuh/oMBIqvnPc1zRZ3eLUqeGZjQVaoR0kdgZUKz7Q2TBeNldxK/s6XO0DnkQTlelNAgMBAAE='
|
||||
}, {
|
||||
id: 'QmckxVrJw1Yo8LqvmDJNUmdAsKtSbiKWmrXJFyKmUraBoN',
|
||||
privKey: 'CAASpwkwggSjAgEAAoIBAQC1/GFud/7xutux7qRfMj1sIdMRh99/chR6HqVj6LQqrgk4jil0mdN/LCk/tqPqmDtObHdmEhCoybzuhLbCKgUqryKDwO6yBJHSKWY9QqrKZtLJ37SgKwGjE3+NUD4r1dJHhtQrICFdOdSCBzs/v8gi+J+KZLHo7+Nms4z09ysy7qZh94Pd7cW4gmSMergqUeANLD9C0ERw1NXolswOW7Bi7UGr7yuBxejICLO3nkxe0OtpQBrYrqdCD9vs3t/HQZbPWVoiRj4VO7fxkAPKLl30HzcIfxj/ayg8NHcH59d08D+N2v5Sdh28gsiYKIPE9CXvuw//HUY2WVRY5fDC5JglAgMBAAECggEBAKb5aN/1w3pBqz/HqRMbQpYLNuD33M3PexBNPAy+P0iFpDo63bh5Rz+A4lvuFNmzUX70MFz7qENlzi6+n/zolxMB29YtWBUH8k904rTEjXXl//NviQgITZk106tx+4k2x5gPEm57LYGfBOdFAUzNhzDnE2LkXwRNzkS161f7zKwOEsaGWRscj6UvhO4MIFxjb32CVwt5eK4yOVqtyMs9u30K4Og+AZYTlhtm+bHg6ndCCBO6CQurCQ3jD6YOkT+L3MotKqt1kORpvzIB0ujZRf49Um8wlcjC5G9aexBeGriXaVdPF62zm7GA7RMsbQM/6aRbA1fEQXvJhHUNF9UFeaECgYEA8wCjKqQA7UQnHjRwTsktdwG6szfxd7z+5MTqHHTWhWzgcQLgdh5/dO/zanEoOThadMk5C1Bqjq96gH2xim8dg5XQofSVtV3Ui0dDa+XRB3E3fyY4D3RF5hHv85O0GcvQc6DIb+Ja1oOhvHowFB1C+CT3yEgwzX/EK9xpe+KtYAkCgYEAv7hCnj/DcZFU3fAfS+unBLuVoVJT/drxv66P686s7J8UM6tW+39yDBZ1IcwY9vHFepBvxY2fFfEeLI02QFM+lZXVhNGzFkP90agNHK01psGgrmIufl9zAo8WOKgkLgbYbSHzkkDeqyjEPU+B0QSsZOCE+qLCHSdsnTmo/TjQhj0CgYAz1+j3yfGgrS+jVBC53lXi0+2fGspbf2jqKdDArXSvFqFzuudki/EpY6AND4NDYfB6hguzjD6PnoSGMUrVfAtR7X6LbwEZpqEX7eZGeMt1yQPMDr1bHrVi9mS5FMQR1NfuM1lP9Xzn00GIUpE7WVrWUhzDEBPJY/7YVLf0hFH08QKBgDWBRQZJIVBmkNrHktRrVddaSq4U/d/Q5LrsCrpymYwH8WliHgpeTQPWmKXwAd+ZJdXIzYjCt202N4eTeVqGYOb6Q/anV2WVYBbM4avpIxoA28kPGY6nML+8EyWIt2ApBOmgGgvtEreNzwaVU9NzjHEyv6n7FlVwlT1jxCe3XWq5AoGASYPKQoPeDlW+NmRG7z9EJXJRPVtmLL40fmGgtju9QIjLnjuK8XaczjAWT+ySI93Whu+Eujf2Uj7Q+NfUjvAEzJgwzuOd3jlQvoALq11kuaxlNQTn7rx0A1QhBgUJE8AkvShPC9FEnA4j/CLJU0re9H/8VvyN6qE0Mho0+YbjpP8=',
|
||||
pubKey: 'CAASpgIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC1/GFud/7xutux7qRfMj1sIdMRh99/chR6HqVj6LQqrgk4jil0mdN/LCk/tqPqmDtObHdmEhCoybzuhLbCKgUqryKDwO6yBJHSKWY9QqrKZtLJ37SgKwGjE3+NUD4r1dJHhtQrICFdOdSCBzs/v8gi+J+KZLHo7+Nms4z09ysy7qZh94Pd7cW4gmSMergqUeANLD9C0ERw1NXolswOW7Bi7UGr7yuBxejICLO3nkxe0OtpQBrYrqdCD9vs3t/HQZbPWVoiRj4VO7fxkAPKLl30HzcIfxj/ayg8NHcH59d08D+N2v5Sdh28gsiYKIPE9CXvuw//HUY2WVRY5fDC5JglAgMBAAE='
|
||||
}]
|
Loading…
x
Reference in New Issue
Block a user