From 690b11999a790c98994ddd506cb4c2bc0bbc9724 Mon Sep 17 00:00:00 2001 From: David Dias Date: Mon, 14 Sep 2015 08:54:37 +0100 Subject: [PATCH 01/66] Initial commit --- .gitignore | 27 +++++++++++++++++++++++++++ LICENSE | 22 ++++++++++++++++++++++ README.md | 2 ++ 3 files changed, 51 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README.md diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..123ae94 --- /dev/null +++ b/.gitignore @@ -0,0 +1,27 @@ +# Logs +logs +*.log + +# Runtime data +pids +*.pid +*.seed + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage + +# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (http://nodejs.org/api/addons.html) +build/Release + +# Dependency directory +# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git +node_modules diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..59a33ba --- /dev/null +++ b/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2015 David Dias + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/README.md b/README.md new file mode 100644 index 0000000..c2b5110 --- /dev/null +++ b/README.md @@ -0,0 +1,2 @@ +# abstract-transport +A test suite and interface you can use to implement a transport interface. From a422c81b90e60c25574ebf45bd30f3a57059757b Mon Sep 17 00:00:00 2001 From: David Dias Date: Wed, 16 Sep 2015 16:21:35 +0100 Subject: [PATCH 02/66] add logo --- img/badge.png | Bin 0 -> 5226 bytes img/badge.sketch | Bin 0 -> 40960 bytes img/badge.svg | 19 +++++++++++++++++++ 3 files changed, 19 insertions(+) create mode 100644 img/badge.png create mode 100644 img/badge.sketch create mode 100644 img/badge.svg diff --git a/img/badge.png b/img/badge.png new file mode 100644 index 0000000000000000000000000000000000000000..1c8fe6972b5957adcd6088961701fda1ff5cf368 GIT binary patch literal 5226 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 literal 0 HcmV?d00001 diff --git a/img/badge.svg b/img/badge.svg new file mode 100644 index 0000000..aa56dac --- /dev/null +++ b/img/badge.svg @@ -0,0 +1,19 @@ + + + + badge + Created with Sketch. + + + + + + Transpor + t + + + Compatibl + e + + + \ No newline at end of file From e1b7badc2d41defde1ead8d7497d6ca32a1ebfe4 Mon Sep 17 00:00:00 2001 From: David Dias Date: Wed, 16 Sep 2015 17:32:10 +0100 Subject: [PATCH 03/66] landing --- README.md | 63 ++++++++++++++++++++++++++++++++++++++++++++-- package.json | 18 +++++++++++++ tests/base-test.js | 8 ++++++ tests/index.js | 6 +++++ 4 files changed, 93 insertions(+), 2 deletions(-) create mode 100644 package.json create mode 100644 tests/base-test.js create mode 100644 tests/index.js diff --git a/README.md b/README.md index c2b5110..3140d22 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,61 @@ -# abstract-transport -A test suite and interface you can use to implement a transport interface. +abstract-transport +================== + +[![](https://img.shields.io/badge/made%20by-Protocol%20Labs-blue.svg?style=flat-square)](http://ipn.io) [![](https://img.shields.io/badge/freenode-%23ipfs-blue.svg?style=flat-square)](http://webchat.freenode.net/?channels=%23ipfs) + +> A test suite and interface you can use to implement a transport. A transport 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 abstract-stream-muxer. + +The primary goal of module is to enable developers to pick, swap or upgrade their transport without loosing the same API expectations and mechanisms such as back pressure and the hability to half close a stream. + +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 not actual limitations for it to be extended for any other language, pushing forward the cross compatibility and interop through diferent stacks. + +> **IMPORTANT** - Tests are still not finished nor the interface + + +# Modules that implement the interface + +- [node-libp2p-tcp](https://github.com/diasdavid/node-libp2p-tcp) + +# Badge + +Include this badge in your readme if you make a module that is compatible with the abstract-connection API. You can validate this by running the tests. + +![](https://raw.githubusercontent.com/diasdavid/abstract-transport/master/img/badge.png) + +# How to use the battery of tests + +## Node.js + +``` +var tape = require('tape') +var tests = require('abstract-transport/tests') +var YourTransportHandler = require('../src') + +var common = { + setup: function (t, cb) { + cb(null, YourTransportHandler) + }, + teardown: function (t, cb) { + cb() + } +} + +tests(tape, common) +``` + +## Go + +> WIP + +# API + +A valid (read: that follows this abstraction) connection, must implement the following API. + +notes: + - 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 + diff --git a/package.json b/package.json new file mode 100644 index 0000000..2705582 --- /dev/null +++ b/package.json @@ -0,0 +1,18 @@ +{ + "name": "abstract-transport", + "version": "0.0.0", + "description": "A test suite and interface you can use to implement a transport interface.", + "repository": { + "type": "git", + "url": "https://github.com/diasdavid/abstract-transport.git" + }, + "keywords": [ + "IPFS" + ], + "author": "David Dias ", + "license": "MIT", + "bugs": { + "url": "https://github.com/diasdavid/abstract-transport/issues" + }, + "homepage": "https://github.com/diasdavid/abstract-transport" +} diff --git a/tests/base-test.js b/tests/base-test.js new file mode 100644 index 0000000..4d42a06 --- /dev/null +++ b/tests/base-test.js @@ -0,0 +1,8 @@ +module.exports.all = function (test, common) { + test('a test', function (t) { + common.setup(test, function (err, conn) { + t.ifError(err) + t.end() + }) + }) +} diff --git a/tests/index.js b/tests/index.js new file mode 100644 index 0000000..b232406 --- /dev/null +++ b/tests/index.js @@ -0,0 +1,6 @@ +var timed = require('timed-tape') + +module.exports = function (test, common) { + test = timed(test) + require('./base-test.js').all(test, common) +} From 3b0a98ccf0dcfb0043ec40cd7c4366a8536f7f2b Mon Sep 17 00:00:00 2001 From: David Dias Date: Thu, 17 Sep 2015 02:44:07 +0100 Subject: [PATCH 04/66] rename, following https://github.com/diasdavid/node-ipfs-swarm/issues/8#issuecomment-140929746 --- README.md | 14 +++++++------- img/badge.png | Bin 5226 -> 5258 bytes img/badge.sketch | Bin 40960 -> 65536 bytes img/badge.svg | 8 ++++---- package.json | 10 +++++----- 5 files changed, 16 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 3140d22..a35cbe4 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,11 @@ -abstract-transport +abstract-connection ================== [![](https://img.shields.io/badge/made%20by-Protocol%20Labs-blue.svg?style=flat-square)](http://ipn.io) [![](https://img.shields.io/badge/freenode-%23ipfs-blue.svg?style=flat-square)](http://webchat.freenode.net/?channels=%23ipfs) -> A test suite and interface you can use to implement a transport. A transport 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 abstract-stream-muxer. +> 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 abstract-stream-muxer. -The primary goal of module is to enable developers to pick, swap or upgrade their transport without loosing the same API expectations and mechanisms such as back pressure and the hability to half close a stream. +The primary goal of module is to enable developers to pick, swap or upgrade their connection without loosing the same API expectations and mechanisms such as back pressure and the hability 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. @@ -22,7 +22,7 @@ The API is presented with both Node.js and Go primitives, however, there is not Include this badge in your readme if you make a module that is compatible with the abstract-connection API. You can validate this by running the tests. -![](https://raw.githubusercontent.com/diasdavid/abstract-transport/master/img/badge.png) +![](https://raw.githubusercontent.com/diasdavid/abstract-connection/master/img/badge.png) # How to use the battery of tests @@ -30,12 +30,12 @@ Include this badge in your readme if you make a module that is compatible with t ``` var tape = require('tape') -var tests = require('abstract-transport/tests') -var YourTransportHandler = require('../src') +var tests = require('abstract-connection/tests') +var YourConnectionHandler = require('../src') var common = { setup: function (t, cb) { - cb(null, YourTransportHandler) + cb(null, YourConnectionHandler) }, teardown: function (t, cb) { cb() diff --git a/img/badge.png b/img/badge.png index 1c8fe6972b5957adcd6088961701fda1ff5cf368..a79ca44967796cd1569661767d3bfbe09a1ad512 100644 GIT binary patch delta 5257 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 delta 12580 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 JfB6_B{2wM#je`IH diff --git a/img/badge.svg b/img/badge.svg index aa56dac..a240ced 100644 --- a/img/badge.svg +++ b/img/badge.svg @@ -6,10 +6,10 @@ - - - Transpor - t + + + Connectio + n Compatibl diff --git a/package.json b/package.json index 2705582..67d24e9 100644 --- a/package.json +++ b/package.json @@ -1,10 +1,10 @@ { - "name": "abstract-transport", + "name": "abstract-connection", "version": "0.0.0", - "description": "A test suite and interface you can use to implement a transport interface.", + "description": "A test suite and interface you can use to implement a connection interface.", "repository": { "type": "git", - "url": "https://github.com/diasdavid/abstract-transport.git" + "url": "https://github.com/diasdavid/abstract-connection.git" }, "keywords": [ "IPFS" @@ -12,7 +12,7 @@ "author": "David Dias ", "license": "MIT", "bugs": { - "url": "https://github.com/diasdavid/abstract-transport/issues" + "url": "https://github.com/diasdavid/abstract-connection/issues" }, - "homepage": "https://github.com/diasdavid/abstract-transport" + "homepage": "https://github.com/diasdavid/abstract-connection" } From 1a2497cebcbc9f0d36ee0f7ad419f4465b563544 Mon Sep 17 00:00:00 2001 From: David Dias Date: Thu, 17 Sep 2015 02:45:02 +0100 Subject: [PATCH 05/66] Release v0.0.1. --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 67d24e9..b949b7a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "abstract-connection", - "version": "0.0.0", + "version": "0.0.1", "description": "A test suite and interface you can use to implement a connection interface.", "repository": { "type": "git", From 4b8ca19b1030cd15c56ee179e8dc32dec0255bdd Mon Sep 17 00:00:00 2001 From: Pau Ramon Revilla Date: Fri, 30 Oct 2015 09:06:04 +0100 Subject: [PATCH 06/66] Added missing dependency --- package.json | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index b949b7a..ababa2f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "abstract-connection", - "version": "0.0.1", + "version": "0.0.2", "description": "A test suite and interface you can use to implement a connection interface.", "repository": { "type": "git", @@ -14,5 +14,8 @@ "bugs": { "url": "https://github.com/diasdavid/abstract-connection/issues" }, - "homepage": "https://github.com/diasdavid/abstract-connection" + "homepage": "https://github.com/diasdavid/abstract-connection", + "dependencies": { + "timed-tape": "^0.1.0" + } } From 2af97d4e79b1f20435ab364a819b48906883b85a Mon Sep 17 00:00:00 2001 From: David Dias Date: Fri, 11 Dec 2015 20:38:40 -0800 Subject: [PATCH 07/66] update name --- README.md | 10 +++++----- package.json | 8 ++++---- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index a35cbe4..e885054 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,9 @@ -abstract-connection +interface-connection ================== [![](https://img.shields.io/badge/made%20by-Protocol%20Labs-blue.svg?style=flat-square)](http://ipn.io) [![](https://img.shields.io/badge/freenode-%23ipfs-blue.svg?style=flat-square)](http://webchat.freenode.net/?channels=%23ipfs) -> 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 abstract-stream-muxer. +> 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. The primary goal of module is to enable developers to pick, swap or upgrade their connection without loosing the same API expectations and mechanisms such as back pressure and the hability to half close a connection. @@ -20,9 +20,9 @@ The API is presented with both Node.js and Go primitives, however, there is not # Badge -Include this badge in your readme if you make a module that is compatible with the abstract-connection API. You can validate this by running the tests. +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. -![](https://raw.githubusercontent.com/diasdavid/abstract-connection/master/img/badge.png) +![](https://raw.githubusercontent.com/diasdavid/interface-connection/master/img/badge.png) # How to use the battery of tests @@ -30,7 +30,7 @@ Include this badge in your readme if you make a module that is compatible with t ``` var tape = require('tape') -var tests = require('abstract-connection/tests') +var tests = require('interface-connection/tests') var YourConnectionHandler = require('../src') var common = { diff --git a/package.json b/package.json index ababa2f..350f9ab 100644 --- a/package.json +++ b/package.json @@ -1,10 +1,10 @@ { - "name": "abstract-connection", + "name": "interface-connection", "version": "0.0.2", "description": "A test suite and interface you can use to implement a connection interface.", "repository": { "type": "git", - "url": "https://github.com/diasdavid/abstract-connection.git" + "url": "https://github.com/diasdavid/interface-connection.git" }, "keywords": [ "IPFS" @@ -12,9 +12,9 @@ "author": "David Dias ", "license": "MIT", "bugs": { - "url": "https://github.com/diasdavid/abstract-connection/issues" + "url": "https://github.com/diasdavid/interface-connection/issues" }, - "homepage": "https://github.com/diasdavid/abstract-connection", + "homepage": "https://github.com/diasdavid/interface-connection", "dependencies": { "timed-tape": "^0.1.0" } From 0f710752d976853e44449df758b4ff7b8d19541f Mon Sep 17 00:00:00 2001 From: David Dias Date: Fri, 11 Dec 2015 20:38:44 -0800 Subject: [PATCH 08/66] Release v0.0.3. --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 350f9ab..8ce1c49 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "interface-connection", - "version": "0.0.2", + "version": "0.0.3", "description": "A test suite and interface you can use to implement a connection interface.", "repository": { "type": "git", From e6d0b81f63b76952aae7c7d368facb3d825b7067 Mon Sep 17 00:00:00 2001 From: David Dias Date: Sun, 12 Jun 2016 11:01:32 -0700 Subject: [PATCH 09/66] add required methods by the consumers of the conn obj, to the conn interface --- .gitignore | 3 +++ .npmignore | 29 ++++++++++++++++++++++ README.md | 61 +++++++++++++++++++++++++++++++++++++++++------ package.json | 18 ++++++++++++++ src/connection.js | 53 ++++++++++++++++++++++++++++++++++++++++ src/index.js | 4 ++++ test/test.spec.js | 3 +++ 7 files changed, 164 insertions(+), 7 deletions(-) create mode 100644 .npmignore create mode 100644 src/connection.js create mode 100644 src/index.js create mode 100644 test/test.spec.js diff --git a/.gitignore b/.gitignore index 123ae94..5cd7358 100644 --- a/.gitignore +++ b/.gitignore @@ -25,3 +25,6 @@ build/Release # Dependency directory # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git node_modules + +lib +dist diff --git a/.npmignore b/.npmignore new file mode 100644 index 0000000..00a160d --- /dev/null +++ b/.npmignore @@ -0,0 +1,29 @@ +# Logs +logs +*.log + +# Runtime data +pids +*.pid +*.seed + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage + +# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (http://nodejs.org/api/addons.html) +build/Release + +# Dependency directory +# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git +node_modules + +test diff --git a/README.md b/README.md index e885054..868ee83 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,8 @@ interface-connection ================== -[![](https://img.shields.io/badge/made%20by-Protocol%20Labs-blue.svg?style=flat-square)](http://ipn.io) [![](https://img.shields.io/badge/freenode-%23ipfs-blue.svg?style=flat-square)](http://webchat.freenode.net/?channels=%23ipfs) +[![](https://img.shields.io/badge/made%20by-Protocol%20Labs-blue.svg?style=flat-square)](http://ipn.io) +[![](https://img.shields.io/badge/freenode-%23ipfs-blue.svg?style=flat-square)](http://webchat.freenode.net/?channels=%23ipfs) > 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. @@ -11,12 +12,16 @@ Publishing a test suite as a module lets multiple modules all ensure compatibili The API is presented with both Node.js and Go primitives, however, there is not actual limitations for it to be extended for any other language, pushing forward the cross compatibility and interop through diferent stacks. -> **IMPORTANT** - Tests are still not finished nor the interface - - # Modules that implement the interface -- [node-libp2p-tcp](https://github.com/diasdavid/node-libp2p-tcp) +- [js-libp2p-tcp](https://github.com/diasdavid/node-libp2p-tcp) +- [js-libp2p-webrtc-star](https://github.com/diasdavid/js-libp2p-webrtc-star) +- [js-libp2p-websockets](https://github.com/diasdavid/js-libp2p-websockets) +- [js-libp2p-utp](https://github.com/diasdavid/js-libp2p-utp) +- [webrtc-explorer](https://github.com/diasdavid/webrtc-explorer) +- [js-libp2p-spdy](https://github.com/diasdavid/js-libp2p-spdy) +- [js-libp2p-multiplex](https://github.com/diasdavid/js-libp2p-multiplex) +- [js-libp2p-secio](https://github.com/ipfs/js-libp2p-secio) # Badge @@ -53,9 +58,51 @@ tests(tape, common) A valid (read: that follows this abstraction) connection, must implement the following API. -notes: +**Table of contents:** + +- type: `Connection` + - `conn.getObservedAddrs(callback)` + - `conn.getPeerInfo(callback)` + - `conn.setPeerInfo(peerInfo)` + - `conn.destroy` + - `conn.write` + - `conn.read` + - `conn.pipe` + - `conn.end` + - `conn.pause` + - `conn.resume` + - `conn.destroy` + - `...` + +### Get the Observed Addresses of the peer in the other end + +- `JavaScript` - `conn.getObservedAddrs(callback)` + +This method retrieves the observed addresses we get from the underlying transport, if any. + +`callback` should follow the follow `function (err, multiaddrs) {}`, where `multiaddrs` is an array of [multiaddr](https://github.com/jbenet/multiaddr). + +### Get the PeerInfo + +- `JavaScript` - `conn.getPeerInfo(callback)` + +This method retrieves the a Peer Info object that contains information about the peer that this conn connects to. + +`callback` should follow the `function (err, peerInfo) {}` signature, where peerInfo is a object of type [Peer Info](https://github.com/diasdavid/js-peer-info) + +### Set the PeerInfo + +- `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. + +`peerInfo` is a object of type [Peer Info](https://github.com/diasdavid/js-peer-info) + +--- + +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 - diff --git a/package.json b/package.json index 8ce1c49..e660a21 100644 --- a/package.json +++ b/package.json @@ -2,6 +2,20 @@ "name": "interface-connection", "version": "0.0.3", "description": "A test suite and interface you can use to implement a connection interface.", + "main": "lib/index.js", + "jsnext:main": "src/index.js", + "scripts": { + "lint": "aegir-lint", + "build": "aegir-build", + "test": "exit 0", + "release": "aegir-release", + "release-minor": "aegir-release --type minor", + "release-major": "aegir-release --type major" + }, + "pre-commit": [ + "lint", + "test" + ], "repository": { "type": "git", "url": "https://github.com/diasdavid/interface-connection.git" @@ -16,6 +30,10 @@ }, "homepage": "https://github.com/diasdavid/interface-connection", "dependencies": { + "duplexify": "^3.4.3", "timed-tape": "^0.1.0" + }, + "devDependencies": { + "aegir": "^3.2.0" } } diff --git a/src/connection.js b/src/connection.js new file mode 100644 index 0000000..72d4766 --- /dev/null +++ b/src/connection.js @@ -0,0 +1,53 @@ +'use strict' + +const util = require('util') +const Duplexify = require('duplexify') + +module.exports = Connection + +util.inherits(Connection, Duplexify) + +function Connection (conn) { + if (!(this instanceof Connection)) { + return new Connection(conn) + } + + Duplexify.call(this) + + let peerInfo + + this.getPeerInfo = (callback) => { + if (conn.getPeerInfo) { + return conn.getPeerInfo(callback) + } + + if (!peerInfo) { + return callback(new Error('Peer Info not set yet')) + } + + callback(null, peerInfo) + } + + this.setPeerInfo = (_peerInfo) => { + if (conn.setPeerInfo) { + return conn.setPeerInfo(_peerInfo) + } + peerInfo = _peerInfo + } + + this.getObservedAddrs = (callback) => { + if (conn.getObservedAddrs) { + return conn.getObservedAddrs(callback) + } + callback(null, []) + } + + this.setInnerConn = (conn) => { + this.setReadable(conn) + this.setWritable(conn) + } + + if (conn) { + this.setInnerConn(conn) + } +} diff --git a/src/index.js b/src/index.js new file mode 100644 index 0000000..c15a2c7 --- /dev/null +++ b/src/index.js @@ -0,0 +1,4 @@ +'use strict' + +exports.connection = require('./connection.js') +exports.Connection = require('./connection.js') diff --git a/test/test.spec.js b/test/test.spec.js new file mode 100644 index 0000000..68b07e9 --- /dev/null +++ b/test/test.spec.js @@ -0,0 +1,3 @@ +'use strict' + +// so that aegir does not burp From 1c0c2776b1fa1d91228c88b70252d14200b403e0 Mon Sep 17 00:00:00 2001 From: David Dias Date: Thu, 16 Jun 2016 11:21:16 +0100 Subject: [PATCH 10/66] chore: update contributors --- package.json | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index e660a21..599fea2 100644 --- a/package.json +++ b/package.json @@ -35,5 +35,9 @@ }, "devDependencies": { "aegir": "^3.2.0" - } -} + }, + "contributors": [ + "David Dias ", + "Pau Ramon Revilla " + ] +} \ No newline at end of file From 16a8cdac6f6b13e4c39629a118ffb94ef6bae0b7 Mon Sep 17 00:00:00 2001 From: David Dias Date: Thu, 16 Jun 2016 11:21:16 +0100 Subject: [PATCH 11/66] chore: release version v0.1.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 599fea2..871df0e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "interface-connection", - "version": "0.0.3", + "version": "0.1.0", "description": "A test suite and interface you can use to implement a connection interface.", "main": "lib/index.js", "jsnext:main": "src/index.js", From 8f24d66f4e2a8b1d588bd1376fca8ab7ddbd6134 Mon Sep 17 00:00:00 2001 From: David Dias Date: Thu, 16 Jun 2016 11:45:16 +0100 Subject: [PATCH 12/66] fix version --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 871df0e..bb0936e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "interface-connection", - "version": "0.1.0", + "version": "0.1.2", "description": "A test suite and interface you can use to implement a connection interface.", "main": "lib/index.js", "jsnext:main": "src/index.js", @@ -40,4 +40,4 @@ "David Dias ", "Pau Ramon Revilla " ] -} \ No newline at end of file +} From 05200b9b01f7c509ee4a2a40f69dba86c2931a5b Mon Sep 17 00:00:00 2001 From: David Dias Date: Thu, 16 Jun 2016 11:45:41 +0100 Subject: [PATCH 13/66] chore: update contributors --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index bb0936e..52816fe 100644 --- a/package.json +++ b/package.json @@ -40,4 +40,4 @@ "David Dias ", "Pau Ramon Revilla " ] -} +} \ No newline at end of file From 3af70ac2e8c8c6aecddb9a73f8843d4ddbc80b99 Mon Sep 17 00:00:00 2001 From: David Dias Date: Thu, 16 Jun 2016 11:45:41 +0100 Subject: [PATCH 14/66] chore: release version v0.1.3 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 52816fe..48261a7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "interface-connection", - "version": "0.1.2", + "version": "0.1.3", "description": "A test suite and interface you can use to implement a connection interface.", "main": "lib/index.js", "jsnext:main": "src/index.js", From eb7f16a202cc225af2b815aebc18f01504158849 Mon Sep 17 00:00:00 2001 From: David Dias Date: Thu, 23 Jun 2016 10:22:20 +0100 Subject: [PATCH 15/66] add destroy method --- src/connection.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/connection.js b/src/connection.js index 72d4766..91725fd 100644 --- a/src/connection.js +++ b/src/connection.js @@ -47,6 +47,13 @@ function Connection (conn) { this.setWritable(conn) } + this.destroy = () => { + if (conn.destroy) { + conn.destroy() + } + this.end() + } + if (conn) { this.setInnerConn(conn) } From 1e4182691a28c18823640c5b45f44edfafceaf26 Mon Sep 17 00:00:00 2001 From: David Dias Date: Thu, 23 Jun 2016 10:23:53 +0100 Subject: [PATCH 17/66] chore: release version v0.1.4 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 48261a7..545a839 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "interface-connection", - "version": "0.1.3", + "version": "0.1.4", "description": "A test suite and interface you can use to implement a connection interface.", "main": "lib/index.js", "jsnext:main": "src/index.js", From a6da1000f5e9221410d1d0506dd5e0ed83b0f007 Mon Sep 17 00:00:00 2001 From: David Dias Date: Thu, 23 Jun 2016 10:33:40 +0100 Subject: [PATCH 18/66] fix .destroy --- src/connection.js | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/connection.js b/src/connection.js index 91725fd..64d4786 100644 --- a/src/connection.js +++ b/src/connection.js @@ -47,12 +47,7 @@ function Connection (conn) { this.setWritable(conn) } - this.destroy = () => { - if (conn.destroy) { - conn.destroy() - } - this.end() - } + // .destroy is implemented by Duplexify if (conn) { this.setInnerConn(conn) From ee745d23a76dcc339912cfbca0a5732399dcd37a Mon Sep 17 00:00:00 2001 From: David Dias Date: Thu, 23 Jun 2016 10:33:53 +0100 Subject: [PATCH 20/66] chore: release version v0.1.5 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 545a839..b88229e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "interface-connection", - "version": "0.1.4", + "version": "0.1.5", "description": "A test suite and interface you can use to implement a connection interface.", "main": "lib/index.js", "jsnext:main": "src/index.js", From de465f49864a0ef96541f32339cccac59c96d117 Mon Sep 17 00:00:00 2001 From: David Dias Date: Thu, 23 Jun 2016 16:09:26 +0100 Subject: [PATCH 21/66] use fork fix --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index b88229e..876e708 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,7 @@ }, "homepage": "https://github.com/diasdavid/interface-connection", "dependencies": { - "duplexify": "^3.4.3", + "duplexify": "diasdavid/duplexify#048ec46", "timed-tape": "^0.1.0" }, "devDependencies": { @@ -40,4 +40,4 @@ "David Dias ", "Pau Ramon Revilla " ] -} \ No newline at end of file +} From ce3ea1d444a1611ed9948c5a66bf1d5a47d04845 Mon Sep 17 00:00:00 2001 From: David Dias Date: Thu, 23 Jun 2016 16:09:39 +0100 Subject: [PATCH 22/66] chore: update contributors --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 876e708..6393e07 100644 --- a/package.json +++ b/package.json @@ -40,4 +40,4 @@ "David Dias ", "Pau Ramon Revilla " ] -} +} \ No newline at end of file From a8178c3c77265e7a223e20acd9c70a526461571c Mon Sep 17 00:00:00 2001 From: David Dias Date: Thu, 23 Jun 2016 16:09:39 +0100 Subject: [PATCH 23/66] chore: release version v0.1.6 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 6393e07..a7081db 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "interface-connection", - "version": "0.1.5", + "version": "0.1.6", "description": "A test suite and interface you can use to implement a connection interface.", "main": "lib/index.js", "jsnext:main": "src/index.js", From 5c1d18924d2a6412201963d31fe6ab9f3d79db47 Mon Sep 17 00:00:00 2001 From: David Dias Date: Mon, 27 Jun 2016 10:06:09 +0100 Subject: [PATCH 24/66] assign conn to the object level scope --- src/connection.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/connection.js b/src/connection.js index 64d4786..03458d5 100644 --- a/src/connection.js +++ b/src/connection.js @@ -17,7 +17,7 @@ function Connection (conn) { let peerInfo this.getPeerInfo = (callback) => { - if (conn.getPeerInfo) { + if (conn && conn.getPeerInfo) { return conn.getPeerInfo(callback) } @@ -29,20 +29,21 @@ function Connection (conn) { } this.setPeerInfo = (_peerInfo) => { - if (conn.setPeerInfo) { + if (conn && conn.setPeerInfo) { return conn.setPeerInfo(_peerInfo) } peerInfo = _peerInfo } this.getObservedAddrs = (callback) => { - if (conn.getObservedAddrs) { + if (conn && conn.getObservedAddrs) { return conn.getObservedAddrs(callback) } callback(null, []) } - this.setInnerConn = (conn) => { + this.setInnerConn = (_conn) => { + conn = _conn this.setReadable(conn) this.setWritable(conn) } From 4f2058a51511181e111ff4d08509fb5f6885a1cf Mon Sep 17 00:00:00 2001 From: David Dias Date: Mon, 27 Jun 2016 10:10:02 +0100 Subject: [PATCH 26/66] chore: release version v0.1.7 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index a7081db..892dd55 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "interface-connection", - "version": "0.1.6", + "version": "0.1.7", "description": "A test suite and interface you can use to implement a connection interface.", "main": "lib/index.js", "jsnext:main": "src/index.js", From 9855579edd3e20cf4b510debbdca0c4cd113a38c Mon Sep 17 00:00:00 2001 From: David Dias Date: Wed, 3 Aug 2016 14:17:53 +0100 Subject: [PATCH 27/66] update duplexify fork, fix #6 --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 892dd55..56709eb 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,7 @@ }, "homepage": "https://github.com/diasdavid/interface-connection", "dependencies": { - "duplexify": "diasdavid/duplexify#048ec46", + "duplexify": "diasdavid/duplexify#a22bcdf", "timed-tape": "^0.1.0" }, "devDependencies": { @@ -40,4 +40,4 @@ "David Dias ", "Pau Ramon Revilla " ] -} \ No newline at end of file +} From 51ea1a6e333ee3b8e37a916f973effbdf8e5e7e9 Mon Sep 17 00:00:00 2001 From: David Dias Date: Wed, 3 Aug 2016 14:19:07 +0100 Subject: [PATCH 28/66] update aegir --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 56709eb..ed92761 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,7 @@ "timed-tape": "^0.1.0" }, "devDependencies": { - "aegir": "^3.2.0" + "aegir": "^4.0.0" }, "contributors": [ "David Dias ", From 3e9621562714ac4426161b0bd7ff2fc0e74962dd Mon Sep 17 00:00:00 2001 From: David Dias Date: Wed, 3 Aug 2016 14:24:06 +0100 Subject: [PATCH 29/66] chore: update contributors --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ed92761..7b3d1d7 100644 --- a/package.json +++ b/package.json @@ -40,4 +40,4 @@ "David Dias ", "Pau Ramon Revilla " ] -} +} \ No newline at end of file From 2ecebf14ed4f15d421d2251b3fef0e04b914435b Mon Sep 17 00:00:00 2001 From: David Dias Date: Wed, 3 Aug 2016 14:24:06 +0100 Subject: [PATCH 30/66] chore: release version v0.1.8 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 7b3d1d7..62a2973 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "interface-connection", - "version": "0.1.7", + "version": "0.1.8", "description": "A test suite and interface you can use to implement a connection interface.", "main": "lib/index.js", "jsnext:main": "src/index.js", From 26c92261d1ebf1966c273aa177f38f2ad7740fd6 Mon Sep 17 00:00:00 2001 From: greenkeeperio-bot Date: Tue, 9 Aug 2016 11:50:55 +0200 Subject: [PATCH 31/66] chore(package): update aegir to version 6.0.1 https://greenkeeper.io/ --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 62a2973..246dc11 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,7 @@ "timed-tape": "^0.1.0" }, "devDependencies": { - "aegir": "^4.0.0" + "aegir": "^6.0.1" }, "contributors": [ "David Dias ", From 9ad355de5aef520cccc649ba44fab8e44fd1e12a Mon Sep 17 00:00:00 2001 From: greenkeeperio-bot Date: Thu, 1 Sep 2016 22:07:15 +0200 Subject: [PATCH 32/66] chore(package): update timed-tape to version 0.1.1 https://greenkeeper.io/ --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 246dc11..93aea36 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,7 @@ "homepage": "https://github.com/diasdavid/interface-connection", "dependencies": { "duplexify": "diasdavid/duplexify#a22bcdf", - "timed-tape": "^0.1.0" + "timed-tape": "^0.1.1" }, "devDependencies": { "aegir": "^6.0.1" From ed5727a0fe47add34a752f46094b8bf5835cab49 Mon Sep 17 00:00:00 2001 From: dignifiedquire Date: Tue, 9 Aug 2016 12:16:16 +0200 Subject: [PATCH 33/66] feat(connection): migrate to pull-streams --- README.md | 8 ----- package.json | 6 ++-- src/connection.js | 68 +++++++++++++++++++----------------- src/index.js | 3 +- {tests => test}/base-test.js | 2 ++ {tests => test}/index.js | 2 ++ test/test.spec.js | 3 -- 7 files changed, 44 insertions(+), 48 deletions(-) rename {tests => test}/base-test.js (92%) rename {tests => test}/index.js (91%) delete mode 100644 test/test.spec.js diff --git a/README.md b/README.md index 868ee83..07f962f 100644 --- a/README.md +++ b/README.md @@ -64,14 +64,6 @@ A valid (read: that follows this abstraction) connection, must implement the fol - `conn.getObservedAddrs(callback)` - `conn.getPeerInfo(callback)` - `conn.setPeerInfo(peerInfo)` - - `conn.destroy` - - `conn.write` - - `conn.read` - - `conn.pipe` - - `conn.end` - - `conn.pause` - - `conn.resume` - - `conn.destroy` - `...` ### Get the Observed Addresses of the peer in the other end diff --git a/package.json b/package.json index 93aea36..a4ed3b7 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "interface-connection", "version": "0.1.8", "description": "A test suite and interface you can use to implement a connection interface.", - "main": "lib/index.js", + "main": "src/index.js", "jsnext:main": "src/index.js", "scripts": { "lint": "aegir-lint", @@ -30,8 +30,8 @@ }, "homepage": "https://github.com/diasdavid/interface-connection", "dependencies": { - "duplexify": "diasdavid/duplexify#a22bcdf", "timed-tape": "^0.1.1" + "pull-defer": "^0.2.2", }, "devDependencies": { "aegir": "^6.0.1" @@ -40,4 +40,4 @@ "David Dias ", "Pau Ramon Revilla " ] -} \ No newline at end of file +} diff --git a/src/connection.js b/src/connection.js index 03458d5..02f2786 100644 --- a/src/connection.js +++ b/src/connection.js @@ -1,56 +1,60 @@ 'use strict' -const util = require('util') -const Duplexify = require('duplexify') +const defer = require('pull-defer/duplex') -module.exports = Connection +module.exports = class Connection { + constructor (conn, info) { + this.peerInfo = null + this.conn = defer() -util.inherits(Connection, Duplexify) - -function Connection (conn) { - if (!(this instanceof Connection)) { - return new Connection(conn) + if (conn) { + this.setInnerConn(conn, info) + } else if (info) { + this.info = info + } } - Duplexify.call(this) + get source () { + return this.conn.source + } - let peerInfo + get sink () { + return this.conn.sink + } - this.getPeerInfo = (callback) => { - if (conn && conn.getPeerInfo) { - return conn.getPeerInfo(callback) + getPeerInfo (callback) { + if (this.info && this.info.getPeerInfo) { + return this.info.getPeerInfo(callback) } - if (!peerInfo) { + if (!this.peerInfo) { return callback(new Error('Peer Info not set yet')) } - callback(null, peerInfo) + callback(null, this.peerInfo) } - this.setPeerInfo = (_peerInfo) => { - if (conn && conn.setPeerInfo) { - return conn.setPeerInfo(_peerInfo) + setPeerInfo (peerInfo) { + if (this.info && this.info.setPeerInfo) { + return this.info.setPeerInfo(peerInfo) } - peerInfo = _peerInfo + + this.peerInfo = peerInfo } - this.getObservedAddrs = (callback) => { - if (conn && conn.getObservedAddrs) { - return conn.getObservedAddrs(callback) + getObservedAddrs (callback) { + if (this.info && this.info.getObservedAddrs) { + return this.info.getObservedAddrs(callback) } callback(null, []) } - this.setInnerConn = (_conn) => { - conn = _conn - this.setReadable(conn) - this.setWritable(conn) - } - - // .destroy is implemented by Duplexify - - if (conn) { - this.setInnerConn(conn) + setInnerConn (conn, info) { + this.conn.resolve(conn) + if (info) { + this.info = info + } else { + this.info = conn + } } } diff --git a/src/index.js b/src/index.js index c15a2c7..c4c79fd 100644 --- a/src/index.js +++ b/src/index.js @@ -1,4 +1,3 @@ 'use strict' -exports.connection = require('./connection.js') -exports.Connection = require('./connection.js') +exports.Connection = require('./connection') diff --git a/tests/base-test.js b/test/base-test.js similarity index 92% rename from tests/base-test.js rename to test/base-test.js index 4d42a06..b3f0a78 100644 --- a/tests/base-test.js +++ b/test/base-test.js @@ -1,3 +1,5 @@ +'use strict' + module.exports.all = function (test, common) { test('a test', function (t) { common.setup(test, function (err, conn) { diff --git a/tests/index.js b/test/index.js similarity index 91% rename from tests/index.js rename to test/index.js index b232406..5438379 100644 --- a/tests/index.js +++ b/test/index.js @@ -1,3 +1,5 @@ +'use strict' + var timed = require('timed-tape') module.exports = function (test, common) { diff --git a/test/test.spec.js b/test/test.spec.js deleted file mode 100644 index 68b07e9..0000000 --- a/test/test.spec.js +++ /dev/null @@ -1,3 +0,0 @@ -'use strict' - -// so that aegir does not burp From e0f7db33bc7b311196c1afc9abbdc62ec358d9aa Mon Sep 17 00:00:00 2001 From: David Dias Date: Mon, 5 Sep 2016 17:49:56 -0400 Subject: [PATCH 34/66] fix(deps): fix package.json --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index a4ed3b7..1aac578 100644 --- a/package.json +++ b/package.json @@ -30,8 +30,8 @@ }, "homepage": "https://github.com/diasdavid/interface-connection", "dependencies": { - "timed-tape": "^0.1.1" - "pull-defer": "^0.2.2", + "timed-tape": "^0.1.1", + "pull-defer": "^0.2.2" }, "devDependencies": { "aegir": "^6.0.1" From 06a51f3ddcc33783309d7e2868df1396b2f0d5ce Mon Sep 17 00:00:00 2001 From: David Dias Date: Mon, 5 Sep 2016 17:54:35 -0400 Subject: [PATCH 35/66] chore: update contributors --- package.json | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 1aac578..3fdb018 100644 --- a/package.json +++ b/package.json @@ -38,6 +38,8 @@ }, "contributors": [ "David Dias ", - "Pau Ramon Revilla " + "Pau Ramon Revilla ", + "dignifiedquire ", + "greenkeeperio-bot " ] -} +} \ No newline at end of file From fda59fc8401f50c01768ce0956c61a5679ad97e4 Mon Sep 17 00:00:00 2001 From: David Dias Date: Mon, 5 Sep 2016 17:54:35 -0400 Subject: [PATCH 36/66] chore: release version v0.2.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 3fdb018..a44ec33 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "interface-connection", - "version": "0.1.8", + "version": "0.2.0", "description": "A test suite and interface you can use to implement a connection interface.", "main": "src/index.js", "jsnext:main": "src/index.js", From 84cd2ca473f05fc662d24744c66f85432a5c1941 Mon Sep 17 00:00:00 2001 From: David Dias Date: Mon, 5 Sep 2016 18:17:44 -0400 Subject: [PATCH 37/66] fix(package.json): point to right main --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index a44ec33..f09d23d 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "interface-connection", "version": "0.2.0", "description": "A test suite and interface you can use to implement a connection interface.", - "main": "src/index.js", + "main": "lib/index.js", "jsnext:main": "src/index.js", "scripts": { "lint": "aegir-lint", @@ -42,4 +42,4 @@ "dignifiedquire ", "greenkeeperio-bot " ] -} \ No newline at end of file +} From c1b28c75436a8e15203360d58dff87c1f16fcc32 Mon Sep 17 00:00:00 2001 From: David Dias Date: Mon, 5 Sep 2016 18:18:04 -0400 Subject: [PATCH 38/66] chore: update contributors --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f09d23d..a81393b 100644 --- a/package.json +++ b/package.json @@ -42,4 +42,4 @@ "dignifiedquire ", "greenkeeperio-bot " ] -} +} \ No newline at end of file From 8c9629b00b68fe500a5008df5030dd3524c7845c Mon Sep 17 00:00:00 2001 From: David Dias Date: Mon, 5 Sep 2016 18:18:04 -0400 Subject: [PATCH 39/66] chore: release version v0.2.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index a81393b..a2f41d0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "interface-connection", - "version": "0.2.0", + "version": "0.2.1", "description": "A test suite and interface you can use to implement a connection interface.", "main": "lib/index.js", "jsnext:main": "src/index.js", From 0c11d31de44d55e1a4c220503b03c36863c287a7 Mon Sep 17 00:00:00 2001 From: greenkeeperio-bot Date: Tue, 6 Sep 2016 15:11:47 +0200 Subject: [PATCH 40/66] chore(package): update aegir to version 8.0.0 https://greenkeeper.io/ --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index a2f41d0..a03fa2f 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,7 @@ "pull-defer": "^0.2.2" }, "devDependencies": { - "aegir": "^6.0.1" + "aegir": "^8.0.0" }, "contributors": [ "David Dias ", From 4798da78aa07443b7ee0c84d46d6e5ab4b82497a Mon Sep 17 00:00:00 2001 From: greenkeeperio-bot Date: Fri, 9 Sep 2016 01:34:26 +0200 Subject: [PATCH 41/66] chore(package): update aegir to version 8.0.1 https://greenkeeper.io/ --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index a03fa2f..1c7a242 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,7 @@ "pull-defer": "^0.2.2" }, "devDependencies": { - "aegir": "^8.0.0" + "aegir": "^8.0.1" }, "contributors": [ "David Dias ", From 163dd9d1e81ed04097ef6d1b65ca1513b14199ac Mon Sep 17 00:00:00 2001 From: Richard Littauer Date: Fri, 30 Sep 2016 14:41:54 -0400 Subject: [PATCH 42/66] Update README URLs based on HTTP redirects --- README.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 07f962f..447cee5 100644 --- a/README.md +++ b/README.md @@ -14,14 +14,14 @@ The API is presented with both Node.js and Go primitives, however, there is not # Modules that implement the interface -- [js-libp2p-tcp](https://github.com/diasdavid/node-libp2p-tcp) -- [js-libp2p-webrtc-star](https://github.com/diasdavid/js-libp2p-webrtc-star) -- [js-libp2p-websockets](https://github.com/diasdavid/js-libp2p-websockets) -- [js-libp2p-utp](https://github.com/diasdavid/js-libp2p-utp) +- [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/diasdavid/js-libp2p-spdy) -- [js-libp2p-multiplex](https://github.com/diasdavid/js-libp2p-multiplex) -- [js-libp2p-secio](https://github.com/ipfs/js-libp2p-secio) +- [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) # Badge @@ -72,7 +72,7 @@ A valid (read: that follows this abstraction) connection, must implement the fol This method retrieves the observed addresses we get from the underlying transport, if any. -`callback` should follow the follow `function (err, multiaddrs) {}`, where `multiaddrs` is an array of [multiaddr](https://github.com/jbenet/multiaddr). +`callback` should follow the follow `function (err, multiaddrs) {}`, where `multiaddrs` is an array of [multiaddr](https://github.com/multiformats/multiaddr). ### Get the PeerInfo @@ -80,7 +80,7 @@ This method retrieves the observed addresses we get from the underlying transpor This method retrieves the a Peer Info object that contains information about the peer that this conn connects to. -`callback` should follow the `function (err, peerInfo) {}` signature, where peerInfo is a object of type [Peer Info](https://github.com/diasdavid/js-peer-info) +`callback` should follow the `function (err, peerInfo) {}` signature, where peerInfo is a object of type [Peer Info](https://github.com/libp2p/js-peer-info) ### Set the PeerInfo From b40114c849d767c4626ddaace0065723ac080a99 Mon Sep 17 00:00:00 2001 From: Friedel Ziegelmayer Date: Tue, 1 Nov 2016 12:43:56 +0100 Subject: [PATCH 43/66] feat: async crypto + sauce labs + aegir 9 --- .gitignore | 1 - .travis.yml | 35 +++++++++++++++++++++++++++++++++++ README.md | 2 ++ circle.yml | 15 +++++++++++++++ package.json | 14 ++++++++------ 5 files changed, 60 insertions(+), 7 deletions(-) create mode 100644 .travis.yml create mode 100644 circle.yml diff --git a/.gitignore b/.gitignore index 5cd7358..907c78a 100644 --- a/.gitignore +++ b/.gitignore @@ -26,5 +26,4 @@ build/Release # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git node_modules -lib dist diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..df53c3d --- /dev/null +++ b/.travis.yml @@ -0,0 +1,35 @@ +sudo: false +language: node_js + +matrix: + include: + - node_js: 4 + env: CXX=g++-4.8 + - node_js: 6 + env: + - SAUCE=true + - CXX=g++-4.8 + - node_js: stable + env: CXX=g++-4.8 + +# Make sure we have new NPM. +before_install: + - npm install -g npm + +script: + - npm run lint + +before_script: + - export DISPLAY=:99.0 + - sh -e /etc/init.d/xvfb start + +after_success: + - npm run coverage-publish + +addons: + firefox: 'latest' + apt: + sources: + - ubuntu-toolchain-r-test + packages: + - g++-4.8 \ No newline at end of file diff --git a/README.md b/README.md index 447cee5..730bf4d 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,8 @@ interface-connection [![](https://img.shields.io/badge/made%20by-Protocol%20Labs-blue.svg?style=flat-square)](http://ipn.io) [![](https://img.shields.io/badge/freenode-%23ipfs-blue.svg?style=flat-square)](http://webchat.freenode.net/?channels=%23ipfs) +![](https://img.shields.io/badge/npm-%3E%3D3.0.0-orange.svg?style=flat-square) +![](https://img.shields.io/badge/Node.js-%3E%3D4.0.0-orange.svg?style=flat-square) > 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. diff --git a/circle.yml b/circle.yml new file mode 100644 index 0000000..95f6ee4 --- /dev/null +++ b/circle.yml @@ -0,0 +1,15 @@ +machine: + node: + version: stable + +test: + override: + - npm run lint +dependencies: + pre: + - google-chrome --version + - wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | sudo apt-key add - + - sudo sh -c 'echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list' + - sudo apt-get update + - sudo apt-get --only-upgrade install google-chrome-stable + - google-chrome --version diff --git a/package.json b/package.json index 1c7a242..6ee73d7 100644 --- a/package.json +++ b/package.json @@ -2,8 +2,7 @@ "name": "interface-connection", "version": "0.2.1", "description": "A test suite and interface you can use to implement a connection interface.", - "main": "lib/index.js", - "jsnext:main": "src/index.js", + "main": "src/index.js", "scripts": { "lint": "aegir-lint", "build": "aegir-build", @@ -18,7 +17,7 @@ ], "repository": { "type": "git", - "url": "https://github.com/diasdavid/interface-connection.git" + "url": "https://github.com/libp2p/interface-connection.git" }, "keywords": [ "IPFS" @@ -26,15 +25,18 @@ "author": "David Dias ", "license": "MIT", "bugs": { - "url": "https://github.com/diasdavid/interface-connection/issues" + "url": "https://github.com/libp2p/interface-connection/issues" }, - "homepage": "https://github.com/diasdavid/interface-connection", + "homepage": "https://github.com/libp2p/interface-connection", "dependencies": { "timed-tape": "^0.1.1", "pull-defer": "^0.2.2" }, "devDependencies": { - "aegir": "^8.0.1" + "aegir": "^9.0.0" + }, + "engines": { + "node": ">=4.0.0" }, "contributors": [ "David Dias ", From 99a7163e47378711bebe3c2c088f96076aaf40d9 Mon Sep 17 00:00:00 2001 From: David Dias Date: Thu, 3 Nov 2016 08:27:30 +0000 Subject: [PATCH 44/66] chore: update contributors --- package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 6ee73d7..b061a1c 100644 --- a/package.json +++ b/package.json @@ -40,8 +40,9 @@ }, "contributors": [ "David Dias ", + "Friedel Ziegelmayer ", "Pau Ramon Revilla ", - "dignifiedquire ", + "Richard Littauer ", "greenkeeperio-bot " ] } \ No newline at end of file From 5a78c4d732f94a0b203c76d76f252d3059f51519 Mon Sep 17 00:00:00 2001 From: David Dias Date: Thu, 3 Nov 2016 08:27:30 +0000 Subject: [PATCH 45/66] chore: release version v0.3.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index b061a1c..c75d5a9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "interface-connection", - "version": "0.2.1", + "version": "0.3.0", "description": "A test suite and interface you can use to implement a connection interface.", "main": "src/index.js", "scripts": { From 26eaf46ab967accd880c94cf03e55f5f62a6cfc6 Mon Sep 17 00:00:00 2001 From: Greenkeeper Date: Sat, 10 Dec 2016 21:04:02 +0100 Subject: [PATCH 46/66] chore(package): update aegir to version 9.2.1 (#22) https://greenkeeper.io/ --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index c75d5a9..94e0075 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,7 @@ "pull-defer": "^0.2.2" }, "devDependencies": { - "aegir": "^9.0.0" + "aegir": "^9.2.1" }, "engines": { "node": ">=4.0.0" From 6b2fb8322f3779207b8e1af30e4cf23a8d9bb024 Mon Sep 17 00:00:00 2001 From: David Dias Date: Thu, 9 Feb 2017 08:51:30 -0800 Subject: [PATCH 47/66] chore: ^ to ~ --- package.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 94e0075..35b349c 100644 --- a/package.json +++ b/package.json @@ -29,11 +29,11 @@ }, "homepage": "https://github.com/libp2p/interface-connection", "dependencies": { - "timed-tape": "^0.1.1", - "pull-defer": "^0.2.2" + "timed-tape": "~0.1.1", + "pull-defer": "~0.2.2" }, "devDependencies": { - "aegir": "^9.2.1" + "aegir": "^10.0.0" }, "engines": { "node": ">=4.0.0" @@ -45,4 +45,4 @@ "Richard Littauer ", "greenkeeperio-bot " ] -} \ No newline at end of file +} From 030c52e121d9915956f1124439d7acc7a3266e8a Mon Sep 17 00:00:00 2001 From: David Dias Date: Thu, 9 Feb 2017 08:51:38 -0800 Subject: [PATCH 48/66] chore: update contributors --- package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 35b349c..3043213 100644 --- a/package.json +++ b/package.json @@ -41,8 +41,8 @@ "contributors": [ "David Dias ", "Friedel Ziegelmayer ", + "Greenkeeper ", "Pau Ramon Revilla ", - "Richard Littauer ", - "greenkeeperio-bot " + "Richard Littauer " ] -} +} \ No newline at end of file From f7b996fd3c3eefb7611def8f79a83f1c2b4dc0a0 Mon Sep 17 00:00:00 2001 From: David Dias Date: Thu, 9 Feb 2017 08:51:38 -0800 Subject: [PATCH 49/66] chore: release version v0.3.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 3043213..94054bd 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "interface-connection", - "version": "0.3.0", + "version": "0.3.1", "description": "A test suite and interface you can use to implement a connection interface.", "main": "src/index.js", "scripts": { From 239849fe1a4f4dad624f657a39422e994da99386 Mon Sep 17 00:00:00 2001 From: David Dias Date: Thu, 16 Mar 2017 16:47:47 +0000 Subject: [PATCH 51/66] chore: update deps --- package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 94054bd..a031727 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "lint": "aegir-lint", "build": "aegir-build", "test": "exit 0", - "release": "aegir-release", + "release": "aegir-release --node", "release-minor": "aegir-release --type minor", "release-major": "aegir-release --type major" }, @@ -33,7 +33,7 @@ "pull-defer": "~0.2.2" }, "devDependencies": { - "aegir": "^10.0.0" + "aegir": "^11.0.0" }, "engines": { "node": ">=4.0.0" @@ -45,4 +45,4 @@ "Pau Ramon Revilla ", "Richard Littauer " ] -} \ No newline at end of file +} From 7cf11bef91e37b0071940da86bd0cc3fb22359c9 Mon Sep 17 00:00:00 2001 From: David Dias Date: Tue, 21 Mar 2017 14:08:50 +0000 Subject: [PATCH 52/66] chore: add engine --- package.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index a031727..09dee39 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "interface-connection", - "version": "0.3.1", + "version": "0.3.2", "description": "A test suite and interface you can use to implement a connection interface.", "main": "src/index.js", "scripts": { @@ -36,7 +36,8 @@ "aegir": "^11.0.0" }, "engines": { - "node": ">=4.0.0" + "node": ">=4.0.0", + "npm": ">=3.0.0" }, "contributors": [ "David Dias ", From 3c82be8421120cc35ea9d9c576ac32e80b5a7afa Mon Sep 17 00:00:00 2001 From: James Ray <16969914+jamesray1@users.noreply.github.com> Date: Mon, 25 Jun 2018 21:06:39 +1000 Subject: [PATCH 53/66] Grammar fixes (#25) --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 730bf4d..6a2e31a 100644 --- a/README.md +++ b/README.md @@ -6,13 +6,13 @@ interface-connection ![](https://img.shields.io/badge/npm-%3E%3D3.0.0-orange.svg?style=flat-square) ![](https://img.shields.io/badge/Node.js-%3E%3D4.0.0-orange.svg?style=flat-square) -> 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. 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. -The primary goal of module is to enable developers to pick, swap or upgrade their connection without loosing the same API expectations and mechanisms such as back pressure and the hability to half close a connection. +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 not actual limitations for it to be extended for any other language, pushing forward the cross compatibility and interop through diferent stacks. +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. # Modules that implement the interface From 902f3aa1d6f99e46a966c4ac4096bc9c727f4acc Mon Sep 17 00:00:00 2001 From: Jacob Heun Date: Fri, 9 Nov 2018 16:26:01 +0100 Subject: [PATCH 54/66] chore: add lead maintainer chore: update dependencies --- README.md | 4 ++++ package.json | 19 +++++++++---------- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 6a2e31a..f8499dc 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,10 @@ Publishing a test suite as a module lets multiple modules all ensure compatibili 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. +## Lead Maintainer + +[Jacob Heun](https://github.com/jacobheun/) + # Modules that implement the interface - [js-libp2p-tcp](https://github.com/libp2p/js-libp2p-tcp) diff --git a/package.json b/package.json index 09dee39..8b69b4a 100644 --- a/package.json +++ b/package.json @@ -2,18 +2,18 @@ "name": "interface-connection", "version": "0.3.2", "description": "A test suite and interface you can use to implement a connection interface.", + "leadMaintainer": "Jacob Heun ", "main": "src/index.js", "scripts": { - "lint": "aegir-lint", - "build": "aegir-build", - "test": "exit 0", + "lint": "aegir lint", + "build": "aegir build", + "test": "node -e 'process.exit()'", "release": "aegir-release --node", "release-minor": "aegir-release --type minor", "release-major": "aegir-release --type major" }, "pre-commit": [ - "lint", - "test" + "lint" ], "repository": { "type": "git", @@ -22,7 +22,6 @@ "keywords": [ "IPFS" ], - "author": "David Dias ", "license": "MIT", "bugs": { "url": "https://github.com/libp2p/interface-connection/issues" @@ -30,14 +29,14 @@ "homepage": "https://github.com/libp2p/interface-connection", "dependencies": { "timed-tape": "~0.1.1", - "pull-defer": "~0.2.2" + "pull-defer": "~0.2.3" }, "devDependencies": { - "aegir": "^11.0.0" + "aegir": "^17.0.1" }, "engines": { - "node": ">=4.0.0", - "npm": ">=3.0.0" + "node": ">=8.0.0", + "npm": ">=6.0.0" }, "contributors": [ "David Dias ", From 810b4600b03c3f861f1c0d7f0d9bfd5b9f2d08df Mon Sep 17 00:00:00 2001 From: Jacob Heun Date: Fri, 9 Nov 2018 16:27:16 +0100 Subject: [PATCH 55/66] chore: add files field for npm publishing --- package.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/package.json b/package.json index 8b69b4a..ed6bb6b 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,9 @@ "description": "A test suite and interface you can use to implement a connection interface.", "leadMaintainer": "Jacob Heun ", "main": "src/index.js", + "files": [ + "src" + ], "scripts": { "lint": "aegir lint", "build": "aegir build", From 6578a84236f8377871a2df4d4ffbb4b33918bcda Mon Sep 17 00:00:00 2001 From: Jacob Heun Date: Fri, 9 Nov 2018 16:29:35 +0100 Subject: [PATCH 56/66] chore: update ci --- .gitignore | 1 + .travis.yml | 24 ++---------------------- circle.yml | 15 --------------- 3 files changed, 3 insertions(+), 37 deletions(-) delete mode 100644 circle.yml diff --git a/.gitignore b/.gitignore index 907c78a..229031f 100644 --- a/.gitignore +++ b/.gitignore @@ -27,3 +27,4 @@ build/Release node_modules dist +package-lock.json diff --git a/.travis.yml b/.travis.yml index df53c3d..dc01dc2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,14 +3,9 @@ language: node_js matrix: include: - - node_js: 4 - env: CXX=g++-4.8 - - node_js: 6 - env: - - SAUCE=true - - CXX=g++-4.8 + - node_js: 8 + - node_js: 10 - node_js: stable - env: CXX=g++-4.8 # Make sure we have new NPM. before_install: @@ -18,18 +13,3 @@ before_install: script: - npm run lint - -before_script: - - export DISPLAY=:99.0 - - sh -e /etc/init.d/xvfb start - -after_success: - - npm run coverage-publish - -addons: - firefox: 'latest' - apt: - sources: - - ubuntu-toolchain-r-test - packages: - - g++-4.8 \ No newline at end of file diff --git a/circle.yml b/circle.yml deleted file mode 100644 index 95f6ee4..0000000 --- a/circle.yml +++ /dev/null @@ -1,15 +0,0 @@ -machine: - node: - version: stable - -test: - override: - - npm run lint -dependencies: - pre: - - google-chrome --version - - wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | sudo apt-key add - - - sudo sh -c 'echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list' - - sudo apt-get update - - sudo apt-get --only-upgrade install google-chrome-stable - - google-chrome --version From d7c36330bc39a5b1a2540aa40d9b43e5ef45ff59 Mon Sep 17 00:00:00 2001 From: Jacob Heun Date: Thu, 29 Nov 2018 14:19:46 +0100 Subject: [PATCH 57/66] chore: update deps --- package.json | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index ed6bb6b..3288991 100644 --- a/package.json +++ b/package.json @@ -5,15 +5,16 @@ "leadMaintainer": "Jacob Heun ", "main": "src/index.js", "files": [ + "dist", "src" ], "scripts": { "lint": "aegir lint", "build": "aegir build", "test": "node -e 'process.exit()'", - "release": "aegir-release --node", - "release-minor": "aegir-release --type minor", - "release-major": "aegir-release --type major" + "release": "aegir release --no-test", + "release-minor": "aegir release --type minor --no-test", + "release-major": "aegir release --type major --no-test" }, "pre-commit": [ "lint" @@ -31,11 +32,11 @@ }, "homepage": "https://github.com/libp2p/interface-connection", "dependencies": { - "timed-tape": "~0.1.1", "pull-defer": "~0.2.3" }, "devDependencies": { - "aegir": "^17.0.1" + "aegir": "^17.1.1", + "timed-tape": "~0.1.1" }, "engines": { "node": ">=8.0.0", From 1ac17d660e813ace8a2c26bf5efd7fdcffe477e0 Mon Sep 17 00:00:00 2001 From: Jacob Heun Date: Thu, 29 Nov 2018 14:25:11 +0100 Subject: [PATCH 58/66] chore: update contributors --- package.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 3288991..aff10a0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "interface-connection", - "version": "0.3.2", + "version": "0.3.3", "description": "A test suite and interface you can use to implement a connection interface.", "leadMaintainer": "Jacob Heun ", "main": "src/index.js", @@ -46,6 +46,8 @@ "David Dias ", "Friedel Ziegelmayer ", "Greenkeeper ", + "Jacob Heun ", + "James Ray <16969914+jamesray1@users.noreply.github.com>", "Pau Ramon Revilla ", "Richard Littauer " ] From 4e138dba674535e2b394b4508f2332dce2171962 Mon Sep 17 00:00:00 2001 From: Jacob Heun Date: Thu, 29 Nov 2018 14:25:11 +0100 Subject: [PATCH 59/66] chore: release version v0.3.3 --- CHANGELOG.md | 90 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 90 insertions(+) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..f2cbf76 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,90 @@ + +## [0.3.3](https://github.com/libp2p/interface-connection/compare/v0.3.1...v0.3.3) (2018-11-29) + + + + +## [0.3.1](https://github.com/libp2p/interface-connection/compare/v0.3.0...v0.3.1) (2017-02-09) + + + + +# [0.3.0](https://github.com/libp2p/interface-connection/compare/v0.2.1...v0.3.0) (2016-11-03) + + +### Features + +* async crypto + sauce labs + aegir 9 ([b40114c](https://github.com/libp2p/interface-connection/commit/b40114c)) + + + + +## [0.2.1](https://github.com/libp2p/interface-connection/compare/v0.2.0...v0.2.1) (2016-09-05) + + +### Bug Fixes + +* **package.json:** point to right main ([84cd2ca](https://github.com/libp2p/interface-connection/commit/84cd2ca)) + + + + +# [0.2.0](https://github.com/libp2p/interface-connection/compare/v0.1.8...v0.2.0) (2016-09-05) + + +### Bug Fixes + +* **deps:** fix package.json ([e0f7db3](https://github.com/libp2p/interface-connection/commit/e0f7db3)) + + +### Features + +* **connection:** migrate to pull-streams ([ed5727a](https://github.com/libp2p/interface-connection/commit/ed5727a)) + + + + +## [0.1.8](https://github.com/libp2p/interface-connection/compare/v0.1.7...v0.1.8) (2016-08-03) + + + + +## [0.1.7](https://github.com/libp2p/interface-connection/compare/v0.1.6...v0.1.7) (2016-06-27) + + + + +## [0.1.6](https://github.com/libp2p/interface-connection/compare/v0.1.5...v0.1.6) (2016-06-23) + + + + +## [0.1.5](https://github.com/libp2p/interface-connection/compare/v0.1.4...v0.1.5) (2016-06-23) + + + + +## [0.1.4](https://github.com/libp2p/interface-connection/compare/v0.1.3...v0.1.4) (2016-06-23) + + + + +## [0.1.3](https://github.com/libp2p/interface-connection/compare/v0.1.0...v0.1.3) (2016-06-16) + + + + +# [0.1.0](https://github.com/libp2p/interface-connection/compare/v0.0.3...v0.1.0) (2016-06-16) + + + + +## [0.0.3](https://github.com/libp2p/interface-connection/compare/v0.0.1...v0.0.3) (2015-12-12) + + + + +## 0.0.1 (2015-09-17) + + + From e06af8c4aa2fbc8013edc2a2cbe50c9259336da4 Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Fri, 26 Apr 2019 10:28:13 +0100 Subject: [PATCH 60/66] chore: add discourse badge (#30) --- .travis.yml | 25 ++++++++++++++----------- README.md | 12 ++++++++---- package.json | 2 +- 3 files changed, 23 insertions(+), 16 deletions(-) diff --git a/.travis.yml b/.travis.yml index dc01dc2..bec3772 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,15 +1,18 @@ -sudo: false language: node_js +cache: npm +stages: + - check -matrix: +node_js: + - '10' + +jobs: include: - - node_js: 8 - - node_js: 10 - - node_js: stable + - stage: check + script: + - npx aegir commitlint --travis + - npx aegir dep-check + - npm run lint -# Make sure we have new NPM. -before_install: - - npm install -g npm - -script: - - npm run lint +notifications: + email: false diff --git a/README.md b/README.md index f8499dc..8c23307 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,14 @@ interface-connection ================== -[![](https://img.shields.io/badge/made%20by-Protocol%20Labs-blue.svg?style=flat-square)](http://ipn.io) -[![](https://img.shields.io/badge/freenode-%23ipfs-blue.svg?style=flat-square)](http://webchat.freenode.net/?channels=%23ipfs) -![](https://img.shields.io/badge/npm-%3E%3D3.0.0-orange.svg?style=flat-square) -![](https://img.shields.io/badge/Node.js-%3E%3D4.0.0-orange.svg?style=flat-square) +[![](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/freenode-%23libp2p-yellow.svg?style=flat-square)](http://webchat.freenode.net/?channels=%23libp2p) +[![Discourse posts](https://img.shields.io/discourse/https/discuss.libp2p.io/posts.svg)](https://discuss.libp2p.io) +[![](https://img.shields.io/codecov/c/github/libp2p/interface-connection.svg?style=flat-square)](https://codecov.io/gh/libp2p/interface-connection) +[![](https://img.shields.io/travis/libp2p/interface-connection.svg?style=flat-square)](https://travis-ci.com/libp2p/interface-connection) +[![Dependency Status](https://david-dm.org/libp2p/interface-connection.svg?style=flat-square)](https://david-dm.org/libp2p/interface-connection) +[![js-standard-style](https://img.shields.io/badge/code%20style-standard-brightgreen.svg?style=flat-square)](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. diff --git a/package.json b/package.json index aff10a0..30a2859 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,7 @@ "pull-defer": "~0.2.3" }, "devDependencies": { - "aegir": "^17.1.1", + "aegir": "^18.2.2", "timed-tape": "~0.1.1" }, "engines": { From bf5c646441d26873f7ce9dada5699fb1048f2d5e Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Fri, 27 Sep 2019 11:13:02 +0200 Subject: [PATCH 61/66] 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. --- .travis.yml | 24 +++- README.md | 272 +++++++++++++++++++++++++++++++--------- package.json | 28 +++-- src/connection.js | 222 ++++++++++++++++++++++++++------ src/tests.js | 9 ++ test/base-test.js | 10 -- test/compliance.spec.js | 59 +++++++++ test/connection.js | 135 ++++++++++++++++++++ test/index.js | 8 -- test/utils/peers.js | 27 ++++ 10 files changed, 665 insertions(+), 129 deletions(-) create mode 100644 src/tests.js delete mode 100644 test/base-test.js create mode 100644 test/compliance.spec.js create mode 100644 test/connection.js delete mode 100644 test/index.js create mode 100644 test/utils/peers.js diff --git a/.travis.yml b/.travis.yml index bec3772..c7f74c1 100644 --- a/.travis.yml +++ b/.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 diff --git a/README.md b/README.md index 8c23307..a6b8246 100644 --- a/README.md +++ b/README.md @@ -10,101 +10,253 @@ interface-connection [![Dependency Status](https://david-dm.org/libp2p/interface-connection.svg?style=flat-square)](https://david-dm.org/libp2p/interface-connection) [![js-standard-style](https://img.shields.io/badge/code%20style-standard-brightgreen.svg?style=flat-square)](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. -![](https://raw.githubusercontent.com/diasdavid/interface-connection/master/img/badge.png) +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 + } +}) +``` + - ` conn.localAddr` + - ` conn.remoteAddr` + - ` conn.localPeer` + - ` conn.remotePeer` + - ` conn.stat` + - ` conn.registry` + - `Array conn.streams` + - `Promise conn.newStream(Array)` + - ` conn.removeStream(id)` + - ` 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. diff --git a/package.json b/package.json index 30a2859..a9e2d6b 100644 --- a/package.json +++ b/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": [ diff --git a/src/connection.js b/src/connection.js index 02f2786..77e9ecb 100644 --- a/src/connection.js +++ b/src/connection.js @@ -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} 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' }) diff --git a/src/tests.js b/src/tests.js new file mode 100644 index 0000000..8a80dae --- /dev/null +++ b/src/tests.js @@ -0,0 +1,9 @@ +/* eslint-env mocha */ + +'use strict' + +const connectionSuite = require('../test/connection') + +module.exports = (test) => { + connectionSuite(test) +} diff --git a/test/base-test.js b/test/base-test.js deleted file mode 100644 index b3f0a78..0000000 --- a/test/base-test.js +++ /dev/null @@ -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() - }) - }) -} diff --git a/test/compliance.spec.js b/test/compliance.spec.js new file mode 100644 index 0000000..e2b9b4d --- /dev/null +++ b/test/compliance.spec.js @@ -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() + } + }) +}) diff --git a/test/connection.js b/test/connection.js new file mode 100644 index 0000000..feb388e --- /dev/null +++ b/test/connection.js @@ -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') + }) + }) + }) +} diff --git a/test/index.js b/test/index.js deleted file mode 100644 index 5438379..0000000 --- a/test/index.js +++ /dev/null @@ -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) -} diff --git a/test/utils/peers.js b/test/utils/peers.js new file mode 100644 index 0000000..fad0d23 --- /dev/null +++ b/test/utils/peers.js @@ -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=' +}] From f58356aa10988b5e512294a3914eeed583ec5018 Mon Sep 17 00:00:00 2001 From: Jacob Heun Date: Fri, 27 Sep 2019 11:21:15 +0200 Subject: [PATCH 62/66] chore: update contributors --- package.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index a9e2d6b..bee3f18 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "interface-connection", - "version": "0.3.3", + "version": "0.4.0", "description": "A test suite and interface you can use to implement a connection interface.", "leadMaintainer": "Jacob Heun ", "main": "src/index.js", @@ -59,6 +59,7 @@ "Jacob Heun ", "James Ray <16969914+jamesray1@users.noreply.github.com>", "Pau Ramon Revilla ", - "Richard Littauer " + "Richard Littauer ", + "Vasco Santos " ] } From 721e475fd235f301bf8bfebcb60ca5f087cd2557 Mon Sep 17 00:00:00 2001 From: Jacob Heun Date: Fri, 27 Sep 2019 11:21:16 +0200 Subject: [PATCH 63/66] chore: release version v0.4.0 --- CHANGELOG.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f2cbf76..0c7a4b9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,18 @@ + +# [0.4.0](https://github.com/libp2p/interface-connection/compare/v0.3.3...v0.4.0) (2019-09-27) + + +### Code Refactoring + +* API changes and switch to async iterators ([#29](https://github.com/libp2p/interface-connection/issues/29)) ([bf5c646](https://github.com/libp2p/interface-connection/commit/bf5c646)) + + +### BREAKING CHANGES + +* 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. + + + ## [0.3.3](https://github.com/libp2p/interface-connection/compare/v0.3.1...v0.3.3) (2018-11-29) From 541bf83c1ea56834ceed6f658bd4d2776cfed66e Mon Sep 17 00:00:00 2001 From: Jacob Heun Date: Thu, 17 Oct 2019 14:38:37 +0200 Subject: [PATCH 64/66] feat: add support for timeline proxying (#31) --- package.json | 3 ++- src/connection.js | 4 ---- test/compliance.spec.js | 10 ++++++++-- test/connection.js | 33 ++++++++++++++++++++++++++++++++- 4 files changed, 42 insertions(+), 8 deletions(-) diff --git a/package.json b/package.json index bee3f18..cf84648 100644 --- a/package.json +++ b/package.json @@ -40,7 +40,8 @@ "dirty-chai": "^2.0.1", "err-code": "^2.0.0", "multiaddr": "^7.1.0", - "peer-id": "~0.13.2" + "peer-id": "~0.13.2", + "sinon": "^7.5.0" }, "devDependencies": { "aegir": "^20.2.0", diff --git a/src/connection.js b/src/connection.js index 77e9ecb..71ba299 100644 --- a/src/connection.js +++ b/src/connection.js @@ -75,10 +75,6 @@ class Connection { */ this._stat = { ...stat, - timeline: { - ...stat.timeline, - close: undefined - }, status: 'open' } diff --git a/test/compliance.spec.js b/test/compliance.spec.js index e2b9b4d..decbdd2 100644 --- a/test/compliance.spec.js +++ b/test/compliance.spec.js @@ -10,7 +10,12 @@ const pair = require('it-pair') describe('compliance tests', () => { tests({ - async setup () { + /** + * Test setup. `properties` allows the compliance test to override + * certain values for testing. + * @param {*} properties + */ + async setup (properties) { 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([ @@ -49,7 +54,8 @@ describe('compliance tests', () => { } }, close: () => {}, - getStreams: () => openStreams + getStreams: () => openStreams, + ...properties }) }, async teardown () { diff --git a/test/connection.js b/test/connection.js index feb388e..267760f 100644 --- a/test/connection.js +++ b/test/connection.js @@ -5,6 +5,7 @@ const chai = require('chai') const expect = chai.expect chai.use(require('dirty-chai')) +const sinon = require('sinon') module.exports = (test) => { describe('connection', () => { @@ -69,9 +70,27 @@ module.exports = (test) => { describe('close connection', () => { let connection + let timelineProxy + const proxyHandler = { + set () { + return Reflect.set(...arguments) + } + } beforeEach(async () => { - connection = await test.setup() + timelineProxy = new Proxy({ + open: Date.now() - 10, + upgraded: Date.now() + }, proxyHandler) + + connection = await test.setup({ + stat: { + timeline: timelineProxy, + direction: 'outbound', + encryption: '/crypto/1.0.0', + multiplexer: '/muxer/1.0.0' + } + }) if (!connection) throw new Error('missing connection') }) @@ -100,6 +119,18 @@ module.exports = (test) => { expect(connection.stat.status).to.equal('closed') }) + it('should support a proxy on the timeline', async () => { + sinon.spy(proxyHandler, 'set') + expect(connection.stat.timeline.close).to.not.exist() + + await connection.close() + expect(proxyHandler.set.callCount).to.equal(1) + const [obj, key, value] = proxyHandler.set.getCall(0).args + expect(obj).to.eql(connection.stat.timeline) + expect(key).to.equal('close') + expect(value).to.be.a('number').that.equals(connection.stat.timeline.close) + }) + it('should fail to create a new stream if the connection is closing', async () => { expect(connection.stat.timeline.close).to.not.exist() connection.close() From cac492bb1ecf8b804c9c2d857596051aab7fcb91 Mon Sep 17 00:00:00 2001 From: Jacob Heun Date: Thu, 17 Oct 2019 14:41:11 +0200 Subject: [PATCH 65/66] chore: update contributors --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index cf84648..def4ae6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "interface-connection", - "version": "0.4.0", + "version": "0.4.1", "description": "A test suite and interface you can use to implement a connection interface.", "leadMaintainer": "Jacob Heun ", "main": "src/index.js", From 499575cf8c9cc8c0d072c2c04f0f39e909ca9588 Mon Sep 17 00:00:00 2001 From: Jacob Heun Date: Thu, 17 Oct 2019 14:41:12 +0200 Subject: [PATCH 66/66] chore: release version v0.4.1 --- CHANGELOG.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0c7a4b9..53e87fa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,13 @@ + +## [0.4.1](https://github.com/libp2p/interface-connection/compare/v0.4.0...v0.4.1) (2019-10-17) + + +### Features + +* add support for timeline proxying ([#31](https://github.com/libp2p/interface-connection/issues/31)) ([541bf83](https://github.com/libp2p/interface-connection/commit/541bf83)) + + + # [0.4.0](https://github.com/libp2p/interface-connection/compare/v0.3.3...v0.4.0) (2019-09-27)