From 81b3c584b454d1738a328ebbe1badb19cd6b6d24 Mon Sep 17 00:00:00 2001 From: achingbrain Date: Thu, 15 Jun 2023 08:54:58 +0200 Subject: [PATCH] chore: add packages from libp2p/js-libp2p-interfaces A lift & shift of all package from the libp2p/js-libp2p-interfaces repo. --- .gitignore | 4 +- LICENSE | 4 + LICENSE-APACHE | 5 + LICENSE-MIT | 19 + interop/package.json | 3 - package.json | 3 +- .../interface-address-manager/CHANGELOG.md | 111 ++ packages/interface-address-manager/LICENSE | 4 + .../interface-address-manager/LICENSE-APACHE | 5 + .../interface-address-manager/LICENSE-MIT | 19 + packages/interface-address-manager/README.md | 43 + .../interface-address-manager/package.json | 139 ++ .../interface-address-manager/src/index.ts | 43 + .../interface-address-manager/tsconfig.json | 9 + .../interface-compliance-tests/CHANGELOG.md | 530 ++++++++ packages/interface-compliance-tests/LICENSE | 4 + .../interface-compliance-tests/LICENSE-APACHE | 5 + .../interface-compliance-tests/LICENSE-MIT | 19 + packages/interface-compliance-tests/README.md | 41 + .../interface-compliance-tests/package.json | 160 +++ .../interface-compliance-tests/src/index.ts | 5 + .../src/is-valid-tick.ts | 18 + .../interface-compliance-tests/src/peers.ts | 25 + .../interface-compliance-tests/tsconfig.json | 9 + .../CHANGELOG.md | 93 ++ .../LICENSE | 4 + .../LICENSE-APACHE | 5 + .../LICENSE-MIT | 19 + .../README.md | 61 + .../package.json | 140 +++ .../src/index.ts | 184 +++ .../tsconfig.json | 18 + .../CHANGELOG.md | 173 +++ .../LICENSE | 4 + .../LICENSE-APACHE | 5 + .../LICENSE-MIT | 19 + .../README.md | 54 + .../package.json | 147 +++ .../src/index.ts | 97 ++ .../src/utils/index.ts | 23 + .../tsconfig.json | 24 + .../CHANGELOG.md | 137 ++ .../interface-connection-encrypter/LICENSE | 4 + .../LICENSE-APACHE | 5 + .../LICENSE-MIT | 19 + .../interface-connection-encrypter/README.md | 114 ++ .../package.json | 160 +++ .../src/errors.ts | 33 + .../src/index.ts | 30 + .../tsconfig.json | 14 + .../interface-connection-gater/CHANGELOG.md | 64 + packages/interface-connection-gater/LICENSE | 4 + .../interface-connection-gater/LICENSE-APACHE | 5 + .../interface-connection-gater/LICENSE-MIT | 19 + packages/interface-connection-gater/README.md | 36 + .../interface-connection-gater/package.json | 141 +++ .../interface-connection-gater/src/index.ts | 128 ++ .../interface-connection-gater/tsconfig.json | 17 + .../interface-connection-manager/CHANGELOG.md | 179 +++ packages/interface-connection-manager/LICENSE | 4 + .../LICENSE-APACHE | 5 + .../interface-connection-manager/LICENSE-MIT | 19 + .../interface-connection-manager/README.md | 43 + .../interface-connection-manager/package.json | 143 +++ .../interface-connection-manager/src/index.ts | 79 ++ .../tsconfig.json | 20 + packages/interface-connection/CHANGELOG.md | 188 +++ packages/interface-connection/LICENSE | 4 + packages/interface-connection/LICENSE-APACHE | 5 + packages/interface-connection/LICENSE-MIT | 19 + packages/interface-connection/README.md | 315 +++++ packages/interface-connection/img/badge.png | Bin 0 -> 5258 bytes .../interface-connection/img/badge.sketch | Bin 0 -> 65536 bytes packages/interface-connection/img/badge.svg | 19 + packages/interface-connection/package.json | 163 +++ packages/interface-connection/src/index.ts | 221 ++++ packages/interface-connection/src/status.ts | 4 + packages/interface-connection/tsconfig.json | 17 + .../interface-content-routing/CHANGELOG.md | 111 ++ packages/interface-content-routing/LICENSE | 4 + .../interface-content-routing/LICENSE-APACHE | 5 + .../interface-content-routing/LICENSE-MIT | 19 + packages/interface-content-routing/README.md | 100 ++ .../interface-content-routing/img/badge.png | Bin 0 -> 5001 bytes .../img/badge.sketch | Bin 0 -> 181682 bytes .../interface-content-routing/img/badge.svg | 25 + .../interface-content-routing/package.json | 141 +++ .../interface-content-routing/src/index.ts | 83 ++ .../interface-content-routing/tsconfig.json | 17 + packages/interface-dht/CHANGELOG.md | 77 ++ packages/interface-dht/LICENSE | 4 + packages/interface-dht/LICENSE-APACHE | 5 + packages/interface-dht/LICENSE-MIT | 19 + packages/interface-dht/README.md | 36 + packages/interface-dht/package.json | 143 +++ packages/interface-dht/src/index.ts | 211 ++++ packages/interface-dht/tsconfig.json | 23 + packages/interface-keychain/CHANGELOG.md | 122 ++ packages/interface-keychain/LICENSE | 4 + packages/interface-keychain/LICENSE-APACHE | 5 + packages/interface-keychain/LICENSE-MIT | 19 + packages/interface-keychain/README.md | 100 ++ packages/interface-keychain/package.json | 140 +++ packages/interface-keychain/src/index.ts | 168 +++ packages/interface-keychain/tsconfig.json | 14 + packages/interface-keys/CHANGELOG.md | 76 ++ packages/interface-keys/LICENSE | 4 + packages/interface-keys/LICENSE-APACHE | 5 + packages/interface-keys/LICENSE-MIT | 19 + packages/interface-keys/README.md | 56 + packages/interface-keys/package.json | 136 ++ packages/interface-keys/src/index.ts | 38 + packages/interface-keys/tsconfig.json | 9 + packages/interface-libp2p/CHANGELOG.md | 235 ++++ packages/interface-libp2p/LICENSE | 4 + packages/interface-libp2p/LICENSE-APACHE | 5 + packages/interface-libp2p/LICENSE-MIT | 19 + packages/interface-libp2p/README.md | 36 + packages/interface-libp2p/package.json | 150 +++ packages/interface-libp2p/src/index.ts | 599 +++++++++ packages/interface-libp2p/tsconfig.json | 44 + packages/interface-metrics/CHANGELOG.md | 130 ++ packages/interface-metrics/LICENSE | 4 + packages/interface-metrics/LICENSE-APACHE | 5 + packages/interface-metrics/LICENSE-MIT | 19 + packages/interface-metrics/README.md | 41 + packages/interface-metrics/package.json | 139 ++ packages/interface-metrics/src/index.ts | 187 +++ packages/interface-metrics/tsconfig.json | 14 + packages/interface-mocks/CHANGELOG.md | 1013 +++++++++++++++ packages/interface-mocks/LICENSE | 4 + packages/interface-mocks/LICENSE-APACHE | 5 + packages/interface-mocks/LICENSE-MIT | 19 + packages/interface-mocks/README.md | 50 + packages/interface-mocks/package.json | 181 +++ .../src/connection-encrypter.ts | 113 ++ .../interface-mocks/src/connection-gater.ts | 18 + .../interface-mocks/src/connection-manager.ts | 211 ++++ packages/interface-mocks/src/connection.ts | 218 ++++ packages/interface-mocks/src/duplex.ts | 10 + packages/interface-mocks/src/index.ts | 12 + packages/interface-mocks/src/metrics.ts | 162 +++ .../src/multiaddr-connection.ts | 67 + packages/interface-mocks/src/muxer.ts | 447 +++++++ .../interface-mocks/src/peer-discovery.ts | 60 + packages/interface-mocks/src/registrar.ts | 87 ++ packages/interface-mocks/src/upgrader.ts | 49 + .../test/connection-encrypter.spec.ts | 13 + .../interface-mocks/test/connection.spec.ts | 38 + packages/interface-mocks/test/muxer.spec.ts | 13 + .../test/peer-discovery.spec.ts | 21 + packages/interface-mocks/tsconfig.json | 66 + .../CHANGELOG.md | 90 ++ .../LICENSE | 4 + .../LICENSE-APACHE | 5 + .../LICENSE-MIT | 19 + .../README.md | 55 + .../package.json | 142 +++ .../src/index.ts | 90 ++ .../tsconfig.json | 21 + .../interface-peer-discovery/CHANGELOG.md | 69 + packages/interface-peer-discovery/LICENSE | 4 + .../interface-peer-discovery/LICENSE-APACHE | 5 + packages/interface-peer-discovery/LICENSE-MIT | 19 + packages/interface-peer-discovery/README.md | 118 ++ .../interface-peer-discovery/img/badge.png | Bin 0 -> 6165 bytes .../interface-peer-discovery/img/badge.sketch | Bin 0 -> 106496 bytes .../interface-peer-discovery/img/badge.svg | 39 + .../interface-peer-discovery/package.json | 140 +++ .../interface-peer-discovery/src/index.ts | 29 + .../interface-peer-discovery/tsconfig.json | 17 + packages/interface-peer-id/CHANGELOG.md | 108 ++ packages/interface-peer-id/LICENSE | 4 + packages/interface-peer-id/LICENSE-APACHE | 5 + packages/interface-peer-id/LICENSE-MIT | 19 + packages/interface-peer-id/README.md | 80 ++ packages/interface-peer-id/package.json | 139 ++ packages/interface-peer-id/src/index.ts | 39 + packages/interface-peer-id/tsconfig.json | 9 + packages/interface-peer-info/CHANGELOG.md | 91 ++ packages/interface-peer-info/LICENSE | 4 + packages/interface-peer-info/LICENSE-APACHE | 5 + packages/interface-peer-info/LICENSE-MIT | 19 + packages/interface-peer-info/README.md | 36 + packages/interface-peer-info/package.json | 140 +++ packages/interface-peer-info/src/index.ts | 8 + packages/interface-peer-info/tsconfig.json | 14 + packages/interface-peer-routing/CHANGELOG.md | 79 ++ packages/interface-peer-routing/LICENSE | 4 + .../interface-peer-routing/LICENSE-APACHE | 5 + packages/interface-peer-routing/LICENSE-MIT | 19 + packages/interface-peer-routing/README.md | 83 ++ packages/interface-peer-routing/img/badge.png | Bin 0 -> 7070 bytes .../interface-peer-routing/img/badge.sketch | Bin 0 -> 139264 bytes packages/interface-peer-routing/img/badge.svg | 19 + packages/interface-peer-routing/package.json | 141 +++ packages/interface-peer-routing/src/index.ts | 53 + packages/interface-peer-routing/tsconfig.json | 20 + packages/interface-peer-store/CHANGELOG.md | 136 ++ packages/interface-peer-store/LICENSE | 4 + packages/interface-peer-store/LICENSE-APACHE | 5 + packages/interface-peer-store/LICENSE-MIT | 19 + packages/interface-peer-store/README.md | 50 + packages/interface-peer-store/package.json | 160 +++ packages/interface-peer-store/src/index.ts | 272 ++++ packages/interface-peer-store/src/tags.ts | 2 + packages/interface-peer-store/tsconfig.json | 14 + .../CHANGELOG.md | 225 ++++ .../interface-pubsub-compliance-tests/LICENSE | 4 + .../LICENSE-APACHE | 5 + .../LICENSE-MIT | 19 + .../README.md | 55 + .../package.json | 150 +++ .../src/api.ts | 114 ++ .../src/connection-handlers.ts | 413 ++++++ .../src/emit-self.ts | 99 ++ .../src/index.ts | 34 + .../src/messages.ts | 59 + .../src/multiple-nodes.ts | 440 +++++++ .../src/two-nodes.ts | 273 ++++ .../src/utils.ts | 27 + .../tsconfig.json | 33 + packages/interface-pubsub/CHANGELOG.md | 148 +++ packages/interface-pubsub/LICENSE | 4 + packages/interface-pubsub/LICENSE-APACHE | 5 + packages/interface-pubsub/LICENSE-MIT | 19 + packages/interface-pubsub/README.md | 323 +++++ packages/interface-pubsub/package.json | 143 +++ packages/interface-pubsub/src/index.ts | 269 ++++ packages/interface-pubsub/tsconfig.json | 20 + .../CHANGELOG.md | 63 + .../interface-record-compliance-tests/LICENSE | 4 + .../LICENSE-APACHE | 5 + .../LICENSE-MIT | 19 + .../README.md | 55 + .../package.json | 138 ++ .../src/index.ts | 32 + .../tsconfig.json | 18 + packages/interface-record/CHANGELOG.md | 95 ++ packages/interface-record/LICENSE | 4 + packages/interface-record/LICENSE-APACHE | 5 + packages/interface-record/LICENSE-MIT | 19 + packages/interface-record/README.md | 132 ++ packages/interface-record/package.json | 140 +++ packages/interface-record/src/index.ts | 35 + packages/interface-record/tsconfig.json | 15 + packages/interface-registrar/CHANGELOG.md | 116 ++ packages/interface-registrar/LICENSE | 4 + packages/interface-registrar/LICENSE-APACHE | 5 + packages/interface-registrar/LICENSE-MIT | 19 + packages/interface-registrar/README.md | 36 + packages/interface-registrar/package.json | 140 +++ packages/interface-registrar/src/index.ts | 118 ++ packages/interface-registrar/tsconfig.json | 18 + .../CHANGELOG.md | 239 ++++ .../LICENSE | 4 + .../LICENSE-APACHE | 5 + .../LICENSE-MIT | 19 + .../README.md | 55 + .../package.json | 151 +++ .../src/base-test.ts | 196 +++ .../src/close-test.ts | 346 +++++ .../src/index.ts | 15 + .../src/mega-stress-test.ts | 14 + .../src/spawner.ts | 54 + .../src/stress-test.ts | 27 + .../tsconfig.json | 21 + packages/interface-stream-muxer/CHANGELOG.md | 155 +++ packages/interface-stream-muxer/LICENSE | 4 + .../interface-stream-muxer/LICENSE-APACHE | 5 + packages/interface-stream-muxer/LICENSE-MIT | 19 + packages/interface-stream-muxer/README.md | 191 +++ packages/interface-stream-muxer/img/badge.png | Bin 0 -> 7694 bytes .../interface-stream-muxer/img/badge.sketch | Bin 0 -> 49152 bytes packages/interface-stream-muxer/img/badge.svg | 18 + packages/interface-stream-muxer/package.json | 166 +++ packages/interface-stream-muxer/src/index.ts | 58 + packages/interface-stream-muxer/src/stream.ts | 361 ++++++ packages/interface-stream-muxer/tsconfig.json | 18 + .../CHANGELOG.md | 235 ++++ .../LICENSE | 4 + .../LICENSE-APACHE | 5 + .../LICENSE-MIT | 19 + .../README.md | 55 + .../package.json | 150 +++ .../src/dial-test.ts | 124 ++ .../src/filter-test.ts | 25 + .../src/index.ts | 25 + .../src/listen-test.ts | 191 +++ .../tsconfig.json | 30 + packages/interface-transport/CHANGELOG.md | 150 +++ packages/interface-transport/LICENSE | 4 + packages/interface-transport/LICENSE-APACHE | 5 + packages/interface-transport/LICENSE-MIT | 19 + packages/interface-transport/README.md | 264 ++++ packages/interface-transport/img/badge.png | Bin 0 -> 5226 bytes packages/interface-transport/img/badge.sketch | Bin 0 -> 40960 bytes packages/interface-transport/img/badge.svg | 19 + packages/interface-transport/package.json | 143 +++ packages/interface-transport/src/index.ts | 128 ++ packages/interface-transport/tsconfig.json | 21 + packages/interfaces/CHANGELOG.md | 1120 +++++++++++++++++ packages/interfaces/LICENSE | 4 + packages/interfaces/LICENSE-APACHE | 5 + packages/interfaces/LICENSE-MIT | 19 + packages/interfaces/README.md | 101 ++ packages/interfaces/package.json | 164 +++ packages/interfaces/src/errors.ts | 35 + packages/interfaces/src/events.ts | 101 ++ packages/interfaces/src/index.ts | 30 + packages/interfaces/src/startable.ts | 117 ++ packages/interfaces/tsconfig.json | 9 + packages/libp2p/.aegir.js | 12 +- packages/libp2p/package.json | 44 +- packages/libp2p/tsconfig.json | 74 ++ 315 files changed, 23464 insertions(+), 35 deletions(-) create mode 100644 LICENSE create mode 100644 LICENSE-APACHE create mode 100644 LICENSE-MIT create mode 100644 packages/interface-address-manager/CHANGELOG.md create mode 100644 packages/interface-address-manager/LICENSE create mode 100644 packages/interface-address-manager/LICENSE-APACHE create mode 100644 packages/interface-address-manager/LICENSE-MIT create mode 100644 packages/interface-address-manager/README.md create mode 100644 packages/interface-address-manager/package.json create mode 100644 packages/interface-address-manager/src/index.ts create mode 100644 packages/interface-address-manager/tsconfig.json create mode 100644 packages/interface-compliance-tests/CHANGELOG.md create mode 100644 packages/interface-compliance-tests/LICENSE create mode 100644 packages/interface-compliance-tests/LICENSE-APACHE create mode 100644 packages/interface-compliance-tests/LICENSE-MIT create mode 100644 packages/interface-compliance-tests/README.md create mode 100644 packages/interface-compliance-tests/package.json create mode 100644 packages/interface-compliance-tests/src/index.ts create mode 100644 packages/interface-compliance-tests/src/is-valid-tick.ts create mode 100644 packages/interface-compliance-tests/src/peers.ts create mode 100644 packages/interface-compliance-tests/tsconfig.json create mode 100644 packages/interface-connection-compliance-tests/CHANGELOG.md create mode 100644 packages/interface-connection-compliance-tests/LICENSE create mode 100644 packages/interface-connection-compliance-tests/LICENSE-APACHE create mode 100644 packages/interface-connection-compliance-tests/LICENSE-MIT create mode 100644 packages/interface-connection-compliance-tests/README.md create mode 100644 packages/interface-connection-compliance-tests/package.json create mode 100644 packages/interface-connection-compliance-tests/src/index.ts create mode 100644 packages/interface-connection-compliance-tests/tsconfig.json create mode 100644 packages/interface-connection-encrypter-compliance-tests/CHANGELOG.md create mode 100644 packages/interface-connection-encrypter-compliance-tests/LICENSE create mode 100644 packages/interface-connection-encrypter-compliance-tests/LICENSE-APACHE create mode 100644 packages/interface-connection-encrypter-compliance-tests/LICENSE-MIT create mode 100644 packages/interface-connection-encrypter-compliance-tests/README.md create mode 100644 packages/interface-connection-encrypter-compliance-tests/package.json create mode 100644 packages/interface-connection-encrypter-compliance-tests/src/index.ts create mode 100644 packages/interface-connection-encrypter-compliance-tests/src/utils/index.ts create mode 100644 packages/interface-connection-encrypter-compliance-tests/tsconfig.json create mode 100644 packages/interface-connection-encrypter/CHANGELOG.md create mode 100644 packages/interface-connection-encrypter/LICENSE create mode 100644 packages/interface-connection-encrypter/LICENSE-APACHE create mode 100644 packages/interface-connection-encrypter/LICENSE-MIT create mode 100644 packages/interface-connection-encrypter/README.md create mode 100644 packages/interface-connection-encrypter/package.json create mode 100644 packages/interface-connection-encrypter/src/errors.ts create mode 100644 packages/interface-connection-encrypter/src/index.ts create mode 100644 packages/interface-connection-encrypter/tsconfig.json create mode 100644 packages/interface-connection-gater/CHANGELOG.md create mode 100644 packages/interface-connection-gater/LICENSE create mode 100644 packages/interface-connection-gater/LICENSE-APACHE create mode 100644 packages/interface-connection-gater/LICENSE-MIT create mode 100644 packages/interface-connection-gater/README.md create mode 100644 packages/interface-connection-gater/package.json create mode 100644 packages/interface-connection-gater/src/index.ts create mode 100644 packages/interface-connection-gater/tsconfig.json create mode 100644 packages/interface-connection-manager/CHANGELOG.md create mode 100644 packages/interface-connection-manager/LICENSE create mode 100644 packages/interface-connection-manager/LICENSE-APACHE create mode 100644 packages/interface-connection-manager/LICENSE-MIT create mode 100644 packages/interface-connection-manager/README.md create mode 100644 packages/interface-connection-manager/package.json create mode 100644 packages/interface-connection-manager/src/index.ts create mode 100644 packages/interface-connection-manager/tsconfig.json create mode 100644 packages/interface-connection/CHANGELOG.md create mode 100644 packages/interface-connection/LICENSE create mode 100644 packages/interface-connection/LICENSE-APACHE create mode 100644 packages/interface-connection/LICENSE-MIT create mode 100644 packages/interface-connection/README.md create mode 100644 packages/interface-connection/img/badge.png create mode 100644 packages/interface-connection/img/badge.sketch create mode 100644 packages/interface-connection/img/badge.svg create mode 100644 packages/interface-connection/package.json create mode 100644 packages/interface-connection/src/index.ts create mode 100644 packages/interface-connection/src/status.ts create mode 100644 packages/interface-connection/tsconfig.json create mode 100644 packages/interface-content-routing/CHANGELOG.md create mode 100644 packages/interface-content-routing/LICENSE create mode 100644 packages/interface-content-routing/LICENSE-APACHE create mode 100644 packages/interface-content-routing/LICENSE-MIT create mode 100644 packages/interface-content-routing/README.md create mode 100644 packages/interface-content-routing/img/badge.png create mode 100644 packages/interface-content-routing/img/badge.sketch create mode 100644 packages/interface-content-routing/img/badge.svg create mode 100644 packages/interface-content-routing/package.json create mode 100644 packages/interface-content-routing/src/index.ts create mode 100644 packages/interface-content-routing/tsconfig.json create mode 100644 packages/interface-dht/CHANGELOG.md create mode 100644 packages/interface-dht/LICENSE create mode 100644 packages/interface-dht/LICENSE-APACHE create mode 100644 packages/interface-dht/LICENSE-MIT create mode 100644 packages/interface-dht/README.md create mode 100644 packages/interface-dht/package.json create mode 100644 packages/interface-dht/src/index.ts create mode 100644 packages/interface-dht/tsconfig.json create mode 100644 packages/interface-keychain/CHANGELOG.md create mode 100644 packages/interface-keychain/LICENSE create mode 100644 packages/interface-keychain/LICENSE-APACHE create mode 100644 packages/interface-keychain/LICENSE-MIT create mode 100644 packages/interface-keychain/README.md create mode 100644 packages/interface-keychain/package.json create mode 100644 packages/interface-keychain/src/index.ts create mode 100644 packages/interface-keychain/tsconfig.json create mode 100644 packages/interface-keys/CHANGELOG.md create mode 100644 packages/interface-keys/LICENSE create mode 100644 packages/interface-keys/LICENSE-APACHE create mode 100644 packages/interface-keys/LICENSE-MIT create mode 100644 packages/interface-keys/README.md create mode 100644 packages/interface-keys/package.json create mode 100644 packages/interface-keys/src/index.ts create mode 100644 packages/interface-keys/tsconfig.json create mode 100644 packages/interface-libp2p/CHANGELOG.md create mode 100644 packages/interface-libp2p/LICENSE create mode 100644 packages/interface-libp2p/LICENSE-APACHE create mode 100644 packages/interface-libp2p/LICENSE-MIT create mode 100644 packages/interface-libp2p/README.md create mode 100644 packages/interface-libp2p/package.json create mode 100644 packages/interface-libp2p/src/index.ts create mode 100644 packages/interface-libp2p/tsconfig.json create mode 100644 packages/interface-metrics/CHANGELOG.md create mode 100644 packages/interface-metrics/LICENSE create mode 100644 packages/interface-metrics/LICENSE-APACHE create mode 100644 packages/interface-metrics/LICENSE-MIT create mode 100644 packages/interface-metrics/README.md create mode 100644 packages/interface-metrics/package.json create mode 100644 packages/interface-metrics/src/index.ts create mode 100644 packages/interface-metrics/tsconfig.json create mode 100644 packages/interface-mocks/CHANGELOG.md create mode 100644 packages/interface-mocks/LICENSE create mode 100644 packages/interface-mocks/LICENSE-APACHE create mode 100644 packages/interface-mocks/LICENSE-MIT create mode 100644 packages/interface-mocks/README.md create mode 100644 packages/interface-mocks/package.json create mode 100644 packages/interface-mocks/src/connection-encrypter.ts create mode 100644 packages/interface-mocks/src/connection-gater.ts create mode 100644 packages/interface-mocks/src/connection-manager.ts create mode 100644 packages/interface-mocks/src/connection.ts create mode 100644 packages/interface-mocks/src/duplex.ts create mode 100644 packages/interface-mocks/src/index.ts create mode 100644 packages/interface-mocks/src/metrics.ts create mode 100644 packages/interface-mocks/src/multiaddr-connection.ts create mode 100644 packages/interface-mocks/src/muxer.ts create mode 100644 packages/interface-mocks/src/peer-discovery.ts create mode 100644 packages/interface-mocks/src/registrar.ts create mode 100644 packages/interface-mocks/src/upgrader.ts create mode 100644 packages/interface-mocks/test/connection-encrypter.spec.ts create mode 100644 packages/interface-mocks/test/connection.spec.ts create mode 100644 packages/interface-mocks/test/muxer.spec.ts create mode 100644 packages/interface-mocks/test/peer-discovery.spec.ts create mode 100644 packages/interface-mocks/tsconfig.json create mode 100644 packages/interface-peer-discovery-compliance-tests/CHANGELOG.md create mode 100644 packages/interface-peer-discovery-compliance-tests/LICENSE create mode 100644 packages/interface-peer-discovery-compliance-tests/LICENSE-APACHE create mode 100644 packages/interface-peer-discovery-compliance-tests/LICENSE-MIT create mode 100644 packages/interface-peer-discovery-compliance-tests/README.md create mode 100644 packages/interface-peer-discovery-compliance-tests/package.json create mode 100644 packages/interface-peer-discovery-compliance-tests/src/index.ts create mode 100644 packages/interface-peer-discovery-compliance-tests/tsconfig.json create mode 100644 packages/interface-peer-discovery/CHANGELOG.md create mode 100644 packages/interface-peer-discovery/LICENSE create mode 100644 packages/interface-peer-discovery/LICENSE-APACHE create mode 100644 packages/interface-peer-discovery/LICENSE-MIT create mode 100644 packages/interface-peer-discovery/README.md create mode 100644 packages/interface-peer-discovery/img/badge.png create mode 100644 packages/interface-peer-discovery/img/badge.sketch create mode 100644 packages/interface-peer-discovery/img/badge.svg create mode 100644 packages/interface-peer-discovery/package.json create mode 100644 packages/interface-peer-discovery/src/index.ts create mode 100644 packages/interface-peer-discovery/tsconfig.json create mode 100644 packages/interface-peer-id/CHANGELOG.md create mode 100644 packages/interface-peer-id/LICENSE create mode 100644 packages/interface-peer-id/LICENSE-APACHE create mode 100644 packages/interface-peer-id/LICENSE-MIT create mode 100644 packages/interface-peer-id/README.md create mode 100644 packages/interface-peer-id/package.json create mode 100644 packages/interface-peer-id/src/index.ts create mode 100644 packages/interface-peer-id/tsconfig.json create mode 100644 packages/interface-peer-info/CHANGELOG.md create mode 100644 packages/interface-peer-info/LICENSE create mode 100644 packages/interface-peer-info/LICENSE-APACHE create mode 100644 packages/interface-peer-info/LICENSE-MIT create mode 100644 packages/interface-peer-info/README.md create mode 100644 packages/interface-peer-info/package.json create mode 100644 packages/interface-peer-info/src/index.ts create mode 100644 packages/interface-peer-info/tsconfig.json create mode 100644 packages/interface-peer-routing/CHANGELOG.md create mode 100644 packages/interface-peer-routing/LICENSE create mode 100644 packages/interface-peer-routing/LICENSE-APACHE create mode 100644 packages/interface-peer-routing/LICENSE-MIT create mode 100644 packages/interface-peer-routing/README.md create mode 100644 packages/interface-peer-routing/img/badge.png create mode 100644 packages/interface-peer-routing/img/badge.sketch create mode 100644 packages/interface-peer-routing/img/badge.svg create mode 100644 packages/interface-peer-routing/package.json create mode 100644 packages/interface-peer-routing/src/index.ts create mode 100644 packages/interface-peer-routing/tsconfig.json create mode 100644 packages/interface-peer-store/CHANGELOG.md create mode 100644 packages/interface-peer-store/LICENSE create mode 100644 packages/interface-peer-store/LICENSE-APACHE create mode 100644 packages/interface-peer-store/LICENSE-MIT create mode 100644 packages/interface-peer-store/README.md create mode 100644 packages/interface-peer-store/package.json create mode 100644 packages/interface-peer-store/src/index.ts create mode 100644 packages/interface-peer-store/src/tags.ts create mode 100644 packages/interface-peer-store/tsconfig.json create mode 100644 packages/interface-pubsub-compliance-tests/CHANGELOG.md create mode 100644 packages/interface-pubsub-compliance-tests/LICENSE create mode 100644 packages/interface-pubsub-compliance-tests/LICENSE-APACHE create mode 100644 packages/interface-pubsub-compliance-tests/LICENSE-MIT create mode 100644 packages/interface-pubsub-compliance-tests/README.md create mode 100644 packages/interface-pubsub-compliance-tests/package.json create mode 100644 packages/interface-pubsub-compliance-tests/src/api.ts create mode 100644 packages/interface-pubsub-compliance-tests/src/connection-handlers.ts create mode 100644 packages/interface-pubsub-compliance-tests/src/emit-self.ts create mode 100644 packages/interface-pubsub-compliance-tests/src/index.ts create mode 100644 packages/interface-pubsub-compliance-tests/src/messages.ts create mode 100644 packages/interface-pubsub-compliance-tests/src/multiple-nodes.ts create mode 100644 packages/interface-pubsub-compliance-tests/src/two-nodes.ts create mode 100644 packages/interface-pubsub-compliance-tests/src/utils.ts create mode 100644 packages/interface-pubsub-compliance-tests/tsconfig.json create mode 100644 packages/interface-pubsub/CHANGELOG.md create mode 100644 packages/interface-pubsub/LICENSE create mode 100644 packages/interface-pubsub/LICENSE-APACHE create mode 100644 packages/interface-pubsub/LICENSE-MIT create mode 100644 packages/interface-pubsub/README.md create mode 100644 packages/interface-pubsub/package.json create mode 100644 packages/interface-pubsub/src/index.ts create mode 100644 packages/interface-pubsub/tsconfig.json create mode 100644 packages/interface-record-compliance-tests/CHANGELOG.md create mode 100644 packages/interface-record-compliance-tests/LICENSE create mode 100644 packages/interface-record-compliance-tests/LICENSE-APACHE create mode 100644 packages/interface-record-compliance-tests/LICENSE-MIT create mode 100644 packages/interface-record-compliance-tests/README.md create mode 100644 packages/interface-record-compliance-tests/package.json create mode 100644 packages/interface-record-compliance-tests/src/index.ts create mode 100644 packages/interface-record-compliance-tests/tsconfig.json create mode 100644 packages/interface-record/CHANGELOG.md create mode 100644 packages/interface-record/LICENSE create mode 100644 packages/interface-record/LICENSE-APACHE create mode 100644 packages/interface-record/LICENSE-MIT create mode 100644 packages/interface-record/README.md create mode 100644 packages/interface-record/package.json create mode 100644 packages/interface-record/src/index.ts create mode 100644 packages/interface-record/tsconfig.json create mode 100644 packages/interface-registrar/CHANGELOG.md create mode 100644 packages/interface-registrar/LICENSE create mode 100644 packages/interface-registrar/LICENSE-APACHE create mode 100644 packages/interface-registrar/LICENSE-MIT create mode 100644 packages/interface-registrar/README.md create mode 100644 packages/interface-registrar/package.json create mode 100644 packages/interface-registrar/src/index.ts create mode 100644 packages/interface-registrar/tsconfig.json create mode 100644 packages/interface-stream-muxer-compliance-tests/CHANGELOG.md create mode 100644 packages/interface-stream-muxer-compliance-tests/LICENSE create mode 100644 packages/interface-stream-muxer-compliance-tests/LICENSE-APACHE create mode 100644 packages/interface-stream-muxer-compliance-tests/LICENSE-MIT create mode 100644 packages/interface-stream-muxer-compliance-tests/README.md create mode 100644 packages/interface-stream-muxer-compliance-tests/package.json create mode 100644 packages/interface-stream-muxer-compliance-tests/src/base-test.ts create mode 100644 packages/interface-stream-muxer-compliance-tests/src/close-test.ts create mode 100644 packages/interface-stream-muxer-compliance-tests/src/index.ts create mode 100644 packages/interface-stream-muxer-compliance-tests/src/mega-stress-test.ts create mode 100644 packages/interface-stream-muxer-compliance-tests/src/spawner.ts create mode 100644 packages/interface-stream-muxer-compliance-tests/src/stress-test.ts create mode 100644 packages/interface-stream-muxer-compliance-tests/tsconfig.json create mode 100644 packages/interface-stream-muxer/CHANGELOG.md create mode 100644 packages/interface-stream-muxer/LICENSE create mode 100644 packages/interface-stream-muxer/LICENSE-APACHE create mode 100644 packages/interface-stream-muxer/LICENSE-MIT create mode 100644 packages/interface-stream-muxer/README.md create mode 100644 packages/interface-stream-muxer/img/badge.png create mode 100644 packages/interface-stream-muxer/img/badge.sketch create mode 100644 packages/interface-stream-muxer/img/badge.svg create mode 100644 packages/interface-stream-muxer/package.json create mode 100644 packages/interface-stream-muxer/src/index.ts create mode 100644 packages/interface-stream-muxer/src/stream.ts create mode 100644 packages/interface-stream-muxer/tsconfig.json create mode 100644 packages/interface-transport-compliance-tests/CHANGELOG.md create mode 100644 packages/interface-transport-compliance-tests/LICENSE create mode 100644 packages/interface-transport-compliance-tests/LICENSE-APACHE create mode 100644 packages/interface-transport-compliance-tests/LICENSE-MIT create mode 100644 packages/interface-transport-compliance-tests/README.md create mode 100644 packages/interface-transport-compliance-tests/package.json create mode 100644 packages/interface-transport-compliance-tests/src/dial-test.ts create mode 100644 packages/interface-transport-compliance-tests/src/filter-test.ts create mode 100644 packages/interface-transport-compliance-tests/src/index.ts create mode 100644 packages/interface-transport-compliance-tests/src/listen-test.ts create mode 100644 packages/interface-transport-compliance-tests/tsconfig.json create mode 100644 packages/interface-transport/CHANGELOG.md create mode 100644 packages/interface-transport/LICENSE create mode 100644 packages/interface-transport/LICENSE-APACHE create mode 100644 packages/interface-transport/LICENSE-MIT create mode 100644 packages/interface-transport/README.md create mode 100644 packages/interface-transport/img/badge.png create mode 100644 packages/interface-transport/img/badge.sketch create mode 100644 packages/interface-transport/img/badge.svg create mode 100644 packages/interface-transport/package.json create mode 100644 packages/interface-transport/src/index.ts create mode 100644 packages/interface-transport/tsconfig.json create mode 100644 packages/interfaces/CHANGELOG.md create mode 100644 packages/interfaces/LICENSE create mode 100644 packages/interfaces/LICENSE-APACHE create mode 100644 packages/interfaces/LICENSE-MIT create mode 100644 packages/interfaces/README.md create mode 100644 packages/interfaces/package.json create mode 100644 packages/interfaces/src/errors.ts create mode 100644 packages/interfaces/src/events.ts create mode 100644 packages/interfaces/src/index.ts create mode 100644 packages/interfaces/src/startable.ts create mode 100644 packages/interfaces/tsconfig.json diff --git a/.gitignore b/.gitignore index c31542c23e..7ad9e674ee 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,9 @@ node_modules +build dist .docs .coverage +node_modules package-lock.json yarn.lock -.vscode \ No newline at end of file +.vscode diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000000..20ce483c86 --- /dev/null +++ b/LICENSE @@ -0,0 +1,4 @@ +This project is dual licensed under MIT and Apache-2.0. + +MIT: https://www.opensource.org/licenses/mit +Apache-2.0: https://www.apache.org/licenses/license-2.0 diff --git a/LICENSE-APACHE b/LICENSE-APACHE new file mode 100644 index 0000000000..14478a3b60 --- /dev/null +++ b/LICENSE-APACHE @@ -0,0 +1,5 @@ +Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. diff --git a/LICENSE-MIT b/LICENSE-MIT new file mode 100644 index 0000000000..72dc60d84b --- /dev/null +++ b/LICENSE-MIT @@ -0,0 +1,19 @@ +The MIT License (MIT) + +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/interop/package.json b/interop/package.json index a973758599..78108d265b 100644 --- a/interop/package.json +++ b/interop/package.json @@ -7,9 +7,6 @@ "main": "index.js", "author": "Glen De Cauwsemaecker / @marcopolo", "license": "MIT", - "engines": { - "node": ">=18" - }, "scripts": { "start": "node index.js", "build": "aegir build", diff --git a/package.json b/package.json index 4834f79835..8f198fb6ac 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,8 @@ "dep-check": "aegir run dep-check", "release": "run-s build npm:release", "npm:release": "aegir exec npm -- publish", - "release:rc": "aegir release-rc" + "release:rc": "aegir release-rc", + "docs": "NODE_OPTIONS=--max_old_space_size=4096 aegir docs" }, "devDependencies": { "aegir": "^39.0.5" diff --git a/packages/interface-address-manager/CHANGELOG.md b/packages/interface-address-manager/CHANGELOG.md new file mode 100644 index 0000000000..6ecab8d4eb --- /dev/null +++ b/packages/interface-address-manager/CHANGELOG.md @@ -0,0 +1,111 @@ +## [@libp2p/interface-address-manager-v3.0.1](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-address-manager-v3.0.0...@libp2p/interface-address-manager-v3.0.1) (2023-05-04) + + +### Dependencies + +* bump aegir from 38.1.8 to 39.0.5 ([#393](https://github.com/libp2p/js-libp2p-interfaces/issues/393)) ([31f3797](https://github.com/libp2p/js-libp2p-interfaces/commit/31f3797b24f7c23f3f16e9db3a230bd5f7cd5175)) + +## [@libp2p/interface-address-manager-v3.0.0](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-address-manager-v2.0.5...@libp2p/interface-address-manager-v3.0.0) (2023-04-21) + + +### ⚠ BREAKING CHANGES + +* add libp2p events (#373) + +### Features + +* add libp2p events ([#373](https://github.com/libp2p/js-libp2p-interfaces/issues/373)) ([071c718](https://github.com/libp2p/js-libp2p-interfaces/commit/071c718808902858818ca86167b51b242b67a5a5)) + +## [@libp2p/interface-address-manager-v2.0.5](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-address-manager-v2.0.4...@libp2p/interface-address-manager-v2.0.5) (2023-03-17) + + +### Dependencies + +* update @multiformats/multiaddr to 12.0.0 ([#354](https://github.com/libp2p/js-libp2p-interfaces/issues/354)) ([e0f327b](https://github.com/libp2p/js-libp2p-interfaces/commit/e0f327b5d54e240feabadce21a841629d633ec5e)) + +## [@libp2p/interface-address-manager-v2.0.4](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-address-manager-v2.0.3...@libp2p/interface-address-manager-v2.0.4) (2023-01-18) + + +### Trivial Changes + +* remove lerna ([#330](https://github.com/libp2p/js-libp2p-interfaces/issues/330)) ([6678592](https://github.com/libp2p/js-libp2p-interfaces/commit/6678592dd0cf601a2671852f9d2a0aff5dee2b18)) + + +### Dependencies + +* bump aegir from 37.12.1 to 38.1.0 ([#335](https://github.com/libp2p/js-libp2p-interfaces/issues/335)) ([7368a36](https://github.com/libp2p/js-libp2p-interfaces/commit/7368a363423a08e8fa247dcb76ea13e4cf030d65)) + +## [@libp2p/interface-address-manager-v2.0.3](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-address-manager-v2.0.2...@libp2p/interface-address-manager-v2.0.3) (2022-12-16) + + +### Documentation + +* update project config ([#323](https://github.com/libp2p/js-libp2p-interfaces/issues/323)) ([0fc6a08](https://github.com/libp2p/js-libp2p-interfaces/commit/0fc6a08e9cdcefe361fe325281a3a2a03759ff59)) + +## [@libp2p/interface-address-manager-v2.0.2](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-address-manager-v2.0.1...@libp2p/interface-address-manager-v2.0.2) (2022-12-14) + + +### Bug Fixes + +* generate docs for all packages ([#321](https://github.com/libp2p/js-libp2p-interfaces/issues/321)) ([b6f8b32](https://github.com/libp2p/js-libp2p-interfaces/commit/b6f8b32a920c15a28fe021e6050e31aaae89d518)) + + +### Dependencies + +* update sibling dependencies ([947cedc](https://github.com/libp2p/js-libp2p-interfaces/commit/947cedcc25aa147768ee5b76577d069491db6ef6)) + +## [@libp2p/interface-address-manager-v2.0.1](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-address-manager-v2.0.0...@libp2p/interface-address-manager-v2.0.1) (2022-11-05) + + +### Bug Fixes + +* update project config ([#311](https://github.com/libp2p/js-libp2p-interfaces/issues/311)) ([27dd0ce](https://github.com/libp2p/js-libp2p-interfaces/commit/27dd0ce3c249892ac69cbb24ddaf0b9f32385e37)) + +## [@libp2p/interface-address-manager-v2.0.0](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-address-manager-v1.0.3...@libp2p/interface-address-manager-v2.0.0) (2022-10-07) + + +### ⚠ BREAKING CHANGES + +* add observed address methods (#269) + +### Features + +* add observed address methods ([#269](https://github.com/libp2p/js-libp2p-interfaces/issues/269)) ([0b157d5](https://github.com/libp2p/js-libp2p-interfaces/commit/0b157d5666caaaaa8676265cab3e4b010872ee41)) + +## [@libp2p/interface-address-manager-v1.0.3](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-address-manager-v1.0.2...@libp2p/interface-address-manager-v1.0.3) (2022-09-21) + + +### Trivial Changes + +* update project config ([#271](https://github.com/libp2p/js-libp2p-interfaces/issues/271)) ([59c0bf5](https://github.com/libp2p/js-libp2p-interfaces/commit/59c0bf5e0b05496fca2e4902632b61bb41fad9e9)) + + +### Dependencies + +* update @multiformats/multiaddr to 11.0.0 ([#288](https://github.com/libp2p/js-libp2p-interfaces/issues/288)) ([57b2ad8](https://github.com/libp2p/js-libp2p-interfaces/commit/57b2ad88edfc7807311143791bc49270b1a81eaf)) + +## [@libp2p/interface-address-manager-v1.0.2](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-address-manager-v1.0.1...@libp2p/interface-address-manager-v1.0.2) (2022-06-27) + + +### Trivial Changes + +* update deps ([#262](https://github.com/libp2p/js-libp2p-interfaces/issues/262)) ([51edf7d](https://github.com/libp2p/js-libp2p-interfaces/commit/51edf7d9b3765a6f75c915b1483ea345d0133a41)) + +## [@libp2p/interface-address-manager-v1.0.1](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-address-manager-v1.0.0...@libp2p/interface-address-manager-v1.0.1) (2022-06-14) + + +### Trivial Changes + +* update aegir ([#234](https://github.com/libp2p/js-libp2p-interfaces/issues/234)) ([3e03895](https://github.com/libp2p/js-libp2p-interfaces/commit/3e038959ecab6cfa3585df9ee179c0af7a61eda5)) +* update readmes ([#233](https://github.com/libp2p/js-libp2p-interfaces/issues/233)) ([ee7da38](https://github.com/libp2p/js-libp2p-interfaces/commit/ee7da38dccc08160d26c8436df8739ce7e0b340e)) + +## @libp2p/interface-address-manager-v1.0.0 (2022-06-14) + + +### ⚠ BREAKING CHANGES + +* most modules have been split out of the `@libp2p/interfaces` and `@libp2p/interface-compliance-tests` packages + +### Trivial Changes + +* break modules apart ([#232](https://github.com/libp2p/js-libp2p-interfaces/issues/232)) ([385614e](https://github.com/libp2p/js-libp2p-interfaces/commit/385614e772329052ab17415c8bd421f65b01a61b)), closes [#226](https://github.com/libp2p/js-libp2p-interfaces/issues/226) diff --git a/packages/interface-address-manager/LICENSE b/packages/interface-address-manager/LICENSE new file mode 100644 index 0000000000..20ce483c86 --- /dev/null +++ b/packages/interface-address-manager/LICENSE @@ -0,0 +1,4 @@ +This project is dual licensed under MIT and Apache-2.0. + +MIT: https://www.opensource.org/licenses/mit +Apache-2.0: https://www.apache.org/licenses/license-2.0 diff --git a/packages/interface-address-manager/LICENSE-APACHE b/packages/interface-address-manager/LICENSE-APACHE new file mode 100644 index 0000000000..14478a3b60 --- /dev/null +++ b/packages/interface-address-manager/LICENSE-APACHE @@ -0,0 +1,5 @@ +Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. diff --git a/packages/interface-address-manager/LICENSE-MIT b/packages/interface-address-manager/LICENSE-MIT new file mode 100644 index 0000000000..72dc60d84b --- /dev/null +++ b/packages/interface-address-manager/LICENSE-MIT @@ -0,0 +1,19 @@ +The MIT License (MIT) + +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/packages/interface-address-manager/README.md b/packages/interface-address-manager/README.md new file mode 100644 index 0000000000..04c77b6cf7 --- /dev/null +++ b/packages/interface-address-manager/README.md @@ -0,0 +1,43 @@ +# @libp2p/interface-address-manager + +[![libp2p.io](https://img.shields.io/badge/project-libp2p-yellow.svg?style=flat-square)](http://libp2p.io/) +[![Discuss](https://img.shields.io/discourse/https/discuss.libp2p.io/posts.svg?style=flat-square)](https://discuss.libp2p.io) +[![codecov](https://img.shields.io/codecov/c/github/libp2p/js-libp2p.svg?style=flat-square)](https://codecov.io/gh/libp2p/js-libp2p) +[![CI](https://img.shields.io/github/actions/workflow/status/libp2p/js-libp2p/js-test-and-release.yml?branch=master\&style=flat-square)](https://github.com/libp2p/js-libp2p/actions/workflows/js-test-and-release.yml?query=branch%3Amaster) + +> Address Manager interface for libp2p + +## Table of contents + +- [Install](#install) +- [Usage](#usage) +- [API Docs](#api-docs) +- [License](#license) +- [Contribution](#contribution) + +## Install + +```console +$ npm i @libp2p/interface-address-manager +``` + +## Usage + +```js +import type { AddressManager } from '@libp2p/interfaces-address-manager' +``` + +## API Docs + +- + +## License + +Licensed under either of + +- Apache 2.0, ([LICENSE-APACHE](LICENSE-APACHE) / ) +- MIT ([LICENSE-MIT](LICENSE-MIT) / ) + +## Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. diff --git a/packages/interface-address-manager/package.json b/packages/interface-address-manager/package.json new file mode 100644 index 0000000000..f02b1d170b --- /dev/null +++ b/packages/interface-address-manager/package.json @@ -0,0 +1,139 @@ +{ + "name": "@libp2p/interface-address-manager", + "version": "3.0.1", + "description": "Address Manager interface for libp2p", + "license": "Apache-2.0 OR MIT", + "homepage": "https://github.com/libp2p/js-libp2p/tree/master/packages/interface-address-manager#readme", + "repository": { + "type": "git", + "url": "git+https://github.com/libp2p/js-libp2p.git" + }, + "bugs": { + "url": "https://github.com/libp2p/js-libp2p/issues" + }, + "keywords": [ + "interface", + "libp2p" + ], + "type": "module", + "types": "./dist/src/index.d.ts", + "files": [ + "src", + "dist", + "!dist/test", + "!**/*.tsbuildinfo" + ], + "exports": { + ".": { + "types": "./dist/src/index.d.ts", + "import": "./dist/src/index.js" + } + }, + "eslintConfig": { + "extends": "ipfs", + "parserOptions": { + "sourceType": "module" + } + }, + "release": { + "branches": [ + "master" + ], + "plugins": [ + [ + "@semantic-release/commit-analyzer", + { + "preset": "conventionalcommits", + "releaseRules": [ + { + "breaking": true, + "release": "major" + }, + { + "revert": true, + "release": "patch" + }, + { + "type": "feat", + "release": "minor" + }, + { + "type": "fix", + "release": "patch" + }, + { + "type": "docs", + "release": "patch" + }, + { + "type": "test", + "release": "patch" + }, + { + "type": "deps", + "release": "patch" + }, + { + "scope": "no-release", + "release": false + } + ] + } + ], + [ + "@semantic-release/release-notes-generator", + { + "preset": "conventionalcommits", + "presetConfig": { + "types": [ + { + "type": "feat", + "section": "Features" + }, + { + "type": "fix", + "section": "Bug Fixes" + }, + { + "type": "chore", + "section": "Trivial Changes" + }, + { + "type": "docs", + "section": "Documentation" + }, + { + "type": "deps", + "section": "Dependencies" + }, + { + "type": "test", + "section": "Tests" + } + ] + } + } + ], + "@semantic-release/changelog", + "@semantic-release/npm", + "@semantic-release/github", + "@semantic-release/git" + ] + }, + "scripts": { + "clean": "aegir clean", + "lint": "aegir lint", + "dep-check": "aegir dep-check", + "build": "aegir build", + "release": "aegir release" + }, + "dependencies": { + "@multiformats/multiaddr": "^12.0.0" + }, + "devDependencies": { + "aegir": "^39.0.5" + }, + "typedoc": { + "entryPoint": "./src/index.ts" + } +} diff --git a/packages/interface-address-manager/src/index.ts b/packages/interface-address-manager/src/index.ts new file mode 100644 index 0000000000..622e0cb315 --- /dev/null +++ b/packages/interface-address-manager/src/index.ts @@ -0,0 +1,43 @@ +import type { Multiaddr } from '@multiformats/multiaddr' + +export interface AddressManager { + /** + * Get peer listen multiaddrs + */ + getListenAddrs: () => Multiaddr[] + + /** + * Get peer announcing multiaddrs + */ + getAnnounceAddrs: () => Multiaddr[] + + /** + * Get observed multiaddrs - these addresses may not have been confirmed as + * publicly dialable yet + */ + getObservedAddrs: () => Multiaddr[] + + /** + * Signal that we have confidence an observed multiaddr is publicly dialable - + * this will make it appear in the output of getAddresses() + */ + confirmObservedAddr: (addr: Multiaddr) => void + + /** + * Signal that we do not have confidence an observed multiaddr is publicly dialable - + * this will remove it from the output of getObservedAddrs() + */ + removeObservedAddr: (addr: Multiaddr) => void + + /** + * Add peer observed addresses. These will then appear in the output of getObservedAddrs + * but not getAddresses() until their dialability has been confirmed via a call to + * confirmObservedAddr. + */ + addObservedAddr: (addr: Multiaddr) => void + + /** + * Get the current node's addresses + */ + getAddresses: () => Multiaddr[] +} diff --git a/packages/interface-address-manager/tsconfig.json b/packages/interface-address-manager/tsconfig.json new file mode 100644 index 0000000000..5fe8ea40d7 --- /dev/null +++ b/packages/interface-address-manager/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "aegir/src/config/tsconfig.aegir.json", + "compilerOptions": { + "outDir": "dist" + }, + "include": [ + "src" + ] +} diff --git a/packages/interface-compliance-tests/CHANGELOG.md b/packages/interface-compliance-tests/CHANGELOG.md new file mode 100644 index 0000000000..35f9edbfff --- /dev/null +++ b/packages/interface-compliance-tests/CHANGELOG.md @@ -0,0 +1,530 @@ +## [@libp2p/interface-compliance-tests-v3.0.7](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-compliance-tests-v3.0.6...@libp2p/interface-compliance-tests-v3.0.7) (2023-05-04) + + +### Dependencies + +* bump aegir from 38.1.8 to 39.0.5 ([#393](https://github.com/libp2p/js-libp2p-interfaces/issues/393)) ([31f3797](https://github.com/libp2p/js-libp2p-interfaces/commit/31f3797b24f7c23f3f16e9db3a230bd5f7cd5175)) + +## [@libp2p/interface-compliance-tests-v3.0.6](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-compliance-tests-v3.0.5...@libp2p/interface-compliance-tests-v3.0.6) (2023-01-18) + + +### Trivial Changes + +* remove lerna ([#330](https://github.com/libp2p/js-libp2p-interfaces/issues/330)) ([6678592](https://github.com/libp2p/js-libp2p-interfaces/commit/6678592dd0cf601a2671852f9d2a0aff5dee2b18)) + + +### Dependencies + +* bump aegir from 37.12.1 to 38.1.0 ([#335](https://github.com/libp2p/js-libp2p-interfaces/issues/335)) ([7368a36](https://github.com/libp2p/js-libp2p-interfaces/commit/7368a363423a08e8fa247dcb76ea13e4cf030d65)) + +## [@libp2p/interface-compliance-tests-v3.0.5](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-compliance-tests-v3.0.4...@libp2p/interface-compliance-tests-v3.0.5) (2022-12-16) + + +### Documentation + +* update project config ([#323](https://github.com/libp2p/js-libp2p-interfaces/issues/323)) ([0fc6a08](https://github.com/libp2p/js-libp2p-interfaces/commit/0fc6a08e9cdcefe361fe325281a3a2a03759ff59)) + +## [@libp2p/interface-compliance-tests-v3.0.4](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-compliance-tests-v3.0.3...@libp2p/interface-compliance-tests-v3.0.4) (2022-12-14) + + +### Bug Fixes + +* generate docs for all packages ([#321](https://github.com/libp2p/js-libp2p-interfaces/issues/321)) ([b6f8b32](https://github.com/libp2p/js-libp2p-interfaces/commit/b6f8b32a920c15a28fe021e6050e31aaae89d518)) + +## [@libp2p/interface-compliance-tests-v3.0.3](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-compliance-tests-v3.0.2...@libp2p/interface-compliance-tests-v3.0.3) (2022-11-05) + + +### Bug Fixes + +* update project config ([#311](https://github.com/libp2p/js-libp2p-interfaces/issues/311)) ([27dd0ce](https://github.com/libp2p/js-libp2p-interfaces/commit/27dd0ce3c249892ac69cbb24ddaf0b9f32385e37)) + + +### Trivial Changes + +* update project config ([#271](https://github.com/libp2p/js-libp2p-interfaces/issues/271)) ([59c0bf5](https://github.com/libp2p/js-libp2p-interfaces/commit/59c0bf5e0b05496fca2e4902632b61bb41fad9e9)) + +## [@libp2p/interface-compliance-tests-v3.0.2](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-compliance-tests-v3.0.1...@libp2p/interface-compliance-tests-v3.0.2) (2022-06-27) + + +### Trivial Changes + +* update deps ([#262](https://github.com/libp2p/js-libp2p-interfaces/issues/262)) ([51edf7d](https://github.com/libp2p/js-libp2p-interfaces/commit/51edf7d9b3765a6f75c915b1483ea345d0133a41)) + +## [@libp2p/interface-compliance-tests-v3.0.1](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-compliance-tests-v3.0.0...@libp2p/interface-compliance-tests-v3.0.1) (2022-06-14) + + +### Trivial Changes + +* update readmes ([#233](https://github.com/libp2p/js-libp2p-interfaces/issues/233)) ([ee7da38](https://github.com/libp2p/js-libp2p-interfaces/commit/ee7da38dccc08160d26c8436df8739ce7e0b340e)) + +## [@libp2p/interface-compliance-tests-v3.0.0](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-compliance-tests-v2.0.3...@libp2p/interface-compliance-tests-v3.0.0) (2022-06-14) + + +### ⚠ BREAKING CHANGES + +* most modules have been split out of the `@libp2p/interfaces` and `@libp2p/interface-compliance-tests` packages + +### Trivial Changes + +* break modules apart ([#232](https://github.com/libp2p/js-libp2p-interfaces/issues/232)) ([385614e](https://github.com/libp2p/js-libp2p-interfaces/commit/385614e772329052ab17415c8bd421f65b01a61b)), closes [#226](https://github.com/libp2p/js-libp2p-interfaces/issues/226) + +## [@libp2p/interface-compliance-tests-v2.0.3](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-compliance-tests-v2.0.2...@libp2p/interface-compliance-tests-v2.0.3) (2022-05-24) + + +### Bug Fixes + +* only close muxed stream for reading ([#220](https://github.com/libp2p/js-libp2p-interfaces/issues/220)) ([f2f7141](https://github.com/libp2p/js-libp2p-interfaces/commit/f2f7141f01af715e600201ac9e7e52fbbb5c7e1b)) + +## [@libp2p/interface-compliance-tests-v2.0.2](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-compliance-tests-v2.0.1...@libp2p/interface-compliance-tests-v2.0.2) (2022-05-24) + + +### Bug Fixes + +* accept abort options in connection.newStream ([#219](https://github.com/libp2p/js-libp2p-interfaces/issues/219)) ([8bfcbc9](https://github.com/libp2p/js-libp2p-interfaces/commit/8bfcbc9ee883336f213cdfc83e477549ca368df5)) +* chunk data in mock muxer ([#218](https://github.com/libp2p/js-libp2p-interfaces/issues/218)) ([14604f6](https://github.com/libp2p/js-libp2p-interfaces/commit/14604f69a858bf8c16ce118420c5e49f3f5331ea)) + +## [@libp2p/interface-compliance-tests-v2.0.1](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-compliance-tests-v2.0.0...@libp2p/interface-compliance-tests-v2.0.1) (2022-05-23) + + +### Bug Fixes + +* make stream return types synchronous ([#217](https://github.com/libp2p/js-libp2p-interfaces/issues/217)) ([2fe61b7](https://github.com/libp2p/js-libp2p-interfaces/commit/2fe61b7fbeda2e549edf095a927d623aa8eb476b)) + +## [@libp2p/interface-compliance-tests-v2.0.0](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-compliance-tests-v1.1.34...@libp2p/interface-compliance-tests-v2.0.0) (2022-05-20) + + +### ⚠ BREAKING CHANGES + +* This adds closeWrite and closeRead checks in the tests, which will cause test failures for muxers that don't implement those + +### Bug Fixes + +* close streams when connection is closed ([#214](https://github.com/libp2p/js-libp2p-interfaces/issues/214)) ([88fcd58](https://github.com/libp2p/js-libp2p-interfaces/commit/88fcd586276e03dd740c7095f05e21754ac1f3b5)), closes [#90](https://github.com/libp2p/js-libp2p-interfaces/issues/90) +* update interfaces ([#215](https://github.com/libp2p/js-libp2p-interfaces/issues/215)) ([72e6890](https://github.com/libp2p/js-libp2p-interfaces/commit/72e6890826dadbd6e7cbba5536bde350ca4286e6)) + +## [@libp2p/interface-compliance-tests-v1.1.34](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-compliance-tests-v1.1.33...@libp2p/interface-compliance-tests-v1.1.34) (2022-05-10) + + +### Trivial Changes + +* **deps:** bump sinon from 13.0.2 to 14.0.0 ([#211](https://github.com/libp2p/js-libp2p-interfaces/issues/211)) ([8859f70](https://github.com/libp2p/js-libp2p-interfaces/commit/8859f70943c0bcdb210f54a338ae901739e5e6f2)) + +## [@libp2p/interface-compliance-tests-v1.1.33](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-compliance-tests-v1.1.32...@libp2p/interface-compliance-tests-v1.1.33) (2022-05-06) + + +### Bug Fixes + +* add tag to peer discovery interface ([#210](https://github.com/libp2p/js-libp2p-interfaces/issues/210)) ([f99c833](https://github.com/libp2p/js-libp2p-interfaces/commit/f99c833c8436f8434f380d890ec5d267279312d7)) + +## [@libp2p/interface-compliance-tests-v1.1.32](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-compliance-tests-v1.1.31...@libp2p/interface-compliance-tests-v1.1.32) (2022-05-04) + + +### Bug Fixes + +* move startable and events interfaces ([#209](https://github.com/libp2p/js-libp2p-interfaces/issues/209)) ([8ce8a08](https://github.com/libp2p/js-libp2p-interfaces/commit/8ce8a08c94b0738aa32da516558977b195ddd8ed)) + +## [@libp2p/interface-compliance-tests-v1.1.31](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-compliance-tests-v1.1.30...@libp2p/interface-compliance-tests-v1.1.31) (2022-05-03) + + +### Bug Fixes + +* only send handled protocols ([#207](https://github.com/libp2p/js-libp2p-interfaces/issues/207)) ([1f7afc2](https://github.com/libp2p/js-libp2p-interfaces/commit/1f7afc29d72fde708064ec6479011dbc0a225962)) + +## [@libp2p/interface-compliance-tests-v1.1.30](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-compliance-tests-v1.1.29...@libp2p/interface-compliance-tests-v1.1.30) (2022-05-01) + + +### Bug Fixes + +* move connection manager mock to connection manager module ([#205](https://github.com/libp2p/js-libp2p-interfaces/issues/205)) ([a367375](https://github.com/libp2p/js-libp2p-interfaces/commit/a367375accc690d7b4608c9a3313f91df700efd8)) + +## [@libp2p/interface-compliance-tests-v1.1.29](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-compliance-tests-v1.1.28...@libp2p/interface-compliance-tests-v1.1.29) (2022-04-28) + + +### Bug Fixes + +* pubsub should not be startable ([#204](https://github.com/libp2p/js-libp2p-interfaces/issues/204)) ([59bd924](https://github.com/libp2p/js-libp2p-interfaces/commit/59bd9245a207268525bdd26a05c5306fe436fcc4)) + +## [@libp2p/interface-compliance-tests-v1.1.28](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-compliance-tests-v1.1.27...@libp2p/interface-compliance-tests-v1.1.28) (2022-04-28) + + +### Bug Fixes + +* pubsub and dht are always set ([#203](https://github.com/libp2p/js-libp2p-interfaces/issues/203)) ([86860c1](https://github.com/libp2p/js-libp2p-interfaces/commit/86860c1836a2464b2ad380b09542e3f3271ae287)) + +## [@libp2p/interface-compliance-tests-v1.1.27](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-compliance-tests-v1.1.26...@libp2p/interface-compliance-tests-v1.1.27) (2022-04-26) + + +### Bug Fixes + +* add delays for gossipsub ([#202](https://github.com/libp2p/js-libp2p-interfaces/issues/202)) ([cf85799](https://github.com/libp2p/js-libp2p-interfaces/commit/cf85799fdd0d4848ad2187bbbb0dd6ac5e8cb254)) + +## [@libp2p/interface-compliance-tests-v1.1.26](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-compliance-tests-v1.1.25...@libp2p/interface-compliance-tests-v1.1.26) (2022-04-25) + + +### Bug Fixes + +* stop pubsub after test ([#200](https://github.com/libp2p/js-libp2p-interfaces/issues/200)) ([2d2650c](https://github.com/libp2p/js-libp2p-interfaces/commit/2d2650cb8cabce137665aafd55a2fb14cbd5dacd)) + +## [@libp2p/interface-compliance-tests-v1.1.25](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-compliance-tests-v1.1.24...@libp2p/interface-compliance-tests-v1.1.25) (2022-04-22) + + +### Bug Fixes + +* update pubsub interface in line with gossipsub ([#199](https://github.com/libp2p/js-libp2p-interfaces/issues/199)) ([3f55596](https://github.com/libp2p/js-libp2p-interfaces/commit/3f555965cddea3ef03e7217b755c82aa4107e093)) + +## [@libp2p/interface-compliance-tests-v1.1.24](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-compliance-tests-v1.1.23...@libp2p/interface-compliance-tests-v1.1.24) (2022-04-21) + + +### Bug Fixes + +* test PubSub interface and not PubSubBaseProtocol ([#198](https://github.com/libp2p/js-libp2p-interfaces/issues/198)) ([96c15c9](https://github.com/libp2p/js-libp2p-interfaces/commit/96c15c9780821a3cb763e48854d64377bf562692)) + +## [@libp2p/interface-compliance-tests-v1.1.23](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-compliance-tests-v1.1.22...@libp2p/interface-compliance-tests-v1.1.23) (2022-04-20) + + +### Bug Fixes + +* emit pubsub messages using 'message' event ([#197](https://github.com/libp2p/js-libp2p-interfaces/issues/197)) ([df9b685](https://github.com/libp2p/js-libp2p-interfaces/commit/df9b685cea30653109f2fa2cb5583a3bca7b09bb)) + +## [@libp2p/interface-compliance-tests-v1.1.22](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-compliance-tests-v1.1.21...@libp2p/interface-compliance-tests-v1.1.22) (2022-04-19) + + +### Bug Fixes + +* move dev deps to prod ([#195](https://github.com/libp2p/js-libp2p-interfaces/issues/195)) ([3e1ffc7](https://github.com/libp2p/js-libp2p-interfaces/commit/3e1ffc7b174e74be483943ad4e5fcab823ae3f6d)) + +## [@libp2p/interface-compliance-tests-v1.1.21](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-compliance-tests-v1.1.20...@libp2p/interface-compliance-tests-v1.1.21) (2022-04-08) + + +### Bug Fixes + +* swap protobufjs for protons ([#191](https://github.com/libp2p/js-libp2p-interfaces/issues/191)) ([d72b30c](https://github.com/libp2p/js-libp2p-interfaces/commit/d72b30cfca4b9145e0b31db28e8fa3329a180e83)) + + +### Trivial Changes + +* update aegir ([#192](https://github.com/libp2p/js-libp2p-interfaces/issues/192)) ([41c1494](https://github.com/libp2p/js-libp2p-interfaces/commit/41c14941e8b67d6601a90b4d48a2776573d55e60)) + +## [@libp2p/interface-compliance-tests-v1.1.20](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-compliance-tests-v1.1.19...@libp2p/interface-compliance-tests-v1.1.20) (2022-03-24) + + +### Bug Fixes + +* rename peer data to peer info ([#187](https://github.com/libp2p/js-libp2p-interfaces/issues/187)) ([dfea342](https://github.com/libp2p/js-libp2p-interfaces/commit/dfea3429bad57abde040397e4e7a58539829e9c2)) + +## [@libp2p/interface-compliance-tests-v1.1.19](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-compliance-tests-v1.1.18...@libp2p/interface-compliance-tests-v1.1.19) (2022-03-22) + + +### Bug Fixes + +* add method for startable lifecyle ([#186](https://github.com/libp2p/js-libp2p-interfaces/issues/186)) ([2730e29](https://github.com/libp2p/js-libp2p-interfaces/commit/2730e2947bbd231db3f7f82951b51ee534733ab2)) + +## [@libp2p/interface-compliance-tests-v1.1.18](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-compliance-tests-v1.1.17...@libp2p/interface-compliance-tests-v1.1.18) (2022-03-20) + + +### Bug Fixes + +* update pubsub types ([#183](https://github.com/libp2p/js-libp2p-interfaces/issues/183)) ([7ef4baa](https://github.com/libp2p/js-libp2p-interfaces/commit/7ef4baad0fe30f783f3eecd5199ef92af08b7f57)) + +## [@libp2p/interface-compliance-tests-v1.1.17](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-compliance-tests-v1.1.16...@libp2p/interface-compliance-tests-v1.1.17) (2022-03-15) + + +### Bug Fixes + +* use custom event instead of error event ([#181](https://github.com/libp2p/js-libp2p-interfaces/issues/181)) ([71ab242](https://github.com/libp2p/js-libp2p-interfaces/commit/71ab2424dfbf6337111d6d9d994f27c7967c20f1)) + +## [@libp2p/interface-compliance-tests-v1.1.16](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-compliance-tests-v1.1.15...@libp2p/interface-compliance-tests-v1.1.16) (2022-03-15) + + +### Bug Fixes + +* simplify transport interface, update interfaces for use with libp2p ([#180](https://github.com/libp2p/js-libp2p-interfaces/issues/180)) ([ec81622](https://github.com/libp2p/js-libp2p-interfaces/commit/ec81622e5b7c6d256e0f8aed6d3695642473293b)) + +## [@libp2p/interface-compliance-tests-v1.1.15](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-compliance-tests-v1.1.14...@libp2p/interface-compliance-tests-v1.1.15) (2022-02-27) + + +### Bug Fixes + +* rename crypto to connection-encrypter ([#179](https://github.com/libp2p/js-libp2p-interfaces/issues/179)) ([d197f55](https://github.com/libp2p/js-libp2p-interfaces/commit/d197f554d7cdadb3b05ed2d6c69fda2c4362b1eb)) + +## [@libp2p/interface-compliance-tests-v1.1.14](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-compliance-tests-v1.1.13...@libp2p/interface-compliance-tests-v1.1.14) (2022-02-27) + + +### Bug Fixes + +* update package config and add connection gater interface ([#178](https://github.com/libp2p/js-libp2p-interfaces/issues/178)) ([c6079a6](https://github.com/libp2p/js-libp2p-interfaces/commit/c6079a6367f004788062df3e30ad2e26330d947b)) + +## [@libp2p/interface-compliance-tests-v1.1.13](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-compliance-tests-v1.1.12...@libp2p/interface-compliance-tests-v1.1.13) (2022-02-21) + + +### Bug Fixes + +* increase stream test timeout ([#175](https://github.com/libp2p/js-libp2p-interfaces/issues/175)) ([568aefb](https://github.com/libp2p/js-libp2p-interfaces/commit/568aefb5c099ba0161ffecf86bda238b92d396b0)) + +## [@libp2p/interface-compliance-tests-v1.1.12](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-compliance-tests-v1.1.11...@libp2p/interface-compliance-tests-v1.1.12) (2022-02-21) + + +### Bug Fixes + +* update muxer to pass transport tests ([#174](https://github.com/libp2p/js-libp2p-interfaces/issues/174)) ([466ed53](https://github.com/libp2p/js-libp2p-interfaces/commit/466ed53192aa196ac2dbdb83df3c8db9cd5b1e07)) + +## [@libp2p/interface-compliance-tests-v1.1.11](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-compliance-tests-v1.1.10...@libp2p/interface-compliance-tests-v1.1.11) (2022-02-18) + + +### Bug Fixes + +* remove delays from pubsub tests ([#173](https://github.com/libp2p/js-libp2p-interfaces/issues/173)) ([5c8fe09](https://github.com/libp2p/js-libp2p-interfaces/commit/5c8fe09294f0cbd8add1406a61fa7dbc5b4e788b)) + +## [@libp2p/interface-compliance-tests-v1.1.10](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-compliance-tests-v1.1.9...@libp2p/interface-compliance-tests-v1.1.10) (2022-02-18) + + +### Bug Fixes + +* simpler pubsub ([#172](https://github.com/libp2p/js-libp2p-interfaces/issues/172)) ([98715ed](https://github.com/libp2p/js-libp2p-interfaces/commit/98715ed73183b32e4fda3d878a462389548358d9)) + +## [@libp2p/interface-compliance-tests-v1.1.9](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-compliance-tests-v1.1.8...@libp2p/interface-compliance-tests-v1.1.9) (2022-02-17) + + +### Bug Fixes + +* update deps ([#171](https://github.com/libp2p/js-libp2p-interfaces/issues/171)) ([d0d2564](https://github.com/libp2p/js-libp2p-interfaces/commit/d0d2564a84a0722ab587a3aa6ec01e222442b100)) + +## [@libp2p/interface-compliance-tests-v1.1.8](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-compliance-tests-v1.1.7...@libp2p/interface-compliance-tests-v1.1.8) (2022-02-17) + + +### Bug Fixes + +* add multistream-select and update pubsub types ([#170](https://github.com/libp2p/js-libp2p-interfaces/issues/170)) ([b9ecb2b](https://github.com/libp2p/js-libp2p-interfaces/commit/b9ecb2bee8f2abc0c41bfcf7bf2025894e37ddc2)) + +## [@libp2p/interface-compliance-tests-v1.1.7](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-compliance-tests-v1.1.6...@libp2p/interface-compliance-tests-v1.1.7) (2022-02-16) + + +### Bug Fixes + +* test muxer ([#169](https://github.com/libp2p/js-libp2p-interfaces/issues/169)) ([574723d](https://github.com/libp2p/js-libp2p-interfaces/commit/574723d11007e875e7adfa5c32819445f9b8def7)) + +## [@libp2p/interface-compliance-tests-v1.1.6](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-compliance-tests-v1.1.5...@libp2p/interface-compliance-tests-v1.1.6) (2022-02-12) + + +### Bug Fixes + +* return registered topologies in mock ([#168](https://github.com/libp2p/js-libp2p-interfaces/issues/168)) ([1583019](https://github.com/libp2p/js-libp2p-interfaces/commit/158301982384a694ac3fb8f9df67c71b7b776b47)) + +## [@libp2p/interface-compliance-tests-v1.1.5](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-compliance-tests-v1.1.4...@libp2p/interface-compliance-tests-v1.1.5) (2022-02-12) + + +### Bug Fixes + +* hide implementations behind factory methods ([#167](https://github.com/libp2p/js-libp2p-interfaces/issues/167)) ([2fba080](https://github.com/libp2p/js-libp2p-interfaces/commit/2fba0800c9896af6dcc49da4fa904bb4a3e3e40d)) + +## [@libp2p/interface-compliance-tests-v1.1.4](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-compliance-tests-v1.1.3...@libp2p/interface-compliance-tests-v1.1.4) (2022-02-11) + + +### Bug Fixes + +* simpler topologies ([#164](https://github.com/libp2p/js-libp2p-interfaces/issues/164)) ([45fcaa1](https://github.com/libp2p/js-libp2p-interfaces/commit/45fcaa10a6a3215089340ff2eff117d7fd1100e7)) + +## [@libp2p/interface-compliance-tests-v1.1.3](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-compliance-tests-v1.1.2...@libp2p/interface-compliance-tests-v1.1.3) (2022-02-10) + + +### Bug Fixes + +* make registrar simpler ([#163](https://github.com/libp2p/js-libp2p-interfaces/issues/163)) ([d122f3d](https://github.com/libp2p/js-libp2p-interfaces/commit/d122f3daaccc04039d90814960da92b513265644)) + +## [@libp2p/interface-compliance-tests-v1.1.2](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-compliance-tests-v1.1.1...@libp2p/interface-compliance-tests-v1.1.2) (2022-02-10) + + +### Bug Fixes + +* remove args from listener events ([#162](https://github.com/libp2p/js-libp2p-interfaces/issues/162)) ([011ac89](https://github.com/libp2p/js-libp2p-interfaces/commit/011ac891ec7d44625cb4342f068bcd9f241a5b45)) + +## [@libp2p/interface-compliance-tests-v1.1.1](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-compliance-tests-v1.1.0...@libp2p/interface-compliance-tests-v1.1.1) (2022-02-10) + + +### Bug Fixes + +* remove node event emitters ([#161](https://github.com/libp2p/js-libp2p-interfaces/issues/161)) ([221fb6a](https://github.com/libp2p/js-libp2p-interfaces/commit/221fb6a024430dc56288d73d8b8ce1aa88427701)) + +## [@libp2p/interface-compliance-tests-v1.1.0](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-compliance-tests-v1.0.8...@libp2p/interface-compliance-tests-v1.1.0) (2022-02-09) + + +### Features + +* add peer store/records, and streams are just streams ([#160](https://github.com/libp2p/js-libp2p-interfaces/issues/160)) ([8860a0c](https://github.com/libp2p/js-libp2p-interfaces/commit/8860a0cd46b359a5648402d83870f7ff957222fe)) + +## [@libp2p/interface-compliance-tests-v1.0.8](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-compliance-tests-v1.0.7...@libp2p/interface-compliance-tests-v1.0.8) (2022-02-05) + + +### Bug Fixes + +* fix muxer tests ([#157](https://github.com/libp2p/js-libp2p-interfaces/issues/157)) ([7233c44](https://github.com/libp2p/js-libp2p-interfaces/commit/7233c4438479dff56a682f45209ef7a938d63857)) + +## [@libp2p/interface-compliance-tests-v1.0.7](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-compliance-tests-v1.0.6...@libp2p/interface-compliance-tests-v1.0.7) (2022-01-31) + + +### Trivial Changes + +* **deps:** bump sinon from 12.0.1 to 13.0.0 ([#154](https://github.com/libp2p/js-libp2p-interfaces/issues/154)) ([3fc8812](https://github.com/libp2p/js-libp2p-interfaces/commit/3fc8812897fa197e7b62f77614abaea4a5563404)) + +## [@libp2p/interface-compliance-tests-v1.0.6](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-compliance-tests-v1.0.5...@libp2p/interface-compliance-tests-v1.0.6) (2022-01-29) + + +### Bug Fixes + +* remove extra fields ([#153](https://github.com/libp2p/js-libp2p-interfaces/issues/153)) ([ccd7cf3](https://github.com/libp2p/js-libp2p-interfaces/commit/ccd7cf3f5ac71337baf516d3b0f6fc724ee0d3b4)) + +## [@libp2p/interface-compliance-tests-v1.0.5](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-compliance-tests-v1.0.4...@libp2p/interface-compliance-tests-v1.0.5) (2022-01-15) + + +### Bug Fixes + +* remove abort controller dep ([#151](https://github.com/libp2p/js-libp2p-interfaces/issues/151)) ([518bce1](https://github.com/libp2p/js-libp2p-interfaces/commit/518bce1f9bd1f8b2922338e0c65c9934af7da3af)) + +## [@libp2p/interface-compliance-tests-v1.0.4](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-compliance-tests-v1.0.3...@libp2p/interface-compliance-tests-v1.0.4) (2022-01-15) + + +### Trivial Changes + +* update project config ([#149](https://github.com/libp2p/js-libp2p-interfaces/issues/149)) ([6eb8556](https://github.com/libp2p/js-libp2p-interfaces/commit/6eb85562c0da167d222808da10a7914daf12970b)) + +## [@libp2p/interface-compliance-tests-v1.0.3](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-compliance-tests-v1.0.2...@libp2p/interface-compliance-tests-v1.0.3) (2022-01-14) + + +### Bug Fixes + +* update it-* deps to ts versions ([#148](https://github.com/libp2p/js-libp2p-interfaces/issues/148)) ([7a6fdd7](https://github.com/libp2p/js-libp2p-interfaces/commit/7a6fdd7622ce2870b89dbb849ab421d0dd714b43)) + +## [@libp2p/interface-compliance-tests-v1.0.2](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-compliance-tests-v1.0.1...@libp2p/interface-compliance-tests-v1.0.2) (2022-01-08) + + +### Trivial Changes + +* add semantic release config ([#141](https://github.com/libp2p/js-libp2p-interfaces/issues/141)) ([5f0de59](https://github.com/libp2p/js-libp2p-interfaces/commit/5f0de59136b6343d2411abb2d6a4dd2cd0b7efe4)) +* update package versions ([#140](https://github.com/libp2p/js-libp2p-interfaces/issues/140)) ([cd844f6](https://github.com/libp2p/js-libp2p-interfaces/commit/cd844f6e39f4ee50d006e86eac8dadf696900eb5)) + +# Change Log + +All notable changes to this project will be documented in this file. +See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +# 0.2.0 (2022-01-04) + + +### Features + +* add auto-publish ([7aede5d](https://github.com/libp2p/js-libp2p-interfaces/commit/7aede5df39ea6b5f243348ec9a212b3e33c16a81)) +* update package names ([#133](https://github.com/libp2p/js-libp2p-interfaces/issues/133)) ([337adc9](https://github.com/libp2p/js-libp2p-interfaces/commit/337adc9a9bc0278bdae8cbce9c57d07a83c8b5c2)) + + + + + +## [3.1.1](https://github.com/libp2p/js-libp2p-interfaces/compare/libp2p-interfaces-compliance-tests@3.1.0...libp2p-interfaces-compliance-tests@3.1.1) (2022-01-02) + + +### Bug Fixes + +* move errors ([#132](https://github.com/libp2p/js-libp2p-interfaces/issues/132)) ([21d282a](https://github.com/libp2p/js-libp2p-interfaces/commit/21d282a6d77bd7d1a12daa1cc8b3a3fed8635dad)) + + + + + +# [3.1.0](https://github.com/libp2p/js-libp2p-interfaces/compare/libp2p-interfaces-compliance-tests@3.0.0...libp2p-interfaces-compliance-tests@3.1.0) (2022-01-02) + + +### Bug Fixes + +* update dialer tests ([#116](https://github.com/libp2p/js-libp2p-interfaces/issues/116)) ([c679729](https://github.com/libp2p/js-libp2p-interfaces/commit/c679729113feb963ff27815fcafd7af51f722df7)) + + +### Features + +* simpler peer id ([#117](https://github.com/libp2p/js-libp2p-interfaces/issues/117)) ([fa2c4f5](https://github.com/libp2p/js-libp2p-interfaces/commit/fa2c4f5be74a5cfc11489771881e57b4e53bf174)) + + + + + +# [3.0.0](https://github.com/libp2p/js-libp2p-interfaces/compare/libp2p-interfaces-compliance-tests@2.0.0...libp2p-interfaces-compliance-tests@3.0.0) (2021-12-02) + + +### chore + +* update libp2p-crypto and peer-id ([c711e8b](https://github.com/libp2p/js-libp2p-interfaces/commit/c711e8bd4d606f6974b13fad2eeb723f93cebb87)) + + +### BREAKING CHANGES + +* requires node 15+ + + + + + +# [2.0.0](https://github.com/libp2p/js-libp2p-interfaces/compare/libp2p-interfaces-compliance-tests@1.1.2...libp2p-interfaces-compliance-tests@2.0.0) (2021-11-22) + + +### Features + +* split out code, convert to typescript ([#111](https://github.com/libp2p/js-libp2p-interfaces/issues/111)) ([e174bba](https://github.com/libp2p/js-libp2p-interfaces/commit/e174bba889388269b806643c79a6b53c8d6a0f8c)), closes [#110](https://github.com/libp2p/js-libp2p-interfaces/issues/110) [#101](https://github.com/libp2p/js-libp2p-interfaces/issues/101) + + +### BREAKING CHANGES + +* not all fields from concrete classes have been added to the interfaces, some adjustment may be necessary as this gets rolled out + + + + + +## [1.1.2](https://github.com/libp2p/js-libp2p-interfaces/compare/libp2p-interfaces-compliance-tests@1.1.1...libp2p-interfaces-compliance-tests@1.1.2) (2021-10-18) + +**Note:** Version bump only for package libp2p-interfaces-compliance-tests + + + + + +## [1.1.1](https://github.com/libp2p/js-libp2p-interfaces/compare/libp2p-interfaces-compliance-tests@1.1.0...libp2p-interfaces-compliance-tests@1.1.1) (2021-09-20) + +**Note:** Version bump only for package libp2p-interfaces-compliance-tests + + + + + +# [1.1.0](https://github.com/libp2p/js-libp2p-interfaces/compare/libp2p-interfaces-compliance-tests@1.0.1...libp2p-interfaces-compliance-tests@1.1.0) (2021-08-20) + + +### Features + +* update uint8arrays ([#105](https://github.com/libp2p/js-libp2p-interfaces/issues/105)) ([9297a9c](https://github.com/libp2p/js-libp2p-interfaces/commit/9297a9c379276d03c8da849af6108b38e581b4a6)) + + + + + +## [1.0.1](https://github.com/libp2p/js-libp2p-interfaces/compare/libp2p-interfaces-compliance-tests@1.0.0...libp2p-interfaces-compliance-tests@1.0.1) (2021-07-08) + + +### Bug Fixes + +* make tests more reliable ([#103](https://github.com/libp2p/js-libp2p-interfaces/issues/103)) ([cd4c409](https://github.com/libp2p/js-libp2p-interfaces/commit/cd4c40908efe2e9ffc14aa61aace5176a43fd70a)) +* remove timeouts ([#104](https://github.com/libp2p/js-libp2p-interfaces/issues/104)) ([3699c17](https://github.com/libp2p/js-libp2p-interfaces/commit/3699c17f022da40a87ab24adc3b2081df7a0ddcd)) + + + + + +# 1.0.0 (2021-07-07) + + +### chore + +* monorepo separating interfaces and compliance tests ([#97](https://github.com/libp2p/js-libp2p-interfaces/issues/97)) ([946348f](https://github.com/libp2p/js-libp2p-interfaces/commit/946348f7f8acc1ff7bc9cd0ab4c2602d41106f76)) + + +### BREAKING CHANGES + +* the tests now live in the libp2p-interfaces-compliance-tests module diff --git a/packages/interface-compliance-tests/LICENSE b/packages/interface-compliance-tests/LICENSE new file mode 100644 index 0000000000..20ce483c86 --- /dev/null +++ b/packages/interface-compliance-tests/LICENSE @@ -0,0 +1,4 @@ +This project is dual licensed under MIT and Apache-2.0. + +MIT: https://www.opensource.org/licenses/mit +Apache-2.0: https://www.apache.org/licenses/license-2.0 diff --git a/packages/interface-compliance-tests/LICENSE-APACHE b/packages/interface-compliance-tests/LICENSE-APACHE new file mode 100644 index 0000000000..14478a3b60 --- /dev/null +++ b/packages/interface-compliance-tests/LICENSE-APACHE @@ -0,0 +1,5 @@ +Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. diff --git a/packages/interface-compliance-tests/LICENSE-MIT b/packages/interface-compliance-tests/LICENSE-MIT new file mode 100644 index 0000000000..72dc60d84b --- /dev/null +++ b/packages/interface-compliance-tests/LICENSE-MIT @@ -0,0 +1,19 @@ +The MIT License (MIT) + +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/packages/interface-compliance-tests/README.md b/packages/interface-compliance-tests/README.md new file mode 100644 index 0000000000..295cf0c742 --- /dev/null +++ b/packages/interface-compliance-tests/README.md @@ -0,0 +1,41 @@ +# @libp2p/interface-compliance-tests + +[![libp2p.io](https://img.shields.io/badge/project-libp2p-yellow.svg?style=flat-square)](http://libp2p.io/) +[![Discuss](https://img.shields.io/discourse/https/discuss.libp2p.io/posts.svg?style=flat-square)](https://discuss.libp2p.io) +[![codecov](https://img.shields.io/codecov/c/github/libp2p/js-libp2p.svg?style=flat-square)](https://codecov.io/gh/libp2p/js-libp2p) +[![CI](https://img.shields.io/github/actions/workflow/status/libp2p/js-libp2p/js-test-and-release.yml?branch=master\&style=flat-square)](https://github.com/libp2p/js-libp2p/actions/workflows/js-test-and-release.yml?query=branch%3Amaster) + +> Compliance tests for JS libp2p interfaces + +## Table of contents + +- [Install](#install) +- [Usage](#usage) +- [API Docs](#api-docs) +- [License](#license) +- [Contribution](#contribution) + +## Install + +```console +$ npm i @libp2p/interface-compliance-tests +``` + +## Usage + +Each [interface](https://npmjs.org/packages/@libp2p/interfaces) has its documentation on how to use the compliance tests and should be used as the source of truth. + +## API Docs + +- + +## License + +Licensed under either of + +- Apache 2.0, ([LICENSE-APACHE](LICENSE-APACHE) / ) +- MIT ([LICENSE-MIT](LICENSE-MIT) / ) + +## Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. diff --git a/packages/interface-compliance-tests/package.json b/packages/interface-compliance-tests/package.json new file mode 100644 index 0000000000..b14541466f --- /dev/null +++ b/packages/interface-compliance-tests/package.json @@ -0,0 +1,160 @@ +{ + "name": "@libp2p/interface-compliance-tests", + "version": "3.0.7", + "description": "Compliance tests for JS libp2p interfaces", + "license": "Apache-2.0 OR MIT", + "homepage": "https://github.com/libp2p/js-libp2p/tree/master/packages/interface-compliance-tests#readme", + "repository": { + "type": "git", + "url": "git+https://github.com/libp2p/js-libp2p.git" + }, + "bugs": { + "url": "https://github.com/libp2p/js-libp2p/issues" + }, + "keywords": [ + "interface", + "libp2p" + ], + "type": "module", + "types": "./dist/src/index.d.ts", + "typesVersions": { + "*": { + "*": [ + "*", + "dist/*", + "dist/src/*", + "dist/src/*/index" + ], + "src/*": [ + "*", + "dist/*", + "dist/src/*", + "dist/src/*/index" + ] + } + }, + "files": [ + "src", + "dist", + "!dist/test", + "!**/*.tsbuildinfo" + ], + "exports": { + ".": { + "types": "./dist/src/index.d.ts", + "import": "./dist/src/index.js" + }, + "./is-valid-tick": { + "types": "./dist/src/is-valid-tick.d.ts", + "import": "./dist/src/is-valid-tick.js" + }, + "./peers": { + "types": "./dist/src/peers.d.ts", + "import": "./dist/src/peers.js" + } + }, + "eslintConfig": { + "extends": "ipfs", + "parserOptions": { + "sourceType": "module" + } + }, + "release": { + "branches": [ + "master" + ], + "plugins": [ + [ + "@semantic-release/commit-analyzer", + { + "preset": "conventionalcommits", + "releaseRules": [ + { + "breaking": true, + "release": "major" + }, + { + "revert": true, + "release": "patch" + }, + { + "type": "feat", + "release": "minor" + }, + { + "type": "fix", + "release": "patch" + }, + { + "type": "docs", + "release": "patch" + }, + { + "type": "test", + "release": "patch" + }, + { + "type": "deps", + "release": "patch" + }, + { + "scope": "no-release", + "release": false + } + ] + } + ], + [ + "@semantic-release/release-notes-generator", + { + "preset": "conventionalcommits", + "presetConfig": { + "types": [ + { + "type": "feat", + "section": "Features" + }, + { + "type": "fix", + "section": "Bug Fixes" + }, + { + "type": "chore", + "section": "Trivial Changes" + }, + { + "type": "docs", + "section": "Documentation" + }, + { + "type": "deps", + "section": "Dependencies" + }, + { + "type": "test", + "section": "Tests" + } + ] + } + } + ], + "@semantic-release/changelog", + "@semantic-release/npm", + "@semantic-release/github", + "@semantic-release/git" + ] + }, + "scripts": { + "clean": "aegir clean", + "lint": "aegir lint", + "dep-check": "aegir dep-check", + "build": "aegir build", + "release": "aegir release" + }, + "dependencies": { + "aegir": "^39.0.5" + }, + "typedoc": { + "entryPoint": "./src/index.ts" + } +} diff --git a/packages/interface-compliance-tests/src/index.ts b/packages/interface-compliance-tests/src/index.ts new file mode 100644 index 0000000000..7aea8b17d5 --- /dev/null +++ b/packages/interface-compliance-tests/src/index.ts @@ -0,0 +1,5 @@ + +export interface TestSetup> { + setup: (args?: SetupArgs) => Promise + teardown: () => Promise +} diff --git a/packages/interface-compliance-tests/src/is-valid-tick.ts b/packages/interface-compliance-tests/src/is-valid-tick.ts new file mode 100644 index 0000000000..8e6a1c8bcc --- /dev/null +++ b/packages/interface-compliance-tests/src/is-valid-tick.ts @@ -0,0 +1,18 @@ + +/** + * A tick is considered valid if it happened between now + * and `ms` milliseconds ago + */ +export function isValidTick (date?: number, ms: number = 5000): boolean { + if (date == null) { + throw new Error('date must be a number') + } + + const now = Date.now() + + if (date > now - ms && date <= now) { + return true + } + + return false +} diff --git a/packages/interface-compliance-tests/src/peers.ts b/packages/interface-compliance-tests/src/peers.ts new file mode 100644 index 0000000000..42bacedeac --- /dev/null +++ b/packages/interface-compliance-tests/src/peers.ts @@ -0,0 +1,25 @@ +export default [{ + 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=' +}] diff --git a/packages/interface-compliance-tests/tsconfig.json b/packages/interface-compliance-tests/tsconfig.json new file mode 100644 index 0000000000..5fe8ea40d7 --- /dev/null +++ b/packages/interface-compliance-tests/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "aegir/src/config/tsconfig.aegir.json", + "compilerOptions": { + "outDir": "dist" + }, + "include": [ + "src" + ] +} diff --git a/packages/interface-connection-compliance-tests/CHANGELOG.md b/packages/interface-connection-compliance-tests/CHANGELOG.md new file mode 100644 index 0000000000..8a64b251a8 --- /dev/null +++ b/packages/interface-connection-compliance-tests/CHANGELOG.md @@ -0,0 +1,93 @@ +## [@libp2p/interface-connection-compliance-tests-v2.0.9](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-connection-compliance-tests-v2.0.8...@libp2p/interface-connection-compliance-tests-v2.0.9) (2023-05-04) + + +### Dependencies + +* bump aegir from 38.1.8 to 39.0.5 ([#393](https://github.com/libp2p/js-libp2p-interfaces/issues/393)) ([31f3797](https://github.com/libp2p/js-libp2p-interfaces/commit/31f3797b24f7c23f3f16e9db3a230bd5f7cd5175)) + +## [@libp2p/interface-connection-compliance-tests-v2.0.8](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-connection-compliance-tests-v2.0.7...@libp2p/interface-connection-compliance-tests-v2.0.8) (2023-04-18) + + +### Dependencies + +* update sibling dependencies ([2f52a28](https://github.com/libp2p/js-libp2p-interfaces/commit/2f52a284b59c0a88b040f86da1f5d3f044727f2c)) + +## [@libp2p/interface-connection-compliance-tests-v2.0.7](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-connection-compliance-tests-v2.0.6...@libp2p/interface-connection-compliance-tests-v2.0.7) (2023-04-11) + + +### Dependencies + +* update sibling dependencies ([b034810](https://github.com/libp2p/js-libp2p-interfaces/commit/b0348102e41dc18166e70063f4708a2b3544f4b6)) + +## [@libp2p/interface-connection-compliance-tests-v2.0.6](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-connection-compliance-tests-v2.0.5...@libp2p/interface-connection-compliance-tests-v2.0.6) (2023-01-18) + + +### Trivial Changes + +* remove lerna ([#330](https://github.com/libp2p/js-libp2p-interfaces/issues/330)) ([6678592](https://github.com/libp2p/js-libp2p-interfaces/commit/6678592dd0cf601a2671852f9d2a0aff5dee2b18)) + + +### Dependencies + +* bump aegir from 37.12.1 to 38.1.0 ([#335](https://github.com/libp2p/js-libp2p-interfaces/issues/335)) ([7368a36](https://github.com/libp2p/js-libp2p-interfaces/commit/7368a363423a08e8fa247dcb76ea13e4cf030d65)) + +## [@libp2p/interface-connection-compliance-tests-v2.0.5](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-connection-compliance-tests-v2.0.4...@libp2p/interface-connection-compliance-tests-v2.0.5) (2022-12-16) + + +### Documentation + +* update project config ([#323](https://github.com/libp2p/js-libp2p-interfaces/issues/323)) ([0fc6a08](https://github.com/libp2p/js-libp2p-interfaces/commit/0fc6a08e9cdcefe361fe325281a3a2a03759ff59)) + +## [@libp2p/interface-connection-compliance-tests-v2.0.4](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-connection-compliance-tests-v2.0.3...@libp2p/interface-connection-compliance-tests-v2.0.4) (2022-12-14) + + +### Bug Fixes + +* generate docs for all packages ([#321](https://github.com/libp2p/js-libp2p-interfaces/issues/321)) ([b6f8b32](https://github.com/libp2p/js-libp2p-interfaces/commit/b6f8b32a920c15a28fe021e6050e31aaae89d518)) + + +### Dependencies + +* bump sinon from 14.0.2 to 15.0.0 ([#316](https://github.com/libp2p/js-libp2p-interfaces/issues/316)) ([d37721c](https://github.com/libp2p/js-libp2p-interfaces/commit/d37721c9143cd3eeafb5f8249b07d9f2fbce0f54)) + +## [@libp2p/interface-connection-compliance-tests-v2.0.3](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-connection-compliance-tests-v2.0.2...@libp2p/interface-connection-compliance-tests-v2.0.3) (2022-11-05) + + +### Bug Fixes + +* update project config ([#311](https://github.com/libp2p/js-libp2p-interfaces/issues/311)) ([27dd0ce](https://github.com/libp2p/js-libp2p-interfaces/commit/27dd0ce3c249892ac69cbb24ddaf0b9f32385e37)) + +## [@libp2p/interface-connection-compliance-tests-v2.0.2](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-connection-compliance-tests-v2.0.1...@libp2p/interface-connection-compliance-tests-v2.0.2) (2022-08-07) + + +### Trivial Changes + +* update project config ([#271](https://github.com/libp2p/js-libp2p-interfaces/issues/271)) ([59c0bf5](https://github.com/libp2p/js-libp2p-interfaces/commit/59c0bf5e0b05496fca2e4902632b61bb41fad9e9)) + + +### Dependencies + +* update sibling dependencies ([f859920](https://github.com/libp2p/js-libp2p-interfaces/commit/f859920423587ae797ac90ccaa3af8bdf60ae549)) + +## [@libp2p/interface-connection-compliance-tests-v2.0.1](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-connection-compliance-tests-v2.0.0...@libp2p/interface-connection-compliance-tests-v2.0.1) (2022-06-27) + + +### Trivial Changes + +* update deps ([#262](https://github.com/libp2p/js-libp2p-interfaces/issues/262)) ([51edf7d](https://github.com/libp2p/js-libp2p-interfaces/commit/51edf7d9b3765a6f75c915b1483ea345d0133a41)) + +## [@libp2p/interface-connection-compliance-tests-v2.0.0](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-connection-compliance-tests-v1.0.0...@libp2p/interface-connection-compliance-tests-v2.0.0) (2022-06-16) + + +### ⚠ BREAKING CHANGES + +* The Connection and Stream APIs have been updated + +### Features + +* store stream data on the stream, track the stream direction ([#245](https://github.com/libp2p/js-libp2p-interfaces/issues/245)) ([6d74d2f](https://github.com/libp2p/js-libp2p-interfaces/commit/6d74d2f9f344fb4d6741ba0d35263ebe351a4c65)) + + +### Trivial Changes + +* update deps ([545264f](https://github.com/libp2p/js-libp2p-interfaces/commit/545264f87a58394d2a7da77e93f3a596e889238f)) diff --git a/packages/interface-connection-compliance-tests/LICENSE b/packages/interface-connection-compliance-tests/LICENSE new file mode 100644 index 0000000000..20ce483c86 --- /dev/null +++ b/packages/interface-connection-compliance-tests/LICENSE @@ -0,0 +1,4 @@ +This project is dual licensed under MIT and Apache-2.0. + +MIT: https://www.opensource.org/licenses/mit +Apache-2.0: https://www.apache.org/licenses/license-2.0 diff --git a/packages/interface-connection-compliance-tests/LICENSE-APACHE b/packages/interface-connection-compliance-tests/LICENSE-APACHE new file mode 100644 index 0000000000..14478a3b60 --- /dev/null +++ b/packages/interface-connection-compliance-tests/LICENSE-APACHE @@ -0,0 +1,5 @@ +Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. diff --git a/packages/interface-connection-compliance-tests/LICENSE-MIT b/packages/interface-connection-compliance-tests/LICENSE-MIT new file mode 100644 index 0000000000..72dc60d84b --- /dev/null +++ b/packages/interface-connection-compliance-tests/LICENSE-MIT @@ -0,0 +1,19 @@ +The MIT License (MIT) + +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/packages/interface-connection-compliance-tests/README.md b/packages/interface-connection-compliance-tests/README.md new file mode 100644 index 0000000000..eed20b2800 --- /dev/null +++ b/packages/interface-connection-compliance-tests/README.md @@ -0,0 +1,61 @@ +# @libp2p/interface-connection-compliance-tests + +[![libp2p.io](https://img.shields.io/badge/project-libp2p-yellow.svg?style=flat-square)](http://libp2p.io/) +[![Discuss](https://img.shields.io/discourse/https/discuss.libp2p.io/posts.svg?style=flat-square)](https://discuss.libp2p.io) +[![codecov](https://img.shields.io/codecov/c/github/libp2p/js-libp2p.svg?style=flat-square)](https://codecov.io/gh/libp2p/js-libp2p) +[![CI](https://img.shields.io/github/actions/workflow/status/libp2p/js-libp2p/js-test-and-release.yml?branch=master\&style=flat-square)](https://github.com/libp2p/js-libp2p/actions/workflows/js-test-and-release.yml?query=branch%3Amaster) + +> Compliance tests for implementations of the libp2p Connection interface + +## Table of contents + +- [Install](#install) +- [Usage](#usage) +- [API Docs](#api-docs) +- [License](#license) +- [Contribution](#contribution) + +## Install + +```console +$ npm i @libp2p/interface-connection-compliance-tests +``` + +## Usage + +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 ensure compatibility since they use the same test suite. + +```js +import tests from '@libp2p/interface-connection-compliance-tests' + +describe('your connection', () => { + tests({ + // Options should be passed to your connection + async setup (options) { + return YourConnection + }, + async teardown () { + // cleanup resources created by setup() + } + }) +}) +``` + +## API Docs + +- + +## License + +Licensed under either of + +- Apache 2.0, ([LICENSE-APACHE](LICENSE-APACHE) / ) +- MIT ([LICENSE-MIT](LICENSE-MIT) / ) + +## Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. diff --git a/packages/interface-connection-compliance-tests/package.json b/packages/interface-connection-compliance-tests/package.json new file mode 100644 index 0000000000..d322034546 --- /dev/null +++ b/packages/interface-connection-compliance-tests/package.json @@ -0,0 +1,140 @@ +{ + "name": "@libp2p/interface-connection-compliance-tests", + "version": "2.0.9", + "description": "Compliance tests for implementations of the libp2p Connection interface", + "license": "Apache-2.0 OR MIT", + "homepage": "https://github.com/libp2p/js-libp2p/tree/master/packages/interface-connection-compliance-tests#readme", + "repository": { + "type": "git", + "url": "git+https://github.com/libp2p/js-libp2p.git" + }, + "bugs": { + "url": "https://github.com/libp2p/js-libp2p/issues" + }, + "keywords": [ + "interface", + "libp2p" + ], + "type": "module", + "types": "./dist/src/index.d.ts", + "files": [ + "src", + "dist", + "!dist/test", + "!**/*.tsbuildinfo" + ], + "exports": { + ".": { + "types": "./dist/src/index.d.ts", + "import": "./dist/src/index.js" + } + }, + "eslintConfig": { + "extends": "ipfs", + "parserOptions": { + "sourceType": "module" + } + }, + "release": { + "branches": [ + "master" + ], + "plugins": [ + [ + "@semantic-release/commit-analyzer", + { + "preset": "conventionalcommits", + "releaseRules": [ + { + "breaking": true, + "release": "major" + }, + { + "revert": true, + "release": "patch" + }, + { + "type": "feat", + "release": "minor" + }, + { + "type": "fix", + "release": "patch" + }, + { + "type": "docs", + "release": "patch" + }, + { + "type": "test", + "release": "patch" + }, + { + "type": "deps", + "release": "patch" + }, + { + "scope": "no-release", + "release": false + } + ] + } + ], + [ + "@semantic-release/release-notes-generator", + { + "preset": "conventionalcommits", + "presetConfig": { + "types": [ + { + "type": "feat", + "section": "Features" + }, + { + "type": "fix", + "section": "Bug Fixes" + }, + { + "type": "chore", + "section": "Trivial Changes" + }, + { + "type": "docs", + "section": "Documentation" + }, + { + "type": "deps", + "section": "Dependencies" + }, + { + "type": "test", + "section": "Tests" + } + ] + } + } + ], + "@semantic-release/changelog", + "@semantic-release/npm", + "@semantic-release/github", + "@semantic-release/git" + ] + }, + "scripts": { + "clean": "aegir clean", + "lint": "aegir lint", + "dep-check": "aegir dep-check", + "build": "aegir build", + "release": "aegir release" + }, + "dependencies": { + "@libp2p/interface-compliance-tests": "^3.0.0", + "@libp2p/interface-connection": "^5.0.0", + "aegir": "^39.0.5", + "sinon": "^15.0.0", + "ts-sinon": "^2.0.2" + }, + "typedoc": { + "entryPoint": "./src/index.ts" + } +} diff --git a/packages/interface-connection-compliance-tests/src/index.ts b/packages/interface-connection-compliance-tests/src/index.ts new file mode 100644 index 0000000000..032c0ea8bb --- /dev/null +++ b/packages/interface-connection-compliance-tests/src/index.ts @@ -0,0 +1,184 @@ +import { expect } from 'aegir/chai' +import sinon from 'sinon' +import { stubInterface } from 'ts-sinon' +import type { TestSetup } from '@libp2p/interface-compliance-tests' +import type { Connection, Stream } from '@libp2p/interface-connection' + +export default (test: TestSetup): void => { + describe('connection', () => { + describe('open connection', () => { + let connection: Connection + + beforeEach(async () => { + connection = await test.setup() + }) + + afterEach(async () => { + await connection.close() + await test.teardown() + }) + + it('should have properties set', () => { + expect(connection.id).to.exist() + expect(connection.remotePeer).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.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.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 () => { + expect(connection.streams).to.be.empty() + + const protocolToUse = '/echo/0.0.1' + const stream = await connection.newStream([protocolToUse]) + + expect(stream).to.have.nested.property('stat.protocol', protocolToUse) + + const connStreams = 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: Connection + let timelineProxy + const proxyHandler = { + set () { + // @ts-expect-error - TS fails to infer here + return Reflect.set(...arguments) + } + } + + beforeEach(async () => { + timelineProxy = new Proxy({ + open: Date.now() - 10, + upgraded: Date.now() + }, proxyHandler) + + connection = await test.setup() + connection.stat.timeline = timelineProxy + }) + + 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 properly track streams', async () => { + // Open stream + const protocol = '/echo/0.0.1' + const stream = await connection.newStream([protocol]) + expect(stream).to.have.nested.property('stat.protocol', protocol) + + // Close stream + stream.close() + + expect(connection.streams.filter(s => s.id === stream.id)).to.be.empty() + }) + + it('should track outbound streams', async () => { + // Open stream + const protocol = '/echo/0.0.1' + const stream = await connection.newStream(protocol) + expect(stream).to.have.nested.property('stat.direction', 'outbound') + }) + + it.skip('should track inbound streams', async () => { + // Add an remotely opened stream + const stream = stubInterface() + connection.addStream(stream) + expect(stream).to.have.property('direction', 'inbound') + }) + + it('should support a proxy on the timeline', async () => { + sinon.spy(proxyHandler, 'set') + expect(connection.stat.timeline.close).to.not.exist() + + await connection.close() + // @ts-expect-error - fails to infer callCount + expect(proxyHandler.set.callCount).to.equal(1) + // @ts-expect-error - fails to infer getCall + 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() + const p = connection.close() + + try { + const protocol = '/echo/0.0.1' + await connection.newStream([protocol]) + } catch (err: any) { + expect(err).to.exist() + return + } finally { + await p + } + + 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: any) { + 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/packages/interface-connection-compliance-tests/tsconfig.json b/packages/interface-connection-compliance-tests/tsconfig.json new file mode 100644 index 0000000000..40cf163c08 --- /dev/null +++ b/packages/interface-connection-compliance-tests/tsconfig.json @@ -0,0 +1,18 @@ +{ + "extends": "aegir/src/config/tsconfig.aegir.json", + "compilerOptions": { + "outDir": "dist" + }, + "include": [ + "src", + "test" + ], + "references": [ + { + "path": "../interface-compliance-tests" + }, + { + "path": "../interface-connection" + } + ] +} diff --git a/packages/interface-connection-encrypter-compliance-tests/CHANGELOG.md b/packages/interface-connection-encrypter-compliance-tests/CHANGELOG.md new file mode 100644 index 0000000000..d9442ac70f --- /dev/null +++ b/packages/interface-connection-encrypter-compliance-tests/CHANGELOG.md @@ -0,0 +1,173 @@ +## [@libp2p/interface-connection-encrypter-compliance-tests-v5.0.1](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-connection-encrypter-compliance-tests-v5.0.0...@libp2p/interface-connection-encrypter-compliance-tests-v5.0.1) (2023-05-04) + + +### Dependencies + +* bump aegir from 38.1.8 to 39.0.5 ([#393](https://github.com/libp2p/js-libp2p-interfaces/issues/393)) ([31f3797](https://github.com/libp2p/js-libp2p-interfaces/commit/31f3797b24f7c23f3f16e9db3a230bd5f7cd5175)) + +## [@libp2p/interface-connection-encrypter-compliance-tests-v5.0.0](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-connection-encrypter-compliance-tests-v4.0.5...@libp2p/interface-connection-encrypter-compliance-tests-v5.0.0) (2023-04-18) + + +### ⚠ BREAKING CHANGES + +* bump it-stream-types from 1.0.5 to 2.0.1 (#362) + +### Dependencies + +* bump it-stream-types from 1.0.5 to 2.0.1 ([#362](https://github.com/libp2p/js-libp2p-interfaces/issues/362)) ([cdc7747](https://github.com/libp2p/js-libp2p-interfaces/commit/cdc774792beead63e0ded96bd6c23de0335a49e3)) +* update sibling dependencies ([bed9f4c](https://github.com/libp2p/js-libp2p-interfaces/commit/bed9f4c7b7044e974a70678762a51e79e018cf9b)) +* update sibling dependencies ([2f52a28](https://github.com/libp2p/js-libp2p-interfaces/commit/2f52a284b59c0a88b040f86da1f5d3f044727f2c)) + +## [@libp2p/interface-connection-encrypter-compliance-tests-v4.0.5](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-connection-encrypter-compliance-tests-v4.0.4...@libp2p/interface-connection-encrypter-compliance-tests-v4.0.5) (2023-04-13) + + +### Dependencies + +* bump it-all from 2.0.1 to 3.0.1 ([#360](https://github.com/libp2p/js-libp2p-interfaces/issues/360)) ([1b439eb](https://github.com/libp2p/js-libp2p-interfaces/commit/1b439eb7503ed7e31e77f17ce0a685ea78d94442)) + +## [@libp2p/interface-connection-encrypter-compliance-tests-v4.0.4](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-connection-encrypter-compliance-tests-v4.0.3...@libp2p/interface-connection-encrypter-compliance-tests-v4.0.4) (2023-04-11) + + +### Dependencies + +* update sibling dependencies ([b034810](https://github.com/libp2p/js-libp2p-interfaces/commit/b0348102e41dc18166e70063f4708a2b3544f4b6)) + +## [@libp2p/interface-connection-encrypter-compliance-tests-v4.0.3](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-connection-encrypter-compliance-tests-v4.0.2...@libp2p/interface-connection-encrypter-compliance-tests-v4.0.3) (2023-04-04) + + +### Dependencies + +* bump it-pipe from 2.0.5 to 3.0.1 ([#363](https://github.com/libp2p/js-libp2p-interfaces/issues/363)) ([625817b](https://github.com/libp2p/js-libp2p-interfaces/commit/625817b0bbbee276983c40a0604c8810a25abe8f)) + +## [@libp2p/interface-connection-encrypter-compliance-tests-v4.0.2](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-connection-encrypter-compliance-tests-v4.0.1...@libp2p/interface-connection-encrypter-compliance-tests-v4.0.2) (2023-03-17) + + +### Dependencies + +* update @multiformats/multiaddr to 12.0.0 ([#354](https://github.com/libp2p/js-libp2p-interfaces/issues/354)) ([e0f327b](https://github.com/libp2p/js-libp2p-interfaces/commit/e0f327b5d54e240feabadce21a841629d633ec5e)) + +## [@libp2p/interface-connection-encrypter-compliance-tests-v4.0.1](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-connection-encrypter-compliance-tests-v4.0.0...@libp2p/interface-connection-encrypter-compliance-tests-v4.0.1) (2023-01-18) + + +### Trivial Changes + +* remove lerna ([#330](https://github.com/libp2p/js-libp2p-interfaces/issues/330)) ([6678592](https://github.com/libp2p/js-libp2p-interfaces/commit/6678592dd0cf601a2671852f9d2a0aff5dee2b18)) + + +### Dependencies + +* bump aegir from 37.12.1 to 38.1.0 ([#335](https://github.com/libp2p/js-libp2p-interfaces/issues/335)) ([7368a36](https://github.com/libp2p/js-libp2p-interfaces/commit/7368a363423a08e8fa247dcb76ea13e4cf030d65)) + +## [@libp2p/interface-connection-encrypter-compliance-tests-v4.0.0](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-connection-encrypter-compliance-tests-v3.0.5...@libp2p/interface-connection-encrypter-compliance-tests-v4.0.0) (2023-01-06) + + +### ⚠ BREAKING CHANGES + +* update peer-id dep to pull in new multiformats (#331) + +### Bug Fixes + +* update peer-id dep to pull in new multiformats ([#331](https://github.com/libp2p/js-libp2p-interfaces/issues/331)) ([fb8b7ba](https://github.com/libp2p/js-libp2p-interfaces/commit/fb8b7ba654a30a08da0652e2833e36dd3bb85e90)) + +## [@libp2p/interface-connection-encrypter-compliance-tests-v3.0.5](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-connection-encrypter-compliance-tests-v3.0.4...@libp2p/interface-connection-encrypter-compliance-tests-v3.0.5) (2023-01-06) + + +### Dependencies + +* update sibling dependencies ([b50e621](https://github.com/libp2p/js-libp2p-interfaces/commit/b50e621d31a8b32affc3fadb9f97c4883d577f93)) + +## [@libp2p/interface-connection-encrypter-compliance-tests-v3.0.4](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-connection-encrypter-compliance-tests-v3.0.3...@libp2p/interface-connection-encrypter-compliance-tests-v3.0.4) (2022-12-16) + + +### Documentation + +* update project config ([#323](https://github.com/libp2p/js-libp2p-interfaces/issues/323)) ([0fc6a08](https://github.com/libp2p/js-libp2p-interfaces/commit/0fc6a08e9cdcefe361fe325281a3a2a03759ff59)) + +## [@libp2p/interface-connection-encrypter-compliance-tests-v3.0.3](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-connection-encrypter-compliance-tests-v3.0.2...@libp2p/interface-connection-encrypter-compliance-tests-v3.0.3) (2022-12-14) + + +### Bug Fixes + +* generate docs for all packages ([#321](https://github.com/libp2p/js-libp2p-interfaces/issues/321)) ([b6f8b32](https://github.com/libp2p/js-libp2p-interfaces/commit/b6f8b32a920c15a28fe021e6050e31aaae89d518)) + +## [@libp2p/interface-connection-encrypter-compliance-tests-v3.0.2](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-connection-encrypter-compliance-tests-v3.0.1...@libp2p/interface-connection-encrypter-compliance-tests-v3.0.2) (2022-11-05) + + +### Bug Fixes + +* update project config ([#311](https://github.com/libp2p/js-libp2p-interfaces/issues/311)) ([27dd0ce](https://github.com/libp2p/js-libp2p-interfaces/commit/27dd0ce3c249892ac69cbb24ddaf0b9f32385e37)) + +## [@libp2p/interface-connection-encrypter-compliance-tests-v3.0.1](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-connection-encrypter-compliance-tests-v3.0.0...@libp2p/interface-connection-encrypter-compliance-tests-v3.0.1) (2022-10-17) + + +### Dependencies + +* bump it-all from 1.0.6 to 2.0.0 ([#306](https://github.com/libp2p/js-libp2p-interfaces/issues/306)) ([7c7b388](https://github.com/libp2p/js-libp2p-interfaces/commit/7c7b3882e33a064b8e5588d6befd823360347464)) + +## [@libp2p/interface-connection-encrypter-compliance-tests-v3.0.0](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-connection-encrypter-compliance-tests-v2.0.3...@libp2p/interface-connection-encrypter-compliance-tests-v3.0.0) (2022-10-12) + + +### ⚠ BREAKING CHANGES + +* modules no longer implement `Initializable` instead switching to constructor injection + +### Bug Fixes + +* remove @libp2p/components ([#301](https://github.com/libp2p/js-libp2p-interfaces/issues/301)) ([1d37dc6](https://github.com/libp2p/js-libp2p-interfaces/commit/1d37dc6d3197838a71895d5769ad8bba6eb38fd3)) + +## [@libp2p/interface-connection-encrypter-compliance-tests-v2.0.3](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-connection-encrypter-compliance-tests-v2.0.2...@libp2p/interface-connection-encrypter-compliance-tests-v2.0.3) (2022-10-04) + + +### Dependencies + +* update sibling dependencies ([419f947](https://github.com/libp2p/js-libp2p-interfaces/commit/419f9479e8bba5d0555fe20a6fb9f0cf12a82cf9)) + +## [@libp2p/interface-connection-encrypter-compliance-tests-v2.0.2](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-connection-encrypter-compliance-tests-v2.0.1...@libp2p/interface-connection-encrypter-compliance-tests-v2.0.2) (2022-09-21) + + +### Dependencies + +* update @multiformats/multiaddr to 11.0.0 ([#288](https://github.com/libp2p/js-libp2p-interfaces/issues/288)) ([57b2ad8](https://github.com/libp2p/js-libp2p-interfaces/commit/57b2ad88edfc7807311143791bc49270b1a81eaf)) + +## [@libp2p/interface-connection-encrypter-compliance-tests-v2.0.1](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-connection-encrypter-compliance-tests-v2.0.0...@libp2p/interface-connection-encrypter-compliance-tests-v2.0.1) (2022-08-10) + + +### Bug Fixes + +* revert connection encryption change to accept Uint8ArrayLists ([#280](https://github.com/libp2p/js-libp2p-interfaces/issues/280)) ([03d763c](https://github.com/libp2p/js-libp2p-interfaces/commit/03d763c1a6b168bba001783a1fb59af3f7d4e205)) + +## [@libp2p/interface-connection-encrypter-compliance-tests-v2.0.0](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-connection-encrypter-compliance-tests-v1.0.2...@libp2p/interface-connection-encrypter-compliance-tests-v2.0.0) (2022-08-07) + + +### ⚠ BREAKING CHANGES + +* change connection encryption interface to uint8arraylist (#278) + +### Features + +* change connection encryption interface to uint8arraylist ([#278](https://github.com/libp2p/js-libp2p-interfaces/issues/278)) ([1fa580c](https://github.com/libp2p/js-libp2p-interfaces/commit/1fa580c5a45325dc9384738e9a78a238eabb81c3)) + + +### Trivial Changes + +* update project config ([#271](https://github.com/libp2p/js-libp2p-interfaces/issues/271)) ([59c0bf5](https://github.com/libp2p/js-libp2p-interfaces/commit/59c0bf5e0b05496fca2e4902632b61bb41fad9e9)) + + +### Dependencies + +* update sibling dependencies ([f859920](https://github.com/libp2p/js-libp2p-interfaces/commit/f859920423587ae797ac90ccaa3af8bdf60ae549)) +* update sibling dependencies ([93a89b1](https://github.com/libp2p/js-libp2p-interfaces/commit/93a89b1ca6d35fb5f26963ae7bb10026f3f5d45d)) + +## [@libp2p/interface-connection-encrypter-compliance-tests-v1.0.2](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-connection-encrypter-compliance-tests-v1.0.1...@libp2p/interface-connection-encrypter-compliance-tests-v1.0.2) (2022-06-27) + + +### Trivial Changes + +* update deps ([#262](https://github.com/libp2p/js-libp2p-interfaces/issues/262)) ([51edf7d](https://github.com/libp2p/js-libp2p-interfaces/commit/51edf7d9b3765a6f75c915b1483ea345d0133a41)) + +## [@libp2p/interface-connection-encrypter-compliance-tests-v1.0.1](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-connection-encrypter-compliance-tests-v1.0.0...@libp2p/interface-connection-encrypter-compliance-tests-v1.0.1) (2022-06-16) + + +### Trivial Changes + +* update deps ([545264f](https://github.com/libp2p/js-libp2p-interfaces/commit/545264f87a58394d2a7da77e93f3a596e889238f)) diff --git a/packages/interface-connection-encrypter-compliance-tests/LICENSE b/packages/interface-connection-encrypter-compliance-tests/LICENSE new file mode 100644 index 0000000000..20ce483c86 --- /dev/null +++ b/packages/interface-connection-encrypter-compliance-tests/LICENSE @@ -0,0 +1,4 @@ +This project is dual licensed under MIT and Apache-2.0. + +MIT: https://www.opensource.org/licenses/mit +Apache-2.0: https://www.apache.org/licenses/license-2.0 diff --git a/packages/interface-connection-encrypter-compliance-tests/LICENSE-APACHE b/packages/interface-connection-encrypter-compliance-tests/LICENSE-APACHE new file mode 100644 index 0000000000..14478a3b60 --- /dev/null +++ b/packages/interface-connection-encrypter-compliance-tests/LICENSE-APACHE @@ -0,0 +1,5 @@ +Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. diff --git a/packages/interface-connection-encrypter-compliance-tests/LICENSE-MIT b/packages/interface-connection-encrypter-compliance-tests/LICENSE-MIT new file mode 100644 index 0000000000..72dc60d84b --- /dev/null +++ b/packages/interface-connection-encrypter-compliance-tests/LICENSE-MIT @@ -0,0 +1,19 @@ +The MIT License (MIT) + +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/packages/interface-connection-encrypter-compliance-tests/README.md b/packages/interface-connection-encrypter-compliance-tests/README.md new file mode 100644 index 0000000000..85eba2b556 --- /dev/null +++ b/packages/interface-connection-encrypter-compliance-tests/README.md @@ -0,0 +1,54 @@ +# @libp2p/interface-connection-encrypter-compliance-tests + +[![libp2p.io](https://img.shields.io/badge/project-libp2p-yellow.svg?style=flat-square)](http://libp2p.io/) +[![Discuss](https://img.shields.io/discourse/https/discuss.libp2p.io/posts.svg?style=flat-square)](https://discuss.libp2p.io) +[![codecov](https://img.shields.io/codecov/c/github/libp2p/js-libp2p.svg?style=flat-square)](https://codecov.io/gh/libp2p/js-libp2p) +[![CI](https://img.shields.io/github/actions/workflow/status/libp2p/js-libp2p/js-test-and-release.yml?branch=master\&style=flat-square)](https://github.com/libp2p/js-libp2p/actions/workflows/js-test-and-release.yml?query=branch%3Amaster) + +> Compliance tests for implementations of the libp2p Connection Encrypter interface + +## Table of contents + +- [Install](#install) +- [Usage](#usage) +- [API Docs](#api-docs) +- [License](#license) +- [Contribution](#contribution) + +## Install + +```console +$ npm i @libp2p/interface-connection-encrypter-compliance-tests +``` + +## Usage + +```js +import tests from '@libp2p/interface-connection-encrypter-compliance-tests' +import yourCrypto from './your-encrypter' + +tests({ + setup () { + // Set up your crypto if needed, then return it + return yourCrypto + }, + teardown () { + // Clean up your crypto if needed + } +}) +``` + +## API Docs + +- + +## License + +Licensed under either of + +- Apache 2.0, ([LICENSE-APACHE](LICENSE-APACHE) / ) +- MIT ([LICENSE-MIT](LICENSE-MIT) / ) + +## Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. diff --git a/packages/interface-connection-encrypter-compliance-tests/package.json b/packages/interface-connection-encrypter-compliance-tests/package.json new file mode 100644 index 0000000000..9f19c750c5 --- /dev/null +++ b/packages/interface-connection-encrypter-compliance-tests/package.json @@ -0,0 +1,147 @@ +{ + "name": "@libp2p/interface-connection-encrypter-compliance-tests", + "version": "5.0.1", + "description": "Compliance tests for implementations of the libp2p Connection Encrypter interface", + "license": "Apache-2.0 OR MIT", + "homepage": "https://github.com/libp2p/js-libp2p/tree/master/packages/interface-connection-encrypter-compliance-tests#readme", + "repository": { + "type": "git", + "url": "git+https://github.com/libp2p/js-libp2p.git" + }, + "bugs": { + "url": "https://github.com/libp2p/js-libp2p/issues" + }, + "keywords": [ + "interface", + "libp2p" + ], + "type": "module", + "types": "./dist/src/index.d.ts", + "files": [ + "src", + "dist", + "!dist/test", + "!**/*.tsbuildinfo" + ], + "exports": { + ".": { + "types": "./dist/src/index.d.ts", + "import": "./dist/src/index.js" + } + }, + "eslintConfig": { + "extends": "ipfs", + "parserOptions": { + "sourceType": "module" + } + }, + "release": { + "branches": [ + "master" + ], + "plugins": [ + [ + "@semantic-release/commit-analyzer", + { + "preset": "conventionalcommits", + "releaseRules": [ + { + "breaking": true, + "release": "major" + }, + { + "revert": true, + "release": "patch" + }, + { + "type": "feat", + "release": "minor" + }, + { + "type": "fix", + "release": "patch" + }, + { + "type": "docs", + "release": "patch" + }, + { + "type": "test", + "release": "patch" + }, + { + "type": "deps", + "release": "patch" + }, + { + "scope": "no-release", + "release": false + } + ] + } + ], + [ + "@semantic-release/release-notes-generator", + { + "preset": "conventionalcommits", + "presetConfig": { + "types": [ + { + "type": "feat", + "section": "Features" + }, + { + "type": "fix", + "section": "Bug Fixes" + }, + { + "type": "chore", + "section": "Trivial Changes" + }, + { + "type": "docs", + "section": "Documentation" + }, + { + "type": "deps", + "section": "Dependencies" + }, + { + "type": "test", + "section": "Tests" + } + ] + } + } + ], + "@semantic-release/changelog", + "@semantic-release/npm", + "@semantic-release/github", + "@semantic-release/git" + ] + }, + "scripts": { + "clean": "aegir clean", + "lint": "aegir lint", + "dep-check": "aegir dep-check", + "build": "aegir build", + "release": "aegir release" + }, + "dependencies": { + "@libp2p/interface-compliance-tests": "^3.0.0", + "@libp2p/interface-connection": "^5.0.0", + "@libp2p/interface-connection-encrypter": "^4.0.0", + "@libp2p/interface-peer-id": "^2.0.0", + "@libp2p/peer-id-factory": "^2.0.0", + "@multiformats/multiaddr": "^12.0.0", + "aegir": "^39.0.5", + "it-all": "^3.0.1", + "it-pair": "^2.0.2", + "it-pipe": "^3.0.1", + "it-stream-types": "^2.0.1", + "uint8arrays": "^4.0.2" + }, + "typedoc": { + "entryPoint": "./src/index.ts" + } +} diff --git a/packages/interface-connection-encrypter-compliance-tests/src/index.ts b/packages/interface-connection-encrypter-compliance-tests/src/index.ts new file mode 100644 index 0000000000..c622f67add --- /dev/null +++ b/packages/interface-connection-encrypter-compliance-tests/src/index.ts @@ -0,0 +1,97 @@ +import peers from '@libp2p/interface-compliance-tests/peers' +import { UnexpectedPeerError } from '@libp2p/interface-connection-encrypter/errors' +import * as PeerIdFactory from '@libp2p/peer-id-factory' +import { expect } from 'aegir/chai' +import all from 'it-all' +import { pipe } from 'it-pipe' +import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' +import { createMaConnPair } from './utils/index.js' +import type { TestSetup } from '@libp2p/interface-compliance-tests' +import type { ConnectionEncrypter } from '@libp2p/interface-connection-encrypter' +import type { PeerId } from '@libp2p/interface-peer-id' + +export default (common: TestSetup): void => { + describe('interface-connection-encrypter compliance tests', () => { + let crypto: ConnectionEncrypter + let localPeer: PeerId + let remotePeer: PeerId + let mitmPeer: PeerId + + before(async () => { + [ + crypto, + localPeer, + remotePeer, + mitmPeer + ] = await Promise.all([ + common.setup(), + PeerIdFactory.createFromJSON(peers[0]), + PeerIdFactory.createFromJSON(peers[1]), + PeerIdFactory.createFromJSON(peers[2]) + ]) + }) + + after(async () => { + await common.teardown() + }) + + it('has a protocol string', () => { + expect(crypto.protocol).to.exist() + expect(crypto.protocol).to.be.a('string') + }) + + it('it wraps the provided duplex connection', async () => { + const [localConn, remoteConn] = createMaConnPair() + + const [ + inboundResult, + outboundResult + ] = await Promise.all([ + crypto.secureInbound(remotePeer, localConn), + crypto.secureOutbound(localPeer, remoteConn, remotePeer) + ]) + + // Echo server + void pipe(inboundResult.conn, inboundResult.conn) + + // Send some data and collect the result + const input = uint8ArrayFromString('data to encrypt') + const result = await pipe( + [input], + outboundResult.conn, + async (source) => all(source) + ) + + expect(result).to.eql([input]) + }) + + it('should return the remote peer id', async () => { + const [localConn, remoteConn] = createMaConnPair() + + const [ + inboundResult, + outboundResult + ] = await Promise.all([ + crypto.secureInbound(remotePeer, localConn), + crypto.secureOutbound(localPeer, remoteConn, remotePeer) + ]) + + // Inbound should return the initiator (local) peer + expect(inboundResult.remotePeer.toBytes()).to.equalBytes(localPeer.toBytes()) + // Outbound should return the receiver (remote) peer + expect(outboundResult.remotePeer.toBytes()).to.equalBytes(remotePeer.toBytes()) + }) + + it('inbound connections should verify peer integrity if known', async () => { + const [localConn, remoteConn] = createMaConnPair() + + await Promise.all([ + crypto.secureInbound(remotePeer, localConn, mitmPeer), + crypto.secureOutbound(localPeer, remoteConn, remotePeer) + ]).then(() => expect.fail(), (err) => { + expect(err).to.exist() + expect(err).to.have.property('code', UnexpectedPeerError.code) + }) + }) + }) +} diff --git a/packages/interface-connection-encrypter-compliance-tests/src/utils/index.ts b/packages/interface-connection-encrypter-compliance-tests/src/utils/index.ts new file mode 100644 index 0000000000..4c50901daf --- /dev/null +++ b/packages/interface-connection-encrypter-compliance-tests/src/utils/index.ts @@ -0,0 +1,23 @@ +import { multiaddr } from '@multiformats/multiaddr' +import { duplexPair } from 'it-pair/duplex' +import type { MultiaddrConnection } from '@libp2p/interface-connection' +import type { Duplex, Source } from 'it-stream-types' + +export function createMaConnPair (): [MultiaddrConnection, MultiaddrConnection] { + const [local, remote] = duplexPair() + + function duplexToMaConn (duplex: Duplex, Source, Promise>): MultiaddrConnection { + const output: MultiaddrConnection = { + ...duplex, + close: async () => {}, + remoteAddr: multiaddr('/ip4/127.0.0.1/tcp/4001'), + timeline: { + open: Date.now() + } + } + + return output + } + + return [duplexToMaConn(local), duplexToMaConn(remote)] +} diff --git a/packages/interface-connection-encrypter-compliance-tests/tsconfig.json b/packages/interface-connection-encrypter-compliance-tests/tsconfig.json new file mode 100644 index 0000000000..77fd5244fb --- /dev/null +++ b/packages/interface-connection-encrypter-compliance-tests/tsconfig.json @@ -0,0 +1,24 @@ +{ + "extends": "aegir/src/config/tsconfig.aegir.json", + "compilerOptions": { + "outDir": "dist" + }, + "include": [ + "src", + "test" + ], + "references": [ + { + "path": "../interface-compliance-tests" + }, + { + "path": "../interface-connection" + }, + { + "path": "../interface-connection-encrypter" + }, + { + "path": "../interface-peer-id" + } + ] +} diff --git a/packages/interface-connection-encrypter/CHANGELOG.md b/packages/interface-connection-encrypter/CHANGELOG.md new file mode 100644 index 0000000000..f6826ffa1a --- /dev/null +++ b/packages/interface-connection-encrypter/CHANGELOG.md @@ -0,0 +1,137 @@ +## [@libp2p/interface-connection-encrypter-v4.0.1](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-connection-encrypter-v4.0.0...@libp2p/interface-connection-encrypter-v4.0.1) (2023-05-04) + + +### Dependencies + +* bump aegir from 38.1.8 to 39.0.5 ([#393](https://github.com/libp2p/js-libp2p-interfaces/issues/393)) ([31f3797](https://github.com/libp2p/js-libp2p-interfaces/commit/31f3797b24f7c23f3f16e9db3a230bd5f7cd5175)) + +## [@libp2p/interface-connection-encrypter-v4.0.0](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-connection-encrypter-v3.0.6...@libp2p/interface-connection-encrypter-v4.0.0) (2023-04-18) + + +### ⚠ BREAKING CHANGES + +* bump it-stream-types from 1.0.5 to 2.0.1 (#362) + +### Dependencies + +* bump it-stream-types from 1.0.5 to 2.0.1 ([#362](https://github.com/libp2p/js-libp2p-interfaces/issues/362)) ([cdc7747](https://github.com/libp2p/js-libp2p-interfaces/commit/cdc774792beead63e0ded96bd6c23de0335a49e3)) + +## [@libp2p/interface-connection-encrypter-v3.0.6](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-connection-encrypter-v3.0.5...@libp2p/interface-connection-encrypter-v3.0.6) (2023-01-18) + + +### Trivial Changes + +* remove lerna ([#330](https://github.com/libp2p/js-libp2p-interfaces/issues/330)) ([6678592](https://github.com/libp2p/js-libp2p-interfaces/commit/6678592dd0cf601a2671852f9d2a0aff5dee2b18)) + + +### Dependencies + +* bump aegir from 37.12.1 to 38.1.0 ([#335](https://github.com/libp2p/js-libp2p-interfaces/issues/335)) ([7368a36](https://github.com/libp2p/js-libp2p-interfaces/commit/7368a363423a08e8fa247dcb76ea13e4cf030d65)) + +## [@libp2p/interface-connection-encrypter-v3.0.5](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-connection-encrypter-v3.0.4...@libp2p/interface-connection-encrypter-v3.0.5) (2023-01-06) + + +### Dependencies + +* update sibling dependencies ([b50e621](https://github.com/libp2p/js-libp2p-interfaces/commit/b50e621d31a8b32affc3fadb9f97c4883d577f93)) + +## [@libp2p/interface-connection-encrypter-v3.0.4](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-connection-encrypter-v3.0.3...@libp2p/interface-connection-encrypter-v3.0.4) (2022-12-16) + + +### Documentation + +* update project config ([#323](https://github.com/libp2p/js-libp2p-interfaces/issues/323)) ([0fc6a08](https://github.com/libp2p/js-libp2p-interfaces/commit/0fc6a08e9cdcefe361fe325281a3a2a03759ff59)) + +## [@libp2p/interface-connection-encrypter-v3.0.3](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-connection-encrypter-v3.0.2...@libp2p/interface-connection-encrypter-v3.0.3) (2022-12-14) + + +### Bug Fixes + +* generate docs for all packages ([#321](https://github.com/libp2p/js-libp2p-interfaces/issues/321)) ([b6f8b32](https://github.com/libp2p/js-libp2p-interfaces/commit/b6f8b32a920c15a28fe021e6050e31aaae89d518)) + +## [@libp2p/interface-connection-encrypter-v3.0.2](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-connection-encrypter-v3.0.1...@libp2p/interface-connection-encrypter-v3.0.2) (2022-11-05) + + +### Bug Fixes + +* update project config ([#311](https://github.com/libp2p/js-libp2p-interfaces/issues/311)) ([27dd0ce](https://github.com/libp2p/js-libp2p-interfaces/commit/27dd0ce3c249892ac69cbb24ddaf0b9f32385e37)) + +## [@libp2p/interface-connection-encrypter-v3.0.1](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-connection-encrypter-v3.0.0...@libp2p/interface-connection-encrypter-v3.0.1) (2022-10-04) + + +### Bug Fixes + +* add default extension type ([#296](https://github.com/libp2p/js-libp2p-interfaces/issues/296)) ([ab658bc](https://github.com/libp2p/js-libp2p-interfaces/commit/ab658bc1b11e411e685388acb9da8f65d62ef919)) + +## [@libp2p/interface-connection-encrypter-v3.0.0](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-connection-encrypter-v2.0.2...@libp2p/interface-connection-encrypter-v3.0.0) (2022-10-04) + + +### ⚠ BREAKING CHANGES + +* Add remoteExtensions to connection-encrypter (#293) + +### Features + +* Add remoteExtensions to connection-encrypter ([#293](https://github.com/libp2p/js-libp2p-interfaces/issues/293)) ([501c684](https://github.com/libp2p/js-libp2p-interfaces/commit/501c684d792cd910de7cb9bfbda349db257ee2ca)) + +## [@libp2p/interface-connection-encrypter-v2.0.2](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-connection-encrypter-v2.0.1...@libp2p/interface-connection-encrypter-v2.0.2) (2022-09-30) + + +### Bug Fixes + +* make outbound peer id optional ([#294](https://github.com/libp2p/js-libp2p-interfaces/issues/294)) ([6724ffe](https://github.com/libp2p/js-libp2p-interfaces/commit/6724ffef0d170dba2d4c9973b46334fc421f8ea8)) + +## [@libp2p/interface-connection-encrypter-v2.0.1](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-connection-encrypter-v2.0.0...@libp2p/interface-connection-encrypter-v2.0.1) (2022-08-10) + + +### Bug Fixes + +* revert connection encryption change to accept Uint8ArrayLists ([#280](https://github.com/libp2p/js-libp2p-interfaces/issues/280)) ([03d763c](https://github.com/libp2p/js-libp2p-interfaces/commit/03d763c1a6b168bba001783a1fb59af3f7d4e205)) + +## [@libp2p/interface-connection-encrypter-v2.0.0](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-connection-encrypter-v1.0.3...@libp2p/interface-connection-encrypter-v2.0.0) (2022-08-07) + + +### ⚠ BREAKING CHANGES + +* change connection encryption interface to uint8arraylist (#278) + +### Features + +* change connection encryption interface to uint8arraylist ([#278](https://github.com/libp2p/js-libp2p-interfaces/issues/278)) ([1fa580c](https://github.com/libp2p/js-libp2p-interfaces/commit/1fa580c5a45325dc9384738e9a78a238eabb81c3)) + + +### Trivial Changes + +* update project config ([#271](https://github.com/libp2p/js-libp2p-interfaces/issues/271)) ([59c0bf5](https://github.com/libp2p/js-libp2p-interfaces/commit/59c0bf5e0b05496fca2e4902632b61bb41fad9e9)) + +## [@libp2p/interface-connection-encrypter-v1.0.3](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-connection-encrypter-v1.0.2...@libp2p/interface-connection-encrypter-v1.0.3) (2022-06-27) + + +### Trivial Changes + +* update deps ([#262](https://github.com/libp2p/js-libp2p-interfaces/issues/262)) ([51edf7d](https://github.com/libp2p/js-libp2p-interfaces/commit/51edf7d9b3765a6f75c915b1483ea345d0133a41)) + +## [@libp2p/interface-connection-encrypter-v1.0.2](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-connection-encrypter-v1.0.1...@libp2p/interface-connection-encrypter-v1.0.2) (2022-06-14) + + +### Trivial Changes + +* update aegir ([#234](https://github.com/libp2p/js-libp2p-interfaces/issues/234)) ([3e03895](https://github.com/libp2p/js-libp2p-interfaces/commit/3e038959ecab6cfa3585df9ee179c0af7a61eda5)) + +## [@libp2p/interface-connection-encrypter-v1.0.1](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-connection-encrypter-v1.0.0...@libp2p/interface-connection-encrypter-v1.0.1) (2022-06-14) + + +### Trivial Changes + +* update readmes ([#233](https://github.com/libp2p/js-libp2p-interfaces/issues/233)) ([ee7da38](https://github.com/libp2p/js-libp2p-interfaces/commit/ee7da38dccc08160d26c8436df8739ce7e0b340e)) + +## @libp2p/interface-connection-encrypter-v1.0.0 (2022-06-14) + + +### ⚠ BREAKING CHANGES + +* most modules have been split out of the `@libp2p/interfaces` and `@libp2p/interface-compliance-tests` packages + +### Trivial Changes + +* break modules apart ([#232](https://github.com/libp2p/js-libp2p-interfaces/issues/232)) ([385614e](https://github.com/libp2p/js-libp2p-interfaces/commit/385614e772329052ab17415c8bd421f65b01a61b)), closes [#226](https://github.com/libp2p/js-libp2p-interfaces/issues/226) diff --git a/packages/interface-connection-encrypter/LICENSE b/packages/interface-connection-encrypter/LICENSE new file mode 100644 index 0000000000..20ce483c86 --- /dev/null +++ b/packages/interface-connection-encrypter/LICENSE @@ -0,0 +1,4 @@ +This project is dual licensed under MIT and Apache-2.0. + +MIT: https://www.opensource.org/licenses/mit +Apache-2.0: https://www.apache.org/licenses/license-2.0 diff --git a/packages/interface-connection-encrypter/LICENSE-APACHE b/packages/interface-connection-encrypter/LICENSE-APACHE new file mode 100644 index 0000000000..14478a3b60 --- /dev/null +++ b/packages/interface-connection-encrypter/LICENSE-APACHE @@ -0,0 +1,5 @@ +Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. diff --git a/packages/interface-connection-encrypter/LICENSE-MIT b/packages/interface-connection-encrypter/LICENSE-MIT new file mode 100644 index 0000000000..72dc60d84b --- /dev/null +++ b/packages/interface-connection-encrypter/LICENSE-MIT @@ -0,0 +1,19 @@ +The MIT License (MIT) + +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/packages/interface-connection-encrypter/README.md b/packages/interface-connection-encrypter/README.md new file mode 100644 index 0000000000..685137bc20 --- /dev/null +++ b/packages/interface-connection-encrypter/README.md @@ -0,0 +1,114 @@ +# @libp2p/interface-connection-encrypter + +[![libp2p.io](https://img.shields.io/badge/project-libp2p-yellow.svg?style=flat-square)](http://libp2p.io/) +[![Discuss](https://img.shields.io/discourse/https/discuss.libp2p.io/posts.svg?style=flat-square)](https://discuss.libp2p.io) +[![codecov](https://img.shields.io/codecov/c/github/libp2p/js-libp2p.svg?style=flat-square)](https://codecov.io/gh/libp2p/js-libp2p) +[![CI](https://img.shields.io/github/actions/workflow/status/libp2p/js-libp2p/js-test-and-release.yml?branch=master\&style=flat-square)](https://github.com/libp2p/js-libp2p/actions/workflows/js-test-and-release.yml?query=branch%3Amaster) + +> Connection Encrypter interface for libp2p + +## Table of contents + +- [Install](#install) +- [API](#api) + - [Secure Inbound](#secure-inbound) + - [Secure Outbound](#secure-outbound) +- [Crypto Errors](#crypto-errors) + - [Error Types](#error-types) +- [API Docs](#api-docs) +- [License](#license) +- [Contribution](#contribution) + +## Install + +```console +$ npm i @libp2p/interface-connection-encrypter +``` + +**Modules that implement the interface** + +- [@chainSafe/js-libp2p-noise](https://github.com/ChainSafe/js-libp2p-noise) +- [js-libp2p-secio](https://github.com/NodeFactoryIo/js-libp2p-secio) + +## API + +- `Crypto` + - `protocol`: The protocol id of the crypto module. + - `secureInbound`: Secures inbound connections. + - `secureOutbound`: Secures outbound connections. + +### Secure Inbound + +- `const { conn, remotePeer } = await crypto.secureInbound(localPeer, duplex, [remotePeer])` + +Secures an inbound [streaming iterable duplex][iterable-duplex] connection. It returns an encrypted [streaming iterable duplex][iterable-duplex], as well as the [PeerId][peer-id] of the remote peer. + +**Parameters** + +- `localPeer` is the [PeerId][peer-id] of the receiving peer. +- `duplex` is the [streaming iterable duplex][iterable-duplex] that will be encryption. +- `remotePeer` is the optional [PeerId][peer-id] of the initiating peer, if known. This may only exist during transport upgrades. + +**Return Value** + +- `` + - `conn`: An encrypted [streaming iterable duplex][iterable-duplex]. + - `remotePeer`: The [PeerId][peer-id] of the remote peer. + +### Secure Outbound + +- `const { conn, remotePeer } = await crypto.secureOutbound(localPeer, duplex, remotePeer)` + +Secures an outbound [streaming iterable duplex][iterable-duplex] connection. It returns an encrypted [streaming iterable duplex][iterable-duplex], as well as the [PeerId][peer-id] of the remote peer. + +**Parameters** + +- `localPeer` is the [PeerId][peer-id] of the receiving peer. +- `duplex` is the [streaming iterable duplex][iterable-duplex] that will be encrypted. +- `remotePeer` is the [PeerId][peer-id] of the remote peer. If provided, implementations **should** use this to validate the integrity of the remote peer. + +**Return Value** + +- `` + - `conn`: An encrypted [streaming iterable duplex][iterable-duplex]. + - `remotePeer`: The [PeerId][peer-id] of the remote peer. This **should** match the `remotePeer` parameter, and implementations should enforce this. + +## Crypto Errors + +Common crypto errors come with the interface, and can be imported directly. All Errors take an optional message. + +```js +const { + InvalidCryptoExchangeError, + InvalidCryptoTransmissionError, + UnexpectedPeerError +} = require('libp2p-interfaces/src/crypto/errors') + +const error = new UnexpectedPeerError('a custom error message') +console.log(error.code === UnexpectedPeerError.code) // true +``` + +### Error Types + +- `InvalidCryptoExchangeError` - Should be thrown when a peer provides data that is insufficient to finish the crypto exchange. +- `InvalidCryptoTransmissionError` - Should be thrown when an error occurs during encryption/decryption. +- `UnexpectedPeerError` - Should be thrown when the expected peer id does not match the peer id determined via the crypto exchange. + +## API Docs + +- + +## License + +Licensed under either of + +- Apache 2.0, ([LICENSE-APACHE](LICENSE-APACHE) / ) +- MIT ([LICENSE-MIT](LICENSE-MIT) / ) + +## Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. + +[peer-id]: https://github.com/libp2p/js-peer-id + +[iterable-duplex]: https://gist.github.com/alanshaw/591dc7dd54e4f99338a347ef568d6ee9#duplex-it diff --git a/packages/interface-connection-encrypter/package.json b/packages/interface-connection-encrypter/package.json new file mode 100644 index 0000000000..24611a551d --- /dev/null +++ b/packages/interface-connection-encrypter/package.json @@ -0,0 +1,160 @@ +{ + "name": "@libp2p/interface-connection-encrypter", + "version": "4.0.1", + "description": "Connection Encrypter interface for libp2p", + "license": "Apache-2.0 OR MIT", + "homepage": "https://github.com/libp2p/js-libp2p/tree/master/packages/interface-connection-encrypter#readme", + "repository": { + "type": "git", + "url": "git+https://github.com/libp2p/js-libp2p.git" + }, + "bugs": { + "url": "https://github.com/libp2p/js-libp2p/issues" + }, + "keywords": [ + "interface", + "libp2p" + ], + "type": "module", + "types": "./dist/src/index.d.ts", + "typesVersions": { + "*": { + "*": [ + "*", + "dist/*", + "dist/src/*", + "dist/src/*/index" + ], + "src/*": [ + "*", + "dist/*", + "dist/src/*", + "dist/src/*/index" + ] + } + }, + "files": [ + "src", + "dist", + "!dist/test", + "!**/*.tsbuildinfo" + ], + "exports": { + ".": { + "types": "./dist/src/index.d.ts", + "import": "./dist/src/index.js" + }, + "./errors": { + "types": "./dist/src/errors.d.ts", + "import": "./dist/src/errors.js" + } + }, + "eslintConfig": { + "extends": "ipfs", + "parserOptions": { + "sourceType": "module" + } + }, + "release": { + "branches": [ + "master" + ], + "plugins": [ + [ + "@semantic-release/commit-analyzer", + { + "preset": "conventionalcommits", + "releaseRules": [ + { + "breaking": true, + "release": "major" + }, + { + "revert": true, + "release": "patch" + }, + { + "type": "feat", + "release": "minor" + }, + { + "type": "fix", + "release": "patch" + }, + { + "type": "docs", + "release": "patch" + }, + { + "type": "test", + "release": "patch" + }, + { + "type": "deps", + "release": "patch" + }, + { + "scope": "no-release", + "release": false + } + ] + } + ], + [ + "@semantic-release/release-notes-generator", + { + "preset": "conventionalcommits", + "presetConfig": { + "types": [ + { + "type": "feat", + "section": "Features" + }, + { + "type": "fix", + "section": "Bug Fixes" + }, + { + "type": "chore", + "section": "Trivial Changes" + }, + { + "type": "docs", + "section": "Documentation" + }, + { + "type": "deps", + "section": "Dependencies" + }, + { + "type": "test", + "section": "Tests" + } + ] + } + } + ], + "@semantic-release/changelog", + "@semantic-release/npm", + "@semantic-release/github", + "@semantic-release/git" + ] + }, + "scripts": { + "clean": "aegir clean", + "lint": "aegir lint", + "dep-check": "aegir dep-check", + "build": "aegir build", + "release": "aegir release" + }, + "dependencies": { + "@libp2p/interface-peer-id": "^2.0.0", + "it-stream-types": "^2.0.1" + }, + "devDependencies": { + "aegir": "^39.0.5" + }, + "typedoc": { + "entryPoint": "./src/index.ts" + } +} diff --git a/packages/interface-connection-encrypter/src/errors.ts b/packages/interface-connection-encrypter/src/errors.ts new file mode 100644 index 0000000000..e23304aae3 --- /dev/null +++ b/packages/interface-connection-encrypter/src/errors.ts @@ -0,0 +1,33 @@ + +export class UnexpectedPeerError extends Error { + public code: string + + constructor (message = 'Unexpected Peer') { + super(message) + this.code = UnexpectedPeerError.code + } + + static readonly code = 'ERR_UNEXPECTED_PEER' +} + +export class InvalidCryptoExchangeError extends Error { + public code: string + + constructor (message = 'Invalid crypto exchange') { + super(message) + this.code = InvalidCryptoExchangeError.code + } + + static readonly code = 'ERR_INVALID_CRYPTO_EXCHANGE' +} + +export class InvalidCryptoTransmissionError extends Error { + public code: string + + constructor (message = 'Invalid crypto transmission') { + super(message) + this.code = InvalidCryptoTransmissionError.code + } + + static readonly code = 'ERR_INVALID_CRYPTO_TRANSMISSION' +} diff --git a/packages/interface-connection-encrypter/src/index.ts b/packages/interface-connection-encrypter/src/index.ts new file mode 100644 index 0000000000..d8bcddd2e8 --- /dev/null +++ b/packages/interface-connection-encrypter/src/index.ts @@ -0,0 +1,30 @@ +import type { PeerId } from '@libp2p/interface-peer-id' +import type { Duplex, Source } from 'it-stream-types' + +/** + * A libp2p connection encrypter module must be compliant to this interface + * to ensure all exchanged data between two peers is encrypted. + */ +export interface ConnectionEncrypter { + protocol: string + + /** + * Encrypt outgoing data to the remote party. If the remote PeerId is known, + * pass it for extra verification, otherwise it will be determined during + * the handshake. + */ + secureOutbound: (localPeer: PeerId, connection: Duplex, Source, Promise>, remotePeer?: PeerId) => Promise> + + /** + * Decrypt incoming data. If the remote PeerId is known, + * pass it for extra verification, otherwise it will be determined during + * the handshake + */ + secureInbound: (localPeer: PeerId, connection: Duplex, Source, Promise>, remotePeer?: PeerId) => Promise> +} + +export interface SecuredConnection { + conn: Duplex, Source, Promise> + remoteExtensions?: Extension + remotePeer: PeerId +} diff --git a/packages/interface-connection-encrypter/tsconfig.json b/packages/interface-connection-encrypter/tsconfig.json new file mode 100644 index 0000000000..d8db0b667f --- /dev/null +++ b/packages/interface-connection-encrypter/tsconfig.json @@ -0,0 +1,14 @@ +{ + "extends": "aegir/src/config/tsconfig.aegir.json", + "compilerOptions": { + "outDir": "dist" + }, + "include": [ + "src" + ], + "references": [ + { + "path": "../interface-peer-id" + } + ] +} diff --git a/packages/interface-connection-gater/CHANGELOG.md b/packages/interface-connection-gater/CHANGELOG.md new file mode 100644 index 0000000000..c50efe4d71 --- /dev/null +++ b/packages/interface-connection-gater/CHANGELOG.md @@ -0,0 +1,64 @@ +## [@libp2p/interface-connection-gater-v3.0.1](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-connection-gater-v3.0.0...@libp2p/interface-connection-gater-v3.0.1) (2023-05-04) + + +### Dependencies + +* bump aegir from 38.1.8 to 39.0.5 ([#393](https://github.com/libp2p/js-libp2p-interfaces/issues/393)) ([31f3797](https://github.com/libp2p/js-libp2p-interfaces/commit/31f3797b24f7c23f3f16e9db3a230bd5f7cd5175)) + +## [@libp2p/interface-connection-gater-v3.0.0](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-connection-gater-v2.0.1...@libp2p/interface-connection-gater-v3.0.0) (2023-04-18) + + +### ⚠ BREAKING CHANGES + +* bump it-stream-types from 1.0.5 to 2.0.1 (#362) + +### Dependencies + +* bump it-stream-types from 1.0.5 to 2.0.1 ([#362](https://github.com/libp2p/js-libp2p-interfaces/issues/362)) ([cdc7747](https://github.com/libp2p/js-libp2p-interfaces/commit/cdc774792beead63e0ded96bd6c23de0335a49e3)) +* update sibling dependencies ([2f52a28](https://github.com/libp2p/js-libp2p-interfaces/commit/2f52a284b59c0a88b040f86da1f5d3f044727f2c)) + +## [@libp2p/interface-connection-gater-v2.0.1](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-connection-gater-v2.0.0...@libp2p/interface-connection-gater-v2.0.1) (2023-04-14) + + +### Bug Fixes + +* make deny dial multiaddr optional ([#370](https://github.com/libp2p/js-libp2p-interfaces/issues/370)) ([6bf7a7e](https://github.com/libp2p/js-libp2p-interfaces/commit/6bf7a7e9fe2a77a43c5ddf114c26c4978c579d46)) + +## [@libp2p/interface-connection-gater-v2.0.0](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-connection-gater-v1.0.3...@libp2p/interface-connection-gater-v2.0.0) (2023-04-14) + + +### ⚠ BREAKING CHANGES + +* the peer id argument has been removed from the `denyDialMultiaddr` method of the connection gater + +### Bug Fixes + +* remove peer id argument from deny dial multiaddr ([#366](https://github.com/libp2p/js-libp2p-interfaces/issues/366)) ([aa3e000](https://github.com/libp2p/js-libp2p-interfaces/commit/aa3e0008a94d943df961da7756bf4cf6862bd4c1)) + +## [@libp2p/interface-connection-gater-v1.0.3](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-connection-gater-v1.0.2...@libp2p/interface-connection-gater-v1.0.3) (2023-04-11) + + +### Dependencies + +* update sibling dependencies ([b034810](https://github.com/libp2p/js-libp2p-interfaces/commit/b0348102e41dc18166e70063f4708a2b3544f4b6)) + +## [@libp2p/interface-connection-gater-v1.0.2](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-connection-gater-v1.0.1...@libp2p/interface-connection-gater-v1.0.2) (2023-03-17) + + +### Bug Fixes + +* update project settings ([2aa4f95](https://github.com/libp2p/js-libp2p-interfaces/commit/2aa4f9583fb8ff9b53c51ebb6b81f72d69a1748d)) + +## [@libp2p/interface-connection-gater-v1.0.1](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-connection-gater-v1.0.0...@libp2p/interface-connection-gater-v1.0.1) (2023-03-17) + + +### Dependencies + +* update @multiformats/multiaddr to 12.0.0 ([#354](https://github.com/libp2p/js-libp2p-interfaces/issues/354)) ([e0f327b](https://github.com/libp2p/js-libp2p-interfaces/commit/e0f327b5d54e240feabadce21a841629d633ec5e)) + +## @libp2p/interface-connection-gater-v1.0.0 (2023-03-09) + + +### Features + +* split connection gater out into module ([#347](https://github.com/libp2p/js-libp2p-interfaces/issues/347)) ([1824744](https://github.com/libp2p/js-libp2p-interfaces/commit/18247442aa64c809d9e101ccbd0067ce48bdb80f)) diff --git a/packages/interface-connection-gater/LICENSE b/packages/interface-connection-gater/LICENSE new file mode 100644 index 0000000000..20ce483c86 --- /dev/null +++ b/packages/interface-connection-gater/LICENSE @@ -0,0 +1,4 @@ +This project is dual licensed under MIT and Apache-2.0. + +MIT: https://www.opensource.org/licenses/mit +Apache-2.0: https://www.apache.org/licenses/license-2.0 diff --git a/packages/interface-connection-gater/LICENSE-APACHE b/packages/interface-connection-gater/LICENSE-APACHE new file mode 100644 index 0000000000..14478a3b60 --- /dev/null +++ b/packages/interface-connection-gater/LICENSE-APACHE @@ -0,0 +1,5 @@ +Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. diff --git a/packages/interface-connection-gater/LICENSE-MIT b/packages/interface-connection-gater/LICENSE-MIT new file mode 100644 index 0000000000..72dc60d84b --- /dev/null +++ b/packages/interface-connection-gater/LICENSE-MIT @@ -0,0 +1,19 @@ +The MIT License (MIT) + +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/packages/interface-connection-gater/README.md b/packages/interface-connection-gater/README.md new file mode 100644 index 0000000000..a5f03d34ae --- /dev/null +++ b/packages/interface-connection-gater/README.md @@ -0,0 +1,36 @@ +# @libp2p/interface-connection-gater + +[![libp2p.io](https://img.shields.io/badge/project-libp2p-yellow.svg?style=flat-square)](http://libp2p.io/) +[![Discuss](https://img.shields.io/discourse/https/discuss.libp2p.io/posts.svg?style=flat-square)](https://discuss.libp2p.io) +[![codecov](https://img.shields.io/codecov/c/github/libp2p/js-libp2p.svg?style=flat-square)](https://codecov.io/gh/libp2p/js-libp2p) +[![CI](https://img.shields.io/github/actions/workflow/status/libp2p/js-libp2p/js-test-and-release.yml?branch=master\&style=flat-square)](https://github.com/libp2p/js-libp2p/actions/workflows/js-test-and-release.yml?query=branch%3Amaster) + +> Connection gater interface for libp2p + +## Table of contents + +- [Install](#install) +- [API Docs](#api-docs) +- [License](#license) +- [Contribution](#contribution) + +## Install + +```console +$ npm i @libp2p/interface-connection-gater +``` + +## API Docs + +- + +## License + +Licensed under either of + +- Apache 2.0, ([LICENSE-APACHE](LICENSE-APACHE) / ) +- MIT ([LICENSE-MIT](LICENSE-MIT) / ) + +## Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. diff --git a/packages/interface-connection-gater/package.json b/packages/interface-connection-gater/package.json new file mode 100644 index 0000000000..3d3f6b706b --- /dev/null +++ b/packages/interface-connection-gater/package.json @@ -0,0 +1,141 @@ +{ + "name": "@libp2p/interface-connection-gater", + "version": "3.0.1", + "description": "Connection gater interface for libp2p", + "license": "Apache-2.0 OR MIT", + "homepage": "https://github.com/libp2p/js-libp2p/tree/master/packages/interface-connection-gater#readme", + "repository": { + "type": "git", + "url": "git+https://github.com/libp2p/js-libp2p.git" + }, + "bugs": { + "url": "https://github.com/libp2p/js-libp2p/issues" + }, + "keywords": [ + "interface", + "libp2p" + ], + "type": "module", + "types": "./dist/src/index.d.ts", + "files": [ + "src", + "dist", + "!dist/test", + "!**/*.tsbuildinfo" + ], + "exports": { + ".": { + "types": "./dist/src/index.d.ts", + "import": "./dist/src/index.js" + } + }, + "eslintConfig": { + "extends": "ipfs", + "parserOptions": { + "sourceType": "module" + } + }, + "release": { + "branches": [ + "master" + ], + "plugins": [ + [ + "@semantic-release/commit-analyzer", + { + "preset": "conventionalcommits", + "releaseRules": [ + { + "breaking": true, + "release": "major" + }, + { + "revert": true, + "release": "patch" + }, + { + "type": "feat", + "release": "minor" + }, + { + "type": "fix", + "release": "patch" + }, + { + "type": "docs", + "release": "patch" + }, + { + "type": "test", + "release": "patch" + }, + { + "type": "deps", + "release": "patch" + }, + { + "scope": "no-release", + "release": false + } + ] + } + ], + [ + "@semantic-release/release-notes-generator", + { + "preset": "conventionalcommits", + "presetConfig": { + "types": [ + { + "type": "feat", + "section": "Features" + }, + { + "type": "fix", + "section": "Bug Fixes" + }, + { + "type": "chore", + "section": "Trivial Changes" + }, + { + "type": "docs", + "section": "Documentation" + }, + { + "type": "deps", + "section": "Dependencies" + }, + { + "type": "test", + "section": "Tests" + } + ] + } + } + ], + "@semantic-release/changelog", + "@semantic-release/npm", + "@semantic-release/github", + "@semantic-release/git" + ] + }, + "scripts": { + "clean": "aegir clean", + "lint": "aegir lint", + "dep-check": "aegir dep-check", + "build": "aegir build", + "release": "aegir release" + }, + "dependencies": { + "@libp2p/interface-connection": "^5.0.0", + "@libp2p/interface-peer-id": "^2.0.0", + "@multiformats/multiaddr": "^12.0.0" + }, + "devDependencies": { + "aegir": "^39.0.5" + }, + "typedoc": { + "entryPoint": "./src/index.ts" + } +} diff --git a/packages/interface-connection-gater/src/index.ts b/packages/interface-connection-gater/src/index.ts new file mode 100644 index 0000000000..923449ca5d --- /dev/null +++ b/packages/interface-connection-gater/src/index.ts @@ -0,0 +1,128 @@ +import type { MultiaddrConnection } from '@libp2p/interface-connection' +import type { PeerId } from '@libp2p/interface-peer-id' +import type { Multiaddr } from '@multiformats/multiaddr' + +export interface ConnectionGater { + /** + * denyDialPeer tests whether we're permitted to Dial the + * specified peer. + * + * This is called by the dialer.connectToPeer implementation before + * dialling a peer. + * + * Return true to prevent dialing the passed peer. + */ + denyDialPeer?: (peerId: PeerId) => Promise + + /** + * denyDialMultiaddr tests whether we're permitted to dial the specified + * multiaddr. + * + * This is called by the connection manager - if the peer id of the remote + * node is known it will be present in the multiaddr. + * + * Return true to prevent dialing the passed peer on the passed multiaddr. + */ + denyDialMultiaddr?: (multiaddr: Multiaddr) => Promise + + /** + * denyInboundConnection tests whether an incipient inbound connection is allowed. + * + * This is called by the upgrader, or by the transport directly (e.g. QUIC, + * Bluetooth), straight after it has accepted a connection from its socket. + * + * Return true to deny the incoming passed connection. + */ + denyInboundConnection?: (maConn: MultiaddrConnection) => Promise + + /** + * denyOutboundConnection tests whether an incipient outbound connection is allowed. + * + * This is called by the upgrader, or by the transport directly (e.g. QUIC, + * Bluetooth), straight after it has created a connection with its socket. + * + * Return true to deny the incoming passed connection. + */ + denyOutboundConnection?: (peerId: PeerId, maConn: MultiaddrConnection) => Promise + + /** + * denyInboundEncryptedConnection tests whether a given connection, now encrypted, + * is allowed. + * + * This is called by the upgrader, after it has performed the security + * handshake, and before it negotiates the muxer, or by the directly by the + * transport, at the exact same checkpoint. + * + * Return true to deny the passed secured connection. + */ + denyInboundEncryptedConnection?: (peerId: PeerId, maConn: MultiaddrConnection) => Promise + + /** + * denyOutboundEncryptedConnection tests whether a given connection, now encrypted, + * is allowed. + * + * This is called by the upgrader, after it has performed the security + * handshake, and before it negotiates the muxer, or by the directly by the + * transport, at the exact same checkpoint. + * + * Return true to deny the passed secured connection. + */ + denyOutboundEncryptedConnection?: (peerId: PeerId, maConn: MultiaddrConnection) => Promise + + /** + * denyInboundUpgradedConnection tests whether a fully capable connection is allowed. + * + * This is called after encryption has been negotiated and the connection has been + * multiplexed, if a multiplexer is configured. + * + * Return true to deny the passed upgraded connection. + */ + denyInboundUpgradedConnection?: (peerId: PeerId, maConn: MultiaddrConnection) => Promise + + /** + * denyOutboundUpgradedConnection tests whether a fully capable connection is allowed. + * + * This is called after encryption has been negotiated and the connection has been + * multiplexed, if a multiplexer is configured. + * + * Return true to deny the passed upgraded connection. + */ + denyOutboundUpgradedConnection?: (peerId: PeerId, maConn: MultiaddrConnection) => Promise + + /** + * denyInboundRelayReservation tests whether a remote peer is allowed make a + * relay reservation on this node. + * + * Return true to deny the relay reservation. + */ + denyInboundRelayReservation?: (source: PeerId) => Promise + + /** + * denyOutboundRelayedConnection tests whether a remote peer is allowed to open a relayed + * connection to the destination node. + * + * This is invoked on the relay server when a source client with a reservation instructs + * the server to relay a connection to a destination peer. + * + * Return true to deny the relayed connection. + */ + denyOutboundRelayedConnection?: (source: PeerId, destination: PeerId) => Promise + + /** + * denyInboundRelayedConnection tests whether a remote peer is allowed to open a relayed + * connection to this node. + * + * This is invoked on the relay client when a remote relay has received an instruction to + * relay a connection to the client. + * + * Return true to deny the relayed connection. + */ + denyInboundRelayedConnection?: (relay: PeerId, remotePeer: PeerId) => Promise + + /** + * Used by the address book to filter passed addresses. + * + * Return true to allow storing the passed multiaddr for the passed peer. + */ + filterMultiaddrForPeer?: (peer: PeerId, multiaddr: Multiaddr) => Promise +} diff --git a/packages/interface-connection-gater/tsconfig.json b/packages/interface-connection-gater/tsconfig.json new file mode 100644 index 0000000000..16ab3d8b56 --- /dev/null +++ b/packages/interface-connection-gater/tsconfig.json @@ -0,0 +1,17 @@ +{ + "extends": "aegir/src/config/tsconfig.aegir.json", + "compilerOptions": { + "outDir": "dist" + }, + "include": [ + "src" + ], + "references": [ + { + "path": "../interface-connection" + }, + { + "path": "../interface-peer-id" + } + ] +} diff --git a/packages/interface-connection-manager/CHANGELOG.md b/packages/interface-connection-manager/CHANGELOG.md new file mode 100644 index 0000000000..75dd3e8235 --- /dev/null +++ b/packages/interface-connection-manager/CHANGELOG.md @@ -0,0 +1,179 @@ +## [@libp2p/interface-connection-manager-v3.0.1](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-connection-manager-v3.0.0...@libp2p/interface-connection-manager-v3.0.1) (2023-05-04) + + +### Dependencies + +* bump aegir from 38.1.8 to 39.0.5 ([#393](https://github.com/libp2p/js-libp2p-interfaces/issues/393)) ([31f3797](https://github.com/libp2p/js-libp2p-interfaces/commit/31f3797b24f7c23f3f16e9db3a230bd5f7cd5175)) + +## [@libp2p/interface-connection-manager-v3.0.0](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-connection-manager-v2.1.1...@libp2p/interface-connection-manager-v3.0.0) (2023-04-21) + + +### ⚠ BREAKING CHANGES + +* add libp2p events (#373) + +### Features + +* add libp2p events ([#373](https://github.com/libp2p/js-libp2p-interfaces/issues/373)) ([071c718](https://github.com/libp2p/js-libp2p-interfaces/commit/071c718808902858818ca86167b51b242b67a5a5)) + +## [@libp2p/interface-connection-manager-v2.1.1](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-connection-manager-v2.1.0...@libp2p/interface-connection-manager-v2.1.1) (2023-04-18) + + +### Dependencies + +* update sibling dependencies ([2f52a28](https://github.com/libp2p/js-libp2p-interfaces/commit/2f52a284b59c0a88b040f86da1f5d3f044727f2c)) + +## [@libp2p/interface-connection-manager-v2.1.0](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-connection-manager-v2.0.0...@libp2p/interface-connection-manager-v2.1.0) (2023-04-14) + + +### Features + +* expose get connection map method of connection manager ([#372](https://github.com/libp2p/js-libp2p-interfaces/issues/372)) ([fc7245b](https://github.com/libp2p/js-libp2p-interfaces/commit/fc7245b63764562f5ec66a5a0ba334caea80ed66)) +* expose get dial queue method of connection manager ([#371](https://github.com/libp2p/js-libp2p-interfaces/issues/371)) ([0c407aa](https://github.com/libp2p/js-libp2p-interfaces/commit/0c407aa0772c171bf6650e31fb20a3433df40b6b)) + +## [@libp2p/interface-connection-manager-v2.0.0](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-connection-manager-v1.5.0...@libp2p/interface-connection-manager-v2.0.0) (2023-04-14) + + +### ⚠ BREAKING CHANGES + +* the Dialer interface has been removed + +### Bug Fixes + +* remove dialer interface from interface connection manager ([#364](https://github.com/libp2p/js-libp2p-interfaces/issues/364)) ([5fb8a34](https://github.com/libp2p/js-libp2p-interfaces/commit/5fb8a342150efbc8c0ac8b1ae76ec53dc9f60ee9)) + +## [@libp2p/interface-connection-manager-v1.5.0](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-connection-manager-v1.4.1...@libp2p/interface-connection-manager-v1.5.0) (2023-04-11) + + +### Features + +* add peer:prune event to connection manager ([#367](https://github.com/libp2p/js-libp2p-interfaces/issues/367)) ([45680c6](https://github.com/libp2p/js-libp2p-interfaces/commit/45680c614f082e1d02c5258559f3ee3b711e6a87)) + +## [@libp2p/interface-connection-manager-v1.4.1](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-connection-manager-v1.4.0...@libp2p/interface-connection-manager-v1.4.1) (2023-04-11) + + +### Dependencies + +* update sibling dependencies ([b034810](https://github.com/libp2p/js-libp2p-interfaces/commit/b0348102e41dc18166e70063f4708a2b3544f4b6)) + +## [@libp2p/interface-connection-manager-v1.4.0](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-connection-manager-v1.3.8...@libp2p/interface-connection-manager-v1.4.0) (2023-04-05) + + +### Features + +* support batch dialling ([#351](https://github.com/libp2p/js-libp2p-interfaces/issues/351)) ([e46b72b](https://github.com/libp2p/js-libp2p-interfaces/commit/e46b72b1731ff935a1f0d755cbaf6f3159060ed3)) + +## [@libp2p/interface-connection-manager-v1.3.8](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-connection-manager-v1.3.7...@libp2p/interface-connection-manager-v1.3.8) (2023-03-17) + + +### Dependencies + +* update @multiformats/multiaddr to 12.0.0 ([#354](https://github.com/libp2p/js-libp2p-interfaces/issues/354)) ([e0f327b](https://github.com/libp2p/js-libp2p-interfaces/commit/e0f327b5d54e240feabadce21a841629d633ec5e)) + +## [@libp2p/interface-connection-manager-v1.3.7](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-connection-manager-v1.3.6...@libp2p/interface-connection-manager-v1.3.7) (2023-01-18) + + +### Dependencies + +* bump aegir from 37.12.1 to 38.1.0 ([#335](https://github.com/libp2p/js-libp2p-interfaces/issues/335)) ([7368a36](https://github.com/libp2p/js-libp2p-interfaces/commit/7368a363423a08e8fa247dcb76ea13e4cf030d65)) + +## [@libp2p/interface-connection-manager-v1.3.6](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-connection-manager-v1.3.5...@libp2p/interface-connection-manager-v1.3.6) (2023-01-14) + + +### Bug Fixes + +* accept multiaddr param when opening connections ([#336](https://github.com/libp2p/js-libp2p-interfaces/issues/336)) ([fef9c26](https://github.com/libp2p/js-libp2p-interfaces/commit/fef9c26847cf63cb95f5fcb51ee40cbc679cc6bf)) + + +### Trivial Changes + +* remove lerna ([#330](https://github.com/libp2p/js-libp2p-interfaces/issues/330)) ([6678592](https://github.com/libp2p/js-libp2p-interfaces/commit/6678592dd0cf601a2671852f9d2a0aff5dee2b18)) + +## [@libp2p/interface-connection-manager-v1.3.5](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-connection-manager-v1.3.4...@libp2p/interface-connection-manager-v1.3.5) (2023-01-06) + + +### Dependencies + +* update sibling dependencies ([b50e621](https://github.com/libp2p/js-libp2p-interfaces/commit/b50e621d31a8b32affc3fadb9f97c4883d577f93)) + +## [@libp2p/interface-connection-manager-v1.3.4](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-connection-manager-v1.3.3...@libp2p/interface-connection-manager-v1.3.4) (2022-12-19) + + +### Documentation + +* add interface docs ([#324](https://github.com/libp2p/js-libp2p-interfaces/issues/324)) ([2789445](https://github.com/libp2p/js-libp2p-interfaces/commit/278944594c24e1a3c4b3624a35680d69166546d7)) + +## [@libp2p/interface-connection-manager-v1.3.3](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-connection-manager-v1.3.2...@libp2p/interface-connection-manager-v1.3.3) (2022-12-16) + + +### Documentation + +* update project config ([#323](https://github.com/libp2p/js-libp2p-interfaces/issues/323)) ([0fc6a08](https://github.com/libp2p/js-libp2p-interfaces/commit/0fc6a08e9cdcefe361fe325281a3a2a03759ff59)) + +## [@libp2p/interface-connection-manager-v1.3.2](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-connection-manager-v1.3.1...@libp2p/interface-connection-manager-v1.3.2) (2022-12-14) + + +### Bug Fixes + +* generate docs for all packages ([#321](https://github.com/libp2p/js-libp2p-interfaces/issues/321)) ([b6f8b32](https://github.com/libp2p/js-libp2p-interfaces/commit/b6f8b32a920c15a28fe021e6050e31aaae89d518)) + +## [@libp2p/interface-connection-manager-v1.3.1](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-connection-manager-v1.3.0...@libp2p/interface-connection-manager-v1.3.1) (2022-11-05) + + +### Bug Fixes + +* update project config ([#311](https://github.com/libp2p/js-libp2p-interfaces/issues/311)) ([27dd0ce](https://github.com/libp2p/js-libp2p-interfaces/commit/27dd0ce3c249892ac69cbb24ddaf0b9f32385e37)) + +## [@libp2p/interface-connection-manager-v1.3.0](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-connection-manager-v1.2.0...@libp2p/interface-connection-manager-v1.3.0) (2022-10-11) + + +### Features + +* add afterUpgradeInbound method ([#300](https://github.com/libp2p/js-libp2p-interfaces/issues/300)) ([fbdf5f5](https://github.com/libp2p/js-libp2p-interfaces/commit/fbdf5f54277735a26df0a28099eeae9d57159978)) + +## [@libp2p/interface-connection-manager-v1.2.0](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-connection-manager-v1.1.1...@libp2p/interface-connection-manager-v1.2.0) (2022-10-04) + + +### Features + +* add acceptIncomingConnection to ConnectionManager ([#295](https://github.com/libp2p/js-libp2p-interfaces/issues/295)) ([5d460e8](https://github.com/libp2p/js-libp2p-interfaces/commit/5d460e8815a8b49915da7ffabccc4a8b96a61acc)) + +## [@libp2p/interface-connection-manager-v1.1.1](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-connection-manager-v1.1.0...@libp2p/interface-connection-manager-v1.1.1) (2022-09-21) + + +### Dependencies + +* update @multiformats/multiaddr to 11.0.0 ([#288](https://github.com/libp2p/js-libp2p-interfaces/issues/288)) ([57b2ad8](https://github.com/libp2p/js-libp2p-interfaces/commit/57b2ad88edfc7807311143791bc49270b1a81eaf)) + +## [@libp2p/interface-connection-manager-v1.1.0](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-connection-manager-v1.0.3...@libp2p/interface-connection-manager-v1.1.0) (2022-09-09) + + +### Features + +* add dialer interface ([#285](https://github.com/libp2p/js-libp2p-interfaces/issues/285)) ([1e62df4](https://github.com/libp2p/js-libp2p-interfaces/commit/1e62df4f15b45abe62fe8400dbd88866a2bc13cd)) + +## [@libp2p/interface-connection-manager-v1.0.3](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-connection-manager-v1.0.2...@libp2p/interface-connection-manager-v1.0.3) (2022-08-07) + + +### Trivial Changes + +* update project config ([#271](https://github.com/libp2p/js-libp2p-interfaces/issues/271)) ([59c0bf5](https://github.com/libp2p/js-libp2p-interfaces/commit/59c0bf5e0b05496fca2e4902632b61bb41fad9e9)) + + +### Dependencies + +* update sibling dependencies ([f859920](https://github.com/libp2p/js-libp2p-interfaces/commit/f859920423587ae797ac90ccaa3af8bdf60ae549)) + +## [@libp2p/interface-connection-manager-v1.0.2](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-connection-manager-v1.0.1...@libp2p/interface-connection-manager-v1.0.2) (2022-06-27) + + +### Trivial Changes + +* update deps ([#262](https://github.com/libp2p/js-libp2p-interfaces/issues/262)) ([51edf7d](https://github.com/libp2p/js-libp2p-interfaces/commit/51edf7d9b3765a6f75c915b1483ea345d0133a41)) + +## [@libp2p/interface-connection-manager-v1.0.1](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-connection-manager-v1.0.0...@libp2p/interface-connection-manager-v1.0.1) (2022-06-16) + + +### Trivial Changes + +* update deps ([545264f](https://github.com/libp2p/js-libp2p-interfaces/commit/545264f87a58394d2a7da77e93f3a596e889238f)) diff --git a/packages/interface-connection-manager/LICENSE b/packages/interface-connection-manager/LICENSE new file mode 100644 index 0000000000..20ce483c86 --- /dev/null +++ b/packages/interface-connection-manager/LICENSE @@ -0,0 +1,4 @@ +This project is dual licensed under MIT and Apache-2.0. + +MIT: https://www.opensource.org/licenses/mit +Apache-2.0: https://www.apache.org/licenses/license-2.0 diff --git a/packages/interface-connection-manager/LICENSE-APACHE b/packages/interface-connection-manager/LICENSE-APACHE new file mode 100644 index 0000000000..14478a3b60 --- /dev/null +++ b/packages/interface-connection-manager/LICENSE-APACHE @@ -0,0 +1,5 @@ +Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. diff --git a/packages/interface-connection-manager/LICENSE-MIT b/packages/interface-connection-manager/LICENSE-MIT new file mode 100644 index 0000000000..72dc60d84b --- /dev/null +++ b/packages/interface-connection-manager/LICENSE-MIT @@ -0,0 +1,19 @@ +The MIT License (MIT) + +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/packages/interface-connection-manager/README.md b/packages/interface-connection-manager/README.md new file mode 100644 index 0000000000..ac0985fe6f --- /dev/null +++ b/packages/interface-connection-manager/README.md @@ -0,0 +1,43 @@ +# @libp2p/interface-connection-manager + +[![libp2p.io](https://img.shields.io/badge/project-libp2p-yellow.svg?style=flat-square)](http://libp2p.io/) +[![Discuss](https://img.shields.io/discourse/https/discuss.libp2p.io/posts.svg?style=flat-square)](https://discuss.libp2p.io) +[![codecov](https://img.shields.io/codecov/c/github/libp2p/js-libp2p.svg?style=flat-square)](https://codecov.io/gh/libp2p/js-libp2p) +[![CI](https://img.shields.io/github/actions/workflow/status/libp2p/js-libp2p/js-test-and-release.yml?branch=master\&style=flat-square)](https://github.com/libp2p/js-libp2p/actions/workflows/js-test-and-release.yml?query=branch%3Amaster) + +> Connection Manager interface for libp2p + +## Table of contents + +- [Install](#install) +- [Usage](#usage) +- [API Docs](#api-docs) +- [License](#license) +- [Contribution](#contribution) + +## Install + +```console +$ npm i @libp2p/interface-connection-manager +``` + +## Usage + +```js +import type { ConnectionManager } from '@libp2p/interface-connection-manager' +``` + +## API Docs + +- + +## License + +Licensed under either of + +- Apache 2.0, ([LICENSE-APACHE](LICENSE-APACHE) / ) +- MIT ([LICENSE-MIT](LICENSE-MIT) / ) + +## Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. diff --git a/packages/interface-connection-manager/package.json b/packages/interface-connection-manager/package.json new file mode 100644 index 0000000000..28a8101839 --- /dev/null +++ b/packages/interface-connection-manager/package.json @@ -0,0 +1,143 @@ +{ + "name": "@libp2p/interface-connection-manager", + "version": "3.0.1", + "description": "Connection Manager interface for libp2p", + "license": "Apache-2.0 OR MIT", + "homepage": "https://github.com/libp2p/js-libp2p/tree/master/packages/interface-connection-manager#readme", + "repository": { + "type": "git", + "url": "git+https://github.com/libp2p/js-libp2p.git" + }, + "bugs": { + "url": "https://github.com/libp2p/js-libp2p/issues" + }, + "keywords": [ + "interface", + "libp2p" + ], + "type": "module", + "types": "./dist/src/index.d.ts", + "files": [ + "src", + "dist", + "!dist/test", + "!**/*.tsbuildinfo" + ], + "exports": { + ".": { + "types": "./dist/src/index.d.ts", + "import": "./dist/src/index.js" + } + }, + "eslintConfig": { + "extends": "ipfs", + "parserOptions": { + "sourceType": "module" + } + }, + "release": { + "branches": [ + "master" + ], + "plugins": [ + [ + "@semantic-release/commit-analyzer", + { + "preset": "conventionalcommits", + "releaseRules": [ + { + "breaking": true, + "release": "major" + }, + { + "revert": true, + "release": "patch" + }, + { + "type": "feat", + "release": "minor" + }, + { + "type": "fix", + "release": "patch" + }, + { + "type": "docs", + "release": "patch" + }, + { + "type": "test", + "release": "patch" + }, + { + "type": "deps", + "release": "patch" + }, + { + "scope": "no-release", + "release": false + } + ] + } + ], + [ + "@semantic-release/release-notes-generator", + { + "preset": "conventionalcommits", + "presetConfig": { + "types": [ + { + "type": "feat", + "section": "Features" + }, + { + "type": "fix", + "section": "Bug Fixes" + }, + { + "type": "chore", + "section": "Trivial Changes" + }, + { + "type": "docs", + "section": "Documentation" + }, + { + "type": "deps", + "section": "Dependencies" + }, + { + "type": "test", + "section": "Tests" + } + ] + } + } + ], + "@semantic-release/changelog", + "@semantic-release/npm", + "@semantic-release/github", + "@semantic-release/git" + ] + }, + "scripts": { + "clean": "aegir clean", + "lint": "aegir lint", + "dep-check": "aegir dep-check", + "build": "aegir build", + "release": "aegir release" + }, + "dependencies": { + "@libp2p/interface-connection": "^5.0.0", + "@libp2p/interface-peer-id": "^2.0.0", + "@libp2p/interfaces": "^3.0.0", + "@libp2p/peer-collections": "^3.0.1", + "@multiformats/multiaddr": "^12.0.0" + }, + "devDependencies": { + "aegir": "^39.0.5" + }, + "typedoc": { + "entryPoint": "./src/index.ts" + } +} diff --git a/packages/interface-connection-manager/src/index.ts b/packages/interface-connection-manager/src/index.ts new file mode 100644 index 0000000000..ca9bebc2d2 --- /dev/null +++ b/packages/interface-connection-manager/src/index.ts @@ -0,0 +1,79 @@ +import type { Connection, MultiaddrConnection } from '@libp2p/interface-connection' +import type { PeerId } from '@libp2p/interface-peer-id' +import type { AbortOptions } from '@libp2p/interfaces' +import type { PeerMap } from '@libp2p/peer-collections' +import type { Multiaddr } from '@multiformats/multiaddr' + +export type PendingDialStatus = 'queued' | 'active' | 'error' | 'success' + +export interface PendingDial { + id: string + status: PendingDialStatus + peerId?: PeerId + multiaddrs: Multiaddr[] +} + +export interface ConnectionManager { + /** + * Return connections, optionally filtering by a PeerId + * + * @example + * + * ```js + * const connections = libp2p.connectionManager.get(peerId) + * // [] + * ``` + */ + getConnections: (peerId?: PeerId) => Connection[] + + /** + * Return a map of all connections with their associated PeerIds + * + * @example + * + * ```js + * const connectionsMap = libp2p.connectionManager.getConnectionsMap() + * ``` + */ + getConnectionsMap: () => PeerMap + + /** + * Open a connection to a remote peer + * + * @example + * + * ```js + * const connection = await libp2p.connectionManager.openConnection(peerId) + * ``` + */ + openConnection: (peer: PeerId | Multiaddr | Multiaddr[], options?: AbortOptions) => Promise + + /** + * Close our connections to a peer + */ + closeConnections: (peer: PeerId) => Promise + + /** + * Invoked after an incoming connection is opened but before PeerIds are + * exchanged, this lets the ConnectionManager check we have sufficient + * resources to accept the connection in which case it will return true, + * otherwise it will return false. + */ + acceptIncomingConnection: (maConn: MultiaddrConnection) => Promise + + /** + * Invoked after upgrading a multiaddr connection has finished + */ + afterUpgradeInbound: () => void + + /** + * Return the list of in-progress or queued dials + * + * @example + * + * ```js + * const dials = libp2p.connectionManager.getDialQueue() + * ``` + */ + getDialQueue: () => PendingDial[] +} diff --git a/packages/interface-connection-manager/tsconfig.json b/packages/interface-connection-manager/tsconfig.json new file mode 100644 index 0000000000..cbde3d05cb --- /dev/null +++ b/packages/interface-connection-manager/tsconfig.json @@ -0,0 +1,20 @@ +{ + "extends": "aegir/src/config/tsconfig.aegir.json", + "compilerOptions": { + "outDir": "dist" + }, + "include": [ + "src" + ], + "references": [ + { + "path": "../interface-connection" + }, + { + "path": "../interface-peer-id" + }, + { + "path": "../interfaces" + } + ] +} diff --git a/packages/interface-connection/CHANGELOG.md b/packages/interface-connection/CHANGELOG.md new file mode 100644 index 0000000000..4cbc441900 --- /dev/null +++ b/packages/interface-connection/CHANGELOG.md @@ -0,0 +1,188 @@ +## [@libp2p/interface-connection-v5.1.1](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-connection-v5.1.0...@libp2p/interface-connection-v5.1.1) (2023-06-14) + + +### Bug Fixes + +* add maxOutboundStreams option to connection.newStream ([#415](https://github.com/libp2p/js-libp2p-interfaces/issues/415)) ([45ae18f](https://github.com/libp2p/js-libp2p-interfaces/commit/45ae18fe30436dedf418e0fa328f1af9a18a9a98)) + +## [@libp2p/interface-connection-v5.1.0](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-connection-v5.0.2...@libp2p/interface-connection-v5.1.0) (2023-05-16) + + +### Features + +* add closeRead and closeWrite to stream timeline properties ([#401](https://github.com/libp2p/js-libp2p-interfaces/issues/401)) ([b1de032](https://github.com/libp2p/js-libp2p-interfaces/commit/b1de0327cc6128b52ff1ceabe333481fb8a24bcb)) + +## [@libp2p/interface-connection-v5.0.2](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-connection-v5.0.1...@libp2p/interface-connection-v5.0.2) (2023-05-04) + + +### Dependencies + +* bump aegir from 38.1.8 to 39.0.5 ([#393](https://github.com/libp2p/js-libp2p-interfaces/issues/393)) ([31f3797](https://github.com/libp2p/js-libp2p-interfaces/commit/31f3797b24f7c23f3f16e9db3a230bd5f7cd5175)) + +## [@libp2p/interface-connection-v5.0.1](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-connection-v5.0.0...@libp2p/interface-connection-v5.0.1) (2023-04-18) + + +### Bug Fixes + +* specify stream sink return type ([#378](https://github.com/libp2p/js-libp2p-interfaces/issues/378)) ([e0641fc](https://github.com/libp2p/js-libp2p-interfaces/commit/e0641fcc2f2a6562e7f7d8e064ebd98c5cc6dccb)) + +## [@libp2p/interface-connection-v5.0.0](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-connection-v4.0.0...@libp2p/interface-connection-v5.0.0) (2023-04-18) + + +### ⚠ BREAKING CHANGES + +* bump it-stream-types from 1.0.5 to 2.0.1 (#362) + +### Dependencies + +* bump it-stream-types from 1.0.5 to 2.0.1 ([#362](https://github.com/libp2p/js-libp2p-interfaces/issues/362)) ([cdc7747](https://github.com/libp2p/js-libp2p-interfaces/commit/cdc774792beead63e0ded96bd6c23de0335a49e3)) + +## [@libp2p/interface-connection-v4.0.0](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-connection-v3.1.1...@libp2p/interface-connection-v4.0.0) (2023-04-11) + + +### ⚠ BREAKING CHANGES + +* remove connection gater from interface-connection (#365) + +### Bug Fixes + +* remove connection gater from interface-connection ([#365](https://github.com/libp2p/js-libp2p-interfaces/issues/365)) ([1cc13d3](https://github.com/libp2p/js-libp2p-interfaces/commit/1cc13d3f0cbb9a82cdfca77af2a4deb5e20e8f6a)) + +## [@libp2p/interface-connection-v3.1.1](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-connection-v3.1.0...@libp2p/interface-connection-v3.1.1) (2023-03-17) + + +### Dependencies + +* update @multiformats/multiaddr to 12.0.0 ([#354](https://github.com/libp2p/js-libp2p-interfaces/issues/354)) ([e0f327b](https://github.com/libp2p/js-libp2p-interfaces/commit/e0f327b5d54e240feabadce21a841629d633ec5e)) + +## [@libp2p/interface-connection-v3.1.0](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-connection-v3.0.8...@libp2p/interface-connection-v3.1.0) (2023-03-09) + + +### Features + +* split connection gater out into module ([#347](https://github.com/libp2p/js-libp2p-interfaces/issues/347)) ([1824744](https://github.com/libp2p/js-libp2p-interfaces/commit/18247442aa64c809d9e101ccbd0067ce48bdb80f)) + +## [@libp2p/interface-connection-v3.0.8](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-connection-v3.0.7...@libp2p/interface-connection-v3.0.8) (2023-01-18) + + +### Trivial Changes + +* remove lerna ([#330](https://github.com/libp2p/js-libp2p-interfaces/issues/330)) ([6678592](https://github.com/libp2p/js-libp2p-interfaces/commit/6678592dd0cf601a2671852f9d2a0aff5dee2b18)) + + +### Dependencies + +* bump aegir from 37.12.1 to 38.1.0 ([#335](https://github.com/libp2p/js-libp2p-interfaces/issues/335)) ([7368a36](https://github.com/libp2p/js-libp2p-interfaces/commit/7368a363423a08e8fa247dcb76ea13e4cf030d65)) + +## [@libp2p/interface-connection-v3.0.7](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-connection-v3.0.6...@libp2p/interface-connection-v3.0.7) (2023-01-06) + + +### Dependencies + +* update sibling dependencies ([b50e621](https://github.com/libp2p/js-libp2p-interfaces/commit/b50e621d31a8b32affc3fadb9f97c4883d577f93)) + +## [@libp2p/interface-connection-v3.0.6](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-connection-v3.0.5...@libp2p/interface-connection-v3.0.6) (2022-12-19) + + +### Documentation + +* add interface docs ([#324](https://github.com/libp2p/js-libp2p-interfaces/issues/324)) ([2789445](https://github.com/libp2p/js-libp2p-interfaces/commit/278944594c24e1a3c4b3624a35680d69166546d7)) + +## [@libp2p/interface-connection-v3.0.5](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-connection-v3.0.4...@libp2p/interface-connection-v3.0.5) (2022-12-16) + + +### Documentation + +* update project config ([#323](https://github.com/libp2p/js-libp2p-interfaces/issues/323)) ([0fc6a08](https://github.com/libp2p/js-libp2p-interfaces/commit/0fc6a08e9cdcefe361fe325281a3a2a03759ff59)) + +## [@libp2p/interface-connection-v3.0.4](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-connection-v3.0.3...@libp2p/interface-connection-v3.0.4) (2022-12-14) + + +### Bug Fixes + +* generate docs for all packages ([#321](https://github.com/libp2p/js-libp2p-interfaces/issues/321)) ([b6f8b32](https://github.com/libp2p/js-libp2p-interfaces/commit/b6f8b32a920c15a28fe021e6050e31aaae89d518)) + +## [@libp2p/interface-connection-v3.0.3](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-connection-v3.0.2...@libp2p/interface-connection-v3.0.3) (2022-11-05) + + +### Bug Fixes + +* update project config ([#311](https://github.com/libp2p/js-libp2p-interfaces/issues/311)) ([27dd0ce](https://github.com/libp2p/js-libp2p-interfaces/commit/27dd0ce3c249892ac69cbb24ddaf0b9f32385e37)) + +## [@libp2p/interface-connection-v3.0.2](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-connection-v3.0.1...@libp2p/interface-connection-v3.0.2) (2022-09-21) + + +### Dependencies + +* update @multiformats/multiaddr to 11.0.0 ([#288](https://github.com/libp2p/js-libp2p-interfaces/issues/288)) ([57b2ad8](https://github.com/libp2p/js-libp2p-interfaces/commit/57b2ad88edfc7807311143791bc49270b1a81eaf)) + +## [@libp2p/interface-connection-v3.0.1](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-connection-v3.0.0...@libp2p/interface-connection-v3.0.1) (2022-08-10) + + +### Bug Fixes + +* revert connection encryption change to accept Uint8ArrayLists ([#280](https://github.com/libp2p/js-libp2p-interfaces/issues/280)) ([03d763c](https://github.com/libp2p/js-libp2p-interfaces/commit/03d763c1a6b168bba001783a1fb59af3f7d4e205)) + +## [@libp2p/interface-connection-v3.0.0](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-connection-v2.1.1...@libp2p/interface-connection-v3.0.0) (2022-08-07) + + +### ⚠ BREAKING CHANGES + +* change stream muxer interface (#279) +* change connection encryption interface to uint8arraylist (#278) + +### Features + +* change connection encryption interface to uint8arraylist ([#278](https://github.com/libp2p/js-libp2p-interfaces/issues/278)) ([1fa580c](https://github.com/libp2p/js-libp2p-interfaces/commit/1fa580c5a45325dc9384738e9a78a238eabb81c3)) +* change stream muxer interface ([#279](https://github.com/libp2p/js-libp2p-interfaces/issues/279)) ([1ebe269](https://github.com/libp2p/js-libp2p-interfaces/commit/1ebe26988b6a286f36a4fc5177f502cfb60368a1)) + + +### Trivial Changes + +* update project config ([#271](https://github.com/libp2p/js-libp2p-interfaces/issues/271)) ([59c0bf5](https://github.com/libp2p/js-libp2p-interfaces/commit/59c0bf5e0b05496fca2e4902632b61bb41fad9e9)) + +## [@libp2p/interface-connection-v2.1.1](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-connection-v2.1.0...@libp2p/interface-connection-v2.1.1) (2022-06-27) + + +### Trivial Changes + +* update deps ([#262](https://github.com/libp2p/js-libp2p-interfaces/issues/262)) ([51edf7d](https://github.com/libp2p/js-libp2p-interfaces/commit/51edf7d9b3765a6f75c915b1483ea345d0133a41)) + +## [@libp2p/interface-connection-v2.1.0](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-connection-v2.0.0...@libp2p/interface-connection-v2.1.0) (2022-06-21) + + +### Features + +* add direction to StreamMuxerInit ([#253](https://github.com/libp2p/js-libp2p-interfaces/issues/253)) ([6d34d75](https://github.com/libp2p/js-libp2p-interfaces/commit/6d34d755ff4e798d52945f1f099052bdd6a83f2b)) + +## [@libp2p/interface-connection-v2.0.0](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-connection-v1.0.1...@libp2p/interface-connection-v2.0.0) (2022-06-16) + + +### ⚠ BREAKING CHANGES + +* The Connection and Stream APIs have been updated + +### Features + +* store stream data on the stream, track the stream direction ([#245](https://github.com/libp2p/js-libp2p-interfaces/issues/245)) ([6d74d2f](https://github.com/libp2p/js-libp2p-interfaces/commit/6d74d2f9f344fb4d6741ba0d35263ebe351a4c65)) + +## [@libp2p/interface-connection-v1.0.1](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-connection-v1.0.0...@libp2p/interface-connection-v1.0.1) (2022-06-14) + + +### Trivial Changes + +* **release:** 1.0.0 [skip ci] ([0005492](https://github.com/libp2p/js-libp2p-interfaces/commit/0005492cc5958d261017f6db5fe1073b83b46265)), closes [#226](https://github.com/libp2p/js-libp2p-interfaces/issues/226) [#234](https://github.com/libp2p/js-libp2p-interfaces/issues/234) [#233](https://github.com/libp2p/js-libp2p-interfaces/issues/233) + +## @libp2p/interface-connection-v1.0.0 (2022-06-14) + + +### ⚠ BREAKING CHANGES + +* most modules have been split out of the `@libp2p/interfaces` and `@libp2p/interface-compliance-tests` packages + +### Trivial Changes + +* break modules apart ([#232](https://github.com/libp2p/js-libp2p-interfaces/issues/232)) ([385614e](https://github.com/libp2p/js-libp2p-interfaces/commit/385614e772329052ab17415c8bd421f65b01a61b)), closes [#226](https://github.com/libp2p/js-libp2p-interfaces/issues/226) +* release [skip ci] ([357286d](https://github.com/libp2p/js-libp2p-interfaces/commit/357286df899899cf7a94348aeb8dd7387f7acad5)) +* update aegir ([#234](https://github.com/libp2p/js-libp2p-interfaces/issues/234)) ([3e03895](https://github.com/libp2p/js-libp2p-interfaces/commit/3e038959ecab6cfa3585df9ee179c0af7a61eda5)) +* update readmes ([#233](https://github.com/libp2p/js-libp2p-interfaces/issues/233)) ([ee7da38](https://github.com/libp2p/js-libp2p-interfaces/commit/ee7da38dccc08160d26c8436df8739ce7e0b340e)) diff --git a/packages/interface-connection/LICENSE b/packages/interface-connection/LICENSE new file mode 100644 index 0000000000..20ce483c86 --- /dev/null +++ b/packages/interface-connection/LICENSE @@ -0,0 +1,4 @@ +This project is dual licensed under MIT and Apache-2.0. + +MIT: https://www.opensource.org/licenses/mit +Apache-2.0: https://www.apache.org/licenses/license-2.0 diff --git a/packages/interface-connection/LICENSE-APACHE b/packages/interface-connection/LICENSE-APACHE new file mode 100644 index 0000000000..14478a3b60 --- /dev/null +++ b/packages/interface-connection/LICENSE-APACHE @@ -0,0 +1,5 @@ +Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. diff --git a/packages/interface-connection/LICENSE-MIT b/packages/interface-connection/LICENSE-MIT new file mode 100644 index 0000000000..72dc60d84b --- /dev/null +++ b/packages/interface-connection/LICENSE-MIT @@ -0,0 +1,19 @@ +The MIT License (MIT) + +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/packages/interface-connection/README.md b/packages/interface-connection/README.md new file mode 100644 index 0000000000..b2e2acc46f --- /dev/null +++ b/packages/interface-connection/README.md @@ -0,0 +1,315 @@ +# @libp2p/interface-connection + +[![libp2p.io](https://img.shields.io/badge/project-libp2p-yellow.svg?style=flat-square)](http://libp2p.io/) +[![Discuss](https://img.shields.io/discourse/https/discuss.libp2p.io/posts.svg?style=flat-square)](https://discuss.libp2p.io) +[![codecov](https://img.shields.io/codecov/c/github/libp2p/js-libp2p.svg?style=flat-square)](https://codecov.io/gh/libp2p/js-libp2p) +[![CI](https://img.shields.io/github/actions/workflow/status/libp2p/js-libp2p/js-test-and-release.yml?branch=master\&style=flat-square)](https://github.com/libp2p/js-libp2p/actions/workflows/js-test-and-release.yml?query=branch%3Amaster) + +> Connection interface for libp2p + +## Table of contents + +- [Install](#install) +- [Usage](#usage) +- [Connection](#connection) +- [Test suite](#test-suite) +- [API](#api) + - [Connection](#connection-1) + - [Creating a connection instance](#creating-a-connection-instance) + - [Create a new stream](#create-a-new-stream) + - [Add stream metadata](#add-stream-metadata) + - [Remove a from the registry](#remove-a-from-the-registry) + - [Close connection](#close-connection) + - [Connection identifier](#connection-identifier) + - [Connection streams registry](#connection-streams-registry) + - [Remote peer](#remote-peer) + - [Local peer](#local-peer) + - [Get the connection Streams](#get-the-connection-streams) + - [Remote address](#remote-address) + - [Local address](#local-address) + - [Stat](#stat) + - [Tags](#tags) +- [API Docs](#api-docs) +- [License](#license) +- [Contribution](#contribution) + +## Install + +```console +$ npm i @libp2p/interface-connection +``` + +## Usage + +```js +import type { Connection } from '@libp2p/interface-connection' +``` + +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 ensure compatibility since they use the same test suite. + +## Connection + +Before creating a connection from a transport compatible with `libp2p` it is important to understand some concepts: + +- **socket**: the underlying raw duplex connection between two nodes. It is created by the transports during a dial/listen. +- **[multiaddr connection](https://github.com/libp2p/interface-transport#multiaddrconnection)**: an abstraction over the socket to allow it to work with multiaddr addresses. It is a duplex connection that transports create to wrap the socket before passing to an upgrader that turns it into a standard connection (see below). +- **connection**: a connection between two *peers* that has built in multiplexing and info about the connected peer. It is created from a [multiaddr connection](https://github.com/libp2p/interface-transport#multiaddrconnection) by an upgrader. The upgrader uses multistream-select to add secio and multiplexing and returns this object. +- **stream**: a muxed duplex channel of the `connection`. Each connection may have many streams. + +A connection stands for the libp2p communication duplex layer between two nodes. It is **not** the underlying raw transport duplex layer (socket), such as a TCP socket, but an abstracted layer that sits on top of the raw socket. + +This helps ensuring that the transport is responsible for socket management, while also allowing the application layer to handle the connection management. + +## Test suite + +```js +const tests = require('@libp2p/interface-connection-compliance-tests') +describe('your connection', () => { + tests({ + // Options should be passed to your connection + async setup (options) { + return YourConnection + }, + async teardown () { + // cleanup resources created by setup() + } + }) +}) +``` + +## API + +### Connection + +A valid connection (one that follows this abstraction), must implement the following API: + +- type: `Connection` + +```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()` + +It can be obtained as follows: + +```js +const { Connection } = require('interface-connection') + +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 + } +}) +``` + +#### Creating a connection instance + +- `JavaScript` - `const conn = new Connection({localAddr, remoteAddr, localPeer, remotePeer, newStream, close, getStreams, direction, multiplexer, encryption})` + +Creates a new Connection instance. + +`localAddr` is the optional [multiaddr](https://github.com/multiformats/multiaddr) address used by the local peer to reach the remote. +`remoteAddr` is the optional [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). +- `status` is a `string` indicating the overall status of the connection. It is one of \[`'open'`, `'closing'`, `'closed'`] + +#### Create a new stream + +- `JavaScript` - `conn.newStream(protocols)` + +Create a new stream within the connection. + +`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: + +```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`. These values can also be directly referenced by importing the `status` file: + +```js +const { + OPEN, CLOSING, CLOSED +} = require('libp2p-interfaces/src/connection/status') + +if (connection.stat.status === OPEN) { + // ... +} +``` + +- `timeline`: + +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. + +## API Docs + +- + +## License + +Licensed under either of + +- Apache 2.0, ([LICENSE-APACHE](LICENSE-APACHE) / ) +- MIT ([LICENSE-MIT](LICENSE-MIT) / ) + +## Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. diff --git a/packages/interface-connection/img/badge.png b/packages/interface-connection/img/badge.png new file mode 100644 index 0000000000000000000000000000000000000000..a79ca44967796cd1569661767d3bfbe09a1ad512 GIT binary patch literal 5258 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 literal 0 HcmV?d00001 diff --git a/packages/interface-connection/img/badge.svg b/packages/interface-connection/img/badge.svg new file mode 100644 index 0000000000..a240ced76c --- /dev/null +++ b/packages/interface-connection/img/badge.svg @@ -0,0 +1,19 @@ + + + + badge + Created with Sketch. + + + + + + Connectio + n + + + Compatibl + e + + + \ No newline at end of file diff --git a/packages/interface-connection/package.json b/packages/interface-connection/package.json new file mode 100644 index 0000000000..96934ced1f --- /dev/null +++ b/packages/interface-connection/package.json @@ -0,0 +1,163 @@ +{ + "name": "@libp2p/interface-connection", + "version": "5.1.1", + "description": "Connection interface for libp2p", + "license": "Apache-2.0 OR MIT", + "homepage": "https://github.com/libp2p/js-libp2p/tree/master/packages/interface-connection#readme", + "repository": { + "type": "git", + "url": "git+https://github.com/libp2p/js-libp2p.git" + }, + "bugs": { + "url": "https://github.com/libp2p/js-libp2p/issues" + }, + "keywords": [ + "interface", + "libp2p" + ], + "type": "module", + "types": "./dist/src/index.d.ts", + "typesVersions": { + "*": { + "*": [ + "*", + "dist/*", + "dist/src/*", + "dist/src/*/index" + ], + "src/*": [ + "*", + "dist/*", + "dist/src/*", + "dist/src/*/index" + ] + } + }, + "files": [ + "src", + "dist", + "!dist/test", + "!**/*.tsbuildinfo" + ], + "exports": { + ".": { + "types": "./dist/src/index.d.ts", + "import": "./dist/src/index.js" + }, + "./status": { + "types": "./dist/src/status.d.ts", + "import": "./dist/src/status.js" + } + }, + "eslintConfig": { + "extends": "ipfs", + "parserOptions": { + "sourceType": "module" + } + }, + "release": { + "branches": [ + "master" + ], + "plugins": [ + [ + "@semantic-release/commit-analyzer", + { + "preset": "conventionalcommits", + "releaseRules": [ + { + "breaking": true, + "release": "major" + }, + { + "revert": true, + "release": "patch" + }, + { + "type": "feat", + "release": "minor" + }, + { + "type": "fix", + "release": "patch" + }, + { + "type": "docs", + "release": "patch" + }, + { + "type": "test", + "release": "patch" + }, + { + "type": "deps", + "release": "patch" + }, + { + "scope": "no-release", + "release": false + } + ] + } + ], + [ + "@semantic-release/release-notes-generator", + { + "preset": "conventionalcommits", + "presetConfig": { + "types": [ + { + "type": "feat", + "section": "Features" + }, + { + "type": "fix", + "section": "Bug Fixes" + }, + { + "type": "chore", + "section": "Trivial Changes" + }, + { + "type": "docs", + "section": "Documentation" + }, + { + "type": "deps", + "section": "Dependencies" + }, + { + "type": "test", + "section": "Tests" + } + ] + } + } + ], + "@semantic-release/changelog", + "@semantic-release/npm", + "@semantic-release/github", + "@semantic-release/git" + ] + }, + "scripts": { + "clean": "aegir clean", + "lint": "aegir lint", + "dep-check": "aegir dep-check", + "build": "aegir build", + "release": "aegir release" + }, + "dependencies": { + "@libp2p/interface-peer-id": "^2.0.0", + "@libp2p/interfaces": "^3.0.0", + "@multiformats/multiaddr": "^12.0.0", + "it-stream-types": "^2.0.1", + "uint8arraylist": "^2.4.3" + }, + "devDependencies": { + "aegir": "^39.0.5" + }, + "typedoc": { + "entryPoint": "./src/index.ts" + } +} diff --git a/packages/interface-connection/src/index.ts b/packages/interface-connection/src/index.ts new file mode 100644 index 0000000000..ea5d9296d0 --- /dev/null +++ b/packages/interface-connection/src/index.ts @@ -0,0 +1,221 @@ +import type * as Status from './status.js' +import type { PeerId } from '@libp2p/interface-peer-id' +import type { AbortOptions } from '@libp2p/interfaces' +import type { Multiaddr } from '@multiformats/multiaddr' +import type { Duplex, Source } from 'it-stream-types' +import type { Uint8ArrayList } from 'uint8arraylist' + +export interface ConnectionTimeline { + open: number + upgraded?: number + close?: number +} + +/** + * Outbound conections are opened by the local node, inbound streams are opened by the remote + */ +export type Direction = 'inbound' | 'outbound' + +export interface ConnectionStat { + /** + * Outbound conections are opened by the local node, inbound streams are opened by the remote + */ + direction: Direction + + /** + * Lifecycle times for the connection + */ + timeline: ConnectionTimeline + + /** + * Once a multiplexer has been negotiated for this stream, it will be set on the stat object + */ + multiplexer?: string + + /** + * Once a connection encrypter has been negotiated for this stream, it will be set on the stat object + */ + encryption?: string + + /** + * The current status of the connection + */ + status: keyof typeof Status +} + +export interface StreamTimeline { + /** + * A timestamp of when the stream was opened + */ + open: number + + /** + * A timestamp of when the stream was closed for both reading and writing + */ + close?: number + + /** + * A timestamp of when the stream was closed for reading + */ + closeRead?: number + + /** + * A timestamp of when the stream was closed for writing + */ + closeWrite?: number + + /** + * A timestamp of when the stream was reset + */ + reset?: number +} + +export interface StreamStat { + /** + * Outbound streams are opened by the local node, inbound streams are opened by the remote + */ + direction: Direction + + /** + * Lifecycle times for the stream + */ + timeline: StreamTimeline + + /** + * Once a protocol has been negotiated for this stream, it will be set on the stat object + */ + protocol?: string +} + +/** + * A Stream is a data channel between two peers that + * can be written to and read from at both ends. + * + * It may be encrypted and multiplexed depending on the + * configuration of the nodes. + */ +export interface Stream extends Duplex, Source, Promise> { + /** + * Closes the stream for **reading** *and* **writing**. + * + * Any buffered data in the source can still be consumed and the stream will end normally. + * + * This will cause a `CLOSE` message to be sent to the remote, *unless* the sink has already ended. + * + * The sink and the source will return normally. + */ + close: () => void + + /** + * Closes the stream for **reading**. If iterating over the source of this stream in a `for await of` loop, it will return (exit the loop) after any buffered data has been consumed. + * + * This function is called automatically by the muxer when it receives a `CLOSE` message from the remote. + * + * The source will return normally, the sink will continue to consume. + */ + closeRead: () => void + + /** + * Closes the stream for **writing**. If iterating over the source of this stream in a `for await of` loop, it will return (exit the loop) after any buffered data has been consumed. + * + * The source will return normally, the sink will continue to consume. + */ + closeWrite: () => void + + /** + * Closes the stream for **reading** *and* **writing**. This should be called when a *local error* has occurred. + * + * Note, if called without an error any buffered data in the source can still be consumed and the stream will end normally. + * + * This will cause a `RESET` message to be sent to the remote, *unless* the sink has already ended. + * + * The sink will return and the source will throw if an error is passed or return normally if not. + */ + abort: (err: Error) => void + + /** + * Closes the stream *immediately* for **reading** *and* **writing**. This should be called when a *remote error* has occurred. + * + * This function is called automatically by the muxer when it receives a `RESET` message from the remote. + * + * The sink will return and the source will throw. + */ + reset: () => void + + /** + * Unique identifier for a stream. Identifiers are not unique across muxers. + */ + id: string + + /** + * Stats about this stream + */ + stat: StreamStat + + /** + * User defined stream metadata + */ + metadata: Record +} + +export interface NewStreamOptions extends AbortOptions { + /** + * If specified, and no handler has been registered with the registrar for the + * successfully negotiated protocol, use this as the max outbound stream limit + * for the protocol + */ + maxOutboundStreams?: number +} + +/** + * A Connection is a high-level representation of a connection + * to a remote peer that may have been secured by encryption and + * multiplexed, depending on the configuration of the nodes + * between which the connection is made. + */ +export interface Connection { + id: string + stat: ConnectionStat + remoteAddr: Multiaddr + remotePeer: PeerId + tags: string[] + streams: Stream[] + + newStream: (multicodecs: string | string[], options?: NewStreamOptions) => Promise + addStream: (stream: Stream) => void + removeStream: (id: string) => void + close: () => Promise +} + +export const symbol = Symbol.for('@libp2p/connection') + +export function isConnection (other: any): other is Connection { + return other != null && Boolean(other[symbol]) +} + +export interface ConnectionProtector { + + /** + * Takes a given Connection and creates a private encryption stream + * between its two peers from the PSK the Protector instance was + * created with. + */ + protect: (connection: MultiaddrConnection) => Promise +} + +export interface MultiaddrConnectionTimeline { + open: number + upgraded?: number + close?: number +} + +/** + * A MultiaddrConnection is returned by transports after dialing + * a peer. It is a low-level primitive and is the raw connection + * without encryption or stream multiplexing. + */ +export interface MultiaddrConnection extends Duplex, Source, Promise> { + close: (err?: Error) => Promise + remoteAddr: Multiaddr + timeline: MultiaddrConnectionTimeline +} diff --git a/packages/interface-connection/src/status.ts b/packages/interface-connection/src/status.ts new file mode 100644 index 0000000000..a640d97e03 --- /dev/null +++ b/packages/interface-connection/src/status.ts @@ -0,0 +1,4 @@ + +export const OPEN = 'OPEN' +export const CLOSING = 'CLOSING' +export const CLOSED = 'CLOSED' diff --git a/packages/interface-connection/tsconfig.json b/packages/interface-connection/tsconfig.json new file mode 100644 index 0000000000..22a45bf0b5 --- /dev/null +++ b/packages/interface-connection/tsconfig.json @@ -0,0 +1,17 @@ +{ + "extends": "aegir/src/config/tsconfig.aegir.json", + "compilerOptions": { + "outDir": "dist" + }, + "include": [ + "src" + ], + "references": [ + { + "path": "../interface-peer-id" + }, + { + "path": "../interfaces" + } + ] +} diff --git a/packages/interface-content-routing/CHANGELOG.md b/packages/interface-content-routing/CHANGELOG.md new file mode 100644 index 0000000000..a2a789a7c3 --- /dev/null +++ b/packages/interface-content-routing/CHANGELOG.md @@ -0,0 +1,111 @@ +## [@libp2p/interface-content-routing-v2.1.1](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-content-routing-v2.1.0...@libp2p/interface-content-routing-v2.1.1) (2023-05-04) + + +### Dependencies + +* bump aegir from 38.1.8 to 39.0.5 ([#393](https://github.com/libp2p/js-libp2p-interfaces/issues/393)) ([31f3797](https://github.com/libp2p/js-libp2p-interfaces/commit/31f3797b24f7c23f3f16e9db3a230bd5f7cd5175)) + +## [@libp2p/interface-content-routing-v2.1.0](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-content-routing-v2.0.2...@libp2p/interface-content-routing-v2.1.0) (2023-04-27) + + +### Features + +* add routing symbols ([#388](https://github.com/libp2p/js-libp2p-interfaces/issues/388)) ([9ee7691](https://github.com/libp2p/js-libp2p-interfaces/commit/9ee76915d2b8298d99557e105c4f71d585e97e7d)) + +## [@libp2p/interface-content-routing-v2.0.2](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-content-routing-v2.0.1...@libp2p/interface-content-routing-v2.0.2) (2023-03-08) + + +### Documentation + +* update content/peer routing interface comments ([#346](https://github.com/libp2p/js-libp2p-interfaces/issues/346)) ([8080944](https://github.com/libp2p/js-libp2p-interfaces/commit/8080944d3c3a81834c6b432843441996cd9e34e5)) + +## [@libp2p/interface-content-routing-v2.0.1](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-content-routing-v2.0.0...@libp2p/interface-content-routing-v2.0.1) (2023-01-18) + + +### Trivial Changes + +* remove lerna ([#330](https://github.com/libp2p/js-libp2p-interfaces/issues/330)) ([6678592](https://github.com/libp2p/js-libp2p-interfaces/commit/6678592dd0cf601a2671852f9d2a0aff5dee2b18)) + + +### Dependencies + +* bump aegir from 37.12.1 to 38.1.0 ([#335](https://github.com/libp2p/js-libp2p-interfaces/issues/335)) ([7368a36](https://github.com/libp2p/js-libp2p-interfaces/commit/7368a363423a08e8fa247dcb76ea13e4cf030d65)) + +## [@libp2p/interface-content-routing-v2.0.0](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-content-routing-v1.0.7...@libp2p/interface-content-routing-v2.0.0) (2023-01-06) + + +### ⚠ BREAKING CHANGES + +* bump multiformats from 10.0.3 to 11.0.0 (#329) + +### Dependencies + +* bump multiformats from 10.0.3 to 11.0.0 ([#329](https://github.com/libp2p/js-libp2p-interfaces/issues/329)) ([ba3a98b](https://github.com/libp2p/js-libp2p-interfaces/commit/ba3a98be61e3cf0996fefbd3004e974bb41ad2f0)) + +## [@libp2p/interface-content-routing-v1.0.7](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-content-routing-v1.0.6...@libp2p/interface-content-routing-v1.0.7) (2022-12-19) + + +### Documentation + +* add interface docs ([#324](https://github.com/libp2p/js-libp2p-interfaces/issues/324)) ([2789445](https://github.com/libp2p/js-libp2p-interfaces/commit/278944594c24e1a3c4b3624a35680d69166546d7)) + +## [@libp2p/interface-content-routing-v1.0.6](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-content-routing-v1.0.5...@libp2p/interface-content-routing-v1.0.6) (2022-12-16) + + +### Documentation + +* update project config ([#323](https://github.com/libp2p/js-libp2p-interfaces/issues/323)) ([0fc6a08](https://github.com/libp2p/js-libp2p-interfaces/commit/0fc6a08e9cdcefe361fe325281a3a2a03759ff59)) + +## [@libp2p/interface-content-routing-v1.0.5](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-content-routing-v1.0.4...@libp2p/interface-content-routing-v1.0.5) (2022-12-14) + + +### Bug Fixes + +* generate docs for all packages ([#321](https://github.com/libp2p/js-libp2p-interfaces/issues/321)) ([b6f8b32](https://github.com/libp2p/js-libp2p-interfaces/commit/b6f8b32a920c15a28fe021e6050e31aaae89d518)) + +## [@libp2p/interface-content-routing-v1.0.4](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-content-routing-v1.0.3...@libp2p/interface-content-routing-v1.0.4) (2022-11-05) + + +### Bug Fixes + +* update project config ([#311](https://github.com/libp2p/js-libp2p-interfaces/issues/311)) ([27dd0ce](https://github.com/libp2p/js-libp2p-interfaces/commit/27dd0ce3c249892ac69cbb24ddaf0b9f32385e37)) + +## [@libp2p/interface-content-routing-v1.0.3](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-content-routing-v1.0.2...@libp2p/interface-content-routing-v1.0.3) (2022-10-12) + + +### Trivial Changes + +* update project config ([#271](https://github.com/libp2p/js-libp2p-interfaces/issues/271)) ([59c0bf5](https://github.com/libp2p/js-libp2p-interfaces/commit/59c0bf5e0b05496fca2e4902632b61bb41fad9e9)) + + +### Dependencies + +* bump multiformats from 9.9.0 to 10.0.0 ([#302](https://github.com/libp2p/js-libp2p-interfaces/issues/302)) ([fe11d69](https://github.com/libp2p/js-libp2p-interfaces/commit/fe11d69b6aca3dd6ef6053bec27b534ec9908aa1)) + +## [@libp2p/interface-content-routing-v1.0.2](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-content-routing-v1.0.1...@libp2p/interface-content-routing-v1.0.2) (2022-06-27) + + +### Trivial Changes + +* update deps ([#262](https://github.com/libp2p/js-libp2p-interfaces/issues/262)) ([51edf7d](https://github.com/libp2p/js-libp2p-interfaces/commit/51edf7d9b3765a6f75c915b1483ea345d0133a41)) + +## [@libp2p/interface-content-routing-v1.0.1](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-content-routing-v1.0.0...@libp2p/interface-content-routing-v1.0.1) (2022-06-14) + + +### Trivial Changes + +* **release:** 1.0.0 [skip ci] ([1211753](https://github.com/libp2p/js-libp2p-interfaces/commit/121175364dc47cd683bee7bc40b645f193f33528)), closes [#226](https://github.com/libp2p/js-libp2p-interfaces/issues/226) [#234](https://github.com/libp2p/js-libp2p-interfaces/issues/234) [#233](https://github.com/libp2p/js-libp2p-interfaces/issues/233) + +## @libp2p/interface-content-routing-v1.0.0 (2022-06-14) + + +### ⚠ BREAKING CHANGES + +* most modules have been split out of the `@libp2p/interfaces` and `@libp2p/interface-compliance-tests` packages + +### Trivial Changes + +* break modules apart ([#232](https://github.com/libp2p/js-libp2p-interfaces/issues/232)) ([385614e](https://github.com/libp2p/js-libp2p-interfaces/commit/385614e772329052ab17415c8bd421f65b01a61b)), closes [#226](https://github.com/libp2p/js-libp2p-interfaces/issues/226) +* release [skip ci] ([357286d](https://github.com/libp2p/js-libp2p-interfaces/commit/357286df899899cf7a94348aeb8dd7387f7acad5)) +* update aegir ([#234](https://github.com/libp2p/js-libp2p-interfaces/issues/234)) ([3e03895](https://github.com/libp2p/js-libp2p-interfaces/commit/3e038959ecab6cfa3585df9ee179c0af7a61eda5)) +* update readmes ([#233](https://github.com/libp2p/js-libp2p-interfaces/issues/233)) ([ee7da38](https://github.com/libp2p/js-libp2p-interfaces/commit/ee7da38dccc08160d26c8436df8739ce7e0b340e)) diff --git a/packages/interface-content-routing/LICENSE b/packages/interface-content-routing/LICENSE new file mode 100644 index 0000000000..20ce483c86 --- /dev/null +++ b/packages/interface-content-routing/LICENSE @@ -0,0 +1,4 @@ +This project is dual licensed under MIT and Apache-2.0. + +MIT: https://www.opensource.org/licenses/mit +Apache-2.0: https://www.apache.org/licenses/license-2.0 diff --git a/packages/interface-content-routing/LICENSE-APACHE b/packages/interface-content-routing/LICENSE-APACHE new file mode 100644 index 0000000000..14478a3b60 --- /dev/null +++ b/packages/interface-content-routing/LICENSE-APACHE @@ -0,0 +1,5 @@ +Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. diff --git a/packages/interface-content-routing/LICENSE-MIT b/packages/interface-content-routing/LICENSE-MIT new file mode 100644 index 0000000000..72dc60d84b --- /dev/null +++ b/packages/interface-content-routing/LICENSE-MIT @@ -0,0 +1,19 @@ +The MIT License (MIT) + +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/packages/interface-content-routing/README.md b/packages/interface-content-routing/README.md new file mode 100644 index 0000000000..489d9b5a64 --- /dev/null +++ b/packages/interface-content-routing/README.md @@ -0,0 +1,100 @@ +# @libp2p/interface-content-routing + +[![libp2p.io](https://img.shields.io/badge/project-libp2p-yellow.svg?style=flat-square)](http://libp2p.io/) +[![Discuss](https://img.shields.io/discourse/https/discuss.libp2p.io/posts.svg?style=flat-square)](https://discuss.libp2p.io) +[![codecov](https://img.shields.io/codecov/c/github/libp2p/js-libp2p.svg?style=flat-square)](https://codecov.io/gh/libp2p/js-libp2p) +[![CI](https://img.shields.io/github/actions/workflow/status/libp2p/js-libp2p/js-test-and-release.yml?branch=master\&style=flat-square)](https://github.com/libp2p/js-libp2p/actions/workflows/js-test-and-release.yml?query=branch%3Amaster) + +> Content routing interface for libp2p + +## Table of contents + +- - [Install](#install) +- [Modules that implement the interface](#modules-that-implement-the-interface) +- [Badge](#badge) +- [How to use the battery of tests](#how-to-use-the-battery-of-tests) + - [Node.js](#nodejs) +- [API](#api) + - - [findProviders](#findproviders) + - [provide](#provide) + - [API Docs](#api-docs) + - [License](#license) + - [Contribution](#contribution) + +## Install + +```console +$ npm i @libp2p/interface-content-routing +``` + +The primary goal of this module is to enable developers to pick and swap their Content Routing module as they see fit for their libp2p installation, without having to go through shims or compatibility issues. This module and test suite were heavily inspired by abstract-blob-store and interface-stream-muxer. + +Publishing a test suite as a module lets multiple modules all ensure compatibility since they use the same test suite. + +# Modules that implement the interface + +- [JavaScript libp2p-kad-dht](https://github.com/libp2p/js-libp2p-kad-dht) +- [JavaScript libp2p-delegated-content-routing](https://github.com/libp2p/js-libp2p-delegated-content-routing) + +# Badge + +Include this badge in your readme if you make a module that is compatible with the interface-content-routing API. You can validate this by running the tests. + +![](img/badge.png) + +# How to use the battery of tests + +## Node.js + +TBD + +# API + +A valid (read: that follows this abstraction) Content Routing module must implement the following API. + +### findProviders + +- `findProviders(cid)` + +Find peers in the network that can provide a specific value, given a key. + +**Parameters** + +- [CID](https://github.com/multiformats/js-cid) + +**Returns** + +It returns an `AsyncIterable` containing the identification and addresses of the peers providing the given key, as follows: + +`AsyncIterable<{ id: PeerId, multiaddrs: Multiaddr[] }>` + +### provide + +- `provide(cid)` + +Announce to the network that we are providing the given value. + +**Parameters** + +- [CID](https://github.com/multiformats/js-cid) + +**Returns** + +It returns a promise that is resolved on the success of the operation. + +`Promise` + +## API Docs + +- + +## License + +Licensed under either of + +- Apache 2.0, ([LICENSE-APACHE](LICENSE-APACHE) / ) +- MIT ([LICENSE-MIT](LICENSE-MIT) / ) + +## Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. diff --git a/packages/interface-content-routing/img/badge.png b/packages/interface-content-routing/img/badge.png new file mode 100644 index 0000000000000000000000000000000000000000..07f73279b58e851bf2809016636752921496b0e6 GIT binary patch literal 5001 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 T)EaV300000NkvXXu0mjfPt@3B literal 0 HcmV?d00001 diff --git a/packages/interface-content-routing/img/badge.sketch b/packages/interface-content-routing/img/badge.sketch new file mode 100644 index 0000000000000000000000000000000000000000..985a835db67bcf3257a65f280accc40565acaf7f GIT binary patch literal 181682 zcmeF2^LJ!#@aL0E&=YfF+qP|MV%xSev2EKE+qRwb#J0Wpe0TSU{U5f^xu^T~Iekw* z_0+AZ_p9nENdEvw1pxyAfc*C=GdWlI1quQp4F&=N_kC_`XXIjIV(Uz2>11d7M?p(I zhynGJ-Y0}&lx-`sE?vJ<)O%x8X&lr_sv{{Oiss!7;WrKzI?!|4jr$|Ucm%xKs-C3H zP!?hyG4vl8>}3qO^KVfpfPM&dI7vzHJitaNG%4c{r^$66jn+UY2kT>)rbGU$eW6a0 z8$14-@<)B=;NLkcVcj^S2^{zVWRfXxp4nf{G;rpY#L>3;A}D>B?*YH0e$boDtED49 zy3TQvmY`iX_`habjzSSA%`4Ce?%hzm^~Od)`Gt#jlzXmz5PJs&ab~{5<*=`9oRY1N z?DKi}w(o)I=7U%xR638!2{YyosxGBBfTnk*J*}Xf6D<)UQY}d`3W=(*3nn>fsBGI79D7Hn zHgPq7PPs0>hvx!COQnA3%-5COmEEn2%K07%9{7l{)o^SfL>ITR@itIJqm6#|hD~ql z-4z{G_4egsYK!eRsrs2sypuLrzea~mT&-OTsm`@Xar)yYne;8o#@72Vvqbasp3bAY zkNGdI-7g95+9)ERbwaC_?Omv27fb~6pF3uH!S%VNL_tj2#An0aiAo-txT!7ITpnBN zICbuix}VFfS2qrcEHJg1Q=6LwM7{=ga!;lv`=Kz78%5*zw=NZYHuftrK3IGU>nR=n zm#;$3#+l&K)MY7_A$QZLm0N#XQ+6!CU7BK=NT^!i@|h1*y9?jGu>Pd2|mj?*^xIYxe@>%#}NI#dE9Ov1ki8A1gc zWsq#oe$z%edXY|-VG2&6XVRD_Vf=baWLvOFsLHIQJ&5JaPxY2OS5!^yoeGql?{j0L zKUY{p6Z_>7Og&MC+T|#Xq|#)9K?SO2=n=sBX(b#=p_7LF=0ptT#m0v2hFG<*n+t=_ zZ|-O}5?i-9Ot1Z6agC-|E14j-PAh9yxMCPK2eyrY~#5Pg@)t%2Yw*neL;+r6O zKV>?dHI?PT;#5I>?>XeLkXb)2pE<120c*6vmCwR?x~>}=;{1uuT_j8|41lQ`muRKd z7`d0UP-Zdrgb``pG7ZqDK=HHos~vc@^gCm!MMj}LviInaUT(eElLYI7FML~XO&GVZ z&f;(Pl6tn|Pk`dVnzjn>eH;ta11Oln1?`a3{JBYK1eYbG?WeGmHd2)Y&p~rmea_{6 zK_@R5o%N%0PBw`LKieT&1T-c9Z0sScBGvbycv z-a=U3&!FTkesDH*(I0xf zaLK;Ew+ufK8Pjp(EsQ_#5~2N7TbI1ZaiktMcd6Gt{Y@NUK8W~dG@SKtEIemq=4|QGMscGosUXfojr zMhR&{`!X`PuoWdTl4j>Jq+GmcTXh-m)Jx+=U3XUjQcBA>so8la0`srK&?0vsy{Vri zVL(byfaep;`XrrS#k8-j?-TJyz*DqGjP9&2UhzFK%NK@ghYg-#`8q}c@2L?}AgXV2 z(jl>tNeu&wrDow>URbk=*S{KhO3L`(QC^C zsro{8G~BngaKCR{Yf#5)`wT-ocY^I^oZGWvBA6!%ge$wg=e(kKlFJy=Ifl1(1!=AB zjzKBgF%R=|{s>-UaKnvj4VHZJolm>t)!UT5Kvi)B_uzO^8?yG!{qR>jh$(c?-;{CQ z?BB|(-40KgKqYr#jrEDmYC6%dp#SG(cOc1-mT;Jcpc-TyB)wey zZ(8fPTj@xWY3LhzE#n5r6oRBHg*xqD!}#(mc4y zD)($lz`J5}UYOqqiyAxcEpkN%$75o=W6kjPcWu45i*F|j9xUgk2s2N1S4Gz) zzVMFlQRs!c@l9+p72S+!f{hUB%vz-_7KPOO zWQ=1=`$eO)^d|@6PvPq*vyo^*6BdoOfN(Ok0C|3;LKpBZzH4H=fCd@Oup71)8)il|5}NMR4H7&3Kvo(4;ywUXewtZIDfzZIJ9EY)9=t zpZyUK6bfc1kEc(6vlk?7$C)ywD<)pvUBzG}hcA5wRdy&HkIhMLbE=dm5iG?U^H>J5 zCTC5LotY)z=YbIx{s$&yd^3h?Nn1`EU&eHG&pN8Pr(H&{!{{_wJ~A&u?Bty9YJ9u_ z)Sc=hHL_6j`2$bJZtMq)NQvPESSRniDxoPv?dpxcm5jn)05Io z<2*MdWyp%DuTD^#{WCn=c#MX4$z_JG-G1%c>FqWi`k=;oJ2&NddCN~T$jnI&VuY2< z$83gkPhvNGc?2|=iEhmmHZBG;siuLB5q+u}q{)^tBxJV}pV(+_U3%@Qt8(I2NB{H0 z0UWTTxDp2NhB(~lzvar*{Y{B-YS7d4k4{54R)gt^6IfL_kF1k$>>!3hNeQv|!?-K; zT^QAKt_<0cE4fkIi`_{{$Ol{$+V%QAHoih;y6q26^t+JiuO5@f37 z)Kycw5uSm7Kl}Ifm>9i(1c9>i#}@b#6Rd2jP&>5nHl=gcFXRUMQym%x4USWkZ9oXu zN|roAm=P+08SD{rEr!gh;Wp(e1Dll<(-mKhNnrw$p9@8eket4oSO)fv6K`)EmP|4) zl~z$dHm#dPZYNgFgvnGVW`?WtUFgm}hh~h%uAEd_5k^ilvMKh!1CJ3nF3KV0*a=X- z|7q5}ljb~O{%Mov-1AQ;bh><3mj=HV>fO#>=$*}gzW9`QMnkI)yWW&~^nwM=*3tYy zh{|Z>eHsc$-edM+)@22%S{^iFsYu{-_}ts?4>mEES+Nw;QMa5hAp=EBz(3jsW>m;^whNuE(#hLpK;RD5E*qZ){J2$rIFbQgM&)NEtI^fMbY({ zf65iM*`K`DE=?70G}h7&c&}G^U`=vRTq@*^xX+8K5dUISH|2K{8`9t{WTxuIxJ60Q*Q4=0m54YHZ5LI2Uhq$nt_Z=3*n;Tes|1Zo7UoJ*qL zI9JW*vj`LUc^@H|$nxByY* ztHb;-+|9SN1@Mxcl|g(5+)!N8=P|U(U7j{ClQGqR4DSKY3O&fnNg#GkE-6E37uP)0 zcms*1Nyv%=Bl<(xRB8+TGOzNmiCWfn4mw3BGj?b^c`p4G@eYjcqW(;k_u^jqTJZ_^ zysa*opd`}^xTq*)u=93mw2XbT=)x_V{ET!*R^ki+JlYTa+QCva719gUqVaerd~oTV zp#%Mr5;ifQ#d*9iombu1sK-}A0>bG+Jl`71HxTAAH>M3ZR#_}@lQSG>;ak)2-D!zI z$byiQ4*M-~YG%ygV(Qs_bWA4r%)Q!qvT~nYxcbdB1FfGBGMY!yTvD)uS`!TcAwPAc zz!*qY?x1I^G%@&|r-1!@1y1+xC;9MP!{9E%)4T{5^VI#Dp045*4>tpC$W4?%qH1)h zRb##O-5Ko3R>bcy9VZWZKLQp1eEtOJ;3p(mV~Z7D?r&gB$@%1Tq^D_PVUVGlYgZ0! z$(ULc@od$KmpiE_v*F3b>?SR*((p$3{%UmEE5`9yTD)c^ZuzEyIRU`eD@A_;)Lq%jNxgUCI)~z0UJ~S!O%EU~y%rv%ZjO1p)&`|Fn{Z8AGA$uNu6; zx)s&Gcq;aG6<3zhp`PJO-3NX7%Q7RHy$;`{q}31c0N13L>wo615gp!ftmxK`%AOBc< z?+cv|sg>d<%@~i$ck*_+$?`Lz?yo<|pKv;<48ZEZR5N8x!9+}*;1se59A25>CR8NtQ=|Jf{s^WO}6LFJxUs(CSa7KO^mniBgOs$i1JVZ^8Ran;$vmMYdZHk=JHJu-Wnbn{sI zOVh2E2#L15I?@}PqnuXii#JrVHlK#aP2q%8=w*=~?F;I_ zuP-*^Q?u+}A;_JH=+9k>6g19X?z%g_)qW-C;r@*1cli0SrG%{*(43l`EEfx7i=Ch@ z=89L$Pg0I2A9sGoVG;Fow^lOMq?z>QUQapx@4O+W;_j;O;|2eJ^DL160|W`eS_z4x zKtWKy59V*4WnuF_K#PgZ*w}!XiGjnA-GJ4U$$*8OlhM?Wfr*uwgN2FBz`%&pkja>t z*@VvC)(qtB3mha|K~5YF8VmaS5^$0dB1#}2p!feB0LbreCYFH_-*2E!O5#EwHPbjJ zARq!Dk|KgC?x5${P~OQ)A758pYeBHCs|F|vaikRR3JzB{PR*8Yl^<3%xJ%9JCRP{r zs&7sz7mHkHW!Rzu0uXD&KMhO;{6QoEYwT@LU!OeN6^9$jx_U)E7`k>wwT+p|TN8ozTg%^7KhVizuRjyI^)kiF&-~KfE66uPHj}HE_ExoBw|Df88+nCuqED@W4*{ z5+-);L^EBx8Bq8~>2!_KvfahrZ_o`d&KCYQbk1-b`4}>*xGSQKA3uCEjQ{(~KLKg6 zj}q=uCDD)p%Sf{=Jd>EXPfD@fK_mEX2i4(}LE_v*Muylr8tTdy6tt`7zdvsGR1ZYC zTRg2QFaHJG7)xF(?PEFOw&l=JZb_W_>n8~QlMwKq>y-ST6~*|EzAKp--*-ki)#5w8 z!#xcSKb>CLi{jZTE_6TVeBcnvaq*91?woe{orQckxSt|8`Vg++{bN_{!QZ(~JH!T$ zfd@ZFcs_2`a!Q2^sKp0+4Ot+(b7}Cq7crz#^6*mnIse)Z1S;)SS9T?z)sI4r7VksX zlelh7+mg8SGK>+-W+O2md%F2@mDoTAq#z-tcPKRU(2sdvCn=k}*{VlK4+mdHI9~vB zIL8$AT87>JE?$g>Om6%s;anq_Z2~lQ{5W1e%vgs5gA5o3wA!Xjd7r1NGnkmr)wKLb;}pRx6BwPD^(Hm5 zZD&iZ^&zgDTq&`KfTzgk_#X?26Qzy`O1Wu=aa$m8SP-5-h6&(o2F}G(aW&0uvTlmV z0gV{f&fBM@!z;0Uo-Io?pruvlaV9)=>rPKT5mN(fGA;+gmH!BogbcWh@Kq2JchqylcVt>q>!{>2N&F6B0k#qTg!i_u z`Z5i$U^W8!j=4K~K_w<<$vK4m!zq`?l5(Wf|DpiA5!SEJ0{`2p08O*mY`gjIrP-4Z zTqowf7o{$o$VA4*@}pv6YP{~@f2EDurc`uT+}T)gt|B*#qj-=fc*UXd%Een)k8pJ8 zGXH^7_-~=?;DK9&&Mj}u7mNTLMZo)gDL*foQcKisT)SwNi;Iiy$KwXqi_U!?6h7>h zO5Gd6(dVHml3nQnH-FpYUKJH}L~JAw7actn9R;N!zL{pD+viOadS%m@b8HQ1pGzl~ z^1tW=p!?nn1iGA(#%n@Ea|X{r>6j;=-&X zp@mX0sMS4BHa}B0b8M7U3S4tAT{12B7 zZQ5;(4T$ypyEnE6E+3lRM%9}QoZjHX`feEF*R*LR_@scx|XCcN`E5z@u17Er1_^NA?`q&AvjiD1fLI4qv^hv) z^`}X8hWFrKlRMS_C{T`*IlmBB1S=;uBa`~24^1` zdOX+Ca?!VoT0qn2GbBDctAzI%^zb8|7^W_tpC)q2Tpf@)CE5hOAF>B2C?W`ETtbbJ z3cAmIg3E4H?Gv9;)x>AF<0QK>y}hm-y@t)Zu>lm=#yV^^nqTOAk$)%^Wo} zU3cD1b9;>p55ouDFN{()vZ_T}nj7!oH#N4=aB+FseY*uHNza11++IWbA$^lZjQDiZ7CgNje1YU>23i=@O?#C*)g1pDjH1dgBfRSf_0DHy6YOZ*cKTkI2PgT zCe-RN$zAC8jw8-?aKhEUx&i+tdLd*ovdh85lHAAp%L!HRs5CBULOY)ov{Xn#H=o}u ztN6qcryD~EBehWzL=5VE$))lKd8(OmM1npK>1e=1#G z;_-T(_jYs*eu+SWSu8Cr9Yr2D>4{h*at|K=ah7Jc-F&{Zs_iPN-;ejjiEQW`*roZ4 ztDQA8u}edw0Y_2vFgg^{yHp36!o%v=~Jrat|*s7Ezh#W=OEFjUix|BIc%$FzG!F|Kf6NHwF!Kn~)< zZuX1UToTwUXQj&O)$V)Vt95mJv!%LMKQ5lj($>7FDhacMGV3dABz-ed7xaB*@Z@{_ z(F*$Dq7xn31ok6TVaEG)tP!pAAZ&m&ZVJK;fp z&>snk;qq(onO;V^eKjy6*>1u~h7B9Ri`JsBVQf=gbMX(Cz%C?syEf{64K*$0j>tQh zv5iqC3+qc@&(~+e3||MkmgHVS|AE7m*jZh1eLd6JoH#!1VW;WBMGJ5{u}_c-qsYM~ z;;I||%+33W(xt299F37-bY!o`Kk#8vQ%0XF>dfo?b)_eTEWdhetbpZ-gP7Ql=_KN6 z1R>Yu(!j=2bGD{Q8i)GxbHfq`L-z>RD`_u9>`yhXe$aa&H#T|`FhKlMU8v5H9bTHLrF7N z_f*m6X=SZg)pwW$7_(p$oKVmE8yDxmCGj9Cq6KROW^^HjyNX-w+n^VCIyyRrIN3nD zjQLuTo^Ha04=b0dTvK^bZXa-;55#?8oMr@JaoaweE_qO^*T}etjeM%WRLgYVLMbM& zWqS5ZWa_}%SL(eMwse}C(ONzh9H*JnpvSw^0Ms2!JPxqN8%MJ|9ml*zv2d0I>w?x| z3U&Bc!;3#xZe;X2G)eWQUEUvCdSpd2F1 z=sc49c~71UtZ{zYa(l|lafn<0O{W5aE-Tqkq*t^)85ju0zA*d=RbMI4{?&^|Akied z)j-&v<31v;rm8CX`Elj**4qt!L^ywUN%r--f*zpcOoM1A9vmqp|)Ij>CMW zgxBlQnEz!z5lf(nddqdghBEHPKAtB{^VAbM?$>S|Nt8q=M{O7v42I19mgiY@SKW(V zRpQzeWFl$Y*YW@zB+et;F75=+MGpUy1RjX?g^bd`OvhEG?e|mW zB=$x218s-QaoK~bC!XrN6!>&fv9-OVxE{-s<>jma4M(q^GEzu_kgJb$;}9GjGsh2c z%2isa^oH{U>qaN?s8e~X0u8BEN~idxC|7f3XVWOORq(e|hm&~k{Va2#`8gZ!`REX$ z){DLSWseKDy$K+%wk2)&xdA^C&cKx(#Gu*#&#u;HRXH$7er+IwJ(H>AI;J!IhdZC%MV9? zgOr62C8==~J}Wxd^SV^KZ3u<8mJ*Z|R?Dw3ShLw;*VE0+`;e3N@%9wyd>#;~GQcDp zR%Zf5Z|MK`>Ul2~KcZ_BMy`W_y|c-3iK;x&hRH4%{SA9w$8Bo~zmyZHLqs>znN^%O z^478Y>*K1jMvgq*TSG&G>xtL@Q7R!-vS7#;wtl7a!{r&TYWIDwFyYPH`_oQO(z%_B zh4NpeS1;7gN3NchdXwFe-zsBnXLc266r?QHH3Sa$Um!`4z$l{mjW9r?xEKR<9T9+s6^dxg$TY3fjuS2 zg}wz1`-?cg<}g9q(x|1nsp%9~@kXR7jfD?-vkF__S+A_D%)aF?f~zzKDMMtvIA(;Y zEpxO^(aegN9=1U?X?Z?=#s7XipcHA9OY)mGAVVmnkq*rp_}6G;`p@hxV227NcncD# zokUVH$?-D5xh>$#$DGfJ^z2rS^ z+1_W1V3o#di>f?BBFx68M-nj94ATI?HKRNai@ZCZmpsRLrt!>|riv?hx%0Kv?JaPH znNs9OMRj+TV4_V)o$A#&(-;AwCpg!ZLr8acNk}0GC>%{Tl0$f@{uSIf_nOxr_{#Q! zw3n)yD%p_ApbC-Cgl;E(=3R-;+oOrk?HD-`V|mVoh)UMVq4Q~Zsn~QDr&EzU+Lji& z&}jX|rE%t;#cZgTMos#KdF|D?yH*|qT4C6IAIP4)Q9QsTge1D)VFKVHpbemT5FtT= zq@I@?roX<1cN$G5Ap@!j$|!z{1tAQ*98zI^(!V+QtamUF%-VhU*ZTdRcb1)H5H4MQ!9;0~8h9K|uV& zM(n=M+_z?V#A54SFs=#oLcqV;ym^88(Y2KP@vQuV*n+DOt)$sPc&22@Y>M;yR^*EI z-$HV3Cs6)I3n+kN-l$&1litnTFILLGSO4TtAC`(qJu-1}`5ZgADfvtxkUa>MJOwp1 z*KG|G&=aW@C_9P`$swv6L_NP1{cfDdM^`he`E2JM)#)ikKwGP>QVS$Ek)gCuDIHSS zxS65<#A^znN91PM{I5SBoX`blKl2~-2~Z%~3P5XHSiq8wb~3mcp=?zSnpUT*>^q(I zqK;A`u$V@|Hxm0jh{m~2>!AcHYIMei|3r!uHM!$l9}Wem4r)Tx*G0@ejd?AJ50i_ zwiQA9dK2;Mg`s6j@T1XysUbFGNwt-~;R*Y824Lx0zL4EwVy`Pp+aVjRENyh=tt@>V zlB{}g1g$eF>x^53A!8{FcEUr+~ z83rg=upPnPvnbh7;@yp~Zljnp=V&N5TPMW&hLCb?s7k5|*FxLvDE4R5&icE!#V7eR z20}VVn%!|K(OxLg?l-5Jk=?0$NwRVRZEvbzP3Sp!po~$cSD7hyp z{iyZYeS!HF2?_;lg$cLyzUUhOyguChgsxO@ z4-JC^ybu|JxA7V$+ww(vPpWFQ6cbWWP=)Bl4s`W&{?IO*Ijr2p2E(;`Uu}1hPO<}g zoKKY25=6r;h@AhJ3{mU2?igrO|4fS}KHlEEjUdk!=aodXzM0PE1}5XaaRXoEcr0&B zWPVbs4AGL1!<+9B%EcE$(#efZ3mUgcSl>Z(pNXtD->*7vD6rUgz-V=@&!aSb+_+C@ z*;sJo8}Uc9(j484;K%me*uwrOf~}J&=)#0G9Cp0gSTlvV{^YM>!+{EQymY>l7c`ez zJ>2=(vvs7WpGz;vRbNwLc@of>HTHvSdLEc9)mfP^YtoN6Oyq!hu~d3$muv@Dv-(Z) z05IQW%K%tIc@ZtZeRFBz$wtR-C#_9=9obHc4#r(haT*IjC8X*k>Y>2mDZ2=Mc^b49 zrcrrem|$^E!Uq#EgD6wuu38l5J$r5KWm)zNHGKp8RQ{0b>V83fh38dPM9j)lW1)xC zAr>({k@kVpk2`)i)M+<_jQks58b7J~SrQ0lZZ~ZdRe#!67Hanj`EdIztS#Rid4gTQ z3XLyEfmra037rz(=+le!PaB8yFALo4p;4jR3^80fAO$4{s+Ejz77}HhMYdQY{*-3j zE>~&vs9sULO3B{zErkttgI08MFhgP>9-SsKuR(wWI|Z<|l1OP!*0=G0No$+o>@}th ze{;ay@EGUI$e0MaAdJU6Tixh(Pev*oPPz-y7OH$|c+QFwSP4^9aHBRE;fU;rFtIM$ zl9bWTAs7VyiIY91@;1 zts6ldnqj^$oRuerY_0~d&IRQ1Ia$2|)JO?e0jIh_wJMid@fU_;HJzQEc(vW1hd!m- zAAUC%I-|oi;v2O%yWK+tY^)nEPVAhqS)$t#+fvKiB`?Pp%U}-C-vkeyP#@4GT$dgh z808Ppu^cbj?t1%dW@9_SHrssDhBY+lsO&o#yXm8^c~H9ob^*~&s7SLcSgB4Eba}nT z27J(aRs@hI4yQ6p;eX?e%7)8E)>43%2NvD8V1u-opQ`@~kX;O5?;7T-R^HG@CY!$m z>0Fi{7`!H1SguhUfXCsuCdxOa4>{+v2pZ@6*9G1X4N7g^sq8eluttg!`qiK?$j2M0 zEy3e>e>|7SQR8$EzjS*}^o$D;z@9XG=fsp&Ik0(s8H3AR6LH3S9nf(3W|l==@Iy+9 z1@F$+C#a=>VMaRo4X|F=cFaAx9(2-BO(brsC0Q5~K+6uTpZGy?XMaRU%+)(Uz((8102nFk`(&=cneZJn= z_{mcUVdZIA+0)ch__W>cqPwmkG@Z_5G7g^;8g!Hl##wr;9pa>ClpcdYsKq_QfqfBF zQvDzzq>hO9Q`(6#me(WbrX9yQE*QW3kZb#|gbcGn8FFEvkh~ zOhnAmMIQ>pI=>)1i`SfJMPHxe-;go4pvVdj$Z;~dqZ zbb8(1l<~u+cI)jQp9bVvXuLP|T13PJs5sRJDxqH+5q`Y;TV1ZVBQqRQhXXt zLF`EBHuP-Ac)}sDn88@O%76DybRrwoAvDZMS|j{~Ji##)W-* z)&VRQ8r;lSPsU=>BD=dA{;pm5KeRIO22d7%z!*WC<2Q0=5$Z+sB<7gJ&leJ4Y$ZG& zlTaHdBc3OdUm|B(`6XG2!R>~n9EH67trKl*ml?yOt{8U)GcO}gS-k`0ex^Md`<(p# z+^l6ZQ&XLtle$fEE)t+fNO2q^x~JU-y9F(>zZxUY<@w{e@X{eDRJd1bsCW1Sfu}Cz zgHW+j*|rcB-q2d=I2vmO4?zVg-+W6z_St1DhH zKC_Ijy1Rx?RGo~Ryw;=w7`=L0?3p^PXKV|=MU;W&{ZG8c-YDt>y{et(F~?miYb+?WbfN_XYt#tv#=9tKf&E5;Yb8Lz?egkDDrZ7fer*d8l7fybUoCGF~|v1dln_cH*VrIvBiP(;~Q3jYMPu92@)a(5GUIXi>m%l=J>!{Xocen&#T z5eMNze|;tQ?=k@oFH4q|K)`Gh#p~X~xem=^%?^}035nYox)o{&?mLPg|DBllefpTa8hsYa zd6=ehe)QM5)3$S26r^J`{iz{^O)J*=rD~K7E1W*4ht@*+t(+U3yE6sM#%7znw1xLi zB0yO3xP8xlQ(8;SDKVXpoBIu$Mbb1W#KMASfJoMd*!L?&H1e0^T|q!*m>$*JC*tb{ z4edtglRfE5-{v$Z&0c0dwyHEnkmcgZ+^i%2bn;+VMwMF?2@#bc6CwQki%w|lu#d!1 zCgB56O;rufEceIT=aI}!N6POz?)#SSgrh*L58zz{WZu$(E5yo)Li5FJ9%tXBuAaDe zNgPYZm5J2<==s^*S!Fjo;)@u2T2_UR+zM5HE3EjNMjSOCt4_++HMw&(&WSw1X{MhG}iS9^n zbHy6j^6`9np^FKty_x1s(2M($osJdKq{q(Td;g@>YUUYz`OZQ@5dwwNtVOKp{jpNu z$Hra+xY-j^k-w<%r1O%?L^_fXz|^W~wZ2x; zfbzl^{pnkY@rO7ofK>$n$y}T}iHII#yt=@C!tjdkbsrQbcvyU4LXpqGuf&3SLWAfa z)M1ALlhR`NA%CD$2a`sGa)!Tau>zu=6&wI6zY?ymogHVD#ZKFpC&G}H3G%6V@@wT^ zW&D-!Lh=ev(hu=j}SF;|_7ibLfN`;l5*nF}2sQt}7C8ix1Om3OC$E;=v z%T&wjKl z$^5>#UCJR*Y>1qu@8mNw`CygiHt#_nK}Yj-oECya-CaLywn8FjmvZDbjT$hNt7M3| zSJ{ZHmR53%Yh`DcamE@Vd({g^^gdrxq1=gy1c1+hVm+qrQgE>3lp(es_CUQwlGwiO=e| zSewRs!!YvQvjoTeZVm{BE~1ukZb|UeCu=EjhAkHK=Royc{o+ELWG4`(y`Y3Dc621d zwIx_E5IBrB711fT2U88xas&(*<#&I6D37^>&{Vgk2cSiFoH#DlEWBGH~A z=g3=Bb7n<&{<6Udf3o+}oC$HHPy(QX>`~lYwo-ZWduw$@TD?Y_vV2>Q%1(7^ z`h}JZ5_(BGg@)4KpRGWY;s_6UOmd&45ges;hA$1qGVYP2O=FAzoRzM`@gd(_E0 z*k_r0OzrA7?})CXoMg^#WgX^E{k1-rzr{!TVB>~2>Dq49Z<3#Qy`M%@)Km)ysQ9f& z7F{97J`=6W-y~20@q3CvL7(N11Qq$)VC019BSMW4&?VY% zik6?_s&}&VGZCxMRta8s*4@R_TjX7)`j%JYBRFPEj2`M7;uUv{{M@) z62@KR2$)uU`)eNsFJ2C+G#U)!>gWp^o+Oyar32N=As)Sd_e7!}=2{UetsPYyAma71Vzfb_gbY5pGN@6!RV+7u;eT7nHl4QoPJ5w`0K$6M$r_+xHKy)A<>`&{t3Yi= zqfS3ShMrIsOFwU{Ew~-`zb<`^P{Ad;=i)H}xlCHzl)ll6Q7muD4C+WUES;3QtECaq z(UWl)U4dh@5wX+gi$U zC~*@othp4+!l$j4r-2dR3;-q0cLT6F#Ur}!hLa=5D`6EmN~kms#`?ZY=>2*nUc^a1 z!;yS+CsH@);7JS+z?@g)%ihv(E>SXi^%__ayw-{@s{i^P<yTn>Ua8X-My7$jX7;f@u;&udp}0%y&Fd6BzEz`YSG?1a$TuSh2r}je|-1I z(Fp}twjA$h`FpLM01dM+zBYBQqA8{qf38At2*n*}ltwVQmw7)+NAS%ew0M5BCSEW6 zK6T6fn)yZQl6{=GoZE0zUHFG-zMVht(5>UYv7in@W^@4P;$$M>1Y~)ZoE?I|KO0)?t21;%4jbevf;z;Ym-!dN(Z2Fp7d56a> zA(Da{Q@lgo*;e}J$r(>kUvftGu)rSb_?_X=`C#IYS5eEQ8fb z%$`5t8H+@1i~K2jX!DHcF;v75t|@q$mm__d9b@m_sEC@{|Ee zbEvTB-bk$c}ti(pK(SLkc_*+3bji!Bs5)yoq zU%Lps(Co;H+dTo1MXdC~>2q2aGNl;_s`|xr>*D%UR5N)4C&f<+0Oe8LFw0s%gcnUw zt7He~ml#_Fc5ZmXmYJ*^+*+;;X_e`v4%@ypP7zzaBA~*}?OPT-O(-=!>dX9{tL_yv zS7|oLo%TG=58aPjrD#Gc6hnu&K_D8~5;tzmWrm<#A(Q`dE3H-?A|I?B(()l5)54zIsG2(#i04Zgqv|ub5M*YB^Hl*=lt>K%gU-_(nFm6TNcJ%9sx@_ zo~F?F5N>Va#uc@Eqf$KuW;PE;J2^Wl0NTj#^JgqeeR&3W$Zk2c4!2l#kslp9JT<(@ zf+2_DD@+8>9kp3;wp>CgQ|9D6jXqF0tLX^eZMPhK7OvIc8W7rN@~9&ZxTCf;Q*POX z2|5ajQH!aYTH@ajKcJG4axr>D=QbF;-a(gMV!aR8I=OACLoonAQuj&aImS8etJV*A zi(pL{u0=8>^$!kUcDKvMve`K()cmW^l-A7TB<%Rx?6Mcj5v~USLx}%2N8`5^EGCY) z8S9cBRZkBDd4quQbT^(m$A&}G1*JuOr7fWuAz`w)E|ak@9lLr=#7D7!lAo5{CON0C z@DcOx=d{O5^Rp&gPI?UR;J|9EQ7KK@r5H8By^>HC1XNSZk^V2D+ifTe0tL zVE{cqG$V`@4W-h|m_Gy}N}fFv@(9~wae5(#P}T=~obi)7pC^7Er2`>B{lH=K-wF+U z*$D{Bagh1Yh5jDFtQZ#N%v2b0W6N3I7E-1Q%e)|}D0f&gBDd~V+dYs|0u1Pa*`i*% zjccyg$wXV;cevoY7uu)(F1qvsGfH&LOiu;!*!t z#V5+fd9Ymx3xw8*!I>Y%>Km=du)=kC9PyFqzMaSjdzIGJT?dBzz z;;cZyXPP!e(mLlpi#g*Oj!9ZCX1(>FGA;0_rstZ!k8T7Ep&#APztP=`SeZL5wRfAP zaupylXX@G65ORbh5qluOp>nB=u>t0^MAE36r$Qc+A+D_q92Tg|V5Ix*yYHK{B}Rh@ z-+&|-#ad;Ai-=U2Rv#nU+<4|tkK%spdF_>wk|puAHdtw^gs#=)cvfQ>2#up2O*d1j zV{o>GL6JQ14}7&Nxc`R(OG3N;A3A~a1>syx zJ@M54b|48}?deFO{^<-9CNapUNA3QXj+7DTns5+MpnA1FIAKUf`KG@0)HxbJq1w%Z zATpbCQf!UTH6Y`8$*F4?MOx3FZK(!htmD1;wbx$L?1PY|P-9BS*4@kt@6Qy;yYL!+ zC|Eax{rnQ_=dWW=FUFqUk8)cK7$hRs6h8({aw;71%rnn4kUA0M?nFcNbSpuJV$cl8 zwytl+lGgEr{|Wc>Ot9|Blyl@FU{=6B9dFf!X&LVlFn^765#1{bj3)~|8DL4_8ga2Jx)Y7YOulmeU5E@ zV|%U-bZ)4LCs3V%_T6>YUH#Ffj0d-Lj$0(mGdRe-YCd$1KM4mSbf1U)D6Y}`|5RTc zR$O(SV;0Xp?DL;Pg)LLdZe($vzerVm@kJM>`C}6Z+xG6m)4ia!rgl(TTH1H{mj2w< z>uCGEWw3&rod#Cp z$cjv-WQEDqT=0hq2GS{?LC=tmI2<4)b|XkvXL~ikr*q#Dxz!t4+X|Vd0Spw zTs$4g_IV^*YxYAh?+z!m`|(J>*Ecs^s|B_zVd{u_^UXKkqKtDy>T_n#c>>MXCccZ- zf(}Vqf0lc?iV7vI9~d)w%ujHbdsYo#u#m;^U$RW=tLqu*48z$W41! zh|L!|G9YNJUJ)4H`j_cE+WdM)?ZBZRn%3Ihp5r_p#g88&Vn#oeC3q0Z8qo-_jMLg=AQOR>t3Yw`2z#qIZ6CL`c-L|yq!Hba%&(@s ziQKClfvz2`bU1=ZFzu?igD2anT>J+lxkvjKqrn@`FbA_r)*t`)$5Q`e-RXC*FlR9O zBASP*m}2$?Q_M~X)jcx5{7p?oLmTi4+JGxNSWhETpE7yMT^C$%!CnTUc0i|=f)2%S zug`I>s@sy*`}OTNAE&#^SR!`+v?x=q^>EH}LWH?^Q@;-pEQKV3LgsStg3Pswm4gm2Q`=A_+ARr9OR=WO zuHK${f<-Ltsk5`^Vp~t*IVbb%Y>#;VA|rK-=bOZHZS;=#pVZaA6>Sc3dY3j9^B(qu ztj^Q3XivM&KKtyLJ$v_@t!s$u|8#qrqrPs8Gb(bYr@HEMho^oe`<3&^B4y1DR@QGjYh|=!T9V4?7#*Cm zY2E1~iOg9|EQV(_hUc6CDi)!DuU4wt!E+obq`+FiX+{VK{mt=1n)XJJl})XMYt{K( zkvj7XMvor-6Bbf=fOmTr)9xC~5TUw{*!bAY>dNX2!nFR3l>_hZL_Lip^&x|YT#Fk2 zwdR5jF<{+-St*lms)WU6dh(p_Vim*l@U0k68|;3hj;VEDFtzUF0PV8RN$1${4EJZa zVq;JCy6Xn>l5Fu-m4C+m)nxTGd&zcRBaePb=nZ|1?{79Hbp-~LTYac) zS~X4QwI@l!px&Mk&U}KTY;VuC9XXf!PNzQIZw*t}{?S?)ogtFe$`0mnJoo$XOl7uc zmZ<%m;#wkp25Om+wgLKlqquCnIbo$ zll288bzhl>1s&qMb-w%VyAw0BGcT1y?mMq(|6YfU|78XT*ZH+g=ft@pq8{?PBEH#s z^u)S8BPS)M4jtu6?49N)+mr7&kegM1VC(wI;_cZ{UQgZQ4v$CL4mnTSeU17XTaXaJ z8#O#2bVX|Vz`LRirq0F)=$VXkM#sIH)Nk;pnculDA)(KJ#%K8bRa<`0@#Q~%UsII5 zs?6)I{vn4XeO6p~<(28E8IMJ(_CR7@Bsa|~pLg@kH^(#ARjZuIzG|s3Mf4iwLkwAu zKKf{}U-2O!b4|ID+K_rGSv*Ca=hnc%zusW~mUADH)3qvBD$JIq<#m)mg5Bok%JYR5 z9l>BoQRJ-7ThACjft}o&qR16!0B2wCPw4hOZPB_6;yGCJp^@6HzJTivP;REZ7P|Y1Sl61m~av|DZF4acxVaZWSFU z=nzx%`XHg7x_j^LiL@n_`SF_lVE6SNR>*rEhq>#0`(`+!V_)l+F(q}{wLglFPPEPS z9Bq;+_7yrlfA;6qhxQcYduy$}W)r7Wkp0PB#BNBv33lQ7(|*X|s?XHZG6EKvJ0hZ1 zPq^?~abwQ7GJ+^AEnnd~TCi%7W5qvySL3X!`4wundLm*)F_*QP2yPX0^7BOOsO82Y z$u+E34GEMmwJEc?#Xo|rX18wyX?YIBF%pSzHNjA%#tTc54*|p_ZN&iwH?Ie(w7tX; zsEknUvz^_TayEwhi1F1^%`?qm%2Rf8^Z%P8bi17l@)8;R*)tj9eEVy8KAXFf^IUtL z4fpdNCWv#8n&%-k%TyGWg#G-l_=Mb$l$`W&*}<~8L8!DTF)gd^%J?)!6jvqYbRCc!^dA*KJAHCwqeEYdttn)W< zgg-jdV zNKlhf-j6uyNhbGYAUGaJt2DBk>t)huko=pgFI4;Wym)Tgd7lT*|*m2THC#CT0`hku5b*pb@fKkb`w#~CnoHd{877%ZKkjLS1VN1W# z;flU=*7tswJm8p_{zbL?8v&#D$O%{iqh0&AZ=8ltk2HenyYybDAF*?ny(hHcs7;2@ zyU(L{Vl%%xIW7I->DT`xrmd2>fAbS$^V+x5S&+THm&@tMfwWU0f&NHvk$mXos8ZKs zOI-NWQ%~hASg=5ITLY$0k+~|HK7G34Xb+;($wgf#m0j2pgO^gsP4!1W25}Hnu3dR| zBXbRWgcU?Pn?cAp@RFmO0>*5{!D}&o5UaVL14KP*wc~7!&}|IZdMUFQTypS;-hl>3 zGXqi>Jfu+n2+A3I`st@fjT$xT;Hp)t!Zr}|YoDVnq>g6l*Eoco%r*zL_wD}Ihx#{U zzMp!}#)LJ5!H|mq^}Jxz*%GPCoOI!Z7f#`MjpjR#)nJ}F^?c+vqgW9g^-gxlX45%v11cRrR7ouIzEW2xWRDXvW)zm^!m0%vr_)kq3^=+suc z@x~iVmMvRWfPJl5NlrC}lFTJ-ilC?_noj0(g2{ARdTB38NHOvBZGFkf5Z4-A-<`-@ z{soZc@sRe(G;I($nojv3c&#C%>%ym>etJ9hZK*cfoZ3O?4Q2AKRPlWBiGrrZbt-5> z589AK9)Rg)0up;qG#=HTefC+{pJ8jHUR_iD943imRzQ0k_4ne2n+nqp=Db%!?3dKK zYe#_BBUBp{Vr!1n89;bh=p`QwOBQ+kr=Nbh;r;jD-v+toGl<{Gz`g`+j*E9b8eBZ9 zSFb)ZH^1<@lYa0}tSjKm#x?+jq~3qr)QD|M-cDz>%@(exCTkF3JIdK=_cjz}P6-wQ zA`+9j9sxavi+YZl^tah}KGwWfN*mj`=G8xW3YNZ;<8*jmM^ccDE0?UOUuzk;{RR#kcs=vM{}IFn9A;C$_S$O~L*B>p{uc3lZD)a_ zeLSP)1`&F0KOS+~<-eG8`S%(H9qLFM7d-TRcgfau3%FK!D>;{}nKClZNO+LM-dtNa zWKu8@FcNw&(d<}RB6H<$3_1RcQ~QpcY zSlG#&x*gbOPo$$sLRwgj^jD;P;Z8UXd1lR;C6#|7onS9i?<4r#C1frSWl7ISbF;xA zop#6}P0ONgKvlaCZ}m;V&|-8%LJ#y@WTru@u^k)rCQLcCIOoYHBMBS_BgIcZvyh0? zUV+*^EF>C8Qb#)dKG+QzkT&2y5a~;S?js!gz@tA7g(uW|>IWbtKNrk88_Fr8002M$ zNklJeT2MFrIgOFzR(D%d1xS`5365aYe)@-aF#t3r8mpNbhvB z%8Pcon9`>HL?kXLUC-X`Yb>=tf14C01oVJS>RJS>kBp!(i)Y+;?+F8rJ)zUBDK9E; ze02Y}-O(=ZeC&96i3y22@cPX|eOkmC%7=(OT|;N=K|-}g&U6CFbUF$`A+_9Yeh1x& zh;n1G1y7|8ef-O*TUvsyNw#N^99$5#jjWJdnKJ={hhfLA$aU={?sO6Sr&bz0dYZ>^r#A?I~RU zxntRLzdq=#t$c^Va>(+F5$@6m${ZshLf^cR(g7jVDKgX*nDX0%fJ+hJ6f`CnDY4HW zhu2*j6_cDeG$}DD4riETaz6Ks$e2*wOVefx zPwdAhvOf~f?zP7qcbovUQx8S=me&Y-O55MZv?v`H{Ep8f3Tg$GmtrD z-THM8W4pIjX=5;Debcg=Q>WDG^23P7mOk*7TW*PX;)y4FsgDRovidA?uf)Ec|IjK+iX1rD)mK+l*YYV< zArY2BSbG?~7qTrynyG?3>iMndNvcqic?loA)Mi0XNWV_%)^eQ@)Fba{sph&QAq7V; zXc@}=tHV+FQ%#?f3OOY0p}qt<*i`Plhg|{Bqeghs;U1*kkau>F>p0a-JF@LIglem2 z&3ly$#+vgSt9nE*+;ce?6!RV^5J}L}p+kpU+jne#Km|{lG-;6!<2>r?GEGp%xQv%X z=*$5>k!fZRqK22lSWpvw1V4ZF*=O%%YTn!h3m4qNd+onUo9fd0MIcFcE%^>Ic*2ZT^3L+GJKxNYS-6`P%K zuE+Gxm}Y+jZM!nSSta?KnV1z(TU%3ESzA+kkh!qCiPyaaTiO;*-%CD3kdi(~>vQ?t zsU=VYRDakmVoZ;wGZ{-KUxYK<$A_H|rX98umB^g76hr1Iw95slRubPm;2iCaG);@# zU}u(es<67r)S+TL)VUqP%R=oLl7UlmZN(|hB6RL?Npt6fVn4CR`?B`>cQX`QK+S^C(--V?Pp_I&}w{fy`i(*O4-8 zGN)r}aDXGw#wnCFl(uEC?KGj^dFP$IQEMu!?nugu^bxsC9L=s}&9Sd-dg!5tK%D2 z=JSzx@IZ5VWA#*Jr0%0oNc=ebuW45ymdkRG6k`76qt1*yF zoaTRbq+wc)RT>D$=^~`jOCa;Hlp}0+GJu&!h}oCWO02o#jyv=`j>;sg!9c6ry^O^8 zA=u;tgv>mn(T-dMc9(E`jHVQJ1R#Ro9FI!-80e#xSqt~8K21NQo(?a)+L3kyx(02; z{}E5V0?+g@3@$W)40-}XFW9~cnV(@Vy^5Ou1qQ5_`&FiXz?VE<=6(4|wfc(AB=uDX zwr&(@+kK7t7(3g2PiLwy*R~S@L+I@UEJUa4<)#PkcagnG#vqLSJQ>TL|MDm`O4RzBMS6tD#9z+GTGc`x%Dh5^LA=Kj5 z2nzCTms6bLszcVtZ`iQm8p=2oa_Y;rhWlJ!T3Y(@{rBJhK17wre!q^iosIvRoh~f2 z(jVA-$ds;^!D=nHSk3<45dA5{2e<}IXEI%{!TY~05D-h?$DHTv)4R`S>({T}scX6m zsS{%LP)c&j+iDZ{{6lSno%A02-m^yQsRtl+Rjv1NLG8hVnOX1s-LeIBv^~rJ@msIA z-YrjdN$Bch{Iklogb21Kg$V&e=wSl7g=*{$->W51wQ8CcTIonSSh(G><>OZz-ntsu z$wlV+(ByOLfxGXvtCqNy!dbMa>l45}DhVAW^T@ zZ_EOHWc#-F2ihX>Ie+w{9}PhoIvW+||8P$eIWLxb+=uhkr$oW~7lWi_%`K7=sLZ2v zpGXL-zGQ0Izrb!G816+=5(B2kKz#qhVCWo{_Z=q8Tf&HW)=7Kz?m0u}@Qy$3e{Odn zbu>4Bpj}?n?q|?OX{$n+S|fG!H}zh3)onTRUanQ2(UGJ+YTu^SgoUr)$Mq5#B&Ex| zr2a;~)&Df~ZTvPV3+PLI_A%c4-}sk z7azBs>0389ZDXL|Gl3R#X?k5S|HcD}v@cl#i#Ee*g6wh~Es-CzIu*G1G3mX8dZ=xpke)b#~q z*g59_3TkU=E1BEn5#PNB1kxKRJ%zcYtorOur@x7vSUA>y__ zt7Sol4ITCraoZR8muLU(4y2Ao$4f-O^WAmsbl#sw?6!q++mzG^Lz~0kBI{e<`c{7i zNEh@R3L5=9RW}HC2w4FFfSk_|~%`b2&Wfn--xTAQzHF z<|1`T?Ix%UAcQ-8B^8FdR#RV71oRb&%r$Q}8C&;pNPZWu*_b&sA~I&&kQsAby@!u? zr4AY8P(TRHGuPBKd9%v_PBwF&n!O51B^F|;!rQtuIx2cQBk@W)$P!3?#ee?upN}Zt z9yNzlnj|j#AnelTFo-yW1p6{QMOa4Z?Im;YaL6=3#I0}YHyzs|HZGrMelP8l$$(Bc zA1As@RK=E!R{M{X6s%b8U2(r!Ux0U6vm}O6zr{BEF6Mb;u>i@Kb?er>;fioo%$YrB z;QaaXTO2fY zwFHmfda2tXr(DQ6x!^BG(_Z8 zw0#ra!`V+h`J^NflfsMu11L#;h0JMB;C&v8wnt0J_T<^jBACi`YEJ)t{Xc1q%yph@ z>X7=oT{NYwE;S|fWd@G76qgj=N^`icUBRL|Km@1^luW8(+&;U?qo7U7JF#<|T|<8arwBO(b#<#e+09$gUqkIU)e z@@jv{+*Y+qYb6J{u=F+5W0DU6qjK*?uL3IEbNEmO&|%3dycV^i!mw8E-Me?WquyPB zDn2hMsfUv|4x1fyZhbr@WQ~O7h$5SnrDo^43W&UtZ3){l)O~fRD+lor?_)J8ogY#{ zzxn2y6B$&@VifOT;fRHPi$hR#Xi^8IFBe2U42fkHnDYR?)bfl=$HJqF!F8C1MPz$f{-gf!iI@4W+wC4ctx>C?Au*|H`1P}!joJ92kS_SAcp zvkdP%Dpy}%3l!>43h7(&6XIhl;$mZ~Jnq_^_3oNNhtpe1kQ`@pWOS_;|GxOR_$t2f z8sfvR=bPEVb<$kflDP=oc)0^YxRW9Da3dbLJ{KR}l@MTWk-0L&UPX@PcO45oWN}#y z5=uNDQ9Ngq6LCy-Ds^5Kk+T)S7s_VNI?#&Weo$mC2^~iqH|p<5jtz#`hn;oSS$nbl zN*xxGbpHA0PejU1L(1Lu*kg}L4HweXUNZmAcfJ!32^@>F)g160OV(m7ZtxeOyJ+`5 z=FhL7(IFl9gcKCN!oLoL?sq$0Z0#i|ux^1Ir>CT*Y&`SKGe6=PCX9M%+M0cs3FPornh>Ahh16>p4CKY=^98Rj>Aro3Lm>XsNtAM zzC%yZCKAUgS@2;Hwsj{Tnwv0>`Vi%eBIgu~%thXEfWwLF5a(=@WG*u2cO7bX4+DgQ zlvj`R*`MVWt8rA5sy^7s#h_sh^+YjxUxH*M?Lo*=d&pe&Se)FBrz1Lv_6?*_QD9uc z@71h8w-Bc)4W`n_Nl1FubheN+Jd6c@%?Mq{IV-9vo`YBt)6>&`&$sX#-$pslF_D}^ zPGj&pfdN}@-t(G3?|D~|I?k!3IHp|*QI5cA^-=CcL>km2{}nKpOrbKVYNa*xEta4_ zU$d5Y?opK$m18R^E6#`1vmo`2I*)9TIonFUhaU7D(Wun(`Hlx?x3E3p)7OpAaT>jeMNh88 z$?O2zE0``=hdO>gQ{S?WKmPb#3|zGUPpY0Z&wLb8`$%RoxGuZwvV$Ld@IeECb_J2t#7lCDgmKVvqhgmQX(=B~86Qv? zBj^37x%LrIL-x1L=PcJJB!ziQ2Zv;pt1k!0DBwD1kjb^!z47kJiRazLxRC9|E0QQPUpA=ONPt= zp{tUS(7OP6q{8DMZYG$!D)`W3chY>@hQu-bX5*}dSTY%LANN$VCZ=ShSXLWLbcU==GA!scWo0aBcYjbAxS1zy0>xd(yt? z)Ll!P7lkU&N(*G}l7;{q>PcWbjr;CHTNJ2d7n+V`45F6eAhdhcs#TIi0w$!I{hT|V zgHhm`hvSgch(~n#I!Wl0Cr>^$cSr6oc_8(u+plB>f(YoaB5JhU;^2AnBh>$z|O5;GpLdW^RD#NHk83SuMqmq-IyPk80inNiy-F!ebe0)72wgha1CvdEuk82(F_=TJ+va(0Y z9*BGqe^(>1YA*U48m)&)Qm1W}bWW$anE)+~Bq!PsloW`}sW*~Nc{&57Yw4`#_%N;I z_XaR~1*z;k)c?7{w6#VEC(eih#{$)UFkFmu_5uNIzCdEvOn`PJu3xJQ=&1V3^xm$E&B-A{fM*?t zdiZQrz%zfcy%j0OzGCewemu{35${S7+Zf)RV{s_Gl7KI>`L5QnKjf>l5hvn7wV(Lz z=b#OcNublI1u~beeWQ=qo7U5a)QOA#JSM$M!NH@x20=l6iuxow*U5>_N8n>r7=xvG z^g}xCV!(B-_L$hzqRzHW$)r6iNrR(o)4%m zlJ;l_MmAmz0Hj`zB5R6O79a8w_BAVg`a8f$%`SC2*9BGme(9x`j-~CxXwxnhev#TP zsL7wqDOiqwQl)FkSu!262YcyO9C((<5szt2`#YoV=lXp(QcdD{xbRho#NldZP;Vy8 zg?(LA?vVITn67pfQ>O}c7w*2XqcAFdJkR(5>fuc?s~~M^!J-t(OXWRE7m<O}po0C??}y3{U07dNzmp3QmQwO8v;gCxdqwH+`O$T5b5ws9fk#i@eM+sDNR}FKIrH@Yg(K7=v=r)if|9T~?odH&Eh!`@g_=S- zg#gmuH7a1xz(N1+R6%|hKm72+`_bfliciYF*!>0X@jYsn{`L7rRjBV~(2G5x~W`FmXH3}g3eBb#-_1+wC zA>MBjLx`&}#rq@RQ%ec^x(O2|e5hmkDES~18P3i8&Mo}CmCnSH4B7TY_4OXaZh6bm z;m8$!D}#bsw$?uX^4sz|n5532B8@g_ickf_n%9>4+j5T7-Dol<(4k(<`ANElI=E$T zK3IH!s_zy25!T65xh>_j=Nz1UzVMwNttC_2bFDcy_z0jTKOL!d40@R=rc*r->>3pQ zmv?CczAdj1_~m`xv0S}tzIu{yz`I6TwP2cH3JVMW91$6D5b1arJ~8KaxvEk@oS^#S}J~6HN*;0*27T3^7=$c*PZ0$VTPB`E9(mNa0&<1l0Ht zvXP|Pp%g4qXD+?fb~-|75G?1u4mHSk5o+^f$a*GohwUk6vSR~-XCtb~m$~;3B@uvw z-Qjkox`5Y8W5V;XJgCu#SlX0;Cz_pML4c`-KU75oHd(|hh5ezdR15HkoT&392F>fK z%fmOz&xjeAz^>!nJn^>MZj*|>v#ExNY?iix_xcCzEiA}nZdY%SI*U4egn~VK-THM8 z<6CgKebC;jk5XTyzcB||+BtqIc-4IEL&KQDKwo|>CWR3JL+D`y+vfo~O;tihyXZ75 zwJ!UkY=<&D*ez9-&UxmUXDa0_uFeFB^elE+&x=v8rJ9$M51o%ZP&=GDOX>=v%DoJu z0Uqp|ifGrN1bI4fU>gQepF`*NKMY=up`d7AVdV@OUZR8eytDV)p2pFJFLgfm*YlA_ z9*JwuwdUO5A%LB>@4o%}PEmSEtO+tSjZmY=Fcb?idTHuMN|_(cqT)4qN+AaP3#CCnRC?Yc`GxJId? zfH-!GV71Rq3RmhQbGftcw)Y2f+Zj@4`jG5wa?Z=u0HnR`(trR}=49-cS0M?{p~u*&0n(EC z$x_oXqFrFC+I0{$m8G&mrEHCOtqb_ipUicP>$cg)g1zkssq-;A`S8c7Ep$eW+TL3G zWOs+ePs4NhYVLn3=gV|ZN?t){aEJj;j5<)w4Gx8kytVe($LX+h^qXzEz0YjJivaf6 z%TyzCq@NFO&Ht^Z!L|3h-~H}gc0{>mBy~%pX!l{hYdvV$KKA`i@%4Rnv1S`W1Pq~v5L~-x zjocwVOVv&Xuv0-!>|=M`byt5r9C>e7+A4S*Yf9!2V7tB9?K;}JZ7->F{!oZF0Rk>T z6<*j{Ju)qDO$KVY%TULi4;CYI4fnqT`}?~{K?<}pf->bG*yUudwk=q&Km!afZHiFa zwa=vRA%N59b(mWQ61TmSLF1$0tNHMn{7K#Sj*ilRlXv-T-eGI7&3l{9w|*W{`xOw` z1X~q-cxnIL!+ZZqWOU?i;=A|8Iqv%Z9%=k5oMdj-%zZX;lxs@r>a+MhsyIKze;^}2 z1Te8w@9O(HqP?)&V76^Xzz}-dvFh-d*u~G~!*DW+tz$K`dHw}4s<#}Z`9FrdEd(A9gq$wJd8o8E3SK{;la^GPj%3 zEU7b4&sHDCJq(jijC}!H5B*w$*Sq>czhmZS2oNxY9s*G9BBf^HMVEb8>vc9#wQC>C z5?T&reEfNG`szx_T+H z*2%#CF1sb&gv{;c1SECM22dZyNm@$P@~N>ekm{ZL&eh-Q3ytdCMkX{{L+GKw)hZS5 z)$^oo;*UB$?hpN|;w9Jq28XilMCNuonLzxWnD<-6D5FqZFU5@dfR-^^JGT%`UWIVt%OU59hr zMQjqf8JVkTN&!jTc8cXbEFtBqQ@{G3zE4uRrFxftf&Ve{JGclOruqqP@keiInu^8U z4;KQw#2@2a)ribcMGeEoHjYuN77xkCo_rQt1(C^W&H;`PHfp>t-30>?KCERE92| zgZ^&S2~gK{hjb*yawi(x?f6w(7!w<_TfP_F0mndX&@!!US$b7#3-B!{eth=y>C+4N z?p=Jxa{f$ZJCgTlG|}+#zg+R`hZ<@)0MqEk}y|5|7;%Ao2wdJn+EY=9+n=k-2>zJWFd3#q(<9{9_+C+u$L9 zGu-tmZ9v+9H-lGdXi92}r0!p{)*Rp)S%hD}3w$%nI94ViWPAL$vE!EL-2Dgk-_TWL zuKL@M8cFKA3U@vK_SzR zs&<{I(58^#AV|F)a**n}+mgB3t2AQ72z5HEA#*KQV(sI%Ry-?UQ1YQ1fgtg<>fEe@ z&HQ(ylDTdNo9^en+lrNmq?ubZyE}=%`0?XUBy!q#oWyFF#`T*{78jPw+e_+dl;X$p z&8){!?qx{s1HPraGtM|;l`Fzkfpgq2q<>8dT{~~yyhCBRiS}30h}2iDUiIJ?Uwk1c zd_bVkxC~+%RPX8|+h3izqBR5zp|^%mJCEbZIfai<60210pRhSBwmHsWmhGJLWJmAT zWPabG-JM z6nq2@lpMI5Qk}#TU&4xCM~rEsBcyJlhZe7ZZ|O_=j9ONc`%L>;rc~i>r)W(BzQGd$ zk)(Zn7j7Go`utDke_yM#*-o=f+Jy$+Bga37`beKyvdJmt&(0uV2)#4tHMv$E-bmfD zoy&QuAdsqE4rSQGY)??F!`!URyh-t4`xDi&lhO20rrDg0=k1kf5su{pROB`}^mw>O zk+%jaE~N8I+A@{vNGEd}d_0g>>o-{RdHnImW95l9HaDicO!F=uEI3Z zX#rZnN{s);yrsF`ZobKZ{GLnyaunYDDUi)R_VE-2mq~Ffm9t|_Ihq~4UwZEyBoJ$pT=mY0^5UdJLRB@D8cY}~j}k?20- zJ1l|x6ZlrAYApo5-P8F-)4JwP8`Wx&It#kI-_p{nGSAWWk9=1q$xPX(6!S-i5irx% zIt<-^FO;es5}%7C>x8KD`S@1-_vlx@MCP1xHXovk53-(V9j|LDOVjgO`a77@)}#*m z*0;VjhB8l~J;(8p^<=AcjPtmM&lsS5K^u#*va;&cCREKsI7i;AWh^OaIfn(B(st&V zXXa|ZuK7BWx$cWaLiUg+^1LH(Vk%=glC^HSj&;X$q)c1G8Sc`;!ou(16m|%8`z3@_ zt?iC@wzW;IO=)Y6%vC;`iVeiCznS>%eQ^xT!S80HzD2&teS{;=rs&(H zUm3&n(cyg0PDuWs{oS^;txeBsZQ5GX^Zoz6_S$P-!|VN|@76kRtdMVYzSctEn_NlXQ$hcd%6TWEvR5R!Sz1G(>2=}wn{dwV z8?L|Mp4Qe=Cz|AZ1|^p2UFOPgwC(F&havP%K4CtbseDkDsvSG?VP5SvnNtnlT%;Fb zbRcq0*Gv}Fe=FAxkX(phWyI6)bfRQBaOePj(0Qy_3X*~mouax^)UkOu( z{*-$xE)l_3!ZvH}2q} zgQw9BE`Z>_6RADCL0w}*OH`WRYfM=+yLX*=zKCb70Qx+n^r_zO^}o(SVC&oJyYIgH zS;pGG7;`10o56C>be*BVn7@>UR*9)0xDBcAah^;>?EHAq;ax&}c0 z3}qMv18G63+=Nlv^Spb7S-uS20;>M`cACo0L|~`(&iX2^H>eYTAbHh9`#f`BWevx( zMT-`dQ@;kK#TGUJQ8M(P1(@XiKN*2>_V(HpD^{%RK~gdKsReb735^Y|fMBjll$4b0 z$79~R{M2reJG!Wtld-&;G5snNfWI)tHxR#>F+YOwd$!J2;N8hu9_%8Wu)G$JV^XnK zppFLfb*-s;AHAxKSiUQTcka?}?sIype9A8oLlV$?(5tM8>$p#cK$5{^XPPEOPcRpN zLfkoefn;ZVpZ6f!E(;FTI8Rk+-jpd*OufD1Cc^)8y=f2HR&H=(X&X_zHE(L!fwIr4(5T!0 z5O0MKw!H4K;0dU;?lI#x38d_Y^|G?6@(ON2ZIjfQ35@X%aj*KnjQRJV%|qOC-~}h@ ztOa<(dGLraelrcz3mMNcMqD@Ec;la#sJ-M5)X@-1vuRZCTD}ZZ_I)y$B%pV*+cn!| zkQh~~x2oM6%mLP7spSFwqilQH*Uc;ges<-RS7x$}WhT=2Rjk)v%pe}G4;WB80pgdL zaQu~ZyG;x(v*jvaVjeGHI1~6cTTB6!e1*rW4nOUhIc;JjbLtw@mbMOw#MwWzwf3Y- zyOGM$%j`HSe#{hi+O%okYH4a2h7s67c2)dO%oHq)1@T@px01TfQ`gw>mlaa_V*J_f zVJqK2!1!DK;`w?mJ00@f!q|VYp`qa=c!73j=&*%Cc*KR!?-$@1vm)hBfiWSCr6%}! zH(Y;qGR^tlpFz2H;n- zV8NWanKkp>yLbOx>w<|6>gr1_V?apT&+Kj+_x0~e%Svxh8;l+``W0>gmw)21t-Qfp zV?$#E-chMB(-o+53fq&acg@k7Z+5r-nvBB>mXNQuzI`O&g8KmIb{@+eUt+EOQ>?`g z>u&nKzSH;spp6y_p=Mf5bM18fn@ z7Cx$xi_JnooL8sUi;pqf92orx_-$`tz4+cPyI zG`5pAXl&ay8ha+TCU(v|=e+OvJ|AZN*1q>%y#8Jvd=or6f+aN1tBw(LE&MM?W0o{kllS$5PorL%qddb56N z`#VR3wH4t3MrFl&WpM?UuYmiH(0KuQ8gU6?kPl*u3Xduh@B-T;M^F z@lA#b8Gwd(Y)E3g2bK6h5amFFy3&Wnu=6x$yiemPn~#JdQ}EU04`qJDzgKel$eF4% z7Z!^{tL4G>U_RMWcGePFB-mJ^H1~XRR^0zPyEB@!@;oG<ts;$8#AN5f>ncqDQg_K>sr;(yHHJ828i=3U0G+E&MqK!Yu6sD( z%MAU5G#qosxER#rxOi!WRysD8e)S&Gp>f4KdotR>V_zu*0xNG#N!#be=gj|6xxI)1 zDVE+3M|+me+#bz-3QD98Be(prCuKQlRC z?8)orO6;c!#SY+~didJ+=2Qka3}G5H&oJOd5nUk#B;Y%Q9(neMw7-`yB)D)Jb^$V& z4Se7o!2m77OIMC#4vx8)w3nfZQeKa!P4}mNJFc!SuF>-Ha=jP~7fTB+&`5{ec{PF) zbi|^D-+c|@TkJSbkFg;fqTYq|JqfAX_wUg~w|;iFCAFw`!Knzvd#dLbyLpMDNO!gh4XbLp zbQh!*!t@ZI{=MRmTQa6kVmG~}fa-KQmWIaXZYu|RCWB585I0pU(IULU(g3D<@JqgB z95^bF9}@buo9^=o4`ss_>iw&SW{bG|GEGV=cs!I&;MtSbx6Ih#R@O~6Cn|yM*aVYv zZJF=CZ|2FqSBh=5;^C-|ZSTk){2N>B*rNE7bDn6lCj-mgn8mHoQ9tObq#PNpj3?kY zVFx)yN25EeMBTGqp>C0TW9-pKO-j*>#BM2XVb%cqpto0lig=|RrLUxNAUK7( z?1Z`A2naB+-=chLSus7nR|nSbo>_bLZ!R}5sEh)(Tdhy5HK!G)_f$FqX994>1+ZSY zy=Gy(68|i9Au6+bpl&9ZcE755Eq{wWI|;Iv%6F;j^@F=QI4;=+XZ|Fo7_Crcy|^pG)5|8Bk(( zrW8`e>ZUWqvUaTsM15`#v~uu3S?I4Smnc(mE23SqH<|E7fV2@pGerfaWR#Zj`|JF5~EU9^f3%V~V1$%7+ri{mw@Ud(6 z$BG8TDXsuVFZ89OxTayW9%{_zclF&mTg<9y5L^ii#+Kr z)cp94_wRsqrxFfpaC4f%Ua)Tq_jK(u<#C275&CY%etrj`!b69-wQ?Qu7Nab|EI%Dp z)iGo&!{aF^&`iZ5Dq&m{E{#jAMI4jR=03*DlM57ol%kNEar&Kn-+&mS`nDHD?&uUAD!fcqhopCgspE5~&e&kj!UK1(i7 zX!AS6j!{P69`$tC zQr3fj|8L|Ry?pUd&w!k3WwYjT`HO!i^W$ddK*nI!72;jq&M&UJMcez59I_WkENKK% zLRg`r3=4!7^*5CP%vVwzja&gs^~Y~#;JKYwcFfL2HKpN%AI1sq8aL@3fvqin!U(49 zyD>2qRYfifm49nUYs9+Tq#z6H21T7GI6-B@RNL!gWTF^_e68`Co)YAt2sX&~`k-_LWI;INrzIm#WRAy&%>}}? z#;sT(!Oy;c4CzPExb0T2n<(ERh<(Pi{X0Yw4w=$)8t?>>YvPi=xa##wK)X|am#TzJ zNlvH0^J{|0-4U)Gl{r#=_9Eg*Acr|@fqV=0`PhDw{s!0ls<}DTT0l(MMw%5!3bu9p z5z+zFqz_%dhkI)PhGXM6kuc?!4Ma;DdhPsTdLrh%YRr4LyGY8Wz}ca@)?j>IG0`Gp z@t|(|Q*-_Z2)tq_qUp)`zK*@gM6zPg*-QyK14Jcs=II1D_U<|F!e}hR^c_w z=HI*TBExq5ezsuVU}g>;S5`6W0S>>+F_bOkNgFh)9@)GqP|Bfgjp!ee;%tijn4~IR z9;5XfiWgPnRFS^0@@QMsCa=QM@g6cZweKV|>W zBl-@W5euN=U}%(>XG|%m-DsJeCoFd`Ld#|iR2S6i z+PHS|YWL!wz1o{Ea)!`i&LDKr>BBjuVJxe3@uw=oD&=Q){Yc|58@m5>J7d5Lnv##P zFnqbM!pqRl(`9`Qd;1t1opCcWW2zF-cOIO}rhozCo2!Fp(Zs^A+ULnirk^Aqkq#T| zEV+HSC9;Q$Tc*R?S3q0*5H1MGPs^>qfX)ruJ=|FoQ*Lr*4}pG8|^b9Zgn1X7dfkHM4XdA-CK{D+wH$BdL?_E{rkCf zt_MU`;b9}*&y**%b%G1Fr0n$K?xR5R<^i{8rJ&p3&+qaBB4pDBFmH@BSv1z0jubHh_>`~F9L)QpU5&r zRK)Z*hWT^18@`<2I=uVk*UPE_my~XZl%z@-qu;`|Sz>4&4aP0g)QFWrTTCk@Fx{q)b_J^908!o43?|?U{?GCM5({%c)6AA%u#b}Sk1 zFnM#mfk8=ofo_1Uz^Rv*Y*ZT_i0}ju3GRrqUtpvjlP=#aN%mQqtJo$1uGM<;2 zq3bjkl*wjkA7d?y@hoj?=_8( z*M8O-+^9E=Hjwt*uds=4sy~O|h%&0fgJMFQ1M(byKxJ~fr935KS>bp6QlEN3_KAz$ z^~P-f%SRX-{ls6uhZqfKjdBw$4CxLrFmup%c^Yd`S>S@Ruy~O75)%Rdk#D()`;mi~ zQ0|nW9Nu;z`746I?ZuSPGmUY7i-mB>J?>M}?M1St{K)7xu}tTBZsKc%*N0B)JGlT8 z#hDs$&LCBN0B#11J7k9wE19TJ5~Kl9>R|S>k@ClB;N9eT=?9X~RL}0l$w~XW<*wrk z15K{iE{?HOM*c_ERJIC#SSwQb-ZzUsf#Os^nN#+@vJpH5>tjC2D*rkGe;!5)528KfGu0<+UC1|y32-{+$oL`XAAj}CJkeT%kcUv~Km9zi4VK+t}HyPUFh0Dr{TzX5ahwzg=>W^{+FIu`8 zp?H7JKN_&*7lwc0{w@6qV8Mi3OcIPnYfc#}hjI#Y^uYR~6iD zFMxEKlJNUYS!l3w{T_ev!5I0H9{0f%mY<-Zg*W4K*lPV_oBS=6O|P3~ILz@q%-Y4NNa*ZgRx74Jk+v#$yeC{d(55Kv*6xr71$&>kON?JR)mF#2`mSahxAX{#ahTft)r!hDl9d(EJ*3I zsHlaDE05Ky$^d`4@G?Tol%}|jyGGJWr@_{PCsLmN0U0?j)-7Ia^g(+=PwCkY7RB#^ zBj#1Qfgg5b%0jn1Fis zb9rlqez|}12XcUVRR1dQ{*%U5q+Q08-%!+`3scN0Vj`Z?!K*C57aGCB^%jYy)m!lF zLD?j@v6(9m8v3xK$fMQvggr6FS1|S!@gG7@2t4Gq`$3o){1F4yV`Ip836%&mN2J?<{vqKO8>Qc2VOKQEJ}ogzpj z%}Egz=WV;M;p+q4bpC*j2J;O}zoZ|kODdJ^s zfJ@G`aC^uN#Y&P5YM(i;HdcIhYP*Ti;5RgOpmdoqB&;4Z4ym^63gNdI+*+LbM;7a0 zSa+|O>cx6a^0O%|6x)_yB=|G--e;H~vr_00yfc*aV9@Vyiu{K&rz*;)D~Mn47X7U; z#Kx?lh;KYbn9v%Jj{`0imnI)YevDW@-+l;2A9HMIwAf{(p(;q&?m6*?#&_~B9XaOq z_PlQjeJQ8GdP4GW8@=*L*xQE*+Uz`bj&G6onBME zA%-$rI;ji9!W{I;3xcSIwA6I{ayP!o?JSuhk{QlDj4Au~r1`*NVCUoc2dCSQZ_4@c zs+Uq}Ip!jc&7FnS?EwagYq{5i+A!Fh8pPGGgSW^Jv6t$JV-#hy^Ms3 zd!?2O=+*;2fQ7!p%gyL%@-9gpU|pMSy}h3o znp|P@ppbHXPRj)qSNC8ROBJ#jV(2+5x;z2irFN+wEW9}kX!d$6vkpS217D<1>+PK> zsS7Q@96Q&}0OD3rB_ahGGf`z(MP(zcZpYu%wh5P9ub%vM^Ggb<9DJ@j`X%t=o58-b z7iBS=XECxN;DV*SqWE;cAAGPF3DSwac{3S6^nHZD(JoktUL)X=X5EMKCC2~@s7eH~ zdQkR$i(Yye5yU3~FW&%$ci&6Lt#Q%z7!=z*S#r#xy$@nO^z z%#T%iVDlBizwfd4)4zI)%8mEqX^+Ne3wlcN_Di_Pku*cZ?>{%- zUaxW_j*4eq?vDaF(qiQ{uukxddF_ikZ`$*EmSrv}vEE!XoQnjO5!^z{Ua`D*h9~h?6aOj*;iQG9|0l`{Tui7QgX! zQ0w!FDAeQC^0Pv$>}mOK+-}ltn*NGQ^ghn&a=AMFaH)FM9X5zVzD4$WrQ`2&=w&z@ zPc)7v&-ur8bLwfgfbMfow0w_+d0B;g@4X;dkWfXCIE56s_Vo`quJ&yFp4)LFVr!c+ z;nnIc;eeN;n&pyk{zJ#KD!6o0(I1eBJpHM^+gra}D@C#q45TB#&l zVwa$Lf^vQSR@!Ajlwt2Nf=fjYgREWx0xO)XHSut=!-l7!(+eAId4c;WC246H{y(my z;t1~rjGPPJ8Rz;oTaJQ0por(MDo*LWB_`v=R6L`3qNSs?)TB&Qn74BVKXXmL56si_ zJ`JxAU$Sm)${7^-Q= z^EXg2>TD02=a$wV7L(z6HZ;^byXIhq1n^^=8>0kH#E#>rLETEFjY@I(l;7B)v zd@6&Pj;A4fe;2t?Aqt%ur+g#>?gN`bL|C=FuU&YO`U`K@o2M^TnkH~({J@I@Au!OKoF+|Xf^&esMKK`K7Vxzl3_-i zjI>4yrQ-vl^cI^mdg$}Uj2+Gd+{XTi9P_BX@SHblL%4&eK@uP-kj#Wdw+23bFb@}n z9j+1a^>S1fj>xyhbZ7%emXDi&I?Ew4x63TpCXHyy9%?b>Z<>vWmJhP%Am$axfV5>->@#*8x2 zMYPl^(iwuJCc-oFoE*IG>9lO^r^yU8u5U!V~4UuR*twlvPeowF7fg4 zi8r;(bSz;mUauoAqg76GW#?!dScEbQ8L32c$cgMuexp@Sa0(XO=T7gKF_?XG?Mo`(JU-2 zXTyZqm4xmWlhH)pR4~6w3=aH4QdWb#0vFXPA}u6#U5{pj%)uQNPZP;?e&^jG2GtCk z^C5|2B;%TJsR@IP)x{@+CQ1r6fI*0mcztdlSd6zCi&|mzmzy9qAhovhW5u=m5&4g- zA$%M-;qP=inZK;o>cCp$gDrZ*^V;zPNf0+e%F=Lm(r;%?(p}-+Bq=|ptwQ>7f|Iwb zH)77fkGG3!h2Zw5b}ugmL2q|kQ-57h!%Ato{mZJRXBlPMr@C6w`59_@QEIhOdV~Ks zQq9ur2l>JFGMA#l7rAF8PFmIsQR?Q-Fiy}cCNpH`NIqv>FqIJbkWZqy2RST~M)q?Q z>0;KG{pp&+kyqVpf}^X~<9nS0TObK={cy^?xw)jY*pjoEOAL>0revy0LE6<;jtT^G zcxv~~e6)@fH0k-O3h#gvTluom`{DkK7WgENF$N=ZW*YdmNAhXFjGUyV6s%1H62u}W zj+XG)3H&$;ecc~;ImG%C;~xNEyB51hCKsm z(S7C92QXMPc=KGCAnXH;Ac@rp`7f-hk7}4&ftHT$88XdzyY(H4x+{k|XRgA^2-3CL z(cJtX>Fw=JtIfzjQ`21_0nm8CEK6iqubsOOed>_lVj^Oq+QQGzjSKlbzP0IS1C~kM zA;S^Y{>Ti^hD>lgSmh4iZ&878`)_BYAi}@A5d#}fMP5ot+f9vF7Q$B8za@dzYY!ND zo>e4?xoI2~&CTs3f}2^VGS=3%)^;Z2VuE^pMsL0EdI3m7!?lxbVF=vub>G`e>O=ED zIGks)`}s=A-wK0tDzK+#hRlR%=^58K%#1&m@Zwu|ZeGl*ZEY();aP!G;HNK3r`h}c z_Q{&x0QBf|vA@m(PWuRh>f^g}ZJ&&Wc85aRL$VHBgWS_wj3ZUZCeWP7Zz%gdS>P9f zqTmS-UZI!27035J`2t@Jg@KQ}&`^g7f;TeQP|`M4UzK{+7xxrQdBN+;z2b0{OMa{^ zCWarCrfr&?%=iHtx8U|h{Lg_pl9QJhK-63Y_^q%qGz5?bbXM3_%o1?fEULWfKgv`9 zhZlx6hW3We7&BwwqH&^W{@()k$e;w3yzlImDxQ1u_ic!VMhVFFcKy@SZ2Ds=MTDaHGB(+1AqD^Q+ zi$Xs3Xeo|ohqd42qF@LHnbq;MAhuRVAclYAL^&6!OQwJ?&R!eT{FCzA72S4jB4h_9 zxuCVZHs`$B-t@gIsb4JwU#?NGTMOKs#eDPV^#)TZJ+(&?S-=57&#U*$vp;?a0LG+M zY+oUss8Hqz@j>4ZifxuM&f%AFpFTn(jTd{X+szgYUA zrTSi(4it9Uiyz52Z33v#7hHo3M8a*)Fno|?)mr$jRq672P$LPL8ZbQMzB)wZDT|4S z%7`kBYK>|cJ{!6ndL4oqMiQDNGo;HcZ}1X6N*zG#5s<*wfq3)+M2$wTa2xu8hTpfh z>=|*+#aZVt=eemz1m?1T>>uu@$9TGm#164&cHC_AHyeO9J%zAVk&vn?wyvE%a4d%0 zWi-+{FTT1b^2>|mGn3kaxsor&kvpV0hO>6ezjc0M4TYw^1x1p=uqs=6l;Qn^nlZD% zBP5TkC|57d4RJG^g?JA8Z<~zeb!`w%6~NT?XSeY*q_2&2w=^|ri29?8Co5P4PfvYGs2fS0uye+$co?x>C{Z-|qVhN| z&%$K#om5mSJ2wbmPWJUb%44n9QI6*_i*}tcTH~4+x;-(}ci&Ywj=`M!6XigkFH+!{ z2}p^si~a@Ln!38Wftpa&9y#w{H@PS4Xy&P3f(U0no2ODmjBkvMsNRHrpd1K4w$G*& z>Iy=_rh-8PrJ8dq7CO|JMDqkv&a1!lkx>E&6t##vs54BFX)N%X|E=`vqa?shHa~fO zqK-HZI;(eM^qp~LJzCO6Jh*T+oh;B^!ihYI&GA8d?Ue883~@*~1UccY5XfvX4DI>M zKSQ{12`U*2Z-d2)fRns6qk}(*yL^P-8q)5}&uPfYlMaY*oDZ6BS%E|a=ggE3_{Shq zz~qB$#sq~ZPA1Ym7V1=ainCm;Zvh8R@t0|efgX0D~5P$pT2n=WC=y{>`%NzJ8+Gi`5@W7MUWCD z-0I+?E~~!*MSk|10)*~`XDT0)HtPz{qG`uv_PyFiA`R&acdj_2+-b6swymHRv!PeZL!eLDZG=|Rwv9@#O46Ve8r)SkOvoYUKIP`t1(A{=V`ND2IIg8p!#rc+tp^b zZ!2{lp1z_LLIcD-J#-y@SOcb@XRb`w^1)<=t-|<>Y=&3MCFYQZm+S!oMV2}a7N$4K z*76*{;Lkn>q*Drix8gQ3Bq!uP%G9w9wx@R_;hEIPY9hn7qog?rHN4u+{0E+F4guKHK9v1kre}LBrWnfW zHVMnQ!XD?^re8PMM(RJIPv>uOVr?CUTnoEstIcf?=W!4J)oyybNCV6je4 zWZUIV1j%JSESe|FDXn^oOIOgrkvejXebKDhD(+ldP`)LD2)u4C;7i4teS2ng_U(F9 z?_{1FU_|lGw<`EWIp8J*b1`g$6h8YmN$o|#(Gbbs+6lAWPRMVU5pxyNm{S6+e$LJy z2aoeHDT*|%gTO1oz0X{)kPfd{K;%*1qf{gusP-Y+5z7%XA^NU;Utn^;s>lKOk zPY;qju|tX;AF>hBeGAf7*@_^cJK5q5mnHmk=x8_T9PuMeQET6(DeEllP?og9) z5?qC=q*9B_i{J+2p9X}HQ;fzFr87&S!iTuepD2qumu$LNFXM6& zpk<8s`G)yP4J-Y)4>sLj$|3Ea=_H&^|0sN7oHx@pW5X^OTD+pXqP=2PUUmQTF{lXe z6DX=yz59UelCdnU{! zzx+XgP#^5-GaX3FcxxzaIN$==!dp7)uSK7kC#EvU{BADD=43qYwKAQJWDSa3 zY%C^6J^V9}iS$T+h!XAP9nj-7tto#z2&A)neE#OJoT(cCS#n6OJqVP^BwfyhO!& zt~d`KXay_f>5im?)u-lwi~hMJv;pMZXX*;XyM$+#g5`5L>o)xygEID|>l_2Bovj^~ zb?if{`H$#PYqR!yQm2-Ir$TM*n^Q)*MtZ#Z-n?&t z3mH_!RJVK*(i>)Z=(Ffo@Tmn}%1xqMH~LRa7)@>RmH((5*mBR+$`77{D>7NHI_xsT z%IUsAZX#-sEM90DEXe~BBMZ9|G|{^=tAhfAx@w#gw2x6UeQ{u+MEMX)JK~{`gM_+P z>@;(aN3JjBV4t{xv}+ZVx+bwJKcxv4buicAtMNoWtuzitYy{R^{w8;$P*g&0K2q$w z*ZLEm_+n0VI+(=S$2rHj$9eal=Xtt>55ujsul+Vq(wf;Fj)_YJxZ8~Wlz>{?X)YY| zN8c-mH;~JV#2H+130bp-23Bf}&$Xcn{0Qvi zEaA&Rvi1BKiG`|nF;j4Kxv+IPf_tg!A0t0^)cISg3`o_#85m=YY|Ch?U~7l9z)wHF zhBY}1HG~9H(s_!|K0{*X>kXt)gGQ75M{cJQh@nC_AHJ}*B2r60jX z|CYIW!OSoHjy^()S_&2J{BkD4hAQg!HEbaShul6yEzzp%|wqsW8IaFdN$ah+Q_xL8#WU+?&%@!-f557qiKntFR^I=6=T+0PrkV z+I{vPXRVn9f7JL$Ka>JipvjFXsVII?>^Q8ncSrkf*tc?t8h4T=<|A`_DZYAV);F$k z%bLz^fI_zTah8)|2Yr2WH<3&wpGz8FbvXTk7fMMoe(k5>ZglYe=A}J)U!&Ldk$_zm zGm_6&NIyh+yYI9_vw31n=o2wdXvq3T^wvUx==lOTp#q0TF;;dqu{KpUQ#R)vrkwxz z$u?HcH$UE}lyleW5JpHq*Y~c|@G9h`l}D6_00pmid}V~#_oZn0rTh%_8)hjRBv*0o z%TjHVL`CsPDwqNYo`ep>;=jWajew|fmN3mH_6N^4azNb4Mh_ydlzG|t_07MbCBc|y zn)D*Mq0wY^S~lO5?eBB)a#Y@Dv_7~t2$|z?JkdZVt(2!gV(wb1eeNA}W<6#MEpXEZ zx?jxPS_Sjlj3j6pQYMkrw|#ZlbRIbpc#?!vrQEFp--JNmZTvD^GSNRN_8Mq?Br^Yu5P1SzcbB4E+1t-B}M z$nX3}POreBZ zxQp70nMG)X_=WhD_>KRa$^Hizo5yKFmX4686Ntl{{(vJQ7Pb6K>%`dJC)5X}-SNfF zKip$|HvtWvS%RN5@jJk4@xfOiAl2!WFw`-6Fk57y>*N=(tY#bA5NUiCg3qYAhi%@5 z7eF=SMPw5k))7w0z#QLDT2A6>=-5$~`T}Vg_%ENHe9w)ZV7j?xjZf03OaYa@-^a& zJ!1%}@xj0U8ZQuAALe;<78tKZ1i5`APWy#BFWj{ZuQ|N1`42M+hu^_p%N ztzye?rF?tKkCe6W-7qxiEg0|0CfQm;ZJ#63;~#fc?GDz9Tf5b{1-3It$1$YC7v(Mp zKb-4+t$tnfTT}y+)%h6~#P0*F!B83`1%rY24e`@**Fs#VkgAo}4DfKv||a zhsgg57!_4jg1=mNBTxt)fJwBt77!_cmmBa6R>Mbs-qz;>;ge`l5Fyru`~>e&oSBP zv#-$k94d&5dTu>HJZ27|@+xx!opZp=c5CSHs%mhW4TJ~yV*Af52LHu8iyxB^D4dne zk|!VodjFQ;HG}uCcyC%?mW(f z(B6)_RsGW!HA8mRc+At!e8?4YU&fqy(n(5}-q<#}041Lz&* z$T(%^!GJB7NhU=Z^eb5^nOCO2Oj92L>(miZbFy}S+jGypSfhU_#&W#!L0YuNaqS>h z2>jYx$o)f^|D-4uoU_F&DL>hNR$uNQ`$JA~!E$1Af&))Zsl{-74txQ8@&9bvvD>Jb z-_>4<_zJhzHraN6Mjya>)roZ46oa9_=y*ZTV*4ilDvNuIw;fmPW5U*FSp@Ba%SAmG zW1krvy;*hDQ0te`>$e4U4$onV7vzqIo9`Q`G3yD5!a%Ief&^cniM#nFFbjZi&+M8(XT2E1Kl|gf zLbiIxEB=nT~n*&mt>VkbX5Jj%}6Il+`)(2F^}Acs{v@ zr@^(slYirnH-%K(lSkV;+PB3y8I)50aMph7pw^dzh1>(z+fxb$z{PXLJCB|S*h6Sx zkO&E$2u5O&rpO#3(_seSZjY0@ukz~?Mu8aKmwHv6SFWi@&df?g)?auX8}9z^@fOC3 zM3UxD$~C}+Tn5@0RRUKMQIbChxAWzWd1@OHjemB=I>GNbb*q;7622|(#AXgBc(Fm# z|33lrUiTy4Kp-W3W=xCZUV;0Z;62H<1Jtvo(ck*NYz6hZU^e|NpW4e+nvuKa{r%1q z&sUX*9uyMOf-l9 zmTg?2XdOnu9~PL;HCgF#GVA`Ycm6wX155EcGH@zyR3BSs_yqVgIM~>^MDd*V`29g0 zUR3s&sbUR^D}XJ*4TzS5gNK~%1-S4>J;u6OEKu%s5b9rCQ~072dLyDSV-8y#84%Uq z8l!Onf)n}fKc^4#77fQJtXfjIOSt8V*_vFem53U=O9ExOA{<1Ve+!oC#j_;RmWC}mB@oE9|< zs6kHdo}|uI{QcWpZE~`EuWtk8s#SaYT{!9E3Mt58(V(i)jXly8DpK|RC$i;=?)Oup z${Q23S5EkeIX&YVKb1!1RA`8PId$|~vDZqrw`X+O1KEi;SaTjIMQ(^U5nu2!gxaVP zvZ!te?yY+ROe|%YxM^i7+8tLF&*Q5pGEd#E=x}Ko&+!=6R}-~9${QT|z{ct`%c6sy)6ai)1|tZ_X!WfbhywGObEBv_Fchki^va$Gd{M4S zE?oVH=!b_U6ePSIGlaiSlRNCv@aMM@4DEEbVHwxzG+K}gnXd;aJ<|1iE~Ox^WA18j zLrNR8TmTvyg~KEni7*yf+}`l*jvK-@98=`QghaglTy|yLY<) zTexp==`;10%ou9P-vz33^rrt_G=WsQ*orYNzUx1_-~DA}$7$N%T~>lz})CxtEqH?zsK2f;*>nUIo_;se6^fNy1?Ne3*u7gU2t{Y!}nToeDHY6 zCS1JcJC~A0fS!)Qvw1q2;eDCq>M6B~bZ6TtqA<8iZuzg|Mi38M`0y}>y~-BNE2`3j zyu|%M1DX|Y@|>bT7t?Y!s!I*oq=fOTs3wbV16c4`VKfZSw%-9z_yYe(5I( zxw~Dfcvncfl_Dq2Iu=R=i)2cH;#`@PCg;XQcvL+acoH&CVOBmqQdKk z8jZg-H%BU(T9aLp558HxpvK66FeZDp+S4sB$ra|I>USvqShqGG#O=!_1C;OVXWU)f zL|a?LOS%tz!BDeQLWL71LZzH)HXeFXnQ}b=DM!@sZAbL5znx+?-6hDwrD}H_?$D6? zl*?+JTg3d~)Pb&<_JL`SNFDrWeA!C~JY|!$TyFY*`RAxz$qjfGM_XwVk|?Blg4s6b zE)Q`WjhVAC?W0B{Yzz373^-|ZzbZy^cPIR~ba)+F^{ftsLkUnaUvKhWI3m~K2FY*| z>(+1vBh2&d7C(GC08m(3M+S1OWx)MjCTg!9`63_!$}#d7ol z6~FA253vF|r0x<1SXh;y_-vAuBJ(aJE|i`T4R#@1Ju>%1EOeSx4rJDz5N!u@mK-Pn z^MoIIlh{d{)pU#e#uu1o|2;2FJ(~6#n{mqytUIM7MKKgR)Y^bW7YQt*D38(4|F_ET z!gKbsa{YZvfto58e7{iYVk$f>sTtSUHT&ffD>)xZ3ou=PQj>~z=1FPP^y@Xc&Sq# z{J2AGR;AZU1QL%NXP3(!EXu4jIdYLkzSUG><}d71@HG9b=uL-B4D;DRPU-EX*5eF| z|1CO^U%Y+2=!(1RYyJV(8`0?SphH<<+pe@9SV(tb9kBBv{3k!2EuFeV3Q??BL`2{G zPU#2?FZ47aOzRJpJOh{8+6P|!SwBaiGGl&7a1;>DC0faHO1|qpcPlnggKdOSKaz-S z^nJYi$r*p8nz0CGB45SIEREYIR=pp=ZFMy!1BJf0gIr$hMf@|vSs*}955@`8u;*Co ztjs2mM4@eSr;3z18Ld83_Pl@nH@9y5FHsIXrU~6UWoJu(N{S{gVg~E|h&(M@^uC$V zn9XLKQNiOqgD=HzqrLKa1k>;^69@cwbn+j2vz=h}yQu{=v64!JHQpXsCXg3W4Vs_4 z`CK&X&1cf_6>CdYWVv6zi3}g3TBSFoT|$7JQ;7Wkp2nB?FrE8W$m`c%0XSDo!cZ4s zI(scw1MaPYm_b`b9@uEVa($6cgW9=jI5r~fz0Gk)9ZSQ=Vo)9o$hWYt|EUsv>kz0I z#OJG!)8UI=TOkLXqcn582e;2V``X%ut-Wwe!E<$@g2)2Af2mu=noEBhjmCZMmHDA} z)qa$Fq>b~+F(vA6&8YKi5b5>|1DFha+aJp)IwKr_IW6qs(2Y=?QnVJ2vp~-M-o$ z3Y*2yj@omlYVV>IDEyUJhqN=S72soAI**D|#*xKH-L6{$8PvuW{p01*xDG-%yDr=r z0qJ;ZF}9e3qXBheDKl8v)AQ0Ia>Cq~Khu|#b>&glNIYb{0N8`fao0r?+!4F@nA$0x z(I-Zhd5?~{pl z`QlWOUG(GGJ}^tqmiA<{z^q1C3m5|{fwVYkkO_;mO_P*X8iufgeSab5ofIxFoU?Z>qea?_)_6lNu_gX=j!( zO0B1Cz7OYEY~lRD33_W49!9iLL-?7M2}>)duJ_$mqD`dP==PCP5)2Ly@WmDW0>Jzv%GO)H5V9T%K}I)1kb?DU%o#Ytn_n1vP;e;btE z;84sbK|nZ0)`iQlM&3=#^xXIuNc!aD*9_O_bm+X`W*A;u3Ah4}7GNf>aN~{%-<+6b z?(l??8XE6x3U8QI3sX*iM<1urPyG+zLE+skktb5_ZkgE*YJ!C#K@@L?kGkN`{Gik5 zS{W5p7BeV?RU>IkL$0i;KNm5y>8Swv& zgjTQZA%sJ(6_;<+w^z~A@|A8|{|Od8X8*wfJARq+kDqEwT?NOn^vW}4B@}AG4t80O z_NpNaN{2%PB@n!;RfYeMY>)<{+nNM6w8&-39&*X467Ef9BnD%RQIJV6?bBjDjBycpP@fTs?sK1dQyxu7d^?Z{!n4<{~J7tiB795b>s zDiUR|c%HXBbbezcgCwH1j3sU(pn$(eg9;FoNQElXf)x#*aJx__}QS6Gxig>bTw*P>gZ{%Up|CTWr@qc(a3%0nnCQ9QH9D-X2mf#M--QAtW zp$QP&-Q9w_ySuwXa0u=$jk`?e&NuT1cuujayU(s#>s@F9`S`qy8EJ!9LjX}X*!XpJ zJTXf&`|7|fU+(?2$dlNeW^Noh&0o?ANRa|(Jk0(l*$L??k(>92{!)x9fT}L#>{8Ht z2oPzEo-knj#Q3i9nKz}4V%O0I5}88Aa#fC@m^S_HLWgG<;XEu!=!l#?Xi=DlMIQ;z z9qMG~YUj=*HfnVTk%Ro2GfgXyo4%7uS5i{2SsZO*o)2=G377?&fxkj>!H{z5ffv5k zljf;eeabATNXk>rZY9_hIqLDCEhxcegnY+{_YTSl*_Z7)`Hk7_Bsl=pJnwxUfX1IP z*Gc66QAV9(lbn#!0_XW`PYWEZnYBpTW#MKBe#7O1bUNz21W;Vx?@J4A0Q?6L7Q&dn zy%Yo@fCbcxt`iVBs`lt}2<{cFB_$Rf8hlGwOwE?NbcVFDMJQ&gy-fXGX!p@d!{2sL z?E|9q_z~`JOgb2}A^J<4N=&lN3_c>JCGpQIMpIsgNkfFDlObbhr|U!=IX3ka&0iMm zk7TgoWSfpYJ1MDkSDR0y*y;bX!bj$uNP&vI$=eJ>p(hsLPmu1yp2fWi{&zkn<5go> zo}}7Hx?m*}V_{+O0vjiL4D^}+;mXnnF5&K^_jJ;b(g@t>I;_)`yF~#kI%!p<{l%<> zOnOpT-?+G<<^0mdyf*Fyj3!l|9!*473J&g!dWlpMyRm{s4RGArRWpOgG*lDufHgk+=!s8$kdI z1Yb@YpDw@#vCrM%hHat-Kry%fVHT}0xGN7N^XT6?!9n#6dBj~%XnOWBnc~P^Hhw>A~JW7)xGyuE_fI0ZGrPt@hl`Qjgb78dpwCP}}9<#$iL6kFx z6}*M_IP}G4DvK{6CF(fxTA5kahcD=vWt^lzcuo5B&w{}#AZt9G&{E2MM8DJh?h(*+ z6)ECoLdkgg%6H+=&u2jGBhdo;i=z$~@e&s9JsSdR@-_;VxLcwpg+FBP+s9RXzgDE8 z5b;(5WYG*&4ZW3jiRt!{!ISmW*lBh%;>QZ;`K61qM;9FUtF~Ge)k<{1v025hXi%c? zn#lab%R;ZHtXvnhD+f*lt5X6f#fC7l&5+ zoVWqiMf@cX2|i`D!z2ZACL@f3G9Z@t-VOahHFWByhcQ}Pswv!+9E&uJc2DJ3UEx_7 zz~{N#(&cGYpZ8=@Pns`|U^Z5BUEOv6FFa$!dB|y8sTX_Zuj_Z+SvTz}fa^AFL<<}? zgZX1qTr}`}%1z90>MFYi?jD~f9>8F(@bvUl%zWR#TBz3pm>?3%)0WwQYy|QnWq9J< z<81XL8Ps`M3#mCR;7Y!#SLJjf2&d2ClR{0`+!hKFTb~%w`i8lze)<VPPd5s+1P2d!jTbJBRo1z0)Mr1_=S6_{1M!@`C%U zTKp>vzo3sPJQx|@9>vRvo%J0&F_-q^Oz6Qq{^0QA&Y@R)XvM`tvn9ov!n6B|F%EqzJ_4A`ysx(%*XvKd>bi(RB1zdj=D9;m>GCN|)hAGYv^224X zFD44vGab7P)#m)ECwFJi|3lz|!^>(6+UTUF6_Ag9flolJf6&djpwG1WdX{R|@ge_B zFj=$;)lmFsN72vyVPZ2(x+o#2h259DP0P9Gau*syl7jp5sUq|k`dk43@3pddgFCb? zX0`&S+}OAfKg^zT4p|0?E4Mw-cSdD;+35K^M;Tisx4U@zf8`#+yvCM`B(p@bbmFo<=Uo)VzNFL!;+3$My9VJf&zw|-*QAVrmtLLu6{^`3!Ep8Jw;A;p>`y@hNM|`%KF7L+*1v*IcgS zJi_yYP{<+^c>-B*zYg~UjKhY;td~8uWhSa<1*f4=`iObMm>3lyIA^G{`S+&lyU{eo z!udM^O>lNQwIN7p;31Zudgj3)N3Jw^U%(sniwjhPVxG#JzZzo%8B_x#%KnH*wAz7) zaej-{?3T*TpsmNyij&o35^-cO$*>%aQcO^me`|w`rs$OU&nT+F`!J-)`rx+eq6?|0 z1nV=d4m2XB%ifc+lL)X#ol(rp-0finvmv_S;MuD+yC-5yS5GX|8& zj%oZ82U+&TA=|A%K`|kByiW{VYdt%nsbO?l((Y(d9G3%F3}9{pxCe@9r7|5CMT}YU z99q~R`E{b-2AU1m*uK&7lskfVbP9hZ`5+w@gq)NzRXG>pz~BWBRhLE~%n56mVNhpF z%j5Z3#+mh_3kV+IGi)Gt(QzYh&GQ?MEf)NvH5K44|IA0B!AxZp9t$;=SuSZEwZC2*5#WGw%0=%7Wyw zc@Iqcn%NR9rMwwt6Ar-WyVL>7^P=DG?`5X=R62}(sIHZFXSk5ai4YE#@l2~P3fg*7 zCj~mlUJ8N)i}7=x46sW|N?PQnz_VdUIO{w|_=$ZD;)OTfYXUTfpNEvwExIE%kK1TI zE$XsM8u;;3JI%0gC?vNp*TL5>%+c*x+ON&kX#j|yk9cgAg~g$t9lx!9 zrw5so>rQV2EJxW=JNA=w(o&Az10Ha9h-*_`_G0*-Es$r}m@%>{_TxE(#2xc=5?A%_ zeMnN#6CBt15v;!(zgCHxFaCB8F}ty3=ek6Is=k9^HIB35Gv1`?WU&Q>F|f{q$ic$eYYAs!{gOQLqn*5K1Mj1UCK{0et(( z#>gbKP|&v-e2yuyhi#FjABEo-W&h-O|zeMx9i@~sJwc6o#9Q2q29 zyZ=^*Nw1K7?Awq{j|s!S*&ruh~BuYNTYIBQ&ZdB zHfC6C=J{*xIuy}kOWp9)Hh&gB0edj39ycWn(`J;^=lI+HX*w6yxcromA6QvUn8jzJ zId{}*j$it|RTWh^nHXZW75&}b&}kQV>s&pIKf)e-%@wO@f{6b-@&-jsxsCY>-JL(7M z)rFI^TB_npWh++XsAvCOH|D3p`e6O2W)SB%c(N>l_|}g4alvTlVMnk0{7_;EF9<&o zCB))bJG}a(l~WrwElFv_fHKv=U}T`L7d7JhYZbOPn>kuq?N<56)Q&`zdFt9DGu&QB zJKFza0lc=9HyM2_R2>Abx^^eXD0`46!w5mP6UQ)9mJTE3AbARaB&R%$o`aT575G3A z$C|vCB@Pf#yw;-B_T58uqGfqg*f_1dP{$VxvUA<33PK5vUOEr0Iy zZJ^5!kgUs@(n{yF<b8sf@k7d&!kho z(7Zs4Ag5!tA^P))V(=bD7@kxr>bpp>w6|6J21&a2^@fW!1pV|^;0q-(k3f{&nq)(a zX&GO~ykGF>-@r?ruS36jo9g~=*Hwd4S`DBDx-eM0JFfR=s}>JlMsBdH2&%Lh{Mvib zE_T3Cw_i+fz70U?ojjXtMC4SAW?FO_9UsA=|H z&EF=WA06T3Y0%m$LVBWnWPR1i7#YIQIvN_TceY z_wUC~n~B4Ge%QUb`v%~XD0tUPia68M9mi^C{t#OwiIhrqW5i8Gj`w+%MG}NQ{bwdJ zQh3&-a&K0-;!$-ws*Oc7AZbtzAS-A(5oed2l!rWlIanWxdFl4T&C!?VUU8y0?j{y^ ztVCzcAvyJB$dMD1yN)zd7Yb;#+%@*OootBcO6z)R6z~=(Y`v1;O?0$blx<-RbHrzo z_0?U9PQVZk_JvQtw?935p-7G@DA@o@> z#QdN(>jUgb$Li$XCD#R3Vlb5VYW1Sm+Fv^#uzS#cpf~N^6VIOPsUVDGT}XLU8_zMG z!#aWj^j+jSrK5)i zmMG-_ch20mQclaswUXubWzo4mFGeR_iMCO^Z9a#%7B7eEqZaC>uO<&gfmI#*Zzu|S z#XU?CmYWQ~MM&9iCg+|5tD#a?3Awh-5VI}5j`_Sjqn60G+|s^C0EJk{jKzsUvt$!_ zUUDbsvE6mwpF>>5Z*mqTws^@&Sw;x@)55j4yTw&9QL^{-6*VI9eGfx6_k3yLZNeo|_ ztxjf2i+Gka;T=ONmQ5iJZ6|q47De9p50N$}T|sd^0$Akm*86csxv6ScNp1KgkqwN0 zRfH7=g0UXP4J2nx zmo#=HF2|9?kc2ilDU7d=IM7+@z=mnbd*%M}YAgo#B)isls0%GrCO-b-VV$=v@^NLw zGT^n$R*1l06xw*x(0~FoigO9{Op%`}xrnb^f1~SLIkz-Hk?3{kh7C`1aJ}{~->IuwTS{0% zS8Dgu^eY(^3~fBY*Eyn50ih;{hUU-`&RlRh0kjg%XKd`_zdH2agZV;n9oB4^b$kRu z#HDU^VAn4IM&K(NYXqtB*D%sOS~|;vI$s7$K45PZr9>qJB#U~;gi*FzKHZWB!Ef<{ zEi#~E<#(JTqtm~lc-s6LG|Jb)6eO(`M1PR$r2r&SyKGWAdus9<_`27c4Jy%S?%Gp( z4_@W4Fg8Yf**Cz+Da%#`yWg$63=MOOA9~fj@@*5vF(($Z zN71E`{^6+2OAqzBq`bgu3jq{`a@Q?Cc+{YH@o#p634`f_`Ge(8&iqiRGyFp1Z|nM8 zK~{Mm%hxKHX0IuNZGAC@y7q?|{gi&PPmM7@yd@|_J0T!}mGV!h>F=OymwrAz2}jJA zOHKi2l9k78p?OiB8~DRy2PQFmD*^arXFC|DIQgSEpUsj#HBb!_*J^dJ!Zwg8S-Zs# zAGi98KSCIETP`su94m*ZMe1Pt7NKZ0sxls9z+T}^(`>sJ>*f#N!ZBlDcFjJzyKvqM zSnqCYu6Fp@lOy9%sp)y0`1b)c!_%kbi9%Dj)|kX0jgyC}B*;+AR`hbrw#@TDkG^fd zs(f1gWNMH*GvRltgu4{vNT0m|gO`~}?A8PMqKuz@8`h8oMfgxRkrk2Q;2aC`uVvF4 zl}aqlnpTiRF6R@YlgmqsD~qJ?BGlsTo=GvMBnNfh_?FX0Qd1*S_J51_6;U{rVb1`C{#jR3!6g|2sH{YRF6N*&2=9K8Ihui|RknV6fpE_(CeTha|9x|+? zO1*Teb($|!fMHTt)SagyFBFL{f516eUIh*)pBeGv>2q)Zgy;Bl&_LRAMxM1|i|L39@=e?>bR!OKo)ynRH0 zCbI+5HYQzD4DZ;aB(cUjEx{@o2oU1YD`Drq|E_gh)Z^l05>HnaT;YteUg`6guMlx; zFwb9YiaT-NH9w(YWqC`TKR3|m(n-jSqFk?}alc;9o6(#C+*kvT{A;5hZcnh|WD~-u zbdg5Bp$5DE;BTxBfYgDOQBkmPN5Gm(tvGAk(sxcZ-xqUu&91V&t(;ZV&a#W|PN1Jw zL*l(2A=kRB3Nz34x|fJnl@KxZ+I37~o;o^dzv#kMz_G=N3HFg*=tS;%tC|f+iZOy$ z2K%iVAK;$M&~UxkG>8`|dUryY2K3drHJI}VRC6>@QGV*s4s*WXwK|+jeL*=;PObbt ziPBWhLouws28Uosini}J4(UMHOW;AKL)q75Er-9`1NIeKKsPbvPB%3QyG88IP`hU# zkK!OqLv5@s%Bo7y1RBYq#%azn1t-+TzBc(F(|hPZom!|^+a+M(YYn5Bp5F*pgiJQE zR~#j*E65jH$s|Ef}=mtFEvuN&dL0u|i*8UHn8D1iiMuXXx6B z`ollk%PH(KpHBC2dt?G=yjrjDdYS0PmNm$o)9!c@MYKQ8>CyaqB7ek8$;r7ihxey6mBh|5h;Pe)D*^w&P8!c(B)w*q2!fS-j zDVr;cRKqOG(+7yIdFhj)tjC%|9NO0x^{`u+;!M3bmgM~GIEd32(F}rI_}bdq1nO2U z4>&uDk=*3bq-^28M8AN-NcmUGI7x=;#`I7`MSl29m2&cMS%?X)Ro3ihPZh+%m0>2o zUXEcZoqn0!A5#~xV-$t;%jyM6{O#w$gprzH! z_n$M!o&Kb%imsU8w=D3dhP!{A>^nVopAA;=#!*h9;#;dmE^xEqQ406ZUE@aH9onht z!ME;HC9h_taajrFTHh*9asoVvIm0pHS7LG$>pY^H*x5m@2k{1o(E;Nw*bX%FTG(Lc ziETPpd)AQpqaWfjneuY0?LMGk_u*2Y&!V26)Tw-r=cfl@sRLdXEQocfU9qRY!-sU; zYf_LKSHw>#oG*=20rC$yW^yEd^2ASeAvb;mNEv*ZeKKRq47Sa4_d!m4tOk}@Rpq-~ zgezk?^~A`A+`w}?v0^?7y-|QSX6^%5Xu zzE8Ar?|0v*whkpef2d`ftWqn`aJfE2#DInH;~Sd%Tx_EXU8%w#-Yh^V<4< zYrN|{7~UO$HLHv7!CnFT{v`BwVzZpVH;)$I%p&vAX-;hjq+3TS_hK?^pTyDq*6Gkc z!b><~RFPCma_JoBX#b=tMi3e@;^#sTXQgJif-++aw6yJNapK6NSBtZSGVFHjLOOF0 zCiPWP{0`Wh=q0D*`=uRfaEW53%FC+aiE5_m%W5IUtrQ>kK{$ZC5_;?FU#+WFR6^8>c-yK$9 zgc=aKFH{0ITOkoe*%)jlp6(b3?o}S=X=xRKRtI7h?>6K>q7`b3rXtg=D=?EZbbO&G z*mfvYmT#h%boMdMP$a}}`-gFgl+Bi+AHkxhwkx%uhW;3A zCn@SoHv5GWwH1y%qnGKg1;wbFtdN?$TrII70irQbPP5m=V14(ZT*S&^E0Hg$40kb; zS+HO^%Vmb!^KtRi7Pl`cXkXytX^QV4#wy-MKi?c;{-38%J6iT(d6^>2IpI0F=yP|y|RbAe37hv!XP_{A_X(D~K;4>4n z{#iuV=pLmxzJTij*FxgfWbEeZ&H8wem}xdD8)^CHJ)NwWXVP9WV0@eFtR$ zSYu@y#JxANs*$ByTB*@PMZx*YKYqA5nh^sS#_!21ZrG3}m?6scfSD|&V^<~1FRUI` zSUDm&7_bgwu~6jf_1dlvRjW4ba;)vBe*K21J4^>(#PFNUCJBBZEH9D?UuU=2LOw6r zfphyN;SkL0NHVQxVdgA@w$Fk|l3(&r2o+p>097trvd4&nz<c^_91YRz0nou z&bcMQIIc3;c@KYW5m0{cr24sA5?*Lnx)39bn+;Ky9#CrZPl^NK5gcbhp5)~FN8I6UY zhnNiKv}_JE;OF?&(<1?URI*-8b>h-WaV*ZO$rMt2=ujI`b*+|^2{Unhq0R?pQTh?x zX^$>MI&;)B)JJFbON_Zv_bUw6a((fv^)Fy5@|OX>492(BjFhbgDKTTL;LFJVE^;7) zX=s}V0Ch|G=UagD>$*@KmAXd3I*`Y}zzb-}9j7*jE$KnKVd7$Lnm-)#XVuUL@Sz$Z z6clLIlT2EL*?n)i%#>1!V1HDvwcu}==fnscdQ725{5W6|3 z>3NEZ{%Uca8?onxQ2z*$%-4P|y$sv%h?~7p^lawWr2y*nV7+_6(TnSEI#KeLkkPWu< z(_QyG8BmtJ3>h2l*S{ws-ke9?5mC6ocEd^QHoG%mZ7=A&6v)`o3j|| zp}RO&zgdr)=VNty+zz2^eGQP=I6Ihz%QGYnWQ%>#g80C1=#2&&rs>Tq?!xEAyGGHA za3CO}9OX%Eu&O`(rW>+T5K8z^Qq#Mm-{HggX2Z47I}9He-4v*LvDxiY8&aEvZ3#SgZ9K6O)cRSKb300{Rc5_V2Iwq@NtXgXg)NK{iKa|x(1QW`1dvQ}vTO+KUe_XyXWA4SS2^dCNWXv-$YyH<+lySCp2G8{Rl`egF zhEYtzRq03(MxQ3WPkmJGA7|sCK{?T8$~@PwQsYyPFy+HI$*}wCA9t1g)mEyk|I?Zl zO)VIUKS5J&$iUN5r`X=z>tdD^HnRp**?sBr7rI`MwOmYl)q6LJ6D~eOcH^2v;_3cI zp>4HX_wV>`S*D-#fY&l(1ZU#1{fOgcLusu9PP~x=dYvX-H?Wz$E3kn^>dPKFatf_d zr1!;6QaFJfY_YzSH69c1?X-w8Vfc?iKsECwk9~^ZIz@R~Im$c^5UVjtzoc^B49K%H zv9HvDfLEmNb&l3yfr-_F{9HL-8HV^5hR%+J2u!082-^cd{gUVzS@-;&MZav}p1!er>g8X94ePa$8yPSN4r zF+YPT*ALCagk=aLQ0n~?%_gD@&1qNo)s(X*V8Q z#E_N3rw?j>W$^tYezJvRd#~i9E{lAvsjl}Cl!}X0jsqYKy=7@TWsGNb&xbwgKgM(QiW}WkyFLDx+VJQZ z*3xtPV9hrhucOqt(7PbU{guB`1dl0<$UX|Se5p_u3n4Q?1$trlo{mB$2!;4T*=|e2 zCrAL04cUSk>fGKi)oC)7N?Is%>+T-cWhR)6EV43Vd$@5sa69_ZUDr`huDUp?Pp@KsmuVUJ8rE}(L5y|Kk?BvDoyw&n* ziAp^VrJM@dlru{&T&ehfW2`GKRGw%$oxjN)wA}!Gn+RYsZ>iwfEp#Z-g+C#uZSA;{>97C&71*xLvf4CO}L zX0ug8Zr^EcNBzAwagmwQh0Ks)GmO(5vf6>i%OS%}$hpN%xz z=lu!Dh6V9n$V-dsH-I@$Mne7@uhog`(Bvks{(3^{2 zE~MVYxdg1zEq(~R4|uYN72_B_Gu6K{2Q2e(t#ZY;ipRK4QJo`5_xdH~;=>-d46iKw z)0|gOPKCwXD(j`FPcM)mC#nuFd}>Q&4hx+tf)A{@4nPQK7V-cH(@~1<9{g|Sc*Aea zS+#-ZNrIq2hfQ$aSv_TYfEOAuK7Pea>qfvq1ckv5nM|9@eg_;257Lb(x{XWe>tI!) z+?R4WPLjHY=;;YP_a1N{9p6I(>%tdgR{JB%DnWqF9M1-g_`L94nok&I$tL&`2rVVS zCwT{Sp=I2LnO7sd>2mfbB&*in_DpxK;#CpGv_b}jgZ+!jA>DIq2U#6w`8`Ll9t=2Z2{;bhE!-!ed* zn|H7dqd!M&KrS8Cg9>|IOgS6CSlPn`AfOTqOLNMpclj;lmC4}C;8#G2gnF>4Kb!i| zkbe_O_p`O?j<=p%ubWSHvKxG|7_wA+;)if4i6OuIiCh$e*J+SCr^EiMvyR~MQ7t=S z>|vbgeb6_o5n_popYWIRm5wjS_WA@@>yuyiC)^2aEbU?sN0Mou9v(2;hIY=0ONf?- zX^}1ABEua1g##m}>h@oa3ymikI?7(a=mt%&RLGD3Xfx!~0i=9~%r`Pt6n_I@bOxq> z4K`y*?*%E5psHon6G8+xuz_k?FL@ zN7iC}O)h6Uar@UKhXP3_tF!1{7%wt0PP?Wl0=q;;OwwC=BtmBu2~LLfFYBpV^p6k3 z0aFC#zb~ogaSOh9nHXF9NH}yYscl(sbBC6D(uC55&4F>dAlI&r{Kn?ELXx#is~j%7 zw0V%^avZGtpQG1YL6DPUcAa;DOHTDpwCZXYl+(V1Qzf^?`oEtc_4FJLWu4MV7St3~ zohu|~Cdh9#(QMd6#C$!?7iD8*u#vOa>GLFNB;5ys9A8XlklJJr zfwd+`jD1oI>Yujm84G6GotS)wDOEvn=}Ef z=1jdtqBRXFS6V9&Z7%!yUs=d$LTlSQr|cW`beABny-X_1K=3>gpH2|kF$Q!5pA+%6 ziIJ1UzfJAwMYj-OXb0tXWV;Rfpjae-gL%ij-1megriV<9aNnQxf>@0>R66G$oEdw4 zHx650x9A}fkm6(^W@Q5_cH(aWW)6VIvxTH(U(p?x=Y=Hi2{K_%nks;_jj1d0;6nCN zo9^k92+J!M4*s1_9sxQ8hxKOHy5r;5@ZK8?xD=m2{qZz{^ylr(x1#8saJY#|Ar`h= z)GeEI9PMw$=)-Q=oyqct4KR-Gpb%aMYk5RD_7u7mz9CCgs|p-x5pk#!^f=}L2;cmR z1)7*^1_=oYVDP8Hj^q32gPEFqE~Lb>$?1Wo-&42K$ED-d&)QQKuh`}BGOADckPiD- z)43uBb8cI{e$_@JmXZ8-0<&ASC|r4<(&XdZp}FOpD$lZW^p)xo`h_B(j>3Dy`~lEo zMo{9OH8-iXyibr^p@?BC893uR@Ef^@FU%(hbF4xkw zc0#PcwuEkIb?A$srQM{%cDMUd>3%fPuHRcC=)JJNO(OxCz8^+^X56(=^p(j7@xUp5Abxx-ob1T!{?zqV)hvg~|+-wDH+pj-u$swqw_ zZ@_>Ho=h0jzx4HQl-aWnvp9H)#02^91dUD`S{^KODc3Wn*LamdYp%Ke-0yyJQ|Nwm z#$2@;5MV@8p0cz!0TAa89{$cq6cAXR=XqtWEER!W<|)?Pu^9{n#`9qRY2xV4Dd z6u7GdPs!#ySO-e~a4_e(&aSZ4eKL*xR9KoTFu6fEu&r#SeEO;bo!`=nGE8uNPs+NY zNj<&%Tnsu7b|%n5gzc*~#$*G|SBDvGXnmHVPifYT3P3NizKFM%hh$+L+nZLb@f#1% zd~j{p+08CtlBC#>V*6L6$mrs3Q>pJO1*$%7(07>4jp)L~|hZwJ~v!c~LNeL^r+ZC&p# zh+WnU8#pr6)--*8xJ~gpK2Gn`K;Og;L2J;BLqA%pdQPV#;RI2V#-q%up*FUz=(By% z-by+?iU8Y?^6+P(EhItaebnp#Q{-Fl@`LWQP-99Tjh27@B72xjvnz@R2O!_IAe3Ih z<(0gwgcAUZ969zg?bsPL!ce9365@eh!8-~gaFR#Hkxdv^1^0`uNe7NP0^A9BLO~EU zJrYnd)}YEz5h~(}PiEpimT|)Z6bXS2D7iv7IXx29ld0Ar8PG+rbVFDhzb-d-eMkP1 z_PGh38Oi~k5P3aXNy*Me|6&`wx*pL3%0lO>M?JEm7~Zp@abBCiIO)L6_*)47=jtoT z_@6=1bQ4r&-l2S7e75LHcVLg49;0#7wto(^v^jRW-0HP0$ufpK2#24H+!KV9z$VZ- z*E}YcO`ZOAe%F;MzhsGY#NwVKQPgA)xl`F`o1&3aW2PUJm8KLEvYG=PJ8~EYVpDg(b-WIdGgovSIXwd`TT=hAPhaxlY zJ6$tJUrK%mQ$xV++fh%oV@A+CO1-WB{&nVBWJ zPp`>c3*(SfHu*1f(O*dk+;Sfnbn6ybkic=iO&8;VIbZ;$e8wp12~C8B7#sNt21;0x zs0#1cfqTY(u4bg-v(ne99_-1%Z&QG8He$Wg7fSb}C8gz+gc|DV_w*<2XxH_+Rn3$- zHL_;TV=lSoEAo|T;2$Y{l+_x>fun>2_VbyjtJ$AP#9sJO1BFah1_}pz8eUkk zFFfMfK5thsK0nVW8wryLt3DgsRUup&{NHO>IAIl)5VKZEm%?d+8-wHYt@GF_y5RDYIhqS8y=?&h26 zV2t6RIruwh(Ac!qq^7zvauDK=RDP>UmND@x!E`DvtG|Ayz-IUji*|%`C6!|F!_tX! zLUdoOYx#-fZeTI5A>sYgq6o`-vOBVI;S`wjx{u?&K8L~NHb(u$0V>!t6C?4pjB(-l z)nuak`m+_&u&qaRzXYm5P76FT-eu^YE9ChX;2;|4*4%1mO>$0}Qc(k6?0+rCR{WAD zG?Qnc-0+=X;mz^WZw;H6Nj9W?`WW+NFu(n)zBqwRB(Hh*`|~O7SK?HU{kakQv6|)6 ztk%HjNfy}%#IC8ieIIRTZseA!ImL599T~?j0DMnTJ$SGRlKeMnAcgPu@O|%-@Fh;c z-^G$LLn!Jy<7csuuTOU_uWFsePG8PGZ= z<+T=pp%ioRwC-Yv{IKMjoay{mokC@mykOyNP$GNko zP9)qWM|0fNUq_PtXZ?zXMbq`m{M*Lp!M^`+oTv6Rk#u*}FKO84;lX0#ZK-9DYK?Km zweb<}Ct!o8Nr8ze*jhk3=P|56^pV&7(BBzyuRKQy&R0?nfF7epv%|q!=}2Jk0e2M{ z0VlHgK_8x&PJZZOlTMdstFtwWxN1;PP^S)Qpxrie%FL@9dtkkaIB^(+Qw%P*+ZY}V z$kEW`$9H{GHYQ9VSCo_8^l$y&@EByQg}&`S!?K|%SGgTIvQ(|*@)-x>@dTgLKy*mAb z!?3+|#G~`bUR~#Qqi1Lb1#UUKVZL&-GI@2e0%vpgp0NvVa9s^i%;81QW;Ho_2eU`w zE706gnnm~i0Bgo7xf`+?UHhUF4~U}Tu${b#kah-3SbSn~XW-vcW>gBLyuztd6u>*6quSl! zKLt>00dOeKmU*;L1TTV?vo=AsEAec4f_isf|JQSQmh04e;frE7d0sK~NeWpM%hhxb zqo**Y<72-y&6p8V(oO!V-$62@l`E)EnU~rBMs5%ey>A!>6In_ zF-I~Dc1O1=pooL!@WdWDNb=XrrucB9Ux#1#(eAS;8HzNJa?h^M7pS1-_oL)RrI)Yw z3K=x@vECv*N2{M5#c4Qe+t;y~Rt2LVMkKJNnzoU)(CDz-Aq|sf#c8f!92)WUC*L{6 zMSg&zF-|qA^{rL6!hXsm)g;bZvY+~25eo>YTNY!Lta4+}M-+*z=Gk3{To3uY3SRlT z3h|O{)NE4pyPYmp=8s5lUOUMS_0>+ONC2uD6x%@zM_QNs$qd||wbStJV<(EDCzXX6 zwKfhsOk(2RaFAAbW$Ohj(T*m}wa5`PBpfCYs(z_1t+*`T0?7)TgYohbM19-X0YYjx z@*BJ2LunDl1NIohmGg%`F~=S#sJc%;%<$hEpLxpCTTH(SVGI*ad=I{vjuX)+;fnm@ zQ|w?4V=;~g_=;B;6|N;Ip{TJR`afJ!b43}a0r=6;V?s5I)Kis2O-$hl;JWSUW=llC zf*cnm!x{3nq3)N33}Jn$<+zE(D!&s|=5QBRLq9o=^FQE6Zb*szVuHL4_-%ZsecLPt z$Pz1i&?g~p=9qeY^W!oZDYSl$hIjb+^&q-9H)9x~Ao`D(!1c_Ofj*WU+BQ2*K#mp0 zr=*L7ax0?n4DENg=PiTEHz(T))kp!Lb*XDe_t7ym9EDX@!2Q06m_lVJPh-HWl zuzPN3S)J}09qgFqGuewcQ>f`~Ep4^f+VV3%p+qCmS|2BGfAjVyTvc)xEEdDdX;V0z z#)i;ypdE@0z1b413~L|nbT^AygX%N~iHMqs%1%v1>YsV}9D?!G2bUxL>V@OZfkT2* z1|&On3O5>f?->EH$p&|@j=v(OJel#pGmK{??*-hsr_xA?=S*z#uVC7Gf82z?DX+<8 zY_|i8Ro`7VmN1fV_k6;|yk>FsMNZW-CR!-&0M-r)lJE}xRkW*Q$LA_yg$C>1*Sa|T zks)gq%xDgNFH(k-RW>9K?02ChYFdS0n4`a_`1rDK=S_BxS*7O2=;q8l4{mL5AWUER z{G`%tMTG~%d0(bQlB~v@KKzZitC$W^p)b&G-G2I&M86#Ib`VcAg=}*pudVoLFrfUh zt^lgR+0^Td9+8%k8+1QtjZx2R@etnP4$DVkv@rh~?iWlWg+bjM2abi09jnTzY<78l zo30@XQ{QAJ4JX}FfWyKM~pM@WammHEqzZ&?bJ=Q=) z=8n4eux`}WB!fYU@s-Yh`4`xzat`wj38&G6va*;z`SqBd9W>j_m+!Y8FU%$&xBsin zoVat;Lqh!Pc2krf^69!vA!gurv+GVn^x0B?{t3M}LAPo2omZt6D(8i&yFeV^&ur#J z$tt7r+n1L250g@A@!zMsZusccrtx~BrNyDc(cqYX((M>@b zl30`%|9z}pZbTt_==@qMMJ&{Py_xU><%gQnC6$MDmraL)R&q_E<7>J&Koxhs@l!P( z{9T{z3;%1AKqB3pB%h>fnv*7YU&TwV|8x=#J{o0kIsZt9mqz;wxT@%b+SpkpxUe>SMt(}`&+UNY(P!m0GAh?nx#Z?6Md)fT;SIw+y z;_l(>%8IL?j_T~$PK4b%gbs^-+G78@9cR1m@|sHiEBr=krMR?4)VV+}Euh-Zzu5uw zaY`{IOKNUb{!{O2^IGOmCDdLEWwrJpB=-1kC7^!(V%_$x0A}Nnq;j-M9rgymeMlXe ze40*W7WB{VyIw}O!Ea+JDX$tZ-eCtHNsJDA7B?NbZMei>`Ht4qTaCQBIQqkIJh7qI z*_}OI@0z&I-+FLN+K}GEHLypCHDiQcHDU;P$Zg!)VVeGeJMA^27SeJx_tbw^HBOyk)7=A#(n+?uIPe~~ky zF~#)qsrkzeJTHgLZ=HjqTsuaMb~>_8tiS0t9gl_mB0V(P8Oa*Y-CYK)^)Pn+uz!Jl zH9z`grMF?d?o%9f+q5#oeF?9w2F~tiaF@CLDaS4e!}qqu|lGkU3n8W-N(h>T|=U9w^%ObQ%as57yAiS zOguK0eNOxvcxCGWW7GPNU3ZNUXEFP00%0rp>o+z!dbUW|b!uM&H|3~Dr`mj6*=F?z zH-Qz=Zhii&ahy=8iEGp07ydxPmi*-x#mJ4{9-0D7`gn@&AV@bDV^HGPx!W5oxT%rq zO%dngCWF4=nRIJ(%MA?rS$)m`uNdjI%w(#(p2jbLr=E3*y~%;!B<`L(v)!6nQFFI( zymN|gbxE0Jg8`9$kUEyxIfCZgSBbEY2}Vp&7E>h}CS2vdF^O&{AjP`()pruMKb^Hb z)xriC52+Hiz@+^mpLPQU$vt%m_^$1Ts~;88xAMZcukLuMQ#rz2{wp)J&gzmdhgBx6 z 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

hvuX{c2)c1i&#C09&mkRc<@X>|o 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�+Ji_kE-{wS_gB_zwKGSOT%7P0 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 IgEz?k0Em2Dpa1{> literal 0 HcmV?d00001 diff --git a/packages/interface-content-routing/img/badge.svg b/packages/interface-content-routing/img/badge.svg new file mode 100644 index 0000000000..43b16d1579 --- /dev/null +++ b/packages/interface-content-routing/img/badge.svg @@ -0,0 +1,25 @@ + + + + badge + Created with Sketch. + + + + + + + + + + Content Routin + g + + + Compatibl + e + + + + + \ No newline at end of file diff --git a/packages/interface-content-routing/package.json b/packages/interface-content-routing/package.json new file mode 100644 index 0000000000..56f6a2983a --- /dev/null +++ b/packages/interface-content-routing/package.json @@ -0,0 +1,141 @@ +{ + "name": "@libp2p/interface-content-routing", + "version": "2.1.1", + "description": "Content routing interface for libp2p", + "license": "Apache-2.0 OR MIT", + "homepage": "https://github.com/libp2p/js-libp2p/tree/master/packages/interface-content-routing#readme", + "repository": { + "type": "git", + "url": "git+https://github.com/libp2p/js-libp2p.git" + }, + "bugs": { + "url": "https://github.com/libp2p/js-libp2p/issues" + }, + "keywords": [ + "interface", + "libp2p" + ], + "type": "module", + "types": "./dist/src/index.d.ts", + "files": [ + "src", + "dist", + "!dist/test", + "!**/*.tsbuildinfo" + ], + "exports": { + ".": { + "types": "./dist/src/index.d.ts", + "import": "./dist/src/index.js" + } + }, + "eslintConfig": { + "extends": "ipfs", + "parserOptions": { + "sourceType": "module" + } + }, + "release": { + "branches": [ + "master" + ], + "plugins": [ + [ + "@semantic-release/commit-analyzer", + { + "preset": "conventionalcommits", + "releaseRules": [ + { + "breaking": true, + "release": "major" + }, + { + "revert": true, + "release": "patch" + }, + { + "type": "feat", + "release": "minor" + }, + { + "type": "fix", + "release": "patch" + }, + { + "type": "docs", + "release": "patch" + }, + { + "type": "test", + "release": "patch" + }, + { + "type": "deps", + "release": "patch" + }, + { + "scope": "no-release", + "release": false + } + ] + } + ], + [ + "@semantic-release/release-notes-generator", + { + "preset": "conventionalcommits", + "presetConfig": { + "types": [ + { + "type": "feat", + "section": "Features" + }, + { + "type": "fix", + "section": "Bug Fixes" + }, + { + "type": "chore", + "section": "Trivial Changes" + }, + { + "type": "docs", + "section": "Documentation" + }, + { + "type": "deps", + "section": "Dependencies" + }, + { + "type": "test", + "section": "Tests" + } + ] + } + } + ], + "@semantic-release/changelog", + "@semantic-release/npm", + "@semantic-release/github", + "@semantic-release/git" + ] + }, + "scripts": { + "clean": "aegir clean", + "lint": "aegir lint", + "dep-check": "aegir dep-check", + "build": "aegir build", + "release": "aegir release" + }, + "dependencies": { + "@libp2p/interface-peer-info": "^1.0.0", + "@libp2p/interfaces": "^3.0.0", + "multiformats": "^11.0.0" + }, + "devDependencies": { + "aegir": "^39.0.5" + }, + "typedoc": { + "entryPoint": "./src/index.ts" + } +} diff --git a/packages/interface-content-routing/src/index.ts b/packages/interface-content-routing/src/index.ts new file mode 100644 index 0000000000..c22bd19edb --- /dev/null +++ b/packages/interface-content-routing/src/index.ts @@ -0,0 +1,83 @@ +import type { PeerInfo } from '@libp2p/interface-peer-info' +import type { AbortOptions } from '@libp2p/interfaces' +import type { CID } from 'multiformats/cid' + +/** + * Any object that implements this Symbol as a property should return a + * ContentRouting instance as the property value, similar to how + * `Symbol.Iterable` can be used to return an `Iterable` from an `Iterator`. + * + * @example + * + * ```js + * import { contentRouting, ContentRouting } from '@libp2p/content-routing' + * + * class MyContentRouter implements ContentRouting { + * get [contentRouting] () { + * return this + * } + * + * // ...other methods + * } + * ``` + */ +export const contentRouting = Symbol.for('@libp2p/content-routing') + +export interface ContentRouting { + /** + * The implementation of this method should ensure that network peers know the + * caller can provide content that corresponds to the passed CID. + * + * @example + * + * ```js + * // ... + * await contentRouting.provide(cid) + * ``` + */ + provide: (cid: CID, options?: AbortOptions) => Promise + + /** + * Find the providers of the passed CID. + * + * @example + * + * ```js + * // Iterate over the providers found for the given cid + * for await (const provider of contentRouting.findProviders(cid)) { + * console.log(provider.id, provider.multiaddrs) + * } + * ``` + */ + findProviders: (cid: CID, options?: AbortOptions) => AsyncIterable + + /** + * Puts a value corresponding to the passed key in a way that can later be + * retrieved by another network peer using the get method. + * + * @example + * + * ```js + * // ... + * const key = '/key' + * const value = uint8ArrayFromString('oh hello there') + * + * await contentRouting.put(key, value) + * ``` + */ + put: (key: Uint8Array, value: Uint8Array, options?: AbortOptions) => Promise + + /** + * Retrieves a value from the network corresponding to the passed key. + * + * @example + * + * ```js + * // ... + * + * const key = '/key' + * const value = await contentRouting.get(key) + * ``` + */ + get: (key: Uint8Array, options?: AbortOptions) => Promise +} diff --git a/packages/interface-content-routing/tsconfig.json b/packages/interface-content-routing/tsconfig.json new file mode 100644 index 0000000000..84bbd0ffe3 --- /dev/null +++ b/packages/interface-content-routing/tsconfig.json @@ -0,0 +1,17 @@ +{ + "extends": "aegir/src/config/tsconfig.aegir.json", + "compilerOptions": { + "outDir": "dist" + }, + "include": [ + "src" + ], + "references": [ + { + "path": "../interface-peer-info" + }, + { + "path": "../interfaces" + } + ] +} diff --git a/packages/interface-dht/CHANGELOG.md b/packages/interface-dht/CHANGELOG.md new file mode 100644 index 0000000000..71d1f444e1 --- /dev/null +++ b/packages/interface-dht/CHANGELOG.md @@ -0,0 +1,77 @@ +## [@libp2p/interface-dht-v2.0.3](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-dht-v2.0.2...@libp2p/interface-dht-v2.0.3) (2023-05-04) + + +### Dependencies + +* update sibling dependencies ([45cf513](https://github.com/libp2p/js-libp2p-interfaces/commit/45cf513090d2a069bb6752ad2e231df65c76df36)) + +## [@libp2p/interface-dht-v2.0.2](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-dht-v2.0.1...@libp2p/interface-dht-v2.0.2) (2023-05-04) + + +### Dependencies + +* bump aegir from 38.1.8 to 39.0.5 ([#393](https://github.com/libp2p/js-libp2p-interfaces/issues/393)) ([31f3797](https://github.com/libp2p/js-libp2p-interfaces/commit/31f3797b24f7c23f3f16e9db3a230bd5f7cd5175)) + +## [@libp2p/interface-dht-v2.0.1](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-dht-v2.0.0...@libp2p/interface-dht-v2.0.1) (2023-01-18) + + +### Trivial Changes + +* remove lerna ([#330](https://github.com/libp2p/js-libp2p-interfaces/issues/330)) ([6678592](https://github.com/libp2p/js-libp2p-interfaces/commit/6678592dd0cf601a2671852f9d2a0aff5dee2b18)) + + +### Dependencies + +* bump aegir from 37.12.1 to 38.1.0 ([#335](https://github.com/libp2p/js-libp2p-interfaces/issues/335)) ([7368a36](https://github.com/libp2p/js-libp2p-interfaces/commit/7368a363423a08e8fa247dcb76ea13e4cf030d65)) + +## [@libp2p/interface-dht-v2.0.0](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-dht-v1.0.5...@libp2p/interface-dht-v2.0.0) (2023-01-06) + + +### ⚠ BREAKING CHANGES + +* bump multiformats from 10.0.3 to 11.0.0 (#329) + +### Dependencies + +* bump multiformats from 10.0.3 to 11.0.0 ([#329](https://github.com/libp2p/js-libp2p-interfaces/issues/329)) ([ba3a98b](https://github.com/libp2p/js-libp2p-interfaces/commit/ba3a98be61e3cf0996fefbd3004e974bb41ad2f0)) +* update sibling dependencies ([b50e621](https://github.com/libp2p/js-libp2p-interfaces/commit/b50e621d31a8b32affc3fadb9f97c4883d577f93)) + +## [@libp2p/interface-dht-v1.0.5](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-dht-v1.0.4...@libp2p/interface-dht-v1.0.5) (2022-12-16) + + +### Documentation + +* update project config ([#323](https://github.com/libp2p/js-libp2p-interfaces/issues/323)) ([0fc6a08](https://github.com/libp2p/js-libp2p-interfaces/commit/0fc6a08e9cdcefe361fe325281a3a2a03759ff59)) + +## [@libp2p/interface-dht-v1.0.4](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-dht-v1.0.3...@libp2p/interface-dht-v1.0.4) (2022-12-14) + + +### Bug Fixes + +* generate docs for all packages ([#321](https://github.com/libp2p/js-libp2p-interfaces/issues/321)) ([b6f8b32](https://github.com/libp2p/js-libp2p-interfaces/commit/b6f8b32a920c15a28fe021e6050e31aaae89d518)) + +## [@libp2p/interface-dht-v1.0.3](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-dht-v1.0.2...@libp2p/interface-dht-v1.0.3) (2022-11-05) + + +### Bug Fixes + +* update project config ([#311](https://github.com/libp2p/js-libp2p-interfaces/issues/311)) ([27dd0ce](https://github.com/libp2p/js-libp2p-interfaces/commit/27dd0ce3c249892ac69cbb24ddaf0b9f32385e37)) + +## [@libp2p/interface-dht-v1.0.2](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-dht-v1.0.1...@libp2p/interface-dht-v1.0.2) (2022-10-12) + + +### Trivial Changes + +* update project config ([#271](https://github.com/libp2p/js-libp2p-interfaces/issues/271)) ([59c0bf5](https://github.com/libp2p/js-libp2p-interfaces/commit/59c0bf5e0b05496fca2e4902632b61bb41fad9e9)) + + +### Dependencies + +* bump multiformats from 9.9.0 to 10.0.0 ([#302](https://github.com/libp2p/js-libp2p-interfaces/issues/302)) ([fe11d69](https://github.com/libp2p/js-libp2p-interfaces/commit/fe11d69b6aca3dd6ef6053bec27b534ec9908aa1)) + +## [@libp2p/interface-dht-v1.0.1](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-dht-v1.0.0...@libp2p/interface-dht-v1.0.1) (2022-06-27) + + +### Trivial Changes + +* update deps ([#262](https://github.com/libp2p/js-libp2p-interfaces/issues/262)) ([51edf7d](https://github.com/libp2p/js-libp2p-interfaces/commit/51edf7d9b3765a6f75c915b1483ea345d0133a41)) diff --git a/packages/interface-dht/LICENSE b/packages/interface-dht/LICENSE new file mode 100644 index 0000000000..20ce483c86 --- /dev/null +++ b/packages/interface-dht/LICENSE @@ -0,0 +1,4 @@ +This project is dual licensed under MIT and Apache-2.0. + +MIT: https://www.opensource.org/licenses/mit +Apache-2.0: https://www.apache.org/licenses/license-2.0 diff --git a/packages/interface-dht/LICENSE-APACHE b/packages/interface-dht/LICENSE-APACHE new file mode 100644 index 0000000000..14478a3b60 --- /dev/null +++ b/packages/interface-dht/LICENSE-APACHE @@ -0,0 +1,5 @@ +Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. diff --git a/packages/interface-dht/LICENSE-MIT b/packages/interface-dht/LICENSE-MIT new file mode 100644 index 0000000000..72dc60d84b --- /dev/null +++ b/packages/interface-dht/LICENSE-MIT @@ -0,0 +1,19 @@ +The MIT License (MIT) + +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/packages/interface-dht/README.md b/packages/interface-dht/README.md new file mode 100644 index 0000000000..917812c917 --- /dev/null +++ b/packages/interface-dht/README.md @@ -0,0 +1,36 @@ +# @libp2p/interface-dht + +[![libp2p.io](https://img.shields.io/badge/project-libp2p-yellow.svg?style=flat-square)](http://libp2p.io/) +[![Discuss](https://img.shields.io/discourse/https/discuss.libp2p.io/posts.svg?style=flat-square)](https://discuss.libp2p.io) +[![codecov](https://img.shields.io/codecov/c/github/libp2p/js-libp2p.svg?style=flat-square)](https://codecov.io/gh/libp2p/js-libp2p) +[![CI](https://img.shields.io/github/actions/workflow/status/libp2p/js-libp2p/js-test-and-release.yml?branch=master\&style=flat-square)](https://github.com/libp2p/js-libp2p/actions/workflows/js-test-and-release.yml?query=branch%3Amaster) + +> DHT interface for libp2p + +## Table of contents + +- [Install](#install) +- [API Docs](#api-docs) +- [License](#license) +- [Contribution](#contribution) + +## Install + +```console +$ npm i @libp2p/interface-dht +``` + +## API Docs + +- + +## License + +Licensed under either of + +- Apache 2.0, ([LICENSE-APACHE](LICENSE-APACHE) / ) +- MIT ([LICENSE-MIT](LICENSE-MIT) / ) + +## Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. diff --git a/packages/interface-dht/package.json b/packages/interface-dht/package.json new file mode 100644 index 0000000000..36deae40a0 --- /dev/null +++ b/packages/interface-dht/package.json @@ -0,0 +1,143 @@ +{ + "name": "@libp2p/interface-dht", + "version": "2.0.3", + "description": "DHT interface for libp2p", + "license": "Apache-2.0 OR MIT", + "homepage": "https://github.com/libp2p/js-libp2p/tree/master/packages/interface-dht#readme", + "repository": { + "type": "git", + "url": "git+https://github.com/libp2p/js-libp2p.git" + }, + "bugs": { + "url": "https://github.com/libp2p/js-libp2p/issues" + }, + "keywords": [ + "interface", + "libp2p" + ], + "type": "module", + "types": "./dist/src/index.d.ts", + "files": [ + "src", + "dist", + "!dist/test", + "!**/*.tsbuildinfo" + ], + "exports": { + ".": { + "types": "./dist/src/index.d.ts", + "import": "./dist/src/index.js" + } + }, + "eslintConfig": { + "extends": "ipfs", + "parserOptions": { + "sourceType": "module" + } + }, + "release": { + "branches": [ + "master" + ], + "plugins": [ + [ + "@semantic-release/commit-analyzer", + { + "preset": "conventionalcommits", + "releaseRules": [ + { + "breaking": true, + "release": "major" + }, + { + "revert": true, + "release": "patch" + }, + { + "type": "feat", + "release": "minor" + }, + { + "type": "fix", + "release": "patch" + }, + { + "type": "docs", + "release": "patch" + }, + { + "type": "test", + "release": "patch" + }, + { + "type": "deps", + "release": "patch" + }, + { + "scope": "no-release", + "release": false + } + ] + } + ], + [ + "@semantic-release/release-notes-generator", + { + "preset": "conventionalcommits", + "presetConfig": { + "types": [ + { + "type": "feat", + "section": "Features" + }, + { + "type": "fix", + "section": "Bug Fixes" + }, + { + "type": "chore", + "section": "Trivial Changes" + }, + { + "type": "docs", + "section": "Documentation" + }, + { + "type": "deps", + "section": "Dependencies" + }, + { + "type": "test", + "section": "Tests" + } + ] + } + } + ], + "@semantic-release/changelog", + "@semantic-release/npm", + "@semantic-release/github", + "@semantic-release/git" + ] + }, + "scripts": { + "clean": "aegir clean", + "lint": "aegir lint", + "dep-check": "aegir dep-check", + "build": "aegir build", + "release": "aegir release" + }, + "dependencies": { + "@libp2p/interface-peer-discovery": "^2.0.0", + "@libp2p/interface-peer-id": "^2.0.0", + "@libp2p/interface-peer-info": "^1.0.0", + "@libp2p/interfaces": "^3.0.0", + "multiformats": "^11.0.0" + }, + "devDependencies": { + "aegir": "^39.0.5" + }, + "typedoc": { + "entryPoint": "./src/index.ts" + } +} diff --git a/packages/interface-dht/src/index.ts b/packages/interface-dht/src/index.ts new file mode 100644 index 0000000000..25f46a7d79 --- /dev/null +++ b/packages/interface-dht/src/index.ts @@ -0,0 +1,211 @@ +import type { PeerDiscovery } from '@libp2p/interface-peer-discovery' +import type { PeerId } from '@libp2p/interface-peer-id' +import type { PeerInfo } from '@libp2p/interface-peer-info' +import type { AbortOptions } from '@libp2p/interfaces' +import type { CID } from 'multiformats/cid' + +/** + * The types of events emitted during DHT queries + */ +export enum EventTypes { + SENDING_QUERY = 0, + PEER_RESPONSE, + FINAL_PEER, + QUERY_ERROR, + PROVIDER, + VALUE, + ADDING_PEER, + DIALING_PEER +} + +/** + * The types of messages sent to peers during DHT queries + */ +export enum MessageType { + PUT_VALUE = 0, + GET_VALUE, + ADD_PROVIDER, + GET_PROVIDERS, + FIND_NODE, + PING +} + +export type MessageName = keyof typeof MessageType + +export interface DHTRecord { + key: Uint8Array + value: Uint8Array + timeReceived?: Date +} + +export interface QueryOptions extends AbortOptions { + queryFuncTimeout?: number +} + +/** + * Emitted when sending queries to remote peers + */ +export interface SendingQueryEvent { + to: PeerId + type: EventTypes.SENDING_QUERY + name: 'SENDING_QUERY' + messageName: keyof typeof MessageType + messageType: MessageType +} + +/** + * Emitted when query responses are received form remote peers. Depending on the query + * these events may be followed by a `FinalPeerEvent`, a `ValueEvent` or a `ProviderEvent`. + */ +export interface PeerResponseEvent { + from: PeerId + type: EventTypes.PEER_RESPONSE + name: 'PEER_RESPONSE' + messageName: keyof typeof MessageType + messageType: MessageType + closer: PeerInfo[] + providers: PeerInfo[] + record?: DHTRecord +} + +/** + * Emitted at the end of a `findPeer` query + */ +export interface FinalPeerEvent { + from: PeerId + peer: PeerInfo + type: EventTypes.FINAL_PEER + name: 'FINAL_PEER' +} + +/** + * Something went wrong with the query + */ +export interface QueryErrorEvent { + from: PeerId + type: EventTypes.QUERY_ERROR + name: 'QUERY_ERROR' + error: Error +} + +/** + * Emitted when providers are found + */ +export interface ProviderEvent { + from: PeerId + type: EventTypes.PROVIDER + name: 'PROVIDER' + providers: PeerInfo[] +} + +/** + * Emitted when values are found + */ +export interface ValueEvent { + from: PeerId + type: EventTypes.VALUE + name: 'VALUE' + value: Uint8Array +} + +/** + * Emitted when peers are added to a query + */ +export interface AddingPeerEvent { + type: EventTypes.ADDING_PEER + name: 'ADDING_PEER' + peer: PeerId +} + +/** + * Emitted when peers are dialled as part of a query + */ +export interface DialingPeerEvent { + peer: PeerId + type: EventTypes.DIALING_PEER + name: 'DIALING_PEER' +} + +export type QueryEvent = SendingQueryEvent | PeerResponseEvent | FinalPeerEvent | QueryErrorEvent | ProviderEvent | ValueEvent | AddingPeerEvent | DialingPeerEvent + +export interface RoutingTable { + size: number +} + +export interface DHT extends PeerDiscovery { + /** + * Get a value from the DHT, the final ValueEvent will be the best value + */ + get: (key: Uint8Array, options?: QueryOptions) => AsyncIterable + + /** + * Find providers of a given CID + */ + findProviders: (key: CID, options?: QueryOptions) => AsyncIterable + + /** + * Find a peer on the DHT + */ + findPeer: (id: PeerId, options?: QueryOptions) => AsyncIterable + + /** + * Find the closest peers to the passed key + */ + getClosestPeers: (key: Uint8Array, options?: QueryOptions) => AsyncIterable + + /** + * Store provider records for the passed CID on the DHT pointing to us + */ + provide: (key: CID, options?: QueryOptions) => AsyncIterable + + /** + * Store the passed value under the passed key on the DHT + */ + put: (key: Uint8Array, value: Uint8Array, options?: QueryOptions) => AsyncIterable + + /** + * Returns the mode this node is in + */ + getMode: () => Promise<'client' | 'server'> + + /** + * If 'server' this node will respond to DHT queries, if 'client' this node will not + */ + setMode: (mode: 'client' | 'server') => Promise + + /** + * Force a routing table refresh + */ + refreshRoutingTable: () => Promise +} + +export interface SingleDHT extends DHT { + routingTable: RoutingTable +} + +export interface DualDHT extends DHT { + wan: SingleDHT + lan: SingleDHT +} + +/** + * A selector function takes a DHT key and a list of records and returns the + * index of the best record in the list + */ +export interface SelectFn { (key: Uint8Array, records: Uint8Array[]): number } + +/** + * A validator function takes a DHT key and the value of the record for that key + * and throws if the record is invalid + */ +export interface ValidateFn { (key: Uint8Array, value: Uint8Array): Promise } + +/** + * Selectors are a map of key prefixes to selector functions + */ +export type Selectors = Record + +/** + * Validators are a map of key prefixes to validator functions + */ +export type Validators = Record diff --git a/packages/interface-dht/tsconfig.json b/packages/interface-dht/tsconfig.json new file mode 100644 index 0000000000..29f2945683 --- /dev/null +++ b/packages/interface-dht/tsconfig.json @@ -0,0 +1,23 @@ +{ + "extends": "aegir/src/config/tsconfig.aegir.json", + "compilerOptions": { + "outDir": "dist" + }, + "include": [ + "src" + ], + "references": [ + { + "path": "../interface-peer-discovery" + }, + { + "path": "../interface-peer-id" + }, + { + "path": "../interface-peer-info" + }, + { + "path": "../interfaces" + } + ] +} diff --git a/packages/interface-keychain/CHANGELOG.md b/packages/interface-keychain/CHANGELOG.md new file mode 100644 index 0000000000..f94cdbd62f --- /dev/null +++ b/packages/interface-keychain/CHANGELOG.md @@ -0,0 +1,122 @@ +## [@libp2p/interface-keychain-v2.0.5](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-keychain-v2.0.4...@libp2p/interface-keychain-v2.0.5) (2023-05-04) + + +### Dependencies + +* bump aegir from 38.1.8 to 39.0.5 ([#393](https://github.com/libp2p/js-libp2p-interfaces/issues/393)) ([31f3797](https://github.com/libp2p/js-libp2p-interfaces/commit/31f3797b24f7c23f3f16e9db3a230bd5f7cd5175)) + +## [@libp2p/interface-keychain-v2.0.4](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-keychain-v2.0.3...@libp2p/interface-keychain-v2.0.4) (2023-01-27) + + +### Bug Fixes + +* add missing method to keychain interface to rotate password ([#340](https://github.com/libp2p/js-libp2p-interfaces/issues/340)) ([db60895](https://github.com/libp2p/js-libp2p-interfaces/commit/db60895f9b86f627b1cb5c1bcabff69398e34b93)) + +## [@libp2p/interface-keychain-v2.0.3](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-keychain-v2.0.2...@libp2p/interface-keychain-v2.0.3) (2023-01-18) + + +### Dependencies + +* bump aegir from 37.12.1 to 38.1.0 ([#335](https://github.com/libp2p/js-libp2p-interfaces/issues/335)) ([7368a36](https://github.com/libp2p/js-libp2p-interfaces/commit/7368a363423a08e8fa247dcb76ea13e4cf030d65)) + +## [@libp2p/interface-keychain-v2.0.2](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-keychain-v2.0.1...@libp2p/interface-keychain-v2.0.2) (2023-01-18) + + +### Bug Fixes + +* add exportPeer method to keychain interface ([#337](https://github.com/libp2p/js-libp2p-interfaces/issues/337)) ([a970939](https://github.com/libp2p/js-libp2p-interfaces/commit/a970939ee685c1fd8ba2121e04f8a7cff5b953ca)) + +## [@libp2p/interface-keychain-v2.0.1](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-keychain-v2.0.0...@libp2p/interface-keychain-v2.0.1) (2023-01-07) + + +### Bug Fixes + +* add importPeer method to keychain interface ([#333](https://github.com/libp2p/js-libp2p-interfaces/issues/333)) ([51de73c](https://github.com/libp2p/js-libp2p-interfaces/commit/51de73c85f72151d1b9e5f3248d757512d40659b)) + + +### Trivial Changes + +* remove lerna ([#330](https://github.com/libp2p/js-libp2p-interfaces/issues/330)) ([6678592](https://github.com/libp2p/js-libp2p-interfaces/commit/6678592dd0cf601a2671852f9d2a0aff5dee2b18)) + +## [@libp2p/interface-keychain-v2.0.0](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-keychain-v1.0.8...@libp2p/interface-keychain-v2.0.0) (2023-01-06) + + +### ⚠ BREAKING CHANGES + +* bump multiformats from 10.0.3 to 11.0.0 (#329) + +### Dependencies + +* bump multiformats from 10.0.3 to 11.0.0 ([#329](https://github.com/libp2p/js-libp2p-interfaces/issues/329)) ([ba3a98b](https://github.com/libp2p/js-libp2p-interfaces/commit/ba3a98be61e3cf0996fefbd3004e974bb41ad2f0)) + +## [@libp2p/interface-keychain-v1.0.8](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-keychain-v1.0.7...@libp2p/interface-keychain-v1.0.8) (2022-12-19) + + +### Documentation + +* add interface docs ([#324](https://github.com/libp2p/js-libp2p-interfaces/issues/324)) ([2789445](https://github.com/libp2p/js-libp2p-interfaces/commit/278944594c24e1a3c4b3624a35680d69166546d7)) + +## [@libp2p/interface-keychain-v1.0.7](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-keychain-v1.0.6...@libp2p/interface-keychain-v1.0.7) (2022-12-16) + + +### Documentation + +* update project config ([#323](https://github.com/libp2p/js-libp2p-interfaces/issues/323)) ([0fc6a08](https://github.com/libp2p/js-libp2p-interfaces/commit/0fc6a08e9cdcefe361fe325281a3a2a03759ff59)) + +## [@libp2p/interface-keychain-v1.0.6](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-keychain-v1.0.5...@libp2p/interface-keychain-v1.0.6) (2022-12-14) + + +### Bug Fixes + +* generate docs for all packages ([#321](https://github.com/libp2p/js-libp2p-interfaces/issues/321)) ([b6f8b32](https://github.com/libp2p/js-libp2p-interfaces/commit/b6f8b32a920c15a28fe021e6050e31aaae89d518)) + +## [@libp2p/interface-keychain-v1.0.5](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-keychain-v1.0.4...@libp2p/interface-keychain-v1.0.5) (2022-11-05) + + +### Bug Fixes + +* update project config ([#311](https://github.com/libp2p/js-libp2p-interfaces/issues/311)) ([27dd0ce](https://github.com/libp2p/js-libp2p-interfaces/commit/27dd0ce3c249892ac69cbb24ddaf0b9f32385e37)) + +## [@libp2p/interface-keychain-v1.0.4](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-keychain-v1.0.3...@libp2p/interface-keychain-v1.0.4) (2022-10-12) + + +### Trivial Changes + +* update project config ([#271](https://github.com/libp2p/js-libp2p-interfaces/issues/271)) ([59c0bf5](https://github.com/libp2p/js-libp2p-interfaces/commit/59c0bf5e0b05496fca2e4902632b61bb41fad9e9)) + + +### Dependencies + +* bump multiformats from 9.9.0 to 10.0.0 ([#302](https://github.com/libp2p/js-libp2p-interfaces/issues/302)) ([fe11d69](https://github.com/libp2p/js-libp2p-interfaces/commit/fe11d69b6aca3dd6ef6053bec27b534ec9908aa1)) + +## [@libp2p/interface-keychain-v1.0.3](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-keychain-v1.0.2...@libp2p/interface-keychain-v1.0.3) (2022-06-27) + + +### Trivial Changes + +* update deps ([#262](https://github.com/libp2p/js-libp2p-interfaces/issues/262)) ([51edf7d](https://github.com/libp2p/js-libp2p-interfaces/commit/51edf7d9b3765a6f75c915b1483ea345d0133a41)) + +## [@libp2p/interface-keychain-v1.0.2](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-keychain-v1.0.1...@libp2p/interface-keychain-v1.0.2) (2022-06-14) + + +### Trivial Changes + +* update aegir ([#234](https://github.com/libp2p/js-libp2p-interfaces/issues/234)) ([3e03895](https://github.com/libp2p/js-libp2p-interfaces/commit/3e038959ecab6cfa3585df9ee179c0af7a61eda5)) + +## [@libp2p/interface-keychain-v1.0.1](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-keychain-v1.0.0...@libp2p/interface-keychain-v1.0.1) (2022-06-14) + + +### Trivial Changes + +* update readmes ([#233](https://github.com/libp2p/js-libp2p-interfaces/issues/233)) ([ee7da38](https://github.com/libp2p/js-libp2p-interfaces/commit/ee7da38dccc08160d26c8436df8739ce7e0b340e)) + +## @libp2p/interface-keychain-v1.0.0 (2022-06-14) + + +### ⚠ BREAKING CHANGES + +* most modules have been split out of the `@libp2p/interfaces` and `@libp2p/interface-compliance-tests` packages + +### Trivial Changes + +* break modules apart ([#232](https://github.com/libp2p/js-libp2p-interfaces/issues/232)) ([385614e](https://github.com/libp2p/js-libp2p-interfaces/commit/385614e772329052ab17415c8bd421f65b01a61b)), closes [#226](https://github.com/libp2p/js-libp2p-interfaces/issues/226) diff --git a/packages/interface-keychain/LICENSE b/packages/interface-keychain/LICENSE new file mode 100644 index 0000000000..20ce483c86 --- /dev/null +++ b/packages/interface-keychain/LICENSE @@ -0,0 +1,4 @@ +This project is dual licensed under MIT and Apache-2.0. + +MIT: https://www.opensource.org/licenses/mit +Apache-2.0: https://www.apache.org/licenses/license-2.0 diff --git a/packages/interface-keychain/LICENSE-APACHE b/packages/interface-keychain/LICENSE-APACHE new file mode 100644 index 0000000000..14478a3b60 --- /dev/null +++ b/packages/interface-keychain/LICENSE-APACHE @@ -0,0 +1,5 @@ +Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. diff --git a/packages/interface-keychain/LICENSE-MIT b/packages/interface-keychain/LICENSE-MIT new file mode 100644 index 0000000000..72dc60d84b --- /dev/null +++ b/packages/interface-keychain/LICENSE-MIT @@ -0,0 +1,19 @@ +The MIT License (MIT) + +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/packages/interface-keychain/README.md b/packages/interface-keychain/README.md new file mode 100644 index 0000000000..5906ebc6bf --- /dev/null +++ b/packages/interface-keychain/README.md @@ -0,0 +1,100 @@ +# @libp2p/interface-keychain + +[![libp2p.io](https://img.shields.io/badge/project-libp2p-yellow.svg?style=flat-square)](http://libp2p.io/) +[![Discuss](https://img.shields.io/discourse/https/discuss.libp2p.io/posts.svg?style=flat-square)](https://discuss.libp2p.io) +[![codecov](https://img.shields.io/codecov/c/github/libp2p/js-libp2p.svg?style=flat-square)](https://codecov.io/gh/libp2p/js-libp2p) +[![CI](https://img.shields.io/github/actions/workflow/status/libp2p/js-libp2p/js-test-and-release.yml?branch=master\&style=flat-square)](https://github.com/libp2p/js-libp2p/actions/workflows/js-test-and-release.yml?query=branch%3Amaster) + +> Keychain interface for libp2p + +## Table of contents + +- - [Install](#install) +- [Modules that implement the interface](#modules-that-implement-the-interface) +- [Badge](#badge) +- [How to use the battery of tests](#how-to-use-the-battery-of-tests) + - [Node.js](#nodejs) +- [API](#api) + - - [findProviders](#findproviders) + - [provide](#provide) + - [API Docs](#api-docs) + - [License](#license) + - [Contribution](#contribution) + +## Install + +```console +$ npm i @libp2p/interface-keychain +``` + +The primary goal of this module is to enable developers to pick and swap their Content Routing module as they see fit for their libp2p installation, without having to go through shims or compatibility issues. This module and test suite were heavily inspired by abstract-blob-store and interface-stream-muxer. + +Publishing a test suite as a module lets multiple modules all ensure compatibility since they use the same test suite. + +# Modules that implement the interface + +- [JavaScript libp2p-kad-dht](https://github.com/libp2p/js-libp2p-kad-dht) +- [JavaScript libp2p-delegated-content-routing](https://github.com/libp2p/js-libp2p-delegated-content-routing) + +# Badge + +Include this badge in your readme if you make a module that is compatible with the interface-content-routing API. You can validate this by running the tests. + +![](img/badge.png) + +# How to use the battery of tests + +## Node.js + +TBD + +# API + +A valid (read: that follows this abstraction) Content Routing module must implement the following API. + +### findProviders + +- `findProviders(cid)` + +Find peers in the network that can provide a specific value, given a key. + +**Parameters** + +- [CID](https://github.com/multiformats/js-cid) + +**Returns** + +It returns an `AsyncIterable` containing the identification and addresses of the peers providing the given key, as follows: + +`AsyncIterable<{ id: PeerId, multiaddrs: Multiaddr[] }>` + +### provide + +- `provide(cid)` + +Announce to the network that we are providing the given value. + +**Parameters** + +- [CID](https://github.com/multiformats/js-cid) + +**Returns** + +It returns a promise that is resolved on the success of the operation. + +`Promise` + +## API Docs + +- + +## License + +Licensed under either of + +- Apache 2.0, ([LICENSE-APACHE](LICENSE-APACHE) / ) +- MIT ([LICENSE-MIT](LICENSE-MIT) / ) + +## Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. diff --git a/packages/interface-keychain/package.json b/packages/interface-keychain/package.json new file mode 100644 index 0000000000..12d971d1c6 --- /dev/null +++ b/packages/interface-keychain/package.json @@ -0,0 +1,140 @@ +{ + "name": "@libp2p/interface-keychain", + "version": "2.0.5", + "description": "Keychain interface for libp2p", + "license": "Apache-2.0 OR MIT", + "homepage": "https://github.com/libp2p/js-libp2p/tree/master/packages/interface-keychain#readme", + "repository": { + "type": "git", + "url": "git+https://github.com/libp2p/js-libp2p.git" + }, + "bugs": { + "url": "https://github.com/libp2p/js-libp2p/issues" + }, + "keywords": [ + "interface", + "libp2p" + ], + "type": "module", + "types": "./dist/src/index.d.ts", + "files": [ + "src", + "dist", + "!dist/test", + "!**/*.tsbuildinfo" + ], + "exports": { + ".": { + "types": "./dist/src/index.d.ts", + "import": "./dist/src/index.js" + } + }, + "eslintConfig": { + "extends": "ipfs", + "parserOptions": { + "sourceType": "module" + } + }, + "release": { + "branches": [ + "master" + ], + "plugins": [ + [ + "@semantic-release/commit-analyzer", + { + "preset": "conventionalcommits", + "releaseRules": [ + { + "breaking": true, + "release": "major" + }, + { + "revert": true, + "release": "patch" + }, + { + "type": "feat", + "release": "minor" + }, + { + "type": "fix", + "release": "patch" + }, + { + "type": "docs", + "release": "patch" + }, + { + "type": "test", + "release": "patch" + }, + { + "type": "deps", + "release": "patch" + }, + { + "scope": "no-release", + "release": false + } + ] + } + ], + [ + "@semantic-release/release-notes-generator", + { + "preset": "conventionalcommits", + "presetConfig": { + "types": [ + { + "type": "feat", + "section": "Features" + }, + { + "type": "fix", + "section": "Bug Fixes" + }, + { + "type": "chore", + "section": "Trivial Changes" + }, + { + "type": "docs", + "section": "Documentation" + }, + { + "type": "deps", + "section": "Dependencies" + }, + { + "type": "test", + "section": "Tests" + } + ] + } + } + ], + "@semantic-release/changelog", + "@semantic-release/npm", + "@semantic-release/github", + "@semantic-release/git" + ] + }, + "scripts": { + "clean": "aegir clean", + "lint": "aegir lint", + "dep-check": "aegir dep-check", + "build": "aegir build", + "release": "aegir release" + }, + "dependencies": { + "@libp2p/interface-peer-id": "^2.0.0", + "multiformats": "^11.0.0" + }, + "devDependencies": { + "aegir": "^39.0.5" + }, + "typedoc": { + "entryPoint": "./src/index.ts" + } +} diff --git a/packages/interface-keychain/src/index.ts b/packages/interface-keychain/src/index.ts new file mode 100644 index 0000000000..1c71ea5b0b --- /dev/null +++ b/packages/interface-keychain/src/index.ts @@ -0,0 +1,168 @@ +/** + * @packageDocumentation + * + * The libp2p keychain provides an API to store keys in a datastore in + * an encrypted format. + * + * @example + * + * ```typescript + * import { createLibp2p } from 'libp2p' + * import { FsDatastore } from 'datastore-fs' + * + * const node = await createLibp2p({ + * datastore: new FsDatastore('/path/to/dir') + * }) + * + * const info = await node.keychain.createKey('my-new-key', 'Ed25519') + * + * console.info(info) // { id: '...', name: 'my-new-key' } + * ``` + */ + +import type { PeerId } from '@libp2p/interface-peer-id' +import type { Multibase } from 'multiformats/bases/interface' + +export interface KeyInfo { + /** + * The universally unique key id + */ + id: string + + /** + * The local key name + */ + name: string +} + +export type KeyType = 'RSA' | 'Ed25519' | 'secp256k1' + +export interface KeyChain { + /** + * Export an existing key as a PEM encrypted PKCS #8 string. + * + * @example + * + * ```js + * await libp2p.keychain.createKey('keyTest', 'RSA', 4096) + * const pemKey = await libp2p.keychain.exportKey('keyTest', 'password123') + * ``` + */ + exportKey: (name: string, password: string) => Promise> + + /** + * Import a new key from a PEM encoded PKCS #8 string. + * + * @example + * + * ```js + * await libp2p.keychain.createKey('keyTest', 'RSA', 4096) + * const pemKey = await libp2p.keychain.exportKey('keyTest', 'password123') + * const keyInfo = await libp2p.keychain.importKey('keyTestImport', pemKey, 'password123') + * ``` + */ + importKey: (name: string, pem: string, password: string) => Promise + + /** + * Import a new key from a PeerId with a private key component + * + * @example + * + * ```js + * const keyInfo = await libp2p.keychain.importPeer('keyTestImport', peerIdFromString('12D3Foo...')) + * ``` + */ + importPeer: (name: string, peerId: PeerId) => Promise + + /** + * Export an existing key as a PeerId + * + * @example + * + * ```js + * const peerId = await libp2p.keychain.exportPeerId('key-name') + * ``` + */ + exportPeerId: (name: string) => Promise + + /** + * Create a key in the keychain. + * + * @example + * + * ```js + * const keyInfo = await libp2p.keychain.createKey('keyTest', 'RSA', 4096) + * ``` + */ + createKey: (name: string, type: KeyType, size?: number) => Promise + + /** + * List all the keys. + * + * @example + * + * ```js + * const keyInfos = await libp2p.keychain.listKeys() + * ``` + */ + listKeys: () => Promise + + /** + * Removes a key from the keychain. + * + * @example + * + * ```js + * await libp2p.keychain.createKey('keyTest', 'RSA', 4096) + * const keyInfo = await libp2p.keychain.removeKey('keyTest') + * ``` + */ + removeKey: (name: string) => Promise + + /** + * Rename a key in the keychain. + * + * @example + * + * ```js + * await libp2p.keychain.createKey('keyTest', 'RSA', 4096) + * const keyInfo = await libp2p.keychain.renameKey('keyTest', 'keyNewNtest') + * ``` + */ + renameKey: (oldName: string, newName: string) => Promise + + /** + * Find a key by it's id. + * + * @example + * + * ```js + * const keyInfo = await libp2p.keychain.createKey('keyTest', 'RSA', 4096) + * const keyInfo2 = await libp2p.keychain.findKeyById(keyInfo.id) + * ``` + */ + findKeyById: (id: string) => Promise + + /** + * Find a key by it's name. + * + * @example + * + * ```js + * const keyInfo = await libp2p.keychain.createKey('keyTest', 'RSA', 4096) + * const keyInfo2 = await libp2p.keychain.findKeyByName('keyTest') + * ``` + */ + findKeyByName: (name: string) => Promise + + /** + * Rotate keychain password and re-encrypt all associated keys + * + * @example + * + * ```js + * await libp2p.keychain.rotateKeychainPass('oldPassword', 'newPassword') + * ``` + */ + rotateKeychainPass: (oldPass: string, newPass: string) => Promise +} diff --git a/packages/interface-keychain/tsconfig.json b/packages/interface-keychain/tsconfig.json new file mode 100644 index 0000000000..d8db0b667f --- /dev/null +++ b/packages/interface-keychain/tsconfig.json @@ -0,0 +1,14 @@ +{ + "extends": "aegir/src/config/tsconfig.aegir.json", + "compilerOptions": { + "outDir": "dist" + }, + "include": [ + "src" + ], + "references": [ + { + "path": "../interface-peer-id" + } + ] +} diff --git a/packages/interface-keys/CHANGELOG.md b/packages/interface-keys/CHANGELOG.md new file mode 100644 index 0000000000..f2f84f5302 --- /dev/null +++ b/packages/interface-keys/CHANGELOG.md @@ -0,0 +1,76 @@ +## [@libp2p/interface-keys-v1.0.8](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-keys-v1.0.7...@libp2p/interface-keys-v1.0.8) (2023-05-04) + + +### Dependencies + +* bump aegir from 38.1.8 to 39.0.5 ([#393](https://github.com/libp2p/js-libp2p-interfaces/issues/393)) ([31f3797](https://github.com/libp2p/js-libp2p-interfaces/commit/31f3797b24f7c23f3f16e9db3a230bd5f7cd5175)) + +## [@libp2p/interface-keys-v1.0.7](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-keys-v1.0.6...@libp2p/interface-keys-v1.0.7) (2023-01-18) + + +### Trivial Changes + +* remove lerna ([#330](https://github.com/libp2p/js-libp2p-interfaces/issues/330)) ([6678592](https://github.com/libp2p/js-libp2p-interfaces/commit/6678592dd0cf601a2671852f9d2a0aff5dee2b18)) + + +### Dependencies + +* bump aegir from 37.12.1 to 38.1.0 ([#335](https://github.com/libp2p/js-libp2p-interfaces/issues/335)) ([7368a36](https://github.com/libp2p/js-libp2p-interfaces/commit/7368a363423a08e8fa247dcb76ea13e4cf030d65)) + +## [@libp2p/interface-keys-v1.0.6](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-keys-v1.0.5...@libp2p/interface-keys-v1.0.6) (2022-12-16) + + +### Documentation + +* update project config ([#323](https://github.com/libp2p/js-libp2p-interfaces/issues/323)) ([0fc6a08](https://github.com/libp2p/js-libp2p-interfaces/commit/0fc6a08e9cdcefe361fe325281a3a2a03759ff59)) + +## [@libp2p/interface-keys-v1.0.5](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-keys-v1.0.4...@libp2p/interface-keys-v1.0.5) (2022-12-14) + + +### Bug Fixes + +* generate docs for all packages ([#321](https://github.com/libp2p/js-libp2p-interfaces/issues/321)) ([b6f8b32](https://github.com/libp2p/js-libp2p-interfaces/commit/b6f8b32a920c15a28fe021e6050e31aaae89d518)) + +## [@libp2p/interface-keys-v1.0.4](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-keys-v1.0.3...@libp2p/interface-keys-v1.0.4) (2022-11-05) + + +### Bug Fixes + +* update project config ([#311](https://github.com/libp2p/js-libp2p-interfaces/issues/311)) ([27dd0ce](https://github.com/libp2p/js-libp2p-interfaces/commit/27dd0ce3c249892ac69cbb24ddaf0b9f32385e37)) + + +### Trivial Changes + +* update project config ([#271](https://github.com/libp2p/js-libp2p-interfaces/issues/271)) ([59c0bf5](https://github.com/libp2p/js-libp2p-interfaces/commit/59c0bf5e0b05496fca2e4902632b61bb41fad9e9)) + +## [@libp2p/interface-keys-v1.0.3](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-keys-v1.0.2...@libp2p/interface-keys-v1.0.3) (2022-06-27) + + +### Trivial Changes + +* update deps ([#262](https://github.com/libp2p/js-libp2p-interfaces/issues/262)) ([51edf7d](https://github.com/libp2p/js-libp2p-interfaces/commit/51edf7d9b3765a6f75c915b1483ea345d0133a41)) + +## [@libp2p/interface-keys-v1.0.2](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-keys-v1.0.1...@libp2p/interface-keys-v1.0.2) (2022-06-14) + + +### Trivial Changes + +* update aegir ([#234](https://github.com/libp2p/js-libp2p-interfaces/issues/234)) ([3e03895](https://github.com/libp2p/js-libp2p-interfaces/commit/3e038959ecab6cfa3585df9ee179c0af7a61eda5)) + +## [@libp2p/interface-keys-v1.0.1](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-keys-v1.0.0...@libp2p/interface-keys-v1.0.1) (2022-06-14) + + +### Trivial Changes + +* update readmes ([#233](https://github.com/libp2p/js-libp2p-interfaces/issues/233)) ([ee7da38](https://github.com/libp2p/js-libp2p-interfaces/commit/ee7da38dccc08160d26c8436df8739ce7e0b340e)) + +## @libp2p/interface-keys-v1.0.0 (2022-06-14) + + +### ⚠ BREAKING CHANGES + +* most modules have been split out of the `@libp2p/interfaces` and `@libp2p/interface-compliance-tests` packages + +### Trivial Changes + +* break modules apart ([#232](https://github.com/libp2p/js-libp2p-interfaces/issues/232)) ([385614e](https://github.com/libp2p/js-libp2p-interfaces/commit/385614e772329052ab17415c8bd421f65b01a61b)), closes [#226](https://github.com/libp2p/js-libp2p-interfaces/issues/226) diff --git a/packages/interface-keys/LICENSE b/packages/interface-keys/LICENSE new file mode 100644 index 0000000000..20ce483c86 --- /dev/null +++ b/packages/interface-keys/LICENSE @@ -0,0 +1,4 @@ +This project is dual licensed under MIT and Apache-2.0. + +MIT: https://www.opensource.org/licenses/mit +Apache-2.0: https://www.apache.org/licenses/license-2.0 diff --git a/packages/interface-keys/LICENSE-APACHE b/packages/interface-keys/LICENSE-APACHE new file mode 100644 index 0000000000..14478a3b60 --- /dev/null +++ b/packages/interface-keys/LICENSE-APACHE @@ -0,0 +1,5 @@ +Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. diff --git a/packages/interface-keys/LICENSE-MIT b/packages/interface-keys/LICENSE-MIT new file mode 100644 index 0000000000..72dc60d84b --- /dev/null +++ b/packages/interface-keys/LICENSE-MIT @@ -0,0 +1,19 @@ +The MIT License (MIT) + +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/packages/interface-keys/README.md b/packages/interface-keys/README.md new file mode 100644 index 0000000000..227c3c2702 --- /dev/null +++ b/packages/interface-keys/README.md @@ -0,0 +1,56 @@ +# @libp2p/interface-keys + +[![libp2p.io](https://img.shields.io/badge/project-libp2p-yellow.svg?style=flat-square)](http://libp2p.io/) +[![Discuss](https://img.shields.io/discourse/https/discuss.libp2p.io/posts.svg?style=flat-square)](https://discuss.libp2p.io) +[![codecov](https://img.shields.io/codecov/c/github/libp2p/js-libp2p.svg?style=flat-square)](https://codecov.io/gh/libp2p/js-libp2p) +[![CI](https://img.shields.io/github/actions/workflow/status/libp2p/js-libp2p/js-test-and-release.yml?branch=master\&style=flat-square)](https://github.com/libp2p/js-libp2p/actions/workflows/js-test-and-release.yml?query=branch%3Amaster) + +> Keys interface for libp2p + +## Table of contents + +- [Install](#install) +- [Using the Test Suite](#using-the-test-suite) +- [API Docs](#api-docs) +- [License](#license) +- [Contribution](#contribution) + +## Install + +```console +$ npm i @libp2p/interface-keys +``` + +## Using the Test Suite + +You can also check out the [internal test suite](../../test/crypto/compliance.spec.js) to see the setup in action. + +```js +const tests = require('libp2p-interfaces-compliance-tests/keys') +const yourKeys = require('./your-keys') + +tests({ + setup () { + // Set up your keys if needed, then return it + return yourKeys + }, + teardown () { + // Clean up your keys if needed + } +}) +``` + +## API Docs + +- + +## License + +Licensed under either of + +- Apache 2.0, ([LICENSE-APACHE](LICENSE-APACHE) / ) +- MIT ([LICENSE-MIT](LICENSE-MIT) / ) + +## Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. diff --git a/packages/interface-keys/package.json b/packages/interface-keys/package.json new file mode 100644 index 0000000000..2767047daf --- /dev/null +++ b/packages/interface-keys/package.json @@ -0,0 +1,136 @@ +{ + "name": "@libp2p/interface-keys", + "version": "1.0.8", + "description": "Keys interface for libp2p", + "license": "Apache-2.0 OR MIT", + "homepage": "https://github.com/libp2p/js-libp2p/tree/master/packages/interface-keys#readme", + "repository": { + "type": "git", + "url": "git+https://github.com/libp2p/js-libp2p.git" + }, + "bugs": { + "url": "https://github.com/libp2p/js-libp2p/issues" + }, + "keywords": [ + "interface", + "libp2p" + ], + "type": "module", + "types": "./dist/src/index.d.ts", + "files": [ + "src", + "dist", + "!dist/test", + "!**/*.tsbuildinfo" + ], + "exports": { + ".": { + "types": "./dist/src/index.d.ts", + "import": "./dist/src/index.js" + } + }, + "eslintConfig": { + "extends": "ipfs", + "parserOptions": { + "sourceType": "module" + } + }, + "release": { + "branches": [ + "master" + ], + "plugins": [ + [ + "@semantic-release/commit-analyzer", + { + "preset": "conventionalcommits", + "releaseRules": [ + { + "breaking": true, + "release": "major" + }, + { + "revert": true, + "release": "patch" + }, + { + "type": "feat", + "release": "minor" + }, + { + "type": "fix", + "release": "patch" + }, + { + "type": "docs", + "release": "patch" + }, + { + "type": "test", + "release": "patch" + }, + { + "type": "deps", + "release": "patch" + }, + { + "scope": "no-release", + "release": false + } + ] + } + ], + [ + "@semantic-release/release-notes-generator", + { + "preset": "conventionalcommits", + "presetConfig": { + "types": [ + { + "type": "feat", + "section": "Features" + }, + { + "type": "fix", + "section": "Bug Fixes" + }, + { + "type": "chore", + "section": "Trivial Changes" + }, + { + "type": "docs", + "section": "Documentation" + }, + { + "type": "deps", + "section": "Dependencies" + }, + { + "type": "test", + "section": "Tests" + } + ] + } + } + ], + "@semantic-release/changelog", + "@semantic-release/npm", + "@semantic-release/github", + "@semantic-release/git" + ] + }, + "scripts": { + "clean": "aegir clean", + "lint": "aegir lint", + "dep-check": "aegir dep-check", + "build": "aegir build", + "release": "aegir release" + }, + "devDependencies": { + "aegir": "^39.0.5" + }, + "typedoc": { + "entryPoint": "./src/index.ts" + } +} diff --git a/packages/interface-keys/src/index.ts b/packages/interface-keys/src/index.ts new file mode 100644 index 0000000000..a201e0487b --- /dev/null +++ b/packages/interface-keys/src/index.ts @@ -0,0 +1,38 @@ + +export interface PublicKey { + readonly bytes: Uint8Array + verify: (data: Uint8Array, sig: Uint8Array) => Promise + marshal: () => Uint8Array + equals: (key: PublicKey) => boolean + hash: () => Promise +} + +/** + * Generic private key interface + */ +export interface PrivateKey { + readonly public: PublicKey + readonly bytes: Uint8Array + sign: (data: Uint8Array) => Promise + marshal: () => Uint8Array + equals: (key: PrivateKey) => boolean + hash: () => Promise + /** + * Gets the ID of the key. + * + * The key id is the base58 encoding of the SHA-256 multihash of its public key. + * The public key is a protobuf encoding containing a type and the DER encoding + * of the PKCS SubjectPublicKeyInfo. + */ + id: () => Promise + /** + * Exports the password protected key in the format specified. + */ + export: (password: string, format?: 'pkcs-8' | string) => Promise +} + +export const Ed25519 = 'Ed25519' +export const RSA = 'RSA' +export const secp256k1 = 'secp256k1' + +export type KeyType = typeof Ed25519 | typeof RSA | typeof secp256k1 diff --git a/packages/interface-keys/tsconfig.json b/packages/interface-keys/tsconfig.json new file mode 100644 index 0000000000..5fe8ea40d7 --- /dev/null +++ b/packages/interface-keys/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "aegir/src/config/tsconfig.aegir.json", + "compilerOptions": { + "outDir": "dist" + }, + "include": [ + "src" + ] +} diff --git a/packages/interface-libp2p/CHANGELOG.md b/packages/interface-libp2p/CHANGELOG.md new file mode 100644 index 0000000000..1c294f09b8 --- /dev/null +++ b/packages/interface-libp2p/CHANGELOG.md @@ -0,0 +1,235 @@ +## [@libp2p/interface-libp2p-v3.2.0](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-libp2p-v3.1.0...@libp2p/interface-libp2p-v3.2.0) (2023-05-19) + + +### Features + +* add start/stop events to libp2p interface ([#407](https://github.com/libp2p/js-libp2p-interfaces/issues/407)) ([016c1e8](https://github.com/libp2p/js-libp2p-interfaces/commit/016c1e82b060c93c80546cd8c493ec6e6c97cbec)) + +## [@libp2p/interface-libp2p-v3.1.0](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-libp2p-v3.0.1...@libp2p/interface-libp2p-v3.1.0) (2023-05-05) + + +### Features + +* add peer:identify event to libp2p ([#395](https://github.com/libp2p/js-libp2p-interfaces/issues/395)) ([6aee82a](https://github.com/libp2p/js-libp2p-interfaces/commit/6aee82ad81a752f204ec27838ccd6de4908aeb0e)) + +## [@libp2p/interface-libp2p-v3.0.1](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-libp2p-v3.0.0...@libp2p/interface-libp2p-v3.0.1) (2023-05-04) + + +### Dependencies + +* bump aegir from 38.1.8 to 39.0.5 ([#393](https://github.com/libp2p/js-libp2p-interfaces/issues/393)) ([31f3797](https://github.com/libp2p/js-libp2p-interfaces/commit/31f3797b24f7c23f3f16e9db3a230bd5f7cd5175)) + +## [@libp2p/interface-libp2p-v3.0.0](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-libp2p-v2.0.0...@libp2p/interface-libp2p-v3.0.0) (2023-04-27) + + +### ⚠ BREAKING CHANGES + +* pubsub, dht, ping, fetch and identify have been removed - re-enable these by passing them as services + +### Features + +* allow user defined services ([#375](https://github.com/libp2p/js-libp2p-interfaces/issues/375)) ([13cf442](https://github.com/libp2p/js-libp2p-interfaces/commit/13cf442ff29acbe28cf75431dee02bfefd9a4e40)) + + +### Documentation + +* fix typos in docs ([#386](https://github.com/libp2p/js-libp2p-interfaces/issues/386)) ([8ec2cdc](https://github.com/libp2p/js-libp2p-interfaces/commit/8ec2cdcc5deed76e0c673a75c27bf7a2e931eea1)) + +## [@libp2p/interface-libp2p-v2.0.0](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-libp2p-v1.3.2...@libp2p/interface-libp2p-v2.0.0) (2023-04-21) + + +### ⚠ BREAKING CHANGES + +* add libp2p events (#373) + +### Features + +* add libp2p events ([#373](https://github.com/libp2p/js-libp2p-interfaces/issues/373)) ([071c718](https://github.com/libp2p/js-libp2p-interfaces/commit/071c718808902858818ca86167b51b242b67a5a5)) + + +### Dependencies + +* update sibling dependencies ([17ed429](https://github.com/libp2p/js-libp2p-interfaces/commit/17ed429d57e83cb38484ac52a0e0975a7d8af963)) +* update sibling dependencies ([4421374](https://github.com/libp2p/js-libp2p-interfaces/commit/4421374d85ac7d4e9cf0b1a4c5072e881e091b31)) + +## [@libp2p/interface-libp2p-v1.3.2](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-libp2p-v1.3.1...@libp2p/interface-libp2p-v1.3.2) (2023-04-21) + + +### Dependencies + +* update sibling dependencies ([bc1588c](https://github.com/libp2p/js-libp2p-interfaces/commit/bc1588c70ffa35c1ba9c954090a7ac8087a22b0c)) + +## [@libp2p/interface-libp2p-v1.3.1](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-libp2p-v1.3.0...@libp2p/interface-libp2p-v1.3.1) (2023-04-18) + + +### Dependencies + +* update sibling dependencies ([3d23367](https://github.com/libp2p/js-libp2p-interfaces/commit/3d233676a17299bfa1b59d309543598176826523)) +* update sibling dependencies ([2f52a28](https://github.com/libp2p/js-libp2p-interfaces/commit/2f52a284b59c0a88b040f86da1f5d3f044727f2c)) + +## [@libp2p/interface-libp2p-v1.3.0](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-libp2p-v1.2.0...@libp2p/interface-libp2p-v1.3.0) (2023-04-17) + + +### Features + +* expose dial queue inspection method ([#374](https://github.com/libp2p/js-libp2p-interfaces/issues/374)) ([973263f](https://github.com/libp2p/js-libp2p-interfaces/commit/973263f582d39a5b727c9fd90abeea7ed72a9aff)) + +## [@libp2p/interface-libp2p-v1.2.0](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-libp2p-v1.1.2...@libp2p/interface-libp2p-v1.2.0) (2023-04-11) + + +### Features + +* support batch dialling ([#351](https://github.com/libp2p/js-libp2p-interfaces/issues/351)) ([e46b72b](https://github.com/libp2p/js-libp2p-interfaces/commit/e46b72b1731ff935a1f0d755cbaf6f3159060ed3)) + + +### Dependencies + +* update sibling dependencies ([b034810](https://github.com/libp2p/js-libp2p-interfaces/commit/b0348102e41dc18166e70063f4708a2b3544f4b6)) + +## [@libp2p/interface-libp2p-v1.1.2](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-libp2p-v1.1.1...@libp2p/interface-libp2p-v1.1.2) (2023-03-17) + + +### Dependencies + +* update @multiformats/multiaddr to 12.0.0 ([#354](https://github.com/libp2p/js-libp2p-interfaces/issues/354)) ([e0f327b](https://github.com/libp2p/js-libp2p-interfaces/commit/e0f327b5d54e240feabadce21a841629d633ec5e)) + +## [@libp2p/interface-libp2p-v1.1.1](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-libp2p-v1.1.0...@libp2p/interface-libp2p-v1.1.1) (2023-01-18) + + +### Dependencies + +* bump aegir from 37.12.1 to 38.1.0 ([#335](https://github.com/libp2p/js-libp2p-interfaces/issues/335)) ([7368a36](https://github.com/libp2p/js-libp2p-interfaces/commit/7368a363423a08e8fa247dcb76ea13e4cf030d65)) + +## [@libp2p/interface-libp2p-v1.1.0](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-libp2p-v1.0.2...@libp2p/interface-libp2p-v1.1.0) (2023-01-07) + + +### Features + +* add register and unregister methods to libp2p type ([#332](https://github.com/libp2p/js-libp2p-interfaces/issues/332)) ([e37c55a](https://github.com/libp2p/js-libp2p-interfaces/commit/e37c55a62b1b4a927996ca9ea2a311651640de7f)) + + +### Trivial Changes + +* remove lerna ([#330](https://github.com/libp2p/js-libp2p-interfaces/issues/330)) ([6678592](https://github.com/libp2p/js-libp2p-interfaces/commit/6678592dd0cf601a2671852f9d2a0aff5dee2b18)) + +## [@libp2p/interface-libp2p-v1.0.2](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-libp2p-v1.0.1...@libp2p/interface-libp2p-v1.0.2) (2023-01-06) + + +### Dependencies + +* update sibling dependencies ([acf0058](https://github.com/libp2p/js-libp2p-interfaces/commit/acf0058696e343f3330e63e45a85a520424d0bd8)) +* update sibling dependencies ([29515c6](https://github.com/libp2p/js-libp2p-interfaces/commit/29515c65f84203cdbdd5f5562acc0e8cbbda6664)) +* update sibling dependencies ([b599221](https://github.com/libp2p/js-libp2p-interfaces/commit/b599221d9044a0c573bac40c57e70a842930b253)) +* update sibling dependencies ([b50e621](https://github.com/libp2p/js-libp2p-interfaces/commit/b50e621d31a8b32affc3fadb9f97c4883d577f93)) + +## [@libp2p/interface-libp2p-v1.0.1](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-libp2p-v1.0.0...@libp2p/interface-libp2p-v1.0.1) (2022-12-21) + + +### Bug Fixes + +* add getProtocols method, events, and identify service ([#326](https://github.com/libp2p/js-libp2p-interfaces/issues/326)) ([b036505](https://github.com/libp2p/js-libp2p-interfaces/commit/b036505100d32742065190e47d1803cbd8f61f4a)) + +## @libp2p/interface-libp2p-v1.0.0 (2022-12-19) + + +### Features + +* add libp2p interface ([#325](https://github.com/libp2p/js-libp2p-interfaces/issues/325)) ([79a474d](https://github.com/libp2p/js-libp2p-interfaces/commit/79a474d8eda95ad3ff3bcdb2a15bfcf778f51772)) + +## [@libp2p/interface-metrics-v4.0.4](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-metrics-v4.0.3...@libp2p/interface-metrics-v4.0.4) (2022-12-16) + + +### Documentation + +* update project config ([#323](https://github.com/libp2p/js-libp2p-interfaces/issues/323)) ([0fc6a08](https://github.com/libp2p/js-libp2p-interfaces/commit/0fc6a08e9cdcefe361fe325281a3a2a03759ff59)) + +## [@libp2p/interface-metrics-v4.0.3](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-metrics-v4.0.2...@libp2p/interface-metrics-v4.0.3) (2022-12-14) + + +### Bug Fixes + +* generate docs for all packages ([#321](https://github.com/libp2p/js-libp2p-interfaces/issues/321)) ([b6f8b32](https://github.com/libp2p/js-libp2p-interfaces/commit/b6f8b32a920c15a28fe021e6050e31aaae89d518)) + +## [@libp2p/interface-metrics-v4.0.2](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-metrics-v4.0.1...@libp2p/interface-metrics-v4.0.2) (2022-11-05) + + +### Bug Fixes + +* metrics only need numbers ([#312](https://github.com/libp2p/js-libp2p-interfaces/issues/312)) ([0076c1f](https://github.com/libp2p/js-libp2p-interfaces/commit/0076c1f354ebc1106b6ac42d48688c0209866084)) + +## [@libp2p/interface-metrics-v4.0.1](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-metrics-v4.0.0...@libp2p/interface-metrics-v4.0.1) (2022-11-05) + + +### Bug Fixes + +* update project config ([#311](https://github.com/libp2p/js-libp2p-interfaces/issues/311)) ([27dd0ce](https://github.com/libp2p/js-libp2p-interfaces/commit/27dd0ce3c249892ac69cbb24ddaf0b9f32385e37)) + +## [@libp2p/interface-metrics-v4.0.0](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-metrics-v3.0.0...@libp2p/interface-metrics-v4.0.0) (2022-11-05) + + +### ⚠ BREAKING CHANGES + +* the global/per-peer moving average tracking has been removed from the interface as it's expensive and requires lots of timers - this functionality can be replicated by implementations if it's desirable. It's better to have simple counters instead and let an external system like Prometheus or Graphana calculate the values over time + +### Features + +* return metrics objects from register instead of updating with an options object ([#310](https://github.com/libp2p/js-libp2p-interfaces/issues/310)) ([3b106ce](https://github.com/libp2p/js-libp2p-interfaces/commit/3b106ce799b5d84a82a66238995e09970ed8116c)) + +## [@libp2p/interface-metrics-v3.0.0](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-metrics-v2.0.0...@libp2p/interface-metrics-v3.0.0) (2022-08-07) + + +### ⚠ BREAKING CHANGES + +* change stream muxer interface (#279) + +### Features + +* change stream muxer interface ([#279](https://github.com/libp2p/js-libp2p-interfaces/issues/279)) ([1ebe269](https://github.com/libp2p/js-libp2p-interfaces/commit/1ebe26988b6a286f36a4fc5177f502cfb60368a1)) + + +### Trivial Changes + +* update project config ([#271](https://github.com/libp2p/js-libp2p-interfaces/issues/271)) ([59c0bf5](https://github.com/libp2p/js-libp2p-interfaces/commit/59c0bf5e0b05496fca2e4902632b61bb41fad9e9)) + +## [@libp2p/interface-metrics-v2.0.0](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-metrics-v1.0.3...@libp2p/interface-metrics-v2.0.0) (2022-07-01) + + +### ⚠ BREAKING CHANGES + +* the return type of `metrics.getComponentMetrics` has been changed to include optional labels/help text and also is now a function that returns a single or group value + +### Features + +* add metrics groups ([#267](https://github.com/libp2p/js-libp2p-interfaces/issues/267)) ([b9d898a](https://github.com/libp2p/js-libp2p-interfaces/commit/b9d898abdb551ebe2e0e961ec325d5e6abcf4fab)), closes [#257](https://github.com/libp2p/js-libp2p-interfaces/issues/257) [#258](https://github.com/libp2p/js-libp2p-interfaces/issues/258) + +## [@libp2p/interface-metrics-v1.0.3](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-metrics-v1.0.2...@libp2p/interface-metrics-v1.0.3) (2022-06-27) + + +### Trivial Changes + +* update deps ([#262](https://github.com/libp2p/js-libp2p-interfaces/issues/262)) ([51edf7d](https://github.com/libp2p/js-libp2p-interfaces/commit/51edf7d9b3765a6f75c915b1483ea345d0133a41)) + +## [@libp2p/interface-metrics-v1.0.2](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-metrics-v1.0.1...@libp2p/interface-metrics-v1.0.2) (2022-06-14) + + +### Trivial Changes + +* update aegir ([#234](https://github.com/libp2p/js-libp2p-interfaces/issues/234)) ([3e03895](https://github.com/libp2p/js-libp2p-interfaces/commit/3e038959ecab6cfa3585df9ee179c0af7a61eda5)) + +## [@libp2p/interface-metrics-v1.0.1](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-metrics-v1.0.0...@libp2p/interface-metrics-v1.0.1) (2022-06-14) + + +### Trivial Changes + +* update readmes ([#233](https://github.com/libp2p/js-libp2p-interfaces/issues/233)) ([ee7da38](https://github.com/libp2p/js-libp2p-interfaces/commit/ee7da38dccc08160d26c8436df8739ce7e0b340e)) + +## @libp2p/interface-metrics-v1.0.0 (2022-06-14) + + +### ⚠ BREAKING CHANGES + +* most modules have been split out of the `@libp2p/interfaces` and `@libp2p/interface-compliance-tests` packages + +### Trivial Changes + +* break modules apart ([#232](https://github.com/libp2p/js-libp2p-interfaces/issues/232)) ([385614e](https://github.com/libp2p/js-libp2p-interfaces/commit/385614e772329052ab17415c8bd421f65b01a61b)), closes [#226](https://github.com/libp2p/js-libp2p-interfaces/issues/226) diff --git a/packages/interface-libp2p/LICENSE b/packages/interface-libp2p/LICENSE new file mode 100644 index 0000000000..20ce483c86 --- /dev/null +++ b/packages/interface-libp2p/LICENSE @@ -0,0 +1,4 @@ +This project is dual licensed under MIT and Apache-2.0. + +MIT: https://www.opensource.org/licenses/mit +Apache-2.0: https://www.apache.org/licenses/license-2.0 diff --git a/packages/interface-libp2p/LICENSE-APACHE b/packages/interface-libp2p/LICENSE-APACHE new file mode 100644 index 0000000000..14478a3b60 --- /dev/null +++ b/packages/interface-libp2p/LICENSE-APACHE @@ -0,0 +1,5 @@ +Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. diff --git a/packages/interface-libp2p/LICENSE-MIT b/packages/interface-libp2p/LICENSE-MIT new file mode 100644 index 0000000000..72dc60d84b --- /dev/null +++ b/packages/interface-libp2p/LICENSE-MIT @@ -0,0 +1,19 @@ +The MIT License (MIT) + +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/packages/interface-libp2p/README.md b/packages/interface-libp2p/README.md new file mode 100644 index 0000000000..80044041ec --- /dev/null +++ b/packages/interface-libp2p/README.md @@ -0,0 +1,36 @@ +# @libp2p/interface-libp2p + +[![libp2p.io](https://img.shields.io/badge/project-libp2p-yellow.svg?style=flat-square)](http://libp2p.io/) +[![Discuss](https://img.shields.io/discourse/https/discuss.libp2p.io/posts.svg?style=flat-square)](https://discuss.libp2p.io) +[![codecov](https://img.shields.io/codecov/c/github/libp2p/js-libp2p.svg?style=flat-square)](https://codecov.io/gh/libp2p/js-libp2p) +[![CI](https://img.shields.io/github/actions/workflow/status/libp2p/js-libp2p/js-test-and-release.yml?branch=master\&style=flat-square)](https://github.com/libp2p/js-libp2p/actions/workflows/js-test-and-release.yml?query=branch%3Amaster) + +> The interface implemented by a libp2p node + +## Table of contents + +- [Install](#install) +- [API Docs](#api-docs) +- [License](#license) +- [Contribution](#contribution) + +## Install + +```console +$ npm i @libp2p/interface-libp2p +``` + +## API Docs + +- + +## License + +Licensed under either of + +- Apache 2.0, ([LICENSE-APACHE](LICENSE-APACHE) / ) +- MIT ([LICENSE-MIT](LICENSE-MIT) / ) + +## Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. diff --git a/packages/interface-libp2p/package.json b/packages/interface-libp2p/package.json new file mode 100644 index 0000000000..5178f5a138 --- /dev/null +++ b/packages/interface-libp2p/package.json @@ -0,0 +1,150 @@ +{ + "name": "@libp2p/interface-libp2p", + "version": "3.2.0", + "description": "The interface implemented by a libp2p node", + "license": "Apache-2.0 OR MIT", + "homepage": "https://github.com/libp2p/js-libp2p/tree/master/packages/interface-libp2p#readme", + "repository": { + "type": "git", + "url": "git+https://github.com/libp2p/js-libp2p.git" + }, + "bugs": { + "url": "https://github.com/libp2p/js-libp2p/issues" + }, + "keywords": [ + "interface", + "libp2p" + ], + "type": "module", + "types": "./dist/src/index.d.ts", + "files": [ + "src", + "dist", + "!dist/test", + "!**/*.tsbuildinfo" + ], + "exports": { + ".": { + "types": "./dist/src/index.d.ts", + "import": "./dist/src/index.js" + } + }, + "eslintConfig": { + "extends": "ipfs", + "parserOptions": { + "sourceType": "module" + } + }, + "release": { + "branches": [ + "master" + ], + "plugins": [ + [ + "@semantic-release/commit-analyzer", + { + "preset": "conventionalcommits", + "releaseRules": [ + { + "breaking": true, + "release": "major" + }, + { + "revert": true, + "release": "patch" + }, + { + "type": "feat", + "release": "minor" + }, + { + "type": "fix", + "release": "patch" + }, + { + "type": "docs", + "release": "patch" + }, + { + "type": "test", + "release": "patch" + }, + { + "type": "deps", + "release": "patch" + }, + { + "scope": "no-release", + "release": false + } + ] + } + ], + [ + "@semantic-release/release-notes-generator", + { + "preset": "conventionalcommits", + "presetConfig": { + "types": [ + { + "type": "feat", + "section": "Features" + }, + { + "type": "fix", + "section": "Bug Fixes" + }, + { + "type": "chore", + "section": "Trivial Changes" + }, + { + "type": "docs", + "section": "Documentation" + }, + { + "type": "deps", + "section": "Dependencies" + }, + { + "type": "test", + "section": "Tests" + } + ] + } + } + ], + "@semantic-release/changelog", + "@semantic-release/npm", + "@semantic-release/github", + "@semantic-release/git" + ] + }, + "scripts": { + "clean": "aegir clean", + "lint": "aegir lint", + "dep-check": "aegir dep-check", + "build": "aegir build", + "release": "aegir release" + }, + "dependencies": { + "@libp2p/interface-connection": "^5.0.0", + "@libp2p/interface-content-routing": "^2.0.0", + "@libp2p/interface-keychain": "^2.0.0", + "@libp2p/interface-metrics": "^4.0.0", + "@libp2p/interface-peer-id": "^2.0.0", + "@libp2p/interface-peer-info": "^1.0.0", + "@libp2p/interface-peer-routing": "^1.0.0", + "@libp2p/interface-peer-store": "^2.0.0", + "@libp2p/interface-registrar": "^2.0.0", + "@libp2p/interface-transport": "^4.0.0", + "@libp2p/interfaces": "^3.0.0", + "@multiformats/multiaddr": "^12.0.0" + }, + "devDependencies": { + "aegir": "^39.0.5" + }, + "typedoc": { + "entryPoint": "./src/index.ts" + } +} diff --git a/packages/interface-libp2p/src/index.ts b/packages/interface-libp2p/src/index.ts new file mode 100644 index 0000000000..3e0a606cae --- /dev/null +++ b/packages/interface-libp2p/src/index.ts @@ -0,0 +1,599 @@ +/** + * @packageDocumentation + * + * Exports a `Libp2p` type for modules to use as a type argument. + * + * @example + * + * ```typescript + * import type { Libp2p } from '@libp2p/interface-libp2p' + * + * function doSomethingWithLibp2p (node: Libp2p) { + * // ... + * } + * ``` + */ + +import type { Connection, Stream } from '@libp2p/interface-connection' +import type { ContentRouting } from '@libp2p/interface-content-routing' +import type { KeyChain } from '@libp2p/interface-keychain' +import type { Metrics } from '@libp2p/interface-metrics' +import type { PeerId } from '@libp2p/interface-peer-id' +import type { PeerInfo } from '@libp2p/interface-peer-info' +import type { PeerRouting } from '@libp2p/interface-peer-routing' +import type { Address, Peer, PeerStore } from '@libp2p/interface-peer-store' +import type { StreamHandler, StreamHandlerOptions, Topology } from '@libp2p/interface-registrar' +import type { Listener } from '@libp2p/interface-transport' +import type { AbortOptions } from '@libp2p/interfaces' +import type { EventEmitter } from '@libp2p/interfaces/events' +import type { Startable } from '@libp2p/interfaces/startable' +import type { Multiaddr } from '@multiformats/multiaddr' + +/** + * Used by the connection manager to sort addresses into order before dialling + */ +export interface AddressSorter { + (a: Address, b: Address): -1 | 0 | 1 +} + +/** + * Event detail emitted when peer data changes + */ +export interface PeerUpdate { + peer: Peer + previous?: Peer +} + +/** + * Peer data signed by the remote Peer's public key + */ +export interface SignedPeerRecord { + addresses: Multiaddr[] + seq: bigint +} + +/** + * Data returned from a successful identify response + */ +export interface IdentifyResult { + /** + * The remote Peer's PeerId + */ + peerId: PeerId + + /** + * The unsigned addresses they are listening on. Note - any multiaddrs present + * in the signed peer record should be preferred to the value here. + */ + listenAddrs: Multiaddr[] + + /** + * The protocols the remote peer supports + */ + protocols: string[] + + /** + * The remote protocol version + */ + protocolVersion?: string + + /** + * The remote agent version + */ + agentVersion?: string + + /** + * The public key part of the remote PeerId - this is only useful for older + * RSA-based PeerIds, the more modern Ed25519 and secp256k1 types have the + * public key embedded in them + */ + publicKey?: Uint8Array + + /** + * If set this is the address that the remote peer saw the identify request + * originate from + */ + observedAddr?: Multiaddr + + /** + * If sent by the remote peer this is the deserialized signed peer record + */ + signedPeerRecord?: SignedPeerRecord +} + +/** + * Once you have a libp2p instance, you can listen to several events it emits, + * so that you can be notified of relevant network events. + * + * Event names are `noun:verb` so the first part is the name of the object + * being acted on and the second is the action. + */ +export interface Libp2pEvents { + /** + * This event is dispatched when a new network peer is discovered. + * + * @example + * + * ```js + * libp2p.addEventListener('peer:discovery', (event) => { + * const peerInfo = event.detail + * // ... + * }) + * ``` + */ + 'peer:discovery': CustomEvent + + /** + * This event will be triggered any time a new peer connects. + * + * @example + * + * ```js + * libp2p.addEventListener('peer:connect', (event) => { + * const peerId = event.detail + * // ... + * }) + * ``` + */ + 'peer:connect': CustomEvent + + /** + * This event will be triggered any time we are disconnected from another peer, regardless of + * the circumstances of that disconnection. If we happen to have multiple connections to a + * peer, this event will **only** be triggered when the last connection is closed. + * + * @example + * + * ```js + * libp2p.addEventListener('peer:disconnect', (event) => { + * const peerId = event.detail + * // ... + * }) + * ``` + */ + 'peer:disconnect': CustomEvent + + /** + * This event is dispatched after a remote peer has successfully responded to the identify + * protocol. Note that for this to be emitted, both peers must have an identify service + * configured. + * + * @example + * + * ```js + * libp2p.addEventListener('peer:identify', (event) => { + * const identifyResult = event.detail + * // ... + * }) + * ``` + */ + 'peer:identify': CustomEvent + + /** + * This event is dispatched when the peer store data for a peer has been + * updated - e.g. their multiaddrs, protocols etc have changed. + * + * If they were previously known to this node, the old peer data will be + * set in the `previous` field. + * + * This may be in response to the identify protocol running, a manual + * update or some other event. + */ + 'peer:update': CustomEvent + + /** + * This event is dispatched when the current node's peer record changes - + * for example a transport started listening on a new address or a new + * protocol handler was registered. + * + * @example + * + * ```js + * libp2p.addEventListener('self:peer:update', (event) => { + * const { peer } = event.detail + * // ... + * }) + * ``` + */ + 'self:peer:update': CustomEvent + + /** + * This event is dispatched when a transport begins listening on a new address + */ + 'transport:listening': CustomEvent + + /** + * This event is dispatched when a transport stops listening on an address + */ + 'transport:close': CustomEvent + + /** + * This event is dispatched when the connection manager has more than the + * configured allowable max connections and has closed some connections to + * bring the node back under the limit. + */ + 'connection:prune': CustomEvent + + /** + * This event notifies listeners when new incoming or outgoing connections + * are opened. + */ + 'connection:open': CustomEvent + + /** + * This event notifies listeners when incoming or outgoing connections are + * closed. + */ + 'connection:close': CustomEvent + + /** + * This event notifies listeners that the node has started + * + * ```js + * libp2p.addEventListener('start', (event) => { + * console.info(libp2p.isStarted()) // true + * }) + * ``` + */ + 'start': CustomEvent> + + /** + * This event notifies listeners that the node has stopped + * + * ```js + * libp2p.addEventListener('stop', (event) => { + * console.info(libp2p.isStarted()) // false + * }) + * ``` + */ + 'stop': CustomEvent> +} + +/** + * A map of user defined services available on the libp2p node via the + * `services` key + * + * @example + * + * ```js + * const node = await createLibp2p({ + * // ...other options + * services: { + * myService: myService({ + * // ...service options + * }) + * } + * }) + * + * // invoke methods on the service + * node.services.myService.anOperation() + * ``` + */ +export type ServiceMap = Record + +export type PendingDialStatus = 'queued' | 'active' | 'error' | 'success' + +/** + * An item in the dial queue + */ +export interface PendingDial { + /** + * A unique identifier for this dial + */ + id: string + + /** + * The current status of the dial + */ + status: PendingDialStatus + + /** + * If known, this is the peer id that libp2p expects to be dialling + */ + peerId?: PeerId + + /** + * The list of multiaddrs that will be dialled. The returned connection will + * use the first address that succeeds, all other dials part of this pending + * dial will be cancelled. + */ + multiaddrs: Multiaddr[] +} + +/** + * Libp2p nodes implement this interface. + */ +export interface Libp2p extends Startable, EventEmitter> { + /** + * The PeerId is a unique identifier for a node on the network. + * + * It is the hash of an RSA public key or, for Ed25519 or secp256k1 keys, + * the key itself. + * + * @example + * + * ```js + * console.info(libp2p.peerId) + * // PeerId(12D3Foo...) + * ```` + */ + peerId: PeerId + + /** + * The peer store holds information we know about other peers on the network. + * - multiaddrs, supported protocols, etc. + * + * @example + * + * ```js + * const peer = await libp2p.peerStore.get(peerId) + * console.info(peer) + * // { id: PeerId(12D3Foo...), addresses: [] ... } + * ``` + */ + peerStore: PeerStore + + /** + * The peer routing subsystem allows the user to find peers on the network + * or to find peers close to binary keys. + * + * @example + * + * ```js + * const peerInfo = await libp2p.peerRouting.findPeer(peerId) + * console.info(peerInfo) + * // { id: PeerId(12D3Foo...), multiaddrs: [] ... } + * ``` + * + * @example + * + * ```js + * for await (const peerInfo of libp2p.peerRouting.getClosestPeers(key)) { + * console.info(peerInfo) + * // { id: PeerId(12D3Foo...), multiaddrs: [] ... } + * } + * ``` + */ + peerRouting: PeerRouting + + /** + * The content routing subsystem allows the user to find providers for content, + * let the network know they are providers for content, and get/put values to + * the DHT. + * + * @example + * + * ```js + * for await (const peerInfo of libp2p.contentRouting.findProviders(cid)) { + * console.info(peerInfo) + * // { id: PeerId(12D3Foo...), multiaddrs: [] ... } + * } + * ``` + */ + contentRouting: ContentRouting + + /** + * The keychain contains the keys used by the current node, and can create new + * keys, export them, import them, etc. + * + * @example + * + * ```js + * const keyInfo = await libp2p.keychain.createKey('new key') + * console.info(keyInfo) + * // { id: '...', name: 'new key' } + * ``` + */ + keychain: KeyChain + + /** + * The metrics subsystem allows recording values to assess the health/performance + * of the running node. + * + * @example + * + * ```js + * const metric = libp2p.metrics.registerMetric({ + * 'my-metric' + * }) + * + * // later + * metric.update(5) + * ``` + */ + metrics?: Metrics + + /** + * Get a deduplicated list of peer advertising multiaddrs by concatenating + * the listen addresses used by transports with any configured + * announce addresses as well as observed addresses reported by peers. + * + * If Announce addrs are specified, configured listen addresses will be + * ignored though observed addresses will still be included. + * + * @example + * + * ```js + * const listenMa = libp2p.getMultiaddrs() + * // [ ] + * ``` + */ + getMultiaddrs: () => Multiaddr[] + + /** + * Returns a list of supported protocols + * + * @example + * + * ```js + * const protocols = libp2p.getProtocols() + * // [ '/ipfs/ping/1.0.0', '/ipfs/id/1.0.0' ] + * ``` + */ + getProtocols: () => string[] + + /** + * Return a list of all connections this node has open, optionally filtering + * by a PeerId + * + * @example + * + * ```js + * for (const connection of libp2p.getConnections()) { + * console.log(peerId, connection.remoteAddr.toString()) + * // Logs the PeerId string and the observed remote multiaddr of each Connection + * } + * ``` + */ + getConnections: (peerId?: PeerId) => Connection[] + + /** + * Return the list of dials currently in progress or queued to start + * + * @example + * + * ```js + * for (const pendingDial of libp2p.getDialQueue()) { + * console.log(pendingDial) + * } + * ``` + */ + getDialQueue: () => PendingDial[] + + /** + * Return a list of all peers we currently have a connection open to + */ + getPeers: () => PeerId[] + + /** + * Dials to the provided peer. If successful, the known metadata of the + * peer will be added to the nodes `peerStore`. + * + * If a PeerId is passed as the first argument, the peer will need to have known multiaddrs for it in the PeerStore. + * + * @example + * + * ```js + * const conn = await libp2p.dial(remotePeerId) + * + * // create a new stream within the connection + * const { stream, protocol } = await conn.newStream(['/echo/1.1.0', '/echo/1.0.0']) + * + * // protocol negotiated: 'echo/1.0.0' means that the other party only supports the older version + * + * // ... + * await conn.close() + * ``` + */ + dial: (peer: PeerId | Multiaddr | Multiaddr[], options?: AbortOptions) => Promise + + /** + * Dials to the provided peer and tries to handshake with the given protocols in order. + * If successful, the known metadata of the peer will be added to the nodes `peerStore`, + * and the `MuxedStream` will be returned together with the successful negotiated protocol. + * + * @example + * + * ```js + * import { pipe } from 'it-pipe' + * + * const { stream, protocol } = await libp2p.dialProtocol(remotePeerId, protocols) + * + * // Use this new stream like any other duplex stream + * pipe([1, 2, 3], stream, consume) + * ``` + */ + dialProtocol: (peer: PeerId | Multiaddr | Multiaddr[], protocols: string | string[], options?: AbortOptions) => Promise + + /** + * Attempts to gracefully close an open connection to the given peer. If the connection is not closed in the grace period, it will be forcefully closed. + * + * @example + * + * ```js + * await libp2p.hangUp(remotePeerId) + * ``` + */ + hangUp: (peer: PeerId | Multiaddr) => Promise + + /** + * Sets up [multistream-select routing](https://github.com/multiformats/multistream-select) of protocols to their application handlers. Whenever a stream is opened on one of the provided protocols, the handler will be called. `handle` must be called in order to register a handler and support for a given protocol. This also informs other peers of the protocols you support. + * + * `libp2p.handle(protocols, handler, options)` + * + * In the event of a new handler for the same protocol being added, the first one is discarded. + * + * @example + * + * ```js + * const handler = ({ connection, stream, protocol }) => { + * // use stream or connection according to the needs + * } + * + * libp2p.handle('/echo/1.0.0', handler, { + * maxInboundStreams: 5, + * maxOutboundStreams: 5 + * }) + * ``` + */ + handle: (protocol: string | string[], handler: StreamHandler, options?: StreamHandlerOptions) => Promise + + /** + * Removes the handler for each protocol. The protocol + * will no longer be supported on streams. + * + * @example + * + * ```js + * libp2p.unhandle(['/echo/1.0.0']) + * ``` + */ + unhandle: (protocols: string[] | string) => Promise + + /** + * Register a topology to be informed when peers are encountered that + * support the specified protocol + * + * @example + * + * ```js + * import { createTopology } from '@libp2p/topology' + * + * const id = await libp2p.register('/echo/1.0.0', createTopology({ + * onConnect: (peer, connection) => { + * // handle connect + * }, + * onDisconnect: (peer, connection) => { + * // handle disconnect + * } + * })) + * ``` + */ + register: (protocol: string, topology: Topology) => Promise + + /** + * Unregister topology to no longer be informed when peers connect or + * disconnect. + * + * @example + * + * ```js + * const id = await libp2p.register(...) + * + * libp2p.unregister(id) + * ``` + */ + unregister: (id: string) => void + + /** + * Returns the public key for the passed PeerId. If the PeerId is of the 'RSA' type + * this may mean searching the DHT if the key is not present in the KeyStore. + * A set of user defined services + */ + getPublicKey: (peer: PeerId, options?: AbortOptions) => Promise + + /** + * A set of user defined services + */ + services: T +} diff --git a/packages/interface-libp2p/tsconfig.json b/packages/interface-libp2p/tsconfig.json new file mode 100644 index 0000000000..141be663e3 --- /dev/null +++ b/packages/interface-libp2p/tsconfig.json @@ -0,0 +1,44 @@ +{ + "extends": "aegir/src/config/tsconfig.aegir.json", + "compilerOptions": { + "outDir": "dist" + }, + "include": [ + "src" + ], + "references": [ + { + "path": "../interface-connection" + }, + { + "path": "../interface-content-routing" + }, + { + "path": "../interface-keychain" + }, + { + "path": "../interface-metrics" + }, + { + "path": "../interface-peer-id" + }, + { + "path": "../interface-peer-info" + }, + { + "path": "../interface-peer-routing" + }, + { + "path": "../interface-peer-store" + }, + { + "path": "../interface-registrar" + }, + { + "path": "../interface-transport" + }, + { + "path": "../interfaces" + } + ] +} diff --git a/packages/interface-metrics/CHANGELOG.md b/packages/interface-metrics/CHANGELOG.md new file mode 100644 index 0000000000..387c1c4b68 --- /dev/null +++ b/packages/interface-metrics/CHANGELOG.md @@ -0,0 +1,130 @@ +## [@libp2p/interface-metrics-v4.0.8](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-metrics-v4.0.7...@libp2p/interface-metrics-v4.0.8) (2023-05-04) + + +### Dependencies + +* bump aegir from 38.1.8 to 39.0.5 ([#393](https://github.com/libp2p/js-libp2p-interfaces/issues/393)) ([31f3797](https://github.com/libp2p/js-libp2p-interfaces/commit/31f3797b24f7c23f3f16e9db3a230bd5f7cd5175)) + +## [@libp2p/interface-metrics-v4.0.7](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-metrics-v4.0.6...@libp2p/interface-metrics-v4.0.7) (2023-04-18) + + +### Dependencies + +* update sibling dependencies ([2f52a28](https://github.com/libp2p/js-libp2p-interfaces/commit/2f52a284b59c0a88b040f86da1f5d3f044727f2c)) + +## [@libp2p/interface-metrics-v4.0.6](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-metrics-v4.0.5...@libp2p/interface-metrics-v4.0.6) (2023-04-11) + + +### Dependencies + +* update sibling dependencies ([b034810](https://github.com/libp2p/js-libp2p-interfaces/commit/b0348102e41dc18166e70063f4708a2b3544f4b6)) + +## [@libp2p/interface-metrics-v4.0.5](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-metrics-v4.0.4...@libp2p/interface-metrics-v4.0.5) (2023-01-18) + + +### Trivial Changes + +* remove lerna ([#330](https://github.com/libp2p/js-libp2p-interfaces/issues/330)) ([6678592](https://github.com/libp2p/js-libp2p-interfaces/commit/6678592dd0cf601a2671852f9d2a0aff5dee2b18)) + + +### Dependencies + +* bump aegir from 37.12.1 to 38.1.0 ([#335](https://github.com/libp2p/js-libp2p-interfaces/issues/335)) ([7368a36](https://github.com/libp2p/js-libp2p-interfaces/commit/7368a363423a08e8fa247dcb76ea13e4cf030d65)) + +## [@libp2p/interface-metrics-v4.0.4](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-metrics-v4.0.3...@libp2p/interface-metrics-v4.0.4) (2022-12-16) + + +### Documentation + +* update project config ([#323](https://github.com/libp2p/js-libp2p-interfaces/issues/323)) ([0fc6a08](https://github.com/libp2p/js-libp2p-interfaces/commit/0fc6a08e9cdcefe361fe325281a3a2a03759ff59)) + +## [@libp2p/interface-metrics-v4.0.3](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-metrics-v4.0.2...@libp2p/interface-metrics-v4.0.3) (2022-12-14) + + +### Bug Fixes + +* generate docs for all packages ([#321](https://github.com/libp2p/js-libp2p-interfaces/issues/321)) ([b6f8b32](https://github.com/libp2p/js-libp2p-interfaces/commit/b6f8b32a920c15a28fe021e6050e31aaae89d518)) + +## [@libp2p/interface-metrics-v4.0.2](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-metrics-v4.0.1...@libp2p/interface-metrics-v4.0.2) (2022-11-05) + + +### Bug Fixes + +* metrics only need numbers ([#312](https://github.com/libp2p/js-libp2p-interfaces/issues/312)) ([0076c1f](https://github.com/libp2p/js-libp2p-interfaces/commit/0076c1f354ebc1106b6ac42d48688c0209866084)) + +## [@libp2p/interface-metrics-v4.0.1](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-metrics-v4.0.0...@libp2p/interface-metrics-v4.0.1) (2022-11-05) + + +### Bug Fixes + +* update project config ([#311](https://github.com/libp2p/js-libp2p-interfaces/issues/311)) ([27dd0ce](https://github.com/libp2p/js-libp2p-interfaces/commit/27dd0ce3c249892ac69cbb24ddaf0b9f32385e37)) + +## [@libp2p/interface-metrics-v4.0.0](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-metrics-v3.0.0...@libp2p/interface-metrics-v4.0.0) (2022-11-05) + + +### ⚠ BREAKING CHANGES + +* the global/per-peer moving average tracking has been removed from the interface as it's expensive and requires lots of timers - this functionality can be replicated by implementations if it's desirable. It's better to have simple counters instead and let an external system like Prometheus or Graphana calculate the values over time + +### Features + +* return metrics objects from register instead of updating with an options object ([#310](https://github.com/libp2p/js-libp2p-interfaces/issues/310)) ([3b106ce](https://github.com/libp2p/js-libp2p-interfaces/commit/3b106ce799b5d84a82a66238995e09970ed8116c)) + +## [@libp2p/interface-metrics-v3.0.0](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-metrics-v2.0.0...@libp2p/interface-metrics-v3.0.0) (2022-08-07) + + +### ⚠ BREAKING CHANGES + +* change stream muxer interface (#279) + +### Features + +* change stream muxer interface ([#279](https://github.com/libp2p/js-libp2p-interfaces/issues/279)) ([1ebe269](https://github.com/libp2p/js-libp2p-interfaces/commit/1ebe26988b6a286f36a4fc5177f502cfb60368a1)) + + +### Trivial Changes + +* update project config ([#271](https://github.com/libp2p/js-libp2p-interfaces/issues/271)) ([59c0bf5](https://github.com/libp2p/js-libp2p-interfaces/commit/59c0bf5e0b05496fca2e4902632b61bb41fad9e9)) + +## [@libp2p/interface-metrics-v2.0.0](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-metrics-v1.0.3...@libp2p/interface-metrics-v2.0.0) (2022-07-01) + + +### ⚠ BREAKING CHANGES + +* the return type of `metrics.getComponentMetrics` has been changed to include optional labels/help text and also is now a function that returns a single or group value + +### Features + +* add metrics groups ([#267](https://github.com/libp2p/js-libp2p-interfaces/issues/267)) ([b9d898a](https://github.com/libp2p/js-libp2p-interfaces/commit/b9d898abdb551ebe2e0e961ec325d5e6abcf4fab)), closes [#257](https://github.com/libp2p/js-libp2p-interfaces/issues/257) [#258](https://github.com/libp2p/js-libp2p-interfaces/issues/258) + +## [@libp2p/interface-metrics-v1.0.3](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-metrics-v1.0.2...@libp2p/interface-metrics-v1.0.3) (2022-06-27) + + +### Trivial Changes + +* update deps ([#262](https://github.com/libp2p/js-libp2p-interfaces/issues/262)) ([51edf7d](https://github.com/libp2p/js-libp2p-interfaces/commit/51edf7d9b3765a6f75c915b1483ea345d0133a41)) + +## [@libp2p/interface-metrics-v1.0.2](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-metrics-v1.0.1...@libp2p/interface-metrics-v1.0.2) (2022-06-14) + + +### Trivial Changes + +* update aegir ([#234](https://github.com/libp2p/js-libp2p-interfaces/issues/234)) ([3e03895](https://github.com/libp2p/js-libp2p-interfaces/commit/3e038959ecab6cfa3585df9ee179c0af7a61eda5)) + +## [@libp2p/interface-metrics-v1.0.1](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-metrics-v1.0.0...@libp2p/interface-metrics-v1.0.1) (2022-06-14) + + +### Trivial Changes + +* update readmes ([#233](https://github.com/libp2p/js-libp2p-interfaces/issues/233)) ([ee7da38](https://github.com/libp2p/js-libp2p-interfaces/commit/ee7da38dccc08160d26c8436df8739ce7e0b340e)) + +## @libp2p/interface-metrics-v1.0.0 (2022-06-14) + + +### ⚠ BREAKING CHANGES + +* most modules have been split out of the `@libp2p/interfaces` and `@libp2p/interface-compliance-tests` packages + +### Trivial Changes + +* break modules apart ([#232](https://github.com/libp2p/js-libp2p-interfaces/issues/232)) ([385614e](https://github.com/libp2p/js-libp2p-interfaces/commit/385614e772329052ab17415c8bd421f65b01a61b)), closes [#226](https://github.com/libp2p/js-libp2p-interfaces/issues/226) diff --git a/packages/interface-metrics/LICENSE b/packages/interface-metrics/LICENSE new file mode 100644 index 0000000000..20ce483c86 --- /dev/null +++ b/packages/interface-metrics/LICENSE @@ -0,0 +1,4 @@ +This project is dual licensed under MIT and Apache-2.0. + +MIT: https://www.opensource.org/licenses/mit +Apache-2.0: https://www.apache.org/licenses/license-2.0 diff --git a/packages/interface-metrics/LICENSE-APACHE b/packages/interface-metrics/LICENSE-APACHE new file mode 100644 index 0000000000..14478a3b60 --- /dev/null +++ b/packages/interface-metrics/LICENSE-APACHE @@ -0,0 +1,5 @@ +Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. diff --git a/packages/interface-metrics/LICENSE-MIT b/packages/interface-metrics/LICENSE-MIT new file mode 100644 index 0000000000..72dc60d84b --- /dev/null +++ b/packages/interface-metrics/LICENSE-MIT @@ -0,0 +1,19 @@ +The MIT License (MIT) + +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/packages/interface-metrics/README.md b/packages/interface-metrics/README.md new file mode 100644 index 0000000000..39ddd7192e --- /dev/null +++ b/packages/interface-metrics/README.md @@ -0,0 +1,41 @@ +# @libp2p/interface-metrics + +[![libp2p.io](https://img.shields.io/badge/project-libp2p-yellow.svg?style=flat-square)](http://libp2p.io/) +[![Discuss](https://img.shields.io/discourse/https/discuss.libp2p.io/posts.svg?style=flat-square)](https://discuss.libp2p.io) +[![codecov](https://img.shields.io/codecov/c/github/libp2p/js-libp2p.svg?style=flat-square)](https://codecov.io/gh/libp2p/js-libp2p) +[![CI](https://img.shields.io/github/actions/workflow/status/libp2p/js-libp2p/js-test-and-release.yml?branch=master\&style=flat-square)](https://github.com/libp2p/js-libp2p/actions/workflows/js-test-and-release.yml?query=branch%3Amaster) + +> Metrics interface for libp2p + +## Table of contents + +- [Install](#install) +- [Implementations](#implementations) +- [API Docs](#api-docs) +- [License](#license) +- [Contribution](#contribution) + +## Install + +```console +$ npm i @libp2p/interface-metrics +``` + +## Implementations + +- [@libp2p/prometheus-metrics](https://github.com/libp2p/js-libp2p-prometheus-metrics) + +## API Docs + +- + +## License + +Licensed under either of + +- Apache 2.0, ([LICENSE-APACHE](LICENSE-APACHE) / ) +- MIT ([LICENSE-MIT](LICENSE-MIT) / ) + +## Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. diff --git a/packages/interface-metrics/package.json b/packages/interface-metrics/package.json new file mode 100644 index 0000000000..a96e776b71 --- /dev/null +++ b/packages/interface-metrics/package.json @@ -0,0 +1,139 @@ +{ + "name": "@libp2p/interface-metrics", + "version": "4.0.8", + "description": "Metrics interface for libp2p", + "license": "Apache-2.0 OR MIT", + "homepage": "https://github.com/libp2p/js-libp2p/tree/master/packages/interface-metrics#readme", + "repository": { + "type": "git", + "url": "git+https://github.com/libp2p/js-libp2p.git" + }, + "bugs": { + "url": "https://github.com/libp2p/js-libp2p/issues" + }, + "keywords": [ + "interface", + "libp2p" + ], + "type": "module", + "types": "./dist/src/index.d.ts", + "files": [ + "src", + "dist", + "!dist/test", + "!**/*.tsbuildinfo" + ], + "exports": { + ".": { + "types": "./dist/src/index.d.ts", + "import": "./dist/src/index.js" + } + }, + "eslintConfig": { + "extends": "ipfs", + "parserOptions": { + "sourceType": "module" + } + }, + "release": { + "branches": [ + "master" + ], + "plugins": [ + [ + "@semantic-release/commit-analyzer", + { + "preset": "conventionalcommits", + "releaseRules": [ + { + "breaking": true, + "release": "major" + }, + { + "revert": true, + "release": "patch" + }, + { + "type": "feat", + "release": "minor" + }, + { + "type": "fix", + "release": "patch" + }, + { + "type": "docs", + "release": "patch" + }, + { + "type": "test", + "release": "patch" + }, + { + "type": "deps", + "release": "patch" + }, + { + "scope": "no-release", + "release": false + } + ] + } + ], + [ + "@semantic-release/release-notes-generator", + { + "preset": "conventionalcommits", + "presetConfig": { + "types": [ + { + "type": "feat", + "section": "Features" + }, + { + "type": "fix", + "section": "Bug Fixes" + }, + { + "type": "chore", + "section": "Trivial Changes" + }, + { + "type": "docs", + "section": "Documentation" + }, + { + "type": "deps", + "section": "Dependencies" + }, + { + "type": "test", + "section": "Tests" + } + ] + } + } + ], + "@semantic-release/changelog", + "@semantic-release/npm", + "@semantic-release/github", + "@semantic-release/git" + ] + }, + "scripts": { + "clean": "aegir clean", + "lint": "aegir lint", + "dep-check": "aegir dep-check", + "build": "aegir build", + "release": "aegir release" + }, + "dependencies": { + "@libp2p/interface-connection": "^5.0.0" + }, + "devDependencies": { + "aegir": "^39.0.5" + }, + "typedoc": { + "entryPoint": "./src/index.ts" + } +} diff --git a/packages/interface-metrics/src/index.ts b/packages/interface-metrics/src/index.ts new file mode 100644 index 0000000000..eaa89ca775 --- /dev/null +++ b/packages/interface-metrics/src/index.ts @@ -0,0 +1,187 @@ +import type { MultiaddrConnection, Stream, Connection } from '@libp2p/interface-connection' + +/** + * Create tracked metrics with these options. Loosely based on the + * interfaces exposed by the prom-client module + */ +export interface MetricOptions { + /** + * Optional label for the metric + */ + label?: string + + /** + * Optional help for the metric + */ + help?: string +} + +/** + * A function that returns a tracked metric which may be expensive + * to calculate so it is only invoked when metrics are being scraped + */ +export type CalculateMetric = (() => T) | (() => Promise) + +/** + * Create tracked metrics that are expensive to calculate by passing + * a function that is only invoked when metrics are being scraped + */ +export interface CalculatedMetricOptions extends MetricOptions { + /** + * An optional function invoked to calculate the component metric instead of + * using `.update`, `.increment`, and `.decrement` + */ + calculate: CalculateMetric +} + +/** + * Call this function to stop the timer returned from the `.timer` method + * on the metric + */ +export interface StopTimer { (): void } + +/** + * A tracked metric loosely based on the interfaces exposed by the + * prom-client module + */ +export interface Metric { + /** + * Update the stored metric to the passed value + */ + update: (value: number) => void + + /** + * Increment the metric by the passed value or 1 + */ + increment: (value?: number) => void + + /** + * Decrement the metric by the passed value or 1 + */ + decrement: (value?: number) => void + + /** + * Reset this metric to its default value + */ + reset: () => void + + /** + * Start a timed metric, call the returned function to + * stop the timer + */ + timer: () => StopTimer +} + +/** + * A group of related metrics loosely based on the interfaces exposed by the + * prom-client module + */ +export interface MetricGroup { + /** + * Update the stored metric group to the passed value + */ + update: (values: Record) => void + + /** + * Increment the metric group keys by the passed number or + * any non-numeric value to increment by 1 + */ + increment: (values: Record) => void + + /** + * Decrement the metric group keys by the passed number or + * any non-numeric value to decrement by 1 + */ + decrement: (values: Record) => void + + /** + * Reset the passed key in this metric group to its default value + * or all keys if no key is passed + */ + reset: () => void + + /** + * Start a timed metric for the named key in the group, call + * the returned function to stop the timer + */ + timer: (key: string) => StopTimer +} + +/** + * A tracked counter loosely based on the Counter interface exposed + * by the prom-client module - counters are metrics that only go up + */ +export interface Counter { + /** + * Increment the metric by the passed value or 1 + */ + increment: (value?: number) => void + + /** + * Reset this metric to its default value + */ + reset: () => void +} + +/** + * A group of tracked counters loosely based on the Counter interface + * exposed by the prom-client module - counters are metrics that only + * go up + */ +export interface CounterGroup { + /** + * Increment the metric group keys by the passed number or + * any non-numeric value to increment by 1 + */ + increment: (values: Record) => void + + /** + * Reset the passed key in this metric group to its default value + * or all keys if no key is passed + */ + reset: () => void +} + +/** + * The libp2p metrics tracking object. This interface is only concerned + * with the collection of metrics, please see the individual implementations + * for how to extract metrics for viewing. + */ +export interface Metrics { + /** + * Track a newly opened multiaddr connection + */ + trackMultiaddrConnection: (maConn: MultiaddrConnection) => void + + /** + * Track a newly opened protocol stream + */ + trackProtocolStream: (stream: Stream, connection: Connection) => void + + /** + * Register an arbitrary metric. Call this to set help/labels for metrics + * and update/increment/decrement/etc them by calling methods on the returned + * metric object + */ + registerMetric: ((name: string, options?: MetricOptions) => Metric) & ((name: string, options: CalculatedMetricOptions) => void) + + /** + * Register a a group of related metrics. Call this to set help/labels for + * groups of related metrics that will be updated with by calling `.update`, + * `.increment` and/or `.decrement` methods on the returned metric group object + */ + registerMetricGroup: ((name: string, options?: MetricOptions) => MetricGroup) & ((name: string, options: CalculatedMetricOptions>) => void) + + /** + * Register an arbitrary counter. Call this to set help/labels for counters + * and increment them by calling methods on the returned counter object + */ + registerCounter: ((name: string, options?: MetricOptions) => Counter) & ((name: string, options: CalculatedMetricOptions) => void) + + /** + * Register a a group of related counters. Call this to set help/labels for + * groups of related counters that will be updated with by calling the `.increment` + * method on the returned counter group object + */ + registerCounterGroup: ((name: string, options?: MetricOptions) => CounterGroup) & ((name: string, options: CalculatedMetricOptions>) => void) +} diff --git a/packages/interface-metrics/tsconfig.json b/packages/interface-metrics/tsconfig.json new file mode 100644 index 0000000000..01ca33094c --- /dev/null +++ b/packages/interface-metrics/tsconfig.json @@ -0,0 +1,14 @@ +{ + "extends": "aegir/src/config/tsconfig.aegir.json", + "compilerOptions": { + "outDir": "dist" + }, + "include": [ + "src" + ], + "references": [ + { + "path": "../interface-connection" + } + ] +} diff --git a/packages/interface-mocks/CHANGELOG.md b/packages/interface-mocks/CHANGELOG.md new file mode 100644 index 0000000000..5a66070f58 --- /dev/null +++ b/packages/interface-mocks/CHANGELOG.md @@ -0,0 +1,1013 @@ +## [@libp2p/interface-mocks-v12.0.1](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-mocks-v12.0.0...@libp2p/interface-mocks-v12.0.1) (2023-05-10) + + +### Bug Fixes + +* emit peer:connect events from mock connection manager ([#399](https://github.com/libp2p/js-libp2p-interfaces/issues/399)) ([836dcf3](https://github.com/libp2p/js-libp2p-interfaces/commit/836dcf3d0fbdd00686f662260940c5600db25c09)) + +## [@libp2p/interface-mocks-v12.0.0](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-mocks-v11.0.3...@libp2p/interface-mocks-v12.0.0) (2023-05-04) + + +### ⚠ BREAKING CHANGES + +* the `symbol` export is now named `peerDiscovery` and the getter with that name should return an instance of `PeerDiscovery` + +### Features + +* rename peer discovery symbol to peerDiscovery ([#394](https://github.com/libp2p/js-libp2p-interfaces/issues/394)) ([5957c77](https://github.com/libp2p/js-libp2p-interfaces/commit/5957c77718df6e6336ca22386d8c03a045fd1d89)) + + +### Dependencies + +* update sibling dependencies ([45cf513](https://github.com/libp2p/js-libp2p-interfaces/commit/45cf513090d2a069bb6752ad2e231df65c76df36)) + +## [@libp2p/interface-mocks-v11.0.3](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-mocks-v11.0.2...@libp2p/interface-mocks-v11.0.3) (2023-05-04) + + +### Dependencies + +* bump aegir from 38.1.8 to 39.0.5 ([#393](https://github.com/libp2p/js-libp2p-interfaces/issues/393)) ([31f3797](https://github.com/libp2p/js-libp2p-interfaces/commit/31f3797b24f7c23f3f16e9db3a230bd5f7cd5175)) + +## [@libp2p/interface-mocks-v11.0.2](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-mocks-v11.0.1...@libp2p/interface-mocks-v11.0.2) (2023-04-27) + + +### Dependencies + +* update sibling dependencies ([6aa5ee8](https://github.com/libp2p/js-libp2p-interfaces/commit/6aa5ee87f9e431cabd4081cf8bc76b8f5180f344)) + +## [@libp2p/interface-mocks-v11.0.1](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-mocks-v11.0.0...@libp2p/interface-mocks-v11.0.1) (2023-04-24) + + +### Bug Fixes + +* make events optional in mock upgrader ([#385](https://github.com/libp2p/js-libp2p-interfaces/issues/385)) ([51f4aae](https://github.com/libp2p/js-libp2p-interfaces/commit/51f4aaea6ab216a1f60b899ecc25b7a325de988d)) + +## [@libp2p/interface-mocks-v11.0.0](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-mocks-v10.0.3...@libp2p/interface-mocks-v11.0.0) (2023-04-21) + + +### ⚠ BREAKING CHANGES + +* add libp2p events (#373) + +### Features + +* add libp2p events ([#373](https://github.com/libp2p/js-libp2p-interfaces/issues/373)) ([071c718](https://github.com/libp2p/js-libp2p-interfaces/commit/071c718808902858818ca86167b51b242b67a5a5)) + + +### Dependencies + +* update sibling dependencies ([a1b72f9](https://github.com/libp2p/js-libp2p-interfaces/commit/a1b72f90414536308befd07df2a003985951ceb7)) +* update sibling dependencies ([17ed429](https://github.com/libp2p/js-libp2p-interfaces/commit/17ed429d57e83cb38484ac52a0e0975a7d8af963)) +* update sibling dependencies ([6c18790](https://github.com/libp2p/js-libp2p-interfaces/commit/6c18790f6178053c69a8cd6bd289fd749d4e9633)) + +## [@libp2p/interface-mocks-v10.0.3](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-mocks-v10.0.2...@libp2p/interface-mocks-v10.0.3) (2023-04-19) + + +### Bug Fixes + +* update mock duplex type ([#380](https://github.com/libp2p/js-libp2p-interfaces/issues/380)) ([5260314](https://github.com/libp2p/js-libp2p-interfaces/commit/52603142bc91aaeb192ebf9b3a7559e8a270b7bf)) + +## [@libp2p/interface-mocks-v10.0.2](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-mocks-v10.0.1...@libp2p/interface-mocks-v10.0.2) (2023-04-18) + + +### Dependencies + +* update abortable iterator to 5.x.x ([#379](https://github.com/libp2p/js-libp2p-interfaces/issues/379)) ([d405e5b](https://github.com/libp2p/js-libp2p-interfaces/commit/d405e5b5db624d97f47588ef55c379debccfd160)) + +## [@libp2p/interface-mocks-v10.0.1](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-mocks-v10.0.0...@libp2p/interface-mocks-v10.0.1) (2023-04-18) + + +### Bug Fixes + +* specify stream sink return type ([#378](https://github.com/libp2p/js-libp2p-interfaces/issues/378)) ([e0641fc](https://github.com/libp2p/js-libp2p-interfaces/commit/e0641fcc2f2a6562e7f7d8e064ebd98c5cc6dccb)) + +## [@libp2p/interface-mocks-v10.0.0](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-mocks-v9.4.0...@libp2p/interface-mocks-v10.0.0) (2023-04-18) + + +### ⚠ BREAKING CHANGES + +* bump it-stream-types from 1.0.5 to 2.0.1 (#362) + +### Dependencies + +* bump it-stream-types from 1.0.5 to 2.0.1 ([#362](https://github.com/libp2p/js-libp2p-interfaces/issues/362)) ([cdc7747](https://github.com/libp2p/js-libp2p-interfaces/commit/cdc774792beead63e0ded96bd6c23de0335a49e3)) +* update sibling dependencies ([4972cc6](https://github.com/libp2p/js-libp2p-interfaces/commit/4972cc6c4c43319730305b58f329d6cf4591517a)) +* update sibling dependencies ([a5b7b33](https://github.com/libp2p/js-libp2p-interfaces/commit/a5b7b33dccee52d03fce788d2876a398d6fd6d99)) +* update sibling dependencies ([99a862b](https://github.com/libp2p/js-libp2p-interfaces/commit/99a862baed66d4e83ba006a70c33561855c9682e)) +* update sibling dependencies ([e95dcc2](https://github.com/libp2p/js-libp2p-interfaces/commit/e95dcc28f0a8b42457a44155eb0dfb3d813b03c8)) +* update sibling dependencies ([3d23367](https://github.com/libp2p/js-libp2p-interfaces/commit/3d233676a17299bfa1b59d309543598176826523)) +* update sibling dependencies ([2b9ddda](https://github.com/libp2p/js-libp2p-interfaces/commit/2b9ddda88d2655d389bf5142f7131f333ab9f780)) +* update sibling dependencies ([bed9f4c](https://github.com/libp2p/js-libp2p-interfaces/commit/bed9f4c7b7044e974a70678762a51e79e018cf9b)) +* update sibling dependencies ([2f52a28](https://github.com/libp2p/js-libp2p-interfaces/commit/2f52a284b59c0a88b040f86da1f5d3f044727f2c)) + +## [@libp2p/interface-mocks-v9.4.0](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-mocks-v9.3.4...@libp2p/interface-mocks-v9.4.0) (2023-04-14) + + +### Features + +* expose get connection map method of connection manager ([#372](https://github.com/libp2p/js-libp2p-interfaces/issues/372)) ([fc7245b](https://github.com/libp2p/js-libp2p-interfaces/commit/fc7245b63764562f5ec66a5a0ba334caea80ed66)) +* expose get dial queue method of connection manager ([#371](https://github.com/libp2p/js-libp2p-interfaces/issues/371)) ([0c407aa](https://github.com/libp2p/js-libp2p-interfaces/commit/0c407aa0772c171bf6650e31fb20a3433df40b6b)) + +## [@libp2p/interface-mocks-v9.3.4](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-mocks-v9.3.3...@libp2p/interface-mocks-v9.3.4) (2023-04-14) + + +### Dependencies + +* update sibling dependencies ([34b1627](https://github.com/libp2p/js-libp2p-interfaces/commit/34b1627458b2ada5e94e00cf4bcba41a77232090)) + +## [@libp2p/interface-mocks-v9.3.3](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-mocks-v9.3.2...@libp2p/interface-mocks-v9.3.3) (2023-04-14) + + +### Dependencies + +* update sibling dependencies ([3e743bb](https://github.com/libp2p/js-libp2p-interfaces/commit/3e743bba3d8ebd081907e74f02728a1e8476a147)) + +## [@libp2p/interface-mocks-v9.3.2](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-mocks-v9.3.1...@libp2p/interface-mocks-v9.3.2) (2023-04-13) + + +### Dependencies + +* update any-signal to 4.x.x ([#369](https://github.com/libp2p/js-libp2p-interfaces/issues/369)) ([72be911](https://github.com/libp2p/js-libp2p-interfaces/commit/72be91176509f619e5d621463cb4ecc014fde0b7)) + +## [@libp2p/interface-mocks-v9.3.1](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-mocks-v9.3.0...@libp2p/interface-mocks-v9.3.1) (2023-04-13) + + +### Dependencies + +* bump it-map from 2.0.1 to 3.0.2 ([#361](https://github.com/libp2p/js-libp2p-interfaces/issues/361)) ([c016269](https://github.com/libp2p/js-libp2p-interfaces/commit/c01626912eae85969bf2b1027b68a5242a4ae4d4)) + +## [@libp2p/interface-mocks-v9.3.0](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-mocks-v9.2.4...@libp2p/interface-mocks-v9.3.0) (2023-04-11) + + +### Features + +* support batch dialling ([#351](https://github.com/libp2p/js-libp2p-interfaces/issues/351)) ([e46b72b](https://github.com/libp2p/js-libp2p-interfaces/commit/e46b72b1731ff935a1f0d755cbaf6f3159060ed3)) + + +### Dependencies + +* update sibling dependencies ([b034810](https://github.com/libp2p/js-libp2p-interfaces/commit/b0348102e41dc18166e70063f4708a2b3544f4b6)) + +## [@libp2p/interface-mocks-v9.2.4](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-mocks-v9.2.3...@libp2p/interface-mocks-v9.2.4) (2023-04-04) + + +### Dependencies + +* bump it-pipe from 2.0.5 to 3.0.1 ([#363](https://github.com/libp2p/js-libp2p-interfaces/issues/363)) ([625817b](https://github.com/libp2p/js-libp2p-interfaces/commit/625817b0bbbee276983c40a0604c8810a25abe8f)) + +## [@libp2p/interface-mocks-v9.2.3](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-mocks-v9.2.2...@libp2p/interface-mocks-v9.2.3) (2023-03-17) + + +### Bug Fixes + +* update project settings ([2aa4f95](https://github.com/libp2p/js-libp2p-interfaces/commit/2aa4f9583fb8ff9b53c51ebb6b81f72d69a1748d)) + +## [@libp2p/interface-mocks-v9.2.2](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-mocks-v9.2.1...@libp2p/interface-mocks-v9.2.2) (2023-03-17) + + +### Dependencies + +* update @multiformats/multiaddr to 12.0.0 ([#354](https://github.com/libp2p/js-libp2p-interfaces/issues/354)) ([e0f327b](https://github.com/libp2p/js-libp2p-interfaces/commit/e0f327b5d54e240feabadce21a841629d633ec5e)) + +## [@libp2p/interface-mocks-v9.2.1](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-mocks-v9.2.0...@libp2p/interface-mocks-v9.2.1) (2023-03-10) + + +### Bug Fixes + +* filter closed connections properly ([#349](https://github.com/libp2p/js-libp2p-interfaces/issues/349)) ([21021c3](https://github.com/libp2p/js-libp2p-interfaces/commit/21021c366579db5b45b93ea4446118f32aca0428)) + +## [@libp2p/interface-mocks-v9.2.0](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-mocks-v9.1.3...@libp2p/interface-mocks-v9.2.0) (2023-03-09) + + +### Features + +* split connection gater out into module ([#347](https://github.com/libp2p/js-libp2p-interfaces/issues/347)) ([1824744](https://github.com/libp2p/js-libp2p-interfaces/commit/18247442aa64c809d9e101ccbd0067ce48bdb80f)) + + +### Bug Fixes + +* update @libp2p/interface-connection-gater depdendency ([e53cf8b](https://github.com/libp2p/js-libp2p-interfaces/commit/e53cf8b26b83dcca553b934f171d07d817df15ca)) + + +### Dependencies + +* update sibling dependencies ([e72292f](https://github.com/libp2p/js-libp2p-interfaces/commit/e72292fe1e37ac55b041a09058365fb74de0e629)) + +## [@libp2p/interface-mocks-v9.1.3](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-mocks-v9.1.2...@libp2p/interface-mocks-v9.1.3) (2023-03-07) + + +### Bug Fixes + +* dispatch connection event from mock upgrader ([#345](https://github.com/libp2p/js-libp2p-interfaces/issues/345)) ([b691b1f](https://github.com/libp2p/js-libp2p-interfaces/commit/b691b1fa28e23b549c32e89d6b7c98d6a50c7b8f)) + +## [@libp2p/interface-mocks-v9.1.2](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-mocks-v9.1.1...@libp2p/interface-mocks-v9.1.2) (2023-02-22) + + +### Bug Fixes + +* replace err-code with CodeError ([#334](https://github.com/libp2p/js-libp2p-interfaces/issues/334)) ([a909d41](https://github.com/libp2p/js-libp2p-interfaces/commit/a909d418ce1128c771b682dc78bb48789d4b319a)), closes [js-libp2p#1269](https://github.com/libp2p/js-libp2p/issues/1269) + +## [@libp2p/interface-mocks-v9.1.1](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-mocks-v9.1.0...@libp2p/interface-mocks-v9.1.1) (2023-01-18) + + +### Dependencies + +* bump aegir from 37.12.1 to 38.1.0 ([#335](https://github.com/libp2p/js-libp2p-interfaces/issues/335)) ([7368a36](https://github.com/libp2p/js-libp2p-interfaces/commit/7368a363423a08e8fa247dcb76ea13e4cf030d65)) + +## [@libp2p/interface-mocks-v9.1.0](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-mocks-v9.0.1...@libp2p/interface-mocks-v9.1.0) (2023-01-17) + + +### Features + +* safe dispatch event ([#319](https://github.com/libp2p/js-libp2p-interfaces/issues/319)) ([8caeee8](https://github.com/libp2p/js-libp2p-interfaces/commit/8caeee8221e78c2412d8aeb9a7db7cc43abfdf1b)), closes [#317](https://github.com/libp2p/js-libp2p-interfaces/issues/317) + +## [@libp2p/interface-mocks-v9.0.1](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-mocks-v9.0.0...@libp2p/interface-mocks-v9.0.1) (2023-01-14) + + +### Bug Fixes + +* accept multiaddr param when opening connections ([#336](https://github.com/libp2p/js-libp2p-interfaces/issues/336)) ([fef9c26](https://github.com/libp2p/js-libp2p-interfaces/commit/fef9c26847cf63cb95f5fcb51ee40cbc679cc6bf)) + + +### Trivial Changes + +* remove lerna ([#330](https://github.com/libp2p/js-libp2p-interfaces/issues/330)) ([6678592](https://github.com/libp2p/js-libp2p-interfaces/commit/6678592dd0cf601a2671852f9d2a0aff5dee2b18)) + +## [@libp2p/interface-mocks-v9.0.0](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-mocks-v8.0.5...@libp2p/interface-mocks-v9.0.0) (2023-01-06) + + +### ⚠ BREAKING CHANGES + +* update peer-id dep to pull in new multiformats (#331) + +### Bug Fixes + +* update peer-id dep to pull in new multiformats ([#331](https://github.com/libp2p/js-libp2p-interfaces/issues/331)) ([fb8b7ba](https://github.com/libp2p/js-libp2p-interfaces/commit/fb8b7ba654a30a08da0652e2833e36dd3bb85e90)) + + +### Dependencies + +* update sibling dependencies ([667082f](https://github.com/libp2p/js-libp2p-interfaces/commit/667082f7070ec28a2f19c356fe44fd7499958f2e)) + +## [@libp2p/interface-mocks-v8.0.5](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-mocks-v8.0.4...@libp2p/interface-mocks-v8.0.5) (2023-01-06) + + +### Dependencies + +* update sibling dependencies ([b50e621](https://github.com/libp2p/js-libp2p-interfaces/commit/b50e621d31a8b32affc3fadb9f97c4883d577f93)) + +## [@libp2p/interface-mocks-v8.0.4](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-mocks-v8.0.3...@libp2p/interface-mocks-v8.0.4) (2022-12-16) + + +### Documentation + +* update project config ([#323](https://github.com/libp2p/js-libp2p-interfaces/issues/323)) ([0fc6a08](https://github.com/libp2p/js-libp2p-interfaces/commit/0fc6a08e9cdcefe361fe325281a3a2a03759ff59)) + +## [@libp2p/interface-mocks-v8.0.3](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-mocks-v8.0.2...@libp2p/interface-mocks-v8.0.3) (2022-12-14) + + +### Bug Fixes + +* generate docs for all packages ([#321](https://github.com/libp2p/js-libp2p-interfaces/issues/321)) ([b6f8b32](https://github.com/libp2p/js-libp2p-interfaces/commit/b6f8b32a920c15a28fe021e6050e31aaae89d518)) + +## [@libp2p/interface-mocks-v8.0.2](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-mocks-v8.0.1...@libp2p/interface-mocks-v8.0.2) (2022-12-07) + + +### Bug Fixes + +* add missing dependency ([e2168e8](https://github.com/libp2p/js-libp2p-interfaces/commit/e2168e8f863d6a488e4117800b5143dce1122b0b)) + +## [@libp2p/interface-mocks-v8.0.1](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-mocks-v8.0.0...@libp2p/interface-mocks-v8.0.1) (2022-11-05) + + +### Bug Fixes + +* metrics only need numbers ([#312](https://github.com/libp2p/js-libp2p-interfaces/issues/312)) ([0076c1f](https://github.com/libp2p/js-libp2p-interfaces/commit/0076c1f354ebc1106b6ac42d48688c0209866084)) + +## [@libp2p/interface-mocks-v8.0.0](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-mocks-v7.1.0...@libp2p/interface-mocks-v8.0.0) (2022-11-05) + + +### ⚠ BREAKING CHANGES + +* the global/per-peer moving average tracking has been removed from the interface as it's expensive and requires lots of timers - this functionality can be replicated by implementations if it's desirable. It's better to have simple counters instead and let an external system like Prometheus or Graphana calculate the values over time + +### Features + +* return metrics objects from register instead of updating with an options object ([#310](https://github.com/libp2p/js-libp2p-interfaces/issues/310)) ([3b106ce](https://github.com/libp2p/js-libp2p-interfaces/commit/3b106ce799b5d84a82a66238995e09970ed8116c)) + + +### Bug Fixes + +* update project config ([#311](https://github.com/libp2p/js-libp2p-interfaces/issues/311)) ([27dd0ce](https://github.com/libp2p/js-libp2p-interfaces/commit/27dd0ce3c249892ac69cbb24ddaf0b9f32385e37)) + + +### Dependencies + +* update sibling dependencies ([6f41152](https://github.com/libp2p/js-libp2p-interfaces/commit/6f41152cc10e9babd338fe0c0d3c9bfff6eee960)) + +## [@libp2p/interface-mocks-v7.1.0](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-mocks-v7.0.3...@libp2p/interface-mocks-v7.1.0) (2022-11-05) + + +### Features + +* allow passing muxer factory to mock upgrader ([#309](https://github.com/libp2p/js-libp2p-interfaces/issues/309)) ([b2a4d92](https://github.com/libp2p/js-libp2p-interfaces/commit/b2a4d9231580e4cfc7b662e4cdae72f43e1c1011)) + +## [@libp2p/interface-mocks-v7.0.3](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-mocks-v7.0.2...@libp2p/interface-mocks-v7.0.3) (2022-10-18) + + +### Dependencies + +* bump it-ndjson from 0.1.1 to 1.0.0 ([#308](https://github.com/libp2p/js-libp2p-interfaces/issues/308)) ([54db8a4](https://github.com/libp2p/js-libp2p-interfaces/commit/54db8a45c8e533b832c0b7b0f6847c28d7185676)) + +## [@libp2p/interface-mocks-v7.0.2](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-mocks-v7.0.1...@libp2p/interface-mocks-v7.0.2) (2022-10-17) + + +### Dependencies + +* bump it-map from 1.0.6 to 2.0.0 ([#304](https://github.com/libp2p/js-libp2p-interfaces/issues/304)) ([8a1f7f4](https://github.com/libp2p/js-libp2p-interfaces/commit/8a1f7f4241d3acf250ee81a2265a00f58e80e6ed)) + +## [@libp2p/interface-mocks-v7.0.1](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-mocks-v7.0.0...@libp2p/interface-mocks-v7.0.1) (2022-10-12) + + +### Bug Fixes + +* export network components type ([79a5d8f](https://github.com/libp2p/js-libp2p-interfaces/commit/79a5d8fc57ae47274ff9ad9c3969c5898f07eb1d)) +* update mock network components use ([c760e95](https://github.com/libp2p/js-libp2p-interfaces/commit/c760e95f07b6199f08adb20c1e3a4265649fdda0)) + + +### Trivial Changes + +* fix linting ([a8ab192](https://github.com/libp2p/js-libp2p-interfaces/commit/a8ab19295452c388d6556ea7847c490035455c99)) + +## [@libp2p/interface-mocks-v7.0.0](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-mocks-v6.1.0...@libp2p/interface-mocks-v7.0.0) (2022-10-12) + + +### ⚠ BREAKING CHANGES + +* modules no longer implement `Initializable` instead switching to constructor injection + +### Bug Fixes + +* remove @libp2p/components ([#301](https://github.com/libp2p/js-libp2p-interfaces/issues/301)) ([1d37dc6](https://github.com/libp2p/js-libp2p-interfaces/commit/1d37dc6d3197838a71895d5769ad8bba6eb38fd3)) + + +### Dependencies + +* update sibling dependencies ([99330b2](https://github.com/libp2p/js-libp2p-interfaces/commit/99330b20842b2aff7530d1b9d373e8dce1ec3699)) +* update sibling dependencies ([6f26d1b](https://github.com/libp2p/js-libp2p-interfaces/commit/6f26d1b0343f4b41c064fab3ef87f308fc0c652d)) +* update sibling dependencies ([2ad1fa3](https://github.com/libp2p/js-libp2p-interfaces/commit/2ad1fa37c46b4c472570d79a592e798f20ed0cc8)) + +## [@libp2p/interface-mocks-v6.1.0](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-mocks-v6.0.1...@libp2p/interface-mocks-v6.1.0) (2022-10-11) + + +### Features + +* add afterUpgradeInbound method ([#300](https://github.com/libp2p/js-libp2p-interfaces/issues/300)) ([fbdf5f5](https://github.com/libp2p/js-libp2p-interfaces/commit/fbdf5f54277735a26df0a28099eeae9d57159978)) + +## [@libp2p/interface-mocks-v6.0.1](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-mocks-v6.0.0...@libp2p/interface-mocks-v6.0.1) (2022-10-07) + + +### Dependencies + +* bump @libp2p/components from 2.1.1 to 3.0.0 ([#299](https://github.com/libp2p/js-libp2p-interfaces/issues/299)) ([b3f493c](https://github.com/libp2p/js-libp2p-interfaces/commit/b3f493c5e260f697f66de54b56379d036ca3db59)) + +## [@libp2p/interface-mocks-v6.0.0](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-mocks-v5.1.0...@libp2p/interface-mocks-v6.0.0) (2022-10-06) + + +### ⚠ BREAKING CHANGES + +* the return type of StreamMuxer.newStream can now return a promise + +Co-authored-by: Marco Munizaga + +### Features + +* add upgrader options ([#290](https://github.com/libp2p/js-libp2p-interfaces/issues/290)) ([c502b66](https://github.com/libp2p/js-libp2p-interfaces/commit/c502b66d87020eb8e2768c49be17392c55503f69)) + + +### Dependencies + +* update sibling dependencies ([0fae3ee](https://github.com/libp2p/js-libp2p-interfaces/commit/0fae3ee43fab43293fb290654a927b5c5c5759fc)) +* update sibling dependencies ([8a89a05](https://github.com/libp2p/js-libp2p-interfaces/commit/8a89a054e95827dd8cccc033669e17ae58059fbc)) +* update sibling dependencies ([66b4993](https://github.com/libp2p/js-libp2p-interfaces/commit/66b49938a09eeb12bf8ec8d78938d5cffd6ec134)) + +## [@libp2p/interface-mocks-v5.1.0](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-mocks-v5.0.0...@libp2p/interface-mocks-v5.1.0) (2022-10-04) + + +### Features + +* add acceptIncomingConnection to ConnectionManager ([#295](https://github.com/libp2p/js-libp2p-interfaces/issues/295)) ([5d460e8](https://github.com/libp2p/js-libp2p-interfaces/commit/5d460e8815a8b49915da7ffabccc4a8b96a61acc)) + +## [@libp2p/interface-mocks-v5.0.0](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-mocks-v4.0.3...@libp2p/interface-mocks-v5.0.0) (2022-10-04) + + +### ⚠ BREAKING CHANGES + +* Add remoteExtensions to connection-encrypter (#293) + +### Features + +* Add remoteExtensions to connection-encrypter ([#293](https://github.com/libp2p/js-libp2p-interfaces/issues/293)) ([501c684](https://github.com/libp2p/js-libp2p-interfaces/commit/501c684d792cd910de7cb9bfbda349db257ee2ca)) + + +### Dependencies + +* update sibling dependencies ([419f947](https://github.com/libp2p/js-libp2p-interfaces/commit/419f9479e8bba5d0555fe20a6fb9f0cf12a82cf9)) + +## [@libp2p/interface-mocks-v4.0.3](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-mocks-v4.0.2...@libp2p/interface-mocks-v4.0.3) (2022-09-21) + + +### Dependencies + +* update @multiformats/multiaddr to 11.0.0 ([#288](https://github.com/libp2p/js-libp2p-interfaces/issues/288)) ([57b2ad8](https://github.com/libp2p/js-libp2p-interfaces/commit/57b2ad88edfc7807311143791bc49270b1a81eaf)) + +## [@libp2p/interface-mocks-v4.0.2](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-mocks-v4.0.1...@libp2p/interface-mocks-v4.0.2) (2022-08-11) + + +### Bug Fixes + +* update marshal type ([#282](https://github.com/libp2p/js-libp2p-interfaces/issues/282)) ([2c04ff9](https://github.com/libp2p/js-libp2p-interfaces/commit/2c04ff98097ba33dc64878b788c6b9318d2ea98b)) + +## [@libp2p/interface-mocks-v4.0.1](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-mocks-v4.0.0...@libp2p/interface-mocks-v4.0.1) (2022-08-10) + + +### Bug Fixes + +* revert connection encryption change to accept Uint8ArrayLists ([#280](https://github.com/libp2p/js-libp2p-interfaces/issues/280)) ([03d763c](https://github.com/libp2p/js-libp2p-interfaces/commit/03d763c1a6b168bba001783a1fb59af3f7d4e205)) + +## [@libp2p/interface-mocks-v4.0.0](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-mocks-v3.0.3...@libp2p/interface-mocks-v4.0.0) (2022-08-07) + + +### ⚠ BREAKING CHANGES + +* change stream muxer interface (#279) +* change connection encryption interface to uint8arraylist (#278) + +### Features + +* change connection encryption interface to uint8arraylist ([#278](https://github.com/libp2p/js-libp2p-interfaces/issues/278)) ([1fa580c](https://github.com/libp2p/js-libp2p-interfaces/commit/1fa580c5a45325dc9384738e9a78a238eabb81c3)) +* change stream muxer interface ([#279](https://github.com/libp2p/js-libp2p-interfaces/issues/279)) ([1ebe269](https://github.com/libp2p/js-libp2p-interfaces/commit/1ebe26988b6a286f36a4fc5177f502cfb60368a1)) + + +### Dependencies + +* update sibling dependencies ([f75e927](https://github.com/libp2p/js-libp2p-interfaces/commit/f75e9271345910e812ad600f936f4f774028e3fe)) +* update sibling dependencies ([d98a5ea](https://github.com/libp2p/js-libp2p-interfaces/commit/d98a5ea604c817cf6da47d9e86eea1e981b48711)) +* update sibling dependencies ([f859920](https://github.com/libp2p/js-libp2p-interfaces/commit/f859920423587ae797ac90ccaa3af8bdf60ae549)) +* update sibling dependencies ([93a89b1](https://github.com/libp2p/js-libp2p-interfaces/commit/93a89b1ca6d35fb5f26963ae7bb10026f3f5d45d)) + +## [@libp2p/interface-mocks-v3.0.3](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-mocks-v3.0.2...@libp2p/interface-mocks-v3.0.3) (2022-07-31) + + +### Dependencies + +* update uint8arraylist and p-wait-for deps ([#274](https://github.com/libp2p/js-libp2p-interfaces/issues/274)) ([c55f12e](https://github.com/libp2p/js-libp2p-interfaces/commit/c55f12e47be0a10e41709b0d6a60dd8bc1209ee5)) + +## [@libp2p/interface-mocks-v3.0.2](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-mocks-v3.0.1...@libp2p/interface-mocks-v3.0.2) (2022-07-31) + + +### Bug Fixes + +* mock connection manager close reciprocal connection ([#268](https://github.com/libp2p/js-libp2p-interfaces/issues/268)) ([f16dd7b](https://github.com/libp2p/js-libp2p-interfaces/commit/f16dd7bed2735e3a27e8febfe48bac75d4ff009f)) + + +### Trivial Changes + +* update project config ([#271](https://github.com/libp2p/js-libp2p-interfaces/issues/271)) ([59c0bf5](https://github.com/libp2p/js-libp2p-interfaces/commit/59c0bf5e0b05496fca2e4902632b61bb41fad9e9)) + +## [@libp2p/interface-mocks-v3.0.1](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-mocks-v3.0.0...@libp2p/interface-mocks-v3.0.1) (2022-06-27) + + +### Trivial Changes + +* update deps ([#262](https://github.com/libp2p/js-libp2p-interfaces/issues/262)) ([51edf7d](https://github.com/libp2p/js-libp2p-interfaces/commit/51edf7d9b3765a6f75c915b1483ea345d0133a41)) + +## [@libp2p/interface-mocks-v3.0.0](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-mocks-v2.1.0...@libp2p/interface-mocks-v3.0.0) (2022-06-24) + + +### ⚠ BREAKING CHANGES + +* StreamMuxer now has a `close` method + +### Features + +* add stream muxer close ([#254](https://github.com/libp2p/js-libp2p-interfaces/issues/254)) ([d1f511e](https://github.com/libp2p/js-libp2p-interfaces/commit/d1f511e4b5857769c4eddf902288dc69fcb667b4)) + + +### Trivial Changes + +* update sibling dependencies [skip ci] ([7f7fb67](https://github.com/libp2p/js-libp2p-interfaces/commit/7f7fb67b054688bfbc0cc68b9f2892bee8b41f13)) +* update sibling dependencies [skip ci] ([c522241](https://github.com/libp2p/js-libp2p-interfaces/commit/c522241b08cfef3995efb5415104f46521dcd3b7)) + +## [@libp2p/interface-mocks-v2.1.0](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-mocks-v2.0.1...@libp2p/interface-mocks-v2.1.0) (2022-06-21) + + +### Features + +* add direction to StreamMuxerInit ([#253](https://github.com/libp2p/js-libp2p-interfaces/issues/253)) ([6d34d75](https://github.com/libp2p/js-libp2p-interfaces/commit/6d34d755ff4e798d52945f1f099052bdd6a83f2b)) + +## [@libp2p/interface-mocks-v2.0.1](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-mocks-v2.0.0...@libp2p/interface-mocks-v2.0.1) (2022-06-17) + + +### Bug Fixes + +* update stream handler args ([#247](https://github.com/libp2p/js-libp2p-interfaces/issues/247)) ([d29e134](https://github.com/libp2p/js-libp2p-interfaces/commit/d29e134bd70295c725bfd627d5887954d1a278ae)) + +## [@libp2p/interface-mocks-v2.0.0](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-mocks-v1.1.0...@libp2p/interface-mocks-v2.0.0) (2022-06-16) + + +### ⚠ BREAKING CHANGES + +* The Connection and Stream APIs have been updated + +### Features + +* store stream data on the stream, track the stream direction ([#245](https://github.com/libp2p/js-libp2p-interfaces/issues/245)) ([6d74d2f](https://github.com/libp2p/js-libp2p-interfaces/commit/6d74d2f9f344fb4d6741ba0d35263ebe351a4c65)) + + +### Trivial Changes + +* update deps ([970a940](https://github.com/libp2p/js-libp2p-interfaces/commit/970a940a2f65b946936a53febdc52527baefbd34)) +* update deps ([219e60e](https://github.com/libp2p/js-libp2p-interfaces/commit/219e60ec6f886b95803457fe48dfcdb4ed57e34c)) +* update deps ([545264f](https://github.com/libp2p/js-libp2p-interfaces/commit/545264f87a58394d2a7da77e93f3a596e889238f)) + +## [@libp2p/interface-mocks-v1.1.0](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-mocks-v1.0.1...@libp2p/interface-mocks-v1.1.0) (2022-06-16) + + +### Features + +* define stream limits as input/output ([#240](https://github.com/libp2p/js-libp2p-interfaces/issues/240)) ([554fe95](https://github.com/libp2p/js-libp2p-interfaces/commit/554fe95865c4851fcef3b311d80d44f82a613969)) + +## [@libp2p/interface-mocks-v1.0.1](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-mocks-v1.0.0...@libp2p/interface-mocks-v1.0.1) (2022-06-14) + + +### Bug Fixes + +* remove components from muxer factory function ([#238](https://github.com/libp2p/js-libp2p-interfaces/issues/238)) ([e4dab30](https://github.com/libp2p/js-libp2p-interfaces/commit/e4dab306d9bf406b9bb3cb92644e28cf81f7bda6)) + + +### Trivial Changes + +* update components module ([#235](https://github.com/libp2p/js-libp2p-interfaces/issues/235)) ([5844207](https://github.com/libp2p/js-libp2p-interfaces/commit/58442070af59aa852c83ec3aecdbd1d2c646b018)) +* update it-pushable dep ([#237](https://github.com/libp2p/js-libp2p-interfaces/issues/237)) ([2e16465](https://github.com/libp2p/js-libp2p-interfaces/commit/2e164658df344b5ec475be2a571df5d6f20ee086)) + +## [@libp2p/interface-compliance-tests-v2.0.3](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-compliance-tests-v2.0.2...@libp2p/interface-compliance-tests-v2.0.3) (2022-05-24) + + +### Bug Fixes + +* only close muxed stream for reading ([#220](https://github.com/libp2p/js-libp2p-interfaces/issues/220)) ([f2f7141](https://github.com/libp2p/js-libp2p-interfaces/commit/f2f7141f01af715e600201ac9e7e52fbbb5c7e1b)) + +## [@libp2p/interface-compliance-tests-v2.0.2](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-compliance-tests-v2.0.1...@libp2p/interface-compliance-tests-v2.0.2) (2022-05-24) + + +### Bug Fixes + +* accept abort options in connection.newStream ([#219](https://github.com/libp2p/js-libp2p-interfaces/issues/219)) ([8bfcbc9](https://github.com/libp2p/js-libp2p-interfaces/commit/8bfcbc9ee883336f213cdfc83e477549ca368df5)) +* chunk data in mock muxer ([#218](https://github.com/libp2p/js-libp2p-interfaces/issues/218)) ([14604f6](https://github.com/libp2p/js-libp2p-interfaces/commit/14604f69a858bf8c16ce118420c5e49f3f5331ea)) + +## [@libp2p/interface-compliance-tests-v2.0.1](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-compliance-tests-v2.0.0...@libp2p/interface-compliance-tests-v2.0.1) (2022-05-23) + + +### Bug Fixes + +* make stream return types synchronous ([#217](https://github.com/libp2p/js-libp2p-interfaces/issues/217)) ([2fe61b7](https://github.com/libp2p/js-libp2p-interfaces/commit/2fe61b7fbeda2e549edf095a927d623aa8eb476b)) + +## [@libp2p/interface-compliance-tests-v2.0.0](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-compliance-tests-v1.1.34...@libp2p/interface-compliance-tests-v2.0.0) (2022-05-20) + + +### ⚠ BREAKING CHANGES + +* This adds closeWrite and closeRead checks in the tests, which will cause test failures for muxers that don't implement those + +### Bug Fixes + +* close streams when connection is closed ([#214](https://github.com/libp2p/js-libp2p-interfaces/issues/214)) ([88fcd58](https://github.com/libp2p/js-libp2p-interfaces/commit/88fcd586276e03dd740c7095f05e21754ac1f3b5)), closes [#90](https://github.com/libp2p/js-libp2p-interfaces/issues/90) +* update interfaces ([#215](https://github.com/libp2p/js-libp2p-interfaces/issues/215)) ([72e6890](https://github.com/libp2p/js-libp2p-interfaces/commit/72e6890826dadbd6e7cbba5536bde350ca4286e6)) + +## [@libp2p/interface-compliance-tests-v1.1.34](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-compliance-tests-v1.1.33...@libp2p/interface-compliance-tests-v1.1.34) (2022-05-10) + + +### Trivial Changes + +* **deps:** bump sinon from 13.0.2 to 14.0.0 ([#211](https://github.com/libp2p/js-libp2p-interfaces/issues/211)) ([8859f70](https://github.com/libp2p/js-libp2p-interfaces/commit/8859f70943c0bcdb210f54a338ae901739e5e6f2)) + +## [@libp2p/interface-compliance-tests-v1.1.33](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-compliance-tests-v1.1.32...@libp2p/interface-compliance-tests-v1.1.33) (2022-05-06) + + +### Bug Fixes + +* add tag to peer discovery interface ([#210](https://github.com/libp2p/js-libp2p-interfaces/issues/210)) ([f99c833](https://github.com/libp2p/js-libp2p-interfaces/commit/f99c833c8436f8434f380d890ec5d267279312d7)) + +## [@libp2p/interface-compliance-tests-v1.1.32](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-compliance-tests-v1.1.31...@libp2p/interface-compliance-tests-v1.1.32) (2022-05-04) + + +### Bug Fixes + +* move startable and events interfaces ([#209](https://github.com/libp2p/js-libp2p-interfaces/issues/209)) ([8ce8a08](https://github.com/libp2p/js-libp2p-interfaces/commit/8ce8a08c94b0738aa32da516558977b195ddd8ed)) + +## [@libp2p/interface-compliance-tests-v1.1.31](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-compliance-tests-v1.1.30...@libp2p/interface-compliance-tests-v1.1.31) (2022-05-03) + + +### Bug Fixes + +* only send handled protocols ([#207](https://github.com/libp2p/js-libp2p-interfaces/issues/207)) ([1f7afc2](https://github.com/libp2p/js-libp2p-interfaces/commit/1f7afc29d72fde708064ec6479011dbc0a225962)) + +## [@libp2p/interface-compliance-tests-v1.1.30](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-compliance-tests-v1.1.29...@libp2p/interface-compliance-tests-v1.1.30) (2022-05-01) + + +### Bug Fixes + +* move connection manager mock to connection manager module ([#205](https://github.com/libp2p/js-libp2p-interfaces/issues/205)) ([a367375](https://github.com/libp2p/js-libp2p-interfaces/commit/a367375accc690d7b4608c9a3313f91df700efd8)) + +## [@libp2p/interface-compliance-tests-v1.1.29](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-compliance-tests-v1.1.28...@libp2p/interface-compliance-tests-v1.1.29) (2022-04-28) + + +### Bug Fixes + +* pubsub should not be startable ([#204](https://github.com/libp2p/js-libp2p-interfaces/issues/204)) ([59bd924](https://github.com/libp2p/js-libp2p-interfaces/commit/59bd9245a207268525bdd26a05c5306fe436fcc4)) + +## [@libp2p/interface-compliance-tests-v1.1.28](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-compliance-tests-v1.1.27...@libp2p/interface-compliance-tests-v1.1.28) (2022-04-28) + + +### Bug Fixes + +* pubsub and dht are always set ([#203](https://github.com/libp2p/js-libp2p-interfaces/issues/203)) ([86860c1](https://github.com/libp2p/js-libp2p-interfaces/commit/86860c1836a2464b2ad380b09542e3f3271ae287)) + +## [@libp2p/interface-compliance-tests-v1.1.27](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-compliance-tests-v1.1.26...@libp2p/interface-compliance-tests-v1.1.27) (2022-04-26) + + +### Bug Fixes + +* add delays for gossipsub ([#202](https://github.com/libp2p/js-libp2p-interfaces/issues/202)) ([cf85799](https://github.com/libp2p/js-libp2p-interfaces/commit/cf85799fdd0d4848ad2187bbbb0dd6ac5e8cb254)) + +## [@libp2p/interface-compliance-tests-v1.1.26](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-compliance-tests-v1.1.25...@libp2p/interface-compliance-tests-v1.1.26) (2022-04-25) + + +### Bug Fixes + +* stop pubsub after test ([#200](https://github.com/libp2p/js-libp2p-interfaces/issues/200)) ([2d2650c](https://github.com/libp2p/js-libp2p-interfaces/commit/2d2650cb8cabce137665aafd55a2fb14cbd5dacd)) + +## [@libp2p/interface-compliance-tests-v1.1.25](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-compliance-tests-v1.1.24...@libp2p/interface-compliance-tests-v1.1.25) (2022-04-22) + + +### Bug Fixes + +* update pubsub interface in line with gossipsub ([#199](https://github.com/libp2p/js-libp2p-interfaces/issues/199)) ([3f55596](https://github.com/libp2p/js-libp2p-interfaces/commit/3f555965cddea3ef03e7217b755c82aa4107e093)) + +## [@libp2p/interface-compliance-tests-v1.1.24](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-compliance-tests-v1.1.23...@libp2p/interface-compliance-tests-v1.1.24) (2022-04-21) + + +### Bug Fixes + +* test PubSub interface and not PubSubBaseProtocol ([#198](https://github.com/libp2p/js-libp2p-interfaces/issues/198)) ([96c15c9](https://github.com/libp2p/js-libp2p-interfaces/commit/96c15c9780821a3cb763e48854d64377bf562692)) + +## [@libp2p/interface-compliance-tests-v1.1.23](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-compliance-tests-v1.1.22...@libp2p/interface-compliance-tests-v1.1.23) (2022-04-20) + + +### Bug Fixes + +* emit pubsub messages using 'message' event ([#197](https://github.com/libp2p/js-libp2p-interfaces/issues/197)) ([df9b685](https://github.com/libp2p/js-libp2p-interfaces/commit/df9b685cea30653109f2fa2cb5583a3bca7b09bb)) + +## [@libp2p/interface-compliance-tests-v1.1.22](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-compliance-tests-v1.1.21...@libp2p/interface-compliance-tests-v1.1.22) (2022-04-19) + + +### Bug Fixes + +* move dev deps to prod ([#195](https://github.com/libp2p/js-libp2p-interfaces/issues/195)) ([3e1ffc7](https://github.com/libp2p/js-libp2p-interfaces/commit/3e1ffc7b174e74be483943ad4e5fcab823ae3f6d)) + +## [@libp2p/interface-compliance-tests-v1.1.21](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-compliance-tests-v1.1.20...@libp2p/interface-compliance-tests-v1.1.21) (2022-04-08) + + +### Bug Fixes + +* swap protobufjs for protons ([#191](https://github.com/libp2p/js-libp2p-interfaces/issues/191)) ([d72b30c](https://github.com/libp2p/js-libp2p-interfaces/commit/d72b30cfca4b9145e0b31db28e8fa3329a180e83)) + + +### Trivial Changes + +* update aegir ([#192](https://github.com/libp2p/js-libp2p-interfaces/issues/192)) ([41c1494](https://github.com/libp2p/js-libp2p-interfaces/commit/41c14941e8b67d6601a90b4d48a2776573d55e60)) + +## [@libp2p/interface-compliance-tests-v1.1.20](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-compliance-tests-v1.1.19...@libp2p/interface-compliance-tests-v1.1.20) (2022-03-24) + + +### Bug Fixes + +* rename peer data to peer info ([#187](https://github.com/libp2p/js-libp2p-interfaces/issues/187)) ([dfea342](https://github.com/libp2p/js-libp2p-interfaces/commit/dfea3429bad57abde040397e4e7a58539829e9c2)) + +## [@libp2p/interface-compliance-tests-v1.1.19](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-compliance-tests-v1.1.18...@libp2p/interface-compliance-tests-v1.1.19) (2022-03-22) + + +### Bug Fixes + +* add method for startable lifecyle ([#186](https://github.com/libp2p/js-libp2p-interfaces/issues/186)) ([2730e29](https://github.com/libp2p/js-libp2p-interfaces/commit/2730e2947bbd231db3f7f82951b51ee534733ab2)) + +## [@libp2p/interface-compliance-tests-v1.1.18](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-compliance-tests-v1.1.17...@libp2p/interface-compliance-tests-v1.1.18) (2022-03-20) + + +### Bug Fixes + +* update pubsub types ([#183](https://github.com/libp2p/js-libp2p-interfaces/issues/183)) ([7ef4baa](https://github.com/libp2p/js-libp2p-interfaces/commit/7ef4baad0fe30f783f3eecd5199ef92af08b7f57)) + +## [@libp2p/interface-compliance-tests-v1.1.17](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-compliance-tests-v1.1.16...@libp2p/interface-compliance-tests-v1.1.17) (2022-03-15) + + +### Bug Fixes + +* use custom event instead of error event ([#181](https://github.com/libp2p/js-libp2p-interfaces/issues/181)) ([71ab242](https://github.com/libp2p/js-libp2p-interfaces/commit/71ab2424dfbf6337111d6d9d994f27c7967c20f1)) + +## [@libp2p/interface-compliance-tests-v1.1.16](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-compliance-tests-v1.1.15...@libp2p/interface-compliance-tests-v1.1.16) (2022-03-15) + + +### Bug Fixes + +* simplify transport interface, update interfaces for use with libp2p ([#180](https://github.com/libp2p/js-libp2p-interfaces/issues/180)) ([ec81622](https://github.com/libp2p/js-libp2p-interfaces/commit/ec81622e5b7c6d256e0f8aed6d3695642473293b)) + +## [@libp2p/interface-compliance-tests-v1.1.15](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-compliance-tests-v1.1.14...@libp2p/interface-compliance-tests-v1.1.15) (2022-02-27) + + +### Bug Fixes + +* rename crypto to connection-encrypter ([#179](https://github.com/libp2p/js-libp2p-interfaces/issues/179)) ([d197f55](https://github.com/libp2p/js-libp2p-interfaces/commit/d197f554d7cdadb3b05ed2d6c69fda2c4362b1eb)) + +## [@libp2p/interface-compliance-tests-v1.1.14](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-compliance-tests-v1.1.13...@libp2p/interface-compliance-tests-v1.1.14) (2022-02-27) + + +### Bug Fixes + +* update package config and add connection gater interface ([#178](https://github.com/libp2p/js-libp2p-interfaces/issues/178)) ([c6079a6](https://github.com/libp2p/js-libp2p-interfaces/commit/c6079a6367f004788062df3e30ad2e26330d947b)) + +## [@libp2p/interface-compliance-tests-v1.1.13](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-compliance-tests-v1.1.12...@libp2p/interface-compliance-tests-v1.1.13) (2022-02-21) + + +### Bug Fixes + +* increase stream test timeout ([#175](https://github.com/libp2p/js-libp2p-interfaces/issues/175)) ([568aefb](https://github.com/libp2p/js-libp2p-interfaces/commit/568aefb5c099ba0161ffecf86bda238b92d396b0)) + +## [@libp2p/interface-compliance-tests-v1.1.12](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-compliance-tests-v1.1.11...@libp2p/interface-compliance-tests-v1.1.12) (2022-02-21) + + +### Bug Fixes + +* update muxer to pass transport tests ([#174](https://github.com/libp2p/js-libp2p-interfaces/issues/174)) ([466ed53](https://github.com/libp2p/js-libp2p-interfaces/commit/466ed53192aa196ac2dbdb83df3c8db9cd5b1e07)) + +## [@libp2p/interface-compliance-tests-v1.1.11](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-compliance-tests-v1.1.10...@libp2p/interface-compliance-tests-v1.1.11) (2022-02-18) + + +### Bug Fixes + +* remove delays from pubsub tests ([#173](https://github.com/libp2p/js-libp2p-interfaces/issues/173)) ([5c8fe09](https://github.com/libp2p/js-libp2p-interfaces/commit/5c8fe09294f0cbd8add1406a61fa7dbc5b4e788b)) + +## [@libp2p/interface-compliance-tests-v1.1.10](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-compliance-tests-v1.1.9...@libp2p/interface-compliance-tests-v1.1.10) (2022-02-18) + + +### Bug Fixes + +* simpler pubsub ([#172](https://github.com/libp2p/js-libp2p-interfaces/issues/172)) ([98715ed](https://github.com/libp2p/js-libp2p-interfaces/commit/98715ed73183b32e4fda3d878a462389548358d9)) + +## [@libp2p/interface-compliance-tests-v1.1.9](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-compliance-tests-v1.1.8...@libp2p/interface-compliance-tests-v1.1.9) (2022-02-17) + + +### Bug Fixes + +* update deps ([#171](https://github.com/libp2p/js-libp2p-interfaces/issues/171)) ([d0d2564](https://github.com/libp2p/js-libp2p-interfaces/commit/d0d2564a84a0722ab587a3aa6ec01e222442b100)) + +## [@libp2p/interface-compliance-tests-v1.1.8](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-compliance-tests-v1.1.7...@libp2p/interface-compliance-tests-v1.1.8) (2022-02-17) + + +### Bug Fixes + +* add multistream-select and update pubsub types ([#170](https://github.com/libp2p/js-libp2p-interfaces/issues/170)) ([b9ecb2b](https://github.com/libp2p/js-libp2p-interfaces/commit/b9ecb2bee8f2abc0c41bfcf7bf2025894e37ddc2)) + +## [@libp2p/interface-compliance-tests-v1.1.7](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-compliance-tests-v1.1.6...@libp2p/interface-compliance-tests-v1.1.7) (2022-02-16) + + +### Bug Fixes + +* test muxer ([#169](https://github.com/libp2p/js-libp2p-interfaces/issues/169)) ([574723d](https://github.com/libp2p/js-libp2p-interfaces/commit/574723d11007e875e7adfa5c32819445f9b8def7)) + +## [@libp2p/interface-compliance-tests-v1.1.6](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-compliance-tests-v1.1.5...@libp2p/interface-compliance-tests-v1.1.6) (2022-02-12) + + +### Bug Fixes + +* return registered topologies in mock ([#168](https://github.com/libp2p/js-libp2p-interfaces/issues/168)) ([1583019](https://github.com/libp2p/js-libp2p-interfaces/commit/158301982384a694ac3fb8f9df67c71b7b776b47)) + +## [@libp2p/interface-compliance-tests-v1.1.5](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-compliance-tests-v1.1.4...@libp2p/interface-compliance-tests-v1.1.5) (2022-02-12) + + +### Bug Fixes + +* hide implementations behind factory methods ([#167](https://github.com/libp2p/js-libp2p-interfaces/issues/167)) ([2fba080](https://github.com/libp2p/js-libp2p-interfaces/commit/2fba0800c9896af6dcc49da4fa904bb4a3e3e40d)) + +## [@libp2p/interface-compliance-tests-v1.1.4](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-compliance-tests-v1.1.3...@libp2p/interface-compliance-tests-v1.1.4) (2022-02-11) + + +### Bug Fixes + +* simpler topologies ([#164](https://github.com/libp2p/js-libp2p-interfaces/issues/164)) ([45fcaa1](https://github.com/libp2p/js-libp2p-interfaces/commit/45fcaa10a6a3215089340ff2eff117d7fd1100e7)) + +## [@libp2p/interface-compliance-tests-v1.1.3](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-compliance-tests-v1.1.2...@libp2p/interface-compliance-tests-v1.1.3) (2022-02-10) + + +### Bug Fixes + +* make registrar simpler ([#163](https://github.com/libp2p/js-libp2p-interfaces/issues/163)) ([d122f3d](https://github.com/libp2p/js-libp2p-interfaces/commit/d122f3daaccc04039d90814960da92b513265644)) + +## [@libp2p/interface-compliance-tests-v1.1.2](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-compliance-tests-v1.1.1...@libp2p/interface-compliance-tests-v1.1.2) (2022-02-10) + + +### Bug Fixes + +* remove args from listener events ([#162](https://github.com/libp2p/js-libp2p-interfaces/issues/162)) ([011ac89](https://github.com/libp2p/js-libp2p-interfaces/commit/011ac891ec7d44625cb4342f068bcd9f241a5b45)) + +## [@libp2p/interface-compliance-tests-v1.1.1](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-compliance-tests-v1.1.0...@libp2p/interface-compliance-tests-v1.1.1) (2022-02-10) + + +### Bug Fixes + +* remove node event emitters ([#161](https://github.com/libp2p/js-libp2p-interfaces/issues/161)) ([221fb6a](https://github.com/libp2p/js-libp2p-interfaces/commit/221fb6a024430dc56288d73d8b8ce1aa88427701)) + +## [@libp2p/interface-compliance-tests-v1.1.0](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-compliance-tests-v1.0.8...@libp2p/interface-compliance-tests-v1.1.0) (2022-02-09) + + +### Features + +* add peer store/records, and streams are just streams ([#160](https://github.com/libp2p/js-libp2p-interfaces/issues/160)) ([8860a0c](https://github.com/libp2p/js-libp2p-interfaces/commit/8860a0cd46b359a5648402d83870f7ff957222fe)) + +## [@libp2p/interface-compliance-tests-v1.0.8](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-compliance-tests-v1.0.7...@libp2p/interface-compliance-tests-v1.0.8) (2022-02-05) + + +### Bug Fixes + +* fix muxer tests ([#157](https://github.com/libp2p/js-libp2p-interfaces/issues/157)) ([7233c44](https://github.com/libp2p/js-libp2p-interfaces/commit/7233c4438479dff56a682f45209ef7a938d63857)) + +## [@libp2p/interface-compliance-tests-v1.0.7](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-compliance-tests-v1.0.6...@libp2p/interface-compliance-tests-v1.0.7) (2022-01-31) + + +### Trivial Changes + +* **deps:** bump sinon from 12.0.1 to 13.0.0 ([#154](https://github.com/libp2p/js-libp2p-interfaces/issues/154)) ([3fc8812](https://github.com/libp2p/js-libp2p-interfaces/commit/3fc8812897fa197e7b62f77614abaea4a5563404)) + +## [@libp2p/interface-compliance-tests-v1.0.6](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-compliance-tests-v1.0.5...@libp2p/interface-compliance-tests-v1.0.6) (2022-01-29) + + +### Bug Fixes + +* remove extra fields ([#153](https://github.com/libp2p/js-libp2p-interfaces/issues/153)) ([ccd7cf3](https://github.com/libp2p/js-libp2p-interfaces/commit/ccd7cf3f5ac71337baf516d3b0f6fc724ee0d3b4)) + +## [@libp2p/interface-compliance-tests-v1.0.5](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-compliance-tests-v1.0.4...@libp2p/interface-compliance-tests-v1.0.5) (2022-01-15) + + +### Bug Fixes + +* remove abort controller dep ([#151](https://github.com/libp2p/js-libp2p-interfaces/issues/151)) ([518bce1](https://github.com/libp2p/js-libp2p-interfaces/commit/518bce1f9bd1f8b2922338e0c65c9934af7da3af)) + +## [@libp2p/interface-compliance-tests-v1.0.4](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-compliance-tests-v1.0.3...@libp2p/interface-compliance-tests-v1.0.4) (2022-01-15) + + +### Trivial Changes + +* update project config ([#149](https://github.com/libp2p/js-libp2p-interfaces/issues/149)) ([6eb8556](https://github.com/libp2p/js-libp2p-interfaces/commit/6eb85562c0da167d222808da10a7914daf12970b)) + +## [@libp2p/interface-compliance-tests-v1.0.3](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-compliance-tests-v1.0.2...@libp2p/interface-compliance-tests-v1.0.3) (2022-01-14) + + +### Bug Fixes + +* update it-* deps to ts versions ([#148](https://github.com/libp2p/js-libp2p-interfaces/issues/148)) ([7a6fdd7](https://github.com/libp2p/js-libp2p-interfaces/commit/7a6fdd7622ce2870b89dbb849ab421d0dd714b43)) + +## [@libp2p/interface-compliance-tests-v1.0.2](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-compliance-tests-v1.0.1...@libp2p/interface-compliance-tests-v1.0.2) (2022-01-08) + + +### Trivial Changes + +* add semantic release config ([#141](https://github.com/libp2p/js-libp2p-interfaces/issues/141)) ([5f0de59](https://github.com/libp2p/js-libp2p-interfaces/commit/5f0de59136b6343d2411abb2d6a4dd2cd0b7efe4)) +* update package versions ([#140](https://github.com/libp2p/js-libp2p-interfaces/issues/140)) ([cd844f6](https://github.com/libp2p/js-libp2p-interfaces/commit/cd844f6e39f4ee50d006e86eac8dadf696900eb5)) + +# Change Log + +All notable changes to this project will be documented in this file. +See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +# 0.2.0 (2022-01-04) + + +### Features + +* add auto-publish ([7aede5d](https://github.com/libp2p/js-libp2p-interfaces/commit/7aede5df39ea6b5f243348ec9a212b3e33c16a81)) +* update package names ([#133](https://github.com/libp2p/js-libp2p-interfaces/issues/133)) ([337adc9](https://github.com/libp2p/js-libp2p-interfaces/commit/337adc9a9bc0278bdae8cbce9c57d07a83c8b5c2)) + + + + + +## [3.1.1](https://github.com/libp2p/js-libp2p-interfaces/compare/libp2p-interfaces-compliance-tests@3.1.0...libp2p-interfaces-compliance-tests@3.1.1) (2022-01-02) + + +### Bug Fixes + +* move errors ([#132](https://github.com/libp2p/js-libp2p-interfaces/issues/132)) ([21d282a](https://github.com/libp2p/js-libp2p-interfaces/commit/21d282a6d77bd7d1a12daa1cc8b3a3fed8635dad)) + + + + + +# [3.1.0](https://github.com/libp2p/js-libp2p-interfaces/compare/libp2p-interfaces-compliance-tests@3.0.0...libp2p-interfaces-compliance-tests@3.1.0) (2022-01-02) + + +### Bug Fixes + +* update dialer tests ([#116](https://github.com/libp2p/js-libp2p-interfaces/issues/116)) ([c679729](https://github.com/libp2p/js-libp2p-interfaces/commit/c679729113feb963ff27815fcafd7af51f722df7)) + + +### Features + +* simpler peer id ([#117](https://github.com/libp2p/js-libp2p-interfaces/issues/117)) ([fa2c4f5](https://github.com/libp2p/js-libp2p-interfaces/commit/fa2c4f5be74a5cfc11489771881e57b4e53bf174)) + + + + + +# [3.0.0](https://github.com/libp2p/js-libp2p-interfaces/compare/libp2p-interfaces-compliance-tests@2.0.0...libp2p-interfaces-compliance-tests@3.0.0) (2021-12-02) + + +### chore + +* update libp2p-crypto and peer-id ([c711e8b](https://github.com/libp2p/js-libp2p-interfaces/commit/c711e8bd4d606f6974b13fad2eeb723f93cebb87)) + + +### BREAKING CHANGES + +* requires node 15+ + + + + + +# [2.0.0](https://github.com/libp2p/js-libp2p-interfaces/compare/libp2p-interfaces-compliance-tests@1.1.2...libp2p-interfaces-compliance-tests@2.0.0) (2021-11-22) + + +### Features + +* split out code, convert to typescript ([#111](https://github.com/libp2p/js-libp2p-interfaces/issues/111)) ([e174bba](https://github.com/libp2p/js-libp2p-interfaces/commit/e174bba889388269b806643c79a6b53c8d6a0f8c)), closes [#110](https://github.com/libp2p/js-libp2p-interfaces/issues/110) [#101](https://github.com/libp2p/js-libp2p-interfaces/issues/101) + + +### BREAKING CHANGES + +* not all fields from concrete classes have been added to the interfaces, some adjustment may be necessary as this gets rolled out + + + + + +## [1.1.2](https://github.com/libp2p/js-libp2p-interfaces/compare/libp2p-interfaces-compliance-tests@1.1.1...libp2p-interfaces-compliance-tests@1.1.2) (2021-10-18) + +**Note:** Version bump only for package libp2p-interfaces-compliance-tests + + + + + +## [1.1.1](https://github.com/libp2p/js-libp2p-interfaces/compare/libp2p-interfaces-compliance-tests@1.1.0...libp2p-interfaces-compliance-tests@1.1.1) (2021-09-20) + +**Note:** Version bump only for package libp2p-interfaces-compliance-tests + + + + + +# [1.1.0](https://github.com/libp2p/js-libp2p-interfaces/compare/libp2p-interfaces-compliance-tests@1.0.1...libp2p-interfaces-compliance-tests@1.1.0) (2021-08-20) + + +### Features + +* update uint8arrays ([#105](https://github.com/libp2p/js-libp2p-interfaces/issues/105)) ([9297a9c](https://github.com/libp2p/js-libp2p-interfaces/commit/9297a9c379276d03c8da849af6108b38e581b4a6)) + + + + + +## [1.0.1](https://github.com/libp2p/js-libp2p-interfaces/compare/libp2p-interfaces-compliance-tests@1.0.0...libp2p-interfaces-compliance-tests@1.0.1) (2021-07-08) + + +### Bug Fixes + +* make tests more reliable ([#103](https://github.com/libp2p/js-libp2p-interfaces/issues/103)) ([cd4c409](https://github.com/libp2p/js-libp2p-interfaces/commit/cd4c40908efe2e9ffc14aa61aace5176a43fd70a)) +* remove timeouts ([#104](https://github.com/libp2p/js-libp2p-interfaces/issues/104)) ([3699c17](https://github.com/libp2p/js-libp2p-interfaces/commit/3699c17f022da40a87ab24adc3b2081df7a0ddcd)) + + + + + +# 1.0.0 (2021-07-07) + + +### chore + +* monorepo separating interfaces and compliance tests ([#97](https://github.com/libp2p/js-libp2p-interfaces/issues/97)) ([946348f](https://github.com/libp2p/js-libp2p-interfaces/commit/946348f7f8acc1ff7bc9cd0ab4c2602d41106f76)) + + +### BREAKING CHANGES + +* the tests now live in the libp2p-interfaces-compliance-tests module diff --git a/packages/interface-mocks/LICENSE b/packages/interface-mocks/LICENSE new file mode 100644 index 0000000000..20ce483c86 --- /dev/null +++ b/packages/interface-mocks/LICENSE @@ -0,0 +1,4 @@ +This project is dual licensed under MIT and Apache-2.0. + +MIT: https://www.opensource.org/licenses/mit +Apache-2.0: https://www.apache.org/licenses/license-2.0 diff --git a/packages/interface-mocks/LICENSE-APACHE b/packages/interface-mocks/LICENSE-APACHE new file mode 100644 index 0000000000..14478a3b60 --- /dev/null +++ b/packages/interface-mocks/LICENSE-APACHE @@ -0,0 +1,5 @@ +Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. diff --git a/packages/interface-mocks/LICENSE-MIT b/packages/interface-mocks/LICENSE-MIT new file mode 100644 index 0000000000..72dc60d84b --- /dev/null +++ b/packages/interface-mocks/LICENSE-MIT @@ -0,0 +1,19 @@ +The MIT License (MIT) + +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/packages/interface-mocks/README.md b/packages/interface-mocks/README.md new file mode 100644 index 0000000000..8b131cd614 --- /dev/null +++ b/packages/interface-mocks/README.md @@ -0,0 +1,50 @@ +# @libp2p/interface-mocks + +[![libp2p.io](https://img.shields.io/badge/project-libp2p-yellow.svg?style=flat-square)](http://libp2p.io/) +[![Discuss](https://img.shields.io/discourse/https/discuss.libp2p.io/posts.svg?style=flat-square)](https://discuss.libp2p.io) +[![codecov](https://img.shields.io/codecov/c/github/libp2p/js-libp2p.svg?style=flat-square)](https://codecov.io/gh/libp2p/js-libp2p) +[![CI](https://img.shields.io/github/actions/workflow/status/libp2p/js-libp2p/js-test-and-release.yml?branch=master\&style=flat-square)](https://github.com/libp2p/js-libp2p/actions/workflows/js-test-and-release.yml?query=branch%3Amaster) + +> Mock implementations of several libp2p interfaces + +## Table of contents + +- [Install](#install) + - [Browser ` +``` + +## Usage + +Each [interface](../interfaces) has its documentation on how to use the compliance tests and should be used as the source of truth. + +## API Docs + +- + +## License + +Licensed under either of + +- Apache 2.0, ([LICENSE-APACHE](LICENSE-APACHE) / ) +- MIT ([LICENSE-MIT](LICENSE-MIT) / ) + +## Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. diff --git a/packages/interface-mocks/package.json b/packages/interface-mocks/package.json new file mode 100644 index 0000000000..12ed79bda4 --- /dev/null +++ b/packages/interface-mocks/package.json @@ -0,0 +1,181 @@ +{ + "name": "@libp2p/interface-mocks", + "version": "12.0.1", + "description": "Mock implementations of several libp2p interfaces", + "license": "Apache-2.0 OR MIT", + "homepage": "https://github.com/libp2p/js-libp2p/tree/master/packages/interface-mocks#readme", + "repository": { + "type": "git", + "url": "git+https://github.com/libp2p/js-libp2p.git" + }, + "bugs": { + "url": "https://github.com/libp2p/js-libp2p/issues" + }, + "keywords": [ + "interface", + "libp2p" + ], + "type": "module", + "types": "./dist/src/index.d.ts", + "files": [ + "src", + "dist", + "!dist/test", + "!**/*.tsbuildinfo" + ], + "exports": { + ".": { + "types": "./dist/src/index.d.ts", + "import": "./dist/src/index.js" + } + }, + "eslintConfig": { + "extends": "ipfs", + "parserOptions": { + "sourceType": "module" + } + }, + "release": { + "branches": [ + "master" + ], + "plugins": [ + [ + "@semantic-release/commit-analyzer", + { + "preset": "conventionalcommits", + "releaseRules": [ + { + "breaking": true, + "release": "major" + }, + { + "revert": true, + "release": "patch" + }, + { + "type": "feat", + "release": "minor" + }, + { + "type": "fix", + "release": "patch" + }, + { + "type": "docs", + "release": "patch" + }, + { + "type": "test", + "release": "patch" + }, + { + "type": "deps", + "release": "patch" + }, + { + "scope": "no-release", + "release": false + } + ] + } + ], + [ + "@semantic-release/release-notes-generator", + { + "preset": "conventionalcommits", + "presetConfig": { + "types": [ + { + "type": "feat", + "section": "Features" + }, + { + "type": "fix", + "section": "Bug Fixes" + }, + { + "type": "chore", + "section": "Trivial Changes" + }, + { + "type": "docs", + "section": "Documentation" + }, + { + "type": "deps", + "section": "Dependencies" + }, + { + "type": "test", + "section": "Tests" + } + ] + } + } + ], + "@semantic-release/changelog", + "@semantic-release/npm", + "@semantic-release/github", + "@semantic-release/git" + ] + }, + "scripts": { + "clean": "aegir clean", + "lint": "aegir lint", + "dep-check": "aegir dep-check", + "build": "aegir build", + "test": "aegir test", + "test:chrome": "aegir test -t browser --cov", + "test:chrome-webworker": "aegir test -t webworker", + "test:firefox": "aegir test -t browser -- --browser firefox", + "test:firefox-webworker": "aegir test -t webworker -- --browser firefox", + "test:node": "aegir test -t node --cov", + "test:electron-main": "aegir test -t electron-main", + "release": "aegir release" + }, + "dependencies": { + "@libp2p/interface-connection": "^5.0.0", + "@libp2p/interface-connection-encrypter": "^4.0.0", + "@libp2p/interface-connection-gater": "^3.0.0", + "@libp2p/interface-connection-manager": "^3.0.0", + "@libp2p/interface-libp2p": "^3.0.0", + "@libp2p/interface-metrics": "^4.0.0", + "@libp2p/interface-peer-discovery": "^2.0.0", + "@libp2p/interface-peer-id": "^2.0.0", + "@libp2p/interface-peer-info": "^1.0.0", + "@libp2p/interface-pubsub": "^4.0.0", + "@libp2p/interface-registrar": "^2.0.0", + "@libp2p/interface-stream-muxer": "^4.0.0", + "@libp2p/interface-transport": "^4.0.0", + "@libp2p/interfaces": "^3.0.0", + "@libp2p/logger": "^2.1.1", + "@libp2p/multistream-select": "^3.1.8", + "@libp2p/peer-collections": "^3.0.1", + "@libp2p/peer-id": "^2.0.0", + "@libp2p/peer-id-factory": "^2.0.0", + "@multiformats/multiaddr": "^12.0.0", + "abortable-iterator": "^5.0.1", + "any-signal": "^4.1.1", + "it-handshake": "^4.1.3", + "it-map": "^3.0.2", + "it-ndjson": "^1.0.0", + "it-pair": "^2.0.2", + "it-pipe": "^3.0.1", + "it-pushable": "^3.1.3", + "it-stream-types": "^2.0.1", + "merge-options": "^3.0.4", + "uint8arraylist": "^2.4.3", + "uint8arrays": "^4.0.2" + }, + "devDependencies": { + "@libp2p/interface-connection-compliance-tests": "^2.0.0", + "@libp2p/interface-connection-encrypter-compliance-tests": "^5.0.0", + "@libp2p/interface-peer-discovery-compliance-tests": "^2.0.0", + "@libp2p/interface-stream-muxer-compliance-tests": "^7.0.0", + "aegir": "^39.0.5" + }, + "typedoc": { + "entryPoint": "./src/index.ts" + } +} diff --git a/packages/interface-mocks/src/connection-encrypter.ts b/packages/interface-mocks/src/connection-encrypter.ts new file mode 100644 index 0000000000..281756bb06 --- /dev/null +++ b/packages/interface-mocks/src/connection-encrypter.ts @@ -0,0 +1,113 @@ +import { UnexpectedPeerError } from '@libp2p/interface-connection-encrypter/errors' +import { peerIdFromBytes } from '@libp2p/peer-id' +import { multiaddr } from '@multiformats/multiaddr' +import { handshake } from 'it-handshake' +import map from 'it-map' +import { duplexPair } from 'it-pair/duplex' +import { pipe } from 'it-pipe' +import type { ConnectionEncrypter } from '@libp2p/interface-connection-encrypter' +import type { Transform, Source } from 'it-stream-types' + +// A basic transform that does nothing to the data +const transform = (): Transform, AsyncGenerator> => { + return (source: Source) => (async function * () { + for await (const chunk of source) { + yield chunk + } + })() +} + +export function mockConnectionEncrypter (): ConnectionEncrypter { + const encrypter: ConnectionEncrypter = { + protocol: 'insecure', + secureInbound: async (localPeer, duplex, expectedPeer) => { + // 1. Perform a basic handshake. + const shake = handshake(duplex) + shake.write(localPeer.toBytes()) + const remoteId = await shake.read() + + if (remoteId == null) { + throw new Error('Could not read remote ID') + } + + const remotePeer = peerIdFromBytes(remoteId.slice()) + shake.rest() + + if (expectedPeer?.equals(remotePeer) === false) { + throw new UnexpectedPeerError() + } + + // 2. Create your encryption box/unbox wrapper + const wrapper = duplexPair() + const encrypt = transform() // Use transform iterables to modify data + const decrypt = transform() + + void pipe( + wrapper[0], // We write to wrapper + encrypt, // The data is encrypted + shake.stream, // It goes to the remote peer + source => map(source, (list) => list.subarray()), // turn lists into arrays + decrypt, // Decrypt the incoming data + wrapper[0] // Pipe to the wrapper + ) + + return { + conn: { + ...wrapper[1], + close: async () => { }, + localAddr: multiaddr('/ip4/127.0.0.1/tcp/4001'), + remoteAddr: multiaddr('/ip4/127.0.0.1/tcp/4002'), + timeline: { + open: Date.now() + }, + conn: true + }, + remotePeer, + remoteExtensions: {} + } + }, + secureOutbound: async (localPeer, duplex, remotePeer) => { + // 1. Perform a basic handshake. + const shake = handshake(duplex) + shake.write(localPeer.toBytes()) + const remoteId = await shake.read() + + if (remoteId == null) { + throw new Error('Could not read remote ID') + } + + shake.rest() + + // 2. Create your encryption box/unbox wrapper + const wrapper = duplexPair() + const encrypt = transform() + const decrypt = transform() + + void pipe( + wrapper[0], // We write to wrapper + encrypt, // The data is encrypted + shake.stream, // It goes to the remote peer + source => map(source, (list) => list.subarray()), // turn lists into arrays + decrypt, // Decrypt the incoming data + wrapper[0] // Pipe to the wrapper + ) + + return { + conn: { + ...wrapper[1], + close: async () => { }, + localAddr: multiaddr('/ip4/127.0.0.1/tcp/4001'), + remoteAddr: multiaddr('/ip4/127.0.0.1/tcp/4002'), + timeline: { + open: Date.now() + }, + conn: true + }, + remotePeer: peerIdFromBytes(remoteId.slice()), + remoteExtensions: {} + } + } + } + + return encrypter +} diff --git a/packages/interface-mocks/src/connection-gater.ts b/packages/interface-mocks/src/connection-gater.ts new file mode 100644 index 0000000000..86b873c74c --- /dev/null +++ b/packages/interface-mocks/src/connection-gater.ts @@ -0,0 +1,18 @@ +import type { ConnectionGater } from '@libp2p/interface-connection-gater' + +export function mockConnectionGater (): ConnectionGater { + return { + denyDialPeer: async () => Promise.resolve(false), + denyDialMultiaddr: async () => Promise.resolve(false), + denyInboundConnection: async () => Promise.resolve(false), + denyOutboundConnection: async () => Promise.resolve(false), + denyInboundEncryptedConnection: async () => Promise.resolve(false), + denyOutboundEncryptedConnection: async () => Promise.resolve(false), + denyInboundUpgradedConnection: async () => Promise.resolve(false), + denyOutboundUpgradedConnection: async () => Promise.resolve(false), + denyInboundRelayReservation: async () => Promise.resolve(false), + denyOutboundRelayedConnection: async () => Promise.resolve(false), + denyInboundRelayedConnection: async () => Promise.resolve(false), + filterMultiaddrForPeer: async () => Promise.resolve(true) + } +} diff --git a/packages/interface-mocks/src/connection-manager.ts b/packages/interface-mocks/src/connection-manager.ts new file mode 100644 index 0000000000..d91f0e738a --- /dev/null +++ b/packages/interface-mocks/src/connection-manager.ts @@ -0,0 +1,211 @@ +import { isPeerId, type PeerId } from '@libp2p/interface-peer-id' +import { CodeError } from '@libp2p/interfaces/errors' +import { PeerMap } from '@libp2p/peer-collections' +import { peerIdFromString } from '@libp2p/peer-id' +import { isMultiaddr, type Multiaddr } from '@multiformats/multiaddr' +import { connectionPair } from './connection.js' +import type { Connection } from '@libp2p/interface-connection' +import type { ConnectionManager, PendingDial } from '@libp2p/interface-connection-manager' +import type { Libp2pEvents } from '@libp2p/interface-libp2p' +import type { PubSub } from '@libp2p/interface-pubsub' +import type { Registrar } from '@libp2p/interface-registrar' +import type { EventEmitter } from '@libp2p/interfaces/events' +import type { Startable } from '@libp2p/interfaces/startable' + +export interface MockNetworkComponents { + peerId: PeerId + registrar: Registrar + connectionManager: ConnectionManager + pubsub?: PubSub + events: EventEmitter +} + +class MockNetwork { + private components: MockNetworkComponents[] = [] + + addNode (components: MockNetworkComponents): void { + this.components.push(components) + } + + getNode (peerId: PeerId | Multiaddr []): MockNetworkComponents { + if (Array.isArray(peerId) && peerId.length > 0) { + peerId = peerIdFromString(peerId[0].getPeerId() ?? '') + } else if (isPeerId(peerId)) { + for (const components of this.components) { + if (peerId.equals(components.peerId)) { + return components + } + } + } + + throw new CodeError('Peer not found', 'ERR_PEER_NOT_FOUND') + } + + reset (): void { + this.components = [] + } +} + +export const mockNetwork = new MockNetwork() + +export interface MockConnectionManagerComponents { + peerId: PeerId + registrar: Registrar + events: EventEmitter +} + +class MockConnectionManager implements ConnectionManager, Startable { + private connections: Connection[] = [] + private readonly components: MockConnectionManagerComponents + private started = false + + constructor (components: MockConnectionManagerComponents) { + this.components = components + } + + isStarted (): boolean { + return this.started + } + + async start (): Promise { + this.started = true + } + + async stop (): Promise { + for (const connection of this.connections) { + await this.closeConnections(connection.remotePeer) + } + + this.started = false + } + + getConnections (peerId?: PeerId): Connection[] { + if (peerId != null) { + return this.connections + .filter(c => c.remotePeer.toString() === peerId.toString()) + } + + return this.connections + } + + getConnectionsMap (): PeerMap { + const map = new PeerMap() + + for (const conn of this.connections) { + const conns: Connection[] = map.get(conn.remotePeer) ?? [] + conns.push(conn) + + map.set(conn.remotePeer, conns) + } + + return map + } + + async openConnection (peerId: PeerId | Multiaddr | Multiaddr[]): Promise { + if (this.components == null) { + throw new CodeError('Not initialized', 'ERR_NOT_INITIALIZED') + } + + if (isMultiaddr(peerId)) { + throw new CodeError('Dialing multiaddrs not supported', 'ERR_NOT_SUPPORTED') + } + + let existingConnections: Connection[] = [] + + if (Array.isArray(peerId) && peerId.length > 0) { + existingConnections = this.getConnections(peerIdFromString(peerId[0].getPeerId() ?? '')) + } else if (isPeerId(peerId)) { + existingConnections = this.getConnections(peerId) + } + + if (existingConnections.length > 0) { + return existingConnections[0] + } + + const componentsB = mockNetwork.getNode(peerId) + + const [aToB, bToA] = connectionPair(this.components, componentsB) + + // track connections + this.connections.push(aToB) + ;(componentsB.connectionManager as MockConnectionManager).connections.push(bToA) + + this.components.events.safeDispatchEvent('connection:open', { + detail: aToB + }) + + for (const protocol of this.components.registrar.getProtocols()) { + for (const topology of this.components.registrar.getTopologies(protocol)) { + topology.onConnect(componentsB.peerId, aToB) + } + } + + this.components.events.safeDispatchEvent('peer:connect', { detail: componentsB.peerId }) + + componentsB.events.safeDispatchEvent('connection:open', { + detail: bToA + }) + + for (const protocol of componentsB.registrar.getProtocols()) { + for (const topology of componentsB.registrar.getTopologies(protocol)) { + topology.onConnect(this.components.peerId, bToA) + } + } + + componentsB.events.safeDispatchEvent('peer:connect', { detail: this.components.peerId }) + + return aToB + } + + async closeConnections (peerId: PeerId): Promise { + if (this.components == null) { + throw new CodeError('Not initialized', 'ERR_NOT_INITIALIZED') + } + + const connections = this.getConnections(peerId) + + if (connections.length === 0) { + return + } + + const componentsB = mockNetwork.getNode(peerId) + + for (const protocol of this.components.registrar.getProtocols()) { + this.components.registrar.getTopologies(protocol).forEach(topology => { + topology.onDisconnect(componentsB.peerId) + }) + } + + for (const conn of connections) { + await conn.close() + } + + this.connections = this.connections.filter(c => !c.remotePeer.equals(peerId)) + + if (this.connections.filter(c => !c.remotePeer.equals(peerId)).length === 0) { + componentsB.events.safeDispatchEvent('peer:disconnect', { detail: peerId }) + } + + await componentsB.connectionManager?.closeConnections(this.components.peerId) + + if (componentsB.connectionManager?.getConnectionsMap().get(this.components.peerId) == null) { + componentsB.events.safeDispatchEvent('peer:disconnect', { detail: this.components.peerId }) + } + } + + async acceptIncomingConnection (): Promise { + return true + } + + afterUpgradeInbound (): void { + + } + + getDialQueue (): PendingDial[] { + return [] + } +} + +export function mockConnectionManager (components: MockConnectionManagerComponents): ConnectionManager { + return new MockConnectionManager(components) +} diff --git a/packages/interface-mocks/src/connection.ts b/packages/interface-mocks/src/connection.ts new file mode 100644 index 0000000000..ad9ab3a9f9 --- /dev/null +++ b/packages/interface-mocks/src/connection.ts @@ -0,0 +1,218 @@ +import * as STATUS from '@libp2p/interface-connection/status' +import { CodeError } from '@libp2p/interfaces/errors' +import { logger } from '@libp2p/logger' +import * as mss from '@libp2p/multistream-select' +import { peerIdFromString } from '@libp2p/peer-id' +import { duplexPair } from 'it-pair/duplex' +import { pipe } from 'it-pipe' +import { mockMultiaddrConnection } from './multiaddr-connection.js' +import { mockMuxer } from './muxer.js' +import { mockRegistrar } from './registrar.js' +import type { MultiaddrConnection, Connection, Stream, ConnectionStat, Direction } from '@libp2p/interface-connection' +import type { PeerId } from '@libp2p/interface-peer-id' +import type { Registrar } from '@libp2p/interface-registrar' +import type { StreamMuxer, StreamMuxerFactory } from '@libp2p/interface-stream-muxer' +import type { AbortOptions } from '@libp2p/interfaces' +import type { Multiaddr } from '@multiformats/multiaddr' +import type { Duplex, Source } from 'it-stream-types' +import type { Uint8ArrayList } from 'uint8arraylist' + +const log = logger('libp2p:mock-connection') + +export interface MockConnectionOptions { + direction?: Direction + registrar?: Registrar + muxerFactory?: StreamMuxerFactory +} + +interface MockConnectionInit { + remoteAddr: Multiaddr + remotePeer: PeerId + direction: Direction + maConn: MultiaddrConnection + muxer: StreamMuxer +} + +class MockConnection implements Connection { + public id: string + public remoteAddr: Multiaddr + public remotePeer: PeerId + public direction: Direction + public stat: ConnectionStat + public streams: Stream[] + public tags: string[] + + private readonly muxer: StreamMuxer + private readonly maConn: MultiaddrConnection + + constructor (init: MockConnectionInit) { + const { remoteAddr, remotePeer, direction, maConn, muxer } = init + + this.id = `mock-connection-${Math.random()}` + this.remoteAddr = remoteAddr + this.remotePeer = remotePeer + this.direction = direction + this.stat = { + status: STATUS.OPEN, + direction, + timeline: maConn.timeline, + multiplexer: 'test-multiplexer', + encryption: 'yes-yes-very-secure' + } + this.streams = [] + this.tags = [] + this.muxer = muxer + this.maConn = maConn + } + + async newStream (protocols: string | string[], options?: AbortOptions): Promise { + if (!Array.isArray(protocols)) { + protocols = [protocols] + } + + if (protocols.length === 0) { + throw new Error('protocols must have a length') + } + + if (this.stat.status !== STATUS.OPEN) { + throw new CodeError('connection must be open to create streams', 'ERR_CONNECTION_CLOSED') + } + + const id = `${Math.random()}` + const stream = await this.muxer.newStream(id) + const result = await mss.select(stream, protocols, options) + + const streamWithProtocol: Stream = { + ...stream, + ...result.stream, + stat: { + ...stream.stat, + direction: 'outbound', + protocol: result.protocol + } + } + + this.streams.push(streamWithProtocol) + + return streamWithProtocol + } + + addStream (stream: Stream): void { + this.streams.push(stream) + } + + removeStream (id: string): void { + this.streams = this.streams.filter(stream => stream.id !== id) + } + + async close (): Promise { + this.stat.status = STATUS.CLOSING + await this.maConn.close() + this.streams.forEach(s => { + s.close() + }) + this.stat.status = STATUS.CLOSED + this.stat.timeline.close = Date.now() + } +} + +export function mockConnection (maConn: MultiaddrConnection, opts: MockConnectionOptions = {}): Connection { + const remoteAddr = maConn.remoteAddr + const remotePeerIdStr = remoteAddr.getPeerId() ?? '12D3KooWCrhmFM1BCPGBkNzbPfDk4cjYmtAYSpZwUBC69Qg2kZyq' + + if (remotePeerIdStr == null) { + throw new Error('Remote multiaddr must contain a peer id') + } + + const remotePeer = peerIdFromString(remotePeerIdStr) + const direction = opts.direction ?? 'inbound' + const registrar = opts.registrar ?? mockRegistrar() + const muxerFactory = opts.muxerFactory ?? mockMuxer() + + const muxer = muxerFactory.createStreamMuxer({ + direction, + onIncomingStream: (muxedStream) => { + try { + mss.handle(muxedStream, registrar.getProtocols()) + .then(({ stream, protocol }) => { + log('%s: incoming stream opened on %s', direction, protocol) + muxedStream = { ...muxedStream, ...stream } + muxedStream.stat.protocol = protocol + + connection.addStream(muxedStream) + const { handler } = registrar.getHandler(protocol) + + handler({ connection, stream: muxedStream }) + }).catch(err => { + log.error(err) + }) + } catch (err: any) { + log.error(err) + } + }, + onStreamEnd: (muxedStream) => { + connection.removeStream(muxedStream.id) + } + }) + + void pipe( + maConn, muxer, maConn + ) + + const connection = new MockConnection({ + remoteAddr, + remotePeer, + direction, + maConn, + muxer + }) + + return connection +} + +export function mockStream (stream: Duplex, Source, Promise>): Stream { + return { + ...stream, + close: () => {}, + closeRead: () => {}, + closeWrite: () => {}, + abort: () => {}, + reset: () => {}, + stat: { + direction: 'outbound', + protocol: '/foo/1.0.0', + timeline: { + open: Date.now() + } + }, + metadata: {}, + id: `stream-${Date.now()}` + } +} + +export interface Peer { + peerId: PeerId + registrar: Registrar +} + +export function multiaddrConnectionPair (a: { peerId: PeerId, registrar: Registrar }, b: { peerId: PeerId, registrar: Registrar }): [ MultiaddrConnection, MultiaddrConnection ] { + const [peerBtoPeerA, peerAtoPeerB] = duplexPair() + + return [ + mockMultiaddrConnection(peerAtoPeerB, b.peerId), + mockMultiaddrConnection(peerBtoPeerA, a.peerId) + ] +} + +export function connectionPair (a: { peerId: PeerId, registrar: Registrar }, b: { peerId: PeerId, registrar: Registrar }): [ Connection, Connection ] { + const [peerBtoPeerA, peerAtoPeerB] = multiaddrConnectionPair(a, b) + + return [ + mockConnection(peerBtoPeerA, { + registrar: a.registrar + }), + mockConnection(peerAtoPeerB, { + registrar: b.registrar + }) + ] +} diff --git a/packages/interface-mocks/src/duplex.ts b/packages/interface-mocks/src/duplex.ts new file mode 100644 index 0000000000..736366c254 --- /dev/null +++ b/packages/interface-mocks/src/duplex.ts @@ -0,0 +1,10 @@ +import type { Duplex, Source } from 'it-stream-types' + +export function mockDuplex (): Duplex, Source, Promise> { + return { + source: (async function * () { + yield * [] + }()), + sink: async () => {} + } +} diff --git a/packages/interface-mocks/src/index.ts b/packages/interface-mocks/src/index.ts new file mode 100644 index 0000000000..8b78f1f7f3 --- /dev/null +++ b/packages/interface-mocks/src/index.ts @@ -0,0 +1,12 @@ +export { mockConnectionEncrypter } from './connection-encrypter.js' +export { mockConnectionGater } from './connection-gater.js' +export { mockConnectionManager, mockNetwork } from './connection-manager.js' +export { mockConnection, mockStream, connectionPair } from './connection.js' +export { mockMultiaddrConnection, mockMultiaddrConnPair } from './multiaddr-connection.js' +export { mockMuxer } from './muxer.js' +export { mockRegistrar } from './registrar.js' +export { mockUpgrader } from './upgrader.js' +export { mockDuplex } from './duplex.js' +export { mockMetrics } from './metrics.js' +export type { MockUpgraderInit } from './upgrader.js' +export type { MockNetworkComponents } from './connection-manager.js' diff --git a/packages/interface-mocks/src/metrics.ts b/packages/interface-mocks/src/metrics.ts new file mode 100644 index 0000000000..760ac8fcf1 --- /dev/null +++ b/packages/interface-mocks/src/metrics.ts @@ -0,0 +1,162 @@ +import type { MultiaddrConnection, Stream, Connection } from '@libp2p/interface-connection' +import type { Metric, MetricGroup, StopTimer, Metrics, CalculatedMetricOptions, MetricOptions } from '@libp2p/interface-metrics' + +class DefaultMetric implements Metric { + public value: number = 0 + + update (value: number): void { + this.value = value + } + + increment (value: number = 1): void { + this.value += value + } + + decrement (value: number = 1): void { + this.value -= value + } + + reset (): void { + this.value = 0 + } + + timer (): StopTimer { + const start = Date.now() + + return () => { + this.value = Date.now() - start + } + } +} + +class DefaultGroupMetric implements MetricGroup { + public values: Record = {} + + update (values: Record): void { + Object.entries(values).forEach(([key, value]) => { + this.values[key] = value + }) + } + + increment (values: Record): void { + Object.entries(values).forEach(([key, value]) => { + this.values[key] = this.values[key] ?? 0 + const inc = typeof value === 'number' ? value : 1 + + this.values[key] += Number(inc) + }) + } + + decrement (values: Record): void { + Object.entries(values).forEach(([key, value]) => { + this.values[key] = this.values[key] ?? 0 + const dec = typeof value === 'number' ? value : 1 + + this.values[key] -= Number(dec) + }) + } + + reset (): void { + this.values = {} + } + + timer (key: string): StopTimer { + const start = Date.now() + + return () => { + this.values[key] = Date.now() - start + } + } +} + +class MockMetrics implements Metrics { + public metrics = new Map() + + trackMultiaddrConnection (maConn: MultiaddrConnection): void { + + } + + trackProtocolStream (stream: Stream, connection: Connection): void { + + } + + registerMetric (name: string, opts: CalculatedMetricOptions): void + registerMetric (name: string, opts?: MetricOptions): Metric + registerMetric (name: string, opts: any): any { + if (name == null ?? name.trim() === '') { + throw new Error('Metric name is required') + } + + if (opts?.calculate != null) { + // calculated metric + this.metrics.set(name, opts.calculate) + return + } + + const metric = new DefaultMetric() + this.metrics.set(name, metric) + + return metric + } + + registerCounter (name: string, opts: CalculatedMetricOptions): void + registerCounter (name: string, opts?: MetricOptions): Metric + registerCounter (name: string, opts: any): any { + if (name == null ?? name.trim() === '') { + throw new Error('Metric name is required') + } + + if (opts?.calculate != null) { + // calculated metric + this.metrics.set(name, opts.calculate) + return + } + + const metric = new DefaultMetric() + this.metrics.set(name, metric) + + return metric + } + + registerMetricGroup (name: string, opts: CalculatedMetricOptions>): void + registerMetricGroup (name: string, opts?: MetricOptions): MetricGroup + registerMetricGroup (name: string, opts: any): any { + if (name == null ?? name.trim() === '') { + throw new Error('Metric name is required') + } + + if (opts?.calculate != null) { + // calculated metric + this.metrics.set(name, opts.calculate) + return + } + + const metric = new DefaultGroupMetric() + this.metrics.set(name, metric) + + return metric + } + + registerCounterGroup (name: string, opts: CalculatedMetricOptions>): void + registerCounterGroup (name: string, opts?: MetricOptions): MetricGroup + registerCounterGroup (name: string, opts: any): any { + if (name == null ?? name.trim() === '') { + throw new Error('Metric name is required') + } + + if (opts?.calculate != null) { + // calculated metric + this.metrics.set(name, opts.calculate) + return + } + + const metric = new DefaultGroupMetric() + this.metrics.set(name, metric) + + return metric + } +} + +export function mockMetrics (): () => Metrics { + return () => new MockMetrics() +} diff --git a/packages/interface-mocks/src/multiaddr-connection.ts b/packages/interface-mocks/src/multiaddr-connection.ts new file mode 100644 index 0000000000..868bbee1af --- /dev/null +++ b/packages/interface-mocks/src/multiaddr-connection.ts @@ -0,0 +1,67 @@ +import { multiaddr } from '@multiformats/multiaddr' +import { abortableSource } from 'abortable-iterator' +import { duplexPair } from 'it-pair/duplex' +import type { MultiaddrConnection } from '@libp2p/interface-connection' +import type { PeerId } from '@libp2p/interface-peer-id' +import type { Multiaddr } from '@multiformats/multiaddr' +import type { Duplex } from 'it-stream-types' + +export function mockMultiaddrConnection (source: Duplex> & Partial, peerId: PeerId): MultiaddrConnection { + const maConn: MultiaddrConnection = { + async close () { + + }, + timeline: { + open: Date.now() + }, + remoteAddr: multiaddr(`/ip4/127.0.0.1/tcp/4001/p2p/${peerId.toString()}`), + ...source + } + + return maConn +} + +export interface MockMultiaddrConnPairOptions { + addrs: Multiaddr[] + remotePeer: PeerId +} + +/** + * Returns both sides of a mocked MultiaddrConnection + */ +export function mockMultiaddrConnPair (opts: MockMultiaddrConnPairOptions): { inbound: MultiaddrConnection, outbound: MultiaddrConnection } { + const { addrs, remotePeer } = opts + const controller = new AbortController() + const [localAddr, remoteAddr] = addrs + const [inboundStream, outboundStream] = duplexPair() + + const outbound: MultiaddrConnection = { + ...outboundStream, + remoteAddr: remoteAddr.toString().includes(`/p2p/${remotePeer.toString()}`) ? remoteAddr : remoteAddr.encapsulate(`/p2p/${remotePeer.toString()}`), + timeline: { + open: Date.now() + }, + close: async () => { + outbound.timeline.close = Date.now() + controller.abort() + } + } + + const inbound: MultiaddrConnection = { + ...inboundStream, + remoteAddr: localAddr, + timeline: { + open: Date.now() + }, + close: async () => { + inbound.timeline.close = Date.now() + controller.abort() + } + } + + // Make the sources abortable so we can close them easily + inbound.source = abortableSource(inbound.source, controller.signal) + outbound.source = abortableSource(outbound.source, controller.signal) + + return { inbound, outbound } +} diff --git a/packages/interface-mocks/src/muxer.ts b/packages/interface-mocks/src/muxer.ts new file mode 100644 index 0000000000..00f4370d79 --- /dev/null +++ b/packages/interface-mocks/src/muxer.ts @@ -0,0 +1,447 @@ +import { CodeError } from '@libp2p/interfaces/errors' +import { type Logger, logger } from '@libp2p/logger' +import { abortableSource } from 'abortable-iterator' +import { anySignal } from 'any-signal' +import map from 'it-map' +import * as ndjson from 'it-ndjson' +import { pipe } from 'it-pipe' +import { type Pushable, pushable } from 'it-pushable' +import { Uint8ArrayList } from 'uint8arraylist' +import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' +import { toString as uint8ArrayToString } from 'uint8arrays/to-string' +import type { Stream } from '@libp2p/interface-connection' +import type { StreamMuxer, StreamMuxerFactory, StreamMuxerInit } from '@libp2p/interface-stream-muxer' +import type { Source } from 'it-stream-types' + +let muxers = 0 +let streams = 0 +const MAX_MESSAGE_SIZE = 1024 * 1024 + +interface DataMessage { + id: string + type: 'data' + direction: 'initiator' | 'recipient' + chunk: string +} + +interface ResetMessage { + id: string + type: 'reset' + direction: 'initiator' | 'recipient' +} + +interface CloseMessage { + id: string + type: 'close' + direction: 'initiator' | 'recipient' +} + +interface CreateMessage { + id: string + type: 'create' + direction: 'initiator' +} + +type StreamMessage = DataMessage | ResetMessage | CloseMessage | CreateMessage + +class MuxedStream { + public id: string + public input: Pushable + public stream: Stream + public type: 'initiator' | 'recipient' + + private sinkEnded: boolean + private sourceEnded: boolean + private readonly abortController: AbortController + private readonly resetController: AbortController + private readonly closeController: AbortController + private readonly log: Logger + + constructor (init: { id: string, type: 'initiator' | 'recipient', push: Pushable, onEnd: (err?: Error) => void }) { + const { id, type, push, onEnd } = init + + this.log = logger(`libp2p:mock-muxer:stream:${id}:${type}`) + + this.id = id + this.type = type + this.abortController = new AbortController() + this.resetController = new AbortController() + this.closeController = new AbortController() + + this.sourceEnded = false + this.sinkEnded = false + + let endErr: Error | undefined + + const onSourceEnd = (err?: Error): void => { + if (this.sourceEnded) { + return + } + + this.log('onSourceEnd sink ended? %s', this.sinkEnded) + + this.sourceEnded = true + + if (err != null && endErr == null) { + endErr = err + } + + if (this.sinkEnded) { + this.stream.stat.timeline.close = Date.now() + + if (onEnd != null) { + onEnd(endErr) + } + } + } + + const onSinkEnd = (err?: Error): void => { + if (this.sinkEnded) { + return + } + + this.log('onSinkEnd source ended? %s', this.sourceEnded) + + this.sinkEnded = true + + if (err != null && endErr == null) { + endErr = err + } + + if (this.sourceEnded) { + this.stream.stat.timeline.close = Date.now() + + if (onEnd != null) { + onEnd(endErr) + } + } + } + + this.input = pushable({ + onEnd: onSourceEnd + }) + + this.stream = { + id, + sink: async (source) => { + if (this.sinkEnded) { + throw new CodeError('stream closed for writing', 'ERR_SINK_ENDED') + } + + const signal = anySignal([ + this.abortController.signal, + this.resetController.signal, + this.closeController.signal + ]) + + source = abortableSource(source, signal) + + try { + if (this.type === 'initiator') { + // If initiator, open a new stream + const createMsg: CreateMessage = { + id: this.id, + type: 'create', + direction: this.type + } + push.push(createMsg) + } + + const list = new Uint8ArrayList() + + for await (const chunk of source) { + list.append(chunk) + + while (list.length > 0) { + const available = Math.min(list.length, MAX_MESSAGE_SIZE) + const dataMsg: DataMessage = { + id, + type: 'data', + chunk: uint8ArrayToString(list.subarray(0, available), 'base64pad'), + direction: this.type + } + + push.push(dataMsg) + list.consume(available) + } + } + } catch (err: any) { + if (err.type === 'aborted' && err.message === 'The operation was aborted') { + if (this.closeController.signal.aborted) { + return + } + + if (this.resetController.signal.aborted) { + err.message = 'stream reset' + err.code = 'ERR_STREAM_RESET' + } + + if (this.abortController.signal.aborted) { + err.message = 'stream aborted' + err.code = 'ERR_STREAM_ABORT' + } + } + + // Send no more data if this stream was remotely reset + if (err.code !== 'ERR_STREAM_RESET') { + const resetMsg: ResetMessage = { + id, + type: 'reset', + direction: this.type + } + push.push(resetMsg) + } + + this.log('sink erred', err) + + this.input.end(err) + onSinkEnd(err) + return + } finally { + signal.clear() + } + + this.log('sink ended') + + onSinkEnd() + + const closeMsg: CloseMessage = { + id, + type: 'close', + direction: this.type + } + push.push(closeMsg) + }, + source: this.input, + + // Close for reading + close: () => { + this.stream.closeRead() + this.stream.closeWrite() + }, + + closeRead: () => { + this.input.end() + }, + + closeWrite: () => { + this.closeController.abort() + + const closeMsg: CloseMessage = { + id, + type: 'close', + direction: this.type + } + push.push(closeMsg) + onSinkEnd() + }, + + // Close for reading and writing (local error) + abort: (err: Error) => { + // End the source with the passed error + this.input.end(err) + this.abortController.abort() + onSinkEnd(err) + }, + + // Close immediately for reading and writing (remote error) + reset: () => { + const err = new CodeError('stream reset', 'ERR_STREAM_RESET') + this.resetController.abort() + this.input.end(err) + onSinkEnd(err) + }, + stat: { + direction: type === 'initiator' ? 'outbound' : 'inbound', + timeline: { + open: Date.now() + } + }, + metadata: {} + } + } +} + +class MockMuxer implements StreamMuxer { + public source: AsyncGenerator + public input: Pushable + public streamInput: Pushable + public name: string + public protocol: string = '/mock-muxer/1.0.0' + + private readonly closeController: AbortController + private readonly registryInitiatorStreams: Map + private readonly registryRecipientStreams: Map + private readonly options: StreamMuxerInit + + private readonly log: Logger + + constructor (init?: StreamMuxerInit) { + this.name = `muxer:${muxers++}` + this.log = logger(`libp2p:mock-muxer:${this.name}`) + this.registryInitiatorStreams = new Map() + this.registryRecipientStreams = new Map() + this.log('create muxer') + this.options = init ?? { direction: 'inbound' } + this.closeController = new AbortController() + // receives data from the muxer at the other end of the stream + this.source = this.input = pushable({ + onEnd: (err) => { + this.close(err) + } + }) + + // receives messages from all of the muxed streams + this.streamInput = pushable({ + objectMode: true + }) + } + + // receive incoming messages + async sink (source: Source): Promise { + try { + await pipe( + abortableSource(source, this.closeController.signal), + (source) => map(source, buf => uint8ArrayToString(buf.subarray())), + ndjson.parse, + async (source) => { + for await (const message of source) { + this.log.trace('-> %s %s %s', message.type, message.direction, message.id) + this.handleMessage(message) + } + } + ) + + this.log('muxed stream ended') + this.input.end() + } catch (err: any) { + this.log('muxed stream errored', err) + this.input.end(err) + } + } + + handleMessage (message: StreamMessage): void { + let muxedStream: MuxedStream | undefined + + const registry = message.direction === 'initiator' ? this.registryRecipientStreams : this.registryInitiatorStreams + + if (message.type === 'create') { + if (registry.has(message.id)) { + throw new Error(`Already had stream for ${message.id}`) + } + + muxedStream = this.createStream(message.id, 'recipient') + registry.set(muxedStream.stream.id, muxedStream) + + if (this.options.onIncomingStream != null) { + this.options.onIncomingStream(muxedStream.stream) + } + } + + muxedStream = registry.get(message.id) + + if (muxedStream == null) { + this.log.error(`No stream found for ${message.id}`) + + return + } + + if (message.type === 'data') { + muxedStream.input.push(new Uint8ArrayList(uint8ArrayFromString(message.chunk, 'base64pad'))) + } else if (message.type === 'reset') { + this.log('-> reset stream %s %s', muxedStream.type, muxedStream.stream.id) + muxedStream.stream.reset() + } else if (message.type === 'close') { + this.log('-> closing stream %s %s', muxedStream.type, muxedStream.stream.id) + muxedStream.stream.closeRead() + } + } + + get streams (): Stream[] { + return Array.from(this.registryRecipientStreams.values()) + .concat(Array.from(this.registryInitiatorStreams.values())) + .map(({ stream }) => stream) + } + + newStream (name?: string): Stream { + if (this.closeController.signal.aborted) { + throw new Error('Muxer already closed') + } + this.log('newStream %s', name) + const storedStream = this.createStream(name, 'initiator') + this.registryInitiatorStreams.set(storedStream.stream.id, storedStream) + + return storedStream.stream + } + + createStream (name?: string, type: 'initiator' | 'recipient' = 'initiator'): MuxedStream { + const id = name ?? `${this.name}:stream:${streams++}` + + this.log('createStream %s %s', type, id) + + const muxedStream: MuxedStream = new MuxedStream({ + id, + type, + push: this.streamInput, + onEnd: () => { + this.log('stream ended %s %s', type, id) + + if (type === 'initiator') { + this.registryInitiatorStreams.delete(id) + } else { + this.registryRecipientStreams.delete(id) + } + + if (this.options.onStreamEnd != null) { + this.options.onStreamEnd(muxedStream.stream) + } + } + }) + + return muxedStream + } + + close (err?: Error): void { + if (this.closeController.signal.aborted) return + this.log('closing muxed streams') + + if (err == null) { + this.streams.forEach(s => { + s.close() + }) + } else { + this.streams.forEach(s => { + s.abort(err) + }) + } + this.closeController.abort() + this.input.end(err) + } +} + +class MockMuxerFactory implements StreamMuxerFactory { + public protocol: string = '/mock-muxer/1.0.0' + + createStreamMuxer (init?: StreamMuxerInit): StreamMuxer { + const mockMuxer = new MockMuxer(init) + + void Promise.resolve().then(async () => { + void pipe( + mockMuxer.streamInput, + ndjson.stringify, + (source) => map(source, str => new Uint8ArrayList(uint8ArrayFromString(str))), + async (source) => { + for await (const buf of source) { + mockMuxer.input.push(buf.subarray()) + } + } + ) + }) + + return mockMuxer + } +} + +export function mockMuxer (): MockMuxerFactory { + return new MockMuxerFactory() +} diff --git a/packages/interface-mocks/src/peer-discovery.ts b/packages/interface-mocks/src/peer-discovery.ts new file mode 100644 index 0000000000..88a39a8c81 --- /dev/null +++ b/packages/interface-mocks/src/peer-discovery.ts @@ -0,0 +1,60 @@ +import { peerDiscovery } from '@libp2p/interface-peer-discovery' +import { EventEmitter } from '@libp2p/interfaces/events' +import * as PeerIdFactory from '@libp2p/peer-id-factory' +import { multiaddr } from '@multiformats/multiaddr' +import type { PeerDiscovery, PeerDiscoveryEvents } from '@libp2p/interface-peer-discovery' +import type { PeerInfo } from '@libp2p/interface-peer-info' + +interface MockDiscoveryInit { + discoveryDelay?: number +} + +/** + * Emits 'peer' events on discovery. + */ +export class MockDiscovery extends EventEmitter implements PeerDiscovery { + public readonly options: MockDiscoveryInit + private _isRunning: boolean + private _timer: any + + constructor (init = {}) { + super() + + this.options = init + this._isRunning = false + } + + readonly [peerDiscovery] = this + + start (): void { + this._isRunning = true + this._discoverPeer() + } + + stop (): void { + clearTimeout(this._timer) + this._isRunning = false + } + + isStarted (): boolean { + return this._isRunning + } + + _discoverPeer (): void { + if (!this._isRunning) return + + PeerIdFactory.createEd25519PeerId() + .then(peerId => { + this._timer = setTimeout(() => { + this.safeDispatchEvent('peer', { + detail: { + id: peerId, + multiaddrs: [multiaddr('/ip4/127.0.0.1/tcp/8000')], + protocols: [] + } + }) + }, this.options.discoveryDelay ?? 1000) + }) + .catch(() => {}) + } +} diff --git a/packages/interface-mocks/src/registrar.ts b/packages/interface-mocks/src/registrar.ts new file mode 100644 index 0000000000..c6c1482533 --- /dev/null +++ b/packages/interface-mocks/src/registrar.ts @@ -0,0 +1,87 @@ +import merge from 'merge-options' +import type { Connection } from '@libp2p/interface-connection' +import type { PeerId } from '@libp2p/interface-peer-id' +import type { IncomingStreamData, Registrar, StreamHandler, Topology, StreamHandlerOptions, StreamHandlerRecord } from '@libp2p/interface-registrar' + +export class MockRegistrar implements Registrar { + private readonly topologies = new Map>() + private readonly handlers = new Map() + + getProtocols (): string[] { + return Array.from(this.handlers.keys()).sort() + } + + async handle (protocol: string, handler: StreamHandler, opts?: StreamHandlerOptions): Promise { + const options = merge.bind({ ignoreUndefined: true })({ + maxInboundStreams: 1, + maxOutboundStreams: 1 + }, opts) + + if (this.handlers.has(protocol)) { + throw new Error(`Handler already registered for protocol ${protocol}`) + } + + this.handlers.set(protocol, { + handler, + options + }) + } + + async unhandle (protocol: string): Promise { + this.handlers.delete(protocol) + } + + getHandler (protocol: string): StreamHandlerRecord { + const handler = this.handlers.get(protocol) + + if (handler == null) { + throw new Error(`No handler registered for protocol ${protocol}`) + } + + return handler + } + + async register (protocol: string, topology: Topology): Promise { + const id = `topology-id-${Math.random()}` + let topologies = this.topologies.get(protocol) + + if (topologies == null) { + topologies = [] + } + + topologies.push({ + id, + topology + }) + + this.topologies.set(protocol, topologies) + + return id + } + + unregister (id: string | string[]): void { + if (!Array.isArray(id)) { + id = [id] + } + + id.forEach(id => this.topologies.delete(id)) + } + + getTopologies (protocol: string): Topology[] { + return (this.topologies.get(protocol) ?? []).map(t => t.topology) + } +} + +export function mockRegistrar (): Registrar { + return new MockRegistrar() +} + +export async function mockIncomingStreamEvent (protocol: string, conn: Connection, remotePeer: PeerId): Promise { + return { + ...await conn.newStream([protocol]), + // @ts-expect-error incomplete implementation + connection: { + remotePeer + } + } +} diff --git a/packages/interface-mocks/src/upgrader.ts b/packages/interface-mocks/src/upgrader.ts new file mode 100644 index 0000000000..7739633495 --- /dev/null +++ b/packages/interface-mocks/src/upgrader.ts @@ -0,0 +1,49 @@ +import { mockConnection } from './connection.js' +import type { Connection, MultiaddrConnection } from '@libp2p/interface-connection' +import type { Libp2pEvents } from '@libp2p/interface-libp2p' +import type { Registrar } from '@libp2p/interface-registrar' +import type { Upgrader, UpgraderOptions } from '@libp2p/interface-transport' +import type { EventEmitter } from '@libp2p/interfaces/events' + +export interface MockUpgraderInit { + registrar?: Registrar + events?: EventEmitter +} + +class MockUpgrader implements Upgrader { + private readonly registrar?: Registrar + private readonly events?: EventEmitter + + constructor (init: MockUpgraderInit) { + this.registrar = init.registrar + this.events = init.events + } + + async upgradeOutbound (multiaddrConnection: MultiaddrConnection, opts: UpgraderOptions = {}): Promise { + const connection = mockConnection(multiaddrConnection, { + direction: 'outbound', + registrar: this.registrar, + ...opts + }) + + this.events?.safeDispatchEvent('connection:open', { detail: connection }) + + return connection + } + + async upgradeInbound (multiaddrConnection: MultiaddrConnection, opts: UpgraderOptions = {}): Promise { + const connection = mockConnection(multiaddrConnection, { + direction: 'inbound', + registrar: this.registrar, + ...opts + }) + + this.events?.safeDispatchEvent('connection:open', { detail: connection }) + + return connection + } +} + +export function mockUpgrader (init: MockUpgraderInit = {}): Upgrader { + return new MockUpgrader(init) +} diff --git a/packages/interface-mocks/test/connection-encrypter.spec.ts b/packages/interface-mocks/test/connection-encrypter.spec.ts new file mode 100644 index 0000000000..73f0ca513e --- /dev/null +++ b/packages/interface-mocks/test/connection-encrypter.spec.ts @@ -0,0 +1,13 @@ +import tests from '@libp2p/interface-connection-encrypter-compliance-tests' +import { mockConnectionEncrypter } from '../src/connection-encrypter.js' + +describe('mock connection encrypter compliance tests', () => { + tests({ + async setup () { + return mockConnectionEncrypter() + }, + async teardown () { + + } + }) +}) diff --git a/packages/interface-mocks/test/connection.spec.ts b/packages/interface-mocks/test/connection.spec.ts new file mode 100644 index 0000000000..901d641fb8 --- /dev/null +++ b/packages/interface-mocks/test/connection.spec.ts @@ -0,0 +1,38 @@ +import tests from '@libp2p/interface-connection-compliance-tests' +import { createEd25519PeerId } from '@libp2p/peer-id-factory' +import { pipe } from 'it-pipe' +import { connectionPair } from '../src/connection.js' +import { mockRegistrar } from '../src/registrar.js' +import type { Connection } from '@libp2p/interface-connection' + +describe('mock connection compliance tests', () => { + let connections: Connection[] = [] + + tests({ + async setup () { + const componentsA = { + peerId: await createEd25519PeerId(), + registrar: mockRegistrar() + } + const componentsB = { + peerId: await createEd25519PeerId(), + registrar: mockRegistrar() + } + connections = connectionPair(componentsA, componentsB) + + await componentsB.registrar.handle('/echo/0.0.1', (data) => { + void pipe( + data.stream, + data.stream + ) + }) + + return connections[0] + }, + async teardown () { + await Promise.all(connections.map(async conn => { + await conn.close() + })) + } + }) +}) diff --git a/packages/interface-mocks/test/muxer.spec.ts b/packages/interface-mocks/test/muxer.spec.ts new file mode 100644 index 0000000000..4d7f280d67 --- /dev/null +++ b/packages/interface-mocks/test/muxer.spec.ts @@ -0,0 +1,13 @@ +import tests from '@libp2p/interface-stream-muxer-compliance-tests' +import { mockMuxer } from '../src/muxer.js' + +describe('mock stream muxer compliance tests', () => { + tests({ + async setup () { + return mockMuxer() + }, + async teardown () { + + } + }) +}) diff --git a/packages/interface-mocks/test/peer-discovery.spec.ts b/packages/interface-mocks/test/peer-discovery.spec.ts new file mode 100644 index 0000000000..ad2076499a --- /dev/null +++ b/packages/interface-mocks/test/peer-discovery.spec.ts @@ -0,0 +1,21 @@ +import tests from '@libp2p/interface-peer-discovery-compliance-tests' +import { MockDiscovery } from '../src/peer-discovery.js' + +describe('mock peer discovery compliance tests', () => { + let intervalId: any + + tests({ + async setup () { + const mockDiscovery = new MockDiscovery({ + discoveryDelay: 1 + }) + + intervalId = setInterval(mockDiscovery._discoverPeer, 1000) + + return mockDiscovery + }, + async teardown () { + clearInterval(intervalId) + } + }) +}) diff --git a/packages/interface-mocks/tsconfig.json b/packages/interface-mocks/tsconfig.json new file mode 100644 index 0000000000..d49aa5945a --- /dev/null +++ b/packages/interface-mocks/tsconfig.json @@ -0,0 +1,66 @@ +{ + "extends": "aegir/src/config/tsconfig.aegir.json", + "compilerOptions": { + "outDir": "dist" + }, + "include": [ + "src", + "test" + ], + "references": [ + { + "path": "../interface-connection" + }, + { + "path": "../interface-connection-compliance-tests" + }, + { + "path": "../interface-connection-encrypter" + }, + { + "path": "../interface-connection-encrypter-compliance-tests" + }, + { + "path": "../interface-connection-gater" + }, + { + "path": "../interface-connection-manager" + }, + { + "path": "../interface-libp2p" + }, + { + "path": "../interface-metrics" + }, + { + "path": "../interface-peer-discovery" + }, + { + "path": "../interface-peer-discovery-compliance-tests" + }, + { + "path": "../interface-peer-id" + }, + { + "path": "../interface-peer-info" + }, + { + "path": "../interface-pubsub" + }, + { + "path": "../interface-registrar" + }, + { + "path": "../interface-stream-muxer" + }, + { + "path": "../interface-stream-muxer-compliance-tests" + }, + { + "path": "../interface-transport" + }, + { + "path": "../interfaces" + } + ] +} diff --git a/packages/interface-peer-discovery-compliance-tests/CHANGELOG.md b/packages/interface-peer-discovery-compliance-tests/CHANGELOG.md new file mode 100644 index 0000000000..3095d8070c --- /dev/null +++ b/packages/interface-peer-discovery-compliance-tests/CHANGELOG.md @@ -0,0 +1,90 @@ +## [@libp2p/interface-peer-discovery-compliance-tests-v2.0.8](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-peer-discovery-compliance-tests-v2.0.7...@libp2p/interface-peer-discovery-compliance-tests-v2.0.8) (2023-05-04) + + +### Dependencies + +* update sibling dependencies ([45cf513](https://github.com/libp2p/js-libp2p-interfaces/commit/45cf513090d2a069bb6752ad2e231df65c76df36)) + +## [@libp2p/interface-peer-discovery-compliance-tests-v2.0.7](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-peer-discovery-compliance-tests-v2.0.6...@libp2p/interface-peer-discovery-compliance-tests-v2.0.7) (2023-05-04) + + +### Dependencies + +* bump aegir from 38.1.8 to 39.0.5 ([#393](https://github.com/libp2p/js-libp2p-interfaces/issues/393)) ([31f3797](https://github.com/libp2p/js-libp2p-interfaces/commit/31f3797b24f7c23f3f16e9db3a230bd5f7cd5175)) + +## [@libp2p/interface-peer-discovery-compliance-tests-v2.0.6](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-peer-discovery-compliance-tests-v2.0.5...@libp2p/interface-peer-discovery-compliance-tests-v2.0.6) (2023-03-17) + + +### Dependencies + +* update @multiformats/multiaddr to 12.0.0 ([#354](https://github.com/libp2p/js-libp2p-interfaces/issues/354)) ([e0f327b](https://github.com/libp2p/js-libp2p-interfaces/commit/e0f327b5d54e240feabadce21a841629d633ec5e)) + +## [@libp2p/interface-peer-discovery-compliance-tests-v2.0.5](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-peer-discovery-compliance-tests-v2.0.4...@libp2p/interface-peer-discovery-compliance-tests-v2.0.5) (2023-01-18) + + +### Trivial Changes + +* remove lerna ([#330](https://github.com/libp2p/js-libp2p-interfaces/issues/330)) ([6678592](https://github.com/libp2p/js-libp2p-interfaces/commit/6678592dd0cf601a2671852f9d2a0aff5dee2b18)) + + +### Dependencies + +* bump aegir from 37.12.1 to 38.1.0 ([#335](https://github.com/libp2p/js-libp2p-interfaces/issues/335)) ([7368a36](https://github.com/libp2p/js-libp2p-interfaces/commit/7368a363423a08e8fa247dcb76ea13e4cf030d65)) + +## [@libp2p/interface-peer-discovery-compliance-tests-v2.0.4](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-peer-discovery-compliance-tests-v2.0.3...@libp2p/interface-peer-discovery-compliance-tests-v2.0.4) (2023-01-03) + + +### Bug Fixes + +* refactor peer discovery compliance test ([#328](https://github.com/libp2p/js-libp2p-interfaces/issues/328)) ([87b4608](https://github.com/libp2p/js-libp2p-interfaces/commit/87b4608ae4c373c8dd59b1b68a63293fe9e15d69)) + +## [@libp2p/interface-peer-discovery-compliance-tests-v2.0.3](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-peer-discovery-compliance-tests-v2.0.2...@libp2p/interface-peer-discovery-compliance-tests-v2.0.3) (2022-12-16) + + +### Documentation + +* update project config ([#323](https://github.com/libp2p/js-libp2p-interfaces/issues/323)) ([0fc6a08](https://github.com/libp2p/js-libp2p-interfaces/commit/0fc6a08e9cdcefe361fe325281a3a2a03759ff59)) + +## [@libp2p/interface-peer-discovery-compliance-tests-v2.0.2](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-peer-discovery-compliance-tests-v2.0.1...@libp2p/interface-peer-discovery-compliance-tests-v2.0.2) (2022-12-14) + + +### Bug Fixes + +* generate docs for all packages ([#321](https://github.com/libp2p/js-libp2p-interfaces/issues/321)) ([b6f8b32](https://github.com/libp2p/js-libp2p-interfaces/commit/b6f8b32a920c15a28fe021e6050e31aaae89d518)) + +## [@libp2p/interface-peer-discovery-compliance-tests-v2.0.1](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-peer-discovery-compliance-tests-v2.0.0...@libp2p/interface-peer-discovery-compliance-tests-v2.0.1) (2022-11-05) + + +### Bug Fixes + +* update project config ([#311](https://github.com/libp2p/js-libp2p-interfaces/issues/311)) ([27dd0ce](https://github.com/libp2p/js-libp2p-interfaces/commit/27dd0ce3c249892ac69cbb24ddaf0b9f32385e37)) + +## [@libp2p/interface-peer-discovery-compliance-tests-v2.0.0](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-peer-discovery-compliance-tests-v1.0.2...@libp2p/interface-peer-discovery-compliance-tests-v2.0.0) (2022-10-12) + + +### ⚠ BREAKING CHANGES + +* modules no longer implement `Initializable` instead switching to constructor injection + +### Bug Fixes + +* remove @libp2p/components ([#301](https://github.com/libp2p/js-libp2p-interfaces/issues/301)) ([1d37dc6](https://github.com/libp2p/js-libp2p-interfaces/commit/1d37dc6d3197838a71895d5769ad8bba6eb38fd3)) + +## [@libp2p/interface-peer-discovery-compliance-tests-v1.0.2](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-peer-discovery-compliance-tests-v1.0.1...@libp2p/interface-peer-discovery-compliance-tests-v1.0.2) (2022-09-21) + + +### Trivial Changes + +* update project config ([#271](https://github.com/libp2p/js-libp2p-interfaces/issues/271)) ([59c0bf5](https://github.com/libp2p/js-libp2p-interfaces/commit/59c0bf5e0b05496fca2e4902632b61bb41fad9e9)) + + +### Dependencies + +* update @multiformats/multiaddr to 11.0.0 ([#288](https://github.com/libp2p/js-libp2p-interfaces/issues/288)) ([57b2ad8](https://github.com/libp2p/js-libp2p-interfaces/commit/57b2ad88edfc7807311143791bc49270b1a81eaf)) + +## [@libp2p/interface-peer-discovery-compliance-tests-v1.0.1](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-peer-discovery-compliance-tests-v1.0.0...@libp2p/interface-peer-discovery-compliance-tests-v1.0.1) (2022-06-27) + + +### Trivial Changes + +* update deps ([#262](https://github.com/libp2p/js-libp2p-interfaces/issues/262)) ([51edf7d](https://github.com/libp2p/js-libp2p-interfaces/commit/51edf7d9b3765a6f75c915b1483ea345d0133a41)) diff --git a/packages/interface-peer-discovery-compliance-tests/LICENSE b/packages/interface-peer-discovery-compliance-tests/LICENSE new file mode 100644 index 0000000000..20ce483c86 --- /dev/null +++ b/packages/interface-peer-discovery-compliance-tests/LICENSE @@ -0,0 +1,4 @@ +This project is dual licensed under MIT and Apache-2.0. + +MIT: https://www.opensource.org/licenses/mit +Apache-2.0: https://www.apache.org/licenses/license-2.0 diff --git a/packages/interface-peer-discovery-compliance-tests/LICENSE-APACHE b/packages/interface-peer-discovery-compliance-tests/LICENSE-APACHE new file mode 100644 index 0000000000..14478a3b60 --- /dev/null +++ b/packages/interface-peer-discovery-compliance-tests/LICENSE-APACHE @@ -0,0 +1,5 @@ +Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. diff --git a/packages/interface-peer-discovery-compliance-tests/LICENSE-MIT b/packages/interface-peer-discovery-compliance-tests/LICENSE-MIT new file mode 100644 index 0000000000..72dc60d84b --- /dev/null +++ b/packages/interface-peer-discovery-compliance-tests/LICENSE-MIT @@ -0,0 +1,19 @@ +The MIT License (MIT) + +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/packages/interface-peer-discovery-compliance-tests/README.md b/packages/interface-peer-discovery-compliance-tests/README.md new file mode 100644 index 0000000000..b59ca18214 --- /dev/null +++ b/packages/interface-peer-discovery-compliance-tests/README.md @@ -0,0 +1,55 @@ +# @libp2p/interface-peer-discovery-compliance-tests + +[![libp2p.io](https://img.shields.io/badge/project-libp2p-yellow.svg?style=flat-square)](http://libp2p.io/) +[![Discuss](https://img.shields.io/discourse/https/discuss.libp2p.io/posts.svg?style=flat-square)](https://discuss.libp2p.io) +[![codecov](https://img.shields.io/codecov/c/github/libp2p/js-libp2p.svg?style=flat-square)](https://codecov.io/gh/libp2p/js-libp2p) +[![CI](https://img.shields.io/github/actions/workflow/status/libp2p/js-libp2p/js-test-and-release.yml?branch=master\&style=flat-square)](https://github.com/libp2p/js-libp2p/actions/workflows/js-test-and-release.yml?query=branch%3Amaster) + +> Compliance tests for implementations of the libp2p Peer Discovery interface + +## Table of contents + +- [Install](#install) +- [Usage](#usage) +- [API Docs](#api-docs) +- [License](#license) +- [Contribution](#contribution) + +## Install + +```console +$ npm i @libp2p/interface-peer-discovery-compliance-tests +``` + +## Usage + +```js +import tests from '@libp2p/interface-peer-discovery-compliance-tests' + +describe('your peer discovery implementation', () => { + tests({ + // Options should be passed to your implementation + async setup (options) { + return new YourImplementation() + }, + async teardown () { + // cleanup resources created by setup() + } + }) +}) +``` + +## API Docs + +- + +## License + +Licensed under either of + +- Apache 2.0, ([LICENSE-APACHE](LICENSE-APACHE) / ) +- MIT ([LICENSE-MIT](LICENSE-MIT) / ) + +## Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. diff --git a/packages/interface-peer-discovery-compliance-tests/package.json b/packages/interface-peer-discovery-compliance-tests/package.json new file mode 100644 index 0000000000..c09f705e82 --- /dev/null +++ b/packages/interface-peer-discovery-compliance-tests/package.json @@ -0,0 +1,142 @@ +{ + "name": "@libp2p/interface-peer-discovery-compliance-tests", + "version": "2.0.8", + "description": "Compliance tests for implementations of the libp2p Peer Discovery interface", + "license": "Apache-2.0 OR MIT", + "homepage": "https://github.com/libp2p/js-libp2p/tree/master/packages/interface-peer-discovery-compliance-tests#readme", + "repository": { + "type": "git", + "url": "git+https://github.com/libp2p/js-libp2p.git" + }, + "bugs": { + "url": "https://github.com/libp2p/js-libp2p/issues" + }, + "keywords": [ + "interface", + "libp2p" + ], + "type": "module", + "types": "./dist/src/index.d.ts", + "files": [ + "src", + "dist", + "!dist/test", + "!**/*.tsbuildinfo" + ], + "exports": { + ".": { + "types": "./dist/src/index.d.ts", + "import": "./dist/src/index.js" + } + }, + "eslintConfig": { + "extends": "ipfs", + "parserOptions": { + "sourceType": "module" + } + }, + "release": { + "branches": [ + "master" + ], + "plugins": [ + [ + "@semantic-release/commit-analyzer", + { + "preset": "conventionalcommits", + "releaseRules": [ + { + "breaking": true, + "release": "major" + }, + { + "revert": true, + "release": "patch" + }, + { + "type": "feat", + "release": "minor" + }, + { + "type": "fix", + "release": "patch" + }, + { + "type": "docs", + "release": "patch" + }, + { + "type": "test", + "release": "patch" + }, + { + "type": "deps", + "release": "patch" + }, + { + "scope": "no-release", + "release": false + } + ] + } + ], + [ + "@semantic-release/release-notes-generator", + { + "preset": "conventionalcommits", + "presetConfig": { + "types": [ + { + "type": "feat", + "section": "Features" + }, + { + "type": "fix", + "section": "Bug Fixes" + }, + { + "type": "chore", + "section": "Trivial Changes" + }, + { + "type": "docs", + "section": "Documentation" + }, + { + "type": "deps", + "section": "Dependencies" + }, + { + "type": "test", + "section": "Tests" + } + ] + } + } + ], + "@semantic-release/changelog", + "@semantic-release/npm", + "@semantic-release/github", + "@semantic-release/git" + ] + }, + "scripts": { + "clean": "aegir clean", + "lint": "aegir lint", + "dep-check": "aegir dep-check", + "build": "aegir build", + "release": "aegir release" + }, + "dependencies": { + "@libp2p/interface-compliance-tests": "^3.0.0", + "@libp2p/interface-peer-discovery": "^2.0.0", + "@libp2p/interfaces": "^3.0.0", + "@multiformats/multiaddr": "^12.0.0", + "aegir": "^39.0.5", + "delay": "^6.0.0", + "p-defer": "^4.0.0" + }, + "typedoc": { + "entryPoint": "./src/index.ts" + } +} diff --git a/packages/interface-peer-discovery-compliance-tests/src/index.ts b/packages/interface-peer-discovery-compliance-tests/src/index.ts new file mode 100644 index 0000000000..64c751b59c --- /dev/null +++ b/packages/interface-peer-discovery-compliance-tests/src/index.ts @@ -0,0 +1,90 @@ +import { start, stop } from '@libp2p/interfaces/startable' +import { isMultiaddr } from '@multiformats/multiaddr' +import { expect } from 'aegir/chai' +import delay from 'delay' +import pDefer from 'p-defer' +import type { TestSetup } from '@libp2p/interface-compliance-tests' +import type { PeerDiscovery } from '@libp2p/interface-peer-discovery' + +export default (common: TestSetup): void => { + describe('interface-peer-discovery compliance tests', () => { + let discovery: PeerDiscovery + + beforeEach(async () => { + discovery = await common.setup() + }) + + afterEach('ensure discovery was stopped', async () => { + await stop(discovery) + + await common.teardown() + }) + + it('can start the service', async () => { + await start(discovery) + }) + + it('can start and stop the service', async () => { + await start(discovery) + await stop(discovery) + }) + + it('should not fail to stop the service if it was not started', async () => { + await stop(discovery) + }) + + it('should not fail to start the service if it is already started', async () => { + await start(discovery) + await start(discovery) + }) + + it('should emit a peer event after start', async () => { + const defer = pDefer() + + discovery.addEventListener('peer', (evt) => { + const { id, multiaddrs } = evt.detail + expect(id).to.exist() + expect(id) + .to.have.property('type') + .that.is.oneOf(['RSA', 'Ed25519', 'secp256k1']) + expect(multiaddrs).to.exist() + + multiaddrs.forEach((m) => expect(isMultiaddr(m)).to.eql(true)) + + defer.resolve() + }) + + await start(discovery) + + await defer.promise + }) + + it('should not receive a peer event before start', async () => { + discovery.addEventListener('peer', () => { + throw new Error('should not receive a peer event before start') + }) + + await delay(2000) + }) + + it('should not receive a peer event after stop', async () => { + const deferStart = pDefer() + + discovery.addEventListener('peer', () => { + deferStart.resolve() + }) + + await start(discovery) + + await deferStart.promise + + await stop(discovery) + + discovery.addEventListener('peer', () => { + throw new Error('should not receive a peer event after stop') + }) + + await delay(2000) + }) + }) +} diff --git a/packages/interface-peer-discovery-compliance-tests/tsconfig.json b/packages/interface-peer-discovery-compliance-tests/tsconfig.json new file mode 100644 index 0000000000..faf685538c --- /dev/null +++ b/packages/interface-peer-discovery-compliance-tests/tsconfig.json @@ -0,0 +1,21 @@ +{ + "extends": "aegir/src/config/tsconfig.aegir.json", + "compilerOptions": { + "outDir": "dist" + }, + "include": [ + "src", + "test" + ], + "references": [ + { + "path": "../interface-compliance-tests" + }, + { + "path": "../interface-peer-discovery" + }, + { + "path": "../interfaces" + } + ] +} diff --git a/packages/interface-peer-discovery/CHANGELOG.md b/packages/interface-peer-discovery/CHANGELOG.md new file mode 100644 index 0000000000..778f7bfde4 --- /dev/null +++ b/packages/interface-peer-discovery/CHANGELOG.md @@ -0,0 +1,69 @@ +## [@libp2p/interface-peer-discovery-v2.0.0](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-peer-discovery-v1.1.1...@libp2p/interface-peer-discovery-v2.0.0) (2023-05-04) + + +### ⚠ BREAKING CHANGES + +* the `symbol` export is now named `peerDiscovery` and the getter with that name should return an instance of `PeerDiscovery` + +### Features + +* rename peer discovery symbol to peerDiscovery ([#394](https://github.com/libp2p/js-libp2p-interfaces/issues/394)) ([5957c77](https://github.com/libp2p/js-libp2p-interfaces/commit/5957c77718df6e6336ca22386d8c03a045fd1d89)) + +## [@libp2p/interface-peer-discovery-v1.1.1](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-peer-discovery-v1.1.0...@libp2p/interface-peer-discovery-v1.1.1) (2023-05-04) + + +### Dependencies + +* bump aegir from 38.1.8 to 39.0.5 ([#393](https://github.com/libp2p/js-libp2p-interfaces/issues/393)) ([31f3797](https://github.com/libp2p/js-libp2p-interfaces/commit/31f3797b24f7c23f3f16e9db3a230bd5f7cd5175)) + +## [@libp2p/interface-peer-discovery-v1.1.0](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-peer-discovery-v1.0.5...@libp2p/interface-peer-discovery-v1.1.0) (2023-04-27) + + +### Features + +* add routing symbols ([#388](https://github.com/libp2p/js-libp2p-interfaces/issues/388)) ([9ee7691](https://github.com/libp2p/js-libp2p-interfaces/commit/9ee76915d2b8298d99557e105c4f71d585e97e7d)) + +## [@libp2p/interface-peer-discovery-v1.0.5](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-peer-discovery-v1.0.4...@libp2p/interface-peer-discovery-v1.0.5) (2023-01-18) + + +### Trivial Changes + +* remove lerna ([#330](https://github.com/libp2p/js-libp2p-interfaces/issues/330)) ([6678592](https://github.com/libp2p/js-libp2p-interfaces/commit/6678592dd0cf601a2671852f9d2a0aff5dee2b18)) + + +### Dependencies + +* bump aegir from 37.12.1 to 38.1.0 ([#335](https://github.com/libp2p/js-libp2p-interfaces/issues/335)) ([7368a36](https://github.com/libp2p/js-libp2p-interfaces/commit/7368a363423a08e8fa247dcb76ea13e4cf030d65)) + +## [@libp2p/interface-peer-discovery-v1.0.4](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-peer-discovery-v1.0.3...@libp2p/interface-peer-discovery-v1.0.4) (2022-12-16) + + +### Documentation + +* update project config ([#323](https://github.com/libp2p/js-libp2p-interfaces/issues/323)) ([0fc6a08](https://github.com/libp2p/js-libp2p-interfaces/commit/0fc6a08e9cdcefe361fe325281a3a2a03759ff59)) + +## [@libp2p/interface-peer-discovery-v1.0.3](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-peer-discovery-v1.0.2...@libp2p/interface-peer-discovery-v1.0.3) (2022-12-14) + + +### Bug Fixes + +* generate docs for all packages ([#321](https://github.com/libp2p/js-libp2p-interfaces/issues/321)) ([b6f8b32](https://github.com/libp2p/js-libp2p-interfaces/commit/b6f8b32a920c15a28fe021e6050e31aaae89d518)) + +## [@libp2p/interface-peer-discovery-v1.0.2](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-peer-discovery-v1.0.1...@libp2p/interface-peer-discovery-v1.0.2) (2022-11-05) + + +### Bug Fixes + +* update project config ([#311](https://github.com/libp2p/js-libp2p-interfaces/issues/311)) ([27dd0ce](https://github.com/libp2p/js-libp2p-interfaces/commit/27dd0ce3c249892ac69cbb24ddaf0b9f32385e37)) + + +### Trivial Changes + +* update project config ([#271](https://github.com/libp2p/js-libp2p-interfaces/issues/271)) ([59c0bf5](https://github.com/libp2p/js-libp2p-interfaces/commit/59c0bf5e0b05496fca2e4902632b61bb41fad9e9)) + +## [@libp2p/interface-peer-discovery-v1.0.1](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-peer-discovery-v1.0.0...@libp2p/interface-peer-discovery-v1.0.1) (2022-06-27) + + +### Trivial Changes + +* update deps ([#262](https://github.com/libp2p/js-libp2p-interfaces/issues/262)) ([51edf7d](https://github.com/libp2p/js-libp2p-interfaces/commit/51edf7d9b3765a6f75c915b1483ea345d0133a41)) diff --git a/packages/interface-peer-discovery/LICENSE b/packages/interface-peer-discovery/LICENSE new file mode 100644 index 0000000000..20ce483c86 --- /dev/null +++ b/packages/interface-peer-discovery/LICENSE @@ -0,0 +1,4 @@ +This project is dual licensed under MIT and Apache-2.0. + +MIT: https://www.opensource.org/licenses/mit +Apache-2.0: https://www.apache.org/licenses/license-2.0 diff --git a/packages/interface-peer-discovery/LICENSE-APACHE b/packages/interface-peer-discovery/LICENSE-APACHE new file mode 100644 index 0000000000..14478a3b60 --- /dev/null +++ b/packages/interface-peer-discovery/LICENSE-APACHE @@ -0,0 +1,5 @@ +Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. diff --git a/packages/interface-peer-discovery/LICENSE-MIT b/packages/interface-peer-discovery/LICENSE-MIT new file mode 100644 index 0000000000..72dc60d84b --- /dev/null +++ b/packages/interface-peer-discovery/LICENSE-MIT @@ -0,0 +1,19 @@ +The MIT License (MIT) + +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/packages/interface-peer-discovery/README.md b/packages/interface-peer-discovery/README.md new file mode 100644 index 0000000000..c7bcc6798e --- /dev/null +++ b/packages/interface-peer-discovery/README.md @@ -0,0 +1,118 @@ +# @libp2p/interface-peer-discovery + +[![libp2p.io](https://img.shields.io/badge/project-libp2p-yellow.svg?style=flat-square)](http://libp2p.io/) +[![Discuss](https://img.shields.io/discourse/https/discuss.libp2p.io/posts.svg?style=flat-square)](https://discuss.libp2p.io) +[![codecov](https://img.shields.io/codecov/c/github/libp2p/js-libp2p.svg?style=flat-square)](https://codecov.io/gh/libp2p/js-libp2p) +[![CI](https://img.shields.io/github/actions/workflow/status/libp2p/js-libp2p/js-test-and-release.yml?branch=master\&style=flat-square)](https://github.com/libp2p/js-libp2p/actions/workflows/js-test-and-release.yml?query=branch%3Amaster) + +> Peer Discovery interface for libp2p + +## Table of contents + +- [Install](#install) +- [Modules that implement the interface](#modules-that-implement-the-interface) +- [Badge](#badge) +- [Usage](#usage) + - [Node.js](#nodejs) +- [API](#api) + - [`start` the service](#start-the-service) + - [`stop` the service](#stop-the-service) + - [discovering peers](#discovering-peers) +- [API Docs](#api-docs) +- [License](#license) +- [Contribution](#contribution) + +## Install + +```console +$ npm i @libp2p/interface-peer-discovery +``` + +The primary goal of this module is to enable developers to pick and/or swap their Peer Discovery modules as they see fit for their application, without having to go through shims or compatibility issues. This module and test suite was heavily inspired by [abstract-blob-store](https://github.com/maxogden/abstract-blob-store). + +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 different stacks. + +## Modules that implement the interface + +- [JavaScript libp2p-mdns](https://github.com/libp2p/js-libp2p-mdns) +- [JavaScript libp2p-bootstrap](https://github.com/libp2p/js-libp2p-bootstrap) +- [JavaScript libp2p-kad-dht](https://github.com/libp2p/js-libp2p-kad-dht) +- [JavaScript libp2p-webrtc-star](https://github.com/libp2p/js-libp2p-webrtc-star) +- [JavaScript libp2p-websocket-star](https://github.com/libp2p/js-libp2p-websocket-star) +- [TypeScript discv5](https://github.com/chainsafe/discv5) + +Send a PR to add a new one if you happen to find or write one. + +## Badge + +Include this badge in your readme if you make a new module that uses interface-peer-discovery API. + +![](img/badge.png) + +## Usage + +### Node.js + +Install `interface-discovery` as one of the dependencies of your project and as a test file. Then, using `mocha` (for JavaScript) or a test runner with compatible API, do: + +```js +const tests = require('libp2p-interfaces-compliance-tests/peer-discovery') + +describe('your discovery', () => { + // use all of the test suits + tests({ + setup () { + return YourDiscovery + }, + teardown () { + // Clean up any resources created by setup() + } + }) +}) +``` + +## API + +A valid (read: that follows this abstraction) Peer Discovery module must implement the following API: + +### `start` the service + +- `await discovery.start()` + +Start the discovery service. + +It returns a `Promise` + +### `stop` the service + +- `await discovery.stop()` + +Stop the discovery service. + +It returns a `Promise` + +### discovering peers + +- `discovery.on('peer', (peerData) => {})` + +Every time a peer is discovered by a discovery service, it emits a `peer` event with the discovered peer's information, which must contain the following properties: + +- `<`[`PeerId`](https://github.com/libp2p/js-peer-id)`>` `peerData.id` +- `>` `peerData.multiaddrs` + +## API Docs + +- + +## License + +Licensed under either of + +- Apache 2.0, ([LICENSE-APACHE](LICENSE-APACHE) / ) +- MIT ([LICENSE-MIT](LICENSE-MIT) / ) + +## Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. diff --git a/packages/interface-peer-discovery/img/badge.png b/packages/interface-peer-discovery/img/badge.png new file mode 100644 index 0000000000000000000000000000000000000000..785abc493c3b0906e47b1690ffcdcadd588fa96e GIT binary patch literal 6165 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 literal 0 HcmV?d00001 diff --git a/packages/interface-peer-discovery/img/badge.sketch b/packages/interface-peer-discovery/img/badge.sketch new file mode 100644 index 0000000000000000000000000000000000000000..51a982d06d62a16903e4e3c4283af1479bfb8438 GIT binary patch literal 106496 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 S4jaEosZHNM{jWoE?7sjWaGS0G literal 0 HcmV?d00001 diff --git a/packages/interface-peer-discovery/img/badge.svg b/packages/interface-peer-discovery/img/badge.svg new file mode 100644 index 0000000000..8ecfc6dee3 --- /dev/null +++ b/packages/interface-peer-discovery/img/badge.svg @@ -0,0 +1,39 @@ + + + + badge + Created with Sketch. + + + + + + + + + + + + Peer Discovery + + + Compatibl + e + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/interface-peer-discovery/package.json b/packages/interface-peer-discovery/package.json new file mode 100644 index 0000000000..05ccd1766d --- /dev/null +++ b/packages/interface-peer-discovery/package.json @@ -0,0 +1,140 @@ +{ + "name": "@libp2p/interface-peer-discovery", + "version": "2.0.0", + "description": "Peer Discovery interface for libp2p", + "license": "Apache-2.0 OR MIT", + "homepage": "https://github.com/libp2p/js-libp2p/tree/master/packages/interface-peer-discovery#readme", + "repository": { + "type": "git", + "url": "git+https://github.com/libp2p/js-libp2p.git" + }, + "bugs": { + "url": "https://github.com/libp2p/js-libp2p/issues" + }, + "keywords": [ + "interface", + "libp2p" + ], + "type": "module", + "types": "./dist/src/index.d.ts", + "files": [ + "src", + "dist", + "!dist/test", + "!**/*.tsbuildinfo" + ], + "exports": { + ".": { + "types": "./dist/src/index.d.ts", + "import": "./dist/src/index.js" + } + }, + "eslintConfig": { + "extends": "ipfs", + "parserOptions": { + "sourceType": "module" + } + }, + "release": { + "branches": [ + "master" + ], + "plugins": [ + [ + "@semantic-release/commit-analyzer", + { + "preset": "conventionalcommits", + "releaseRules": [ + { + "breaking": true, + "release": "major" + }, + { + "revert": true, + "release": "patch" + }, + { + "type": "feat", + "release": "minor" + }, + { + "type": "fix", + "release": "patch" + }, + { + "type": "docs", + "release": "patch" + }, + { + "type": "test", + "release": "patch" + }, + { + "type": "deps", + "release": "patch" + }, + { + "scope": "no-release", + "release": false + } + ] + } + ], + [ + "@semantic-release/release-notes-generator", + { + "preset": "conventionalcommits", + "presetConfig": { + "types": [ + { + "type": "feat", + "section": "Features" + }, + { + "type": "fix", + "section": "Bug Fixes" + }, + { + "type": "chore", + "section": "Trivial Changes" + }, + { + "type": "docs", + "section": "Documentation" + }, + { + "type": "deps", + "section": "Dependencies" + }, + { + "type": "test", + "section": "Tests" + } + ] + } + } + ], + "@semantic-release/changelog", + "@semantic-release/npm", + "@semantic-release/github", + "@semantic-release/git" + ] + }, + "scripts": { + "clean": "aegir clean", + "lint": "aegir lint", + "dep-check": "aegir dep-check", + "build": "aegir build", + "release": "aegir release" + }, + "dependencies": { + "@libp2p/interface-peer-info": "^1.0.0", + "@libp2p/interfaces": "^3.0.0" + }, + "devDependencies": { + "aegir": "^39.0.5" + }, + "typedoc": { + "entryPoint": "./src/index.ts" + } +} diff --git a/packages/interface-peer-discovery/src/index.ts b/packages/interface-peer-discovery/src/index.ts new file mode 100644 index 0000000000..b0840503d6 --- /dev/null +++ b/packages/interface-peer-discovery/src/index.ts @@ -0,0 +1,29 @@ +import type { PeerInfo } from '@libp2p/interface-peer-info' +import type { EventEmitter } from '@libp2p/interfaces/events' + +/** + * Any object that implements this Symbol as a property should return a + * PeerDiscovery instance as the property value, similar to how + * `Symbol.Iterable` can be used to return an `Iterable` from an `Iterator`. + * + * @example + * + * ```js + * import { peerDiscovery, PeerDiscovery } from '@libp2p/peer-discovery' + * + * class MyPeerDiscoverer implements PeerDiscovery { + * get [peerDiscovery] () { + * return this + * } + * + * // ...other methods + * } + * ``` + */ +export const peerDiscovery = Symbol.for('@libp2p/peer-discovery') + +export interface PeerDiscoveryEvents { + 'peer': CustomEvent +} + +export interface PeerDiscovery extends EventEmitter {} diff --git a/packages/interface-peer-discovery/tsconfig.json b/packages/interface-peer-discovery/tsconfig.json new file mode 100644 index 0000000000..84bbd0ffe3 --- /dev/null +++ b/packages/interface-peer-discovery/tsconfig.json @@ -0,0 +1,17 @@ +{ + "extends": "aegir/src/config/tsconfig.aegir.json", + "compilerOptions": { + "outDir": "dist" + }, + "include": [ + "src" + ], + "references": [ + { + "path": "../interface-peer-info" + }, + { + "path": "../interfaces" + } + ] +} diff --git a/packages/interface-peer-id/CHANGELOG.md b/packages/interface-peer-id/CHANGELOG.md new file mode 100644 index 0000000000..268e8ca277 --- /dev/null +++ b/packages/interface-peer-id/CHANGELOG.md @@ -0,0 +1,108 @@ +## [@libp2p/interface-peer-id-v2.0.2](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-peer-id-v2.0.1...@libp2p/interface-peer-id-v2.0.2) (2023-05-04) + + +### Dependencies + +* bump aegir from 38.1.8 to 39.0.5 ([#393](https://github.com/libp2p/js-libp2p-interfaces/issues/393)) ([31f3797](https://github.com/libp2p/js-libp2p-interfaces/commit/31f3797b24f7c23f3f16e9db3a230bd5f7cd5175)) + +## [@libp2p/interface-peer-id-v2.0.1](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-peer-id-v2.0.0...@libp2p/interface-peer-id-v2.0.1) (2023-01-18) + + +### Trivial Changes + +* remove lerna ([#330](https://github.com/libp2p/js-libp2p-interfaces/issues/330)) ([6678592](https://github.com/libp2p/js-libp2p-interfaces/commit/6678592dd0cf601a2671852f9d2a0aff5dee2b18)) + + +### Dependencies + +* bump aegir from 37.12.1 to 38.1.0 ([#335](https://github.com/libp2p/js-libp2p-interfaces/issues/335)) ([7368a36](https://github.com/libp2p/js-libp2p-interfaces/commit/7368a363423a08e8fa247dcb76ea13e4cf030d65)) + +## [@libp2p/interface-peer-id-v2.0.0](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-peer-id-v1.1.2...@libp2p/interface-peer-id-v2.0.0) (2023-01-06) + + +### ⚠ BREAKING CHANGES + +* bump multiformats from 10.0.3 to 11.0.0 (#329) + +### Dependencies + +* bump multiformats from 10.0.3 to 11.0.0 ([#329](https://github.com/libp2p/js-libp2p-interfaces/issues/329)) ([ba3a98b](https://github.com/libp2p/js-libp2p-interfaces/commit/ba3a98be61e3cf0996fefbd3004e974bb41ad2f0)) + +## [@libp2p/interface-peer-id-v1.1.2](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-peer-id-v1.1.1...@libp2p/interface-peer-id-v1.1.2) (2022-12-16) + + +### Documentation + +* update project config ([#323](https://github.com/libp2p/js-libp2p-interfaces/issues/323)) ([0fc6a08](https://github.com/libp2p/js-libp2p-interfaces/commit/0fc6a08e9cdcefe361fe325281a3a2a03759ff59)) + +## [@libp2p/interface-peer-id-v1.1.1](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-peer-id-v1.1.0...@libp2p/interface-peer-id-v1.1.1) (2022-12-14) + + +### Bug Fixes + +* generate docs for all packages ([#321](https://github.com/libp2p/js-libp2p-interfaces/issues/321)) ([b6f8b32](https://github.com/libp2p/js-libp2p-interfaces/commit/b6f8b32a920c15a28fe021e6050e31aaae89d518)) + +## [@libp2p/interface-peer-id-v1.1.0](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-peer-id-v1.0.6...@libp2p/interface-peer-id-v1.1.0) (2022-12-07) + + +### Features + +* expose supported peer id types ([#318](https://github.com/libp2p/js-libp2p-interfaces/issues/318)) ([1197484](https://github.com/libp2p/js-libp2p-interfaces/commit/11974843445a7f4792192340c0697d40bf87fddc)) + +## [@libp2p/interface-peer-id-v1.0.6](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-peer-id-v1.0.5...@libp2p/interface-peer-id-v1.0.6) (2022-11-05) + + +### Bug Fixes + +* update project config ([#311](https://github.com/libp2p/js-libp2p-interfaces/issues/311)) ([27dd0ce](https://github.com/libp2p/js-libp2p-interfaces/commit/27dd0ce3c249892ac69cbb24ddaf0b9f32385e37)) + +## [@libp2p/interface-peer-id-v1.0.5](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-peer-id-v1.0.4...@libp2p/interface-peer-id-v1.0.5) (2022-10-12) + + +### Trivial Changes + +* update project config ([#271](https://github.com/libp2p/js-libp2p-interfaces/issues/271)) ([59c0bf5](https://github.com/libp2p/js-libp2p-interfaces/commit/59c0bf5e0b05496fca2e4902632b61bb41fad9e9)) + + +### Dependencies + +* bump multiformats from 9.9.0 to 10.0.0 ([#302](https://github.com/libp2p/js-libp2p-interfaces/issues/302)) ([fe11d69](https://github.com/libp2p/js-libp2p-interfaces/commit/fe11d69b6aca3dd6ef6053bec27b534ec9908aa1)) + +## [@libp2p/interface-peer-id-v1.0.4](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-peer-id-v1.0.3...@libp2p/interface-peer-id-v1.0.4) (2022-06-27) + + +### Trivial Changes + +* update `peer-id` link to new implementation ([#261](https://github.com/libp2p/js-libp2p-interfaces/issues/261)) ([a2dda7d](https://github.com/libp2p/js-libp2p-interfaces/commit/a2dda7d0cfa33737ebc6bb4ae56de5b55e951c63)) + +## [@libp2p/interface-peer-id-v1.0.3](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-peer-id-v1.0.2...@libp2p/interface-peer-id-v1.0.3) (2022-06-27) + + +### Trivial Changes + +* update deps ([#262](https://github.com/libp2p/js-libp2p-interfaces/issues/262)) ([51edf7d](https://github.com/libp2p/js-libp2p-interfaces/commit/51edf7d9b3765a6f75c915b1483ea345d0133a41)) + +## [@libp2p/interface-peer-id-v1.0.2](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-peer-id-v1.0.1...@libp2p/interface-peer-id-v1.0.2) (2022-06-14) + + +### Trivial Changes + +* update aegir ([#234](https://github.com/libp2p/js-libp2p-interfaces/issues/234)) ([3e03895](https://github.com/libp2p/js-libp2p-interfaces/commit/3e038959ecab6cfa3585df9ee179c0af7a61eda5)) + +## [@libp2p/interface-peer-id-v1.0.1](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-peer-id-v1.0.0...@libp2p/interface-peer-id-v1.0.1) (2022-06-14) + + +### Trivial Changes + +* update readmes ([#233](https://github.com/libp2p/js-libp2p-interfaces/issues/233)) ([ee7da38](https://github.com/libp2p/js-libp2p-interfaces/commit/ee7da38dccc08160d26c8436df8739ce7e0b340e)) + +## @libp2p/interface-peer-id-v1.0.0 (2022-06-14) + + +### ⚠ BREAKING CHANGES + +* most modules have been split out of the `@libp2p/interfaces` and `@libp2p/interface-compliance-tests` packages + +### Trivial Changes + +* break modules apart ([#232](https://github.com/libp2p/js-libp2p-interfaces/issues/232)) ([385614e](https://github.com/libp2p/js-libp2p-interfaces/commit/385614e772329052ab17415c8bd421f65b01a61b)), closes [#226](https://github.com/libp2p/js-libp2p-interfaces/issues/226) diff --git a/packages/interface-peer-id/LICENSE b/packages/interface-peer-id/LICENSE new file mode 100644 index 0000000000..20ce483c86 --- /dev/null +++ b/packages/interface-peer-id/LICENSE @@ -0,0 +1,4 @@ +This project is dual licensed under MIT and Apache-2.0. + +MIT: https://www.opensource.org/licenses/mit +Apache-2.0: https://www.apache.org/licenses/license-2.0 diff --git a/packages/interface-peer-id/LICENSE-APACHE b/packages/interface-peer-id/LICENSE-APACHE new file mode 100644 index 0000000000..14478a3b60 --- /dev/null +++ b/packages/interface-peer-id/LICENSE-APACHE @@ -0,0 +1,5 @@ +Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. diff --git a/packages/interface-peer-id/LICENSE-MIT b/packages/interface-peer-id/LICENSE-MIT new file mode 100644 index 0000000000..72dc60d84b --- /dev/null +++ b/packages/interface-peer-id/LICENSE-MIT @@ -0,0 +1,19 @@ +The MIT License (MIT) + +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/packages/interface-peer-id/README.md b/packages/interface-peer-id/README.md new file mode 100644 index 0000000000..3246aa028b --- /dev/null +++ b/packages/interface-peer-id/README.md @@ -0,0 +1,80 @@ +# @libp2p/interface-peer-id + +[![libp2p.io](https://img.shields.io/badge/project-libp2p-yellow.svg?style=flat-square)](http://libp2p.io/) +[![Discuss](https://img.shields.io/discourse/https/discuss.libp2p.io/posts.svg?style=flat-square)](https://discuss.libp2p.io) +[![codecov](https://img.shields.io/codecov/c/github/libp2p/js-libp2p.svg?style=flat-square)](https://codecov.io/gh/libp2p/js-libp2p) +[![CI](https://img.shields.io/github/actions/workflow/status/libp2p/js-libp2p/js-test-and-release.yml?branch=master\&style=flat-square)](https://github.com/libp2p/js-libp2p/actions/workflows/js-test-and-release.yml?query=branch%3Amaster) + +> Peer Identifier interface for libp2p + +## Table of contents + +- [Install](#install) +- [Modules that implement the interface](#modules-that-implement-the-interface) +- [Badge](#badge) +- [Usage](#usage) + - [Node.js](#nodejs) +- [API Docs](#api-docs) +- [License](#license) +- [Contribution](#contribution) + +## Install + +```console +$ npm i @libp2p/interface-peer-id +``` + +The primary goal of this module is to enable developers to implement PeerId modules. This module and test suite was heavily inspired by earlier implementation of [PeerId](https://github.com/libp2p/js-peer-id). + +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 different stacks. + +## Modules that implement the interface + +- [JavaScript libp2p-peer-id](https://github.com/libp2p/js-libp2p-peer-id) + +Send a PR to add a new one if you happen to find or write one. + +## Badge + +Include this badge in your readme if you make a new module that uses interface-peer-id API. + +![](/img/badge.png) + +## Usage + +### Node.js + +Install `libp2p-interfaces-compliance-tests` as one of the development dependencies of your project and as a test file. Then, using `mocha` (for JavaScript) or a test runner with compatible API, do: + +```js +const tests = require('libp2p-interfaces-compliance-tests/peer-id') + +describe('your peer id', () => { + // use all of the test suits + tests({ + setup () { + return YourPeerIdFactory + }, + teardown () { + // Clean up any resources created by setup() + } + }) +}) +``` + +## API Docs + +- + +## License + +Licensed under either of + +- Apache 2.0, ([LICENSE-APACHE](LICENSE-APACHE) / ) +- MIT ([LICENSE-MIT](LICENSE-MIT) / ) + +## Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. diff --git a/packages/interface-peer-id/package.json b/packages/interface-peer-id/package.json new file mode 100644 index 0000000000..3a5610ae56 --- /dev/null +++ b/packages/interface-peer-id/package.json @@ -0,0 +1,139 @@ +{ + "name": "@libp2p/interface-peer-id", + "version": "2.0.2", + "description": "Peer Identifier interface for libp2p", + "license": "Apache-2.0 OR MIT", + "homepage": "https://github.com/libp2p/js-libp2p/tree/master/packages/interface-peer-id#readme", + "repository": { + "type": "git", + "url": "git+https://github.com/libp2p/js-libp2p.git" + }, + "bugs": { + "url": "https://github.com/libp2p/js-libp2p/issues" + }, + "keywords": [ + "interface", + "libp2p" + ], + "type": "module", + "types": "./dist/src/index.d.ts", + "files": [ + "src", + "dist", + "!dist/test", + "!**/*.tsbuildinfo" + ], + "exports": { + ".": { + "types": "./dist/src/index.d.ts", + "import": "./dist/src/index.js" + } + }, + "eslintConfig": { + "extends": "ipfs", + "parserOptions": { + "sourceType": "module" + } + }, + "release": { + "branches": [ + "master" + ], + "plugins": [ + [ + "@semantic-release/commit-analyzer", + { + "preset": "conventionalcommits", + "releaseRules": [ + { + "breaking": true, + "release": "major" + }, + { + "revert": true, + "release": "patch" + }, + { + "type": "feat", + "release": "minor" + }, + { + "type": "fix", + "release": "patch" + }, + { + "type": "docs", + "release": "patch" + }, + { + "type": "test", + "release": "patch" + }, + { + "type": "deps", + "release": "patch" + }, + { + "scope": "no-release", + "release": false + } + ] + } + ], + [ + "@semantic-release/release-notes-generator", + { + "preset": "conventionalcommits", + "presetConfig": { + "types": [ + { + "type": "feat", + "section": "Features" + }, + { + "type": "fix", + "section": "Bug Fixes" + }, + { + "type": "chore", + "section": "Trivial Changes" + }, + { + "type": "docs", + "section": "Documentation" + }, + { + "type": "deps", + "section": "Dependencies" + }, + { + "type": "test", + "section": "Tests" + } + ] + } + } + ], + "@semantic-release/changelog", + "@semantic-release/npm", + "@semantic-release/github", + "@semantic-release/git" + ] + }, + "scripts": { + "clean": "aegir clean", + "lint": "aegir lint", + "dep-check": "aegir dep-check", + "build": "aegir build", + "release": "aegir release" + }, + "dependencies": { + "multiformats": "^11.0.0" + }, + "devDependencies": { + "aegir": "^39.0.5" + }, + "typedoc": { + "entryPoint": "./src/index.ts" + } +} diff --git a/packages/interface-peer-id/src/index.ts b/packages/interface-peer-id/src/index.ts new file mode 100644 index 0000000000..39b629141d --- /dev/null +++ b/packages/interface-peer-id/src/index.ts @@ -0,0 +1,39 @@ +import type { CID } from 'multiformats/cid' +import type { MultihashDigest } from 'multiformats/hashes/interface' + +export type PeerIdType = 'RSA' | 'Ed25519' | 'secp256k1' + +interface BasePeerId { + readonly type: PeerIdType + readonly multihash: MultihashDigest + readonly privateKey?: Uint8Array + readonly publicKey?: Uint8Array + + toString: () => string + toCID: () => CID + toBytes: () => Uint8Array + equals: (other: PeerId | Uint8Array | string) => boolean +} + +export interface RSAPeerId extends BasePeerId { + readonly type: 'RSA' + readonly publicKey?: Uint8Array +} + +export interface Ed25519PeerId extends BasePeerId { + readonly type: 'Ed25519' + readonly publicKey: Uint8Array +} + +export interface Secp256k1PeerId extends BasePeerId { + readonly type: 'secp256k1' + readonly publicKey: Uint8Array +} + +export type PeerId = RSAPeerId | Ed25519PeerId | Secp256k1PeerId + +export const symbol = Symbol.for('@libp2p/peer-id') + +export function isPeerId (other: any): other is PeerId { + return other != null && Boolean(other[symbol]) +} diff --git a/packages/interface-peer-id/tsconfig.json b/packages/interface-peer-id/tsconfig.json new file mode 100644 index 0000000000..5fe8ea40d7 --- /dev/null +++ b/packages/interface-peer-id/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "aegir/src/config/tsconfig.aegir.json", + "compilerOptions": { + "outDir": "dist" + }, + "include": [ + "src" + ] +} diff --git a/packages/interface-peer-info/CHANGELOG.md b/packages/interface-peer-info/CHANGELOG.md new file mode 100644 index 0000000000..e4ac0e783d --- /dev/null +++ b/packages/interface-peer-info/CHANGELOG.md @@ -0,0 +1,91 @@ +## [@libp2p/interface-peer-info-v1.0.10](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-peer-info-v1.0.9...@libp2p/interface-peer-info-v1.0.10) (2023-05-04) + + +### Dependencies + +* bump aegir from 38.1.8 to 39.0.5 ([#393](https://github.com/libp2p/js-libp2p-interfaces/issues/393)) ([31f3797](https://github.com/libp2p/js-libp2p-interfaces/commit/31f3797b24f7c23f3f16e9db3a230bd5f7cd5175)) + +## [@libp2p/interface-peer-info-v1.0.9](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-peer-info-v1.0.8...@libp2p/interface-peer-info-v1.0.9) (2023-03-17) + + +### Dependencies + +* update @multiformats/multiaddr to 12.0.0 ([#354](https://github.com/libp2p/js-libp2p-interfaces/issues/354)) ([e0f327b](https://github.com/libp2p/js-libp2p-interfaces/commit/e0f327b5d54e240feabadce21a841629d633ec5e)) + +## [@libp2p/interface-peer-info-v1.0.8](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-peer-info-v1.0.7...@libp2p/interface-peer-info-v1.0.8) (2023-01-18) + + +### Trivial Changes + +* remove lerna ([#330](https://github.com/libp2p/js-libp2p-interfaces/issues/330)) ([6678592](https://github.com/libp2p/js-libp2p-interfaces/commit/6678592dd0cf601a2671852f9d2a0aff5dee2b18)) + + +### Dependencies + +* bump aegir from 37.12.1 to 38.1.0 ([#335](https://github.com/libp2p/js-libp2p-interfaces/issues/335)) ([7368a36](https://github.com/libp2p/js-libp2p-interfaces/commit/7368a363423a08e8fa247dcb76ea13e4cf030d65)) + +## [@libp2p/interface-peer-info-v1.0.7](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-peer-info-v1.0.6...@libp2p/interface-peer-info-v1.0.7) (2023-01-06) + + +### Dependencies + +* update sibling dependencies ([b50e621](https://github.com/libp2p/js-libp2p-interfaces/commit/b50e621d31a8b32affc3fadb9f97c4883d577f93)) + +## [@libp2p/interface-peer-info-v1.0.6](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-peer-info-v1.0.5...@libp2p/interface-peer-info-v1.0.6) (2022-12-16) + + +### Documentation + +* update project config ([#323](https://github.com/libp2p/js-libp2p-interfaces/issues/323)) ([0fc6a08](https://github.com/libp2p/js-libp2p-interfaces/commit/0fc6a08e9cdcefe361fe325281a3a2a03759ff59)) + +## [@libp2p/interface-peer-info-v1.0.5](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-peer-info-v1.0.4...@libp2p/interface-peer-info-v1.0.5) (2022-12-14) + + +### Bug Fixes + +* generate docs for all packages ([#321](https://github.com/libp2p/js-libp2p-interfaces/issues/321)) ([b6f8b32](https://github.com/libp2p/js-libp2p-interfaces/commit/b6f8b32a920c15a28fe021e6050e31aaae89d518)) + +## [@libp2p/interface-peer-info-v1.0.4](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-peer-info-v1.0.3...@libp2p/interface-peer-info-v1.0.4) (2022-11-05) + + +### Bug Fixes + +* update project config ([#311](https://github.com/libp2p/js-libp2p-interfaces/issues/311)) ([27dd0ce](https://github.com/libp2p/js-libp2p-interfaces/commit/27dd0ce3c249892ac69cbb24ddaf0b9f32385e37)) + +## [@libp2p/interface-peer-info-v1.0.3](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-peer-info-v1.0.2...@libp2p/interface-peer-info-v1.0.3) (2022-09-21) + + +### Trivial Changes + +* update project config ([#271](https://github.com/libp2p/js-libp2p-interfaces/issues/271)) ([59c0bf5](https://github.com/libp2p/js-libp2p-interfaces/commit/59c0bf5e0b05496fca2e4902632b61bb41fad9e9)) + + +### Dependencies + +* update @multiformats/multiaddr to 11.0.0 ([#288](https://github.com/libp2p/js-libp2p-interfaces/issues/288)) ([57b2ad8](https://github.com/libp2p/js-libp2p-interfaces/commit/57b2ad88edfc7807311143791bc49270b1a81eaf)) + +## [@libp2p/interface-peer-info-v1.0.2](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-peer-info-v1.0.1...@libp2p/interface-peer-info-v1.0.2) (2022-06-27) + + +### Trivial Changes + +* update deps ([#262](https://github.com/libp2p/js-libp2p-interfaces/issues/262)) ([51edf7d](https://github.com/libp2p/js-libp2p-interfaces/commit/51edf7d9b3765a6f75c915b1483ea345d0133a41)) + +## [@libp2p/interface-peer-info-v1.0.1](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-peer-info-v1.0.0...@libp2p/interface-peer-info-v1.0.1) (2022-06-14) + + +### Trivial Changes + +* update aegir ([#234](https://github.com/libp2p/js-libp2p-interfaces/issues/234)) ([3e03895](https://github.com/libp2p/js-libp2p-interfaces/commit/3e038959ecab6cfa3585df9ee179c0af7a61eda5)) +* update readmes ([#233](https://github.com/libp2p/js-libp2p-interfaces/issues/233)) ([ee7da38](https://github.com/libp2p/js-libp2p-interfaces/commit/ee7da38dccc08160d26c8436df8739ce7e0b340e)) + +## @libp2p/interface-peer-info-v1.0.0 (2022-06-14) + + +### ⚠ BREAKING CHANGES + +* most modules have been split out of the `@libp2p/interfaces` and `@libp2p/interface-compliance-tests` packages + +### Trivial Changes + +* break modules apart ([#232](https://github.com/libp2p/js-libp2p-interfaces/issues/232)) ([385614e](https://github.com/libp2p/js-libp2p-interfaces/commit/385614e772329052ab17415c8bd421f65b01a61b)), closes [#226](https://github.com/libp2p/js-libp2p-interfaces/issues/226) diff --git a/packages/interface-peer-info/LICENSE b/packages/interface-peer-info/LICENSE new file mode 100644 index 0000000000..20ce483c86 --- /dev/null +++ b/packages/interface-peer-info/LICENSE @@ -0,0 +1,4 @@ +This project is dual licensed under MIT and Apache-2.0. + +MIT: https://www.opensource.org/licenses/mit +Apache-2.0: https://www.apache.org/licenses/license-2.0 diff --git a/packages/interface-peer-info/LICENSE-APACHE b/packages/interface-peer-info/LICENSE-APACHE new file mode 100644 index 0000000000..14478a3b60 --- /dev/null +++ b/packages/interface-peer-info/LICENSE-APACHE @@ -0,0 +1,5 @@ +Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. diff --git a/packages/interface-peer-info/LICENSE-MIT b/packages/interface-peer-info/LICENSE-MIT new file mode 100644 index 0000000000..72dc60d84b --- /dev/null +++ b/packages/interface-peer-info/LICENSE-MIT @@ -0,0 +1,19 @@ +The MIT License (MIT) + +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/packages/interface-peer-info/README.md b/packages/interface-peer-info/README.md new file mode 100644 index 0000000000..32d57fa659 --- /dev/null +++ b/packages/interface-peer-info/README.md @@ -0,0 +1,36 @@ +# @libp2p/interface-peer-info + +[![libp2p.io](https://img.shields.io/badge/project-libp2p-yellow.svg?style=flat-square)](http://libp2p.io/) +[![Discuss](https://img.shields.io/discourse/https/discuss.libp2p.io/posts.svg?style=flat-square)](https://discuss.libp2p.io) +[![codecov](https://img.shields.io/codecov/c/github/libp2p/js-libp2p.svg?style=flat-square)](https://codecov.io/gh/libp2p/js-libp2p) +[![CI](https://img.shields.io/github/actions/workflow/status/libp2p/js-libp2p/js-test-and-release.yml?branch=master\&style=flat-square)](https://github.com/libp2p/js-libp2p/actions/workflows/js-test-and-release.yml?query=branch%3Amaster) + +> Peer Info interface for libp2p + +## Table of contents + +- [Install](#install) +- [API Docs](#api-docs) +- [License](#license) +- [Contribution](#contribution) + +## Install + +```console +$ npm i @libp2p/interface-peer-info +``` + +## API Docs + +- + +## License + +Licensed under either of + +- Apache 2.0, ([LICENSE-APACHE](LICENSE-APACHE) / ) +- MIT ([LICENSE-MIT](LICENSE-MIT) / ) + +## Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. diff --git a/packages/interface-peer-info/package.json b/packages/interface-peer-info/package.json new file mode 100644 index 0000000000..3916e089be --- /dev/null +++ b/packages/interface-peer-info/package.json @@ -0,0 +1,140 @@ +{ + "name": "@libp2p/interface-peer-info", + "version": "1.0.10", + "description": "Peer Info interface for libp2p", + "license": "Apache-2.0 OR MIT", + "homepage": "https://github.com/libp2p/js-libp2p/tree/master/packages/interface-peer-info#readme", + "repository": { + "type": "git", + "url": "git+https://github.com/libp2p/js-libp2p.git" + }, + "bugs": { + "url": "https://github.com/libp2p/js-libp2p/issues" + }, + "keywords": [ + "interface", + "libp2p" + ], + "type": "module", + "types": "./dist/src/index.d.ts", + "files": [ + "src", + "dist", + "!dist/test", + "!**/*.tsbuildinfo" + ], + "exports": { + ".": { + "types": "./dist/src/index.d.ts", + "import": "./dist/src/index.js" + } + }, + "eslintConfig": { + "extends": "ipfs", + "parserOptions": { + "sourceType": "module" + } + }, + "release": { + "branches": [ + "master" + ], + "plugins": [ + [ + "@semantic-release/commit-analyzer", + { + "preset": "conventionalcommits", + "releaseRules": [ + { + "breaking": true, + "release": "major" + }, + { + "revert": true, + "release": "patch" + }, + { + "type": "feat", + "release": "minor" + }, + { + "type": "fix", + "release": "patch" + }, + { + "type": "docs", + "release": "patch" + }, + { + "type": "test", + "release": "patch" + }, + { + "type": "deps", + "release": "patch" + }, + { + "scope": "no-release", + "release": false + } + ] + } + ], + [ + "@semantic-release/release-notes-generator", + { + "preset": "conventionalcommits", + "presetConfig": { + "types": [ + { + "type": "feat", + "section": "Features" + }, + { + "type": "fix", + "section": "Bug Fixes" + }, + { + "type": "chore", + "section": "Trivial Changes" + }, + { + "type": "docs", + "section": "Documentation" + }, + { + "type": "deps", + "section": "Dependencies" + }, + { + "type": "test", + "section": "Tests" + } + ] + } + } + ], + "@semantic-release/changelog", + "@semantic-release/npm", + "@semantic-release/github", + "@semantic-release/git" + ] + }, + "scripts": { + "clean": "aegir clean", + "lint": "aegir lint", + "dep-check": "aegir dep-check", + "build": "aegir build", + "release": "aegir release" + }, + "dependencies": { + "@libp2p/interface-peer-id": "^2.0.0", + "@multiformats/multiaddr": "^12.0.0" + }, + "devDependencies": { + "aegir": "^39.0.5" + }, + "typedoc": { + "entryPoint": "./src/index.ts" + } +} diff --git a/packages/interface-peer-info/src/index.ts b/packages/interface-peer-info/src/index.ts new file mode 100644 index 0000000000..59ae2b5d33 --- /dev/null +++ b/packages/interface-peer-info/src/index.ts @@ -0,0 +1,8 @@ +import type { PeerId } from '@libp2p/interface-peer-id' +import type { Multiaddr } from '@multiformats/multiaddr' + +export interface PeerInfo { + id: PeerId + multiaddrs: Multiaddr[] + protocols: string[] +} diff --git a/packages/interface-peer-info/tsconfig.json b/packages/interface-peer-info/tsconfig.json new file mode 100644 index 0000000000..d8db0b667f --- /dev/null +++ b/packages/interface-peer-info/tsconfig.json @@ -0,0 +1,14 @@ +{ + "extends": "aegir/src/config/tsconfig.aegir.json", + "compilerOptions": { + "outDir": "dist" + }, + "include": [ + "src" + ], + "references": [ + { + "path": "../interface-peer-id" + } + ] +} diff --git a/packages/interface-peer-routing/CHANGELOG.md b/packages/interface-peer-routing/CHANGELOG.md new file mode 100644 index 0000000000..a6d8da477d --- /dev/null +++ b/packages/interface-peer-routing/CHANGELOG.md @@ -0,0 +1,79 @@ +## [@libp2p/interface-peer-routing-v1.1.1](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-peer-routing-v1.1.0...@libp2p/interface-peer-routing-v1.1.1) (2023-05-04) + + +### Dependencies + +* bump aegir from 38.1.8 to 39.0.5 ([#393](https://github.com/libp2p/js-libp2p-interfaces/issues/393)) ([31f3797](https://github.com/libp2p/js-libp2p-interfaces/commit/31f3797b24f7c23f3f16e9db3a230bd5f7cd5175)) + +## [@libp2p/interface-peer-routing-v1.1.0](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-peer-routing-v1.0.8...@libp2p/interface-peer-routing-v1.1.0) (2023-04-27) + + +### Features + +* add routing symbols ([#388](https://github.com/libp2p/js-libp2p-interfaces/issues/388)) ([9ee7691](https://github.com/libp2p/js-libp2p-interfaces/commit/9ee76915d2b8298d99557e105c4f71d585e97e7d)) + +## [@libp2p/interface-peer-routing-v1.0.8](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-peer-routing-v1.0.7...@libp2p/interface-peer-routing-v1.0.8) (2023-03-09) + + +### Documentation + +* update content/peer routing interface comments ([#346](https://github.com/libp2p/js-libp2p-interfaces/issues/346)) ([8080944](https://github.com/libp2p/js-libp2p-interfaces/commit/8080944d3c3a81834c6b432843441996cd9e34e5)) + +## [@libp2p/interface-peer-routing-v1.0.7](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-peer-routing-v1.0.6...@libp2p/interface-peer-routing-v1.0.7) (2023-01-18) + + +### Trivial Changes + +* remove lerna ([#330](https://github.com/libp2p/js-libp2p-interfaces/issues/330)) ([6678592](https://github.com/libp2p/js-libp2p-interfaces/commit/6678592dd0cf601a2671852f9d2a0aff5dee2b18)) + + +### Dependencies + +* bump aegir from 37.12.1 to 38.1.0 ([#335](https://github.com/libp2p/js-libp2p-interfaces/issues/335)) ([7368a36](https://github.com/libp2p/js-libp2p-interfaces/commit/7368a363423a08e8fa247dcb76ea13e4cf030d65)) + +## [@libp2p/interface-peer-routing-v1.0.6](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-peer-routing-v1.0.5...@libp2p/interface-peer-routing-v1.0.6) (2023-01-06) + + +### Dependencies + +* update sibling dependencies ([b50e621](https://github.com/libp2p/js-libp2p-interfaces/commit/b50e621d31a8b32affc3fadb9f97c4883d577f93)) + +## [@libp2p/interface-peer-routing-v1.0.5](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-peer-routing-v1.0.4...@libp2p/interface-peer-routing-v1.0.5) (2022-12-19) + + +### Documentation + +* add interface docs ([#324](https://github.com/libp2p/js-libp2p-interfaces/issues/324)) ([2789445](https://github.com/libp2p/js-libp2p-interfaces/commit/278944594c24e1a3c4b3624a35680d69166546d7)) + +## [@libp2p/interface-peer-routing-v1.0.4](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-peer-routing-v1.0.3...@libp2p/interface-peer-routing-v1.0.4) (2022-12-16) + + +### Documentation + +* update project config ([#323](https://github.com/libp2p/js-libp2p-interfaces/issues/323)) ([0fc6a08](https://github.com/libp2p/js-libp2p-interfaces/commit/0fc6a08e9cdcefe361fe325281a3a2a03759ff59)) + +## [@libp2p/interface-peer-routing-v1.0.3](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-peer-routing-v1.0.2...@libp2p/interface-peer-routing-v1.0.3) (2022-12-14) + + +### Bug Fixes + +* generate docs for all packages ([#321](https://github.com/libp2p/js-libp2p-interfaces/issues/321)) ([b6f8b32](https://github.com/libp2p/js-libp2p-interfaces/commit/b6f8b32a920c15a28fe021e6050e31aaae89d518)) + +## [@libp2p/interface-peer-routing-v1.0.2](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-peer-routing-v1.0.1...@libp2p/interface-peer-routing-v1.0.2) (2022-11-05) + + +### Bug Fixes + +* update project config ([#311](https://github.com/libp2p/js-libp2p-interfaces/issues/311)) ([27dd0ce](https://github.com/libp2p/js-libp2p-interfaces/commit/27dd0ce3c249892ac69cbb24ddaf0b9f32385e37)) + + +### Trivial Changes + +* update project config ([#271](https://github.com/libp2p/js-libp2p-interfaces/issues/271)) ([59c0bf5](https://github.com/libp2p/js-libp2p-interfaces/commit/59c0bf5e0b05496fca2e4902632b61bb41fad9e9)) + +## [@libp2p/interface-peer-routing-v1.0.1](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-peer-routing-v1.0.0...@libp2p/interface-peer-routing-v1.0.1) (2022-06-27) + + +### Trivial Changes + +* update deps ([#262](https://github.com/libp2p/js-libp2p-interfaces/issues/262)) ([51edf7d](https://github.com/libp2p/js-libp2p-interfaces/commit/51edf7d9b3765a6f75c915b1483ea345d0133a41)) diff --git a/packages/interface-peer-routing/LICENSE b/packages/interface-peer-routing/LICENSE new file mode 100644 index 0000000000..20ce483c86 --- /dev/null +++ b/packages/interface-peer-routing/LICENSE @@ -0,0 +1,4 @@ +This project is dual licensed under MIT and Apache-2.0. + +MIT: https://www.opensource.org/licenses/mit +Apache-2.0: https://www.apache.org/licenses/license-2.0 diff --git a/packages/interface-peer-routing/LICENSE-APACHE b/packages/interface-peer-routing/LICENSE-APACHE new file mode 100644 index 0000000000..14478a3b60 --- /dev/null +++ b/packages/interface-peer-routing/LICENSE-APACHE @@ -0,0 +1,5 @@ +Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. diff --git a/packages/interface-peer-routing/LICENSE-MIT b/packages/interface-peer-routing/LICENSE-MIT new file mode 100644 index 0000000000..72dc60d84b --- /dev/null +++ b/packages/interface-peer-routing/LICENSE-MIT @@ -0,0 +1,19 @@ +The MIT License (MIT) + +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/packages/interface-peer-routing/README.md b/packages/interface-peer-routing/README.md new file mode 100644 index 0000000000..cfbeb3c47a --- /dev/null +++ b/packages/interface-peer-routing/README.md @@ -0,0 +1,83 @@ +# @libp2p/interface-peer-routing + +[![libp2p.io](https://img.shields.io/badge/project-libp2p-yellow.svg?style=flat-square)](http://libp2p.io/) +[![Discuss](https://img.shields.io/discourse/https/discuss.libp2p.io/posts.svg?style=flat-square)](https://discuss.libp2p.io) +[![codecov](https://img.shields.io/codecov/c/github/libp2p/js-libp2p.svg?style=flat-square)](https://codecov.io/gh/libp2p/js-libp2p) +[![CI](https://img.shields.io/github/actions/workflow/status/libp2p/js-libp2p/js-test-and-release.yml?branch=master\&style=flat-square)](https://github.com/libp2p/js-libp2p/actions/workflows/js-test-and-release.yml?query=branch%3Amaster) + +> Peer Routing interface for libp2p + +## Table of contents + +- - [Install](#install) +- [Modules that implement the interface](#modules-that-implement-the-interface) +- [Badge](#badge) +- [How to use the battery of tests](#how-to-use-the-battery-of-tests) + - [Node.js](#nodejs) +- [API](#api) + - - [findPeer](#findpeer) + - [API Docs](#api-docs) + - [License](#license) + - [Contribution](#contribution) + +## Install + +```console +$ npm i @libp2p/interface-peer-routing +``` + +The primary goal of this module is to enable developers to pick and swap their Peer Routing module as they see fit for their libp2p installation, without having to go through shims or compatibility issues. This module and test suite were heavily inspired by abstract-blob-store and interface-stream-muxer. + +Publishing a test suite as a module lets multiple modules all ensure compatibility since they use the same test suite. + +# Modules that implement the interface + +- [JavaScript libp2p-kad-dht](https://github.com/libp2p/js-libp2p-kad-dht) +- [JavaScript libp2p-delegated-peer-routing](https://github.com/libp2p/js-libp2p-delegated-peer-routing) + +# Badge + +Include this badge in your readme if you make a module that is compatible with the interface-record-store API. You can validate this by running the tests. + +![](img/badge.png) + +# How to use the battery of tests + +## Node.js + +TBD + +# API + +A valid (read: that follows this abstraction) Peer Routing module must implement the following API. + +### findPeer + +- `findPeer(peerId)` + +Query the network for all multiaddresses associated with a `PeerId`. + +**Parameters** + +- [peerId](https://github.com/libp2p/js-peer-id). + +**Returns** + +It returns the [peerId](https://github.com/libp2p/js-peer-id) together with the known peers [multiaddrs](https://github.com/multiformats/js-multiaddr), as follows: + +`Promise<{ id: PeerId, multiaddrs: Multiaddr[] }>` + +## API Docs + +- + +## License + +Licensed under either of + +- Apache 2.0, ([LICENSE-APACHE](LICENSE-APACHE) / ) +- MIT ([LICENSE-MIT](LICENSE-MIT) / ) + +## Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. diff --git a/packages/interface-peer-routing/img/badge.png b/packages/interface-peer-routing/img/badge.png new file mode 100644 index 0000000000000000000000000000000000000000..44127db9ee0a1786675bc5f85fb4f94d247ea118 GIT binary patch literal 7070 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�rEB5(lD;hZ!LYbm@=c9-j)Z2O5=eVYvZNpIQ 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 Ar2qf` literal 0 HcmV?d00001 diff --git a/packages/interface-peer-routing/img/badge.svg b/packages/interface-peer-routing/img/badge.svg new file mode 100644 index 0000000000..f3c41b0470 --- /dev/null +++ b/packages/interface-peer-routing/img/badge.svg @@ -0,0 +1,19 @@ + + + + badge + Created with Sketch. + + + + + Peer Routin + g + + + Compatibl + e + + + + \ No newline at end of file diff --git a/packages/interface-peer-routing/package.json b/packages/interface-peer-routing/package.json new file mode 100644 index 0000000000..c7e022b1bd --- /dev/null +++ b/packages/interface-peer-routing/package.json @@ -0,0 +1,141 @@ +{ + "name": "@libp2p/interface-peer-routing", + "version": "1.1.1", + "description": "Peer Routing interface for libp2p", + "license": "Apache-2.0 OR MIT", + "homepage": "https://github.com/libp2p/js-libp2p/tree/master/packages/interface-peer-routing#readme", + "repository": { + "type": "git", + "url": "git+https://github.com/libp2p/js-libp2p.git" + }, + "bugs": { + "url": "https://github.com/libp2p/js-libp2p/issues" + }, + "keywords": [ + "interface", + "libp2p" + ], + "type": "module", + "types": "./dist/src/index.d.ts", + "files": [ + "src", + "dist", + "!dist/test", + "!**/*.tsbuildinfo" + ], + "exports": { + ".": { + "types": "./dist/src/index.d.ts", + "import": "./dist/src/index.js" + } + }, + "eslintConfig": { + "extends": "ipfs", + "parserOptions": { + "sourceType": "module" + } + }, + "release": { + "branches": [ + "master" + ], + "plugins": [ + [ + "@semantic-release/commit-analyzer", + { + "preset": "conventionalcommits", + "releaseRules": [ + { + "breaking": true, + "release": "major" + }, + { + "revert": true, + "release": "patch" + }, + { + "type": "feat", + "release": "minor" + }, + { + "type": "fix", + "release": "patch" + }, + { + "type": "docs", + "release": "patch" + }, + { + "type": "test", + "release": "patch" + }, + { + "type": "deps", + "release": "patch" + }, + { + "scope": "no-release", + "release": false + } + ] + } + ], + [ + "@semantic-release/release-notes-generator", + { + "preset": "conventionalcommits", + "presetConfig": { + "types": [ + { + "type": "feat", + "section": "Features" + }, + { + "type": "fix", + "section": "Bug Fixes" + }, + { + "type": "chore", + "section": "Trivial Changes" + }, + { + "type": "docs", + "section": "Documentation" + }, + { + "type": "deps", + "section": "Dependencies" + }, + { + "type": "test", + "section": "Tests" + } + ] + } + } + ], + "@semantic-release/changelog", + "@semantic-release/npm", + "@semantic-release/github", + "@semantic-release/git" + ] + }, + "scripts": { + "clean": "aegir clean", + "lint": "aegir lint", + "dep-check": "aegir dep-check", + "build": "aegir build", + "release": "aegir release" + }, + "dependencies": { + "@libp2p/interface-peer-id": "^2.0.0", + "@libp2p/interface-peer-info": "^1.0.0", + "@libp2p/interfaces": "^3.0.0" + }, + "devDependencies": { + "aegir": "^39.0.5" + }, + "typedoc": { + "entryPoint": "./src/index.ts" + } +} diff --git a/packages/interface-peer-routing/src/index.ts b/packages/interface-peer-routing/src/index.ts new file mode 100644 index 0000000000..d4bbef25c8 --- /dev/null +++ b/packages/interface-peer-routing/src/index.ts @@ -0,0 +1,53 @@ +import type { PeerId } from '@libp2p/interface-peer-id' +import type { PeerInfo } from '@libp2p/interface-peer-info' +import type { AbortOptions } from '@libp2p/interfaces' + +/** + * Any object that implements this Symbol as a property should return a + * PeerRouting instance as the property value, similar to how + * `Symbol.Iterable` can be used to return an `Iterable` from an `Iterator`. + * + * @example + * + * ```js + * import { peerRouting, PeerRouting } from '@libp2p/peer-routing' + * + * class MyPeerRouter implements PeerRouting { + * get [peerRouting] () { + * return this + * } + * + * // ...other methods + * } + * ``` + */ +export const peerRouting = Symbol.for('@libp2p/peer-routing') + +export interface PeerRouting { + /** + * Searches the network for peer info corresponding to the passed peer id. + * + * @example + * + * ```js + * // ... + * const peer = await peerRouting.findPeer(peerId, options) + * ``` + */ + findPeer: (peerId: PeerId, options?: AbortOptions) => Promise + + /** + * Search the network for peers that are closer to the passed key. Peer + * info should be yielded in ever-increasing closeness to the key. + * + * @example + * + * ```js + * // Iterate over the closest peers found for the given key + * for await (const peer of peerRouting.getClosestPeers(key)) { + * console.log(peer.id, peer.multiaddrs) + * } + * ``` + */ + getClosestPeers: (key: Uint8Array, options?: AbortOptions) => AsyncIterable +} diff --git a/packages/interface-peer-routing/tsconfig.json b/packages/interface-peer-routing/tsconfig.json new file mode 100644 index 0000000000..02d375dbd5 --- /dev/null +++ b/packages/interface-peer-routing/tsconfig.json @@ -0,0 +1,20 @@ +{ + "extends": "aegir/src/config/tsconfig.aegir.json", + "compilerOptions": { + "outDir": "dist" + }, + "include": [ + "src" + ], + "references": [ + { + "path": "../interface-peer-id" + }, + { + "path": "../interface-peer-info" + }, + { + "path": "../interfaces" + } + ] +} diff --git a/packages/interface-peer-store/CHANGELOG.md b/packages/interface-peer-store/CHANGELOG.md new file mode 100644 index 0000000000..d9858e5e11 --- /dev/null +++ b/packages/interface-peer-store/CHANGELOG.md @@ -0,0 +1,136 @@ +## [@libp2p/interface-peer-store-v2.0.4](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-peer-store-v2.0.3...@libp2p/interface-peer-store-v2.0.4) (2023-06-11) + + +### Bug Fixes + +* add peer store query interfaces ([#412](https://github.com/libp2p/js-libp2p-interfaces/issues/412)) ([0247215](https://github.com/libp2p/js-libp2p-interfaces/commit/0247215b8132096884af22499d8a6828281861d0)) + +## [@libp2p/interface-peer-store-v2.0.3](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-peer-store-v2.0.2...@libp2p/interface-peer-store-v2.0.3) (2023-05-10) + + +### Bug Fixes + +* expose peerstore consume peer record method ([#398](https://github.com/libp2p/js-libp2p-interfaces/issues/398)) ([80222b8](https://github.com/libp2p/js-libp2p-interfaces/commit/80222b8474396aaa070fb44d859965ab14080f48)) + + +### Trivial Changes + +* correct the peerStore.save doc ([#397](https://github.com/libp2p/js-libp2p-interfaces/issues/397)) ([6998722](https://github.com/libp2p/js-libp2p-interfaces/commit/69987220a1038318d3798260af517873a30c838f)) + +## [@libp2p/interface-peer-store-v2.0.2](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-peer-store-v2.0.1...@libp2p/interface-peer-store-v2.0.2) (2023-05-04) + + +### Dependencies + +* bump aegir from 38.1.8 to 39.0.5 ([#393](https://github.com/libp2p/js-libp2p-interfaces/issues/393)) ([31f3797](https://github.com/libp2p/js-libp2p-interfaces/commit/31f3797b24f7c23f3f16e9db3a230bd5f7cd5175)) + +## [@libp2p/interface-peer-store-v2.0.1](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-peer-store-v2.0.0...@libp2p/interface-peer-store-v2.0.1) (2023-04-24) + + +### Bug Fixes + +* allow deleting tags/metadata with undefined fields ([#384](https://github.com/libp2p/js-libp2p-interfaces/issues/384)) ([64598ec](https://github.com/libp2p/js-libp2p-interfaces/commit/64598ec27495d4de5c05b573fd2f3f902166b596)) + +## [@libp2p/interface-peer-store-v2.0.0](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-peer-store-v1.2.9...@libp2p/interface-peer-store-v2.0.0) (2023-04-21) + + +### ⚠ BREAKING CHANGES + +* make peer store methods atomic (#368) + +### Bug Fixes + +* make peer store methods atomic ([#368](https://github.com/libp2p/js-libp2p-interfaces/issues/368)) ([47c8b78](https://github.com/libp2p/js-libp2p-interfaces/commit/47c8b78e72fbbf9548d88a5fe0df965444254708)) + +## [@libp2p/interface-peer-store-v1.2.9](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-peer-store-v1.2.8...@libp2p/interface-peer-store-v1.2.9) (2023-03-17) + + +### Dependencies + +* update @multiformats/multiaddr to 12.0.0 ([#354](https://github.com/libp2p/js-libp2p-interfaces/issues/354)) ([e0f327b](https://github.com/libp2p/js-libp2p-interfaces/commit/e0f327b5d54e240feabadce21a841629d633ec5e)) + +## [@libp2p/interface-peer-store-v1.2.8](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-peer-store-v1.2.7...@libp2p/interface-peer-store-v1.2.8) (2023-01-18) + + +### Trivial Changes + +* remove lerna ([#330](https://github.com/libp2p/js-libp2p-interfaces/issues/330)) ([6678592](https://github.com/libp2p/js-libp2p-interfaces/commit/6678592dd0cf601a2671852f9d2a0aff5dee2b18)) + + +### Dependencies + +* bump aegir from 37.12.1 to 38.1.0 ([#335](https://github.com/libp2p/js-libp2p-interfaces/issues/335)) ([7368a36](https://github.com/libp2p/js-libp2p-interfaces/commit/7368a363423a08e8fa247dcb76ea13e4cf030d65)) + +## [@libp2p/interface-peer-store-v1.2.7](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-peer-store-v1.2.6...@libp2p/interface-peer-store-v1.2.7) (2023-01-06) + + +### Dependencies + +* update sibling dependencies ([b50e621](https://github.com/libp2p/js-libp2p-interfaces/commit/b50e621d31a8b32affc3fadb9f97c4883d577f93)) + +## [@libp2p/interface-peer-store-v1.2.6](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-peer-store-v1.2.5...@libp2p/interface-peer-store-v1.2.6) (2022-12-19) + + +### Documentation + +* add interface docs ([#324](https://github.com/libp2p/js-libp2p-interfaces/issues/324)) ([2789445](https://github.com/libp2p/js-libp2p-interfaces/commit/278944594c24e1a3c4b3624a35680d69166546d7)) + +## [@libp2p/interface-peer-store-v1.2.5](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-peer-store-v1.2.4...@libp2p/interface-peer-store-v1.2.5) (2022-12-16) + + +### Documentation + +* update project config ([#323](https://github.com/libp2p/js-libp2p-interfaces/issues/323)) ([0fc6a08](https://github.com/libp2p/js-libp2p-interfaces/commit/0fc6a08e9cdcefe361fe325281a3a2a03759ff59)) + +## [@libp2p/interface-peer-store-v1.2.4](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-peer-store-v1.2.3...@libp2p/interface-peer-store-v1.2.4) (2022-12-14) + + +### Bug Fixes + +* generate docs for all packages ([#321](https://github.com/libp2p/js-libp2p-interfaces/issues/321)) ([b6f8b32](https://github.com/libp2p/js-libp2p-interfaces/commit/b6f8b32a920c15a28fe021e6050e31aaae89d518)) + +## [@libp2p/interface-peer-store-v1.2.3](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-peer-store-v1.2.2...@libp2p/interface-peer-store-v1.2.3) (2022-11-05) + + +### Bug Fixes + +* update project config ([#311](https://github.com/libp2p/js-libp2p-interfaces/issues/311)) ([27dd0ce](https://github.com/libp2p/js-libp2p-interfaces/commit/27dd0ce3c249892ac69cbb24ddaf0b9f32385e37)) + +## [@libp2p/interface-peer-store-v1.2.2](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-peer-store-v1.2.1...@libp2p/interface-peer-store-v1.2.2) (2022-09-21) + + +### Dependencies + +* update @multiformats/multiaddr to 11.0.0 ([#289](https://github.com/libp2p/js-libp2p-interfaces/issues/289)) ([81daf98](https://github.com/libp2p/js-libp2p-interfaces/commit/81daf9803a952cc8241c0956272b7f5625088636)) + +## [@libp2p/interface-peer-store-v1.2.1](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-peer-store-v1.2.0...@libp2p/interface-peer-store-v1.2.1) (2022-08-03) + + +### Bug Fixes + +* update peer store interface deps ([#277](https://github.com/libp2p/js-libp2p-interfaces/issues/277)) ([9945e16](https://github.com/libp2p/js-libp2p-interfaces/commit/9945e16f1653f22ad190be19fc6038378ae6d0d2)) + + +### Trivial Changes + +* update project config ([#271](https://github.com/libp2p/js-libp2p-interfaces/issues/271)) ([59c0bf5](https://github.com/libp2p/js-libp2p-interfaces/commit/59c0bf5e0b05496fca2e4902632b61bb41fad9e9)) +* update sibling dependencies [skip ci] ([39eed35](https://github.com/libp2p/js-libp2p-interfaces/commit/39eed35c17920032ef821eede4d09fe14f8b30ab)) + +## [@libp2p/interface-peer-store-v1.2.0](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-peer-store-v1.1.0...@libp2p/interface-peer-store-v1.2.0) (2022-06-27) + + +### Features + +* export common tags ([#263](https://github.com/libp2p/js-libp2p-interfaces/issues/263)) ([8ca626e](https://github.com/libp2p/js-libp2p-interfaces/commit/8ca626e0b39f943244bb5ba005b84e2155d471fd)) + +## [@libp2p/interface-peer-store-v1.1.0](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-peer-store-v1.0.0...@libp2p/interface-peer-store-v1.1.0) (2022-06-24) + + +### Features + +* add peer tagging ([#255](https://github.com/libp2p/js-libp2p-interfaces/issues/255)) ([80dadd9](https://github.com/libp2p/js-libp2p-interfaces/commit/80dadd99dc593ce43141a0072a4697339d01b222)) + + +### Trivial Changes + +* update aegir ([b0bcff9](https://github.com/libp2p/js-libp2p-interfaces/commit/b0bcff92f59aed4bb61dd4b67facc9a077de9ea6)) diff --git a/packages/interface-peer-store/LICENSE b/packages/interface-peer-store/LICENSE new file mode 100644 index 0000000000..20ce483c86 --- /dev/null +++ b/packages/interface-peer-store/LICENSE @@ -0,0 +1,4 @@ +This project is dual licensed under MIT and Apache-2.0. + +MIT: https://www.opensource.org/licenses/mit +Apache-2.0: https://www.apache.org/licenses/license-2.0 diff --git a/packages/interface-peer-store/LICENSE-APACHE b/packages/interface-peer-store/LICENSE-APACHE new file mode 100644 index 0000000000..14478a3b60 --- /dev/null +++ b/packages/interface-peer-store/LICENSE-APACHE @@ -0,0 +1,5 @@ +Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. diff --git a/packages/interface-peer-store/LICENSE-MIT b/packages/interface-peer-store/LICENSE-MIT new file mode 100644 index 0000000000..72dc60d84b --- /dev/null +++ b/packages/interface-peer-store/LICENSE-MIT @@ -0,0 +1,19 @@ +The MIT License (MIT) + +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/packages/interface-peer-store/README.md b/packages/interface-peer-store/README.md new file mode 100644 index 0000000000..ac9d9b048e --- /dev/null +++ b/packages/interface-peer-store/README.md @@ -0,0 +1,50 @@ +# @libp2p/interface-peer-store + +[![libp2p.io](https://img.shields.io/badge/project-libp2p-yellow.svg?style=flat-square)](http://libp2p.io/) +[![Discuss](https://img.shields.io/discourse/https/discuss.libp2p.io/posts.svg?style=flat-square)](https://discuss.libp2p.io) +[![codecov](https://img.shields.io/codecov/c/github/libp2p/js-libp2p.svg?style=flat-square)](https://codecov.io/gh/libp2p/js-libp2p) +[![CI](https://img.shields.io/github/actions/workflow/status/libp2p/js-libp2p/js-test-and-release.yml?branch=master\&style=flat-square)](https://github.com/libp2p/js-libp2p/actions/workflows/js-test-and-release.yml?query=branch%3Amaster) + +> Peer Store interface for libp2p + +## Table of contents + +- [Install](#install) +- [Usage](#usage) + - [Tags](#tags) +- [API Docs](#api-docs) +- [License](#license) +- [Contribution](#contribution) + +## Install + +```console +$ npm i @libp2p/interface-peer-store +``` + +## Usage + +### Tags + +Common tags can be imported from the `@libp2p/interface-peer-store/tag` module: + +```js +import { KEEP_ALIVE } from '@libp2p/interface-peer-store/tags' + +await peerStore.tagPeer(peerId, KEEP_ALIVE) +``` + +## API Docs + +- + +## License + +Licensed under either of + +- Apache 2.0, ([LICENSE-APACHE](LICENSE-APACHE) / ) +- MIT ([LICENSE-MIT](LICENSE-MIT) / ) + +## Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. diff --git a/packages/interface-peer-store/package.json b/packages/interface-peer-store/package.json new file mode 100644 index 0000000000..fe9d8f1e64 --- /dev/null +++ b/packages/interface-peer-store/package.json @@ -0,0 +1,160 @@ +{ + "name": "@libp2p/interface-peer-store", + "version": "2.0.4", + "description": "Peer Store interface for libp2p", + "license": "Apache-2.0 OR MIT", + "homepage": "https://github.com/libp2p/js-libp2p/tree/master/packages/interface-peer-store#readme", + "repository": { + "type": "git", + "url": "git+https://github.com/libp2p/js-libp2p.git" + }, + "bugs": { + "url": "https://github.com/libp2p/js-libp2p/issues" + }, + "keywords": [ + "interface", + "libp2p" + ], + "type": "module", + "types": "./dist/src/index.d.ts", + "typesVersions": { + "*": { + "*": [ + "*", + "dist/*", + "dist/src/*", + "dist/src/*/index" + ], + "src/*": [ + "*", + "dist/*", + "dist/src/*", + "dist/src/*/index" + ] + } + }, + "files": [ + "src", + "dist", + "!dist/test", + "!**/*.tsbuildinfo" + ], + "exports": { + ".": { + "types": "./dist/src/index.d.ts", + "import": "./dist/src/index.js" + }, + "./tags": { + "types": "./dist/src/tags.d.ts", + "import": "./dist/src/tags.js" + } + }, + "eslintConfig": { + "extends": "ipfs", + "parserOptions": { + "sourceType": "module" + } + }, + "release": { + "branches": [ + "master" + ], + "plugins": [ + [ + "@semantic-release/commit-analyzer", + { + "preset": "conventionalcommits", + "releaseRules": [ + { + "breaking": true, + "release": "major" + }, + { + "revert": true, + "release": "patch" + }, + { + "type": "feat", + "release": "minor" + }, + { + "type": "fix", + "release": "patch" + }, + { + "type": "docs", + "release": "patch" + }, + { + "type": "test", + "release": "patch" + }, + { + "type": "deps", + "release": "patch" + }, + { + "scope": "no-release", + "release": false + } + ] + } + ], + [ + "@semantic-release/release-notes-generator", + { + "preset": "conventionalcommits", + "presetConfig": { + "types": [ + { + "type": "feat", + "section": "Features" + }, + { + "type": "fix", + "section": "Bug Fixes" + }, + { + "type": "chore", + "section": "Trivial Changes" + }, + { + "type": "docs", + "section": "Documentation" + }, + { + "type": "deps", + "section": "Dependencies" + }, + { + "type": "test", + "section": "Tests" + } + ] + } + } + ], + "@semantic-release/changelog", + "@semantic-release/npm", + "@semantic-release/github", + "@semantic-release/git" + ] + }, + "scripts": { + "clean": "aegir clean", + "lint": "aegir lint", + "dep-check": "aegir dep-check", + "build": "aegir build", + "release": "aegir release" + }, + "dependencies": { + "@libp2p/interface-peer-id": "^2.0.0", + "@multiformats/multiaddr": "^12.0.0" + }, + "devDependencies": { + "aegir": "^39.0.5" + }, + "typedoc": { + "entryPoint": "./src/index.ts" + } +} diff --git a/packages/interface-peer-store/src/index.ts b/packages/interface-peer-store/src/index.ts new file mode 100644 index 0000000000..7350ccc448 --- /dev/null +++ b/packages/interface-peer-store/src/index.ts @@ -0,0 +1,272 @@ +import type { PeerId } from '@libp2p/interface-peer-id' +import type { Multiaddr } from '@multiformats/multiaddr' + +/** + * A multiaddr with an optional flag that indicates if its trustworthy + */ +export interface Address { + /** + * Peer multiaddr + */ + multiaddr: Multiaddr + + /** + * Obtained from a signed peer record + */ + isCertified: boolean +} + +/** + * Data stored in the peer store about peers + */ +export interface Peer { + /** + * Peer's peer-id instance + */ + id: PeerId + + /** + * Peer's addresses containing a list of multiaddrs and a isCertified field + * indicating if the address was loaded from a signed peer record or not + */ + addresses: Address[] + + /** + * Peer's supported protocols + */ + protocols: string[] + + /** + * Peer's metadata map + */ + metadata: Map + + /** + * Tags a peer has + */ + tags: Map + + /** + * The last peer record envelope received + */ + peerRecordEnvelope?: Uint8Array +} + +/** + * Peer data used to update the peer store + */ +export interface PeerData { + /** + * Peer's addresses containing its multiaddrs and metadata - multiaddrs + * passed here can be treated as certified if the `isCertifed` value is + * set to true. + * + * If both addresses and multiaddrs are specified they will be merged + * together with entries in addresses taking precedence. + */ + addresses?: Address[] + + /** + * Peer's multiaddrs - any multiaddrs passed here will be treated as + * uncertified. + * + * If both addresses and multiaddrs are specified they will be merged + * together with entries in addresses taking precedence. + */ + multiaddrs?: Multiaddr[] + + /** + * Peer's supported protocols + */ + protocols?: string[] + + /** + * Peer's metadata map. When merging pass undefined as values to remove metadata. + */ + metadata?: Map | Record + + /** + * Peer tags. When merging pass undefined as values to remove tags. + */ + tags?: Map | Record + + /** + * If this Peer has an RSA key, it's public key can be set with this property + */ + publicKey?: Uint8Array + + /** + * The last peer record envelope received + */ + peerRecordEnvelope?: Uint8Array +} + +export interface TagOptions { + /** + * An optional tag value (1-100) + */ + value?: number + + /** + * An optional duration in ms after which the tag will expire + */ + ttl?: number +} + +export interface Tag { + /** + * The tag value + */ + value: number +} + +/** + * A predicate by which to filter lists of peers + */ +export interface PeerQueryFilter { (peer: Peer): boolean } + +/** + * A predicate by which to sort lists of peers + */ +export interface PeerQueryOrder { (a: Peer, b: Peer): -1 | 0 | 1 } + +/** + * A query for getting lists of peers + */ +export interface PeerQuery { + filters?: PeerQueryFilter[] + orders?: PeerQueryOrder[] + limit?: number + offset?: number +} + +export interface PeerStore { + /** + * Loop over every peer - the looping is async because we read from a + * datastore but the peer operation is sync, this is to prevent + * long-lived peer operations causing deadlocks over the datastore + * which can happen if they try to access the peer store during the + * loop + * + * @example + * + * ```js + * await peerStore.forEach(peer => { + * // ... + * }) + * ``` + */ + forEach: (fn: (peer: Peer) => void, query?: PeerQuery) => Promise + + /** + * Returns all peers in the peer store. + * + * @example + * + * ```js + * for (const peer of await peerStore.all()) { + * // ... + * } + * ``` + */ + all: (query?: PeerQuery) => Promise + + /** + * Delete all data stored for the passed peer + * + * @example + * + * ```js + * await peerStore.addressBook.set(peerId, multiaddrs) + * await peerStore.addressBook.get(peerId) + * // multiaddrs[] + * + * await peerStore.delete(peerId) + * + * await peerStore.addressBook.get(peerId) + * // [] + * ``` + */ + delete: (peerId: PeerId) => Promise + + /** + * Returns true if the passed PeerId is in the peer store + * + * @example + * + * ```js + * await peerStore.has(peerId) + * // false + * await peerStore.addressBook.add(peerId, multiaddrs) + * await peerStore.has(peerId) + * // true + * ``` + */ + has: (peerId: PeerId) => Promise + + /** + * Returns all data stored for the passed PeerId + * + * @example + * + * ```js + * const peer = await peerStore.get(peerId) + * // { .. } + * ``` + */ + get: (peerId: PeerId) => Promise + + /** + * Adds a peer to the peer store, overwriting any existing data + * + * @example + * + * ```js + * await peerStore.save(peerId, { + * multiaddrs + * }) + * ``` + */ + save: (id: PeerId, data: PeerData) => Promise + + /** + * Adds a peer to the peer store, overwriting only the passed fields + * + * @example + * + * ```js + * await peerStore.patch(peerId, { + * multiaddrs + * }) + * ``` + */ + patch: (id: PeerId, data: PeerData) => Promise + + /** + * Adds a peer to the peer store, deeply merging any existing data. + * + * @example + * + * ```js + * await peerStore.merge(peerId, { + * multiaddrs + * }) + * ``` + */ + merge: (id: PeerId, data: PeerData) => Promise + + /** + * Unmarshal and verify a signed peer record, extract the multiaddrs and + * overwrite the stored addresses for the peer. + * + * Optionally pass an expected PeerId to verify that the peer record was + * signed by that peer. + * + * @example + * + * ```js + * await peerStore.consumePeerRecord(buf, expectedPeer) + * ``` + */ + consumePeerRecord: (buf: Uint8Array, expectedPeer?: PeerId) => Promise +} diff --git a/packages/interface-peer-store/src/tags.ts b/packages/interface-peer-store/src/tags.ts new file mode 100644 index 0000000000..121f6a0e96 --- /dev/null +++ b/packages/interface-peer-store/src/tags.ts @@ -0,0 +1,2 @@ + +export const KEEP_ALIVE = 'keep-alive' diff --git a/packages/interface-peer-store/tsconfig.json b/packages/interface-peer-store/tsconfig.json new file mode 100644 index 0000000000..d8db0b667f --- /dev/null +++ b/packages/interface-peer-store/tsconfig.json @@ -0,0 +1,14 @@ +{ + "extends": "aegir/src/config/tsconfig.aegir.json", + "compilerOptions": { + "outDir": "dist" + }, + "include": [ + "src" + ], + "references": [ + { + "path": "../interface-peer-id" + } + ] +} diff --git a/packages/interface-pubsub-compliance-tests/CHANGELOG.md b/packages/interface-pubsub-compliance-tests/CHANGELOG.md new file mode 100644 index 0000000000..3b82510891 --- /dev/null +++ b/packages/interface-pubsub-compliance-tests/CHANGELOG.md @@ -0,0 +1,225 @@ +## [@libp2p/interface-pubsub-compliance-tests-v5.0.7](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-pubsub-compliance-tests-v5.0.6...@libp2p/interface-pubsub-compliance-tests-v5.0.7) (2023-05-04) + + +### Dependencies + +* update sibling dependencies ([eae5fe0](https://github.com/libp2p/js-libp2p-interfaces/commit/eae5fe02ea11c2930242a8d91ee4bc22f9bebc5c)) + +## [@libp2p/interface-pubsub-compliance-tests-v5.0.6](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-pubsub-compliance-tests-v5.0.5...@libp2p/interface-pubsub-compliance-tests-v5.0.6) (2023-05-04) + + +### Dependencies + +* bump aegir from 38.1.8 to 39.0.5 ([#393](https://github.com/libp2p/js-libp2p-interfaces/issues/393)) ([31f3797](https://github.com/libp2p/js-libp2p-interfaces/commit/31f3797b24f7c23f3f16e9db3a230bd5f7cd5175)) + +## [@libp2p/interface-pubsub-compliance-tests-v5.0.5](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-pubsub-compliance-tests-v5.0.4...@libp2p/interface-pubsub-compliance-tests-v5.0.5) (2023-04-21) + + +### Dependencies + +* update sibling dependencies ([74f82d5](https://github.com/libp2p/js-libp2p-interfaces/commit/74f82d53fc89740f4bafa22721a59ab70c3c92a8)) +* update sibling dependencies ([6c18790](https://github.com/libp2p/js-libp2p-interfaces/commit/6c18790f6178053c69a8cd6bd289fd749d4e9633)) + +## [@libp2p/interface-pubsub-compliance-tests-v5.0.4](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-pubsub-compliance-tests-v5.0.3...@libp2p/interface-pubsub-compliance-tests-v5.0.4) (2023-04-18) + + +### Dependencies + +* update sibling dependencies ([294d970](https://github.com/libp2p/js-libp2p-interfaces/commit/294d970d6e4fbbf6a3f0944394c4c8dea06d1265)) +* update sibling dependencies ([3d23367](https://github.com/libp2p/js-libp2p-interfaces/commit/3d233676a17299bfa1b59d309543598176826523)) + +## [@libp2p/interface-pubsub-compliance-tests-v5.0.3](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-pubsub-compliance-tests-v5.0.2...@libp2p/interface-pubsub-compliance-tests-v5.0.3) (2023-04-14) + + +### Dependencies + +* update sibling dependencies ([34b1627](https://github.com/libp2p/js-libp2p-interfaces/commit/34b1627458b2ada5e94e00cf4bcba41a77232090)) + +## [@libp2p/interface-pubsub-compliance-tests-v5.0.2](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-pubsub-compliance-tests-v5.0.1...@libp2p/interface-pubsub-compliance-tests-v5.0.2) (2023-03-10) + + +### Bug Fixes + +* reject promise with error not string ([#350](https://github.com/libp2p/js-libp2p-interfaces/issues/350)) ([9435da9](https://github.com/libp2p/js-libp2p-interfaces/commit/9435da9a4015757b7945fdd55d142c4bd1950c59)) + +## [@libp2p/interface-pubsub-compliance-tests-v5.0.1](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-pubsub-compliance-tests-v5.0.0...@libp2p/interface-pubsub-compliance-tests-v5.0.1) (2023-01-18) + + +### Trivial Changes + +* remove lerna ([#330](https://github.com/libp2p/js-libp2p-interfaces/issues/330)) ([6678592](https://github.com/libp2p/js-libp2p-interfaces/commit/6678592dd0cf601a2671852f9d2a0aff5dee2b18)) + + +### Dependencies + +* bump aegir from 37.12.1 to 38.1.0 ([#335](https://github.com/libp2p/js-libp2p-interfaces/issues/335)) ([7368a36](https://github.com/libp2p/js-libp2p-interfaces/commit/7368a363423a08e8fa247dcb76ea13e4cf030d65)) + +## [@libp2p/interface-pubsub-compliance-tests-v5.0.0](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-pubsub-compliance-tests-v4.0.4...@libp2p/interface-pubsub-compliance-tests-v5.0.0) (2023-01-06) + + +### ⚠ BREAKING CHANGES + +* update peer-id dep to pull in new multiformats (#331) + +### Bug Fixes + +* update peer-id dep to pull in new multiformats ([#331](https://github.com/libp2p/js-libp2p-interfaces/issues/331)) ([fb8b7ba](https://github.com/libp2p/js-libp2p-interfaces/commit/fb8b7ba654a30a08da0652e2833e36dd3bb85e90)) + + +### Dependencies + +* update sibling dependencies ([1442ad3](https://github.com/libp2p/js-libp2p-interfaces/commit/1442ad37e44f886a423e7a09e53e0b1796327fde)) + +## [@libp2p/interface-pubsub-compliance-tests-v4.0.4](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-pubsub-compliance-tests-v4.0.3...@libp2p/interface-pubsub-compliance-tests-v4.0.4) (2023-01-06) + + +### Dependencies + +* update sibling dependencies ([b50e621](https://github.com/libp2p/js-libp2p-interfaces/commit/b50e621d31a8b32affc3fadb9f97c4883d577f93)) + +## [@libp2p/interface-pubsub-compliance-tests-v4.0.3](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-pubsub-compliance-tests-v4.0.2...@libp2p/interface-pubsub-compliance-tests-v4.0.3) (2022-12-16) + + +### Documentation + +* update project config ([#323](https://github.com/libp2p/js-libp2p-interfaces/issues/323)) ([0fc6a08](https://github.com/libp2p/js-libp2p-interfaces/commit/0fc6a08e9cdcefe361fe325281a3a2a03759ff59)) + +## [@libp2p/interface-pubsub-compliance-tests-v4.0.2](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-pubsub-compliance-tests-v4.0.1...@libp2p/interface-pubsub-compliance-tests-v4.0.2) (2022-12-14) + + +### Bug Fixes + +* generate docs for all packages ([#321](https://github.com/libp2p/js-libp2p-interfaces/issues/321)) ([b6f8b32](https://github.com/libp2p/js-libp2p-interfaces/commit/b6f8b32a920c15a28fe021e6050e31aaae89d518)) + + +### Dependencies + +* bump sinon from 14.0.2 to 15.0.0 ([#316](https://github.com/libp2p/js-libp2p-interfaces/issues/316)) ([d37721c](https://github.com/libp2p/js-libp2p-interfaces/commit/d37721c9143cd3eeafb5f8249b07d9f2fbce0f54)) + +## [@libp2p/interface-pubsub-compliance-tests-v4.0.1](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-pubsub-compliance-tests-v4.0.0...@libp2p/interface-pubsub-compliance-tests-v4.0.1) (2022-11-05) + + +### Bug Fixes + +* update project config ([#311](https://github.com/libp2p/js-libp2p-interfaces/issues/311)) ([27dd0ce](https://github.com/libp2p/js-libp2p-interfaces/commit/27dd0ce3c249892ac69cbb24ddaf0b9f32385e37)) + + +### Dependencies + +* update sibling dependencies ([45af2ca](https://github.com/libp2p/js-libp2p-interfaces/commit/45af2cadd55ad58d0c5ee2d11a0b8a39f6300454)) + +## [@libp2p/interface-pubsub-compliance-tests-v4.0.0](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-pubsub-compliance-tests-v3.0.0...@libp2p/interface-pubsub-compliance-tests-v4.0.0) (2022-10-12) + + +### ⚠ BREAKING CHANGES + +* modules no longer implement `Initializable` instead switching to constructor injection + +### Bug Fixes + +* export network components type ([79a5d8f](https://github.com/libp2p/js-libp2p-interfaces/commit/79a5d8fc57ae47274ff9ad9c3969c5898f07eb1d)) +* remove @libp2p/components ([#301](https://github.com/libp2p/js-libp2p-interfaces/issues/301)) ([1d37dc6](https://github.com/libp2p/js-libp2p-interfaces/commit/1d37dc6d3197838a71895d5769ad8bba6eb38fd3)) +* update mock network components use ([c760e95](https://github.com/libp2p/js-libp2p-interfaces/commit/c760e95f07b6199f08adb20c1e3a4265649fdda0)) + + +### Dependencies + +* update sibling dependencies ([d3226f7](https://github.com/libp2p/js-libp2p-interfaces/commit/d3226f7383de85cae2b4771c22eea22c4bb5bbeb)) + +## [@libp2p/interface-pubsub-compliance-tests-v3.0.0](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-pubsub-compliance-tests-v2.0.5...@libp2p/interface-pubsub-compliance-tests-v3.0.0) (2022-10-11) + + +### ⚠ BREAKING CHANGES + +* add topicValidators to pubsub interface (#298) + +### Bug Fixes + +* add topicValidators to pubsub interface ([#298](https://github.com/libp2p/js-libp2p-interfaces/issues/298)) ([e5ff819](https://github.com/libp2p/js-libp2p-interfaces/commit/e5ff819c6dd235b2ea9ea5133457b384c4411cf3)) + + +### Dependencies + +* update sibling dependencies ([8f3680e](https://github.com/libp2p/js-libp2p-interfaces/commit/8f3680e2d87e424936dfe7128b859795f0327d9a)) + +## [@libp2p/interface-pubsub-compliance-tests-v2.0.5](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-pubsub-compliance-tests-v2.0.4...@libp2p/interface-pubsub-compliance-tests-v2.0.5) (2022-10-07) + + +### Dependencies + +* bump @libp2p/components from 2.1.1 to 3.0.0 ([#299](https://github.com/libp2p/js-libp2p-interfaces/issues/299)) ([b3f493c](https://github.com/libp2p/js-libp2p-interfaces/commit/b3f493c5e260f697f66de54b56379d036ca3db59)) + +## [@libp2p/interface-pubsub-compliance-tests-v2.0.4](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-pubsub-compliance-tests-v2.0.3...@libp2p/interface-pubsub-compliance-tests-v2.0.4) (2022-10-06) + + +### Dependencies + +* update sibling dependencies ([2f46d7f](https://github.com/libp2p/js-libp2p-interfaces/commit/2f46d7ff4189c29a63bac93b0b5b73de0a75922f)) + +## [@libp2p/interface-pubsub-compliance-tests-v2.0.3](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-pubsub-compliance-tests-v2.0.2...@libp2p/interface-pubsub-compliance-tests-v2.0.3) (2022-10-04) + + +### Dependencies + +* update sibling dependencies ([1b11e8e](https://github.com/libp2p/js-libp2p-interfaces/commit/1b11e8e9cc2ea1d4d26233f9c11a57e185ea23ed)) + +## [@libp2p/interface-pubsub-compliance-tests-v2.0.2](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-pubsub-compliance-tests-v2.0.1...@libp2p/interface-pubsub-compliance-tests-v2.0.2) (2022-08-10) + + +### Dependencies + +* update sibling dependencies ([fc4c49c](https://github.com/libp2p/js-libp2p-interfaces/commit/fc4c49c22334b9f2059b08e13ba94f3e8938482e)) + +## [@libp2p/interface-pubsub-compliance-tests-v2.0.1](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-pubsub-compliance-tests-v2.0.0...@libp2p/interface-pubsub-compliance-tests-v2.0.1) (2022-07-31) + + +### Dependencies + +* update uint8arraylist and p-wait-for deps ([#274](https://github.com/libp2p/js-libp2p-interfaces/issues/274)) ([c55f12e](https://github.com/libp2p/js-libp2p-interfaces/commit/c55f12e47be0a10e41709b0d6a60dd8bc1209ee5)) + +## [@libp2p/interface-pubsub-compliance-tests-v2.0.0](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-pubsub-compliance-tests-v1.0.4...@libp2p/interface-pubsub-compliance-tests-v2.0.0) (2022-07-31) + + +### ⚠ BREAKING CHANGES + +* The `Message` type is now either a `SignedMessage` +or a `UnsignedMessage` + +### Features + +* pubsub Message types for signature policies ([#266](https://github.com/libp2p/js-libp2p-interfaces/issues/266)) ([9eb710b](https://github.com/libp2p/js-libp2p-interfaces/commit/9eb710bcbdb0aef95c7a8613e00065a3b7c7f887)) + + +### Trivial Changes + +* update project config ([#271](https://github.com/libp2p/js-libp2p-interfaces/issues/271)) ([59c0bf5](https://github.com/libp2p/js-libp2p-interfaces/commit/59c0bf5e0b05496fca2e4902632b61bb41fad9e9)) +* update sibling dependencies [skip ci] ([fbd5281](https://github.com/libp2p/js-libp2p-interfaces/commit/fbd52811b1d074df0755a3ee10c33a99ccc86842)) + +## [@libp2p/interface-pubsub-compliance-tests-v1.0.4](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-pubsub-compliance-tests-v1.0.3...@libp2p/interface-pubsub-compliance-tests-v1.0.4) (2022-06-27) + + +### Trivial Changes + +* update deps ([#262](https://github.com/libp2p/js-libp2p-interfaces/issues/262)) ([51edf7d](https://github.com/libp2p/js-libp2p-interfaces/commit/51edf7d9b3765a6f75c915b1483ea345d0133a41)) + +## [@libp2p/interface-pubsub-compliance-tests-v1.0.3](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-pubsub-compliance-tests-v1.0.2...@libp2p/interface-pubsub-compliance-tests-v1.0.3) (2022-06-24) + + +### Trivial Changes + +* update sibling dependencies [skip ci] ([c5c41c5](https://github.com/libp2p/js-libp2p-interfaces/commit/c5c41c521cf970addc1840d8519cdaa542a0db16)) + +## [@libp2p/interface-pubsub-compliance-tests-v1.0.2](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-pubsub-compliance-tests-v1.0.1...@libp2p/interface-pubsub-compliance-tests-v1.0.2) (2022-06-16) + + +### Trivial Changes + +* update deps ([54fbb37](https://github.com/libp2p/js-libp2p-interfaces/commit/54fbb37c8644a3fd6833c12550a57bf1a9292902)) +* update deps ([970a940](https://github.com/libp2p/js-libp2p-interfaces/commit/970a940a2f65b946936a53febdc52527baefbd34)) + +## [@libp2p/interface-pubsub-compliance-tests-v1.0.1](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-pubsub-compliance-tests-v1.0.0...@libp2p/interface-pubsub-compliance-tests-v1.0.1) (2022-06-14) + + +### Trivial Changes + +* update components module ([#235](https://github.com/libp2p/js-libp2p-interfaces/issues/235)) ([5844207](https://github.com/libp2p/js-libp2p-interfaces/commit/58442070af59aa852c83ec3aecdbd1d2c646b018)) diff --git a/packages/interface-pubsub-compliance-tests/LICENSE b/packages/interface-pubsub-compliance-tests/LICENSE new file mode 100644 index 0000000000..20ce483c86 --- /dev/null +++ b/packages/interface-pubsub-compliance-tests/LICENSE @@ -0,0 +1,4 @@ +This project is dual licensed under MIT and Apache-2.0. + +MIT: https://www.opensource.org/licenses/mit +Apache-2.0: https://www.apache.org/licenses/license-2.0 diff --git a/packages/interface-pubsub-compliance-tests/LICENSE-APACHE b/packages/interface-pubsub-compliance-tests/LICENSE-APACHE new file mode 100644 index 0000000000..14478a3b60 --- /dev/null +++ b/packages/interface-pubsub-compliance-tests/LICENSE-APACHE @@ -0,0 +1,5 @@ +Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. diff --git a/packages/interface-pubsub-compliance-tests/LICENSE-MIT b/packages/interface-pubsub-compliance-tests/LICENSE-MIT new file mode 100644 index 0000000000..72dc60d84b --- /dev/null +++ b/packages/interface-pubsub-compliance-tests/LICENSE-MIT @@ -0,0 +1,19 @@ +The MIT License (MIT) + +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/packages/interface-pubsub-compliance-tests/README.md b/packages/interface-pubsub-compliance-tests/README.md new file mode 100644 index 0000000000..8c6a5c6164 --- /dev/null +++ b/packages/interface-pubsub-compliance-tests/README.md @@ -0,0 +1,55 @@ +# @libp2p/interface-pubsub-compliance-tests + +[![libp2p.io](https://img.shields.io/badge/project-libp2p-yellow.svg?style=flat-square)](http://libp2p.io/) +[![Discuss](https://img.shields.io/discourse/https/discuss.libp2p.io/posts.svg?style=flat-square)](https://discuss.libp2p.io) +[![codecov](https://img.shields.io/codecov/c/github/libp2p/js-libp2p.svg?style=flat-square)](https://codecov.io/gh/libp2p/js-libp2p) +[![CI](https://img.shields.io/github/actions/workflow/status/libp2p/js-libp2p/js-test-and-release.yml?branch=master\&style=flat-square)](https://github.com/libp2p/js-libp2p/actions/workflows/js-test-and-release.yml?query=branch%3Amaster) + +> Compliance tests for implementations of the libp2p PubSub interface + +## Table of contents + +- [Install](#install) +- [Usage](#usage) +- [API Docs](#api-docs) +- [License](#license) +- [Contribution](#contribution) + +## Install + +```console +$ npm i @libp2p/interface-pubsub-compliance-tests +``` + +## Usage + +```js +import tests from '@libp2p/interface-pubsub-compliance-tests' + +describe('your pubsub implementation', () => { + tests({ + // Options should be passed to your implementation + async setup (options) { + return new YourImplementation() + }, + async teardown () { + // cleanup resources created by setup() + } + }) +}) +``` + +## API Docs + +- + +## License + +Licensed under either of + +- Apache 2.0, ([LICENSE-APACHE](LICENSE-APACHE) / ) +- MIT ([LICENSE-MIT](LICENSE-MIT) / ) + +## Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. diff --git a/packages/interface-pubsub-compliance-tests/package.json b/packages/interface-pubsub-compliance-tests/package.json new file mode 100644 index 0000000000..e53a1add68 --- /dev/null +++ b/packages/interface-pubsub-compliance-tests/package.json @@ -0,0 +1,150 @@ +{ + "name": "@libp2p/interface-pubsub-compliance-tests", + "version": "5.0.7", + "description": "Compliance tests for implementations of the libp2p PubSub interface", + "license": "Apache-2.0 OR MIT", + "homepage": "https://github.com/libp2p/js-libp2p/tree/master/packages/interface-pubsub-compliance-tests#readme", + "repository": { + "type": "git", + "url": "git+https://github.com/libp2p/js-libp2p.git" + }, + "bugs": { + "url": "https://github.com/libp2p/js-libp2p/issues" + }, + "keywords": [ + "interface", + "libp2p" + ], + "type": "module", + "types": "./dist/src/index.d.ts", + "files": [ + "src", + "dist", + "!dist/test", + "!**/*.tsbuildinfo" + ], + "exports": { + ".": { + "types": "./dist/src/index.d.ts", + "import": "./dist/src/index.js" + } + }, + "eslintConfig": { + "extends": "ipfs", + "parserOptions": { + "sourceType": "module" + } + }, + "release": { + "branches": [ + "master" + ], + "plugins": [ + [ + "@semantic-release/commit-analyzer", + { + "preset": "conventionalcommits", + "releaseRules": [ + { + "breaking": true, + "release": "major" + }, + { + "revert": true, + "release": "patch" + }, + { + "type": "feat", + "release": "minor" + }, + { + "type": "fix", + "release": "patch" + }, + { + "type": "docs", + "release": "patch" + }, + { + "type": "test", + "release": "patch" + }, + { + "type": "deps", + "release": "patch" + }, + { + "scope": "no-release", + "release": false + } + ] + } + ], + [ + "@semantic-release/release-notes-generator", + { + "preset": "conventionalcommits", + "presetConfig": { + "types": [ + { + "type": "feat", + "section": "Features" + }, + { + "type": "fix", + "section": "Bug Fixes" + }, + { + "type": "chore", + "section": "Trivial Changes" + }, + { + "type": "docs", + "section": "Documentation" + }, + { + "type": "deps", + "section": "Dependencies" + }, + { + "type": "test", + "section": "Tests" + } + ] + } + } + ], + "@semantic-release/changelog", + "@semantic-release/npm", + "@semantic-release/github", + "@semantic-release/git" + ] + }, + "scripts": { + "clean": "aegir clean", + "lint": "aegir lint", + "dep-check": "aegir dep-check", + "build": "aegir build", + "release": "aegir release" + }, + "dependencies": { + "@libp2p/interface-compliance-tests": "^3.0.0", + "@libp2p/interface-connection-manager": "^3.0.0", + "@libp2p/interface-mocks": "^12.0.0", + "@libp2p/interface-peer-id": "^2.0.0", + "@libp2p/interface-pubsub": "^4.0.0", + "@libp2p/interface-registrar": "^2.0.0", + "@libp2p/interfaces": "^3.0.0", + "@libp2p/peer-id-factory": "^2.0.0", + "aegir": "^39.0.5", + "delay": "^6.0.0", + "p-defer": "^4.0.0", + "p-event": "^5.0.1", + "p-wait-for": "^5.0.0", + "sinon": "^15.0.0", + "uint8arrays": "^4.0.2" + }, + "typedoc": { + "entryPoint": "./src/index.ts" + } +} diff --git a/packages/interface-pubsub-compliance-tests/src/api.ts b/packages/interface-pubsub-compliance-tests/src/api.ts new file mode 100644 index 0000000000..00b180f1c0 --- /dev/null +++ b/packages/interface-pubsub-compliance-tests/src/api.ts @@ -0,0 +1,114 @@ +import { mockNetwork } from '@libp2p/interface-mocks' +import { isStartable, start, stop } from '@libp2p/interfaces/startable' +import { expect } from 'aegir/chai' +import delay from 'delay' +import pDefer from 'p-defer' +import pWaitFor from 'p-wait-for' +import sinon from 'sinon' +import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' +import { createComponents } from './utils.js' +import type { PubSubArgs, PubSubComponents } from './index.js' +import type { TestSetup } from '@libp2p/interface-compliance-tests' +import type { PubSub } from '@libp2p/interface-pubsub' + +const topic = 'foo' +const data = uint8ArrayFromString('bar') + +export default (common: TestSetup): void => { + describe('pubsub api', () => { + let pubsub: PubSub + let components: PubSubComponents + + // Create pubsub router + beforeEach(async () => { + mockNetwork.reset() + components = await createComponents() + + pubsub = components.pubsub = await common.setup({ + components, + init: { + emitSelf: true + } + }) + }) + + afterEach(async () => { + sinon.restore() + await stop(...Object.values(components)) + await common.teardown() + mockNetwork.reset() + }) + + it('can start correctly', async () => { + if (!isStartable(pubsub)) { + return + } + + sinon.spy(components.registrar, 'register') + + await start(...Object.values(components)) + + expect(pubsub.isStarted()).to.equal(true) + expect(components.registrar.register).to.have.property('callCount', 1) + }) + + it('can stop correctly', async () => { + if (!isStartable(pubsub)) { + return + } + + sinon.spy(components.registrar, 'unregister') + + await start(...Object.values(components)) + await stop(...Object.values(components)) + + expect(pubsub.isStarted()).to.equal(false) + expect(components.registrar.unregister).to.have.property('callCount', 1) + }) + + it('can subscribe and unsubscribe correctly', async () => { + const handler = (): void => { + throw new Error('a message should not be received') + } + + await start(...Object.values(components)) + pubsub.subscribe(topic) + pubsub.addEventListener('message', handler) + + await pWaitFor(() => { + const topics = pubsub.getTopics() + return topics.length === 1 && topics[0] === topic + }) + + pubsub.removeEventListener('message', handler) + pubsub.unsubscribe(topic) + + await pWaitFor(() => pubsub.getTopics().length === 0) + + // Publish to guarantee the handler is not called + await pubsub.publish(topic, data) + + // handlers are called async + await delay(100) + + await stop(...Object.values(components)) + }) + + it('can subscribe and publish correctly', async () => { + const defer = pDefer() + + await start(...Object.values(components)) + + pubsub.subscribe(topic) + pubsub.addEventListener('message', (evt) => { + expect(evt).to.have.nested.property('detail.topic', topic) + expect(evt).to.have.deep.nested.property('detail.data', data) + defer.resolve() + }) + await pubsub.publish(topic, data) + await defer.promise + + await stop(...Object.values(components)) + }) + }) +} diff --git a/packages/interface-pubsub-compliance-tests/src/connection-handlers.ts b/packages/interface-pubsub-compliance-tests/src/connection-handlers.ts new file mode 100644 index 0000000000..24656a0c7f --- /dev/null +++ b/packages/interface-pubsub-compliance-tests/src/connection-handlers.ts @@ -0,0 +1,413 @@ +import { mockNetwork } from '@libp2p/interface-mocks' +import { start, stop } from '@libp2p/interfaces/startable' +import { expect } from 'aegir/chai' +import pDefer from 'p-defer' +import { pEvent } from 'p-event' +import pWaitFor from 'p-wait-for' +import sinon from 'sinon' +import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' +import { toString as uint8ArrayToString } from 'uint8arrays/to-string' +import { createComponents } from './utils.js' +import type { PubSubArgs } from './index.js' +import type { TestSetup } from '@libp2p/interface-compliance-tests' +import type { MockNetworkComponents } from '@libp2p/interface-mocks' +import type { Message, PubSub } from '@libp2p/interface-pubsub' + +export default (common: TestSetup): void => { + describe('pubsub connection handlers', () => { + let psA: PubSub + let psB: PubSub + let componentsA: MockNetworkComponents + let componentsB: MockNetworkComponents + + describe('nodes send state on connection', () => { + // Create pubsub nodes and connect them + beforeEach(async () => { + mockNetwork.reset() + + componentsA = await createComponents() + componentsB = await createComponents() + + psA = componentsA.pubsub = await common.setup({ + components: componentsA, + init: {} + }) + + psB = componentsB.pubsub = await common.setup({ + components: componentsB, + init: {} + }) + + // Start pubsub + await start(...Object.values(componentsA), ...Object.values(componentsB)) + + expect(psA.getPeers()).to.be.empty() + expect(psB.getPeers()).to.be.empty() + + // Make subscriptions prior to nodes connected + psA.subscribe('Za') + psB.subscribe('Zb') + + expect(psA.getPeers()).to.be.empty() + expect(psA.getTopics()).to.deep.equal(['Za']) + expect(psB.getPeers()).to.be.empty() + expect(psB.getTopics()).to.deep.equal(['Zb']) + }) + + afterEach(async () => { + sinon.restore() + await stop(...Object.values(componentsA), ...Object.values(componentsB)) + await common.teardown() + mockNetwork.reset() + }) + + it('existing subscriptions are sent upon peer connection', async function () { + const subscriptionsChanged = Promise.all([ + pEvent(psA, 'subscription-change'), + pEvent(psB, 'subscription-change') + ]) + + await componentsA.connectionManager.openConnection(componentsB.peerId) + + await subscriptionsChanged + + expect(psA.getPeers()).to.have.lengthOf(1) + expect(psB.getPeers()).to.have.lengthOf(1) + + expect(psA.getTopics()).to.deep.equal(['Za']) + expect(psB.getTopics()).to.deep.equal(['Zb']) + + expect(psA.getSubscribers('Zb').map(p => p.toString())).to.deep.equal([componentsB.peerId.toString()]) + expect(psB.getSubscribers('Za').map(p => p.toString())).to.deep.equal([componentsA.peerId.toString()]) + }) + }) + + describe('pubsub started before connect', () => { + let psA: PubSub + let psB: PubSub + let componentsA: MockNetworkComponents + let componentsB: MockNetworkComponents + + // Create pubsub nodes and start them + beforeEach(async () => { + mockNetwork.reset() + componentsA = await createComponents() + componentsB = await createComponents() + + psA = componentsA.pubsub = await common.setup({ + components: componentsA, + init: {} + }) + psB = componentsB.pubsub = await common.setup({ + components: componentsB, + init: {} + }) + + await start(...Object.values(componentsA), ...Object.values(componentsB)) + }) + + afterEach(async () => { + sinon.restore() + await stop(...Object.values(componentsA), ...Object.values(componentsB)) + await common.teardown() + mockNetwork.reset() + }) + + it('should get notified of connected peers on dial', async () => { + await componentsA.connectionManager.openConnection(componentsB.peerId) + + return Promise.all([ + pWaitFor(() => psA.getPeers().length === 1), + pWaitFor(() => psB.getPeers().length === 1) + ]) + }) + + it('should receive pubsub messages', async () => { + const defer = pDefer() + const topic = 'test-topic' + const data = uint8ArrayFromString('hey!') + + await componentsA.connectionManager.openConnection(componentsB.peerId) + + let subscribedTopics = psA.getTopics() + expect(subscribedTopics).to.not.include(topic) + + psA.subscribe(topic) + psA.addEventListener('message', (evt) => { + if (evt.detail.topic === topic) { + const msg = evt.detail + expect(msg.data).to.equalBytes(data) + defer.resolve() + } + }) + psA.subscribe(topic) + + subscribedTopics = psA.getTopics() + expect(subscribedTopics).to.include(topic) + + // wait for psB to know about psA subscription + await pWaitFor(() => { + const subscribedPeers = psB.getSubscribers(topic) + return subscribedPeers.map(p => p.toString()).includes(componentsA.peerId.toString()) // eslint-disable-line max-nested-callbacks + }) + await psB.publish(topic, data) + + await defer.promise + }) + }) + + describe('pubsub started after connect', () => { + let psA: PubSub + let psB: PubSub + let componentsA: MockNetworkComponents + let componentsB: MockNetworkComponents + + // Create pubsub nodes + beforeEach(async () => { + mockNetwork.reset() + componentsA = await createComponents() + componentsB = await createComponents() + + psA = componentsA.pubsub = await common.setup({ + components: componentsA, + init: {} + }) + psB = componentsB.pubsub = await common.setup({ + components: componentsB, + init: {} + }) + }) + + afterEach(async () => { + sinon.restore() + await stop(...Object.values(componentsA), ...Object.values(componentsB)) + await common.teardown() + mockNetwork.reset() + }) + + it('should get notified of connected peers after starting', async () => { + await start(...Object.values(componentsA), ...Object.values(componentsB)) + + await componentsA.connectionManager.openConnection(componentsB.peerId) + + return Promise.all([ + pWaitFor(() => psA.getPeers().length === 1), + pWaitFor(() => psB.getPeers().length === 1) + ]) + }) + + it('should receive pubsub messages', async () => { + const defer = pDefer() + const topic = 'test-topic' + const data = uint8ArrayFromString('hey!') + + await start(...Object.values(componentsA), ...Object.values(componentsB)) + + await componentsA.connectionManager.openConnection(componentsB.peerId) + + await Promise.all([ + pWaitFor(() => psA.getPeers().length === 1), + pWaitFor(() => psB.getPeers().length === 1) + ]) + + let subscribedTopics = psA.getTopics() + expect(subscribedTopics).to.not.include(topic) + + psA.subscribe(topic) + psA.addEventListener('message', (evt) => { + if (evt.detail.topic === topic) { + const msg = evt.detail + expect(msg.data).to.equalBytes(data) + defer.resolve() + } + }) + psA.subscribe(topic) + + subscribedTopics = psA.getTopics() + expect(subscribedTopics).to.include(topic) + + // wait for psB to know about psA subscription + await pWaitFor(() => { + const subscribedPeers = psB.getSubscribers(topic) + return subscribedPeers.map(p => p.toString()).includes(componentsA.peerId.toString()) // eslint-disable-line max-nested-callbacks + }) + await psB.publish(topic, data) + + await defer.promise + }) + }) + + describe('pubsub with intermittent connections', () => { + let psA: PubSub + let psB: PubSub + let componentsA: MockNetworkComponents + let componentsB: MockNetworkComponents + + // Create pubsub nodes and start them + beforeEach(async () => { + mockNetwork.reset() + componentsA = await createComponents() + componentsB = await createComponents() + + psA = componentsA.pubsub = await common.setup({ + components: componentsA, + init: {} + }) + psB = componentsB.pubsub = await common.setup({ + components: componentsB, + init: {} + }) + + await start(...Object.values(componentsA), ...Object.values(componentsB)) + }) + + afterEach(async () => { + sinon.restore() + await stop(...Object.values(componentsA), ...Object.values(componentsB)) + await common.teardown() + mockNetwork.reset() + }) + + it.skip('should receive pubsub messages after a node restart', async function () { + const topic = 'test-topic' + const data = uint8ArrayFromString('hey!') + + let counter = 0 + const defer1 = pDefer() + const defer2 = pDefer() + + await componentsA.connectionManager.openConnection(componentsB.peerId) + + let subscribedTopics = psA.getTopics() + expect(subscribedTopics).to.not.include(topic) + + psA.subscribe(topic) + psA.addEventListener('message', (evt) => { + if (evt.detail.topic === topic) { + const msg = evt.detail + expect(msg.data).to.equalBytes(data) + counter++ + counter === 1 ? defer1.resolve() : defer2.resolve() + } + }) + psA.subscribe(topic) + + subscribedTopics = psA.getTopics() + expect(subscribedTopics).to.include(topic) + + // wait for psB to know about psA subscription + await pWaitFor(() => { + const subscribedPeers = psB.getSubscribers(topic) + return subscribedPeers.map(p => p.toString()).includes(componentsA.peerId.toString()) // eslint-disable-line max-nested-callbacks + }) + await psB.publish(topic, data) + + await defer1.promise + + await stop(psB) + await pWaitFor(() => { + // @ts-expect-error protected fields + const aHasConnectionToB = psA._libp2p.connectionManager.get(psB.peerId) + // @ts-expect-error protected fields + const bHasConnectionToA = psB._libp2p.connectionManager.get(psA.peerId) + + return aHasConnectionToB != null && bHasConnectionToA != null + }) + await start(psB) + + await componentsA.connectionManager.openConnection(componentsB.peerId) + + // wait for remoteLibp2p to know about libp2p subscription + await pWaitFor(() => { + const subscribedPeers = psB.getSubscribers(topic) + return subscribedPeers.toString().includes(componentsA.peerId.toString()) + }) + + await psB.publish(topic, data) + + await defer2.promise + }) + + it.skip('should handle quick reconnects with a delayed disconnect', async () => { + // Subscribe on both + let aReceivedFirstMessageFromB = false + let aReceivedSecondMessageFromB = false + let bReceivedFirstMessageFromA = false + let bReceivedSecondMessageFromA = false + const topic = 'reconnect-channel' + + const handlerSpyA = (evt: CustomEvent): void => { + if (evt.detail.topic !== topic) { + return + } + + const message = evt.detail + const data = uint8ArrayToString(message.data) + + if (data === 'message-from-b-1') { + aReceivedFirstMessageFromB = true + } + + if (data === 'message-from-b-2') { + aReceivedSecondMessageFromB = true + } + } + const handlerSpyB = (evt: CustomEvent): void => { + if (evt.detail.topic !== topic) { + return + } + + const message = evt.detail + const data = uint8ArrayToString(message.data) + + if (data === 'message-from-a-1') { + bReceivedFirstMessageFromA = true + } + + if (data === 'message-from-a-2') { + bReceivedSecondMessageFromA = true + } + } + + psA.addEventListener('message', handlerSpyA) + psB.addEventListener('message', handlerSpyB) + psA.subscribe(topic) + psB.subscribe(topic) + + // Create two connections to the remote peer + // @ts-expect-error protected fields + const originalConnection = await psA._libp2p.dialer.connectToPeer(psB.peerId) + + // second connection + await componentsA.connectionManager.openConnection(componentsB.peerId) + + // Wait for subscriptions to occur + await pWaitFor(() => { + return psA.getSubscribers(topic).map(p => p.toString()).includes(componentsB.peerId.toString()) && + psB.getSubscribers(topic).map(p => p.toString()).includes(componentsA.peerId.toString()) + }) + + // Verify messages go both ways + await psA.publish(topic, uint8ArrayFromString('message-from-a-1')) + await psB.publish(topic, uint8ArrayFromString('message-from-b-1')) + await pWaitFor(() => { + return aReceivedFirstMessageFromB && bReceivedFirstMessageFromA + }) + + // Disconnect the first connection (this acts as a delayed reconnect) + // @ts-expect-error protected fields + const psAConnUpdateSpy = sinon.spy(psA._libp2p.connectionManager.connections, 'set') + + await originalConnection.close() + await pWaitFor(() => psAConnUpdateSpy.callCount === 1) + + // Verify messages go both ways after the disconnect + await psA.publish(topic, uint8ArrayFromString('message-from-a-2')) + await psB.publish(topic, uint8ArrayFromString('message-from-b-2')) + await pWaitFor(() => { + return aReceivedSecondMessageFromB && bReceivedSecondMessageFromA + }) + }) + }) + }) +} diff --git a/packages/interface-pubsub-compliance-tests/src/emit-self.ts b/packages/interface-pubsub-compliance-tests/src/emit-self.ts new file mode 100644 index 0000000000..956a1f2143 --- /dev/null +++ b/packages/interface-pubsub-compliance-tests/src/emit-self.ts @@ -0,0 +1,99 @@ +import { mockNetwork } from '@libp2p/interface-mocks' +import { start, stop } from '@libp2p/interfaces/startable' +import { expect } from 'aegir/chai' +import sinon from 'sinon' +import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' +import { createComponents } from './utils.js' +import type { PubSubArgs, PubSubComponents } from './index.js' +import type { TestSetup } from '@libp2p/interface-compliance-tests' +import type { PubSub } from '@libp2p/interface-pubsub' + +const topic = 'foo' +const data = uint8ArrayFromString('bar') +const shouldNotHappen = (): void => expect.fail() + +export default (common: TestSetup): void => { + describe('emit self', () => { + describe('enabled', () => { + let pubsub: PubSub + let components: PubSubComponents + + before(async () => { + mockNetwork.reset() + components = await createComponents() + + pubsub = components.pubsub = await common.setup({ + components, + init: { + emitSelf: true + } + }) + + await start(...Object.values(components)) + pubsub.subscribe(topic) + }) + + after(async () => { + sinon.restore() + await stop(...Object.values(components)) + await common.teardown() + mockNetwork.reset() + }) + + it('should emit to self on publish', async () => { + const promise = new Promise((resolve) => { + pubsub.addEventListener('message', (evt) => { + if (evt.detail.topic === topic) { + resolve() + } + }, { + once: true + }) + }) + + const result = await pubsub.publish(topic, data) + + await promise + + expect(result).to.have.property('recipients').with.lengthOf(1) + }) + }) + + describe('disabled', () => { + let pubsub: PubSub + let components: PubSubComponents + + before(async () => { + mockNetwork.reset() + components = await createComponents() + pubsub = components.pubsub = await common.setup({ + components, + init: { + emitSelf: false + } + }) + + await start(...Object.values(components)) + pubsub.subscribe(topic) + }) + + after(async () => { + sinon.restore() + await stop(...Object.values(components)) + await common.teardown() + mockNetwork.reset() + }) + + it('should not emit to self on publish', async () => { + pubsub.addEventListener('message', shouldNotHappen, { + once: true + }) + + await pubsub.publish(topic, data) + + // Wait 1 second to guarantee that self is not noticed + await new Promise((resolve) => setTimeout(resolve, 1000)) + }) + }) + }) +} diff --git a/packages/interface-pubsub-compliance-tests/src/index.ts b/packages/interface-pubsub-compliance-tests/src/index.ts new file mode 100644 index 0000000000..7cf2d45fc5 --- /dev/null +++ b/packages/interface-pubsub-compliance-tests/src/index.ts @@ -0,0 +1,34 @@ +import apiTest from './api.js' +import connectionHandlersTest from './connection-handlers.js' +import emitSelfTest from './emit-self.js' +import messagesTest from './messages.js' +import multipleNodesTest from './multiple-nodes.js' +import twoNodesTest from './two-nodes.js' +import type { TestSetup } from '@libp2p/interface-compliance-tests' +import type { ConnectionManager } from '@libp2p/interface-connection-manager' +import type { PeerId } from '@libp2p/interface-peer-id' +import type { PubSub, PubSubInit } from '@libp2p/interface-pubsub' +import type { Registrar } from '@libp2p/interface-registrar' + +export interface PubSubComponents { + peerId: PeerId + registrar: Registrar + connectionManager: ConnectionManager + pubsub?: PubSub +} + +export interface PubSubArgs { + components: PubSubComponents + init: PubSubInit +} + +export default (common: TestSetup): void => { + describe('interface-pubsub compliance tests', () => { + apiTest(common) + emitSelfTest(common) + messagesTest(common) + connectionHandlersTest(common) + twoNodesTest(common) + multipleNodesTest(common) + }) +} diff --git a/packages/interface-pubsub-compliance-tests/src/messages.ts b/packages/interface-pubsub-compliance-tests/src/messages.ts new file mode 100644 index 0000000000..ddbd9d59c6 --- /dev/null +++ b/packages/interface-pubsub-compliance-tests/src/messages.ts @@ -0,0 +1,59 @@ +import { mockNetwork } from '@libp2p/interface-mocks' +import { start, stop } from '@libp2p/interfaces/startable' +import { expect } from 'aegir/chai' +import { pEvent } from 'p-event' +import sinon from 'sinon' +import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' +import { createComponents } from './utils.js' +import type { PubSubArgs, PubSubComponents } from './index.js' +import type { TestSetup } from '@libp2p/interface-compliance-tests' +import type { Message, PubSub } from '@libp2p/interface-pubsub' + +const topic = 'foo' +const data = uint8ArrayFromString('bar') + +export default (common: TestSetup): void => { + describe('messages', () => { + let pubsub: PubSub + let components: PubSubComponents + + // Create pubsub router + beforeEach(async () => { + mockNetwork.reset() + components = await createComponents() + + pubsub = components.pubsub = await common.setup({ + components, + init: { + emitSelf: true + } + }) + await start(...Object.values(components)) + }) + + afterEach(async () => { + sinon.restore() + await stop(...Object.values(components)) + await common.teardown() + mockNetwork.reset() + }) + + it('should emit normalized signed messages on publish', async () => { + const eventPromise = pEvent<'message', CustomEvent>(pubsub, 'message') + + pubsub.globalSignaturePolicy = 'StrictSign' + pubsub.subscribe(topic) + await pubsub.publish(topic, data) + + const event = await eventPromise + const message = event.detail + + if (message.type === 'signed') { + expect(message.from.toString()).to.equal(components.peerId.toString()) + expect(message.sequenceNumber).to.not.eql(undefined) + expect(message.key).to.not.eql(undefined) + expect(message.signature).to.not.eql(undefined) + } + }) + }) +} diff --git a/packages/interface-pubsub-compliance-tests/src/multiple-nodes.ts b/packages/interface-pubsub-compliance-tests/src/multiple-nodes.ts new file mode 100644 index 0000000000..d5c4c58545 --- /dev/null +++ b/packages/interface-pubsub-compliance-tests/src/multiple-nodes.ts @@ -0,0 +1,440 @@ +/* eslint max-nested-callbacks: ["error", 6] */ +import { mockNetwork } from '@libp2p/interface-mocks' +import { start, stop } from '@libp2p/interfaces/startable' +import { expect } from 'aegir/chai' +import delay from 'delay' +import pDefer from 'p-defer' +import pWaitFor from 'p-wait-for' +import sinon from 'sinon' +import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' +import { toString as uint8ArrayToString } from 'uint8arrays/to-string' +import { createComponents, waitForSubscriptionUpdate } from './utils.js' +import type { PubSubArgs, PubSubComponents } from './index.js' +import type { TestSetup } from '@libp2p/interface-compliance-tests' +import type { Message, PubSub } from '@libp2p/interface-pubsub' + +export default (common: TestSetup): void => { + describe('pubsub with multiple nodes', function () { + describe('every peer subscribes to the topic', () => { + describe('line', () => { + // line + // ◉────◉────◉ + // a b c + let psA: PubSub + let psB: PubSub + let psC: PubSub + let componentsA: PubSubComponents + let componentsB: PubSubComponents + let componentsC: PubSubComponents + + // Create and start pubsub nodes + beforeEach(async () => { + mockNetwork.reset() + + componentsA = await createComponents() + componentsB = await createComponents() + componentsC = await createComponents() + + psA = componentsA.pubsub = await common.setup({ + components: componentsA, + init: { + emitSelf: true + } + }) + psB = componentsB.pubsub = await common.setup({ + components: componentsB, + init: { + emitSelf: true + } + }) + psC = componentsC.pubsub = await common.setup({ + components: componentsC, + init: { + emitSelf: true + } + }) + + // Start pubsub modes + await start(...Object.values(componentsA), ...Object.values(componentsB), ...Object.values(componentsC)) + + // Connect nodes + await componentsA.connectionManager.openConnection(componentsB.peerId) + await componentsB.connectionManager.openConnection(componentsC.peerId) + + // Wait for peers to be ready in pubsub + await pWaitFor(() => + psA.getPeers().length === 1 && + psC.getPeers().length === 1 && + psA.getPeers().length === 1 + ) + }) + + afterEach(async () => { + sinon.restore() + await stop(...Object.values(componentsA), ...Object.values(componentsB), ...Object.values(componentsC)) + await common.teardown() + mockNetwork.reset() + }) + + it('subscribe to the topic on node a', async () => { + const topic = 'Z' + + psA.subscribe(topic) + expect(psA.getTopics()).to.deep.equal([topic]) + + await waitForSubscriptionUpdate(psB, componentsA.peerId) + + expect(psB.getPeers().length).to.equal(2) + expect(psB.getSubscribers(topic).map(p => p.toString())).to.deep.equal([componentsA.peerId.toString()]) + + expect(psC.getPeers().length).to.equal(1) + expect(psC.getSubscribers(topic)).to.be.empty() + }) + + it('subscribe to the topic on node b', async () => { + const topic = 'Z' + psB.subscribe(topic) + expect(psB.getTopics()).to.deep.equal([topic]) + + await Promise.all([ + waitForSubscriptionUpdate(psA, componentsB.peerId), + waitForSubscriptionUpdate(psC, componentsB.peerId) + ]) + + expect(psA.getPeers().length).to.equal(1) + expect(psA.getSubscribers(topic).map(p => p.toString())).to.deep.equal([componentsB.peerId.toString()]) + + expect(psC.getPeers().length).to.equal(1) + expect(psC.getSubscribers(topic).map(p => p.toString())).to.deep.equal([componentsB.peerId.toString()]) + }) + + it('subscribe to the topic on node c', async () => { + const topic = 'Z' + const defer = pDefer() + + psC.subscribe(topic) + expect(psC.getTopics()).to.deep.equal([topic]) + + psB.addEventListener('subscription-change', () => { + expect(psA.getPeers().length).to.equal(1) + expect(psB.getPeers().length).to.equal(2) + expect(psB.getSubscribers(topic).map(p => p.toString())).to.deep.equal([componentsC.peerId.toString()]) + + defer.resolve() + }, { + once: true + }) + + return defer.promise + }) + + it('publish on node a', async () => { + const topic = 'Z' + const defer = pDefer() + + psA.subscribe(topic) + psB.subscribe(topic) + psC.subscribe(topic) + + await Promise.all([ + waitForSubscriptionUpdate(psA, componentsB.peerId), + waitForSubscriptionUpdate(psB, componentsA.peerId), + waitForSubscriptionUpdate(psC, componentsB.peerId) + ]) + + // GossipSub needs time to build the mesh overlay + await delay(1000) + + let counter = 0 + + psA.addEventListener('message', incMsg) + psB.addEventListener('message', incMsg) + psC.addEventListener('message', incMsg) + + const result = await psA.publish(topic, uint8ArrayFromString('hey')) + + expect(result).to.have.property('recipients').with.property('length').greaterThanOrEqual(1) + + function incMsg (evt: CustomEvent): void { + const msg = evt.detail + + if (msg.topic !== topic) { + return + } + + expect(uint8ArrayToString(msg.data)).to.equal('hey') + check() + } + + function check (): void { + if (++counter === 3) { + psA.removeEventListener('message', incMsg) + psB.removeEventListener('message', incMsg) + psC.removeEventListener('message', incMsg) + defer.resolve() + } + } + + return defer.promise + }) + + // since the topology is the same, just the publish + // gets sent by other peer, we reused the same peers + describe('1 level tree', () => { + // 1 level tree + // ┌◉┐ + // │b│ + // ◉─┘ └─◉ + // a c + + it('publish on node b', async () => { + const topic = 'Z' + const defer = pDefer() + let counter = 0 + + psA.subscribe(topic) + psB.subscribe(topic) + psC.subscribe(topic) + + await Promise.all([ + waitForSubscriptionUpdate(psA, componentsB.peerId), + waitForSubscriptionUpdate(psB, componentsA.peerId), + waitForSubscriptionUpdate(psC, componentsB.peerId) + ]) + + // GossipSub needs time to build the mesh overlay + await delay(1000) + + psA.addEventListener('message', incMsg) + psB.addEventListener('message', incMsg) + psC.addEventListener('message', incMsg) + + await psB.publish(topic, uint8ArrayFromString('hey')) + + function incMsg (evt: CustomEvent): void { + const msg = evt.detail + + if (msg.topic !== topic) { + return + } + + expect(uint8ArrayToString(msg.data)).to.equal('hey') + check() + } + + function check (): void { + if (++counter === 3) { + psA.removeEventListener('message', incMsg) + psB.removeEventListener('message', incMsg) + psC.removeEventListener('message', incMsg) + defer.resolve() + } + } + + return defer.promise + }) + }) + }) + + describe('2 level tree', () => { + // 2 levels tree + // ┌◉┐ + // │c│ + // ┌◉─┘ └─◉┐ + // │b d│ + // ◉─┘ └─◉ + // a + let psA: PubSub + let psB: PubSub + let psC: PubSub + let psD: PubSub + let psE: PubSub + let componentsA: PubSubComponents + let componentsB: PubSubComponents + let componentsC: PubSubComponents + let componentsD: PubSubComponents + let componentsE: PubSubComponents + + // Create and start pubsub nodes + beforeEach(async () => { + mockNetwork.reset() + + componentsA = await createComponents() + componentsB = await createComponents() + componentsC = await createComponents() + componentsD = await createComponents() + componentsE = await createComponents() + + psA = componentsA.pubsub = await common.setup({ + components: componentsA, + init: { + emitSelf: true + } + }) + psB = componentsB.pubsub = await common.setup({ + components: componentsB, + init: { + emitSelf: true + } + }) + psC = componentsC.pubsub = await common.setup({ + components: componentsC, + init: { + emitSelf: true + } + }) + psD = componentsD.pubsub = await common.setup({ + components: componentsD, + init: { + emitSelf: true + } + }) + psE = componentsE.pubsub = await common.setup({ + components: componentsE, + init: { + emitSelf: true + } + }) + + // Start pubsub nodes + await start( + ...Object.values(componentsA), + ...Object.values(componentsB), + ...Object.values(componentsC), + ...Object.values(componentsD), + ...Object.values(componentsE) + ) + + // connect nodes + await componentsA.connectionManager.openConnection(componentsB.peerId) + await componentsB.connectionManager.openConnection(componentsC.peerId) + await componentsC.connectionManager.openConnection(componentsD.peerId) + await componentsD.connectionManager.openConnection(componentsE.peerId) + + // Wait for peers to be ready in pubsub + await pWaitFor(() => + psA.getPeers().length === 1 && + psB.getPeers().length === 2 && + psC.getPeers().length === 2 && + psD.getPeers().length === 2 && + psE.getPeers().length === 1 + ) + }) + + afterEach(async () => { + await stop( + ...Object.values(componentsA), + ...Object.values(componentsB), + ...Object.values(componentsC), + ...Object.values(componentsD), + ...Object.values(componentsE) + ) + await common.teardown() + mockNetwork.reset() + }) + + it('subscribes', () => { + psA.subscribe('Z') + expect(psA.getTopics()).to.deep.equal(['Z']) + psB.subscribe('Z') + expect(psB.getTopics()).to.deep.equal(['Z']) + psC.subscribe('Z') + expect(psC.getTopics()).to.deep.equal(['Z']) + psD.subscribe('Z') + expect(psD.getTopics()).to.deep.equal(['Z']) + psE.subscribe('Z') + expect(psE.getTopics()).to.deep.equal(['Z']) + }) + + it('publishes from c', async function () { + const defer = pDefer() + let counter = 0 + const topic = 'Z' + + psA.subscribe(topic) + psA.addEventListener('message', incMsg) + psB.subscribe(topic) + psB.addEventListener('message', incMsg) + psC.subscribe(topic) + psC.addEventListener('message', incMsg) + psD.subscribe(topic) + psD.addEventListener('message', incMsg) + psE.subscribe(topic) + psE.addEventListener('message', incMsg) + + await Promise.all([ + waitForSubscriptionUpdate(psA, componentsB.peerId), + waitForSubscriptionUpdate(psB, componentsA.peerId), + waitForSubscriptionUpdate(psC, componentsB.peerId), + waitForSubscriptionUpdate(psD, componentsC.peerId), + waitForSubscriptionUpdate(psE, componentsD.peerId) + ]) + + // GossipSub needs time to build the mesh overlay + await delay(1000) + + await psC.publish('Z', uint8ArrayFromString('hey from c')) + + function incMsg (evt: CustomEvent): void { + const msg = evt.detail + + if (msg.topic !== topic) { + return + } + + expect(uint8ArrayToString(msg.data)).to.equal('hey from c') + check() + } + + function check (): void { + if (++counter === 5) { + psA.unsubscribe('Z') + psB.unsubscribe('Z') + psC.unsubscribe('Z') + psD.unsubscribe('Z') + psE.unsubscribe('Z') + defer.resolve() + } + } + + return defer.promise + }) + }) + }) + + describe('only some nodes subscribe the networks', () => { + describe('line', () => { + // line + // ◉────◎────◉ + // a b c + + before(() => { }) + after(() => { }) + }) + + describe('1 level tree', () => { + // 1 level tree + // ┌◉┐ + // │b│ + // ◎─┘ └─◉ + // a c + + before(() => { }) + after(() => { }) + }) + + describe('2 level tree', () => { + // 2 levels tree + // ┌◉┐ + // │c│ + // ┌◎─┘ └─◉┐ + // │b d│ + // ◉─┘ └─◎ + // a e + + before(() => { }) + after(() => { }) + }) + }) + }) +} diff --git a/packages/interface-pubsub-compliance-tests/src/two-nodes.ts b/packages/interface-pubsub-compliance-tests/src/two-nodes.ts new file mode 100644 index 0000000000..77862601d5 --- /dev/null +++ b/packages/interface-pubsub-compliance-tests/src/two-nodes.ts @@ -0,0 +1,273 @@ +/* eslint max-nested-callbacks: ["error", 6] */ +import { mockNetwork } from '@libp2p/interface-mocks' +import { TopicValidatorResult } from '@libp2p/interface-pubsub' +import { start, stop } from '@libp2p/interfaces/startable' +import { expect } from 'aegir/chai' +import pDefer from 'p-defer' +import pWaitFor from 'p-wait-for' +import sinon from 'sinon' +import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' +import { toString as uint8ArrayToString } from 'uint8arrays/to-string' +import { createComponents, waitForSubscriptionUpdate } from './utils.js' +import type { PubSubArgs, PubSubComponents } from './index.js' +import type { TestSetup } from '@libp2p/interface-compliance-tests' +import type { Message, PubSub } from '@libp2p/interface-pubsub' + +const topic = 'foo' + +function shouldNotHappen (): void { + expect.fail() +} + +export default (common: TestSetup): void => { + describe('pubsub with two nodes', () => { + let psA: PubSub + let psB: PubSub + let componentsA: PubSubComponents + let componentsB: PubSubComponents + + // Create pubsub nodes and connect them + beforeEach(async () => { + mockNetwork.reset() + + componentsA = await createComponents() + componentsB = await createComponents() + + psA = componentsA.pubsub = await common.setup({ + components: componentsA, + init: { + emitSelf: true + } + }) + psB = componentsB.pubsub = await common.setup({ + components: componentsB, + init: { + emitSelf: false + } + }) + + // Start pubsub and connect nodes + await start(...Object.values(componentsA), ...Object.values(componentsB)) + + expect(psA.getPeers()).to.be.empty() + expect(psB.getPeers()).to.be.empty() + + await componentsA.connectionManager.openConnection(componentsB.peerId) + + // Wait for peers to be ready in pubsub + await pWaitFor(() => psA.getPeers().length === 1 && psB.getPeers().length === 1) + }) + + afterEach(async () => { + sinon.restore() + await stop(...Object.values(componentsA), ...Object.values(componentsB)) + await common.teardown() + mockNetwork.reset() + }) + + it('Subscribe to a topic in nodeA', async () => { + const defer = pDefer() + + psB.addEventListener('subscription-change', (evt) => { + const { peerId: changedPeerId, subscriptions: changedSubs } = evt.detail + expect(psA.getTopics()).to.deep.equal([topic]) + expect(psB.getPeers()).to.have.lengthOf(1) + expect(psB.getSubscribers(topic).map(p => p.toString())).to.deep.equal([componentsA.peerId.toString()]) + expect(changedPeerId).to.deep.equal(psB.getPeers()[0]) + expect(changedSubs).to.have.lengthOf(1) + expect(changedSubs[0].topic).to.equal(topic) + expect(changedSubs[0].subscribe).to.equal(true) + defer.resolve() + }, { + once: true + }) + psA.subscribe(topic) + + return defer.promise + }) + + it('Publish to a topic in nodeA', async () => { + const defer = pDefer() + + psA.addEventListener('message', (evt) => { + if (evt.detail.topic === topic) { + const msg = evt.detail + expect(uint8ArrayToString(msg.data)).to.equal('hey') + psB.removeEventListener('message', shouldNotHappen) + defer.resolve() + } + }, { + once: true + }) + + psA.subscribe(topic) + psB.subscribe(topic) + + await Promise.all([ + waitForSubscriptionUpdate(psA, componentsB.peerId), + waitForSubscriptionUpdate(psB, componentsA.peerId) + ]) + + await psA.publish(topic, uint8ArrayFromString('hey')) + + return defer.promise + }) + + it('Publish to a topic in nodeB', async () => { + const defer = pDefer() + + psA.addEventListener('message', (evt) => { + if (evt.detail.topic !== topic) { + return + } + + const msg = evt.detail + psA.addEventListener('message', (evt) => { + if (evt.detail.topic === topic) { + shouldNotHappen() + } + }, { + once: true + }) + expect(uint8ArrayToString(msg.data)).to.equal('banana') + + setTimeout(() => { + psA.removeEventListener('message') + psB.removeEventListener('message') + + defer.resolve() + }, 100) + }, { + once: true + }) + + psB.addEventListener('message', shouldNotHappen) + + psA.subscribe(topic) + psB.subscribe(topic) + + await Promise.all([ + waitForSubscriptionUpdate(psA, componentsB.peerId), + waitForSubscriptionUpdate(psB, componentsA.peerId) + ]) + + await psB.publish(topic, uint8ArrayFromString('banana')) + + return defer.promise + }) + + it('validate topic message', async () => { + const defer = pDefer() + + psA.subscribe(topic) + + psB.topicValidators.set(topic, (peer, message) => { + if (!peer.equals(componentsA.peerId)) { + defer.reject(new Error('Invalid peer id in topic validator fn')) + return TopicValidatorResult.Reject + } + + if (uint8ArrayToString(message.data) !== 'hey') { + defer.reject(new Error('Invalid message in topic validator fn')) + return TopicValidatorResult.Reject + } + + defer.resolve() + return TopicValidatorResult.Accept + }) + psB.subscribe(topic) + + await Promise.all([ + waitForSubscriptionUpdate(psA, componentsB.peerId), + waitForSubscriptionUpdate(psB, componentsA.peerId) + ]) + + await psA.publish(topic, uint8ArrayFromString('hey')) + + return defer.promise + }) + + it('Publish 10 msg to a topic in nodeB', async () => { + const defer = pDefer() + let counter = 0 + + psB.addEventListener('message', shouldNotHappen) + psA.addEventListener('message', receivedMsg) + + function receivedMsg (evt: CustomEvent): void { + const msg = evt.detail + if (msg.type === 'unsigned') { + expect(uint8ArrayToString(msg.data)).to.equal('banana') + expect(msg.topic).to.be.equal(topic) + } else { + expect(uint8ArrayToString(msg.data)).to.equal('banana') + expect(msg.from.toString()).to.equal(componentsB.peerId.toString()) + expect(msg.sequenceNumber).to.be.a('BigInt') + expect(msg.topic).to.be.equal(topic) + } + + if (++counter === 10) { + psA.removeEventListener('message', receivedMsg) + psB.removeEventListener('message', shouldNotHappen) + + defer.resolve() + } + } + + psA.subscribe(topic) + psB.subscribe(topic) + + await Promise.all([ + waitForSubscriptionUpdate(psA, componentsB.peerId), + waitForSubscriptionUpdate(psB, componentsA.peerId) + ]) + + await Promise.all( + Array.from({ length: 10 }, async (_, i) => { + await psB.publish(topic, uint8ArrayFromString('banana')) + }) + ) + + return defer.promise + }) + + it('Unsubscribe from topic in nodeA', async () => { + const defer = pDefer() + let callCount = 0 + + psB.addEventListener('subscription-change', (evt) => { + callCount++ + + if (callCount === 1) { + // notice subscribe + const { peerId: changedPeerId, subscriptions: changedSubs } = evt.detail + expect(psB.getPeers()).to.have.lengthOf(1) + expect(psB.getTopics()).to.be.empty() + expect(changedPeerId).to.deep.equal(psB.getPeers()[0]) + expect(changedSubs).to.have.lengthOf(1) + expect(changedSubs[0].topic).to.equal(topic) + expect(changedSubs[0].subscribe).to.equal(true) + } else { + // notice unsubscribe + const { peerId: changedPeerId, subscriptions: changedSubs } = evt.detail + expect(psB.getPeers()).to.have.lengthOf(1) + expect(psB.getTopics()).to.be.empty() + expect(changedPeerId).to.deep.equal(psB.getPeers()[0]) + expect(changedSubs).to.have.lengthOf(1) + expect(changedSubs[0].topic).to.equal(topic) + expect(changedSubs[0].subscribe).to.equal(false) + + defer.resolve() + } + }) + + psA.subscribe(topic) + expect(psA.getTopics()).to.not.be.empty() + + psA.unsubscribe(topic) + expect(psA.getTopics()).to.be.empty() + + return defer.promise + }) + }) +} diff --git a/packages/interface-pubsub-compliance-tests/src/utils.ts b/packages/interface-pubsub-compliance-tests/src/utils.ts new file mode 100644 index 0000000000..80e52c5362 --- /dev/null +++ b/packages/interface-pubsub-compliance-tests/src/utils.ts @@ -0,0 +1,27 @@ +import { mockConnectionManager, mockRegistrar, mockNetwork } from '@libp2p/interface-mocks' +import { createEd25519PeerId } from '@libp2p/peer-id-factory' +import { pEvent } from 'p-event' +import pWaitFor from 'p-wait-for' +import type { MockNetworkComponents } from '@libp2p/interface-mocks' +import type { PeerId } from '@libp2p/interface-peer-id' +import type { PubSub, SubscriptionChangeData } from '@libp2p/interface-pubsub' + +export async function waitForSubscriptionUpdate (a: PubSub, b: PeerId): Promise { + await pWaitFor(async () => { + const event = await pEvent<'subscription-change', CustomEvent>(a, 'subscription-change') + + return event.detail.peerId.equals(b) + }) +} + +export async function createComponents (): Promise { + const components: any = { + peerId: await createEd25519PeerId(), + registrar: mockRegistrar() + } + components.connectionManager = mockConnectionManager(components) + + mockNetwork.addNode(components) + + return components +} diff --git a/packages/interface-pubsub-compliance-tests/tsconfig.json b/packages/interface-pubsub-compliance-tests/tsconfig.json new file mode 100644 index 0000000000..e128c8d1e9 --- /dev/null +++ b/packages/interface-pubsub-compliance-tests/tsconfig.json @@ -0,0 +1,33 @@ +{ + "extends": "aegir/src/config/tsconfig.aegir.json", + "compilerOptions": { + "outDir": "dist" + }, + "include": [ + "src", + "test" + ], + "references": [ + { + "path": "../interface-compliance-tests" + }, + { + "path": "../interface-connection-manager" + }, + { + "path": "../interface-mocks" + }, + { + "path": "../interface-peer-id" + }, + { + "path": "../interface-pubsub" + }, + { + "path": "../interface-registrar" + }, + { + "path": "../interfaces" + } + ] +} diff --git a/packages/interface-pubsub/CHANGELOG.md b/packages/interface-pubsub/CHANGELOG.md new file mode 100644 index 0000000000..97617fba67 --- /dev/null +++ b/packages/interface-pubsub/CHANGELOG.md @@ -0,0 +1,148 @@ +## [@libp2p/interface-pubsub-v4.0.1](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-pubsub-v4.0.0...@libp2p/interface-pubsub-v4.0.1) (2023-05-04) + + +### Dependencies + +* bump aegir from 38.1.8 to 39.0.5 ([#393](https://github.com/libp2p/js-libp2p-interfaces/issues/393)) ([31f3797](https://github.com/libp2p/js-libp2p-interfaces/commit/31f3797b24f7c23f3f16e9db3a230bd5f7cd5175)) + +## [@libp2p/interface-pubsub-v4.0.0](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-pubsub-v3.0.7...@libp2p/interface-pubsub-v4.0.0) (2023-04-18) + + +### ⚠ BREAKING CHANGES + +* bump it-stream-types from 1.0.5 to 2.0.1 (#362) + +### Dependencies + +* bump it-stream-types from 1.0.5 to 2.0.1 ([#362](https://github.com/libp2p/js-libp2p-interfaces/issues/362)) ([cdc7747](https://github.com/libp2p/js-libp2p-interfaces/commit/cdc774792beead63e0ded96bd6c23de0335a49e3)) +* update sibling dependencies ([2f52a28](https://github.com/libp2p/js-libp2p-interfaces/commit/2f52a284b59c0a88b040f86da1f5d3f044727f2c)) + +## [@libp2p/interface-pubsub-v3.0.7](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-pubsub-v3.0.6...@libp2p/interface-pubsub-v3.0.7) (2023-04-11) + + +### Dependencies + +* update sibling dependencies ([b034810](https://github.com/libp2p/js-libp2p-interfaces/commit/b0348102e41dc18166e70063f4708a2b3544f4b6)) + +## [@libp2p/interface-pubsub-v3.0.6](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-pubsub-v3.0.5...@libp2p/interface-pubsub-v3.0.6) (2023-01-18) + + +### Trivial Changes + +* remove lerna ([#330](https://github.com/libp2p/js-libp2p-interfaces/issues/330)) ([6678592](https://github.com/libp2p/js-libp2p-interfaces/commit/6678592dd0cf601a2671852f9d2a0aff5dee2b18)) + + +### Dependencies + +* bump aegir from 37.12.1 to 38.1.0 ([#335](https://github.com/libp2p/js-libp2p-interfaces/issues/335)) ([7368a36](https://github.com/libp2p/js-libp2p-interfaces/commit/7368a363423a08e8fa247dcb76ea13e4cf030d65)) + +## [@libp2p/interface-pubsub-v3.0.5](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-pubsub-v3.0.4...@libp2p/interface-pubsub-v3.0.5) (2023-01-06) + + +### Dependencies + +* update sibling dependencies ([b50e621](https://github.com/libp2p/js-libp2p-interfaces/commit/b50e621d31a8b32affc3fadb9f97c4883d577f93)) + +## [@libp2p/interface-pubsub-v3.0.4](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-pubsub-v3.0.3...@libp2p/interface-pubsub-v3.0.4) (2022-12-19) + + +### Documentation + +* add interface docs ([#324](https://github.com/libp2p/js-libp2p-interfaces/issues/324)) ([2789445](https://github.com/libp2p/js-libp2p-interfaces/commit/278944594c24e1a3c4b3624a35680d69166546d7)) + +## [@libp2p/interface-pubsub-v3.0.3](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-pubsub-v3.0.2...@libp2p/interface-pubsub-v3.0.3) (2022-12-16) + + +### Documentation + +* update project config ([#323](https://github.com/libp2p/js-libp2p-interfaces/issues/323)) ([0fc6a08](https://github.com/libp2p/js-libp2p-interfaces/commit/0fc6a08e9cdcefe361fe325281a3a2a03759ff59)) + +## [@libp2p/interface-pubsub-v3.0.2](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-pubsub-v3.0.1...@libp2p/interface-pubsub-v3.0.2) (2022-12-14) + + +### Bug Fixes + +* generate docs for all packages ([#321](https://github.com/libp2p/js-libp2p-interfaces/issues/321)) ([b6f8b32](https://github.com/libp2p/js-libp2p-interfaces/commit/b6f8b32a920c15a28fe021e6050e31aaae89d518)) + +## [@libp2p/interface-pubsub-v3.0.1](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-pubsub-v3.0.0...@libp2p/interface-pubsub-v3.0.1) (2022-11-05) + + +### Bug Fixes + +* update project config ([#311](https://github.com/libp2p/js-libp2p-interfaces/issues/311)) ([27dd0ce](https://github.com/libp2p/js-libp2p-interfaces/commit/27dd0ce3c249892ac69cbb24ddaf0b9f32385e37)) + +## [@libp2p/interface-pubsub-v3.0.0](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-pubsub-v2.1.0...@libp2p/interface-pubsub-v3.0.0) (2022-10-11) + + +### ⚠ BREAKING CHANGES + +* add topicValidators to pubsub interface (#298) + +### Bug Fixes + +* add topicValidators to pubsub interface ([#298](https://github.com/libp2p/js-libp2p-interfaces/issues/298)) ([e5ff819](https://github.com/libp2p/js-libp2p-interfaces/commit/e5ff819c6dd235b2ea9ea5133457b384c4411cf3)) + +## [@libp2p/interface-pubsub-v2.1.0](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-pubsub-v2.0.1...@libp2p/interface-pubsub-v2.1.0) (2022-09-09) + + +### Features + +* add dialer interface ([#285](https://github.com/libp2p/js-libp2p-interfaces/issues/285)) ([1e62df4](https://github.com/libp2p/js-libp2p-interfaces/commit/1e62df4f15b45abe62fe8400dbd88866a2bc13cd)) + +## [@libp2p/interface-pubsub-v2.0.1](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-pubsub-v2.0.0...@libp2p/interface-pubsub-v2.0.1) (2022-08-07) + + +### Dependencies + +* update sibling dependencies ([f859920](https://github.com/libp2p/js-libp2p-interfaces/commit/f859920423587ae797ac90ccaa3af8bdf60ae549)) + +## [@libp2p/interface-pubsub-v2.0.0](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-pubsub-v1.0.4...@libp2p/interface-pubsub-v2.0.0) (2022-07-31) + + +### ⚠ BREAKING CHANGES + +* The `Message` type is now either a `SignedMessage` +or a `UnsignedMessage` +* the inbound/outbound stream types have changed + +### Features + +* pubsub Message types for signature policies ([#266](https://github.com/libp2p/js-libp2p-interfaces/issues/266)) ([9eb710b](https://github.com/libp2p/js-libp2p-interfaces/commit/9eb710bcbdb0aef95c7a8613e00065a3b7c7f887)) + + +### Bug Fixes + +* make stream types Uint8ArrayLists ([#272](https://github.com/libp2p/js-libp2p-interfaces/issues/272)) ([ace7e0c](https://github.com/libp2p/js-libp2p-interfaces/commit/ace7e0cdb81dd241a8e96a44e841d38b2b80e031)) + + +### Trivial Changes + +* update project config ([#271](https://github.com/libp2p/js-libp2p-interfaces/issues/271)) ([59c0bf5](https://github.com/libp2p/js-libp2p-interfaces/commit/59c0bf5e0b05496fca2e4902632b61bb41fad9e9)) + +## [@libp2p/interface-pubsub-v1.0.4](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-pubsub-v1.0.3...@libp2p/interface-pubsub-v1.0.4) (2022-06-27) + + +### Trivial Changes + +* update deps ([#262](https://github.com/libp2p/js-libp2p-interfaces/issues/262)) ([51edf7d](https://github.com/libp2p/js-libp2p-interfaces/commit/51edf7d9b3765a6f75c915b1483ea345d0133a41)) + +## [@libp2p/interface-pubsub-v1.0.3](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-pubsub-v1.0.2...@libp2p/interface-pubsub-v1.0.3) (2022-06-17) + + +### Bug Fixes + +* add stream control for pubsub ([#248](https://github.com/libp2p/js-libp2p-interfaces/issues/248)) ([e687895](https://github.com/libp2p/js-libp2p-interfaces/commit/e687895267d98fcd99d6d0d849527ab9eed69695)) + +## [@libp2p/interface-pubsub-v1.0.2](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-pubsub-v1.0.1...@libp2p/interface-pubsub-v1.0.2) (2022-06-16) + + +### Trivial Changes + +* update deps ([545264f](https://github.com/libp2p/js-libp2p-interfaces/commit/545264f87a58394d2a7da77e93f3a596e889238f)) + +## [@libp2p/interface-pubsub-v1.0.1](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-pubsub-v1.0.0...@libp2p/interface-pubsub-v1.0.1) (2022-06-14) + + +### Trivial Changes + +* update it-pushable dep ([#237](https://github.com/libp2p/js-libp2p-interfaces/issues/237)) ([2e16465](https://github.com/libp2p/js-libp2p-interfaces/commit/2e164658df344b5ec475be2a571df5d6f20ee086)) diff --git a/packages/interface-pubsub/LICENSE b/packages/interface-pubsub/LICENSE new file mode 100644 index 0000000000..20ce483c86 --- /dev/null +++ b/packages/interface-pubsub/LICENSE @@ -0,0 +1,4 @@ +This project is dual licensed under MIT and Apache-2.0. + +MIT: https://www.opensource.org/licenses/mit +Apache-2.0: https://www.apache.org/licenses/license-2.0 diff --git a/packages/interface-pubsub/LICENSE-APACHE b/packages/interface-pubsub/LICENSE-APACHE new file mode 100644 index 0000000000..14478a3b60 --- /dev/null +++ b/packages/interface-pubsub/LICENSE-APACHE @@ -0,0 +1,5 @@ +Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. diff --git a/packages/interface-pubsub/LICENSE-MIT b/packages/interface-pubsub/LICENSE-MIT new file mode 100644 index 0000000000..72dc60d84b --- /dev/null +++ b/packages/interface-pubsub/LICENSE-MIT @@ -0,0 +1,19 @@ +The MIT License (MIT) + +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/packages/interface-pubsub/README.md b/packages/interface-pubsub/README.md new file mode 100644 index 0000000000..c5d0dbf9f9 --- /dev/null +++ b/packages/interface-pubsub/README.md @@ -0,0 +1,323 @@ +# @libp2p/interface-pubsub + +[![libp2p.io](https://img.shields.io/badge/project-libp2p-yellow.svg?style=flat-square)](http://libp2p.io/) +[![Discuss](https://img.shields.io/discourse/https/discuss.libp2p.io/posts.svg?style=flat-square)](https://discuss.libp2p.io) +[![codecov](https://img.shields.io/codecov/c/github/libp2p/js-libp2p.svg?style=flat-square)](https://codecov.io/gh/libp2p/js-libp2p) +[![CI](https://img.shields.io/github/actions/workflow/status/libp2p/js-libp2p/js-test-and-release.yml?branch=master\&style=flat-square)](https://github.com/libp2p/js-libp2p/actions/workflows/js-test-and-release.yml?query=branch%3Amaster) + +> PubSub interface for libp2p + +## Table of contents + +- - [Install](#install) +- [Table of Contents ](#table-of-contents----omit-in-toc---) + - [Implementations using this interface](#implementations-using-this-interface) + - [Interface usage](#interface-usage) + - [Extend interface](#extend-interface) + - [Example](#example) + - [API](#api) + - [Constructor](#constructor) + - [`new Pubsub({options})`](#new-pubsuboptions) + - [Parameters](#parameters) + - [Start](#start) + - [`pubsub.start()`](#pubsubstart) + - [Stop](#stop) + - [`pubsub.stop()`](#pubsubstop) + - [Publish](#publish) + - [`pubsub.publish(topic, message)`](#pubsubpublishtopic-message) + - [Parameters](#parameters-1) + - [Returns](#returns) + - [Subscribe](#subscribe) + - [`pubsub.subscribe(topic)`](#pubsubsubscribetopic) + - [Parameters](#parameters-2) + - [Unsubscribe](#unsubscribe) + - [`pubsub.unsubscribe(topic)`](#pubsubunsubscribetopic) + - [Parameters](#parameters-3) + - [Get Topics](#get-topics) + - [`pubsub.getTopics()`](#pubsubgettopics) + - [Returns](#returns-1) + - [Get Peers Subscribed to a topic](#get-peers-subscribed-to-a-topic) + - [`pubsub.getSubscribers(topic)`](#pubsubgetsubscriberstopic) + - [Parameters](#parameters-4) + - [Returns](#returns-2) + - [Validate](#validate) + - [`pubsub.validate(message)`](#pubsubvalidatemessage) + - [Parameters](#parameters-5) + - [Returns](#returns-3) + - [Test suite usage](#test-suite-usage) + - [API Docs](#api-docs) + - [License](#license) + - [Contribution](#contribution) + +## Install + +```console +$ npm i @libp2p/interface-pubsub +``` + +The `interface-pubsub` contains the base implementation for a libp2p pubsub router implementation. This interface should be used to implement a pubsub router compatible with libp2p. It includes a test suite that pubsub routers should run, in order to ensure compatibility with libp2p. + +# Table of Contents + +- [Implementations using this interface](#implementations-using-this-interface) +- [Interface usage](#interface-usage) + - [Extend interface](#extend-interface) + - [Example](#example) +- [API](#api) + - [Constructor](#constructor) + - [`new Pubsub({options})`](#new-pubsuboptions) + - [Parameters](#parameters) + - [Start](#start) + - [`pubsub.start()`](#pubsubstart) + - [Stop](#stop) + - [`pubsub.stop()`](#pubsubstop) + - [Publish](#publish) + - [`pubsub.publish(topic, message)`](#pubsubpublishtopic-message) + - [Parameters](#parameters-1) + - [Returns](#returns) + - [Subscribe](#subscribe) + - [`pubsub.subscribe(topic)`](#pubsubsubscribetopic) + - [Parameters](#parameters-2) + - [Unsubscribe](#unsubscribe) + - [`pubsub.unsubscribe(topic)`](#pubsubunsubscribetopic) + - [Parameters](#parameters-3) + - [Get Topics](#get-topics) + - [`pubsub.getTopics()`](#pubsubgettopics) + - [Returns](#returns-1) + - [Get Peers Subscribed to a topic](#get-peers-subscribed-to-a-topic) + - [`pubsub.getSubscribers(topic)`](#pubsubgetsubscriberstopic) + - [Parameters](#parameters-4) + - [Returns](#returns-2) + - [Validate](#validate) + - [`pubsub.validate(message)`](#pubsubvalidatemessage) + - [Parameters](#parameters-5) + - [Returns](#returns-3) +- [Test suite usage](#test-suite-usage) +- [License](#license) + - [Contribution](#contribution) + +## Implementations using this interface + +You can check the following implementations as examples for building your own pubsub router. + +- [libp2p/js-libp2p-floodsub](https://github.com/libp2p/js-libp2p-floodsub) +- [ChainSafe/js-libp2p-gossipsub](https://github.com/ChainSafe/js-libp2p-gossipsub) + +## Interface usage + +`interface-pubsub` abstracts the implementation protocol registration within `libp2p` and takes care of all the protocol connections and streams, as well as the subscription management and the features describe in the libp2p [pubsub specs](https://github.com/libp2p/specs/tree/master/pubsub). This way, a pubsub implementation can focus on its message routing algorithm, instead of also needing to create the setup for it. + +### Extend interface + +A pubsub router implementation should start by extending the `interface-pubsub` class and **MUST** override the `_publish` function, according to the router algorithms. This function is responsible for forwarding publish messages to other peers, as well as forwarding received messages if the router provides the `canRelayMessage` option to the base implementation. + +Other functions, such as `start`, `stop`, `subscribe`, `unsubscribe`, `_encodeRpc`, `_decodeRpc`, `_processRpcMessage`, `_addPeer` and `_removePeer` may be overwritten if the pubsub implementation needs to customize their logic. Implementations overriding these functions **MUST** call `super`. + +The `start` and `stop` functions are responsible for the registration of the pubsub protocol with libp2p. The `stop` function also guarantees that the open streams in the protocol are properly closed. + +The `subscribe` and `unsubscribe` functions take care of the subscription management and its inherent message propagation. + +When using a custom protobuf definition for message marshalling, you should override `_encodeRpc` and `_decodeRpc` to use the new protobuf instead of the default one. + +`_processRpcMessage` is responsible for handling messages received from other peers. This should be extended if further operations/validations are needed by the router. + +The `_addPeer` and `_removePeer` functions are called when new peers running the pubsub router protocol establish a connection with the peer. They are used for tracking the open streams between the peers. + +All the remaining functions **MUST NOT** be overwritten. + +### Example + +The following example aims to show how to create your pubsub implementation extending this base protocol. The pubsub implementation will handle the subscriptions logic. + +```JavaScript +const Pubsub = require('libp2p-interfaces/src/pubsub') + +class PubsubImplementation extends Pubsub { + constructor({ libp2p, options }) + super({ + debugName: 'libp2p:pubsub', + multicodecs: '/pubsub-implementation/1.0.0', + libp2p, + globalSigningPolicy: options.globalSigningPolicy + }) + } + + _publish (message) { + // Required to be implemented by the subclass + // Routing logic for the message + } +} +``` + +## API + +The interface aims to specify a common interface that all pubsub router implementation should follow. A pubsub router implementation should extend the [EventEmitter](https://nodejs.org/api/events.html#events_class_eventemitter). When peers receive pubsub messages, these messages will be emitted by the event emitter where the `eventName` will be the `topic` associated with the message. + +### Constructor + +The base class constructor configures the pubsub instance for use with a libp2p instance. It includes settings for logging, signature policies, etc. + +#### `new Pubsub({options})` + +##### Parameters + +| Name | Type | Description | Default | +| ----------------------------- | -------------------------------- | ----------------------------------------------- | -------------------- | +| options.libp2p | `Libp2p` | libp2p instance | required, no default | +| options.debugName | `string` | log namespace | required, no default | +| options.multicodecs | `string \| Array` | protocol identifier(s) | required, no default | +| options.globalSignaturePolicy | `'StrictSign' \| 'StrictNoSign'` | signature policy to be globally applied | `'StrictSign'` | +| options.canRelayMessage | `boolean` | if can relay messages if not subscribed | `false` | +| options.emitSelf | `boolean` | if `publish` should emit to self, if subscribed | `false` | + +### Start + +Starts the pubsub subsystem. The protocol will be registered to `libp2p`, which will result in pubsub being notified when peers who support the protocol connect/disconnect to `libp2p`. + +#### `pubsub.start()` + +### Stop + +Stops the pubsub subsystem. The protocol will be unregistered from `libp2p`, which will remove all listeners for the protocol and the established connections will be closed. + +#### `pubsub.stop()` + +### Publish + +Publish data message to pubsub topics. + +#### `pubsub.publish(topic, message)` + +##### Parameters + +| Name | Type | Description | +| ------- | ------------ | ------------------ | +| topic | `string` | pubsub topic | +| message | `Uint8Array` | message to publish | + +##### Returns + +| Type | Description | +| --------------- | ----------------------------------------------------- | +| `Promise` | resolves once the message is published to the network | + +### Subscribe + +Subscribe to the given topic. + +#### `pubsub.subscribe(topic)` + +##### Parameters + +| Name | Type | Description | +| ----- | -------- | ------------ | +| topic | `string` | pubsub topic | + +### Unsubscribe + +Unsubscribe from the given topic. + +#### `pubsub.unsubscribe(topic)` + +##### Parameters + +| Name | Type | Description | +| ----- | -------- | ------------ | +| topic | `string` | pubsub topic | + +### Get Topics + +Get the list of topics which the peer is subscribed to. + +#### `pubsub.getTopics()` + +##### Returns + +| Type | Description | +| --------------- | -------------------------- | +| `Array` | Array of subscribed topics | + +### Get Peers Subscribed to a topic + +Get a list of the [PeerId](https://github.com/libp2p/js-peer-id) strings that are subscribed to one topic. + +#### `pubsub.getSubscribers(topic)` + +##### Parameters + +| Name | Type | Description | +| ----- | -------- | ------------ | +| topic | `string` | pubsub topic | + +##### Returns + +| Type | Description | +| --------------- | ------------------------- | +| `Array` | Array of base-58 PeerId's | + +### Validate + +Validates a message according to the signature policy and topic-specific validation function. + +#### `pubsub.validate(message)` + +##### Parameters + +| Name | Type | Description | +| ------- | --------- | ---------------- | +| message | `Message` | a pubsub message | + +#### Returns + +| Type | Description | +| --------------- | -------------------------------- | +| `Promise` | resolves if the message is valid | + +## Test suite usage + +```js +'use strict' + +const tests = require('libp2p-interfaces-compliance-tests/pubsub') +const YourPubsubRouter = require('../src') + +describe('compliance', () => { + let peers + let pubsubNodes = [] + + tests({ + async setup (number = 1, options = {}) { + // Create number pubsub nodes with libp2p + peers = await createPeers({ number }) + + peers.forEach((peer) => { + const ps = new YourPubsubRouter(peer, options) + + pubsubNodes.push(ps) + }) + + return pubsubNodes + }, + async teardown () { + // Clean up any resources created by setup() + await Promise.all(pubsubNodes.map(ps => ps.stop())) + peers.length && await Promise.all(peers.map(peer => peer.stop())) + } + }) +}) +``` + +## API Docs + +- + +## License + +Licensed under either of + +- Apache 2.0, ([LICENSE-APACHE](LICENSE-APACHE) / ) +- MIT ([LICENSE-MIT](LICENSE-MIT) / ) + +## Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. diff --git a/packages/interface-pubsub/package.json b/packages/interface-pubsub/package.json new file mode 100644 index 0000000000..f10d63a753 --- /dev/null +++ b/packages/interface-pubsub/package.json @@ -0,0 +1,143 @@ +{ + "name": "@libp2p/interface-pubsub", + "version": "4.0.1", + "description": "PubSub interface for libp2p", + "license": "Apache-2.0 OR MIT", + "homepage": "https://github.com/libp2p/js-libp2p/tree/master/packages/interface-pubsub#readme", + "repository": { + "type": "git", + "url": "git+https://github.com/libp2p/js-libp2p.git" + }, + "bugs": { + "url": "https://github.com/libp2p/js-libp2p/issues" + }, + "keywords": [ + "interface", + "libp2p" + ], + "type": "module", + "types": "./dist/src/index.d.ts", + "files": [ + "src", + "dist", + "!dist/test", + "!**/*.tsbuildinfo" + ], + "exports": { + ".": { + "types": "./dist/src/index.d.ts", + "import": "./dist/src/index.js" + } + }, + "eslintConfig": { + "extends": "ipfs", + "parserOptions": { + "sourceType": "module" + } + }, + "release": { + "branches": [ + "master" + ], + "plugins": [ + [ + "@semantic-release/commit-analyzer", + { + "preset": "conventionalcommits", + "releaseRules": [ + { + "breaking": true, + "release": "major" + }, + { + "revert": true, + "release": "patch" + }, + { + "type": "feat", + "release": "minor" + }, + { + "type": "fix", + "release": "patch" + }, + { + "type": "docs", + "release": "patch" + }, + { + "type": "test", + "release": "patch" + }, + { + "type": "deps", + "release": "patch" + }, + { + "scope": "no-release", + "release": false + } + ] + } + ], + [ + "@semantic-release/release-notes-generator", + { + "preset": "conventionalcommits", + "presetConfig": { + "types": [ + { + "type": "feat", + "section": "Features" + }, + { + "type": "fix", + "section": "Bug Fixes" + }, + { + "type": "chore", + "section": "Trivial Changes" + }, + { + "type": "docs", + "section": "Documentation" + }, + { + "type": "deps", + "section": "Dependencies" + }, + { + "type": "test", + "section": "Tests" + } + ] + } + } + ], + "@semantic-release/changelog", + "@semantic-release/npm", + "@semantic-release/github", + "@semantic-release/git" + ] + }, + "scripts": { + "clean": "aegir clean", + "lint": "aegir lint", + "dep-check": "aegir dep-check", + "build": "aegir build", + "release": "aegir release" + }, + "dependencies": { + "@libp2p/interface-connection": "^5.0.0", + "@libp2p/interface-peer-id": "^2.0.0", + "@libp2p/interfaces": "^3.0.0", + "it-pushable": "^3.1.3", + "uint8arraylist": "^2.4.3" + }, + "devDependencies": { + "aegir": "^39.0.5" + }, + "typedoc": { + "entryPoint": "./src/index.ts" + } +} diff --git a/packages/interface-pubsub/src/index.ts b/packages/interface-pubsub/src/index.ts new file mode 100644 index 0000000000..575bebfb47 --- /dev/null +++ b/packages/interface-pubsub/src/index.ts @@ -0,0 +1,269 @@ +import type { Stream } from '@libp2p/interface-connection' +import type { PeerId } from '@libp2p/interface-peer-id' +import type { EventEmitter } from '@libp2p/interfaces/events' +import type { Pushable } from 'it-pushable' +import type { Uint8ArrayList } from 'uint8arraylist' + +/** + * On the producing side: + * * Build messages with the signature, key (from may be enough for certain inlineable public key types), from and seqno fields. + * + * On the consuming side: + * * Enforce the fields to be present, reject otherwise. + * * Propagate only if the fields are valid and signature can be verified, reject otherwise. + */ +export const StrictSign = 'StrictSign' + +/** + * On the producing side: + * * Build messages without the signature, key, from and seqno fields. + * * The corresponding protobuf key-value pairs are absent from the marshalled message, not just empty. + * + * On the consuming side: + * * Enforce the fields to be absent, reject otherwise. + * * Propagate only if the fields are absent, reject otherwise. + * * A message_id function will not be able to use the above fields, and should instead rely on the data field. A commonplace strategy is to calculate a hash. + */ +export const StrictNoSign = 'StrictNoSign' + +export type SignaturePolicy = typeof StrictSign | typeof StrictNoSign + +export interface SignedMessage { + type: 'signed' + from: PeerId + topic: string + data: Uint8Array + sequenceNumber: bigint + signature: Uint8Array + key: Uint8Array +} + +export interface UnsignedMessage { + type: 'unsigned' + topic: string + data: Uint8Array +} + +export type Message = SignedMessage | UnsignedMessage + +export interface PubSubRPCMessage { + from?: Uint8Array + topic?: string + data?: Uint8Array + sequenceNumber?: Uint8Array + signature?: Uint8Array + key?: Uint8Array +} + +export interface PubSubRPCSubscription { + subscribe?: boolean + topic?: string +} + +export interface PubSubRPC { + subscriptions: PubSubRPCSubscription[] + messages: PubSubRPCMessage[] +} + +export interface PeerStreams extends EventEmitter { + id: PeerId + protocol: string + outboundStream?: Pushable + inboundStream?: AsyncIterable + isWritable: boolean + + close: () => void + write: (buf: Uint8Array | Uint8ArrayList) => void + attachInboundStream: (stream: Stream) => AsyncIterable + attachOutboundStream: (stream: Stream) => Promise> +} + +export interface PubSubInit { + enabled?: boolean + + multicodecs?: string[] + + /** + * defines how signatures should be handled + */ + globalSignaturePolicy?: SignaturePolicy + + /** + * if can relay messages not subscribed + */ + canRelayMessage?: boolean + + /** + * if publish should emit to self, if subscribed + */ + emitSelf?: boolean + + /** + * handle this many incoming pubsub messages concurrently + */ + messageProcessingConcurrency?: number + + /** + * How many parallel incoming streams to allow on the pubsub protocol per-connection + */ + maxInboundStreams?: number + + /** + * How many parallel outgoing streams to allow on the pubsub protocol per-connection + */ + maxOutboundStreams?: number +} + +interface Subscription { + topic: string + subscribe: boolean +} + +export interface SubscriptionChangeData { + peerId: PeerId + subscriptions: Subscription[] +} + +export interface PubSubEvents { + 'subscription-change': CustomEvent + 'message': CustomEvent +} + +export interface PublishResult { + recipients: PeerId[] +} + +export enum TopicValidatorResult { + /** + * The message is considered valid, and it should be delivered and forwarded to the network + */ + Accept = 'accept', + /** + * The message is neither delivered nor forwarded to the network + */ + Ignore = 'ignore', + /** + * The message is considered invalid, and it should be rejected + */ + Reject = 'reject' +} + +export interface TopicValidatorFn { + (peer: PeerId, message: Message): TopicValidatorResult | Promise +} + +export interface PubSub = PubSubEvents> extends EventEmitter { + /** + * The global signature policy controls whether or not we sill send and receive + * signed or unsigned messages. + * + * Signed messages prevent spoofing message senders and should be preferred to + * using unsigned messages. + */ + globalSignaturePolicy: typeof StrictSign | typeof StrictNoSign + + /** + * A list of multicodecs that contain the pubsub protocol name. + */ + multicodecs: string[] + + /** + * Pubsub routers support message validators per topic, which will validate the message + * before its propagations. They are stored in a map where keys are the topic name and + * values are the validators. + * + * @example + * + * ```js + * const topic = 'topic' + * const validateMessage = (msgTopic, msg) => { + * const input = uint8ArrayToString(msg.data) + * const validInputs = ['a', 'b', 'c'] + * + * if (!validInputs.includes(input)) { + * throw new Error('no valid input received') + * } + * } + * libp2p.pubsub.topicValidators.set(topic, validateMessage) + * ``` + */ + topicValidators: Map + + getPeers: () => PeerId[] + + /** + * Gets a list of topics the node is subscribed to. + * + * ```js + * const topics = libp2p.pubsub.getTopics() + * ``` + */ + getTopics: () => string[] + + /** + * Subscribes to a pubsub topic. + * + * @example + * + * ```js + * const topic = 'topic' + * const handler = (msg) => { + * if (msg.topic === topic) { + * // msg.data - pubsub data received + * } + * } + * + * libp2p.pubsub.addEventListener('message', handler) + * libp2p.pubsub.subscribe(topic) + * ``` + */ + subscribe: (topic: string) => void + + /** + * Unsubscribes from a pubsub topic. + * + * @example + * + * ```js + * const topic = 'topic' + * const handler = (msg) => { + * // msg.data - pubsub data received + * } + * + * libp2p.pubsub.removeEventListener(topic handler) + * libp2p.pubsub.unsubscribe(topic) + * ``` + */ + unsubscribe: (topic: string) => void + + /** + * Gets a list of the PeerIds that are subscribed to one topic. + * + * @example + * + * ```js + * const peerIds = libp2p.pubsub.getSubscribers(topic) + * ``` + */ + getSubscribers: (topic: string) => PeerId[] + + /** + * Publishes messages to the given topic. + * + * @example + * + * ```js + * const topic = 'topic' + * const data = uint8ArrayFromString('data') + * + * await libp2p.pubsub.publish(topic, data) + * ``` + */ + publish: (topic: string, data: Uint8Array) => Promise +} + +export interface PeerStreamEvents { + 'stream:inbound': CustomEvent + 'stream:outbound': CustomEvent + 'close': CustomEvent +} diff --git a/packages/interface-pubsub/tsconfig.json b/packages/interface-pubsub/tsconfig.json new file mode 100644 index 0000000000..cbde3d05cb --- /dev/null +++ b/packages/interface-pubsub/tsconfig.json @@ -0,0 +1,20 @@ +{ + "extends": "aegir/src/config/tsconfig.aegir.json", + "compilerOptions": { + "outDir": "dist" + }, + "include": [ + "src" + ], + "references": [ + { + "path": "../interface-connection" + }, + { + "path": "../interface-peer-id" + }, + { + "path": "../interfaces" + } + ] +} diff --git a/packages/interface-record-compliance-tests/CHANGELOG.md b/packages/interface-record-compliance-tests/CHANGELOG.md new file mode 100644 index 0000000000..930998e607 --- /dev/null +++ b/packages/interface-record-compliance-tests/CHANGELOG.md @@ -0,0 +1,63 @@ +## [@libp2p/interface-record-compliance-tests-v2.0.5](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-record-compliance-tests-v2.0.4...@libp2p/interface-record-compliance-tests-v2.0.5) (2023-05-04) + + +### Dependencies + +* bump aegir from 38.1.8 to 39.0.5 ([#393](https://github.com/libp2p/js-libp2p-interfaces/issues/393)) ([31f3797](https://github.com/libp2p/js-libp2p-interfaces/commit/31f3797b24f7c23f3f16e9db3a230bd5f7cd5175)) + +## [@libp2p/interface-record-compliance-tests-v2.0.4](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-record-compliance-tests-v2.0.3...@libp2p/interface-record-compliance-tests-v2.0.4) (2023-01-18) + + +### Trivial Changes + +* remove lerna ([#330](https://github.com/libp2p/js-libp2p-interfaces/issues/330)) ([6678592](https://github.com/libp2p/js-libp2p-interfaces/commit/6678592dd0cf601a2671852f9d2a0aff5dee2b18)) + + +### Dependencies + +* bump aegir from 37.12.1 to 38.1.0 ([#335](https://github.com/libp2p/js-libp2p-interfaces/issues/335)) ([7368a36](https://github.com/libp2p/js-libp2p-interfaces/commit/7368a363423a08e8fa247dcb76ea13e4cf030d65)) + +## [@libp2p/interface-record-compliance-tests-v2.0.3](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-record-compliance-tests-v2.0.2...@libp2p/interface-record-compliance-tests-v2.0.3) (2022-12-16) + + +### Documentation + +* update project config ([#323](https://github.com/libp2p/js-libp2p-interfaces/issues/323)) ([0fc6a08](https://github.com/libp2p/js-libp2p-interfaces/commit/0fc6a08e9cdcefe361fe325281a3a2a03759ff59)) + +## [@libp2p/interface-record-compliance-tests-v2.0.2](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-record-compliance-tests-v2.0.1...@libp2p/interface-record-compliance-tests-v2.0.2) (2022-12-14) + + +### Bug Fixes + +* generate docs for all packages ([#321](https://github.com/libp2p/js-libp2p-interfaces/issues/321)) ([b6f8b32](https://github.com/libp2p/js-libp2p-interfaces/commit/b6f8b32a920c15a28fe021e6050e31aaae89d518)) + +## [@libp2p/interface-record-compliance-tests-v2.0.1](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-record-compliance-tests-v2.0.0...@libp2p/interface-record-compliance-tests-v2.0.1) (2022-11-05) + + +### Bug Fixes + +* update project config ([#311](https://github.com/libp2p/js-libp2p-interfaces/issues/311)) ([27dd0ce](https://github.com/libp2p/js-libp2p-interfaces/commit/27dd0ce3c249892ac69cbb24ddaf0b9f32385e37)) + +## [@libp2p/interface-record-compliance-tests-v2.0.0](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-record-compliance-tests-v1.0.1...@libp2p/interface-record-compliance-tests-v2.0.0) (2022-08-03) + + +### ⚠ BREAKING CHANGES + +* return type of .marshal method and several properties have changed + +### Bug Fixes + +* make record property and return types lists ([#276](https://github.com/libp2p/js-libp2p-interfaces/issues/276)) ([4df31d0](https://github.com/libp2p/js-libp2p-interfaces/commit/4df31d0a1da48dcffd3644e817b0641dca7bd111)) + + +### Trivial Changes + +* update project config ([#271](https://github.com/libp2p/js-libp2p-interfaces/issues/271)) ([59c0bf5](https://github.com/libp2p/js-libp2p-interfaces/commit/59c0bf5e0b05496fca2e4902632b61bb41fad9e9)) +* update sibling dependencies [skip ci] ([39eed35](https://github.com/libp2p/js-libp2p-interfaces/commit/39eed35c17920032ef821eede4d09fe14f8b30ab)) + +## [@libp2p/interface-record-compliance-tests-v1.0.1](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-record-compliance-tests-v1.0.0...@libp2p/interface-record-compliance-tests-v1.0.1) (2022-06-27) + + +### Trivial Changes + +* update deps ([#262](https://github.com/libp2p/js-libp2p-interfaces/issues/262)) ([51edf7d](https://github.com/libp2p/js-libp2p-interfaces/commit/51edf7d9b3765a6f75c915b1483ea345d0133a41)) diff --git a/packages/interface-record-compliance-tests/LICENSE b/packages/interface-record-compliance-tests/LICENSE new file mode 100644 index 0000000000..20ce483c86 --- /dev/null +++ b/packages/interface-record-compliance-tests/LICENSE @@ -0,0 +1,4 @@ +This project is dual licensed under MIT and Apache-2.0. + +MIT: https://www.opensource.org/licenses/mit +Apache-2.0: https://www.apache.org/licenses/license-2.0 diff --git a/packages/interface-record-compliance-tests/LICENSE-APACHE b/packages/interface-record-compliance-tests/LICENSE-APACHE new file mode 100644 index 0000000000..14478a3b60 --- /dev/null +++ b/packages/interface-record-compliance-tests/LICENSE-APACHE @@ -0,0 +1,5 @@ +Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. diff --git a/packages/interface-record-compliance-tests/LICENSE-MIT b/packages/interface-record-compliance-tests/LICENSE-MIT new file mode 100644 index 0000000000..72dc60d84b --- /dev/null +++ b/packages/interface-record-compliance-tests/LICENSE-MIT @@ -0,0 +1,19 @@ +The MIT License (MIT) + +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/packages/interface-record-compliance-tests/README.md b/packages/interface-record-compliance-tests/README.md new file mode 100644 index 0000000000..d62d96cd89 --- /dev/null +++ b/packages/interface-record-compliance-tests/README.md @@ -0,0 +1,55 @@ +# @libp2p/interface-record-compliance-tests + +[![libp2p.io](https://img.shields.io/badge/project-libp2p-yellow.svg?style=flat-square)](http://libp2p.io/) +[![Discuss](https://img.shields.io/discourse/https/discuss.libp2p.io/posts.svg?style=flat-square)](https://discuss.libp2p.io) +[![codecov](https://img.shields.io/codecov/c/github/libp2p/js-libp2p.svg?style=flat-square)](https://codecov.io/gh/libp2p/js-libp2p) +[![CI](https://img.shields.io/github/actions/workflow/status/libp2p/js-libp2p/js-test-and-release.yml?branch=master\&style=flat-square)](https://github.com/libp2p/js-libp2p/actions/workflows/js-test-and-release.yml?query=branch%3Amaster) + +> Compliance tests for implementations of the libp2p Record interface + +## Table of contents + +- [Install](#install) +- [Usage](#usage) +- [API Docs](#api-docs) +- [License](#license) +- [Contribution](#contribution) + +## Install + +```console +$ npm i @libp2p/interface-record-compliance-tests +``` + +## Usage + +```js +import tests from '@libp2p/interface-record-tests' + +describe('your record implementation', () => { + tests({ + // Options should be passed to your implementation + async setup (options) { + return new YourImplementation() + }, + async teardown () { + // cleanup resources created by setup() + } + }) +}) +``` + +## API Docs + +- + +## License + +Licensed under either of + +- Apache 2.0, ([LICENSE-APACHE](LICENSE-APACHE) / ) +- MIT ([LICENSE-MIT](LICENSE-MIT) / ) + +## Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. diff --git a/packages/interface-record-compliance-tests/package.json b/packages/interface-record-compliance-tests/package.json new file mode 100644 index 0000000000..cc9d9f6277 --- /dev/null +++ b/packages/interface-record-compliance-tests/package.json @@ -0,0 +1,138 @@ +{ + "name": "@libp2p/interface-record-compliance-tests", + "version": "2.0.5", + "description": "Compliance tests for implementations of the libp2p Record interface", + "license": "Apache-2.0 OR MIT", + "homepage": "https://github.com/libp2p/js-libp2p/tree/master/packages/interface-record-compliance-tests#readme", + "repository": { + "type": "git", + "url": "git+https://github.com/libp2p/js-libp2p.git" + }, + "bugs": { + "url": "https://github.com/libp2p/js-libp2p/issues" + }, + "keywords": [ + "interface", + "libp2p" + ], + "type": "module", + "types": "./dist/src/index.d.ts", + "files": [ + "src", + "dist", + "!dist/test", + "!**/*.tsbuildinfo" + ], + "exports": { + ".": { + "types": "./dist/src/index.d.ts", + "import": "./dist/src/index.js" + } + }, + "eslintConfig": { + "extends": "ipfs", + "parserOptions": { + "sourceType": "module" + } + }, + "release": { + "branches": [ + "master" + ], + "plugins": [ + [ + "@semantic-release/commit-analyzer", + { + "preset": "conventionalcommits", + "releaseRules": [ + { + "breaking": true, + "release": "major" + }, + { + "revert": true, + "release": "patch" + }, + { + "type": "feat", + "release": "minor" + }, + { + "type": "fix", + "release": "patch" + }, + { + "type": "docs", + "release": "patch" + }, + { + "type": "test", + "release": "patch" + }, + { + "type": "deps", + "release": "patch" + }, + { + "scope": "no-release", + "release": false + } + ] + } + ], + [ + "@semantic-release/release-notes-generator", + { + "preset": "conventionalcommits", + "presetConfig": { + "types": [ + { + "type": "feat", + "section": "Features" + }, + { + "type": "fix", + "section": "Bug Fixes" + }, + { + "type": "chore", + "section": "Trivial Changes" + }, + { + "type": "docs", + "section": "Documentation" + }, + { + "type": "deps", + "section": "Dependencies" + }, + { + "type": "test", + "section": "Tests" + } + ] + } + } + ], + "@semantic-release/changelog", + "@semantic-release/npm", + "@semantic-release/github", + "@semantic-release/git" + ] + }, + "scripts": { + "clean": "aegir clean", + "lint": "aegir lint", + "dep-check": "aegir dep-check", + "build": "aegir build", + "release": "aegir release" + }, + "dependencies": { + "@libp2p/interface-compliance-tests": "^3.0.0", + "@libp2p/interface-record": "^2.0.0", + "aegir": "^39.0.5" + }, + "typedoc": { + "entryPoint": "./src/index.ts" + } +} diff --git a/packages/interface-record-compliance-tests/src/index.ts b/packages/interface-record-compliance-tests/src/index.ts new file mode 100644 index 0000000000..a4d9e436c4 --- /dev/null +++ b/packages/interface-record-compliance-tests/src/index.ts @@ -0,0 +1,32 @@ +import { expect } from 'aegir/chai' +import type { TestSetup } from '@libp2p/interface-compliance-tests' +import type { Record } from '@libp2p/interface-record' + +export default (test: TestSetup): void => { + describe('record', () => { + let record: Record + + beforeEach(async () => { + record = await test.setup() + }) + + afterEach(async () => { + await test.teardown() + }) + + it('has domain and codec', () => { + expect(record.domain).to.exist() + expect(record.codec).to.exist() + }) + + it('is able to marshal', () => { + const rawData = record.marshal() + expect(rawData).to.have.property('byteLength') + }) + + it('is able to compare two records', () => { + const equals = record.equals(record) + expect(equals).to.eql(true) + }) + }) +} diff --git a/packages/interface-record-compliance-tests/tsconfig.json b/packages/interface-record-compliance-tests/tsconfig.json new file mode 100644 index 0000000000..6ca17f6cde --- /dev/null +++ b/packages/interface-record-compliance-tests/tsconfig.json @@ -0,0 +1,18 @@ +{ + "extends": "aegir/src/config/tsconfig.aegir.json", + "compilerOptions": { + "outDir": "dist" + }, + "include": [ + "src", + "test" + ], + "references": [ + { + "path": "../interface-compliance-tests" + }, + { + "path": "../interface-record" + } + ] +} diff --git a/packages/interface-record/CHANGELOG.md b/packages/interface-record/CHANGELOG.md new file mode 100644 index 0000000000..eab614002e --- /dev/null +++ b/packages/interface-record/CHANGELOG.md @@ -0,0 +1,95 @@ +## [@libp2p/interface-record-v2.0.7](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-record-v2.0.6...@libp2p/interface-record-v2.0.7) (2023-05-04) + + +### Dependencies + +* bump aegir from 38.1.8 to 39.0.5 ([#393](https://github.com/libp2p/js-libp2p-interfaces/issues/393)) ([31f3797](https://github.com/libp2p/js-libp2p-interfaces/commit/31f3797b24f7c23f3f16e9db3a230bd5f7cd5175)) + +## [@libp2p/interface-record-v2.0.6](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-record-v2.0.5...@libp2p/interface-record-v2.0.6) (2023-01-18) + + +### Trivial Changes + +* remove lerna ([#330](https://github.com/libp2p/js-libp2p-interfaces/issues/330)) ([6678592](https://github.com/libp2p/js-libp2p-interfaces/commit/6678592dd0cf601a2671852f9d2a0aff5dee2b18)) + + +### Dependencies + +* bump aegir from 37.12.1 to 38.1.0 ([#335](https://github.com/libp2p/js-libp2p-interfaces/issues/335)) ([7368a36](https://github.com/libp2p/js-libp2p-interfaces/commit/7368a363423a08e8fa247dcb76ea13e4cf030d65)) + +## [@libp2p/interface-record-v2.0.5](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-record-v2.0.4...@libp2p/interface-record-v2.0.5) (2023-01-06) + + +### Dependencies + +* update sibling dependencies ([b50e621](https://github.com/libp2p/js-libp2p-interfaces/commit/b50e621d31a8b32affc3fadb9f97c4883d577f93)) + +## [@libp2p/interface-record-v2.0.4](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-record-v2.0.3...@libp2p/interface-record-v2.0.4) (2022-12-16) + + +### Documentation + +* update project config ([#323](https://github.com/libp2p/js-libp2p-interfaces/issues/323)) ([0fc6a08](https://github.com/libp2p/js-libp2p-interfaces/commit/0fc6a08e9cdcefe361fe325281a3a2a03759ff59)) + +## [@libp2p/interface-record-v2.0.3](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-record-v2.0.2...@libp2p/interface-record-v2.0.3) (2022-12-14) + + +### Bug Fixes + +* generate docs for all packages ([#321](https://github.com/libp2p/js-libp2p-interfaces/issues/321)) ([b6f8b32](https://github.com/libp2p/js-libp2p-interfaces/commit/b6f8b32a920c15a28fe021e6050e31aaae89d518)) + +## [@libp2p/interface-record-v2.0.2](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-record-v2.0.1...@libp2p/interface-record-v2.0.2) (2022-11-05) + + +### Bug Fixes + +* update project config ([#311](https://github.com/libp2p/js-libp2p-interfaces/issues/311)) ([27dd0ce](https://github.com/libp2p/js-libp2p-interfaces/commit/27dd0ce3c249892ac69cbb24ddaf0b9f32385e37)) + +## [@libp2p/interface-record-v2.0.1](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-record-v2.0.0...@libp2p/interface-record-v2.0.1) (2022-08-11) + + +### Bug Fixes + +* update marshal type ([#282](https://github.com/libp2p/js-libp2p-interfaces/issues/282)) ([2c04ff9](https://github.com/libp2p/js-libp2p-interfaces/commit/2c04ff98097ba33dc64878b788c6b9318d2ea98b)) + +## [@libp2p/interface-record-v2.0.0](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-record-v1.0.2...@libp2p/interface-record-v2.0.0) (2022-08-03) + + +### ⚠ BREAKING CHANGES + +* return type of .marshal method and several properties have changed + +### Bug Fixes + +* make record property and return types lists ([#276](https://github.com/libp2p/js-libp2p-interfaces/issues/276)) ([4df31d0](https://github.com/libp2p/js-libp2p-interfaces/commit/4df31d0a1da48dcffd3644e817b0641dca7bd111)) + + +### Trivial Changes + +* update project config ([#271](https://github.com/libp2p/js-libp2p-interfaces/issues/271)) ([59c0bf5](https://github.com/libp2p/js-libp2p-interfaces/commit/59c0bf5e0b05496fca2e4902632b61bb41fad9e9)) + +## [@libp2p/interface-record-v1.0.2](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-record-v1.0.1...@libp2p/interface-record-v1.0.2) (2022-06-27) + + +### Trivial Changes + +* update deps ([#262](https://github.com/libp2p/js-libp2p-interfaces/issues/262)) ([51edf7d](https://github.com/libp2p/js-libp2p-interfaces/commit/51edf7d9b3765a6f75c915b1483ea345d0133a41)) + +## [@libp2p/interface-record-v1.0.1](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-record-v1.0.0...@libp2p/interface-record-v1.0.1) (2022-06-14) + + +### Trivial Changes + +* update aegir ([#234](https://github.com/libp2p/js-libp2p-interfaces/issues/234)) ([3e03895](https://github.com/libp2p/js-libp2p-interfaces/commit/3e038959ecab6cfa3585df9ee179c0af7a61eda5)) +* update readmes ([#233](https://github.com/libp2p/js-libp2p-interfaces/issues/233)) ([ee7da38](https://github.com/libp2p/js-libp2p-interfaces/commit/ee7da38dccc08160d26c8436df8739ce7e0b340e)) + +## @libp2p/interface-record-v1.0.0 (2022-06-14) + + +### ⚠ BREAKING CHANGES + +* most modules have been split out of the `@libp2p/interfaces` and `@libp2p/interface-compliance-tests` packages + +### Trivial Changes + +* break modules apart ([#232](https://github.com/libp2p/js-libp2p-interfaces/issues/232)) ([385614e](https://github.com/libp2p/js-libp2p-interfaces/commit/385614e772329052ab17415c8bd421f65b01a61b)), closes [#226](https://github.com/libp2p/js-libp2p-interfaces/issues/226) diff --git a/packages/interface-record/LICENSE b/packages/interface-record/LICENSE new file mode 100644 index 0000000000..20ce483c86 --- /dev/null +++ b/packages/interface-record/LICENSE @@ -0,0 +1,4 @@ +This project is dual licensed under MIT and Apache-2.0. + +MIT: https://www.opensource.org/licenses/mit +Apache-2.0: https://www.apache.org/licenses/license-2.0 diff --git a/packages/interface-record/LICENSE-APACHE b/packages/interface-record/LICENSE-APACHE new file mode 100644 index 0000000000..14478a3b60 --- /dev/null +++ b/packages/interface-record/LICENSE-APACHE @@ -0,0 +1,5 @@ +Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. diff --git a/packages/interface-record/LICENSE-MIT b/packages/interface-record/LICENSE-MIT new file mode 100644 index 0000000000..72dc60d84b --- /dev/null +++ b/packages/interface-record/LICENSE-MIT @@ -0,0 +1,19 @@ +The MIT License (MIT) + +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/packages/interface-record/README.md b/packages/interface-record/README.md new file mode 100644 index 0000000000..9e63dc40a7 --- /dev/null +++ b/packages/interface-record/README.md @@ -0,0 +1,132 @@ +# @libp2p/interface-record + +[![libp2p.io](https://img.shields.io/badge/project-libp2p-yellow.svg?style=flat-square)](http://libp2p.io/) +[![Discuss](https://img.shields.io/discourse/https/discuss.libp2p.io/posts.svg?style=flat-square)](https://discuss.libp2p.io) +[![codecov](https://img.shields.io/codecov/c/github/libp2p/js-libp2p.svg?style=flat-square)](https://codecov.io/gh/libp2p/js-libp2p) +[![CI](https://img.shields.io/github/actions/workflow/status/libp2p/js-libp2p/js-test-and-release.yml?branch=master\&style=flat-square)](https://github.com/libp2p/js-libp2p/actions/workflows/js-test-and-release.yml?query=branch%3Amaster) + +> Record interface for libp2p + +## Table of contents + +- [Install](#install) +- [Usage](#usage) +- [Create Record](#create-record) +- [API](#api) + - [marshal](#marshal) + - [equals](#equals) +- [API Docs](#api-docs) +- [License](#license) +- [Contribution](#contribution) + +## Install + +```console +$ npm i @libp2p/interface-record +``` + +A libp2p node needs to store data in a public location (e.g. a DHT), or rely on potentially untrustworthy intermediaries to relay information. Libp2p provides an all-purpose data container called **envelope**, which includes a signature of the data, so that it its authenticity can be verified. + +The record represents the data that will be stored inside the **envelope** when distributing records across the network. The `interface-record` aims to guarantee that any type of record created is compliant with the libp2p **envelope**. + +Taking into account that a record might be used in different contexts, an **envelope** signature made for a specific purpose **must not** be considered valid for a different purpose. Accordingly, each record has a short and descriptive string representing the record use case, known as **domain**. The data to be signed will be prepended with the domain string, in order to create a domain signature. + +A record can also contain a Uint8Array codec (ideally registered as a [multicodec](https://github.com/multiformats/multicodec)). This codec will prefix the record data in the **envelope** , so that it can be deserialized deterministically. + +## Usage + +```js +const tests = require('libp2p-interfaces-compliance-tests/record') +describe('your record', () => { + tests({ + async setup () { + return YourRecord + }, + async teardown () { + // cleanup resources created by setup() + } + }) +}) +``` + +## Create Record + +```js +const multicodec = require('multicodec') +const Record = require('libp2p-interfaces/src/record') +const { fromString } = require('uint8arrays/from-string') +// const Protobuf = require('./record.proto') + +const ENVELOPE_DOMAIN_PEER_RECORD = 'libp2p-peer-record' +const ENVELOPE_PAYLOAD_TYPE_PEER_RECORD = fromString('0301', 'hex') + +/** + * @implements {import('libp2p-interfaces/src/record/types').Record} + */ +class PeerRecord { + constructor (peerId, multiaddrs, seqNumber) { + this.domain = ENVELOPE_DOMAIN_PEER_RECORD + this.codec = ENVELOPE_PAYLOAD_TYPE_PEER_RECORD + } + + /** + * Marshal a record to be used in an envelope. + * + * @returns {Uint8Array} + */ + marshal () { + // Implement and return using Protobuf + } + + /** + * Returns true if `this` record equals the `other`. + * + * @param {PeerRecord} other + * @returns {other is Record} + */ + equals (other) { + // Verify + } +} +``` + +## API + +### marshal + +- `record.marshal()` + +Marshal a record to be used in a libp2p envelope. + +**Returns** + +It returns a `Protobuf` containing the record data. + +### equals + +- `record.equals(other)` + +Verifies if the other Record is identical to this one. + +**Parameters** + +- other is a `Record` to compare with the current instance. + +**Returns** + +- `other is Record` + +## API Docs + +- + +## License + +Licensed under either of + +- Apache 2.0, ([LICENSE-APACHE](LICENSE-APACHE) / ) +- MIT ([LICENSE-MIT](LICENSE-MIT) / ) + +## Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. diff --git a/packages/interface-record/package.json b/packages/interface-record/package.json new file mode 100644 index 0000000000..bec81eabc8 --- /dev/null +++ b/packages/interface-record/package.json @@ -0,0 +1,140 @@ +{ + "name": "@libp2p/interface-record", + "version": "2.0.7", + "description": "Record interface for libp2p", + "license": "Apache-2.0 OR MIT", + "homepage": "https://github.com/libp2p/js-libp2p/tree/master/packages/interface-record#readme", + "repository": { + "type": "git", + "url": "git+https://github.com/libp2p/js-libp2p.git" + }, + "bugs": { + "url": "https://github.com/libp2p/js-libp2p/issues" + }, + "keywords": [ + "interface", + "libp2p" + ], + "type": "module", + "types": "./dist/src/index.d.ts", + "files": [ + "src", + "dist", + "!dist/test", + "!**/*.tsbuildinfo" + ], + "exports": { + ".": { + "types": "./dist/src/index.d.ts", + "import": "./dist/src/index.js" + } + }, + "eslintConfig": { + "extends": "ipfs", + "parserOptions": { + "sourceType": "module" + } + }, + "release": { + "branches": [ + "master" + ], + "plugins": [ + [ + "@semantic-release/commit-analyzer", + { + "preset": "conventionalcommits", + "releaseRules": [ + { + "breaking": true, + "release": "major" + }, + { + "revert": true, + "release": "patch" + }, + { + "type": "feat", + "release": "minor" + }, + { + "type": "fix", + "release": "patch" + }, + { + "type": "docs", + "release": "patch" + }, + { + "type": "test", + "release": "patch" + }, + { + "type": "deps", + "release": "patch" + }, + { + "scope": "no-release", + "release": false + } + ] + } + ], + [ + "@semantic-release/release-notes-generator", + { + "preset": "conventionalcommits", + "presetConfig": { + "types": [ + { + "type": "feat", + "section": "Features" + }, + { + "type": "fix", + "section": "Bug Fixes" + }, + { + "type": "chore", + "section": "Trivial Changes" + }, + { + "type": "docs", + "section": "Documentation" + }, + { + "type": "deps", + "section": "Dependencies" + }, + { + "type": "test", + "section": "Tests" + } + ] + } + } + ], + "@semantic-release/changelog", + "@semantic-release/npm", + "@semantic-release/github", + "@semantic-release/git" + ] + }, + "scripts": { + "clean": "aegir clean", + "lint": "aegir lint", + "dep-check": "aegir dep-check", + "build": "aegir build", + "release": "aegir release" + }, + "dependencies": { + "@libp2p/interface-peer-id": "^2.0.0", + "uint8arraylist": "^2.4.3" + }, + "devDependencies": { + "aegir": "^39.0.5" + }, + "typedoc": { + "entryPoint": "./src/index.ts" + } +} diff --git a/packages/interface-record/src/index.ts b/packages/interface-record/src/index.ts new file mode 100644 index 0000000000..6903f50588 --- /dev/null +++ b/packages/interface-record/src/index.ts @@ -0,0 +1,35 @@ +import type { PeerId } from '@libp2p/interface-peer-id' +import type { Uint8ArrayList } from 'uint8arraylist' + +/** + * Record is the base implementation of a record that can be used as the payload of a libp2p envelope. + */ +export interface Record { + /** + * signature domain. + */ + domain: string + /** + * identifier of the type of record + */ + codec: Uint8Array + /** + * Marshal a record to be used in an envelope. + */ + marshal: () => Uint8Array + /** + * Verifies if the other provided Record is identical to this one. + */ + equals: (other: Record) => boolean +} + +export interface Envelope { + peerId: PeerId + payloadType: Uint8Array | Uint8ArrayList + payload: Uint8Array + signature: Uint8Array | Uint8ArrayList + + marshal: () => Uint8Array + validate: (domain: string) => Promise + equals: (other: Envelope) => boolean +} diff --git a/packages/interface-record/tsconfig.json b/packages/interface-record/tsconfig.json new file mode 100644 index 0000000000..9da008198f --- /dev/null +++ b/packages/interface-record/tsconfig.json @@ -0,0 +1,15 @@ +{ + "extends": "aegir/src/config/tsconfig.aegir.json", + "compilerOptions": { + "outDir": "dist" + }, + "include": [ + "src", + "test" + ], + "references": [ + { + "path": "../interface-peer-id" + } + ] +} diff --git a/packages/interface-registrar/CHANGELOG.md b/packages/interface-registrar/CHANGELOG.md new file mode 100644 index 0000000000..164110e7c7 --- /dev/null +++ b/packages/interface-registrar/CHANGELOG.md @@ -0,0 +1,116 @@ +## [@libp2p/interface-registrar-v2.0.12](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-registrar-v2.0.11...@libp2p/interface-registrar-v2.0.12) (2023-05-04) + + +### Dependencies + +* bump aegir from 38.1.8 to 39.0.5 ([#393](https://github.com/libp2p/js-libp2p-interfaces/issues/393)) ([31f3797](https://github.com/libp2p/js-libp2p-interfaces/commit/31f3797b24f7c23f3f16e9db3a230bd5f7cd5175)) + +## [@libp2p/interface-registrar-v2.0.11](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-registrar-v2.0.10...@libp2p/interface-registrar-v2.0.11) (2023-04-18) + + +### Dependencies + +* update sibling dependencies ([2f52a28](https://github.com/libp2p/js-libp2p-interfaces/commit/2f52a284b59c0a88b040f86da1f5d3f044727f2c)) + +## [@libp2p/interface-registrar-v2.0.10](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-registrar-v2.0.9...@libp2p/interface-registrar-v2.0.10) (2023-04-11) + + +### Dependencies + +* update sibling dependencies ([b034810](https://github.com/libp2p/js-libp2p-interfaces/commit/b0348102e41dc18166e70063f4708a2b3544f4b6)) + +## [@libp2p/interface-registrar-v2.0.9](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-registrar-v2.0.8...@libp2p/interface-registrar-v2.0.9) (2023-03-09) + + +### Documentation + +* update comments on registrar interface ([#348](https://github.com/libp2p/js-libp2p-interfaces/issues/348)) ([54e3af9](https://github.com/libp2p/js-libp2p-interfaces/commit/54e3af9cfbdfd27d1c3797b957a97931153337c6)) + +## [@libp2p/interface-registrar-v2.0.8](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-registrar-v2.0.7...@libp2p/interface-registrar-v2.0.8) (2023-01-18) + + +### Trivial Changes + +* remove lerna ([#330](https://github.com/libp2p/js-libp2p-interfaces/issues/330)) ([6678592](https://github.com/libp2p/js-libp2p-interfaces/commit/6678592dd0cf601a2671852f9d2a0aff5dee2b18)) + + +### Dependencies + +* bump aegir from 37.12.1 to 38.1.0 ([#335](https://github.com/libp2p/js-libp2p-interfaces/issues/335)) ([7368a36](https://github.com/libp2p/js-libp2p-interfaces/commit/7368a363423a08e8fa247dcb76ea13e4cf030d65)) + +## [@libp2p/interface-registrar-v2.0.7](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-registrar-v2.0.6...@libp2p/interface-registrar-v2.0.7) (2023-01-06) + + +### Dependencies + +* update sibling dependencies ([b50e621](https://github.com/libp2p/js-libp2p-interfaces/commit/b50e621d31a8b32affc3fadb9f97c4883d577f93)) + +## [@libp2p/interface-registrar-v2.0.6](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-registrar-v2.0.5...@libp2p/interface-registrar-v2.0.6) (2022-12-16) + + +### Documentation + +* update project config ([#323](https://github.com/libp2p/js-libp2p-interfaces/issues/323)) ([0fc6a08](https://github.com/libp2p/js-libp2p-interfaces/commit/0fc6a08e9cdcefe361fe325281a3a2a03759ff59)) + +## [@libp2p/interface-registrar-v2.0.5](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-registrar-v2.0.4...@libp2p/interface-registrar-v2.0.5) (2022-12-14) + + +### Bug Fixes + +* generate docs for all packages ([#321](https://github.com/libp2p/js-libp2p-interfaces/issues/321)) ([b6f8b32](https://github.com/libp2p/js-libp2p-interfaces/commit/b6f8b32a920c15a28fe021e6050e31aaae89d518)) + +## [@libp2p/interface-registrar-v2.0.4](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-registrar-v2.0.3...@libp2p/interface-registrar-v2.0.4) (2022-11-05) + + +### Bug Fixes + +* update project config ([#311](https://github.com/libp2p/js-libp2p-interfaces/issues/311)) ([27dd0ce](https://github.com/libp2p/js-libp2p-interfaces/commit/27dd0ce3c249892ac69cbb24ddaf0b9f32385e37)) + +## [@libp2p/interface-registrar-v2.0.3](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-registrar-v2.0.2...@libp2p/interface-registrar-v2.0.3) (2022-08-07) + + +### Trivial Changes + +* update project config ([#271](https://github.com/libp2p/js-libp2p-interfaces/issues/271)) ([59c0bf5](https://github.com/libp2p/js-libp2p-interfaces/commit/59c0bf5e0b05496fca2e4902632b61bb41fad9e9)) + + +### Dependencies + +* update sibling dependencies ([f859920](https://github.com/libp2p/js-libp2p-interfaces/commit/f859920423587ae797ac90ccaa3af8bdf60ae549)) + +## [@libp2p/interface-registrar-v2.0.2](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-registrar-v2.0.1...@libp2p/interface-registrar-v2.0.2) (2022-06-27) + + +### Trivial Changes + +* update deps ([#262](https://github.com/libp2p/js-libp2p-interfaces/issues/262)) ([51edf7d](https://github.com/libp2p/js-libp2p-interfaces/commit/51edf7d9b3765a6f75c915b1483ea345d0133a41)) + +## [@libp2p/interface-registrar-v2.0.1](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-registrar-v2.0.0...@libp2p/interface-registrar-v2.0.1) (2022-06-17) + + +### Bug Fixes + +* update stream handler args ([#247](https://github.com/libp2p/js-libp2p-interfaces/issues/247)) ([d29e134](https://github.com/libp2p/js-libp2p-interfaces/commit/d29e134bd70295c725bfd627d5887954d1a278ae)) + +## [@libp2p/interface-registrar-v2.0.0](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-registrar-v1.1.0...@libp2p/interface-registrar-v2.0.0) (2022-06-16) + + +### ⚠ BREAKING CHANGES + +* The Connection and Stream APIs have been updated + +### Features + +* store stream data on the stream, track the stream direction ([#245](https://github.com/libp2p/js-libp2p-interfaces/issues/245)) ([6d74d2f](https://github.com/libp2p/js-libp2p-interfaces/commit/6d74d2f9f344fb4d6741ba0d35263ebe351a4c65)) + + +### Trivial Changes + +* update deps ([545264f](https://github.com/libp2p/js-libp2p-interfaces/commit/545264f87a58394d2a7da77e93f3a596e889238f)) + +## [@libp2p/interface-registrar-v1.1.0](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-registrar-v1.0.0...@libp2p/interface-registrar-v1.1.0) (2022-06-16) + + +### Features + +* define stream limits as input/output ([#240](https://github.com/libp2p/js-libp2p-interfaces/issues/240)) ([554fe95](https://github.com/libp2p/js-libp2p-interfaces/commit/554fe95865c4851fcef3b311d80d44f82a613969)) diff --git a/packages/interface-registrar/LICENSE b/packages/interface-registrar/LICENSE new file mode 100644 index 0000000000..20ce483c86 --- /dev/null +++ b/packages/interface-registrar/LICENSE @@ -0,0 +1,4 @@ +This project is dual licensed under MIT and Apache-2.0. + +MIT: https://www.opensource.org/licenses/mit +Apache-2.0: https://www.apache.org/licenses/license-2.0 diff --git a/packages/interface-registrar/LICENSE-APACHE b/packages/interface-registrar/LICENSE-APACHE new file mode 100644 index 0000000000..14478a3b60 --- /dev/null +++ b/packages/interface-registrar/LICENSE-APACHE @@ -0,0 +1,5 @@ +Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. diff --git a/packages/interface-registrar/LICENSE-MIT b/packages/interface-registrar/LICENSE-MIT new file mode 100644 index 0000000000..72dc60d84b --- /dev/null +++ b/packages/interface-registrar/LICENSE-MIT @@ -0,0 +1,19 @@ +The MIT License (MIT) + +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/packages/interface-registrar/README.md b/packages/interface-registrar/README.md new file mode 100644 index 0000000000..f1e1a6e598 --- /dev/null +++ b/packages/interface-registrar/README.md @@ -0,0 +1,36 @@ +# @libp2p/interface-registrar + +[![libp2p.io](https://img.shields.io/badge/project-libp2p-yellow.svg?style=flat-square)](http://libp2p.io/) +[![Discuss](https://img.shields.io/discourse/https/discuss.libp2p.io/posts.svg?style=flat-square)](https://discuss.libp2p.io) +[![codecov](https://img.shields.io/codecov/c/github/libp2p/js-libp2p.svg?style=flat-square)](https://codecov.io/gh/libp2p/js-libp2p) +[![CI](https://img.shields.io/github/actions/workflow/status/libp2p/js-libp2p/js-test-and-release.yml?branch=master\&style=flat-square)](https://github.com/libp2p/js-libp2p/actions/workflows/js-test-and-release.yml?query=branch%3Amaster) + +> Registrar interface for libp2p + +## Table of contents + +- [Install](#install) +- [API Docs](#api-docs) +- [License](#license) +- [Contribution](#contribution) + +## Install + +```console +$ npm i @libp2p/interface-registrar +``` + +## API Docs + +- + +## License + +Licensed under either of + +- Apache 2.0, ([LICENSE-APACHE](LICENSE-APACHE) / ) +- MIT ([LICENSE-MIT](LICENSE-MIT) / ) + +## Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. diff --git a/packages/interface-registrar/package.json b/packages/interface-registrar/package.json new file mode 100644 index 0000000000..fc815feead --- /dev/null +++ b/packages/interface-registrar/package.json @@ -0,0 +1,140 @@ +{ + "name": "@libp2p/interface-registrar", + "version": "2.0.12", + "description": "Registrar interface for libp2p", + "license": "Apache-2.0 OR MIT", + "homepage": "https://github.com/libp2p/js-libp2p/tree/master/packages/interface-registrar#readme", + "repository": { + "type": "git", + "url": "git+https://github.com/libp2p/js-libp2p.git" + }, + "bugs": { + "url": "https://github.com/libp2p/js-libp2p/issues" + }, + "keywords": [ + "interface", + "libp2p" + ], + "type": "module", + "types": "./dist/src/index.d.ts", + "files": [ + "src", + "dist", + "!dist/test", + "!**/*.tsbuildinfo" + ], + "exports": { + ".": { + "types": "./dist/src/index.d.ts", + "import": "./dist/src/index.js" + } + }, + "eslintConfig": { + "extends": "ipfs", + "parserOptions": { + "sourceType": "module" + } + }, + "release": { + "branches": [ + "master" + ], + "plugins": [ + [ + "@semantic-release/commit-analyzer", + { + "preset": "conventionalcommits", + "releaseRules": [ + { + "breaking": true, + "release": "major" + }, + { + "revert": true, + "release": "patch" + }, + { + "type": "feat", + "release": "minor" + }, + { + "type": "fix", + "release": "patch" + }, + { + "type": "docs", + "release": "patch" + }, + { + "type": "test", + "release": "patch" + }, + { + "type": "deps", + "release": "patch" + }, + { + "scope": "no-release", + "release": false + } + ] + } + ], + [ + "@semantic-release/release-notes-generator", + { + "preset": "conventionalcommits", + "presetConfig": { + "types": [ + { + "type": "feat", + "section": "Features" + }, + { + "type": "fix", + "section": "Bug Fixes" + }, + { + "type": "chore", + "section": "Trivial Changes" + }, + { + "type": "docs", + "section": "Documentation" + }, + { + "type": "deps", + "section": "Dependencies" + }, + { + "type": "test", + "section": "Tests" + } + ] + } + } + ], + "@semantic-release/changelog", + "@semantic-release/npm", + "@semantic-release/github", + "@semantic-release/git" + ] + }, + "scripts": { + "clean": "aegir clean", + "lint": "aegir lint", + "dep-check": "aegir dep-check", + "build": "aegir build", + "release": "aegir release" + }, + "dependencies": { + "@libp2p/interface-connection": "^5.0.0", + "@libp2p/interface-peer-id": "^2.0.0" + }, + "devDependencies": { + "aegir": "^39.0.5" + }, + "typedoc": { + "entryPoint": "./src/index.ts" + } +} diff --git a/packages/interface-registrar/src/index.ts b/packages/interface-registrar/src/index.ts new file mode 100644 index 0000000000..bac9a91c1b --- /dev/null +++ b/packages/interface-registrar/src/index.ts @@ -0,0 +1,118 @@ +import type { Connection, Stream } from '@libp2p/interface-connection' +import type { PeerId } from '@libp2p/interface-peer-id' + +export interface IncomingStreamData { + stream: Stream + connection: Connection +} + +export interface StreamHandler { + (data: IncomingStreamData): void +} + +export interface StreamHandlerOptions { + /** + * How many incoming streams can be open for this protocol at the same time on each connection (default: 32) + */ + maxInboundStreams?: number + + /** + * How many outgoing streams can be open for this protocol at the same time on each connection (default: 64) + */ + maxOutboundStreams?: number +} + +export interface StreamHandlerRecord { + handler: StreamHandler + options: StreamHandlerOptions +} + +export interface Registrar { + /** + * Return the list of protocols with registered handlers + */ + getProtocols: () => string[] + + /** + * Add a protocol handler + */ + handle: (protocol: string, handler: StreamHandler, options?: StreamHandlerOptions) => Promise + + /** + * Remove a protocol handler + */ + unhandle: (protocol: string) => Promise + + /** + * Return the handler for the passed protocol + */ + getHandler: (protocol: string) => StreamHandlerRecord + + /** + * Register a topology handler for a protocol - the topology will be + * invoked when peers are discovered on the network that support the + * passed protocol. + * + * An id will be returned that can later be used to unregister the + * topology. + */ + register: (protocol: string, topology: Topology) => Promise + + /** + * Remove the topology handler with the passed id. + */ + unregister: (id: string) => void + + /** + * Return all topology handlers that wish to be informed about peers + * that support the passed protocol. + */ + getTopologies: (protocol: string) => Topology[] +} + +export interface onConnectHandler { + (peerId: PeerId, conn: Connection): void +} + +export interface onDisconnectHandler { + (peerId: PeerId, conn?: Connection): void +} + +export interface TopologyInit { + /** + * minimum needed connections + */ + min?: number + + /** + * maximum needed connections + */ + max?: number + + /** + * Invoked when a new peer is connects that supports the configured + * protocol + */ + onConnect?: onConnectHandler + + /** + * Invoked when a peer that supports the configured protocol disconnects + */ + onDisconnect?: onDisconnectHandler +} + +export interface Topology { + min: number + max: number + peers: Set + + onConnect: (peerId: PeerId, conn: Connection) => void + onDisconnect: (peerId: PeerId) => void + setRegistrar: (registrar: Registrar) => Promise +} + +export const topologySymbol = Symbol.for('@libp2p/topology') + +export function isTopology (other: any): other is Topology { + return other != null && Boolean(other[topologySymbol]) +} diff --git a/packages/interface-registrar/tsconfig.json b/packages/interface-registrar/tsconfig.json new file mode 100644 index 0000000000..92a8421e08 --- /dev/null +++ b/packages/interface-registrar/tsconfig.json @@ -0,0 +1,18 @@ +{ + "extends": "aegir/src/config/tsconfig.aegir.json", + "compilerOptions": { + "outDir": "dist" + }, + "include": [ + "src", + "test" + ], + "references": [ + { + "path": "../interface-connection" + }, + { + "path": "../interface-peer-id" + } + ] +} diff --git a/packages/interface-stream-muxer-compliance-tests/CHANGELOG.md b/packages/interface-stream-muxer-compliance-tests/CHANGELOG.md new file mode 100644 index 0000000000..926e438243 --- /dev/null +++ b/packages/interface-stream-muxer-compliance-tests/CHANGELOG.md @@ -0,0 +1,239 @@ +## [@libp2p/interface-stream-muxer-compliance-tests-v7.0.3](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-stream-muxer-compliance-tests-v7.0.2...@libp2p/interface-stream-muxer-compliance-tests-v7.0.3) (2023-05-17) + + +### Bug Fixes + +* handle thrown errors ([#405](https://github.com/libp2p/js-libp2p-interfaces/issues/405)) ([57dfaec](https://github.com/libp2p/js-libp2p-interfaces/commit/57dfaec4c98f13a09fcfa2541c21b163a30c11d2)) + +## [@libp2p/interface-stream-muxer-compliance-tests-v7.0.2](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-stream-muxer-compliance-tests-v7.0.1...@libp2p/interface-stream-muxer-compliance-tests-v7.0.2) (2023-05-04) + + +### Dependencies + +* bump aegir from 38.1.8 to 39.0.5 ([#393](https://github.com/libp2p/js-libp2p-interfaces/issues/393)) ([31f3797](https://github.com/libp2p/js-libp2p-interfaces/commit/31f3797b24f7c23f3f16e9db3a230bd5f7cd5175)) + +## [@libp2p/interface-stream-muxer-compliance-tests-v7.0.1](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-stream-muxer-compliance-tests-v7.0.0...@libp2p/interface-stream-muxer-compliance-tests-v7.0.1) (2023-04-18) + + +### Dependencies + +* update abortable iterator to 5.x.x ([#379](https://github.com/libp2p/js-libp2p-interfaces/issues/379)) ([d405e5b](https://github.com/libp2p/js-libp2p-interfaces/commit/d405e5b5db624d97f47588ef55c379debccfd160)) + +## [@libp2p/interface-stream-muxer-compliance-tests-v7.0.0](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-stream-muxer-compliance-tests-v6.0.12...@libp2p/interface-stream-muxer-compliance-tests-v7.0.0) (2023-04-18) + + +### ⚠ BREAKING CHANGES + +* bump it-stream-types from 1.0.5 to 2.0.1 (#362) + +### Dependencies + +* bump it-stream-types from 1.0.5 to 2.0.1 ([#362](https://github.com/libp2p/js-libp2p-interfaces/issues/362)) ([cdc7747](https://github.com/libp2p/js-libp2p-interfaces/commit/cdc774792beead63e0ded96bd6c23de0335a49e3)) +* update sibling dependencies ([e95dcc2](https://github.com/libp2p/js-libp2p-interfaces/commit/e95dcc28f0a8b42457a44155eb0dfb3d813b03c8)) +* update sibling dependencies ([2f52a28](https://github.com/libp2p/js-libp2p-interfaces/commit/2f52a284b59c0a88b040f86da1f5d3f044727f2c)) + +## [@libp2p/interface-stream-muxer-compliance-tests-v6.0.12](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-stream-muxer-compliance-tests-v6.0.11...@libp2p/interface-stream-muxer-compliance-tests-v6.0.12) (2023-04-14) + + +### Dependencies + +* bump it-drain from 2.0.1 to 3.0.1 ([#358](https://github.com/libp2p/js-libp2p-interfaces/issues/358)) ([ec050fa](https://github.com/libp2p/js-libp2p-interfaces/commit/ec050fa215a2ee845366c95d763ff009b247e986)) + +## [@libp2p/interface-stream-muxer-compliance-tests-v6.0.11](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-stream-muxer-compliance-tests-v6.0.10...@libp2p/interface-stream-muxer-compliance-tests-v6.0.11) (2023-04-13) + + +### Dependencies + +* bump it-all from 2.0.1 to 3.0.1 ([#360](https://github.com/libp2p/js-libp2p-interfaces/issues/360)) ([1b439eb](https://github.com/libp2p/js-libp2p-interfaces/commit/1b439eb7503ed7e31e77f17ce0a685ea78d94442)) +* bump it-map from 2.0.1 to 3.0.2 ([#361](https://github.com/libp2p/js-libp2p-interfaces/issues/361)) ([c016269](https://github.com/libp2p/js-libp2p-interfaces/commit/c01626912eae85969bf2b1027b68a5242a4ae4d4)) + +## [@libp2p/interface-stream-muxer-compliance-tests-v6.0.10](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-stream-muxer-compliance-tests-v6.0.9...@libp2p/interface-stream-muxer-compliance-tests-v6.0.10) (2023-04-11) + + +### Dependencies + +* update sibling dependencies ([b034810](https://github.com/libp2p/js-libp2p-interfaces/commit/b0348102e41dc18166e70063f4708a2b3544f4b6)) + +## [@libp2p/interface-stream-muxer-compliance-tests-v6.0.9](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-stream-muxer-compliance-tests-v6.0.8...@libp2p/interface-stream-muxer-compliance-tests-v6.0.9) (2023-04-04) + + +### Dependencies + +* bump it-pipe from 2.0.5 to 3.0.1 ([#363](https://github.com/libp2p/js-libp2p-interfaces/issues/363)) ([625817b](https://github.com/libp2p/js-libp2p-interfaces/commit/625817b0bbbee276983c40a0604c8810a25abe8f)) + +## [@libp2p/interface-stream-muxer-compliance-tests-v6.0.8](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-stream-muxer-compliance-tests-v6.0.7...@libp2p/interface-stream-muxer-compliance-tests-v6.0.8) (2023-03-07) + + +### Bug Fixes + +* dispatch connection event from mock upgrader ([#345](https://github.com/libp2p/js-libp2p-interfaces/issues/345)) ([b691b1f](https://github.com/libp2p/js-libp2p-interfaces/commit/b691b1fa28e23b549c32e89d6b7c98d6a50c7b8f)) + +## [@libp2p/interface-stream-muxer-compliance-tests-v6.0.7](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-stream-muxer-compliance-tests-v6.0.6...@libp2p/interface-stream-muxer-compliance-tests-v6.0.7) (2023-01-18) + + +### Trivial Changes + +* remove lerna ([#330](https://github.com/libp2p/js-libp2p-interfaces/issues/330)) ([6678592](https://github.com/libp2p/js-libp2p-interfaces/commit/6678592dd0cf601a2671852f9d2a0aff5dee2b18)) + + +### Dependencies + +* bump aegir from 37.12.1 to 38.1.0 ([#335](https://github.com/libp2p/js-libp2p-interfaces/issues/335)) ([7368a36](https://github.com/libp2p/js-libp2p-interfaces/commit/7368a363423a08e8fa247dcb76ea13e4cf030d65)) + +## [@libp2p/interface-stream-muxer-compliance-tests-v6.0.6](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-stream-muxer-compliance-tests-v6.0.5...@libp2p/interface-stream-muxer-compliance-tests-v6.0.6) (2022-12-16) + + +### Documentation + +* update project config ([#323](https://github.com/libp2p/js-libp2p-interfaces/issues/323)) ([0fc6a08](https://github.com/libp2p/js-libp2p-interfaces/commit/0fc6a08e9cdcefe361fe325281a3a2a03759ff59)) + +## [@libp2p/interface-stream-muxer-compliance-tests-v6.0.5](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-stream-muxer-compliance-tests-v6.0.4...@libp2p/interface-stream-muxer-compliance-tests-v6.0.5) (2022-12-14) + + +### Bug Fixes + +* generate docs for all packages ([#321](https://github.com/libp2p/js-libp2p-interfaces/issues/321)) ([b6f8b32](https://github.com/libp2p/js-libp2p-interfaces/commit/b6f8b32a920c15a28fe021e6050e31aaae89d518)) + +## [@libp2p/interface-stream-muxer-compliance-tests-v6.0.4](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-stream-muxer-compliance-tests-v6.0.3...@libp2p/interface-stream-muxer-compliance-tests-v6.0.4) (2022-11-05) + + +### Bug Fixes + +* update project config ([#311](https://github.com/libp2p/js-libp2p-interfaces/issues/311)) ([27dd0ce](https://github.com/libp2p/js-libp2p-interfaces/commit/27dd0ce3c249892ac69cbb24ddaf0b9f32385e37)) + +## [@libp2p/interface-stream-muxer-compliance-tests-v6.0.3](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-stream-muxer-compliance-tests-v6.0.2...@libp2p/interface-stream-muxer-compliance-tests-v6.0.3) (2022-10-18) + + +### Dependencies + +* bump lerna from 5.6.2 to 6.0.1 ([#307](https://github.com/libp2p/js-libp2p-interfaces/issues/307)) ([d203019](https://github.com/libp2p/js-libp2p-interfaces/commit/d2030195b9689c504c148a722027d93af4fdc26d)) + +## [@libp2p/interface-stream-muxer-compliance-tests-v6.0.2](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-stream-muxer-compliance-tests-v6.0.1...@libp2p/interface-stream-muxer-compliance-tests-v6.0.2) (2022-10-17) + + +### Dependencies + +* bump it-drain from 1.0.5 to 2.0.0 ([#305](https://github.com/libp2p/js-libp2p-interfaces/issues/305)) ([67e418c](https://github.com/libp2p/js-libp2p-interfaces/commit/67e418c2abeccfc06e715c1373485f012df06fdb)) + +## [@libp2p/interface-stream-muxer-compliance-tests-v6.0.1](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-stream-muxer-compliance-tests-v6.0.0...@libp2p/interface-stream-muxer-compliance-tests-v6.0.1) (2022-10-17) + + +### Dependencies + +* bump it-all from 1.0.6 to 2.0.0 ([#306](https://github.com/libp2p/js-libp2p-interfaces/issues/306)) ([7c7b388](https://github.com/libp2p/js-libp2p-interfaces/commit/7c7b3882e33a064b8e5588d6befd823360347464)) +* bump it-map from 1.0.6 to 2.0.0 ([#304](https://github.com/libp2p/js-libp2p-interfaces/issues/304)) ([8a1f7f4](https://github.com/libp2p/js-libp2p-interfaces/commit/8a1f7f4241d3acf250ee81a2265a00f58e80e6ed)) + +## [@libp2p/interface-stream-muxer-compliance-tests-v6.0.0](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-stream-muxer-compliance-tests-v5.0.0...@libp2p/interface-stream-muxer-compliance-tests-v6.0.0) (2022-10-12) + + +### ⚠ BREAKING CHANGES + +* modules no longer implement `Initializable` instead switching to constructor injection + +### Bug Fixes + +* remove @libp2p/components ([#301](https://github.com/libp2p/js-libp2p-interfaces/issues/301)) ([1d37dc6](https://github.com/libp2p/js-libp2p-interfaces/commit/1d37dc6d3197838a71895d5769ad8bba6eb38fd3)) + +## [@libp2p/interface-stream-muxer-compliance-tests-v5.0.0](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-stream-muxer-compliance-tests-v4.0.0...@libp2p/interface-stream-muxer-compliance-tests-v5.0.0) (2022-10-06) + + +### ⚠ BREAKING CHANGES + +* the return type of StreamMuxer.newStream can now return a promise + +Co-authored-by: Marco Munizaga + +### Features + +* add upgrader options ([#290](https://github.com/libp2p/js-libp2p-interfaces/issues/290)) ([c502b66](https://github.com/libp2p/js-libp2p-interfaces/commit/c502b66d87020eb8e2768c49be17392c55503f69)) + + +### Dependencies + +* update sibling dependencies ([66b4993](https://github.com/libp2p/js-libp2p-interfaces/commit/66b49938a09eeb12bf8ec8d78938d5cffd6ec134)) + +## [@libp2p/interface-stream-muxer-compliance-tests-v4.0.0](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-stream-muxer-compliance-tests-v3.0.1...@libp2p/interface-stream-muxer-compliance-tests-v4.0.0) (2022-08-07) + + +### ⚠ BREAKING CHANGES + +* change stream muxer interface (#279) + +### Features + +* change stream muxer interface ([#279](https://github.com/libp2p/js-libp2p-interfaces/issues/279)) ([1ebe269](https://github.com/libp2p/js-libp2p-interfaces/commit/1ebe26988b6a286f36a4fc5177f502cfb60368a1)) + + +### Trivial Changes + +* update project config ([#271](https://github.com/libp2p/js-libp2p-interfaces/issues/271)) ([59c0bf5](https://github.com/libp2p/js-libp2p-interfaces/commit/59c0bf5e0b05496fca2e4902632b61bb41fad9e9)) + + +### Dependencies + +* update sibling dependencies ([f859920](https://github.com/libp2p/js-libp2p-interfaces/commit/f859920423587ae797ac90ccaa3af8bdf60ae549)) + +## [@libp2p/interface-stream-muxer-compliance-tests-v3.0.1](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-stream-muxer-compliance-tests-v3.0.0...@libp2p/interface-stream-muxer-compliance-tests-v3.0.1) (2022-06-27) + + +### Trivial Changes + +* update deps ([#262](https://github.com/libp2p/js-libp2p-interfaces/issues/262)) ([51edf7d](https://github.com/libp2p/js-libp2p-interfaces/commit/51edf7d9b3765a6f75c915b1483ea345d0133a41)) + +## [@libp2p/interface-stream-muxer-compliance-tests-v3.0.0](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-stream-muxer-compliance-tests-v2.1.0...@libp2p/interface-stream-muxer-compliance-tests-v3.0.0) (2022-06-24) + + +### ⚠ BREAKING CHANGES + +* StreamMuxer now has a `close` method + +### Features + +* add stream muxer close ([#254](https://github.com/libp2p/js-libp2p-interfaces/issues/254)) ([d1f511e](https://github.com/libp2p/js-libp2p-interfaces/commit/d1f511e4b5857769c4eddf902288dc69fcb667b4)) + + +### Trivial Changes + +* update sibling dependencies [skip ci] ([c522241](https://github.com/libp2p/js-libp2p-interfaces/commit/c522241b08cfef3995efb5415104f46521dcd3b7)) + +## [@libp2p/interface-stream-muxer-compliance-tests-v2.1.0](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-stream-muxer-compliance-tests-v2.0.0...@libp2p/interface-stream-muxer-compliance-tests-v2.1.0) (2022-06-21) + + +### Features + +* add direction to StreamMuxerInit ([#253](https://github.com/libp2p/js-libp2p-interfaces/issues/253)) ([6d34d75](https://github.com/libp2p/js-libp2p-interfaces/commit/6d34d755ff4e798d52945f1f099052bdd6a83f2b)) + +## [@libp2p/interface-stream-muxer-compliance-tests-v2.0.0](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-stream-muxer-compliance-tests-v1.0.2...@libp2p/interface-stream-muxer-compliance-tests-v2.0.0) (2022-06-16) + + +### ⚠ BREAKING CHANGES + +* The Connection and Stream APIs have been updated + +### Features + +* store stream data on the stream, track the stream direction ([#245](https://github.com/libp2p/js-libp2p-interfaces/issues/245)) ([6d74d2f](https://github.com/libp2p/js-libp2p-interfaces/commit/6d74d2f9f344fb4d6741ba0d35263ebe351a4c65)) + + +### Trivial Changes + +* update deps ([545264f](https://github.com/libp2p/js-libp2p-interfaces/commit/545264f87a58394d2a7da77e93f3a596e889238f)) + +## [@libp2p/interface-stream-muxer-compliance-tests-v1.0.2](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-stream-muxer-compliance-tests-v1.0.1...@libp2p/interface-stream-muxer-compliance-tests-v1.0.2) (2022-06-15) + + +### Bug Fixes + +* increase timeout for firefox ([#239](https://github.com/libp2p/js-libp2p-interfaces/issues/239)) ([e7a62b4](https://github.com/libp2p/js-libp2p-interfaces/commit/e7a62b4457ff96788ef4c445ed1549ce5d832b4e)) + +## [@libp2p/interface-stream-muxer-compliance-tests-v1.0.1](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-stream-muxer-compliance-tests-v1.0.0...@libp2p/interface-stream-muxer-compliance-tests-v1.0.1) (2022-06-14) + + +### Bug Fixes + +* remove components from muxer factory function ([#238](https://github.com/libp2p/js-libp2p-interfaces/issues/238)) ([e4dab30](https://github.com/libp2p/js-libp2p-interfaces/commit/e4dab306d9bf406b9bb3cb92644e28cf81f7bda6)) + + +### Trivial Changes + +* update components module ([#235](https://github.com/libp2p/js-libp2p-interfaces/issues/235)) ([5844207](https://github.com/libp2p/js-libp2p-interfaces/commit/58442070af59aa852c83ec3aecdbd1d2c646b018)) diff --git a/packages/interface-stream-muxer-compliance-tests/LICENSE b/packages/interface-stream-muxer-compliance-tests/LICENSE new file mode 100644 index 0000000000..20ce483c86 --- /dev/null +++ b/packages/interface-stream-muxer-compliance-tests/LICENSE @@ -0,0 +1,4 @@ +This project is dual licensed under MIT and Apache-2.0. + +MIT: https://www.opensource.org/licenses/mit +Apache-2.0: https://www.apache.org/licenses/license-2.0 diff --git a/packages/interface-stream-muxer-compliance-tests/LICENSE-APACHE b/packages/interface-stream-muxer-compliance-tests/LICENSE-APACHE new file mode 100644 index 0000000000..14478a3b60 --- /dev/null +++ b/packages/interface-stream-muxer-compliance-tests/LICENSE-APACHE @@ -0,0 +1,5 @@ +Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. diff --git a/packages/interface-stream-muxer-compliance-tests/LICENSE-MIT b/packages/interface-stream-muxer-compliance-tests/LICENSE-MIT new file mode 100644 index 0000000000..72dc60d84b --- /dev/null +++ b/packages/interface-stream-muxer-compliance-tests/LICENSE-MIT @@ -0,0 +1,19 @@ +The MIT License (MIT) + +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/packages/interface-stream-muxer-compliance-tests/README.md b/packages/interface-stream-muxer-compliance-tests/README.md new file mode 100644 index 0000000000..0d11fa9cc9 --- /dev/null +++ b/packages/interface-stream-muxer-compliance-tests/README.md @@ -0,0 +1,55 @@ +# @libp2p/interface-stream-muxer-compliance-tests + +[![libp2p.io](https://img.shields.io/badge/project-libp2p-yellow.svg?style=flat-square)](http://libp2p.io/) +[![Discuss](https://img.shields.io/discourse/https/discuss.libp2p.io/posts.svg?style=flat-square)](https://discuss.libp2p.io) +[![codecov](https://img.shields.io/codecov/c/github/libp2p/js-libp2p.svg?style=flat-square)](https://codecov.io/gh/libp2p/js-libp2p) +[![CI](https://img.shields.io/github/actions/workflow/status/libp2p/js-libp2p/js-test-and-release.yml?branch=master\&style=flat-square)](https://github.com/libp2p/js-libp2p/actions/workflows/js-test-and-release.yml?query=branch%3Amaster) + +> Compliance tests for implementations of the libp2p Stream Muxer interface + +## Table of contents + +- [Install](#install) +- [Usage](#usage) +- [API Docs](#api-docs) +- [License](#license) +- [Contribution](#contribution) + +## Install + +```console +$ npm i @libp2p/interface-stream-muxer-compliance-tests +``` + +## Usage + +```js +import tests from '@libp2p/interface-stream-muxer-compliance-tests' + +describe('your stream muxer implementation', () => { + tests({ + // Options should be passed to your implementation + async setup (options) { + return new YourImplementation() + }, + async teardown () { + // cleanup resources created by setup() + } + }) +}) +``` + +## API Docs + +- + +## License + +Licensed under either of + +- Apache 2.0, ([LICENSE-APACHE](LICENSE-APACHE) / ) +- MIT ([LICENSE-MIT](LICENSE-MIT) / ) + +## Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. diff --git a/packages/interface-stream-muxer-compliance-tests/package.json b/packages/interface-stream-muxer-compliance-tests/package.json new file mode 100644 index 0000000000..cfaf97a400 --- /dev/null +++ b/packages/interface-stream-muxer-compliance-tests/package.json @@ -0,0 +1,151 @@ +{ + "name": "@libp2p/interface-stream-muxer-compliance-tests", + "version": "7.0.3", + "description": "Compliance tests for implementations of the libp2p Stream Muxer interface", + "license": "Apache-2.0 OR MIT", + "homepage": "https://github.com/libp2p/js-libp2p/tree/master/packages/interface-stream-muxer-compliance-tests#readme", + "repository": { + "type": "git", + "url": "git+https://github.com/libp2p/js-libp2p.git" + }, + "bugs": { + "url": "https://github.com/libp2p/js-libp2p/issues" + }, + "keywords": [ + "interface", + "libp2p" + ], + "type": "module", + "types": "./dist/src/index.d.ts", + "files": [ + "src", + "dist", + "!dist/test", + "!**/*.tsbuildinfo" + ], + "exports": { + ".": { + "types": "./dist/src/index.d.ts", + "import": "./dist/src/index.js" + } + }, + "eslintConfig": { + "extends": "ipfs", + "parserOptions": { + "sourceType": "module" + } + }, + "release": { + "branches": [ + "master" + ], + "plugins": [ + [ + "@semantic-release/commit-analyzer", + { + "preset": "conventionalcommits", + "releaseRules": [ + { + "breaking": true, + "release": "major" + }, + { + "revert": true, + "release": "patch" + }, + { + "type": "feat", + "release": "minor" + }, + { + "type": "fix", + "release": "patch" + }, + { + "type": "docs", + "release": "patch" + }, + { + "type": "test", + "release": "patch" + }, + { + "type": "deps", + "release": "patch" + }, + { + "scope": "no-release", + "release": false + } + ] + } + ], + [ + "@semantic-release/release-notes-generator", + { + "preset": "conventionalcommits", + "presetConfig": { + "types": [ + { + "type": "feat", + "section": "Features" + }, + { + "type": "fix", + "section": "Bug Fixes" + }, + { + "type": "chore", + "section": "Trivial Changes" + }, + { + "type": "docs", + "section": "Documentation" + }, + { + "type": "deps", + "section": "Dependencies" + }, + { + "type": "test", + "section": "Tests" + } + ] + } + } + ], + "@semantic-release/changelog", + "@semantic-release/npm", + "@semantic-release/github", + "@semantic-release/git" + ] + }, + "scripts": { + "clean": "aegir clean", + "lint": "aegir lint", + "dep-check": "aegir dep-check", + "build": "aegir build", + "release": "aegir release" + }, + "dependencies": { + "@libp2p/interface-compliance-tests": "^3.0.0", + "@libp2p/interface-connection": "^5.0.0", + "@libp2p/interface-stream-muxer": "^4.0.0", + "abortable-iterator": "^5.0.1", + "aegir": "^39.0.5", + "delay": "^6.0.0", + "it-all": "^3.0.1", + "it-drain": "^3.0.1", + "it-map": "^3.0.2", + "it-pair": "^2.0.2", + "it-pipe": "^3.0.1", + "it-stream-types": "^2.0.1", + "p-defer": "^4.0.0", + "p-limit": "^4.0.0", + "uint8arraylist": "^2.4.3", + "uint8arrays": "^4.0.2" + }, + "typedoc": { + "entryPoint": "./src/index.ts" + } +} diff --git a/packages/interface-stream-muxer-compliance-tests/src/base-test.ts b/packages/interface-stream-muxer-compliance-tests/src/base-test.ts new file mode 100644 index 0000000000..5cda0d5451 --- /dev/null +++ b/packages/interface-stream-muxer-compliance-tests/src/base-test.ts @@ -0,0 +1,196 @@ +import { isValidTick } from '@libp2p/interface-compliance-tests/is-valid-tick' +import { expect } from 'aegir/chai' +import all from 'it-all' +import drain from 'it-drain' +import map from 'it-map' +import { duplexPair } from 'it-pair/duplex' +import { pipe } from 'it-pipe' +import defer from 'p-defer' +import { Uint8ArrayList } from 'uint8arraylist' +import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' +import { toString as uint8ArrayToString } from 'uint8arrays/to-string' +import type { TestSetup } from '@libp2p/interface-compliance-tests' +import type { Stream } from '@libp2p/interface-connection' +import type { StreamMuxerFactory } from '@libp2p/interface-stream-muxer' +import type { Source, Duplex } from 'it-stream-types' +import type { DeferredPromise } from 'p-defer' + +async function drainAndClose (stream: Duplex): Promise { + await pipe([], stream, drain) +} + +export default (common: TestSetup): void => { + describe('base', () => { + it('Open a stream from the dialer', async () => { + const p = duplexPair() + const dialerFactory = await common.setup() + const dialer = dialerFactory.createStreamMuxer({ direction: 'outbound' }) + const onStreamPromise: DeferredPromise = defer() + const onStreamEndPromise: DeferredPromise = defer() + + const listenerFactory = await common.setup() + const listener = listenerFactory.createStreamMuxer({ + direction: 'inbound', + onIncomingStream: (stream) => { + onStreamPromise.resolve(stream) + }, + onStreamEnd: (stream) => { + onStreamEndPromise.resolve(stream) + } + }) + + void pipe(p[0], dialer, p[0]) + void pipe(p[1], listener, p[1]) + + const conn = await dialer.newStream() + expect(dialer.streams).to.include(conn) + expect(isValidTick(conn.stat.timeline.open)).to.equal(true) + + void drainAndClose(conn) + + const stream = await onStreamPromise.promise + expect(isValidTick(stream.stat.timeline.open)).to.equal(true) + // Make sure the stream is being tracked + expect(listener.streams).to.include(stream) + + void drainAndClose(stream) + + // Make sure stream is closed properly + const endedStream = await onStreamEndPromise.promise + expect(listener.streams).to.not.include(endedStream) + + if (endedStream.stat.timeline.close == null) { + throw new Error('timeline had no close time') + } + + // Make sure the stream is removed from tracking + expect(isValidTick(endedStream.stat.timeline.close)).to.equal(true) + + await drainAndClose(dialer) + await drainAndClose(listener) + + // ensure we have no streams left + expect(dialer.streams).to.have.length(0) + expect(listener.streams).to.have.length(0) + }) + + it('Open a stream from the listener', async () => { + const p = duplexPair() + const onStreamPromise: DeferredPromise = defer() + const dialerFactory = await common.setup() + const dialer = dialerFactory.createStreamMuxer({ + direction: 'outbound', + onIncomingStream: (stream: Stream) => { + onStreamPromise.resolve(stream) + } + }) + + const listenerFactory = await common.setup() + const listener = listenerFactory.createStreamMuxer({ direction: 'inbound' }) + + void pipe(p[0], dialer, p[0]) + void pipe(p[1], listener, p[1]) + + const conn = await listener.newStream() + + void drainAndClose(conn) + + const stream = await onStreamPromise.promise + expect(isValidTick(stream.stat.timeline.open)).to.equal(true) + expect(listener.streams).to.include(conn) + expect(isValidTick(conn.stat.timeline.open)).to.equal(true) + void drainAndClose(stream) + + await drainAndClose(dialer) + await drainAndClose(listener) + }) + + it('Open a stream on both sides', async () => { + const p = duplexPair() + const onDialerStreamPromise: DeferredPromise = defer() + const onListenerStreamPromise: DeferredPromise = defer() + const dialerFactory = await common.setup() + const dialer = dialerFactory.createStreamMuxer({ + direction: 'outbound', + onIncomingStream: (stream) => { + onDialerStreamPromise.resolve(stream) + } + }) + + const listenerFactory = await common.setup() + const listener = listenerFactory.createStreamMuxer({ + direction: 'inbound', + onIncomingStream: (stream) => { + onListenerStreamPromise.resolve(stream) + } + }) + + void pipe(p[0], dialer, p[0]) + void pipe(p[1], listener, p[1]) + + const dialerInitiatorStream = await dialer.newStream() + const listenerInitiatorStream = await listener.newStream() + + await Promise.all([ + drainAndClose(dialerInitiatorStream), + drainAndClose(listenerInitiatorStream), + onDialerStreamPromise.promise.then(async stream => { await drainAndClose(stream) }), + onListenerStreamPromise.promise.then(async stream => { await drainAndClose(stream) }) + ]) + + await Promise.all([ + drainAndClose(dialer), + drainAndClose(listener) + ]) + }) + + it('Open a stream on one side, write, open a stream on the other side', async () => { + const toString = (source: Source): AsyncGenerator => map(source, (u) => uint8ArrayToString(u.subarray())) + const p = duplexPair() + const onDialerStreamPromise: DeferredPromise = defer() + const onListenerStreamPromise: DeferredPromise = defer() + const dialerFactory = await common.setup() + const dialer = dialerFactory.createStreamMuxer({ + direction: 'outbound', + onIncomingStream: (stream) => { + onDialerStreamPromise.resolve(stream) + } + }) + const listenerFactory = await common.setup() + const listener = listenerFactory.createStreamMuxer({ + direction: 'inbound', + onIncomingStream: (stream) => { + onListenerStreamPromise.resolve(stream) + } + }) + + void pipe(p[0], dialer, p[0]) + void pipe(p[1], listener, p[1]) + + const dialerConn = await dialer.newStream() + const listenerConn = await listener.newStream() + + void pipe([new Uint8ArrayList(uint8ArrayFromString('hey'))], dialerConn) + void pipe([new Uint8ArrayList(uint8ArrayFromString('hello'))], listenerConn) + + const [ + dialerStream, + listenerStream + ] = await Promise.all([ + onDialerStreamPromise.promise, + onListenerStreamPromise.promise + ]) + + const [ + listenerChunks, + dialerChunks + ] = await Promise.all([ + pipe(listenerStream, toString, async (source) => all(source)), + pipe(dialerStream, toString, async (source) => all(source)) + ]) + + expect(listenerChunks).to.be.eql(['hey']) + expect(dialerChunks).to.be.eql(['hello']) + }) + }) +} diff --git a/packages/interface-stream-muxer-compliance-tests/src/close-test.ts b/packages/interface-stream-muxer-compliance-tests/src/close-test.ts new file mode 100644 index 0000000000..e75d047e9d --- /dev/null +++ b/packages/interface-stream-muxer-compliance-tests/src/close-test.ts @@ -0,0 +1,346 @@ +/* eslint max-nested-callbacks: ["error", 8] */ +import { abortableSource } from 'abortable-iterator' +import { expect } from 'aegir/chai' +import delay from 'delay' +import all from 'it-all' +import drain from 'it-drain' +import { duplexPair } from 'it-pair/duplex' +import { pipe } from 'it-pipe' +import pDefer from 'p-defer' +import { Uint8ArrayList } from 'uint8arraylist' +import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' +import type { TestSetup } from '@libp2p/interface-compliance-tests' +import type { StreamMuxerFactory } from '@libp2p/interface-stream-muxer' + +function randomBuffer (): Uint8Array { + return uint8ArrayFromString(Math.random().toString()) +} + +const infiniteRandom = { + [Symbol.asyncIterator]: async function * () { + while (true) { + yield new Uint8ArrayList(randomBuffer()) + await delay(50) + } + } +} + +export default (common: TestSetup): void => { + describe('close', () => { + it('closing underlying socket closes streams', async () => { + let openedStreams = 0 + const expectedStreams = 5 + const dialerFactory = await common.setup() + const dialer = dialerFactory.createStreamMuxer({ direction: 'outbound' }) + + // Listener is echo server :) + const listenerFactory = await common.setup() + const listener = listenerFactory.createStreamMuxer({ + direction: 'inbound', + onIncomingStream: (stream) => { + openedStreams++ + void pipe(stream, stream) + } + }) + + const p = duplexPair() + void pipe(p[0], dialer, p[0]) + void pipe(p[1], listener, p[1]) + + const streams = await Promise.all(Array(expectedStreams).fill(0).map(async () => dialer.newStream())) + + void Promise.all( + streams.map(async stream => { + await pipe( + infiniteRandom, + stream, + drain + ) + }) + ) + + expect(dialer.streams).to.have.lengthOf(expectedStreams) + + // Pause, and then send some data and close the dialer + await delay(50) + await pipe([randomBuffer()], dialer, drain) + + expect(openedStreams).to.have.equal(expectedStreams) + expect(dialer.streams).to.have.lengthOf(0) + }) + + it('calling close closes streams', async () => { + let openedStreams = 0 + const expectedStreams = 5 + const dialerFactory = await common.setup() + const dialer = dialerFactory.createStreamMuxer({ direction: 'outbound' }) + + // Listener is echo server :) + const listenerFactory = await common.setup() + const listener = listenerFactory.createStreamMuxer({ + direction: 'inbound', + onIncomingStream: (stream) => { + openedStreams++ + void pipe(stream, stream).catch(() => {}) + } + }) + + const p = duplexPair() + void pipe(p[0], dialer, p[0]) + void pipe(p[1], listener, p[1]) + + const streams = await Promise.all(Array(expectedStreams).fill(0).map(async () => dialer.newStream())) + + void Promise.all( + streams.map(async stream => { + await pipe( + infiniteRandom, + stream, + drain + ) + }) + ) + + expect(dialer.streams, 'dialer - number of opened streams should match number of calls to newStream').to.have.lengthOf(expectedStreams) + + // Pause, and then close the dialer + await delay(50) + + dialer.close() + + expect(openedStreams, 'listener - number of opened streams should match number of calls to newStream').to.have.equal(expectedStreams) + expect(dialer.streams, 'all tracked streams should be deleted after the muxer has called close').to.have.lengthOf(0) + }) + + it('calling close with an error aborts streams', async () => { + let openedStreams = 0 + const expectedStreams = 5 + const dialerFactory = await common.setup() + const dialer = dialerFactory.createStreamMuxer({ direction: 'outbound' }) + + // Listener is echo server :) + const listenerFactory = await common.setup() + const listener = listenerFactory.createStreamMuxer({ + direction: 'inbound', + onIncomingStream: (stream) => { + openedStreams++ + void pipe(stream, stream).catch(() => {}) + } + }) + + const p = duplexPair() + void pipe(p[0], dialer, p[0]) + void pipe(p[1], listener, p[1]) + + const streams = await Promise.all(Array(expectedStreams).fill(0).map(async () => dialer.newStream())) + + const streamPipes = streams.map(async stream => { + await pipe( + infiniteRandom, + stream, + drain + ) + }) + + expect(dialer.streams, 'dialer - number of opened streams should match number of calls to newStream').to.have.lengthOf(expectedStreams) + + // Pause, and then close the dialer + await delay(50) + + // close _with an error_ + dialer.close(new Error()) + + const timeoutError = new Error('timeout') + for (const pipe of streamPipes) { + try { + await Promise.race([ + pipe, + new Promise((_resolve, reject) => setTimeout(() => { reject(timeoutError) }, 20)) + ]) + expect.fail('stream pipe with infinite source should never return') + } catch (e) { + if (e === timeoutError) { + expect.fail('expected stream pipe to throw an error after muxer closed with error') + } + } + } + + expect(openedStreams, 'listener - number of opened streams should match number of calls to newStream').to.have.equal(expectedStreams) + expect(dialer.streams, 'all tracked streams should be deleted after the muxer has called close').to.have.lengthOf(0) + }) + + it('calling newStream after close throws an error', async () => { + const dialerFactory = await common.setup() + const dialer = dialerFactory.createStreamMuxer({ direction: 'outbound' }) + + dialer.close() + + try { + await dialer.newStream() + expect.fail('newStream should throw if called after close') + } catch (e) { + expect(dialer.streams, 'closed muxer should have no streams').to.have.lengthOf(0) + } + }) + + it('closing one of the muxed streams doesn\'t close others', async () => { + const p = duplexPair() + const dialerFactory = await common.setup() + const dialer = dialerFactory.createStreamMuxer({ direction: 'outbound' }) + + // Listener is echo server :) + const listenerFactory = await common.setup() + const listener = listenerFactory.createStreamMuxer({ + direction: 'inbound', + onIncomingStream: (stream) => { + void pipe(stream, stream).catch(() => {}) + } + }) + + void pipe(p[0], dialer, p[0]) + void pipe(p[1], listener, p[1]) + + const stream = await dialer.newStream() + const streams = await Promise.all(Array.from(Array(5), async () => dialer.newStream())) + let closed = false + const controllers: AbortController[] = [] + + const streamResults = streams.map(async stream => { + const controller = new AbortController() + controllers.push(controller) + + try { + const abortableRand = abortableSource(infiniteRandom, controller.signal, { abortCode: 'ERR_TEST_ABORT' }) + await pipe(abortableRand, stream, drain) + } catch (err: any) { + if (err.code !== 'ERR_TEST_ABORT') throw err + } + + if (!closed) throw new Error('stream should not have ended yet!') + }) + + // Pause, and then send some data and close the first stream + await delay(50) + await pipe([new Uint8ArrayList(randomBuffer())], stream, drain) + closed = true + + // Abort all the other streams later + await delay(50) + controllers.forEach(c => { c.abort() }) + + // These should now all resolve without error + await Promise.all(streamResults) + }) + + it('can close a stream for writing', async () => { + const deferred = pDefer() + + const p = duplexPair() + const dialerFactory = await common.setup() + const dialer = dialerFactory.createStreamMuxer({ direction: 'outbound' }) + const data = [randomBuffer(), randomBuffer()] + + const listenerFactory = await common.setup() + const listener = listenerFactory.createStreamMuxer({ + direction: 'inbound', + onIncomingStream: (stream) => { + void Promise.resolve().then(async () => { + // Immediate close for write + stream.closeWrite() + + const results = await pipe(stream, async (source) => { + const data = [] + for await (const chunk of source) { + data.push(chunk.slice()) + } + return data + }) + expect(results).to.eql(data) + + try { + await stream.sink([new Uint8ArrayList(randomBuffer())]) + } catch (err: any) { + deferred.resolve(err) + } + + deferred.reject(new Error('should not support writing to closed writer')) + }) + } + }) + + void pipe(p[0], dialer, p[0]) + void pipe(p[1], listener, p[1]) + + const stream = await dialer.newStream() + await stream.sink(data) + + const err = await deferred.promise + expect(err).to.have.property('message').that.matches(/stream closed for writing/) + }) + + it('can close a stream for reading', async () => { + const deferred = pDefer() + + const p = duplexPair() + const dialerFactory = await common.setup() + const dialer = dialerFactory.createStreamMuxer({ direction: 'outbound' }) + const data = [randomBuffer(), randomBuffer()].map(d => new Uint8ArrayList(d)) + + const listenerFactory = await common.setup() + const listener = listenerFactory.createStreamMuxer({ + direction: 'inbound', + onIncomingStream: (stream) => { + void all(stream.source).then(deferred.resolve, deferred.reject) + } + }) + + void pipe(p[0], dialer, p[0]) + void pipe(p[1], listener, p[1]) + + const stream = await dialer.newStream() + stream.closeRead() + + // Source should be done + void Promise.resolve().then(async () => { + expect(await stream.source.next()).to.have.property('done', true) + await stream.sink(data) + }) + + const results = await deferred.promise + expect(results).to.eql(data) + }) + + it('calls onStreamEnd for closed streams not previously written', async () => { + const deferred = pDefer() + + const onStreamEnd = (): void => { deferred.resolve() } + const dialerFactory = await common.setup() + const dialer = dialerFactory.createStreamMuxer({ + direction: 'outbound', + onStreamEnd + }) + + const stream = await dialer.newStream() + + stream.close() + await deferred.promise + }) + + it('calls onStreamEnd for read and write closed streams not previously written', async () => { + const deferred = pDefer() + + const onStreamEnd = (): void => { deferred.resolve() } + const dialerFactory = await common.setup() + const dialer = dialerFactory.createStreamMuxer({ + direction: 'outbound', + onStreamEnd + }) + + const stream = await dialer.newStream() + + stream.closeWrite() + stream.closeRead() + await deferred.promise + }) + }) +} diff --git a/packages/interface-stream-muxer-compliance-tests/src/index.ts b/packages/interface-stream-muxer-compliance-tests/src/index.ts new file mode 100644 index 0000000000..016637421b --- /dev/null +++ b/packages/interface-stream-muxer-compliance-tests/src/index.ts @@ -0,0 +1,15 @@ +import baseTest from './base-test.js' +import closeTest from './close-test.js' +import megaStressTest from './mega-stress-test.js' +import stressTest from './stress-test.js' +import type { TestSetup } from '@libp2p/interface-compliance-tests' +import type { StreamMuxerFactory } from '@libp2p/interface-stream-muxer' + +export default (common: TestSetup): void => { + describe('interface-stream-muxer', () => { + baseTest(common) + closeTest(common) + stressTest(common) + megaStressTest(common) + }) +} diff --git a/packages/interface-stream-muxer-compliance-tests/src/mega-stress-test.ts b/packages/interface-stream-muxer-compliance-tests/src/mega-stress-test.ts new file mode 100644 index 0000000000..1d86075b53 --- /dev/null +++ b/packages/interface-stream-muxer-compliance-tests/src/mega-stress-test.ts @@ -0,0 +1,14 @@ +import spawn from './spawner.js' +import type { TestSetup } from '@libp2p/interface-compliance-tests' +import type { StreamMuxer, StreamMuxerFactory, StreamMuxerInit } from '@libp2p/interface-stream-muxer' + +export default (common: TestSetup): void => { + const createMuxer = async (init?: StreamMuxerInit): Promise => { + const factory = await common.setup() + return factory.createStreamMuxer(init) + } + + describe.skip('mega stress test', function () { + it('10,000 streams with 10,000 msg', async () => { await spawn(createMuxer, 10000, 10000, 5000) }) + }) +} diff --git a/packages/interface-stream-muxer-compliance-tests/src/spawner.ts b/packages/interface-stream-muxer-compliance-tests/src/spawner.ts new file mode 100644 index 0000000000..da16361867 --- /dev/null +++ b/packages/interface-stream-muxer-compliance-tests/src/spawner.ts @@ -0,0 +1,54 @@ +import { expect } from 'aegir/chai' +import all from 'it-all' +import drain from 'it-drain' +import { duplexPair } from 'it-pair/duplex' +import { pipe } from 'it-pipe' +import pLimit from 'p-limit' +import { Uint8ArrayList } from 'uint8arraylist' +import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' +import type { StreamMuxer, StreamMuxerInit } from '@libp2p/interface-stream-muxer' + +export default async (createMuxer: (init?: StreamMuxerInit) => Promise, nStreams: number, nMsg: number, limit?: number): Promise => { + const [dialerSocket, listenerSocket] = duplexPair() + + const msg = new Uint8ArrayList(uint8ArrayFromString('simple msg')) + + const listener = await createMuxer({ + direction: 'inbound', + onIncomingStream: (stream) => { + void pipe( + stream, + drain + ).then(() => { + stream.close() + }) + } + }) + const dialer = await createMuxer({ direction: 'outbound' }) + + void pipe(listenerSocket, listener, listenerSocket) + void pipe(dialerSocket, dialer, dialerSocket) + + const spawnStream = async (): Promise => { + const stream = await dialer.newStream() + expect(stream).to.exist // eslint-disable-line + + const res = await pipe( + (async function * () { + for (let i = 0; i < nMsg; i++) { + yield msg + } + }()), + stream, + async (source) => all(source) + ) + + expect(res).to.be.eql([]) + } + + const limiter = pLimit(limit ?? Infinity) + + await Promise.all( + Array.from(Array(nStreams), async () => { await limiter(async () => { await spawnStream() }) }) + ) +} diff --git a/packages/interface-stream-muxer-compliance-tests/src/stress-test.ts b/packages/interface-stream-muxer-compliance-tests/src/stress-test.ts new file mode 100644 index 0000000000..c3b3ca4aa8 --- /dev/null +++ b/packages/interface-stream-muxer-compliance-tests/src/stress-test.ts @@ -0,0 +1,27 @@ +import spawn from './spawner.js' +import type { TestSetup } from '@libp2p/interface-compliance-tests' +import type { StreamMuxerFactory, StreamMuxerInit, StreamMuxer } from '@libp2p/interface-stream-muxer' + +export default (common: TestSetup): void => { + const createMuxer = async (init?: StreamMuxerInit): Promise => { + const factory = await common.setup() + return factory.createStreamMuxer(init) + } + + describe('stress test', function () { + this.timeout(800000) + + it('1 stream with 1 msg', async () => { await spawn(createMuxer, 1, 1) }) + it('1 stream with 10 msg', async () => { await spawn(createMuxer, 1, 10) }) + it('1 stream with 100 msg', async () => { await spawn(createMuxer, 1, 100) }) + it('10 streams with 1 msg', async () => { await spawn(createMuxer, 10, 1) }) + it('10 streams with 10 msg', async () => { await spawn(createMuxer, 10, 10) }) + it('10 streams with 100 msg', async () => { await spawn(createMuxer, 10, 100) }) + it('100 streams with 1 msg', async () => { await spawn(createMuxer, 100, 1) }) + it('100 streams with 10 msg', async () => { await spawn(createMuxer, 100, 10) }) + it('100 streams with 100 msg', async () => { await spawn(createMuxer, 100, 100) }) + it('1000 streams with 1 msg', async () => { await spawn(createMuxer, 1000, 1) }) + it('1000 streams with 10 msg', async () => { await spawn(createMuxer, 1000, 10) }) + it('1000 streams with 100 msg', async () => { await spawn(createMuxer, 1000, 100) }) + }) +} diff --git a/packages/interface-stream-muxer-compliance-tests/tsconfig.json b/packages/interface-stream-muxer-compliance-tests/tsconfig.json new file mode 100644 index 0000000000..467d95314b --- /dev/null +++ b/packages/interface-stream-muxer-compliance-tests/tsconfig.json @@ -0,0 +1,21 @@ +{ + "extends": "aegir/src/config/tsconfig.aegir.json", + "compilerOptions": { + "outDir": "dist" + }, + "include": [ + "src", + "test" + ], + "references": [ + { + "path": "../interface-compliance-tests" + }, + { + "path": "../interface-connection" + }, + { + "path": "../interface-stream-muxer" + } + ] +} diff --git a/packages/interface-stream-muxer/CHANGELOG.md b/packages/interface-stream-muxer/CHANGELOG.md new file mode 100644 index 0000000000..d6f777d84a --- /dev/null +++ b/packages/interface-stream-muxer/CHANGELOG.md @@ -0,0 +1,155 @@ +## [@libp2p/interface-stream-muxer-v4.1.2](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-stream-muxer-v4.1.1...@libp2p/interface-stream-muxer-v4.1.2) (2023-05-17) + + +### Bug Fixes + +* allow stream methods to be async ([#404](https://github.com/libp2p/js-libp2p-interfaces/issues/404)) ([cfcd6d7](https://github.com/libp2p/js-libp2p-interfaces/commit/cfcd6d751990990746ee7ae7c199b938667df4d8)) + +## [@libp2p/interface-stream-muxer-v4.1.1](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-stream-muxer-v4.1.0...@libp2p/interface-stream-muxer-v4.1.1) (2023-05-17) + + +### Bug Fixes + +* update interface stream muxer config ([#403](https://github.com/libp2p/js-libp2p-interfaces/issues/403)) ([12633e2](https://github.com/libp2p/js-libp2p-interfaces/commit/12633e28eae8e9e26429c29e6d0ecb8df8933a25)) + +## [@libp2p/interface-stream-muxer-v4.1.0](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-stream-muxer-v4.0.1...@libp2p/interface-stream-muxer-v4.1.0) (2023-05-16) + + +### Features + +* add abstract stream class ([#402](https://github.com/libp2p/js-libp2p-interfaces/issues/402)) ([920aa9e](https://github.com/libp2p/js-libp2p-interfaces/commit/920aa9ec2e44ce0bafbfa1f61864079313837020)) + +## [@libp2p/interface-stream-muxer-v4.0.1](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-stream-muxer-v4.0.0...@libp2p/interface-stream-muxer-v4.0.1) (2023-05-04) + + +### Dependencies + +* bump aegir from 38.1.8 to 39.0.5 ([#393](https://github.com/libp2p/js-libp2p-interfaces/issues/393)) ([31f3797](https://github.com/libp2p/js-libp2p-interfaces/commit/31f3797b24f7c23f3f16e9db3a230bd5f7cd5175)) + +## [@libp2p/interface-stream-muxer-v4.0.0](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-stream-muxer-v3.0.6...@libp2p/interface-stream-muxer-v4.0.0) (2023-04-18) + + +### ⚠ BREAKING CHANGES + +* bump it-stream-types from 1.0.5 to 2.0.1 (#362) + +### Dependencies + +* bump it-stream-types from 1.0.5 to 2.0.1 ([#362](https://github.com/libp2p/js-libp2p-interfaces/issues/362)) ([cdc7747](https://github.com/libp2p/js-libp2p-interfaces/commit/cdc774792beead63e0ded96bd6c23de0335a49e3)) +* update sibling dependencies ([2f52a28](https://github.com/libp2p/js-libp2p-interfaces/commit/2f52a284b59c0a88b040f86da1f5d3f044727f2c)) + +## [@libp2p/interface-stream-muxer-v3.0.6](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-stream-muxer-v3.0.5...@libp2p/interface-stream-muxer-v3.0.6) (2023-04-11) + + +### Dependencies + +* update sibling dependencies ([b034810](https://github.com/libp2p/js-libp2p-interfaces/commit/b0348102e41dc18166e70063f4708a2b3544f4b6)) + +## [@libp2p/interface-stream-muxer-v3.0.5](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-stream-muxer-v3.0.4...@libp2p/interface-stream-muxer-v3.0.5) (2023-01-18) + + +### Trivial Changes + +* remove lerna ([#330](https://github.com/libp2p/js-libp2p-interfaces/issues/330)) ([6678592](https://github.com/libp2p/js-libp2p-interfaces/commit/6678592dd0cf601a2671852f9d2a0aff5dee2b18)) + + +### Dependencies + +* bump aegir from 37.12.1 to 38.1.0 ([#335](https://github.com/libp2p/js-libp2p-interfaces/issues/335)) ([7368a36](https://github.com/libp2p/js-libp2p-interfaces/commit/7368a363423a08e8fa247dcb76ea13e4cf030d65)) + +## [@libp2p/interface-stream-muxer-v3.0.4](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-stream-muxer-v3.0.3...@libp2p/interface-stream-muxer-v3.0.4) (2022-12-19) + + +### Documentation + +* add interface docs ([#324](https://github.com/libp2p/js-libp2p-interfaces/issues/324)) ([2789445](https://github.com/libp2p/js-libp2p-interfaces/commit/278944594c24e1a3c4b3624a35680d69166546d7)) + +## [@libp2p/interface-stream-muxer-v3.0.3](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-stream-muxer-v3.0.2...@libp2p/interface-stream-muxer-v3.0.3) (2022-12-16) + + +### Documentation + +* update project config ([#323](https://github.com/libp2p/js-libp2p-interfaces/issues/323)) ([0fc6a08](https://github.com/libp2p/js-libp2p-interfaces/commit/0fc6a08e9cdcefe361fe325281a3a2a03759ff59)) + +## [@libp2p/interface-stream-muxer-v3.0.2](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-stream-muxer-v3.0.1...@libp2p/interface-stream-muxer-v3.0.2) (2022-12-14) + + +### Bug Fixes + +* generate docs for all packages ([#321](https://github.com/libp2p/js-libp2p-interfaces/issues/321)) ([b6f8b32](https://github.com/libp2p/js-libp2p-interfaces/commit/b6f8b32a920c15a28fe021e6050e31aaae89d518)) + +## [@libp2p/interface-stream-muxer-v3.0.1](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-stream-muxer-v3.0.0...@libp2p/interface-stream-muxer-v3.0.1) (2022-11-05) + + +### Bug Fixes + +* update project config ([#311](https://github.com/libp2p/js-libp2p-interfaces/issues/311)) ([27dd0ce](https://github.com/libp2p/js-libp2p-interfaces/commit/27dd0ce3c249892ac69cbb24ddaf0b9f32385e37)) + +## [@libp2p/interface-stream-muxer-v3.0.0](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-stream-muxer-v2.0.2...@libp2p/interface-stream-muxer-v3.0.0) (2022-10-06) + + +### ⚠ BREAKING CHANGES + +* the return type of StreamMuxer.newStream can now return a promise + +Co-authored-by: Marco Munizaga + +### Features + +* add upgrader options ([#290](https://github.com/libp2p/js-libp2p-interfaces/issues/290)) ([c502b66](https://github.com/libp2p/js-libp2p-interfaces/commit/c502b66d87020eb8e2768c49be17392c55503f69)) + +## [@libp2p/interface-stream-muxer-v2.0.2](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-stream-muxer-v2.0.1...@libp2p/interface-stream-muxer-v2.0.2) (2022-08-07) + + +### Trivial Changes + +* update project config ([#271](https://github.com/libp2p/js-libp2p-interfaces/issues/271)) ([59c0bf5](https://github.com/libp2p/js-libp2p-interfaces/commit/59c0bf5e0b05496fca2e4902632b61bb41fad9e9)) + + +### Dependencies + +* update sibling dependencies ([f859920](https://github.com/libp2p/js-libp2p-interfaces/commit/f859920423587ae797ac90ccaa3af8bdf60ae549)) + +## [@libp2p/interface-stream-muxer-v2.0.1](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-stream-muxer-v2.0.0...@libp2p/interface-stream-muxer-v2.0.1) (2022-06-27) + + +### Trivial Changes + +* update deps ([#262](https://github.com/libp2p/js-libp2p-interfaces/issues/262)) ([51edf7d](https://github.com/libp2p/js-libp2p-interfaces/commit/51edf7d9b3765a6f75c915b1483ea345d0133a41)) + +## [@libp2p/interface-stream-muxer-v2.0.0](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-stream-muxer-v1.1.0...@libp2p/interface-stream-muxer-v2.0.0) (2022-06-22) + + +### ⚠ BREAKING CHANGES + +* StreamMuxer now has a `close` method + +### Features + +* add stream muxer close ([#254](https://github.com/libp2p/js-libp2p-interfaces/issues/254)) ([d1f511e](https://github.com/libp2p/js-libp2p-interfaces/commit/d1f511e4b5857769c4eddf902288dc69fcb667b4)) + +## [@libp2p/interface-stream-muxer-v1.1.0](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-stream-muxer-v1.0.2...@libp2p/interface-stream-muxer-v1.1.0) (2022-06-21) + + +### Features + +* add direction to StreamMuxerInit ([#253](https://github.com/libp2p/js-libp2p-interfaces/issues/253)) ([6d34d75](https://github.com/libp2p/js-libp2p-interfaces/commit/6d34d755ff4e798d52945f1f099052bdd6a83f2b)) + +## [@libp2p/interface-stream-muxer-v1.0.2](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-stream-muxer-v1.0.1...@libp2p/interface-stream-muxer-v1.0.2) (2022-06-16) + + +### Trivial Changes + +* update deps ([545264f](https://github.com/libp2p/js-libp2p-interfaces/commit/545264f87a58394d2a7da77e93f3a596e889238f)) + +## [@libp2p/interface-stream-muxer-v1.0.1](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-stream-muxer-v1.0.0...@libp2p/interface-stream-muxer-v1.0.1) (2022-06-14) + + +### Bug Fixes + +* remove components from muxer factory function ([#238](https://github.com/libp2p/js-libp2p-interfaces/issues/238)) ([e4dab30](https://github.com/libp2p/js-libp2p-interfaces/commit/e4dab306d9bf406b9bb3cb92644e28cf81f7bda6)) + + +### Trivial Changes + +* update components module ([#235](https://github.com/libp2p/js-libp2p-interfaces/issues/235)) ([5844207](https://github.com/libp2p/js-libp2p-interfaces/commit/58442070af59aa852c83ec3aecdbd1d2c646b018)) diff --git a/packages/interface-stream-muxer/LICENSE b/packages/interface-stream-muxer/LICENSE new file mode 100644 index 0000000000..20ce483c86 --- /dev/null +++ b/packages/interface-stream-muxer/LICENSE @@ -0,0 +1,4 @@ +This project is dual licensed under MIT and Apache-2.0. + +MIT: https://www.opensource.org/licenses/mit +Apache-2.0: https://www.apache.org/licenses/license-2.0 diff --git a/packages/interface-stream-muxer/LICENSE-APACHE b/packages/interface-stream-muxer/LICENSE-APACHE new file mode 100644 index 0000000000..14478a3b60 --- /dev/null +++ b/packages/interface-stream-muxer/LICENSE-APACHE @@ -0,0 +1,5 @@ +Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. diff --git a/packages/interface-stream-muxer/LICENSE-MIT b/packages/interface-stream-muxer/LICENSE-MIT new file mode 100644 index 0000000000..72dc60d84b --- /dev/null +++ b/packages/interface-stream-muxer/LICENSE-MIT @@ -0,0 +1,19 @@ +The MIT License (MIT) + +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/packages/interface-stream-muxer/README.md b/packages/interface-stream-muxer/README.md new file mode 100644 index 0000000000..fa58c9f692 --- /dev/null +++ b/packages/interface-stream-muxer/README.md @@ -0,0 +1,191 @@ +# @libp2p/interface-stream-muxer + +[![libp2p.io](https://img.shields.io/badge/project-libp2p-yellow.svg?style=flat-square)](http://libp2p.io/) +[![Discuss](https://img.shields.io/discourse/https/discuss.libp2p.io/posts.svg?style=flat-square)](https://discuss.libp2p.io) +[![codecov](https://img.shields.io/codecov/c/github/libp2p/js-libp2p.svg?style=flat-square)](https://codecov.io/gh/libp2p/js-libp2p) +[![CI](https://img.shields.io/github/actions/workflow/status/libp2p/js-libp2p/js-test-and-release.yml?branch=master\&style=flat-square)](https://github.com/libp2p/js-libp2p/actions/workflows/js-test-and-release.yml?query=branch%3Amaster) + +> Stream Muxer interface for libp2p + +## Table of contents + +- [Install](#install) +- [Modules that implement the interface](#modules-that-implement-the-interface) +- [Badge](#badge) +- [Usage](#usage) +- [API](#api) + - [`const muxer = new Muxer([options])`](#const-muxer--new-muxeroptions) + - [`muxer.onStream`](#muxeronstream) + - [`muxer.onStreamEnd`](#muxeronstreamend) + - [`const stream = await muxer.newStream([options])`](#const-stream--await-muxernewstreamoptions) + - [`const streams = muxer.streams`](#const-streams--muxerstreams) +- [API Docs](#api-docs) +- [License](#license) +- [Contribution](#contribution) + +## Install + +```console +$ npm i @libp2p/interface-stream-muxer +``` + +The primary goal of this module is to enable developers to pick and swap their stream muxing module as they see fit for their application, without having to go through shims or compatibility issues. This module and test suite was heavily inspired by [abstract-blob-store](https://github.com/maxogden/abstract-blob-store). + +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 for any other language, pushing forward the cross compatibility and interop through different stacks. + +## Modules that implement the interface + +- [js-libp2p-spdy](https://github.com/libp2p/js-libp2p-spdy) +- [js-libp2p-mplex](https://github.com/libp2p/js-libp2p-mplex) + +Send a PR to add a new one if you happen to find or write one. + +## Badge + +Include this badge in your readme if you make a new module that uses interface-stream-muxer API. + +![](img/badge.png) + +## Usage + +Install `interface-stream-muxer` as one of the dependencies of your project and as a test file. Then, using `mocha` (for JavaScript) or a test runner with compatible API, do: + +```js +const test = require('libp2p-interfaces-compliance-tests/stream-muxer') + +const common = { + async setup () { + return yourMuxer + }, + async teardown () { + // cleanup + } +} + +// use all of the test suits +test(common) +``` + +## API + +A valid (one that follows this abstraction) stream muxer, must implement the following API: + +### `const muxer = new Muxer([options])` + +Create a new *duplex* stream that can be piped together with a connection in order to allow multiplexed communications. + +e.g. + +```js +const Muxer = require('your-muxer-module') +import { pipe } from 'it-pipe' + +// Create a duplex muxer +const muxer = new Muxer() + +// Use the muxer in a pipeline +pipe(conn, muxer, conn) // conn is duplex connection to another peer +``` + +`options` is an optional `Object` that may have the following properties: + +- `onStream` - A function called when receiving a new stream from the remote. e.g. + ```js + // Receive a new stream on the muxed connection + const onStream = stream => { + // Read from this stream and write back to it (echo server) + pipe( + stream, + source => (async function * () { + for await (const data of source) yield data + })() + stream + ) + } + const muxer = new Muxer({ onStream }) + // ... + ``` + **Note:** The `onStream` function can be passed in place of the `options` object. i.e. + ```js + new Mplex(stream => { /* ... */ }) + ``` +- `onStreamEnd` - A function called when a stream ends. + ```js + // Get notified when a stream has ended + const onStreamEnd = stream => { + // Manage any tracking changes, etc + } + const muxer = new Muxer({ onStreamEnd, ... }) + ``` +- `signal` - An [`AbortSignal`](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal) which can be used to abort the muxer, *including* all of it's multiplexed connections. e.g. + ```js + const controller = new AbortController() + const muxer = new Muxer({ signal: controller.signal }) + + pipe(conn, muxer, conn) + + controller.abort() + ``` +- `maxMsgSize` - The maximum size in bytes the data field of multiplexed messages may contain (default 1MB) + +### `muxer.onStream` + +Use this property as an alternative to passing `onStream` as an option to the `Muxer` constructor. + +```js +const muxer = new Muxer() +// ...later +muxer.onStream = stream => { /* ... */ } +``` + +### `muxer.onStreamEnd` + +Use this property as an alternative to passing `onStreamEnd` as an option to the `Muxer` constructor. + +```js +const muxer = new Muxer() +// ...later +muxer.onStreamEnd = stream => { /* ... */ } +``` + +### `const stream = await muxer.newStream([options])` + +Initiate a new stream with the remote. Returns a [duplex stream](https://gist.github.com/alanshaw/591dc7dd54e4f99338a347ef568d6ee9#duplex-it). + +e.g. + +```js +// Create a new stream on the muxed connection +const stream = await muxer.newStream() + +// Use this new stream like any other duplex stream: +pipe([1, 2, 3], stream, consume) +``` + +### `const streams = muxer.streams` + +The streams property returns an array of streams the muxer currently has open. Closed streams will not be returned. + +```js +muxer.streams.map(stream => { + // Log out the stream's id + console.log(stream.id) +}) +``` + +## API Docs + +- + +## License + +Licensed under either of + +- Apache 2.0, ([LICENSE-APACHE](LICENSE-APACHE) / ) +- MIT ([LICENSE-MIT](LICENSE-MIT) / ) + +## Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. diff --git a/packages/interface-stream-muxer/img/badge.png b/packages/interface-stream-muxer/img/badge.png new file mode 100644 index 0000000000000000000000000000000000000000..738bcf4a2f5d9383b844ebc9d0f2ce6d2aa1f642 GIT binary patch literal 7694 zcmV+p9`WIcP)Py7$Vo&&RCodHT@O@L*O~vJpn!scf(A^$K#V3jB)AsZZd5SoO4H-etl8{Q=+B1K z_TAg_;W`4ojt#e3y#m*9JiOPp&j|SV=%fJfO4{4!y?9&V;Rtv^>)}9ov)w);;05jN z^DewC@o)sZp!IN|yxDG_5pYt(`fb)Oxgj6(&xch)z={CWKDFz4zYRZ&P9C z>;32PrVQ&5&|k5FWxv*gzYu7)zz8^44{l#Otq$0cBSs=9C`bX84UdeB#Q5>!5i~Xk!9l^W zT(`h%Hs1lS#oGl>N5C_B;Aw5QC?5e^Syc(&k-nH1HW58NJ+N3T@b&dYkEI6z0Rigp z8|5c#zz58K{rYu`9656E7Ff*a=x9ghRUj}l6k$RBgO~Bw!~ z4J#&0n1IovMk^^xN^t4YC6MEi4*2@{D!^J{#Yo?g0>lov8i>cVp=f;ueEb3s8Xk%0 z=tzVI2OutW9rialN@^3DYHLwj-v-NV^{NJGjJX!8WhUZm#3TQh$+dv_7&3bEq@&7B z7S}?zU(9Ui9z|YrD(E&;gN>`>+>XI5TPEiH{ z>y;~4(B9sT3+FFj#E5>nV>*F%Y;SAr&Kg*cmf_v9qv)(Fg;5PhG5%wFwYm!Mvnh2` zgAw2~RJZw!9}wiw#Oje||MQ-j?!&^u)YMP@ zN^p`c=zt9h4pKm-lqH9y0!B$Z`L4+b2?;@6U0pv7mp7_{0r7KUd-DXvJc<=-lhl3O z#6Z-StjFT#U(wQwm*Ly#t5I~UL&c}3r(=E5e(cU)t=g5EUnTi0I99R|smXCF&L=Jz z*<1EI$*{Jn60A;7MO>UVZO0|2V`E7bbUg8ytUEiu2-}3d#Hr~!4jCJFqphPB8#9s- zmn`KaK0SLIT0G_~n^AJKqqnaxS7YY}?T2ZP4McrLVcZ1R1I6SeL&a zr*^MLs-$Pgc$D=vpauD>87JeiyUAANAmbqvX1$EXx^0J0e`p(0)tKBoP2s#MSm-C* zka&mVvgxw*;K75|0|yRRFI~Lkl;)Vvlk3jTPV2U9+pKSIe_Pe}@y8!qKmFuWYhz=h z_2kKu*5k*ITN@i1trss|v|hPekw6&+AyuxT(^)(fVCirM-^^TUlv`-o@5ES=zG=2CG(|(OTKb!kyMlNm~BA z%67drY5OKEe^Q>c-P&l)((9Ca7OTkB$I58k*Qz~bu<@{VR$66d>m38FpGIpn2Wybv zFeXEHnECkV$9R72^Z5DCe~w@L;upxt$w5P7gVZ_nK;^2WxI}6Hl(3`(>Do35I9j5bQzEDB0I|(~$+NGWzl-i`f6vUp! zE-5E*S=x+HyyGw&q3$uo3z!?%f zIR5A3$j!|~TWcGFgM-!a`Pt9$v!DJ9OdCqLa`g&+`@7$Q%2#-JxKg@k1z=zt0XcTe zSPig@m*3}&8#mC_)`qKBuBwFv0=%QW1Lw}3Lv?jE{Qdjyeaxbl(bX(dc9{lT|3Dk34IT}DF$fAvu?IsIO7k-i9}|U3mH(~9CoROR37t50>=@bu z?nkPK5{A<9lZgM9Vzf3lqVA<{p{uDDhssLu+g%k}hCY7{YyTaKr-w+aFAQ@vfWo){ z--u$Ld@(=uDXoM6%&|#*s*&4j1mB;8zmus2D8#PA0&&X;6e>=?9I5l}cP-*UjBmt3 z5ir^hzq(ihtTC?w&nB?VK+StQwOR%k4=scCcWd42Se!5&5n=j|)-iS9YmY%obJK5r z{TrnSg-!_7KqjD}Vnoe;{rdIz;DZkaewrFO=(Q)LHD(8{Q1P6708p4Y%jY{E6g1&sIRXVP|d*NjOXyy zyGPN}*^25tTSb;>A@ijoX9?EXrxLj}KK(c%^yqHeMfn;SqGMq&7!Vt6z)A{V2@U2R zn43SwD?eN$@sWs(o`J*#My%Pay>Km;{qNMM@x3>pk>Tpvm~4RlGwjh*-Ly+j($C#~ zlknJ54PcqS-;er(?}N=Uta=Kw1Y^34*YmqO`@vzA(Uu`<(bK)-#i+E!oP}(`gYnR_ z0?QGvevI~*p_0QJ`h zDphY4z6DBXMG0hqfPm%HPddOw1g>~dvEt$a!wu^VoIQIM9Tz*G78FMKs5mNV!hbPB z7WU@OovYg5?ztykPeE*KEF$N>)~h7>28Cf(@)8tOZ_+4*>?8GJ&3MTV|R1 zYZ~iNQ&XcIBJnFLDzPK&9=yGBE^>;iVK9n>uB=95YbTm@`q8(c`b(GiUV!$^J7Bk3dV4Qr*cq+OEiE{I{=5PuSHNhGC15}K zT6n>VGgD`&QzV<11>I1O-RZy1lqm(_9_RJP+9h*Qlt)XR3UJwD3Km8Py$nWHn%h*_b0hZS*xh1 zP>R#3hEv#_zZp%9O`3!i=NBJJOrZpnu!2oYoG=lgA)&qYSpt>-r<5h%B_*ID#{9Li zYDLhK5>SB~C`2rL2BQ6{kSTmNz$Xr=sbWU*i^Q_cni**Mnr}g7PJ56S5-KLF{QVl& zwI^s-Aqk6-tLog0#Lzgb&dx@9axj*v`qJ>|*B(Z^jjgOOBp_qmI!O z%OPD(^{sv2d$(3E!Kqz>GKVkbrRDZkl$84fV%$o%Ak0bD8k%E_M*6yJWF&`TnS)5R zh^Z>muuRouSX+u0GM>fHcCJ$Q#VclGizuCgj>kh`y>0oT7aoDOtgK7{jgBko5UEPZ zN{L8c0D_h)RRkbCuLy1en930;!;3Gzhz+l7!09umm4xLY0jG2&F{%BF&6Pg@a%BZh z0W(2-`m@tGbNY)ANvJ8=AtLOl&SU%4}(4`;kHTX!G;fpbD-+Vdg@0 z6|*=K1*-4Bvb0|bO&DxE>`(rpSAq@|1R{bHnV#K+AN=44Sg>G$;;+oZgCL@of28t= z0uw0$l_qNRx>O{{R9=ygbSos_Nht#S15{n)s;q}jF*6^WsnV2!CH1#_-lE(PX%S$4 zE+TNb_SC6Uitna;JHFM!9Ab)rvvS8sRuSFbdEEZq+akxu$ zE?_owpxtCafb=iK#R10xllVnQGJ-wX39^ql(_|A8}qvG^l zIGua@<`nC_8RjD}WYAI(qJ5Tpl{*JW2R=LfnR3QffR(A9PAmjyuf(O)wH*YV0q6%i4ES%z7dF=GbCj~kDZbtkc7#}0%|48xeQW0b{#lzjygMnc5v0$P1h+!?~A=p9eOgCUg@+#kdl`&PitE%{`ki~D)SZh zcF{^e3j(KqO5AsLyrY@R#P+I!&=6&}y?h0A^>vszb0*T34gs)wW4z}(dIW9?wC+ZP z(>V{?XBj^F=p!6Hd{{ZX&~8h+F1N{00+Rw9K5`h%&CP0~BI5{FE*#KNMqCnMh%;c> z_m}l=C2-dWZ`@a61a28<_0bX^SG5Cy762|5Z~;OAmV3QKu_9ne9}fTNu%^6;MS-?l zN@sFjS}I5(mjBc8F9)!u799HUe{ty3Q_|#cOiOqK^Alov9aHpveAUiPRfk}iIvX(| z&XDM+ufnI8ii8-a^}>FKXQ=u6t!RJ*k5=+)Us{B48B{XhO|$-m9j*~a4v zJjm$$v+|&TyDanrpanqx0t_8zNE5h<#Xw+kp@60{x)#!zhIUzcaxsp8W#B@^!w)~K z79do_E&S6`mW|kyOyupU!?VdTy){4gqt#d!Fas+K7vbISYfHJkiFeS&QE`9?O384$ z`Ul@w6o{8v=ycr?Qtx;hj>!6LUX6o?mY(GH+<}AQXOmSi&28$fiu^*7mJ;)wo$si1 zd@5qPbfv2z&0L)J$yaF&p!UaQUs@eV59r`csY}}}Ed>NF^ONV^@*t9A&4Ns1j28-l!rVnzz3o_^96-zd*RWylRR?bbTNcwP?3T+*@BL?Qo02m&qTcSA<6M`y zLGl~$&1r6^gm~0#%TBkAgAbBdZ-LX!edn46yt^sTazi1vdcX0;8_KCAOxDo3#7hq? z+H46%N>ILNQUKBeI=Y-`KBG1xF8rH?Djz|r6fmI&qzL4@vL{UW91Pg%Attu$dsF;C zm*bg5QEp34|Ngt^dGBeLmv(GRI%cG-K+2wK+40+oimeY|#e!&TbLVDfX-0X@W~_Yj zNlbpW2JMaekXX0^(LzTG$NvD$Qhwj2V62$C!`a!~)*?&uab#}J!O|zou&T07c0AN! zYg`%TMrOgCpV|KG`7o|Lk8I{`ZA9t9qez+Yx=Y>ukqhvz{}?CkIR+HuzKZmODK2AB zU%C=ymt3O7iM!gDJT`@7 zQM_nTAm62e#v^56it^|(cbk=$*Ny8p)FJ{wOXnEoeS62-s*YRr=j`!az}lx^Ck3uG zlXE+Tj{QhmR)|fNo#LuDOQ=j35|(6Pt9V1@?sD}NwzQs9o=~~_T1A>qM@ZB|c#sZY z#sa+c+#*Cs`8nVChD7+gouXT7s+bx3+TX>Z_!xvm#9&EIfk15+PP<4pGtRY7l_|YI z%NrJjM^jRrn%{flAA&ivrpoQp_}hh#$X21gQc#=Hm(F7z=g?TSM(F zA|B7Zwo^oQF>2gQ0L@hgWK%+-sJG6czUW1)sm?{~QK=^;3}I0Tc)6rN_RE5S_{#ogL4&TTVl_vOHlSBXa)F(ot# znBXSAKT@;uaIpLxp>+0BAz2M36cLjSdx=n*^O8{=BykY4d*E=SOM6{?|53<~00nOFp{Axn8zX0%rb5PvVs6 z1^G0UTn-_4ENL%wUc#~C$CO2YRsh;mxtP#-sZ%Lc*SfC>?zDgOHK1QD{bjlIOULhN z_f5lt4?d`xM!y0|Rr1(gdcgYRC9xbW4!`{6FV%|J5c)De)(fcab_JR$AQCm%$93=h zRg-Y*#~vF7`Guh=?rw$egLcawavXE~U6p5UiKhuuFQy)mo(m8$VWK|tF zA_~mngg(;Fd_~}K!R1-?yR+dJ9F6}dugB<#_u|nfo-rt4GUO=#yacujBj5S=-Kt<+GG~Fm3hnAA+aM z!`9-TB5sI)^%S2YzS5q0DM5uIK|ymM}Ho zf93SRwcg_DqH6m~;#B@aJmAze6LyIeWO->b@)t(f3e=-Ou2LR!2AE0|uD!Pycr4Eq zOiWi(dYaK?N=HXM$|QY`)20v$KKRW_p#Y_r-WNIYBh3*RTp8`uSk;8?a``>~^jUmA z-Y%M}wyeYZqrT}ZtX1y8|JczoNM7aKEubXec+Eht5v1I5LqPLd#fkDokkcQ5pDUsR zIm_vR`RHT7Je0Khq3?d+J>4L=FYKk4UQ(Yu(#zh=Icm`=VP=Iee0v=ZHFe03%q))C zO(@Ddi z4~yQt>aXmQ*L)H(N#mRM+9j|4M6+yd7hEByuoT)S#Vn{T$;RxJ<=Fb#Ke&<1GO;$D z)ryqggkR0xtyZ}s{`$EQc+G8I!@nh=0#t3;XmH?cY>9xQ&*W$Q6}Sq`Sph}4S@ z>FhCRx#NIRmVhNapmR2rFMazBr}S~-$En>11RcL?aQgIVJoL~*h`7rc5|qN+9!}6w z(oz{yXHnu9M`v&z+~i0u{FB_DsC{Nb>X}pKD7@TWjoipxNQ|b1BLKmn(Rea52sR+X zZmt!Hz80c~xP)dZJZX8@6qt)}`GAOzj~`|j1+bN^SmI(k4Z`9*c_^2?S;`C-5RSc5 z)(R|(k(gQHs*MLslnssLm>V8|?EP(hp528{D}ZFt?E8ks17$Gg6=Un&Z`#@vfW&1d zRht$?>HV~qI&(;jU0Dgv`|MP*FBWFWEQB?P4bb`)yI>9SO6%aR9(#@bU-{UQtUT+= zluLG%3cUaR`^s#^FLRTx>QYtXDcZ;3SoZAaXO+E`^kM1JrE2-MZ=J7fcm>v-uL}aKl5@E!GDvN)XUfr%pxY3z_Oi(zh~?{S+3YOkbqh+SVsUbY>Ds1KnGlHDnp*suj3_kGlp`<%I2 z#Vs!cDZj)`V3Eg?pK|8{D<+t^9h{#oq7Q6;J7uYuDd}pX2b9#Ti>qRk)a1$pJp-Rd zQ(NsPwV3l)bNaMuXt%xszM*KV1gqftOAKP)-7I|c8I5y#{5;JD>ld(WI`_KW zd>1@!(;$xs5z2Qfg2(iL594$_YvwG-2jP?iBsB;e6R1p8l$5lwUc7WsEe;rF8PqnK z{u;^e1kgp1UR{*7{FsK?_$Vu71Sw5s{86x8-6xk^_>Kz}Kg`M9gh%gl0N@?h$y?i@ zAxT<3Iw2uJZMot08v>X>A`d072tahnA z!6l#L#P|N;drDdk)F*XoeY3i~=LhK4JikUsY+4iKySGa6GM?Z1 z;{Se$`@=HjLm2l@yWedcE|GeVToSwy!+8W86*2u4-xO$X#UOkjOSbgEjgF31<}!Y| zv$3gBN$upMWc8J7a$S#u-Xo6-cry-%5xA{D%K_tOIP>!Ilw&w82mBbvcfb2xJEUn=iU+Uj(~RrzWfpRKkk=Q@VaJAMgRZ+07*qo IM6N<$f@W_bMF0Q* literal 0 HcmV?d00001 diff --git a/packages/interface-stream-muxer/img/badge.sketch b/packages/interface-stream-muxer/img/badge.sketch new file mode 100644 index 0000000000000000000000000000000000000000..990ad1c5bf41d62134cb37a14df7086e62098dac GIT binary patch literal 49152 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 n6MYglCDaK+qC62z)F;Ly#wR9 + + + badge + Created with Sketch. + + + + + + Stream Muxer + + + Compatibl + e + + + \ No newline at end of file diff --git a/packages/interface-stream-muxer/package.json b/packages/interface-stream-muxer/package.json new file mode 100644 index 0000000000..e90f1e19ab --- /dev/null +++ b/packages/interface-stream-muxer/package.json @@ -0,0 +1,166 @@ +{ + "name": "@libp2p/interface-stream-muxer", + "version": "4.1.2", + "description": "Stream Muxer interface for libp2p", + "license": "Apache-2.0 OR MIT", + "homepage": "https://github.com/libp2p/js-libp2p/tree/master/packages/interface-stream-muxer#readme", + "repository": { + "type": "git", + "url": "git+https://github.com/libp2p/js-libp2p.git" + }, + "bugs": { + "url": "https://github.com/libp2p/js-libp2p/issues" + }, + "keywords": [ + "interface", + "libp2p" + ], + "type": "module", + "types": "./dist/src/index.d.ts", + "typesVersions": { + "*": { + "*": [ + "*", + "dist/*", + "dist/src/*", + "dist/src/*/index" + ], + "src/*": [ + "*", + "dist/*", + "dist/src/*", + "dist/src/*/index" + ] + } + }, + "files": [ + "src", + "dist", + "!dist/test", + "!**/*.tsbuildinfo" + ], + "exports": { + ".": { + "types": "./dist/src/index.d.ts", + "import": "./dist/src/index.js" + }, + "./stream": { + "types": "./dist/src/stream.d.ts", + "import": "./dist/src/stream.js" + } + }, + "eslintConfig": { + "extends": "ipfs", + "parserOptions": { + "sourceType": "module" + } + }, + "release": { + "branches": [ + "master" + ], + "plugins": [ + [ + "@semantic-release/commit-analyzer", + { + "preset": "conventionalcommits", + "releaseRules": [ + { + "breaking": true, + "release": "major" + }, + { + "revert": true, + "release": "patch" + }, + { + "type": "feat", + "release": "minor" + }, + { + "type": "fix", + "release": "patch" + }, + { + "type": "docs", + "release": "patch" + }, + { + "type": "test", + "release": "patch" + }, + { + "type": "deps", + "release": "patch" + }, + { + "scope": "no-release", + "release": false + } + ] + } + ], + [ + "@semantic-release/release-notes-generator", + { + "preset": "conventionalcommits", + "presetConfig": { + "types": [ + { + "type": "feat", + "section": "Features" + }, + { + "type": "fix", + "section": "Bug Fixes" + }, + { + "type": "chore", + "section": "Trivial Changes" + }, + { + "type": "docs", + "section": "Documentation" + }, + { + "type": "deps", + "section": "Dependencies" + }, + { + "type": "test", + "section": "Tests" + } + ] + } + } + ], + "@semantic-release/changelog", + "@semantic-release/npm", + "@semantic-release/github", + "@semantic-release/git" + ] + }, + "scripts": { + "clean": "aegir clean", + "lint": "aegir lint", + "dep-check": "aegir dep-check", + "build": "aegir build", + "release": "aegir release" + }, + "dependencies": { + "@libp2p/interface-connection": "^5.0.0", + "@libp2p/interfaces": "^3.0.0", + "@libp2p/logger": "^2.1.1", + "abortable-iterator": "^5.0.1", + "any-signal": "^4.1.1", + "it-pushable": "^3.1.3", + "it-stream-types": "^2.0.1", + "uint8arraylist": "^2.4.3" + }, + "devDependencies": { + "aegir": "^39.0.5" + }, + "typedoc": { + "entryPoint": "./src/index.ts" + } +} diff --git a/packages/interface-stream-muxer/src/index.ts b/packages/interface-stream-muxer/src/index.ts new file mode 100644 index 0000000000..da782d5314 --- /dev/null +++ b/packages/interface-stream-muxer/src/index.ts @@ -0,0 +1,58 @@ +import type { Direction, Stream } from '@libp2p/interface-connection' +import type { AbortOptions } from '@libp2p/interfaces' +import type { Duplex, Source } from 'it-stream-types' +import type { Uint8ArrayList } from 'uint8arraylist' + +export interface StreamMuxerFactory { + /** + * The protocol used to select this muxer during connection opening + */ + protocol: string + + /** + * Creates a new stream muxer to be used with a new connection + */ + createStreamMuxer: (init?: StreamMuxerInit) => StreamMuxer +} + +/** + * A libp2p stream muxer + */ +export interface StreamMuxer extends Duplex, Source, Promise> { + /** + * The protocol used to select this muxer during connection opening + */ + protocol: string + + /** + * A list of streams that are currently open. Closed streams will not be returned. + */ + readonly streams: Stream[] + /** + * Initiate a new stream with the given name. If no name is + * provided, the id of the stream will be used. + */ + newStream: (name?: string) => Stream | Promise + + /** + * Close or abort all tracked streams and stop the muxer + */ + close: (err?: Error) => void +} + +export interface StreamMuxerInit extends AbortOptions { + /** + * A callback function invoked every time an incoming stream is opened + */ + onIncomingStream?: (stream: Stream) => void + + /** + * A callback function invoke every time a stream ends + */ + onStreamEnd?: (stream: Stream) => void + + /** + * Outbound stream muxers are opened by the local node, inbound stream muxers are opened by the remote + */ + direction?: Direction +} diff --git a/packages/interface-stream-muxer/src/stream.ts b/packages/interface-stream-muxer/src/stream.ts new file mode 100644 index 0000000000..08d08ed517 --- /dev/null +++ b/packages/interface-stream-muxer/src/stream.ts @@ -0,0 +1,361 @@ +import { CodeError } from '@libp2p/interfaces/errors' +import { logger } from '@libp2p/logger' +import { abortableSource } from 'abortable-iterator' +import { anySignal } from 'any-signal' +import { type Pushable, pushable } from 'it-pushable' +import { Uint8ArrayList } from 'uint8arraylist' +import type { Direction, Stream, StreamStat } from '@libp2p/interface-connection' +import type { Source } from 'it-stream-types' + +const log = logger('libp2p:stream') + +const ERR_STREAM_RESET = 'ERR_STREAM_RESET' +const ERR_STREAM_ABORT = 'ERR_STREAM_ABORT' +const ERR_SINK_ENDED = 'ERR_SINK_ENDED' +const ERR_DOUBLE_SINK = 'ERR_DOUBLE_SINK' + +export interface AbstractStreamInit { + /** + * A unique identifier for this stream + */ + id: string + + /** + * The stream direction + */ + direction: Direction + + /** + * The maximum allowable data size, any data larger than this will be + * chunked and sent in multiple data messages + */ + maxDataSize: number + + /** + * User specific stream metadata + */ + metadata?: Record + + /** + * Invoked when the stream ends + */ + onEnd?: (err?: Error | undefined) => void +} + +function isPromise (res?: any): res is Promise { + return res != null && typeof res.then === 'function' +} + +export abstract class AbstractStream implements Stream { + public id: string + public stat: StreamStat + public metadata: Record + public source: AsyncGenerator + + private readonly abortController: AbortController + private readonly resetController: AbortController + private readonly closeController: AbortController + private sourceEnded: boolean + private sinkEnded: boolean + private sinkSunk: boolean + private endErr: Error | undefined + private readonly streamSource: Pushable + private readonly onEnd?: (err?: Error | undefined) => void + private readonly maxDataSize: number + + constructor (init: AbstractStreamInit) { + this.abortController = new AbortController() + this.resetController = new AbortController() + this.closeController = new AbortController() + this.sourceEnded = false + this.sinkEnded = false + this.sinkSunk = false + + this.id = init.id + this.metadata = init.metadata ?? {} + this.stat = { + direction: init.direction, + timeline: { + open: Date.now() + } + } + this.maxDataSize = init.maxDataSize + this.onEnd = init.onEnd + + this.source = this.streamSource = pushable({ + onEnd: () => { + // already sent a reset message + if (this.stat.timeline.reset !== null) { + const res = this.sendCloseRead() + + if (isPromise(res)) { + res.catch(err => { + log.error('error while sending close read', err) + }) + } + } + + this.onSourceEnd() + } + }) + + // necessary because the libp2p upgrader wraps the sink function + this.sink = this.sink.bind(this) + } + + protected onSourceEnd (err?: Error): void { + if (this.sourceEnded) { + return + } + + this.stat.timeline.closeRead = Date.now() + this.sourceEnded = true + log.trace('%s stream %s source end - err: %o', this.stat.direction, this.id, err) + + if (err != null && this.endErr == null) { + this.endErr = err + } + + if (this.sinkEnded) { + this.stat.timeline.close = Date.now() + + if (this.onEnd != null) { + this.onEnd(this.endErr) + } + } + } + + protected onSinkEnd (err?: Error): void { + if (this.sinkEnded) { + return + } + + this.stat.timeline.closeWrite = Date.now() + this.sinkEnded = true + log.trace('%s stream %s sink end - err: %o', this.stat.direction, this.id, err) + + if (err != null && this.endErr == null) { + this.endErr = err + } + + if (this.sourceEnded) { + this.stat.timeline.close = Date.now() + + if (this.onEnd != null) { + this.onEnd(this.endErr) + } + } + } + + // Close for both Reading and Writing + close (): void { + log.trace('%s stream %s close', this.stat.direction, this.id) + + this.closeRead() + this.closeWrite() + } + + // Close for reading + closeRead (): void { + log.trace('%s stream %s closeRead', this.stat.direction, this.id) + + if (this.sourceEnded) { + return + } + + this.streamSource.end() + } + + // Close for writing + closeWrite (): void { + log.trace('%s stream %s closeWrite', this.stat.direction, this.id) + + if (this.sinkEnded) { + return + } + + this.closeController.abort() + + try { + // need to call this here as the sink method returns in the catch block + // when the close controller is aborted + const res = this.sendCloseWrite() + + if (isPromise(res)) { + res.catch(err => { + log.error('error while sending close write', err) + }) + } + } catch (err) { + log.trace('%s stream %s error sending close', this.stat.direction, this.id, err) + } + + this.onSinkEnd() + } + + // Close for reading and writing (local error) + abort (err: Error): void { + log.trace('%s stream %s abort', this.stat.direction, this.id, err) + // End the source with the passed error + this.streamSource.end(err) + this.abortController.abort() + this.onSinkEnd(err) + } + + // Close immediately for reading and writing (remote error) + reset (): void { + const err = new CodeError('stream reset', ERR_STREAM_RESET) + this.resetController.abort() + this.streamSource.end(err) + this.onSinkEnd(err) + } + + async sink (source: Source): Promise { + if (this.sinkSunk) { + throw new CodeError('sink already called on stream', ERR_DOUBLE_SINK) + } + + this.sinkSunk = true + + if (this.sinkEnded) { + throw new CodeError('stream closed for writing', ERR_SINK_ENDED) + } + + const signal = anySignal([ + this.abortController.signal, + this.resetController.signal, + this.closeController.signal + ]) + + try { + source = abortableSource(source, signal) + + if (this.stat.direction === 'outbound') { // If initiator, open a new stream + const res = this.sendNewStream() + + if (isPromise(res)) { + await res + } + } + + for await (let data of source) { + while (data.length > 0) { + if (data.length <= this.maxDataSize) { + const res = this.sendData(data instanceof Uint8Array ? new Uint8ArrayList(data) : data) + + if (isPromise(res)) { // eslint-disable-line max-depth + await res + } + + break + } + data = data instanceof Uint8Array ? new Uint8ArrayList(data) : data + const res = this.sendData(data.sublist(0, this.maxDataSize)) + + if (isPromise(res)) { + await res + } + + data.consume(this.maxDataSize) + } + } + } catch (err: any) { + if (err.type === 'aborted' && err.message === 'The operation was aborted') { + if (this.closeController.signal.aborted) { + return + } + + if (this.resetController.signal.aborted) { + err.message = 'stream reset' + err.code = ERR_STREAM_RESET + } + + if (this.abortController.signal.aborted) { + err.message = 'stream aborted' + err.code = ERR_STREAM_ABORT + } + } + + // Send no more data if this stream was remotely reset + if (err.code === ERR_STREAM_RESET) { + log.trace('%s stream %s reset', this.stat.direction, this.id) + } else { + log.trace('%s stream %s error', this.stat.direction, this.id, err) + try { + const res = this.sendReset() + + if (isPromise(res)) { + await res + } + + this.stat.timeline.reset = Date.now() + } catch (err) { + log.trace('%s stream %s error sending reset', this.stat.direction, this.id, err) + } + } + + this.streamSource.end(err) + this.onSinkEnd(err) + + throw err + } finally { + signal.clear() + } + + try { + const res = this.sendCloseWrite() + + if (isPromise(res)) { + await res + } + } catch (err) { + log.trace('%s stream %s error sending close', this.stat.direction, this.id, err) + } + + this.onSinkEnd() + } + + /** + * When an extending class reads data from it's implementation-specific source, + * call this method to allow the stream consumer to read the data. + */ + sourcePush (data: Uint8ArrayList): void { + this.streamSource.push(data) + } + + /** + * Returns the amount of unread data - can be used to prevent large amounts of + * data building up when the stream consumer is too slow. + */ + sourceReadableLength (): number { + return this.streamSource.readableLength + } + + /** + * Send a message to the remote muxer informing them a new stream is being + * opened + */ + abstract sendNewStream (): void | Promise + + /** + * Send a data message to the remote muxer + */ + abstract sendData (buf: Uint8ArrayList): void | Promise + + /** + * Send a reset message to the remote muxer + */ + abstract sendReset (): void | Promise + + /** + * Send a message to the remote muxer, informing them no more data messages + * will be sent by this end of the stream + */ + abstract sendCloseWrite (): void | Promise + + /** + * Send a message to the remote muxer, informing them no more data messages + * will be read by this end of the stream + */ + abstract sendCloseRead (): void | Promise +} diff --git a/packages/interface-stream-muxer/tsconfig.json b/packages/interface-stream-muxer/tsconfig.json new file mode 100644 index 0000000000..181d553f61 --- /dev/null +++ b/packages/interface-stream-muxer/tsconfig.json @@ -0,0 +1,18 @@ +{ + "extends": "aegir/src/config/tsconfig.aegir.json", + "compilerOptions": { + "outDir": "dist" + }, + "include": [ + "src", + "test" + ], + "references": [ + { + "path": "../interface-connection" + }, + { + "path": "../interfaces" + } + ] +} diff --git a/packages/interface-transport-compliance-tests/CHANGELOG.md b/packages/interface-transport-compliance-tests/CHANGELOG.md new file mode 100644 index 0000000000..ae8cbd794f --- /dev/null +++ b/packages/interface-transport-compliance-tests/CHANGELOG.md @@ -0,0 +1,235 @@ +## [@libp2p/interface-transport-compliance-tests-v4.0.2](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-transport-compliance-tests-v4.0.1...@libp2p/interface-transport-compliance-tests-v4.0.2) (2023-05-04) + + +### Dependencies + +* update sibling dependencies ([eae5fe0](https://github.com/libp2p/js-libp2p-interfaces/commit/eae5fe02ea11c2930242a8d91ee4bc22f9bebc5c)) + +## [@libp2p/interface-transport-compliance-tests-v4.0.1](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-transport-compliance-tests-v4.0.0...@libp2p/interface-transport-compliance-tests-v4.0.1) (2023-05-04) + + +### Dependencies + +* bump aegir from 38.1.8 to 39.0.5 ([#393](https://github.com/libp2p/js-libp2p-interfaces/issues/393)) ([31f3797](https://github.com/libp2p/js-libp2p-interfaces/commit/31f3797b24f7c23f3f16e9db3a230bd5f7cd5175)) + +## [@libp2p/interface-transport-compliance-tests-v4.0.0](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-transport-compliance-tests-v3.0.13...@libp2p/interface-transport-compliance-tests-v4.0.0) (2023-04-21) + + +### ⚠ BREAKING CHANGES + +* add libp2p events (#373) + +### Features + +* add libp2p events ([#373](https://github.com/libp2p/js-libp2p-interfaces/issues/373)) ([071c718](https://github.com/libp2p/js-libp2p-interfaces/commit/071c718808902858818ca86167b51b242b67a5a5)) + + +### Dependencies + +* update sibling dependencies ([74f82d5](https://github.com/libp2p/js-libp2p-interfaces/commit/74f82d53fc89740f4bafa22721a59ab70c3c92a8)) +* update sibling dependencies ([17ed429](https://github.com/libp2p/js-libp2p-interfaces/commit/17ed429d57e83cb38484ac52a0e0975a7d8af963)) + +## [@libp2p/interface-transport-compliance-tests-v3.0.13](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-transport-compliance-tests-v3.0.12...@libp2p/interface-transport-compliance-tests-v3.0.13) (2023-04-18) + + +### Dependencies + +* update sibling dependencies ([294d970](https://github.com/libp2p/js-libp2p-interfaces/commit/294d970d6e4fbbf6a3f0944394c4c8dea06d1265)) +* update sibling dependencies ([99a862b](https://github.com/libp2p/js-libp2p-interfaces/commit/99a862baed66d4e83ba006a70c33561855c9682e)) +* update sibling dependencies ([2f52a28](https://github.com/libp2p/js-libp2p-interfaces/commit/2f52a284b59c0a88b040f86da1f5d3f044727f2c)) + +## [@libp2p/interface-transport-compliance-tests-v3.0.12](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-transport-compliance-tests-v3.0.11...@libp2p/interface-transport-compliance-tests-v3.0.12) (2023-04-14) + + +### Dependencies + +* bump it-drain from 2.0.1 to 3.0.1 ([#358](https://github.com/libp2p/js-libp2p-interfaces/issues/358)) ([ec050fa](https://github.com/libp2p/js-libp2p-interfaces/commit/ec050fa215a2ee845366c95d763ff009b247e986)) + +## [@libp2p/interface-transport-compliance-tests-v3.0.11](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-transport-compliance-tests-v3.0.10...@libp2p/interface-transport-compliance-tests-v3.0.11) (2023-04-13) + + +### Dependencies + +* bump it-all from 2.0.1 to 3.0.1 ([#360](https://github.com/libp2p/js-libp2p-interfaces/issues/360)) ([1b439eb](https://github.com/libp2p/js-libp2p-interfaces/commit/1b439eb7503ed7e31e77f17ce0a685ea78d94442)) + +## [@libp2p/interface-transport-compliance-tests-v3.0.10](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-transport-compliance-tests-v3.0.9...@libp2p/interface-transport-compliance-tests-v3.0.10) (2023-04-11) + + +### Dependencies + +* update sibling dependencies ([b034810](https://github.com/libp2p/js-libp2p-interfaces/commit/b0348102e41dc18166e70063f4708a2b3544f4b6)) + +## [@libp2p/interface-transport-compliance-tests-v3.0.9](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-transport-compliance-tests-v3.0.8...@libp2p/interface-transport-compliance-tests-v3.0.9) (2023-04-04) + + +### Dependencies + +* bump it-pipe from 2.0.5 to 3.0.1 ([#363](https://github.com/libp2p/js-libp2p-interfaces/issues/363)) ([625817b](https://github.com/libp2p/js-libp2p-interfaces/commit/625817b0bbbee276983c40a0604c8810a25abe8f)) + +## [@libp2p/interface-transport-compliance-tests-v3.0.8](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-transport-compliance-tests-v3.0.7...@libp2p/interface-transport-compliance-tests-v3.0.8) (2023-03-17) + + +### Dependencies + +* update @multiformats/multiaddr to 12.0.0 ([#354](https://github.com/libp2p/js-libp2p-interfaces/issues/354)) ([e0f327b](https://github.com/libp2p/js-libp2p-interfaces/commit/e0f327b5d54e240feabadce21a841629d633ec5e)) + +## [@libp2p/interface-transport-compliance-tests-v3.0.7](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-transport-compliance-tests-v3.0.6...@libp2p/interface-transport-compliance-tests-v3.0.7) (2023-01-18) + + +### Trivial Changes + +* remove lerna ([#330](https://github.com/libp2p/js-libp2p-interfaces/issues/330)) ([6678592](https://github.com/libp2p/js-libp2p-interfaces/commit/6678592dd0cf601a2671852f9d2a0aff5dee2b18)) + + +### Dependencies + +* bump aegir from 37.12.1 to 38.1.0 ([#335](https://github.com/libp2p/js-libp2p-interfaces/issues/335)) ([7368a36](https://github.com/libp2p/js-libp2p-interfaces/commit/7368a363423a08e8fa247dcb76ea13e4cf030d65)) + +## [@libp2p/interface-transport-compliance-tests-v3.0.6](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-transport-compliance-tests-v3.0.5...@libp2p/interface-transport-compliance-tests-v3.0.6) (2023-01-06) + + +### Dependencies + +* update sibling dependencies ([1442ad3](https://github.com/libp2p/js-libp2p-interfaces/commit/1442ad37e44f886a423e7a09e53e0b1796327fde)) + +## [@libp2p/interface-transport-compliance-tests-v3.0.5](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-transport-compliance-tests-v3.0.4...@libp2p/interface-transport-compliance-tests-v3.0.5) (2022-12-16) + + +### Documentation + +* update project config ([#323](https://github.com/libp2p/js-libp2p-interfaces/issues/323)) ([0fc6a08](https://github.com/libp2p/js-libp2p-interfaces/commit/0fc6a08e9cdcefe361fe325281a3a2a03759ff59)) + +## [@libp2p/interface-transport-compliance-tests-v3.0.4](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-transport-compliance-tests-v3.0.3...@libp2p/interface-transport-compliance-tests-v3.0.4) (2022-12-14) + + +### Bug Fixes + +* generate docs for all packages ([#321](https://github.com/libp2p/js-libp2p-interfaces/issues/321)) ([b6f8b32](https://github.com/libp2p/js-libp2p-interfaces/commit/b6f8b32a920c15a28fe021e6050e31aaae89d518)) + + +### Dependencies + +* bump sinon from 14.0.2 to 15.0.0 ([#316](https://github.com/libp2p/js-libp2p-interfaces/issues/316)) ([d37721c](https://github.com/libp2p/js-libp2p-interfaces/commit/d37721c9143cd3eeafb5f8249b07d9f2fbce0f54)) + +## [@libp2p/interface-transport-compliance-tests-v3.0.3](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-transport-compliance-tests-v3.0.2...@libp2p/interface-transport-compliance-tests-v3.0.3) (2022-11-05) + + +### Bug Fixes + +* update project config ([#311](https://github.com/libp2p/js-libp2p-interfaces/issues/311)) ([27dd0ce](https://github.com/libp2p/js-libp2p-interfaces/commit/27dd0ce3c249892ac69cbb24ddaf0b9f32385e37)) + + +### Dependencies + +* update sibling dependencies ([45af2ca](https://github.com/libp2p/js-libp2p-interfaces/commit/45af2cadd55ad58d0c5ee2d11a0b8a39f6300454)) + +## [@libp2p/interface-transport-compliance-tests-v3.0.2](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-transport-compliance-tests-v3.0.1...@libp2p/interface-transport-compliance-tests-v3.0.2) (2022-10-17) + + +### Dependencies + +* bump it-drain from 1.0.5 to 2.0.0 ([#305](https://github.com/libp2p/js-libp2p-interfaces/issues/305)) ([67e418c](https://github.com/libp2p/js-libp2p-interfaces/commit/67e418c2abeccfc06e715c1373485f012df06fdb)) + +## [@libp2p/interface-transport-compliance-tests-v3.0.1](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-transport-compliance-tests-v3.0.0...@libp2p/interface-transport-compliance-tests-v3.0.1) (2022-10-17) + + +### Dependencies + +* bump it-all from 1.0.6 to 2.0.0 ([#306](https://github.com/libp2p/js-libp2p-interfaces/issues/306)) ([7c7b388](https://github.com/libp2p/js-libp2p-interfaces/commit/7c7b3882e33a064b8e5588d6befd823360347464)) + +## [@libp2p/interface-transport-compliance-tests-v3.0.0](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-transport-compliance-tests-v2.0.8...@libp2p/interface-transport-compliance-tests-v3.0.0) (2022-10-12) + + +### ⚠ BREAKING CHANGES + +* modules no longer implement `Initializable` instead switching to constructor injection + +### Bug Fixes + +* remove @libp2p/components ([#301](https://github.com/libp2p/js-libp2p-interfaces/issues/301)) ([1d37dc6](https://github.com/libp2p/js-libp2p-interfaces/commit/1d37dc6d3197838a71895d5769ad8bba6eb38fd3)) + + +### Dependencies + +* update sibling dependencies ([d3226f7](https://github.com/libp2p/js-libp2p-interfaces/commit/d3226f7383de85cae2b4771c22eea22c4bb5bbeb)) + +## [@libp2p/interface-transport-compliance-tests-v2.0.8](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-transport-compliance-tests-v2.0.7...@libp2p/interface-transport-compliance-tests-v2.0.8) (2022-10-06) + + +### Dependencies + +* update sibling dependencies ([2f46d7f](https://github.com/libp2p/js-libp2p-interfaces/commit/2f46d7ff4189c29a63bac93b0b5b73de0a75922f)) +* update sibling dependencies ([0fae3ee](https://github.com/libp2p/js-libp2p-interfaces/commit/0fae3ee43fab43293fb290654a927b5c5c5759fc)) + +## [@libp2p/interface-transport-compliance-tests-v2.0.7](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-transport-compliance-tests-v2.0.6...@libp2p/interface-transport-compliance-tests-v2.0.7) (2022-10-04) + + +### Dependencies + +* update sibling dependencies ([1b11e8e](https://github.com/libp2p/js-libp2p-interfaces/commit/1b11e8e9cc2ea1d4d26233f9c11a57e185ea23ed)) + +## [@libp2p/interface-transport-compliance-tests-v2.0.6](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-transport-compliance-tests-v2.0.5...@libp2p/interface-transport-compliance-tests-v2.0.6) (2022-09-21) + + +### Dependencies + +* update @multiformats/multiaddr to 11.0.0 ([#288](https://github.com/libp2p/js-libp2p-interfaces/issues/288)) ([57b2ad8](https://github.com/libp2p/js-libp2p-interfaces/commit/57b2ad88edfc7807311143791bc49270b1a81eaf)) + +## [@libp2p/interface-transport-compliance-tests-v2.0.5](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-transport-compliance-tests-v2.0.4...@libp2p/interface-transport-compliance-tests-v2.0.5) (2022-08-10) + + +### Bug Fixes + +* compare bytes in transport test ([#281](https://github.com/libp2p/js-libp2p-interfaces/issues/281)) ([b7d9ef6](https://github.com/libp2p/js-libp2p-interfaces/commit/b7d9ef6f5a43b569997bf9e7c908beb2b861d01d)) + +## [@libp2p/interface-transport-compliance-tests-v2.0.4](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-transport-compliance-tests-v2.0.3...@libp2p/interface-transport-compliance-tests-v2.0.4) (2022-08-10) + + +### Dependencies + +* update sibling dependencies ([fc4c49c](https://github.com/libp2p/js-libp2p-interfaces/commit/fc4c49c22334b9f2059b08e13ba94f3e8938482e)) +* update sibling dependencies ([f859920](https://github.com/libp2p/js-libp2p-interfaces/commit/f859920423587ae797ac90ccaa3af8bdf60ae549)) + +## [@libp2p/interface-transport-compliance-tests-v2.0.3](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-transport-compliance-tests-v2.0.2...@libp2p/interface-transport-compliance-tests-v2.0.3) (2022-07-31) + + +### Trivial Changes + +* update project config ([#271](https://github.com/libp2p/js-libp2p-interfaces/issues/271)) ([59c0bf5](https://github.com/libp2p/js-libp2p-interfaces/commit/59c0bf5e0b05496fca2e4902632b61bb41fad9e9)) + + +### Dependencies + +* update uint8arraylist and p-wait-for deps ([#274](https://github.com/libp2p/js-libp2p-interfaces/issues/274)) ([c55f12e](https://github.com/libp2p/js-libp2p-interfaces/commit/c55f12e47be0a10e41709b0d6a60dd8bc1209ee5)) + +## [@libp2p/interface-transport-compliance-tests-v2.0.2](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-transport-compliance-tests-v2.0.1...@libp2p/interface-transport-compliance-tests-v2.0.2) (2022-06-27) + + +### Trivial Changes + +* update deps ([#262](https://github.com/libp2p/js-libp2p-interfaces/issues/262)) ([51edf7d](https://github.com/libp2p/js-libp2p-interfaces/commit/51edf7d9b3765a6f75c915b1483ea345d0133a41)) + +## [@libp2p/interface-transport-compliance-tests-v2.0.1](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-transport-compliance-tests-v2.0.0...@libp2p/interface-transport-compliance-tests-v2.0.1) (2022-06-24) + + +### Trivial Changes + +* update sibling dependencies [skip ci] ([c5c41c5](https://github.com/libp2p/js-libp2p-interfaces/commit/c5c41c521cf970addc1840d8519cdaa542a0db16)) + +## [@libp2p/interface-transport-compliance-tests-v2.0.0](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-transport-compliance-tests-v1.0.0...@libp2p/interface-transport-compliance-tests-v2.0.0) (2022-06-16) + + +### ⚠ BREAKING CHANGES + +* The Connection and Stream APIs have been updated + +### Features + +* store stream data on the stream, track the stream direction ([#245](https://github.com/libp2p/js-libp2p-interfaces/issues/245)) ([6d74d2f](https://github.com/libp2p/js-libp2p-interfaces/commit/6d74d2f9f344fb4d6741ba0d35263ebe351a4c65)) + + +### Trivial Changes + +* update deps ([54fbb37](https://github.com/libp2p/js-libp2p-interfaces/commit/54fbb37c8644a3fd6833c12550a57bf1a9292902)) +* update deps ([219e60e](https://github.com/libp2p/js-libp2p-interfaces/commit/219e60ec6f886b95803457fe48dfcdb4ed57e34c)) +* update deps ([545264f](https://github.com/libp2p/js-libp2p-interfaces/commit/545264f87a58394d2a7da77e93f3a596e889238f)) diff --git a/packages/interface-transport-compliance-tests/LICENSE b/packages/interface-transport-compliance-tests/LICENSE new file mode 100644 index 0000000000..20ce483c86 --- /dev/null +++ b/packages/interface-transport-compliance-tests/LICENSE @@ -0,0 +1,4 @@ +This project is dual licensed under MIT and Apache-2.0. + +MIT: https://www.opensource.org/licenses/mit +Apache-2.0: https://www.apache.org/licenses/license-2.0 diff --git a/packages/interface-transport-compliance-tests/LICENSE-APACHE b/packages/interface-transport-compliance-tests/LICENSE-APACHE new file mode 100644 index 0000000000..14478a3b60 --- /dev/null +++ b/packages/interface-transport-compliance-tests/LICENSE-APACHE @@ -0,0 +1,5 @@ +Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. diff --git a/packages/interface-transport-compliance-tests/LICENSE-MIT b/packages/interface-transport-compliance-tests/LICENSE-MIT new file mode 100644 index 0000000000..72dc60d84b --- /dev/null +++ b/packages/interface-transport-compliance-tests/LICENSE-MIT @@ -0,0 +1,19 @@ +The MIT License (MIT) + +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/packages/interface-transport-compliance-tests/README.md b/packages/interface-transport-compliance-tests/README.md new file mode 100644 index 0000000000..c76d20dc94 --- /dev/null +++ b/packages/interface-transport-compliance-tests/README.md @@ -0,0 +1,55 @@ +# @libp2p/interface-transport-compliance-tests + +[![libp2p.io](https://img.shields.io/badge/project-libp2p-yellow.svg?style=flat-square)](http://libp2p.io/) +[![Discuss](https://img.shields.io/discourse/https/discuss.libp2p.io/posts.svg?style=flat-square)](https://discuss.libp2p.io) +[![codecov](https://img.shields.io/codecov/c/github/libp2p/js-libp2p.svg?style=flat-square)](https://codecov.io/gh/libp2p/js-libp2p) +[![CI](https://img.shields.io/github/actions/workflow/status/libp2p/js-libp2p/js-test-and-release.yml?branch=master\&style=flat-square)](https://github.com/libp2p/js-libp2p/actions/workflows/js-test-and-release.yml?query=branch%3Amaster) + +> Compliance tests for implementations of the libp2p Transport interface + +## Table of contents + +- [Install](#install) +- [Usage](#usage) +- [API Docs](#api-docs) +- [License](#license) +- [Contribution](#contribution) + +## Install + +```console +$ npm i @libp2p/interface-transport-compliance-tests +``` + +## Usage + +```js +import tests from '@libp2p/interface-transport-compliance-tests' + +describe('your transport implementation', () => { + tests({ + // Options should be passed to your implementation + async setup (options) { + return new YourImplementation() + }, + async teardown () { + // cleanup resources created by setup() + } + }) +}) +``` + +## API Docs + +- + +## License + +Licensed under either of + +- Apache 2.0, ([LICENSE-APACHE](LICENSE-APACHE) / ) +- MIT ([LICENSE-MIT](LICENSE-MIT) / ) + +## Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. diff --git a/packages/interface-transport-compliance-tests/package.json b/packages/interface-transport-compliance-tests/package.json new file mode 100644 index 0000000000..30744136dd --- /dev/null +++ b/packages/interface-transport-compliance-tests/package.json @@ -0,0 +1,150 @@ +{ + "name": "@libp2p/interface-transport-compliance-tests", + "version": "4.0.2", + "description": "Compliance tests for implementations of the libp2p Transport interface", + "license": "Apache-2.0 OR MIT", + "homepage": "https://github.com/libp2p/js-libp2p/tree/master/packages/interface-transport-compliance-tests#readme", + "repository": { + "type": "git", + "url": "git+https://github.com/libp2p/js-libp2p.git" + }, + "bugs": { + "url": "https://github.com/libp2p/js-libp2p/issues" + }, + "keywords": [ + "interface", + "libp2p" + ], + "type": "module", + "types": "./dist/src/index.d.ts", + "files": [ + "src", + "dist", + "!dist/test", + "!**/*.tsbuildinfo" + ], + "exports": { + ".": { + "types": "./dist/src/index.d.ts", + "import": "./dist/src/index.js" + } + }, + "eslintConfig": { + "extends": "ipfs", + "parserOptions": { + "sourceType": "module" + } + }, + "release": { + "branches": [ + "master" + ], + "plugins": [ + [ + "@semantic-release/commit-analyzer", + { + "preset": "conventionalcommits", + "releaseRules": [ + { + "breaking": true, + "release": "major" + }, + { + "revert": true, + "release": "patch" + }, + { + "type": "feat", + "release": "minor" + }, + { + "type": "fix", + "release": "patch" + }, + { + "type": "docs", + "release": "patch" + }, + { + "type": "test", + "release": "patch" + }, + { + "type": "deps", + "release": "patch" + }, + { + "scope": "no-release", + "release": false + } + ] + } + ], + [ + "@semantic-release/release-notes-generator", + { + "preset": "conventionalcommits", + "presetConfig": { + "types": [ + { + "type": "feat", + "section": "Features" + }, + { + "type": "fix", + "section": "Bug Fixes" + }, + { + "type": "chore", + "section": "Trivial Changes" + }, + { + "type": "docs", + "section": "Documentation" + }, + { + "type": "deps", + "section": "Dependencies" + }, + { + "type": "test", + "section": "Tests" + } + ] + } + } + ], + "@semantic-release/changelog", + "@semantic-release/npm", + "@semantic-release/github", + "@semantic-release/git" + ] + }, + "scripts": { + "clean": "aegir clean", + "lint": "aegir lint", + "dep-check": "aegir dep-check", + "build": "aegir build", + "release": "aegir release" + }, + "dependencies": { + "@libp2p/interface-compliance-tests": "^3.0.0", + "@libp2p/interface-connection": "^5.0.0", + "@libp2p/interface-mocks": "^12.0.0", + "@libp2p/interface-registrar": "^2.0.0", + "@libp2p/interface-transport": "^4.0.0", + "@libp2p/interfaces": "^3.0.0", + "@multiformats/multiaddr": "^12.0.0", + "aegir": "^39.0.5", + "it-all": "^3.0.1", + "it-drain": "^3.0.1", + "it-pipe": "^3.0.1", + "p-defer": "^4.0.0", + "p-wait-for": "^5.0.0", + "sinon": "^15.0.0", + "uint8arrays": "^4.0.2" + }, + "typedoc": { + "entryPoint": "./src/index.ts" + } +} diff --git a/packages/interface-transport-compliance-tests/src/dial-test.ts b/packages/interface-transport-compliance-tests/src/dial-test.ts new file mode 100644 index 0000000000..855d911f78 --- /dev/null +++ b/packages/interface-transport-compliance-tests/src/dial-test.ts @@ -0,0 +1,124 @@ +import { isValidTick } from '@libp2p/interface-compliance-tests/is-valid-tick' +import { mockUpgrader, mockRegistrar } from '@libp2p/interface-mocks' +import { AbortError } from '@libp2p/interfaces/errors' +import { EventEmitter } from '@libp2p/interfaces/events' +import { expect } from 'aegir/chai' +import all from 'it-all' +import drain from 'it-drain' +import { pipe } from 'it-pipe' +import sinon from 'sinon' +import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' +import type { TransportTestFixtures, Connector } from './index.js' +import type { TestSetup } from '@libp2p/interface-compliance-tests' +import type { Registrar } from '@libp2p/interface-registrar' +import type { Transport, Listener, Upgrader } from '@libp2p/interface-transport' +import type { Multiaddr } from '@multiformats/multiaddr' + +export default (common: TestSetup): void => { + describe('dial', () => { + let upgrader: Upgrader + let registrar: Registrar + let addrs: Multiaddr[] + let transport: Transport + let connector: Connector + let listener: Listener + + before(async () => { + registrar = mockRegistrar() + upgrader = mockUpgrader({ + registrar, + events: new EventEmitter() + }); + + ({ addrs, transport, connector } = await common.setup()) + }) + + after(async () => { + await common.teardown() + }) + + beforeEach(async () => { + listener = transport.createListener({ + upgrader + }) + await listener.listen(addrs[0]) + }) + + afterEach(async () => { + sinon.restore() + connector.restore() + await listener.close() + }) + + it('simple', async () => { + const protocol = '/hello/1.0.0' + void registrar.handle(protocol, (data) => { + void pipe([ + uint8ArrayFromString('hey') + ], + data.stream, + drain + ) + }) + + const upgradeSpy = sinon.spy(upgrader, 'upgradeOutbound') + const conn = await transport.dial(addrs[0], { + upgrader + }) + + const stream = await conn.newStream([protocol]) + const result = await all(stream.source) + + expect(upgradeSpy.callCount).to.equal(1) + await expect(upgradeSpy.getCall(0).returnValue).to.eventually.equal(conn) + expect(result.length).to.equal(1) + expect(result[0].subarray()).to.equalBytes(uint8ArrayFromString('hey')) + await conn.close() + }) + + it('can close connections', async () => { + const upgradeSpy = sinon.spy(upgrader, 'upgradeOutbound') + const conn = await transport.dial(addrs[0], { + upgrader + }) + + expect(upgradeSpy.callCount).to.equal(1) + await expect(upgradeSpy.getCall(0).returnValue).to.eventually.equal(conn) + await conn.close() + expect(isValidTick(conn.stat.timeline.close)).to.equal(true) + }) + + it('to non existent listener', async () => { + const upgradeSpy = sinon.spy(upgrader, 'upgradeOutbound') + + await expect(transport.dial(addrs[1], { + upgrader + })).to.eventually.be.rejected() + expect(upgradeSpy.callCount).to.equal(0) + }) + + it('abort before dialing throws AbortError', async () => { + const upgradeSpy = sinon.spy(upgrader, 'upgradeOutbound') + const controller = new AbortController() + controller.abort() + const conn = transport.dial(addrs[0], { signal: controller.signal, upgrader }) + + await expect(conn).to.eventually.be.rejected().with.property('code', AbortError.code) + expect(upgradeSpy.callCount).to.equal(0) + }) + + it('abort while dialing throws AbortError', async () => { + const upgradeSpy = sinon.spy(upgrader, 'upgradeOutbound') + // Add a delay to connect() so that we can abort while the dial is in + // progress + connector.delay(100) + + const controller = new AbortController() + const conn = transport.dial(addrs[0], { signal: controller.signal, upgrader }) + setTimeout(() => { controller.abort() }, 50) + + await expect(conn).to.eventually.be.rejected().with.property('code', AbortError.code) + expect(upgradeSpy.callCount).to.equal(0) + }) + }) +} diff --git a/packages/interface-transport-compliance-tests/src/filter-test.ts b/packages/interface-transport-compliance-tests/src/filter-test.ts new file mode 100644 index 0000000000..713f86f00d --- /dev/null +++ b/packages/interface-transport-compliance-tests/src/filter-test.ts @@ -0,0 +1,25 @@ +import { expect } from 'aegir/chai' +import type { TransportTestFixtures } from './index.js' +import type { TestSetup } from '@libp2p/interface-compliance-tests' +import type { Transport } from '@libp2p/interface-transport' +import type { Multiaddr } from '@multiformats/multiaddr' + +export default (common: TestSetup): void => { + describe('filter', () => { + let addrs: Multiaddr[] + let transport: Transport + + before(async () => { + ({ addrs, transport } = await common.setup()) + }) + + after(async () => { + await common.teardown() + }) + + it('filters addresses', () => { + const filteredAddrs = transport.filter(addrs) + expect(filteredAddrs).to.eql(addrs) + }) + }) +} diff --git a/packages/interface-transport-compliance-tests/src/index.ts b/packages/interface-transport-compliance-tests/src/index.ts new file mode 100644 index 0000000000..59ff8b02c1 --- /dev/null +++ b/packages/interface-transport-compliance-tests/src/index.ts @@ -0,0 +1,25 @@ +import dial from './dial-test.js' +import filter from './filter-test.js' +import listen from './listen-test.js' +import type { TestSetup } from '@libp2p/interface-compliance-tests' +import type { Transport } from '@libp2p/interface-transport' +import type { Multiaddr } from '@multiformats/multiaddr' + +export interface Connector { + delay: (ms: number) => void + restore: () => void +} + +export interface TransportTestFixtures { + addrs: Multiaddr[] + transport: Transport + connector: Connector +} + +export default (common: TestSetup): void => { + describe('interface-transport', () => { + dial(common) + listen(common) + filter(common) + }) +} diff --git a/packages/interface-transport-compliance-tests/src/listen-test.ts b/packages/interface-transport-compliance-tests/src/listen-test.ts new file mode 100644 index 0000000000..d507e2ca0d --- /dev/null +++ b/packages/interface-transport-compliance-tests/src/listen-test.ts @@ -0,0 +1,191 @@ +/* eslint max-nested-callbacks: ["error", 8] */ +import { isValidTick } from '@libp2p/interface-compliance-tests/is-valid-tick' +import { mockUpgrader, mockRegistrar } from '@libp2p/interface-mocks' +import { CustomEvent, EventEmitter } from '@libp2p/interfaces/events' +import { expect } from 'aegir/chai' +import drain from 'it-drain' +import { pipe } from 'it-pipe' +import defer from 'p-defer' +import pWaitFor from 'p-wait-for' +import sinon from 'sinon' +import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' +import type { TransportTestFixtures } from './index.js' +import type { TestSetup } from '@libp2p/interface-compliance-tests' +import type { Connection } from '@libp2p/interface-connection' +import type { Registrar } from '@libp2p/interface-registrar' +import type { Transport, Upgrader } from '@libp2p/interface-transport' +import type { Multiaddr } from '@multiformats/multiaddr' + +export default (common: TestSetup): void => { + describe('listen', () => { + let upgrader: Upgrader + let addrs: Multiaddr[] + let transport: Transport + let registrar: Registrar + + before(async () => { + registrar = mockRegistrar() + upgrader = mockUpgrader({ + registrar, + events: new EventEmitter() + }); + + ({ transport, addrs } = await common.setup()) + }) + + after(async () => { + await common.teardown() + }) + + afterEach(() => { + sinon.restore() + }) + + it('simple', async () => { + const listener = transport.createListener({ + upgrader + }) + await listener.listen(addrs[0]) + await listener.close() + }) + + it('close listener with connections, through timeout', async () => { + const upgradeSpy = sinon.spy(upgrader, 'upgradeInbound') + const listenerConns: Connection[] = [] + + const protocol = '/test/protocol' + void registrar.handle(protocol, (data) => { + void drain(data.stream.source) + }) + + const listener = transport.createListener({ + upgrader, + handler: (conn) => { + listenerConns.push(conn) + } + }) + + // Listen + await listener.listen(addrs[0]) + + // Create two connections to the listener + const [conn1] = await Promise.all([ + transport.dial(addrs[0], { + upgrader + }), + transport.dial(addrs[0], { + upgrader + }) + ]) + + // Give the listener a chance to finish its upgrade + await pWaitFor(() => listenerConns.length === 2) + + const stream1 = await conn1.newStream([protocol]) + + // Wait for the data send and close to finish + await Promise.all([ + pipe( + [uint8ArrayFromString('Some data that is never handled')], + stream1 + ), + // Closer the listener (will take a couple of seconds to time out) + listener.close() + ]) + + stream1.close() + await conn1.close() + + expect(isValidTick(conn1.stat.timeline.close)).to.equal(true) + listenerConns.forEach(conn => { + expect(isValidTick(conn.stat.timeline.close)).to.equal(true) + }) + + // 2 dials = 2 connections upgraded + expect(upgradeSpy.callCount).to.equal(2) + }) + + it('should not handle connection if upgradeInbound throws', async () => { + sinon.stub(upgrader, 'upgradeInbound').throws() + + const listener = transport.createListener({ + upgrader + }) + + // Listen + await listener.listen(addrs[0]) + + // Create a connection to the listener + const conn = await transport.dial(addrs[0], { + upgrader + }) + + await pWaitFor(() => typeof conn.stat.timeline.close === 'number') + await listener.close() + }) + + describe('events', () => { + it('connection', async () => { + const upgradeSpy = sinon.spy(upgrader, 'upgradeInbound') + const listener = transport.createListener({ + upgrader + }) + const deferred = defer() + let conn + + listener.addEventListener('connection', (evt) => { + conn = evt.detail + deferred.resolve() + }) + + void (async () => { + await listener.listen(addrs[0]) + await transport.dial(addrs[0], { + upgrader + }) + })() + + await deferred.promise + + await expect(upgradeSpy.getCall(0).returnValue).to.eventually.equal(conn) + expect(upgradeSpy.callCount).to.equal(1) + await listener.close() + }) + + it('listening', (done) => { + const listener = transport.createListener({ + upgrader + }) + listener.addEventListener('listening', () => { + listener.close().then(done, done) + }) + void listener.listen(addrs[0]) + }) + + it('error', (done) => { + const listener = transport.createListener({ + upgrader + }) + listener.addEventListener('error', (evt) => { + expect(evt.detail).to.be.an.instanceOf(Error) + listener.close().then(done, done) + }) + listener.dispatchEvent(new CustomEvent('error', { + detail: new Error('my err') + })) + }) + + it('close', (done) => { + const listener = transport.createListener({ + upgrader + }) + listener.addEventListener('close', () => { done() }) + + void (async () => { + await listener.listen(addrs[0]) + await listener.close() + })() + }) + }) + }) +} diff --git a/packages/interface-transport-compliance-tests/tsconfig.json b/packages/interface-transport-compliance-tests/tsconfig.json new file mode 100644 index 0000000000..0ac9c08543 --- /dev/null +++ b/packages/interface-transport-compliance-tests/tsconfig.json @@ -0,0 +1,30 @@ +{ + "extends": "aegir/src/config/tsconfig.aegir.json", + "compilerOptions": { + "outDir": "dist" + }, + "include": [ + "src", + "test" + ], + "references": [ + { + "path": "../interface-compliance-tests" + }, + { + "path": "../interface-connection" + }, + { + "path": "../interface-mocks" + }, + { + "path": "../interface-registrar" + }, + { + "path": "../interface-transport" + }, + { + "path": "../interfaces" + } + ] +} diff --git a/packages/interface-transport/CHANGELOG.md b/packages/interface-transport/CHANGELOG.md new file mode 100644 index 0000000000..2b1562a774 --- /dev/null +++ b/packages/interface-transport/CHANGELOG.md @@ -0,0 +1,150 @@ +## [@libp2p/interface-transport-v4.0.3](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-transport-v4.0.2...@libp2p/interface-transport-v4.0.3) (2023-05-15) + + +### Bug Fixes + +* expose getListeners method from the transport manager ([#400](https://github.com/libp2p/js-libp2p-interfaces/issues/400)) ([94dc4ed](https://github.com/libp2p/js-libp2p-interfaces/commit/94dc4ed0320473b160618d19dddc6e038f5050e6)) + +## [@libp2p/interface-transport-v4.0.2](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-transport-v4.0.1...@libp2p/interface-transport-v4.0.2) (2023-05-04) + + +### Dependencies + +* bump aegir from 38.1.8 to 39.0.5 ([#393](https://github.com/libp2p/js-libp2p-interfaces/issues/393)) ([31f3797](https://github.com/libp2p/js-libp2p-interfaces/commit/31f3797b24f7c23f3f16e9db3a230bd5f7cd5175)) + +## [@libp2p/interface-transport-v4.0.1](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-transport-v4.0.0...@libp2p/interface-transport-v4.0.1) (2023-04-25) + + +### Documentation + +* fix typos in docs ([#386](https://github.com/libp2p/js-libp2p-interfaces/issues/386)) ([8ec2cdc](https://github.com/libp2p/js-libp2p-interfaces/commit/8ec2cdcc5deed76e0c673a75c27bf7a2e931eea1)) + +## [@libp2p/interface-transport-v4.0.0](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-transport-v3.0.0...@libp2p/interface-transport-v4.0.0) (2023-04-21) + + +### ⚠ BREAKING CHANGES + +* add libp2p events (#373) + +### Features + +* add libp2p events ([#373](https://github.com/libp2p/js-libp2p-interfaces/issues/373)) ([071c718](https://github.com/libp2p/js-libp2p-interfaces/commit/071c718808902858818ca86167b51b242b67a5a5)) + +## [@libp2p/interface-transport-v3.0.0](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-transport-v2.1.3...@libp2p/interface-transport-v3.0.0) (2023-04-18) + + +### ⚠ BREAKING CHANGES + +* bump it-stream-types from 1.0.5 to 2.0.1 (#362) + +### Dependencies + +* bump it-stream-types from 1.0.5 to 2.0.1 ([#362](https://github.com/libp2p/js-libp2p-interfaces/issues/362)) ([cdc7747](https://github.com/libp2p/js-libp2p-interfaces/commit/cdc774792beead63e0ded96bd6c23de0335a49e3)) +* update sibling dependencies ([e95dcc2](https://github.com/libp2p/js-libp2p-interfaces/commit/e95dcc28f0a8b42457a44155eb0dfb3d813b03c8)) +* update sibling dependencies ([2f52a28](https://github.com/libp2p/js-libp2p-interfaces/commit/2f52a284b59c0a88b040f86da1f5d3f044727f2c)) + +## [@libp2p/interface-transport-v2.1.3](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-transport-v2.1.2...@libp2p/interface-transport-v2.1.3) (2023-04-11) + + +### Dependencies + +* update sibling dependencies ([b034810](https://github.com/libp2p/js-libp2p-interfaces/commit/b0348102e41dc18166e70063f4708a2b3544f4b6)) + +## [@libp2p/interface-transport-v2.1.2](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-transport-v2.1.1...@libp2p/interface-transport-v2.1.2) (2023-03-17) + + +### Dependencies + +* update @multiformats/multiaddr to 12.0.0 ([#354](https://github.com/libp2p/js-libp2p-interfaces/issues/354)) ([e0f327b](https://github.com/libp2p/js-libp2p-interfaces/commit/e0f327b5d54e240feabadce21a841629d633ec5e)) + +## [@libp2p/interface-transport-v2.1.1](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-transport-v2.1.0...@libp2p/interface-transport-v2.1.1) (2023-01-18) + + +### Trivial Changes + +* remove lerna ([#330](https://github.com/libp2p/js-libp2p-interfaces/issues/330)) ([6678592](https://github.com/libp2p/js-libp2p-interfaces/commit/6678592dd0cf601a2671852f9d2a0aff5dee2b18)) + + +### Dependencies + +* bump aegir from 37.12.1 to 38.1.0 ([#335](https://github.com/libp2p/js-libp2p-interfaces/issues/335)) ([7368a36](https://github.com/libp2p/js-libp2p-interfaces/commit/7368a363423a08e8fa247dcb76ea13e4cf030d65)) + +## [@libp2p/interface-transport-v2.1.0](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-transport-v2.0.3...@libp2p/interface-transport-v2.1.0) (2022-12-21) + + +### Features + +* add fault tolerance enum ([#327](https://github.com/libp2p/js-libp2p-interfaces/issues/327)) ([85f8d60](https://github.com/libp2p/js-libp2p-interfaces/commit/85f8d60b147e1f0af61179137fbcdab500933e70)) + +## [@libp2p/interface-transport-v2.0.3](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-transport-v2.0.2...@libp2p/interface-transport-v2.0.3) (2022-12-16) + + +### Documentation + +* update project config ([#323](https://github.com/libp2p/js-libp2p-interfaces/issues/323)) ([0fc6a08](https://github.com/libp2p/js-libp2p-interfaces/commit/0fc6a08e9cdcefe361fe325281a3a2a03759ff59)) + +## [@libp2p/interface-transport-v2.0.2](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-transport-v2.0.1...@libp2p/interface-transport-v2.0.2) (2022-12-14) + + +### Bug Fixes + +* generate docs for all packages ([#321](https://github.com/libp2p/js-libp2p-interfaces/issues/321)) ([b6f8b32](https://github.com/libp2p/js-libp2p-interfaces/commit/b6f8b32a920c15a28fe021e6050e31aaae89d518)) + +## [@libp2p/interface-transport-v2.0.1](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-transport-v2.0.0...@libp2p/interface-transport-v2.0.1) (2022-11-05) + + +### Bug Fixes + +* update project config ([#311](https://github.com/libp2p/js-libp2p-interfaces/issues/311)) ([27dd0ce](https://github.com/libp2p/js-libp2p-interfaces/commit/27dd0ce3c249892ac69cbb24ddaf0b9f32385e37)) + +## [@libp2p/interface-transport-v2.0.0](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-transport-v1.0.4...@libp2p/interface-transport-v2.0.0) (2022-10-06) + + +### ⚠ BREAKING CHANGES + +* the return type of StreamMuxer.newStream can now return a promise + +Co-authored-by: Marco Munizaga + +### Features + +* add upgrader options ([#290](https://github.com/libp2p/js-libp2p-interfaces/issues/290)) ([c502b66](https://github.com/libp2p/js-libp2p-interfaces/commit/c502b66d87020eb8e2768c49be17392c55503f69)) + + +### Dependencies + +* update sibling dependencies ([66b4993](https://github.com/libp2p/js-libp2p-interfaces/commit/66b49938a09eeb12bf8ec8d78938d5cffd6ec134)) +* update sibling dependencies ([5de9728](https://github.com/libp2p/js-libp2p-interfaces/commit/5de97284827de6c63182b704c1be12c5f8cf7af5)) + +## [@libp2p/interface-transport-v1.0.4](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-transport-v1.0.3...@libp2p/interface-transport-v1.0.4) (2022-09-21) + + +### Dependencies + +* update @multiformats/multiaddr to 11.0.0 ([#288](https://github.com/libp2p/js-libp2p-interfaces/issues/288)) ([57b2ad8](https://github.com/libp2p/js-libp2p-interfaces/commit/57b2ad88edfc7807311143791bc49270b1a81eaf)) + +## [@libp2p/interface-transport-v1.0.3](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-transport-v1.0.2...@libp2p/interface-transport-v1.0.3) (2022-08-07) + + +### Trivial Changes + +* update project config ([#271](https://github.com/libp2p/js-libp2p-interfaces/issues/271)) ([59c0bf5](https://github.com/libp2p/js-libp2p-interfaces/commit/59c0bf5e0b05496fca2e4902632b61bb41fad9e9)) + + +### Dependencies + +* update sibling dependencies ([f859920](https://github.com/libp2p/js-libp2p-interfaces/commit/f859920423587ae797ac90ccaa3af8bdf60ae549)) + +## [@libp2p/interface-transport-v1.0.2](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-transport-v1.0.1...@libp2p/interface-transport-v1.0.2) (2022-06-27) + + +### Trivial Changes + +* update deps ([#262](https://github.com/libp2p/js-libp2p-interfaces/issues/262)) ([51edf7d](https://github.com/libp2p/js-libp2p-interfaces/commit/51edf7d9b3765a6f75c915b1483ea345d0133a41)) + +## [@libp2p/interface-transport-v1.0.1](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-transport-v1.0.0...@libp2p/interface-transport-v1.0.1) (2022-06-16) + + +### Trivial Changes + +* update deps ([545264f](https://github.com/libp2p/js-libp2p-interfaces/commit/545264f87a58394d2a7da77e93f3a596e889238f)) diff --git a/packages/interface-transport/LICENSE b/packages/interface-transport/LICENSE new file mode 100644 index 0000000000..20ce483c86 --- /dev/null +++ b/packages/interface-transport/LICENSE @@ -0,0 +1,4 @@ +This project is dual licensed under MIT and Apache-2.0. + +MIT: https://www.opensource.org/licenses/mit +Apache-2.0: https://www.apache.org/licenses/license-2.0 diff --git a/packages/interface-transport/LICENSE-APACHE b/packages/interface-transport/LICENSE-APACHE new file mode 100644 index 0000000000..14478a3b60 --- /dev/null +++ b/packages/interface-transport/LICENSE-APACHE @@ -0,0 +1,5 @@ +Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. diff --git a/packages/interface-transport/LICENSE-MIT b/packages/interface-transport/LICENSE-MIT new file mode 100644 index 0000000000..72dc60d84b --- /dev/null +++ b/packages/interface-transport/LICENSE-MIT @@ -0,0 +1,19 @@ +The MIT License (MIT) + +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/packages/interface-transport/README.md b/packages/interface-transport/README.md new file mode 100644 index 0000000000..9d8faa0b47 --- /dev/null +++ b/packages/interface-transport/README.md @@ -0,0 +1,264 @@ +# @libp2p/interface-transport + +[![libp2p.io](https://img.shields.io/badge/project-libp2p-yellow.svg?style=flat-square)](http://libp2p.io/) +[![Discuss](https://img.shields.io/discourse/https/discuss.libp2p.io/posts.svg?style=flat-square)](https://discuss.libp2p.io) +[![codecov](https://img.shields.io/codecov/c/github/libp2p/js-libp2p.svg?style=flat-square)](https://codecov.io/gh/libp2p/js-libp2p) +[![CI](https://img.shields.io/github/actions/workflow/status/libp2p/js-libp2p/js-test-and-release.yml?branch=master\&style=flat-square)](https://github.com/libp2p/js-libp2p/actions/workflows/js-test-and-release.yml?query=branch%3Amaster) + +> Transport interface for libp2p + +## Table of contents + +- [Install](#install) +- [Modules that implement the interface](#modules-that-implement-the-interface) +- [Badge](#badge) +- [How to use the battery of tests](#how-to-use-the-battery-of-tests) +- [Node.js](#nodejs) +- [API](#api) + - [Types](#types) + - [Upgrader](#upgrader) + - [MultiaddrConnection](#multiaddrconnection) + - [Creating a transport instance](#creating-a-transport-instance) + - [Dial to another peer](#dial-to-another-peer) + - [Canceling a dial](#canceling-a-dial) + - [Filtering Addresses](#filtering-addresses) + - [Create a listener](#create-a-listener) + - [Start a listener](#start-a-listener) + - [Get listener addrs](#get-listener-addrs) + - [Stop a listener](#stop-a-listener) +- [API Docs](#api-docs) +- [License](#license) +- [Contribution](#contribution) + +## Install + +```console +$ npm i @libp2p/interface-transport +``` + +The primary goal of this module is to enable developers to pick and swap their transport module as they see fit for their libp2p installation, without having to go through shims or compatibility issues. This module and test suite were heavily inspired by abstract-blob-store, interface-stream-muxer and others. + +Publishing a test suite as a module lets multiple modules all ensure compatibility since they use the same test suite. + +The purpose of this interface is not to reinvent any wheels when it comes to dialing and listening to transports. Instead, it tries to provide a uniform API for several transports through a shimmed interface. + +## Modules that implement the interface + +- [js-libp2p-tcp](https://github.com/libp2p/js-libp2p-tcp) +- [js-libp2p-webrtc-star](https://github.com/libp2p/js-libp2p-webrtc-star) +- [js-libp2p-webrtc-direct](https://github.com/libp2p/js-libp2p-webrtc-direct) +- [js-libp2p-websocket-star](https://github.com/libp2p/js-libp2p-websocket-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) + +## Badge + +Include this badge in your readme if you make a module that is compatible with the interface-transport API. You can validate this by running the tests. + +![](img/badge.png) + +## How to use the battery of tests + +## Node.js + +```js +/* eslint-env mocha */ +'use strict' + +const tests = require('libp2p-interfaces-compliance-tests/transport') +const multiaddr = require('@multiformats/multiaddr') +const YourTransport = require('../src') + +describe('compliance', () => { + tests({ + setup (init) { + let transport = new YourTransport(init) + + const addrs = [ + multiaddr('valid-multiaddr-for-your-transport'), + multiaddr('valid-multiaddr2-for-your-transport') + ] + + const network = require('my-network-lib') + const connect = network.connect + const connector = { + delay (delayMs) { + // Add a delay in the connection mechanism for the transport + // (this is used by the dial tests) + network.connect = (...args) => setTimeout(() => connect(...args), delayMs) + }, + restore () { + // Restore the connection mechanism to normal + network.connect = connect + } + } + + return { transport, addrs, connector } + }, + teardown () { + // Clean up any resources created by setup() + } + }) +}) +``` + +## API + +A valid transport (one that follows the interface defined) must implement the following API: + +**Table of contents:** + +- type: `Transport` + - `new Transport({ upgrader, ...[options] })` + - ` transport.dial(multiaddr, [options])` + - ` transport.filter(multiaddrs)` + - `transport.createListener([options], handlerFunction)` + - type: `transport.Listener` + - event: 'listening' + - event: 'close' + - event: 'connection' + - event: 'error' + - ` listener.listen(multiaddr)` + - `listener.getAddrs()` + - ` listener.close([options])` + +### Types + +#### Upgrader + +Upgraders have 2 methods: `upgradeOutbound` and `upgradeInbound`. + +- `upgradeOutbound` must be called and returned by `transport.dial`. +- `upgradeInbound` must be called and the results must be passed to the `createListener` `handlerFunction` and the `connection` event handler, any time a new connection is created. + +```js +const connection = await upgrader.upgradeOutbound(multiaddrConnection) +const connection = await upgrader.upgradeInbound(multiaddrConnection) +``` + +The `Upgrader` methods take a [MultiaddrConnection](#multiaddrconnection) and will return an `interface-connection` instance. + +#### MultiaddrConnection + +- `MultiaddrConnection` + - `sink`: A [streaming iterable sink](https://gist.github.com/alanshaw/591dc7dd54e4f99338a347ef568d6ee9#sink-it) + - `source`: A [streaming iterable source](https://gist.github.com/alanshaw/591dc7dd54e4f99338a347ef568d6ee9#source-it) + - `close`: A method for closing the connection + - `conn`: The raw connection of the transport, such as a TCP socket. + - `remoteAddr`: The remote `Multiaddr` of the connection. + - `[localAddr]`: An optional local `Multiaddr` of the connection. + - `timeline`: A hash map of connection time events + - `open`: The time in ticks the connection was opened + - `close`: The time in ticks the connection was closed + +### Creating a transport instance + +- `const transport = new Transport({ upgrader, ...[options] })` + +Creates a new Transport instance. `options` is an JavaScript object that should include the necessary parameters for the transport instance. Options **MUST** include an `Upgrader` instance, as Transports will use this to return `interface-connection` instances from `transport.dial` and the listener `handlerFunction`. + +**Note: Why is it important to instantiate a transport -** Some transports have state that can be shared between the dialing and listening parts. For example with libp2p-webrtc-star, in order to dial a peer, the peer must be part of some signaling network that is shared with the listener. + +### Dial to another peer + +- `const connection = await transport.dial(multiaddr, [options])` + +This method uses a transport to dial a Peer listening on `multiaddr`. + +`multiaddr` must be of the type [`multiaddr`](https://www.npmjs.com/multiaddr). + +`[options]` the options that may be passed to the dial. Must support the `signal` option (see below) + +Dial **MUST** call and return `upgrader.upgradeOutbound(multiaddrConnection)`. The upgrader will return an [interface-connection](../connection) instance. + +The dial may throw an `Error` instance if there was a problem connecting to the `multiaddr`. + +### Canceling a dial + +Dials may be cancelled using an `AbortController`: + +```Javascript +const { AbortError } = require('libp2p-interfaces/src/transport/errors') +const controller = new AbortController() +try { + const conn = await mytransport.dial(ma, { signal: controller.signal }) + // Do stuff with conn here ... +} catch (err: any) { + if(err.code === AbortError.code) { + // Dial was aborted, just bail out + return + } + throw err +} + +// ---- +// In some other part of the code: + controller.abort() +// ---- +``` + +### Filtering Addresses + +- `const supportedAddrs = await transport.filter(multiaddrs)` + +When using a transport its important to be able to filter out `multiaddr`s that the transport doesn't support. A transport instance provides a filter method to return only the valid addresses it supports. + +`multiaddrs` must be an array of type [`multiaddr`](https://www.npmjs.com/multiaddr). +Filter returns an array of `multiaddr`. + +### Create a listener + +- `const listener = transport.createListener([options], handlerFunction)` + +This method creates a listener on the transport. Implementations **MUST** call `upgrader.upgradeInbound(multiaddrConnection)` and pass its results to the `handlerFunction` and any emitted `connection` events. + +`options` is an optional object that contains the properties the listener must have, in order to properly listen on a given transport/socket. + +`handlerFunction` is a function called each time a new connection is received. It must follow the following signature: `function (conn) {}`, where `conn` is a connection that follows the [`interface-connection`](../connection). + +The listener object created may emit the following events: + +- `listening` - when the listener is ready for incoming connections +- `close` - when the listener is closed +- `connection` - (`conn`) each time an incoming connection is received +- `error` - (`err`) each time there is an error on the connection + +### Start a listener + +- `await listener.listen(multiaddr)` + +This method puts the listener in `listening` mode, waiting for incoming connections. + +`multiaddr` is the address that the listener should bind to. + +### Get listener addrs + +- `listener.getAddrs()` + +This method returns the addresses on which this listener is listening. Useful when listening on port 0 or any interface (0.0.0.0). + +### Stop a listener + +- `await listener.close([options])` + +This method closes the listener so that no more connections can be opened on this transport instance. + +`options` is an optional object that may contain the following properties: + +- `timeout` - A timeout value (in ms) after which all connections on this transport will be destroyed if the transport is not able to close gracefully. (e.g `{ timeout: 1000 }`) + +## API Docs + +- + +## License + +Licensed under either of + +- Apache 2.0, ([LICENSE-APACHE](LICENSE-APACHE) / ) +- MIT ([LICENSE-MIT](LICENSE-MIT) / ) + +## Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. diff --git a/packages/interface-transport/img/badge.png b/packages/interface-transport/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/packages/interface-transport/img/badge.svg b/packages/interface-transport/img/badge.svg new file mode 100644 index 0000000000..aa56dacd5e --- /dev/null +++ b/packages/interface-transport/img/badge.svg @@ -0,0 +1,19 @@ + + + + badge + Created with Sketch. + + + + + + Transpor + t + + + Compatibl + e + + + \ No newline at end of file diff --git a/packages/interface-transport/package.json b/packages/interface-transport/package.json new file mode 100644 index 0000000000..235a2154b6 --- /dev/null +++ b/packages/interface-transport/package.json @@ -0,0 +1,143 @@ +{ + "name": "@libp2p/interface-transport", + "version": "4.0.3", + "description": "Transport interface for libp2p", + "license": "Apache-2.0 OR MIT", + "homepage": "https://github.com/libp2p/js-libp2p/tree/master/packages/interface-transport#readme", + "repository": { + "type": "git", + "url": "git+https://github.com/libp2p/js-libp2p.git" + }, + "bugs": { + "url": "https://github.com/libp2p/js-libp2p/issues" + }, + "keywords": [ + "interface", + "libp2p" + ], + "type": "module", + "types": "./dist/src/index.d.ts", + "files": [ + "src", + "dist", + "!dist/test", + "!**/*.tsbuildinfo" + ], + "exports": { + ".": { + "types": "./dist/src/index.d.ts", + "import": "./dist/src/index.js" + } + }, + "eslintConfig": { + "extends": "ipfs", + "parserOptions": { + "sourceType": "module" + } + }, + "release": { + "branches": [ + "master" + ], + "plugins": [ + [ + "@semantic-release/commit-analyzer", + { + "preset": "conventionalcommits", + "releaseRules": [ + { + "breaking": true, + "release": "major" + }, + { + "revert": true, + "release": "patch" + }, + { + "type": "feat", + "release": "minor" + }, + { + "type": "fix", + "release": "patch" + }, + { + "type": "docs", + "release": "patch" + }, + { + "type": "test", + "release": "patch" + }, + { + "type": "deps", + "release": "patch" + }, + { + "scope": "no-release", + "release": false + } + ] + } + ], + [ + "@semantic-release/release-notes-generator", + { + "preset": "conventionalcommits", + "presetConfig": { + "types": [ + { + "type": "feat", + "section": "Features" + }, + { + "type": "fix", + "section": "Bug Fixes" + }, + { + "type": "chore", + "section": "Trivial Changes" + }, + { + "type": "docs", + "section": "Documentation" + }, + { + "type": "deps", + "section": "Dependencies" + }, + { + "type": "test", + "section": "Tests" + } + ] + } + } + ], + "@semantic-release/changelog", + "@semantic-release/npm", + "@semantic-release/github", + "@semantic-release/git" + ] + }, + "scripts": { + "clean": "aegir clean", + "lint": "aegir lint", + "dep-check": "aegir dep-check", + "build": "aegir build", + "release": "aegir release" + }, + "dependencies": { + "@libp2p/interface-connection": "^5.0.0", + "@libp2p/interface-stream-muxer": "^4.0.0", + "@libp2p/interfaces": "^3.0.0", + "@multiformats/multiaddr": "^12.0.0", + "it-stream-types": "^2.0.1" + }, + "devDependencies": { + "aegir": "^39.0.5" + }, + "typedoc": { + "entryPoint": "./src/index.ts" + } +} diff --git a/packages/interface-transport/src/index.ts b/packages/interface-transport/src/index.ts new file mode 100644 index 0000000000..4b14bd2d3e --- /dev/null +++ b/packages/interface-transport/src/index.ts @@ -0,0 +1,128 @@ +import type { Connection, MultiaddrConnection } from '@libp2p/interface-connection' +import type { StreamMuxerFactory } from '@libp2p/interface-stream-muxer' +import type { AbortOptions } from '@libp2p/interfaces' +import type { EventEmitter } from '@libp2p/interfaces/events' +import type { Multiaddr } from '@multiformats/multiaddr' +import type { Duplex } from 'it-stream-types' + +export const symbol = Symbol.for('@libp2p/transport') + +export interface ConnectionHandler { (connection: Connection): void } + +export interface MultiaddrFilter { (multiaddrs: Multiaddr[]): Multiaddr[] } + +export interface CreateListenerOptions { + handler?: ConnectionHandler + upgrader: Upgrader +} + +export interface DialOptions extends AbortOptions { + upgrader: Upgrader +} + +/** + * A libp2p transport is understood as something that offers a dial and listen interface to establish connections. + */ +export interface Transport { + /** + * Used to identify the transport + */ + [Symbol.toStringTag]: string + + /** + * Used by the isTransport function + */ + [symbol]: true + + /** + * Dial a given multiaddr. + */ + dial: (ma: Multiaddr, options: DialOptions) => Promise + + /** + * Create transport listeners. + */ + createListener: (options: CreateListenerOptions) => Listener + + /** + * Takes a list of `Multiaddr`s and returns only valid addresses for the transport + */ + filter: MultiaddrFilter +} + +export interface ListenerEvents { + 'connection': CustomEvent + 'listening': CustomEvent + 'error': CustomEvent + 'close': CustomEvent +} + +export interface Listener extends EventEmitter { + /** + * Start a listener + */ + listen: (multiaddr: Multiaddr) => Promise + /** + * Get listen addresses + */ + getAddrs: () => Multiaddr[] + /** + * Close listener + * + * @returns {Promise} + */ + close: () => Promise +} + +export interface UpgraderOptions { + skipEncryption?: boolean + skipProtection?: boolean + muxerFactory?: StreamMuxerFactory +} + +export interface Upgrader { + /** + * Upgrades an outbound connection on `transport.dial`. + */ + upgradeOutbound: (maConn: MultiaddrConnection, opts?: UpgraderOptions) => Promise + + /** + * Upgrades an inbound connection on transport listener. + */ + upgradeInbound: (maConn: MultiaddrConnection, opts?: UpgraderOptions) => Promise +} + +export interface ProtocolHandler { + (stream: Duplex, connection: Connection): void +} + +export function isTransport (other: any): other is Transport { + return other != null && Boolean(other[symbol]) +} + +export interface TransportManager { + add: (transport: Transport) => void + dial: (ma: Multiaddr, options?: any) => Promise + getAddrs: () => Multiaddr[] + getTransports: () => Transport[] + getListeners: () => Listener[] + transportForMultiaddr: (ma: Multiaddr) => Transport | undefined + listen: (addrs: Multiaddr[]) => Promise + remove: (key: string) => Promise + removeAll: () => Promise +} + +/** + * Enum Transport Manager Fault Tolerance values + */ +export enum FaultTolerance { + /** + * should be used for failing in any listen circumstance + */ + FATAL_ALL = 0, + + /** + * should be used for not failing when not listening + */ + NO_FATAL +} diff --git a/packages/interface-transport/tsconfig.json b/packages/interface-transport/tsconfig.json new file mode 100644 index 0000000000..28ed110a13 --- /dev/null +++ b/packages/interface-transport/tsconfig.json @@ -0,0 +1,21 @@ +{ + "extends": "aegir/src/config/tsconfig.aegir.json", + "compilerOptions": { + "outDir": "dist" + }, + "include": [ + "src", + "test" + ], + "references": [ + { + "path": "../interface-connection" + }, + { + "path": "../interface-stream-muxer" + }, + { + "path": "../interfaces" + } + ] +} diff --git a/packages/interfaces/CHANGELOG.md b/packages/interfaces/CHANGELOG.md new file mode 100644 index 0000000000..c9960901e2 --- /dev/null +++ b/packages/interfaces/CHANGELOG.md @@ -0,0 +1,1120 @@ +## [@libp2p/interfaces-v3.3.2](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interfaces-v3.3.1...@libp2p/interfaces-v3.3.2) (2023-05-04) + + +### Dependencies + +* bump aegir from 38.1.8 to 39.0.5 ([#393](https://github.com/libp2p/js-libp2p-interfaces/issues/393)) ([31f3797](https://github.com/libp2p/js-libp2p-interfaces/commit/31f3797b24f7c23f3f16e9db3a230bd5f7cd5175)) + +## [@libp2p/interfaces-v3.3.1](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interfaces-v3.3.0...@libp2p/interfaces-v3.3.1) (2023-01-18) + + +### Dependencies + +* bump aegir from 37.12.1 to 38.1.0 ([#335](https://github.com/libp2p/js-libp2p-interfaces/issues/335)) ([7368a36](https://github.com/libp2p/js-libp2p-interfaces/commit/7368a363423a08e8fa247dcb76ea13e4cf030d65)) + +## [@libp2p/interfaces-v3.3.0](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interfaces-v3.2.0...@libp2p/interfaces-v3.3.0) (2023-01-17) + + +### Features + +* safe dispatch event ([#319](https://github.com/libp2p/js-libp2p-interfaces/issues/319)) ([8caeee8](https://github.com/libp2p/js-libp2p-interfaces/commit/8caeee8221e78c2412d8aeb9a7db7cc43abfdf1b)), closes [#317](https://github.com/libp2p/js-libp2p-interfaces/issues/317) + + +### Trivial Changes + +* remove lerna ([#330](https://github.com/libp2p/js-libp2p-interfaces/issues/330)) ([6678592](https://github.com/libp2p/js-libp2p-interfaces/commit/6678592dd0cf601a2671852f9d2a0aff5dee2b18)) + +## [@libp2p/interfaces-v3.2.0](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interfaces-v3.1.0...@libp2p/interfaces-v3.2.0) (2023-01-03) + + +### Features + +* add CodeError ([#314](https://github.com/libp2p/js-libp2p-interfaces/issues/314)) ([59aa8f7](https://github.com/libp2p/js-libp2p-interfaces/commit/59aa8f7fd2a8e80fc28e76fef320c531884e7e00)), closes [/github.com/libp2p/js-libp2p-crypto/pull/284#issuecomment-1324005434](https://github.com/libp2p//github.com/libp2p/js-libp2p-crypto/pull/284/issues/issuecomment-1324005434) + +## [@libp2p/interfaces-v3.1.0](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interfaces-v3.0.6...@libp2p/interfaces-v3.1.0) (2022-12-19) + + +### Features + +* add libp2p interface ([#325](https://github.com/libp2p/js-libp2p-interfaces/issues/325)) ([79a474d](https://github.com/libp2p/js-libp2p-interfaces/commit/79a474d8eda95ad3ff3bcdb2a15bfcf778f51772)) + +## [@libp2p/interfaces-v3.0.6](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interfaces-v3.0.5...@libp2p/interfaces-v3.0.6) (2022-12-16) + + +### Documentation + +* update project config ([#323](https://github.com/libp2p/js-libp2p-interfaces/issues/323)) ([0fc6a08](https://github.com/libp2p/js-libp2p-interfaces/commit/0fc6a08e9cdcefe361fe325281a3a2a03759ff59)) + +## [@libp2p/interfaces-v3.0.5](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interfaces-v3.0.4...@libp2p/interfaces-v3.0.5) (2022-12-14) + + +### Bug Fixes + +* generate docs for all packages ([#321](https://github.com/libp2p/js-libp2p-interfaces/issues/321)) ([b6f8b32](https://github.com/libp2p/js-libp2p-interfaces/commit/b6f8b32a920c15a28fe021e6050e31aaae89d518)) + +## [@libp2p/interfaces-v3.0.4](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interfaces-v3.0.3...@libp2p/interfaces-v3.0.4) (2022-11-05) + + +### Bug Fixes + +* update project config ([#311](https://github.com/libp2p/js-libp2p-interfaces/issues/311)) ([27dd0ce](https://github.com/libp2p/js-libp2p-interfaces/commit/27dd0ce3c249892ac69cbb24ddaf0b9f32385e37)) + + +### Trivial Changes + +* update project config ([#271](https://github.com/libp2p/js-libp2p-interfaces/issues/271)) ([59c0bf5](https://github.com/libp2p/js-libp2p-interfaces/commit/59c0bf5e0b05496fca2e4902632b61bb41fad9e9)) + +## [@libp2p/interfaces-v3.0.3](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interfaces-v3.0.2...@libp2p/interfaces-v3.0.3) (2022-06-27) + + +### Trivial Changes + +* update deps ([#262](https://github.com/libp2p/js-libp2p-interfaces/issues/262)) ([51edf7d](https://github.com/libp2p/js-libp2p-interfaces/commit/51edf7d9b3765a6f75c915b1483ea345d0133a41)) + +## [@libp2p/interfaces-v3.0.2](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interfaces-v3.0.1...@libp2p/interfaces-v3.0.2) (2022-06-14) + + +### Trivial Changes + +* update aegir ([#234](https://github.com/libp2p/js-libp2p-interfaces/issues/234)) ([3e03895](https://github.com/libp2p/js-libp2p-interfaces/commit/3e038959ecab6cfa3585df9ee179c0af7a61eda5)) + +## [@libp2p/interfaces-v3.0.1](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interfaces-v3.0.0...@libp2p/interfaces-v3.0.1) (2022-06-14) + + +### Trivial Changes + +* update readmes ([#233](https://github.com/libp2p/js-libp2p-interfaces/issues/233)) ([ee7da38](https://github.com/libp2p/js-libp2p-interfaces/commit/ee7da38dccc08160d26c8436df8739ce7e0b340e)) + +## [@libp2p/interfaces-v3.0.0](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interfaces-v2.0.4...@libp2p/interfaces-v3.0.0) (2022-06-14) + + +### ⚠ BREAKING CHANGES + +* most modules have been split out of the `@libp2p/interfaces` and `@libp2p/interface-compliance-tests` packages + +### Trivial Changes + +* break modules apart ([#232](https://github.com/libp2p/js-libp2p-interfaces/issues/232)) ([385614e](https://github.com/libp2p/js-libp2p-interfaces/commit/385614e772329052ab17415c8bd421f65b01a61b)), closes [#226](https://github.com/libp2p/js-libp2p-interfaces/issues/226) + +## [@libp2p/interfaces-v2.0.4](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interfaces-v2.0.3...@libp2p/interfaces-v2.0.4) (2022-06-14) + + +### Bug Fixes + +* event emitter types ([#230](https://github.com/libp2p/js-libp2p-interfaces/issues/230)) ([f16f74f](https://github.com/libp2p/js-libp2p-interfaces/commit/f16f74fc972e537698884c1910854e9630058c36)), closes [#227](https://github.com/libp2p/js-libp2p-interfaces/issues/227) + + +### Trivial Changes + +* add missing deps ([#229](https://github.com/libp2p/js-libp2p-interfaces/issues/229)) ([cc3cece](https://github.com/libp2p/js-libp2p-interfaces/commit/cc3cece290108302fb84501e2500fb18cd99f90f)) + +## [@libp2p/interfaces-v2.0.3](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interfaces-v2.0.2...@libp2p/interfaces-v2.0.3) (2022-05-31) + + +### Bug Fixes + +* dead badge ([#221](https://github.com/libp2p/js-libp2p-interfaces/issues/221)) ([2b7fa07](https://github.com/libp2p/js-libp2p-interfaces/commit/2b7fa07935f800b2438b054b9678e9b16694bf45)) + +## [@libp2p/interfaces-v2.0.2](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interfaces-v2.0.1...@libp2p/interfaces-v2.0.2) (2022-05-24) + + +### Bug Fixes + +* accept abort options in connection.newStream ([#219](https://github.com/libp2p/js-libp2p-interfaces/issues/219)) ([8bfcbc9](https://github.com/libp2p/js-libp2p-interfaces/commit/8bfcbc9ee883336f213cdfc83e477549ca368df5)) + +## [@libp2p/interfaces-v2.0.1](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interfaces-v2.0.0...@libp2p/interfaces-v2.0.1) (2022-05-23) + + +### Bug Fixes + +* make stream return types synchronous ([#217](https://github.com/libp2p/js-libp2p-interfaces/issues/217)) ([2fe61b7](https://github.com/libp2p/js-libp2p-interfaces/commit/2fe61b7fbeda2e549edf095a927d623aa8eb476b)) + +## [@libp2p/interfaces-v2.0.0](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interfaces-v1.3.32...@libp2p/interfaces-v2.0.0) (2022-05-20) + + +### ⚠ BREAKING CHANGES + +* This adds closeWrite and closeRead checks in the tests, which will cause test failures for muxers that don't implement those + +### Bug Fixes + +* close streams when connection is closed ([#214](https://github.com/libp2p/js-libp2p-interfaces/issues/214)) ([88fcd58](https://github.com/libp2p/js-libp2p-interfaces/commit/88fcd586276e03dd740c7095f05e21754ac1f3b5)), closes [#90](https://github.com/libp2p/js-libp2p-interfaces/issues/90) + +## [@libp2p/interfaces-v1.3.32](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interfaces-v1.3.31...@libp2p/interfaces-v1.3.32) (2022-05-06) + + +### Bug Fixes + +* add tag to peer discovery interface ([#210](https://github.com/libp2p/js-libp2p-interfaces/issues/210)) ([f99c833](https://github.com/libp2p/js-libp2p-interfaces/commit/f99c833c8436f8434f380d890ec5d267279312d7)) + +## [@libp2p/interfaces-v1.3.31](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interfaces-v1.3.30...@libp2p/interfaces-v1.3.31) (2022-05-04) + + +### Bug Fixes + +* move startable and events interfaces ([#209](https://github.com/libp2p/js-libp2p-interfaces/issues/209)) ([8ce8a08](https://github.com/libp2p/js-libp2p-interfaces/commit/8ce8a08c94b0738aa32da516558977b195ddd8ed)) + +## [@libp2p/interfaces-v1.3.30](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interfaces-v1.3.29...@libp2p/interfaces-v1.3.30) (2022-05-04) + + +### Bug Fixes + +* remove connection manager options ([#208](https://github.com/libp2p/js-libp2p-interfaces/issues/208)) ([b93d051](https://github.com/libp2p/js-libp2p-interfaces/commit/b93d051cb889b175f324f589546033d6723b1298)) + +## [@libp2p/interfaces-v1.3.29](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interfaces-v1.3.28...@libp2p/interfaces-v1.3.29) (2022-05-03) + + +### Bug Fixes + +* only send handled protocols ([#207](https://github.com/libp2p/js-libp2p-interfaces/issues/207)) ([1f7afc2](https://github.com/libp2p/js-libp2p-interfaces/commit/1f7afc29d72fde708064ec6479011dbc0a225962)) + +## [@libp2p/interfaces-v1.3.28](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interfaces-v1.3.27...@libp2p/interfaces-v1.3.28) (2022-05-01) + + +### Bug Fixes + +* add abort options to open connection ([#206](https://github.com/libp2p/js-libp2p-interfaces/issues/206)) ([b32234f](https://github.com/libp2p/js-libp2p-interfaces/commit/b32234f97b6e5cf93ea103b9650bea2119ce6025)) + +## [@libp2p/interfaces-v1.3.27](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interfaces-v1.3.26...@libp2p/interfaces-v1.3.27) (2022-05-01) + + +### Bug Fixes + +* move connection manager mock to connection manager module ([#205](https://github.com/libp2p/js-libp2p-interfaces/issues/205)) ([a367375](https://github.com/libp2p/js-libp2p-interfaces/commit/a367375accc690d7b4608c9a3313f91df700efd8)) + +## [@libp2p/interfaces-v1.3.26](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interfaces-v1.3.25...@libp2p/interfaces-v1.3.26) (2022-04-28) + + +### Bug Fixes + +* pubsub should not be startable ([#204](https://github.com/libp2p/js-libp2p-interfaces/issues/204)) ([59bd924](https://github.com/libp2p/js-libp2p-interfaces/commit/59bd9245a207268525bdd26a05c5306fe436fcc4)) + +## [@libp2p/interfaces-v1.3.25](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interfaces-v1.3.24...@libp2p/interfaces-v1.3.25) (2022-04-28) + + +### Bug Fixes + +* pubsub and dht are always set ([#203](https://github.com/libp2p/js-libp2p-interfaces/issues/203)) ([86860c1](https://github.com/libp2p/js-libp2p-interfaces/commit/86860c1836a2464b2ad380b09542e3f3271ae287)) + +## [@libp2p/interfaces-v1.3.24](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interfaces-v1.3.23...@libp2p/interfaces-v1.3.24) (2022-04-22) + + +### Bug Fixes + +* update pubsub interface in line with gossipsub ([#199](https://github.com/libp2p/js-libp2p-interfaces/issues/199)) ([3f55596](https://github.com/libp2p/js-libp2p-interfaces/commit/3f555965cddea3ef03e7217b755c82aa4107e093)) + +## [@libp2p/interfaces-v1.3.23](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interfaces-v1.3.22...@libp2p/interfaces-v1.3.23) (2022-04-21) + + +### Bug Fixes + +* test PubSub interface and not PubSubBaseProtocol ([#198](https://github.com/libp2p/js-libp2p-interfaces/issues/198)) ([96c15c9](https://github.com/libp2p/js-libp2p-interfaces/commit/96c15c9780821a3cb763e48854d64377bf562692)) + +## [@libp2p/interfaces-v1.3.22](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interfaces-v1.3.21...@libp2p/interfaces-v1.3.22) (2022-04-20) + + +### Bug Fixes + +* emit pubsub messages using 'message' event ([#197](https://github.com/libp2p/js-libp2p-interfaces/issues/197)) ([df9b685](https://github.com/libp2p/js-libp2p-interfaces/commit/df9b685cea30653109f2fa2cb5583a3bca7b09bb)) + +## [@libp2p/interfaces-v1.3.21](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interfaces-v1.3.20...@libp2p/interfaces-v1.3.21) (2022-04-13) + + +### Bug Fixes + +* add keychain types, fix bigint types ([#193](https://github.com/libp2p/js-libp2p-interfaces/issues/193)) ([9ceadf9](https://github.com/libp2p/js-libp2p-interfaces/commit/9ceadf9d5c42a12d88d74ddd9140e34f7fa63537)) + +## [@libp2p/interfaces-v1.3.20](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interfaces-v1.3.19...@libp2p/interfaces-v1.3.20) (2022-04-08) + + +### Trivial Changes + +* update aegir ([#192](https://github.com/libp2p/js-libp2p-interfaces/issues/192)) ([41c1494](https://github.com/libp2p/js-libp2p-interfaces/commit/41c14941e8b67d6601a90b4d48a2776573d55e60)) + +## [@libp2p/interfaces-v1.3.19](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interfaces-v1.3.18...@libp2p/interfaces-v1.3.19) (2022-04-08) + + +### Bug Fixes + +* swap protobufjs for protons ([#191](https://github.com/libp2p/js-libp2p-interfaces/issues/191)) ([d72b30c](https://github.com/libp2p/js-libp2p-interfaces/commit/d72b30cfca4b9145e0b31db28e8fa3329a180e83)) + +## [@libp2p/interfaces-v1.3.18](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interfaces-v1.3.17...@libp2p/interfaces-v1.3.18) (2022-03-24) + + +### Bug Fixes + +* rename peer data to peer info ([#187](https://github.com/libp2p/js-libp2p-interfaces/issues/187)) ([dfea342](https://github.com/libp2p/js-libp2p-interfaces/commit/dfea3429bad57abde040397e4e7a58539829e9c2)) + +## [@libp2p/interfaces-v1.3.17](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interfaces-v1.3.16...@libp2p/interfaces-v1.3.17) (2022-03-21) + + +### Bug Fixes + +* expand startable interface ([#184](https://github.com/libp2p/js-libp2p-interfaces/issues/184)) ([b8e1a0c](https://github.com/libp2p/js-libp2p-interfaces/commit/b8e1a0c77316265cc08eaaf9fcab6cfa05d59ae1)) + +## [@libp2p/interfaces-v1.3.16](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interfaces-v1.3.15...@libp2p/interfaces-v1.3.16) (2022-03-20) + + +### Bug Fixes + +* update pubsub types ([#183](https://github.com/libp2p/js-libp2p-interfaces/issues/183)) ([7ef4baa](https://github.com/libp2p/js-libp2p-interfaces/commit/7ef4baad0fe30f783f3eecd5199ef92af08b7f57)) + +## [@libp2p/interfaces-v1.3.15](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interfaces-v1.3.14...@libp2p/interfaces-v1.3.15) (2022-03-16) + + +### Bug Fixes + +* update content routing get return type ([#182](https://github.com/libp2p/js-libp2p-interfaces/issues/182)) ([49da9d5](https://github.com/libp2p/js-libp2p-interfaces/commit/49da9d5883618a672d64542eee13972e603a5b3d)) + +## [@libp2p/interfaces-v1.3.14](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interfaces-v1.3.13...@libp2p/interfaces-v1.3.14) (2022-03-15) + + +### Bug Fixes + +* simplify transport interface, update interfaces for use with libp2p ([#180](https://github.com/libp2p/js-libp2p-interfaces/issues/180)) ([ec81622](https://github.com/libp2p/js-libp2p-interfaces/commit/ec81622e5b7c6d256e0f8aed6d3695642473293b)) + +## [@libp2p/interfaces-v1.3.13](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interfaces-v1.3.12...@libp2p/interfaces-v1.3.13) (2022-02-27) + + +### Bug Fixes + +* rename crypto to connection-encrypter ([#179](https://github.com/libp2p/js-libp2p-interfaces/issues/179)) ([d197f55](https://github.com/libp2p/js-libp2p-interfaces/commit/d197f554d7cdadb3b05ed2d6c69fda2c4362b1eb)) + +## [@libp2p/interfaces-v1.3.12](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interfaces-v1.3.11...@libp2p/interfaces-v1.3.12) (2022-02-27) + + +### Bug Fixes + +* update package config and add connection gater interface ([#178](https://github.com/libp2p/js-libp2p-interfaces/issues/178)) ([c6079a6](https://github.com/libp2p/js-libp2p-interfaces/commit/c6079a6367f004788062df3e30ad2e26330d947b)) + +## [@libp2p/interfaces-v1.3.11](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interfaces-v1.3.10...@libp2p/interfaces-v1.3.11) (2022-02-21) + + +### Bug Fixes + +* remove unused dht query option ([#176](https://github.com/libp2p/js-libp2p-interfaces/issues/176)) ([e0ce46d](https://github.com/libp2p/js-libp2p-interfaces/commit/e0ce46d371a92a7063f02e7a1729a39def80e15e)) + +## [@libp2p/interfaces-v1.3.10](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interfaces-v1.3.9...@libp2p/interfaces-v1.3.10) (2022-02-21) + + +### Bug Fixes + +* update muxer to pass transport tests ([#174](https://github.com/libp2p/js-libp2p-interfaces/issues/174)) ([466ed53](https://github.com/libp2p/js-libp2p-interfaces/commit/466ed53192aa196ac2dbdb83df3c8db9cd5b1e07)) + +## [@libp2p/interfaces-v1.3.9](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interfaces-v1.3.8...@libp2p/interfaces-v1.3.9) (2022-02-18) + + +### Bug Fixes + +* remove delays from pubsub tests ([#173](https://github.com/libp2p/js-libp2p-interfaces/issues/173)) ([5c8fe09](https://github.com/libp2p/js-libp2p-interfaces/commit/5c8fe09294f0cbd8add1406a61fa7dbc5b4e788b)) + +## [@libp2p/interfaces-v1.3.8](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interfaces-v1.3.7...@libp2p/interfaces-v1.3.8) (2022-02-18) + + +### Bug Fixes + +* simpler pubsub ([#172](https://github.com/libp2p/js-libp2p-interfaces/issues/172)) ([98715ed](https://github.com/libp2p/js-libp2p-interfaces/commit/98715ed73183b32e4fda3d878a462389548358d9)) + +## [@libp2p/interfaces-v1.3.7](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interfaces-v1.3.6...@libp2p/interfaces-v1.3.7) (2022-02-17) + + +### Bug Fixes + +* add multistream-select and update pubsub types ([#170](https://github.com/libp2p/js-libp2p-interfaces/issues/170)) ([b9ecb2b](https://github.com/libp2p/js-libp2p-interfaces/commit/b9ecb2bee8f2abc0c41bfcf7bf2025894e37ddc2)) + +## [@libp2p/interfaces-v1.3.6](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interfaces-v1.3.5...@libp2p/interfaces-v1.3.6) (2022-02-12) + + +### Bug Fixes + +* hide implementations behind factory methods ([#167](https://github.com/libp2p/js-libp2p-interfaces/issues/167)) ([2fba080](https://github.com/libp2p/js-libp2p-interfaces/commit/2fba0800c9896af6dcc49da4fa904bb4a3e3e40d)) + +## [@libp2p/interfaces-v1.3.5](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interfaces-v1.3.4...@libp2p/interfaces-v1.3.5) (2022-02-11) + + +### Bug Fixes + +* add .js to import paths ([#166](https://github.com/libp2p/js-libp2p-interfaces/issues/166)) ([dbf6688](https://github.com/libp2p/js-libp2p-interfaces/commit/dbf6688cc7295c821b473b05d211239616ad2ae1)) + +## [@libp2p/interfaces-v1.3.4](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interfaces-v1.3.3...@libp2p/interfaces-v1.3.4) (2022-02-11) + + +### Bug Fixes + +* simpler topologies ([#164](https://github.com/libp2p/js-libp2p-interfaces/issues/164)) ([45fcaa1](https://github.com/libp2p/js-libp2p-interfaces/commit/45fcaa10a6a3215089340ff2eff117d7fd1100e7)) + +## [@libp2p/interfaces-v1.3.3](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interfaces-v1.3.2...@libp2p/interfaces-v1.3.3) (2022-02-10) + + +### Bug Fixes + +* make registrar simpler ([#163](https://github.com/libp2p/js-libp2p-interfaces/issues/163)) ([d122f3d](https://github.com/libp2p/js-libp2p-interfaces/commit/d122f3daaccc04039d90814960da92b513265644)) + +## [@libp2p/interfaces-v1.3.2](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interfaces-v1.3.1...@libp2p/interfaces-v1.3.2) (2022-02-10) + + +### Bug Fixes + +* remove args from listener events ([#162](https://github.com/libp2p/js-libp2p-interfaces/issues/162)) ([011ac89](https://github.com/libp2p/js-libp2p-interfaces/commit/011ac891ec7d44625cb4342f068bcd9f241a5b45)) + +## [@libp2p/interfaces-v1.3.1](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interfaces-v1.3.0...@libp2p/interfaces-v1.3.1) (2022-02-10) + + +### Bug Fixes + +* remove node event emitters ([#161](https://github.com/libp2p/js-libp2p-interfaces/issues/161)) ([221fb6a](https://github.com/libp2p/js-libp2p-interfaces/commit/221fb6a024430dc56288d73d8b8ce1aa88427701)) + +## [@libp2p/interfaces-v1.3.0](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interfaces-v1.2.0...@libp2p/interfaces-v1.3.0) (2022-02-09) + + +### Features + +* add peer store/records, and streams are just streams ([#160](https://github.com/libp2p/js-libp2p-interfaces/issues/160)) ([8860a0c](https://github.com/libp2p/js-libp2p-interfaces/commit/8860a0cd46b359a5648402d83870f7ff957222fe)) + +## [@libp2p/interfaces-v1.2.0](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interfaces-v1.1.1...@libp2p/interfaces-v1.2.0) (2022-02-07) + + +### Features + +* add logger package ([#158](https://github.com/libp2p/js-libp2p-interfaces/issues/158)) ([f327cd2](https://github.com/libp2p/js-libp2p-interfaces/commit/f327cd24825d9ce2f45a02fdb9b47c9735c847e0)) + +## [@libp2p/interfaces-v1.1.1](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interfaces-v1.1.0...@libp2p/interfaces-v1.1.1) (2022-02-05) + + +### Bug Fixes + +* fix muxer tests ([#157](https://github.com/libp2p/js-libp2p-interfaces/issues/157)) ([7233c44](https://github.com/libp2p/js-libp2p-interfaces/commit/7233c4438479dff56a682f45209ef7a938d63857)) + +## [@libp2p/interfaces-v1.1.0](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interfaces-v1.0.6...@libp2p/interfaces-v1.1.0) (2022-02-05) + + +### Features + +* add tracked-map ([#156](https://github.com/libp2p/js-libp2p-interfaces/issues/156)) ([c17730f](https://github.com/libp2p/js-libp2p-interfaces/commit/c17730f8bca172db85507740eaba81b3cf514d04)) + +## [@libp2p/interfaces-v1.0.6](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interfaces-v1.0.5...@libp2p/interfaces-v1.0.6) (2022-02-02) + + +### Bug Fixes + +* make discovery startable ([#155](https://github.com/libp2p/js-libp2p-interfaces/issues/155)) ([c7db291](https://github.com/libp2p/js-libp2p-interfaces/commit/c7db2918f5c4e00b45bcbd69541224403e5451e1)) + +## [@libp2p/interfaces-v1.0.5](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interfaces-v1.0.4...@libp2p/interfaces-v1.0.5) (2022-01-29) + + +### Bug Fixes + +* remove extra fields ([#153](https://github.com/libp2p/js-libp2p-interfaces/issues/153)) ([ccd7cf3](https://github.com/libp2p/js-libp2p-interfaces/commit/ccd7cf3f5ac71337baf516d3b0f6fc724ee0d3b4)) + +## [@libp2p/interfaces-v1.0.4](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interfaces-v1.0.3...@libp2p/interfaces-v1.0.4) (2022-01-15) + + +### Bug Fixes + +* remove abort controller dep ([#151](https://github.com/libp2p/js-libp2p-interfaces/issues/151)) ([518bce1](https://github.com/libp2p/js-libp2p-interfaces/commit/518bce1f9bd1f8b2922338e0c65c9934af7da3af)) + +## [@libp2p/interfaces-v1.0.3](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interfaces-v1.0.2...@libp2p/interfaces-v1.0.3) (2022-01-15) + + +### Trivial Changes + +* update project config ([#149](https://github.com/libp2p/js-libp2p-interfaces/issues/149)) ([6eb8556](https://github.com/libp2p/js-libp2p-interfaces/commit/6eb85562c0da167d222808da10a7914daf12970b)) + +## [@libp2p/interfaces-v1.0.2](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interfaces-v1.0.1...@libp2p/interfaces-v1.0.2) (2022-01-14) + + +### Bug Fixes + +* update it-* deps to ts versions ([#148](https://github.com/libp2p/js-libp2p-interfaces/issues/148)) ([7a6fdd7](https://github.com/libp2p/js-libp2p-interfaces/commit/7a6fdd7622ce2870b89dbb849ab421d0dd714b43)) + +## [@libp2p/interfaces-v1.0.1](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interfaces-v1.0.0...@libp2p/interfaces-v1.0.1) (2022-01-08) + + +### Trivial Changes + +* add semantic release config ([#141](https://github.com/libp2p/js-libp2p-interfaces/issues/141)) ([5f0de59](https://github.com/libp2p/js-libp2p-interfaces/commit/5f0de59136b6343d2411abb2d6a4dd2cd0b7efe4)) +* update package versions ([#140](https://github.com/libp2p/js-libp2p-interfaces/issues/140)) ([cd844f6](https://github.com/libp2p/js-libp2p-interfaces/commit/cd844f6e39f4ee50d006e86eac8dadf696900eb5)) + +# Change Log + +All notable changes to this project will be documented in this file. +See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +# 0.2.0 (2022-01-04) + + +### Bug Fixes + +* make connection upgrade and encryption abortable ([#121](https://github.com/libp2p/js-libp2p-interfaces/issues/121)) ([d31583d](https://github.com/libp2p/js-libp2p-interfaces/commit/d31583d204c5fa07df0479f37bd0d4a925cc812b)) +* move errors ([#132](https://github.com/libp2p/js-libp2p-interfaces/issues/132)) ([21d282a](https://github.com/libp2p/js-libp2p-interfaces/commit/21d282a6d77bd7d1a12daa1cc8b3a3fed8635dad)) +* update dialer tests ([#116](https://github.com/libp2p/js-libp2p-interfaces/issues/116)) ([c679729](https://github.com/libp2p/js-libp2p-interfaces/commit/c679729113feb963ff27815fcafd7af51f722df7)) + + +### Features + +* add auto-publish ([7aede5d](https://github.com/libp2p/js-libp2p-interfaces/commit/7aede5df39ea6b5f243348ec9a212b3e33c16a81)) +* remove getPublicKey method from dht ([603c818](https://github.com/libp2p/js-libp2p-interfaces/commit/603c818d0694652346bc552525f2379b1dfa0107)) +* simpler peer id ([#117](https://github.com/libp2p/js-libp2p-interfaces/issues/117)) ([fa2c4f5](https://github.com/libp2p/js-libp2p-interfaces/commit/fa2c4f5be74a5cfc11489771881e57b4e53bf174)) +* split out code, convert to typescript ([#111](https://github.com/libp2p/js-libp2p-interfaces/issues/111)) ([e174bba](https://github.com/libp2p/js-libp2p-interfaces/commit/e174bba889388269b806643c79a6b53c8d6a0f8c)), closes [#110](https://github.com/libp2p/js-libp2p-interfaces/issues/110) [#101](https://github.com/libp2p/js-libp2p-interfaces/issues/101) +* update package names ([#133](https://github.com/libp2p/js-libp2p-interfaces/issues/133)) ([337adc9](https://github.com/libp2p/js-libp2p-interfaces/commit/337adc9a9bc0278bdae8cbce9c57d07a83c8b5c2)) + + +### BREAKING CHANGES + +* the dht.getPublicKey method has been removed +* not all fields from concrete classes have been added to the interfaces, some adjustment may be necessary as this gets rolled out + + + + + +## [3.1.1](https://github.com/libp2p/js-libp2p-interfaces/compare/libp2p-interfaces@3.1.0...libp2p-interfaces@3.1.1) (2022-01-02) + + +### Bug Fixes + +* move errors ([#132](https://github.com/libp2p/js-libp2p-interfaces/issues/132)) ([21d282a](https://github.com/libp2p/js-libp2p-interfaces/commit/21d282a6d77bd7d1a12daa1cc8b3a3fed8635dad)) + + + + + +# [3.1.0](https://github.com/libp2p/js-libp2p-interfaces/compare/libp2p-interfaces@3.0.0...libp2p-interfaces@3.1.0) (2022-01-02) + + +### Bug Fixes + +* make connection upgrade and encryption abortable ([#121](https://github.com/libp2p/js-libp2p-interfaces/issues/121)) ([d31583d](https://github.com/libp2p/js-libp2p-interfaces/commit/d31583d204c5fa07df0479f37bd0d4a925cc812b)) +* update dialer tests ([#116](https://github.com/libp2p/js-libp2p-interfaces/issues/116)) ([c679729](https://github.com/libp2p/js-libp2p-interfaces/commit/c679729113feb963ff27815fcafd7af51f722df7)) + + +### Features + +* simpler peer id ([#117](https://github.com/libp2p/js-libp2p-interfaces/issues/117)) ([fa2c4f5](https://github.com/libp2p/js-libp2p-interfaces/commit/fa2c4f5be74a5cfc11489771881e57b4e53bf174)) + + + + + +# [3.0.0](https://github.com/libp2p/js-libp2p-interfaces/compare/libp2p-interfaces@2.0.0...libp2p-interfaces@3.0.0) (2021-12-02) + + +### Features + +* remove getPublicKey method from dht ([603c818](https://github.com/libp2p/js-libp2p-interfaces/commit/603c818d0694652346bc552525f2379b1dfa0107)) + + +### BREAKING CHANGES + +* the dht.getPublicKey method has been removed + + + + + +# [2.0.0](https://github.com/libp2p/js-libp2p-interfaces/compare/libp2p-interfaces@1.2.0...libp2p-interfaces@2.0.0) (2021-11-22) + + +### Features + +* split out code, convert to typescript ([#111](https://github.com/libp2p/js-libp2p-interfaces/issues/111)) ([e174bba](https://github.com/libp2p/js-libp2p-interfaces/commit/e174bba889388269b806643c79a6b53c8d6a0f8c)), closes [#110](https://github.com/libp2p/js-libp2p-interfaces/issues/110) [#101](https://github.com/libp2p/js-libp2p-interfaces/issues/101) + + +### BREAKING CHANGES + +* not all fields from concrete classes have been added to the interfaces, some adjustment may be necessary as this gets rolled out + + + + + +# [1.2.0](https://github.com/libp2p/js-interfaces/compare/libp2p-interfaces@1.1.1...libp2p-interfaces@1.2.0) (2021-10-18) + + +### Features + +* add ValueStore interface ([#108](https://github.com/libp2p/js-interfaces/issues/108)) ([09c6e10](https://github.com/libp2p/js-interfaces/commit/09c6e10353c1d8842a5522d28d5cd38ae47d892f)) + + + + + +## [1.1.1](https://github.com/libp2p/js-interfaces/compare/libp2p-interfaces@1.1.0...libp2p-interfaces@1.1.1) (2021-09-20) + + +### Bug Fixes + +* allow pubsub rpc to be processed concurrently ([#106](https://github.com/libp2p/js-interfaces/issues/106)) ([52f96b3](https://github.com/libp2p/js-interfaces/commit/52f96b3eb867b844a9c4a0d1fd632baa300052a2)) + + + + + +# [1.1.0](https://github.com/libp2p/js-interfaces/compare/libp2p-interfaces@1.0.1...libp2p-interfaces@1.1.0) (2021-08-20) + + +### Features + +* update uint8arrays ([#105](https://github.com/libp2p/js-interfaces/issues/105)) ([9297a9c](https://github.com/libp2p/js-interfaces/commit/9297a9c379276d03c8da849af6108b38e581b4a6)) + + + + + +## [1.0.1](https://github.com/libp2p/js-interfaces/compare/libp2p-interfaces@1.0.0...libp2p-interfaces@1.0.1) (2021-07-08) + + +### Bug Fixes + +* make tests more reliable ([#103](https://github.com/libp2p/js-interfaces/issues/103)) ([cd4c409](https://github.com/libp2p/js-interfaces/commit/cd4c40908efe2e9ffc14aa61aace5176a43fd70a)) +* remove timeouts ([#104](https://github.com/libp2p/js-interfaces/issues/104)) ([3699c17](https://github.com/libp2p/js-interfaces/commit/3699c17f022da40a87ab24adc3b2081df7a0ddcd)) + + + + + +# 1.0.0 (2021-07-07) + + +### chore + +* monorepo separating interfaces and compliance tests ([#97](https://github.com/libp2p/js-interfaces/issues/97)) ([946348f](https://github.com/libp2p/js-interfaces/commit/946348f7f8acc1ff7bc9cd0ab4c2602d41106f76)) + + +### BREAKING CHANGES + +* the tests now live in the libp2p-interfaces-compliance-tests module + + + + + +## [0.12.1](https://github.com/libp2p/js-interfaces/compare/v0.12.0...v0.12.1) (2021-07-07) + + + +# [0.12.0](https://github.com/libp2p/js-interfaces/compare/v0.11.0...v0.12.0) (2021-07-06) + + +### chore + +* update to new multiformats ([#98](https://github.com/libp2p/js-interfaces/issues/98)) ([242027c](https://github.com/libp2p/js-interfaces/commit/242027cdfd08004f20d0148f4905381d34942dc6)) + + +### BREAKING CHANGES + +* uses the CID class from the new multiformats module and pubsub.getMsgId became async + +Co-authored-by: Vasco Santos + + + +# [0.11.0](https://github.com/libp2p/js-interfaces/compare/v0.10.4...v0.11.0) (2021-05-27) + + + +## [0.10.4](https://github.com/libp2p/js-interfaces/compare/v0.10.3...v0.10.4) (2021-04-30) + + +### Bug Fixes + +* event emitter and discovery and routing interfaces ([#95](https://github.com/libp2p/js-interfaces/issues/95)) ([93ddaa4](https://github.com/libp2p/js-interfaces/commit/93ddaa4860ff743b312268b6a859b5e6365fff74)) + + + +## [0.10.3](https://github.com/libp2p/js-interfaces/compare/v0.10.2...v0.10.3) (2021-04-22) + + + +## [0.10.2](https://github.com/libp2p/js-interfaces/compare/v0.10.1...v0.10.2) (2021-04-20) + + +### Bug Fixes + +* specify pbjs root ([#93](https://github.com/libp2p/js-interfaces/issues/93)) ([70ae573](https://github.com/libp2p/js-interfaces/commit/70ae573a4ea2ac074f7cda2a732ed5be283e48c4)) + + + +## [0.10.1](https://github.com/libp2p/js-interfaces/compare/v0.10.0...v0.10.1) (2021-04-19) + + +### Features + +* export type for new stream muxer function ([#91](https://github.com/libp2p/js-interfaces/issues/91)) ([6467fdd](https://github.com/libp2p/js-interfaces/commit/6467fdd6ce1d13cfa3b34e3aaa73a64a669d2033)) + + + +# [0.10.0](https://github.com/libp2p/js-interfaces/compare/v0.9.0...v0.10.0) (2021-04-12) + + + +# [0.9.0](https://github.com/libp2p/js-interfaces/compare/v0.8.4...v0.9.0) (2021-04-07) + + + +## [0.8.4](https://github.com/libp2p/js-interfaces/compare/v0.8.3...v0.8.4) (2021-03-22) + + +### Bug Fixes + +* specify connection direction ([#86](https://github.com/libp2p/js-interfaces/issues/86)) ([3b960d5](https://github.com/libp2p/js-interfaces/commit/3b960d516f70f7e198574a736cb09000ddd7a94c)) + + + +## [0.8.3](https://github.com/libp2p/js-interfaces/compare/v0.8.2...v0.8.3) (2021-01-26) + + + +## [0.8.2](https://github.com/libp2p/js-interfaces/compare/v0.8.1...v0.8.2) (2021-01-20) + + +### Bug Fixes + +* event emitter types with local types ([#80](https://github.com/libp2p/js-interfaces/issues/80)) ([ca52077](https://github.com/libp2p/js-interfaces/commit/ca520775eb26f5ed501375fdb24ba698c9a8c8c8)) + + + +## [0.8.1](https://github.com/libp2p/js-interfaces/compare/v0.8.0...v0.8.1) (2020-12-11) + + +### Bug Fixes + +* pubsub publish message should be uint8array ([#77](https://github.com/libp2p/js-interfaces/issues/77)) ([5b99e6b](https://github.com/libp2p/js-interfaces/commit/5b99e6b56b10439a82ee88fb4e31fb95c182264f)) + + + +# [0.8.0](https://github.com/libp2p/js-interfaces/compare/v0.7.2...v0.8.0) (2020-12-10) + + +### Features + +* add types ([#74](https://github.com/libp2p/js-interfaces/issues/74)) ([e2419ea](https://github.com/libp2p/js-interfaces/commit/e2419ea308b5db38966850ba6349602c93ce3b0e)) + + + + +## [0.7.2](https://github.com/libp2p/js-interfaces/compare/v0.7.1...v0.7.2) (2020-11-11) + + + + +## [0.7.1](https://github.com/libp2p/js-interfaces/compare/v0.7.0...v0.7.1) (2020-11-03) + + +### Bug Fixes + +* typescript types ([#69](https://github.com/libp2p/js-interfaces/issues/69)) ([269a6f5](https://github.com/libp2p/js-interfaces/commit/269a6f5)) + + + + +# [0.7.0](https://github.com/libp2p/js-interfaces/compare/v0.5.2...v0.7.0) (2020-11-03) + + +### Features + +* pubsub: add global signature policy ([#66](https://github.com/libp2p/js-interfaces/issues/66)) ([946b046](https://github.com/libp2p/js-interfaces/commit/946b046)) +* update pubsub getMsgId return type to Uint8Array ([#65](https://github.com/libp2p/js-interfaces/issues/65)) ([e148443](https://github.com/libp2p/js-interfaces/commit/e148443)) + + +### BREAKING CHANGES + +* `signMessages` and `strictSigning` pubsub configuration options replaced +with a `globalSignaturePolicy` option +* new getMsgId return type is not backwards compatible with prior `string` +return type. + + + + +# [0.6.0](https://github.com/libp2p/js-interfaces/compare/v0.5.2...v0.6.0) (2020-10-05) + + +### Features + +* update pubsub getMsgId return type to Uint8Array ([#65](https://github.com/libp2p/js-interfaces/issues/65)) ([e148443](https://github.com/libp2p/js-interfaces/commit/e148443)) + + +### BREAKING CHANGES + +* new getMsgId return type is not backwards compatible with prior `string` +return type. + + + + +## [0.5.2](https://github.com/libp2p/js-interfaces/compare/v0.3.1...v0.5.2) (2020-09-30) + + +### Bug Fixes + +* replace remaining Buffer usage with Uint8Array ([#62](https://github.com/libp2p/js-interfaces/issues/62)) ([4130e7f](https://github.com/libp2p/js-interfaces/commit/4130e7f)) + + +### Chores + +* update deps ([#57](https://github.com/libp2p/js-interfaces/issues/57)) ([75f6777](https://github.com/libp2p/js-interfaces/commit/75f6777)) + + +### Features + +* interface pubsub ([#60](https://github.com/libp2p/js-interfaces/issues/60)) ([ba15a48](https://github.com/libp2p/js-interfaces/commit/ba15a48)) +* record interface ([#52](https://github.com/libp2p/js-interfaces/issues/52)) ([1cc943e](https://github.com/libp2p/js-interfaces/commit/1cc943e)) + + +### BREAKING CHANGES + +* records now marshal as Uint8Array instead of Buffer + +* fix: refactor remaining Buffer usage to Uint8Array +* - The peer id dep of this module has replaced node Buffers with Uint8Arrays + +* chore: update gh deps + + + + +## [0.5.1](https://github.com/libp2p/js-interfaces/compare/v0.5.0...v0.5.1) (2020-08-25) + + +### Features + +* interface pubsub ([#60](https://github.com/libp2p/js-interfaces/issues/60)) ([ba15a48](https://github.com/libp2p/js-interfaces/commit/ba15a48)) + + + + +# [0.5.0](https://github.com/libp2p/js-interfaces/compare/v0.4.1...v0.5.0) (2020-08-24) + + +### Bug Fixes + +* replace remaining Buffer usage with Uint8Array ([#62](https://github.com/libp2p/js-interfaces/issues/62)) ([4130e7f](https://github.com/libp2p/js-interfaces/commit/4130e7f)) + + +### BREAKING CHANGES + +* records now marshal as Uint8Array instead of Buffer + +* fix: refactor remaining Buffer usage to Uint8Array + + + + +## [0.4.1](https://github.com/libp2p/js-interfaces/compare/v0.4.0...v0.4.1) (2020-08-11) + + + + +# [0.4.0](https://github.com/libp2p/js-interfaces/compare/v0.3.2...v0.4.0) (2020-08-10) + + +### Chores + +* update deps ([#57](https://github.com/libp2p/js-interfaces/issues/57)) ([75f6777](https://github.com/libp2p/js-interfaces/commit/75f6777)) + + +### BREAKING CHANGES + +* - The peer id dep of this module has replaced node Buffers with Uint8Arrays + +* chore: update gh deps + + + + +## [0.3.2](https://github.com/libp2p/js-interfaces/compare/v0.3.1...v0.3.2) (2020-07-15) + + +### Features + +* record interface ([#52](https://github.com/libp2p/js-interfaces/issues/52)) ([1cc943e](https://github.com/libp2p/js-interfaces/commit/1cc943e)) + + + + +## [0.3.1](https://github.com/libp2p/js-interfaces/compare/v0.2.8...v0.3.1) (2020-07-03) + + +### Bug Fixes + +* content and peer routing multiaddrs property ([#49](https://github.com/libp2p/js-interfaces/issues/49)) ([9fbf9d0](https://github.com/libp2p/js-interfaces/commit/9fbf9d0)) +* peer-routing typo ([#47](https://github.com/libp2p/js-interfaces/issues/47)) ([9a8f375](https://github.com/libp2p/js-interfaces/commit/9a8f375)) +* reconnect should trigger topology on connect if protocol stored ([#54](https://github.com/libp2p/js-interfaces/issues/54)) ([e10a154](https://github.com/libp2p/js-interfaces/commit/e10a154)) + + +### Chores + +* remove peer-info usage on topology ([#42](https://github.com/libp2p/js-interfaces/issues/42)) ([a55c7c4](https://github.com/libp2p/js-interfaces/commit/a55c7c4)) +* update content and peer routing interfaces removing peer-info ([#43](https://github.com/libp2p/js-interfaces/issues/43)) ([87e2e89](https://github.com/libp2p/js-interfaces/commit/87e2e89)) + + +### Features + +* peer-discovery not using peer-info ([bdd2502](https://github.com/libp2p/js-interfaces/commit/bdd2502)) + + +### BREAKING CHANGES + +* topology api now uses peer-id instead of peer-info +* content-routing and peer-routing APIs return an object with relevant properties instead of peer-info +* peer-discovery emits object with id and multiaddrs properties + + + + +# [0.3.0](https://github.com/libp2p/js-interfaces/compare/v0.2.8...v0.3.0) (2020-04-21) + + +### Chores + +* remove peer-info usage on topology ([#42](https://github.com/libp2p/js-interfaces/issues/42)) ([79a7843](https://github.com/libp2p/js-interfaces/commit/79a7843)) +* update content and peer routing interfaces removing peer-info ([#43](https://github.com/libp2p/js-interfaces/issues/43)) ([d2032e6](https://github.com/libp2p/js-interfaces/commit/d2032e6)) + + +### Features + +* peer-discovery not using peer-info ([5792b13](https://github.com/libp2p/js-interfaces/commit/5792b13)) + + +### BREAKING CHANGES + +* topology api now uses peer-id instead of peer-info +* content-routing and peer-routing APIs return an object with relevant properties instead of peer-info +* peer-discovery emits object with id and multiaddrs properties + + + + +## [0.2.8](https://github.com/libp2p/js-interfaces/compare/v0.2.7...v0.2.8) (2020-04-21) + + + + +## [0.2.7](https://github.com/libp2p/js-interfaces/compare/v0.2.6...v0.2.7) (2020-03-20) + + +### Bug Fixes + +* add buffer ([#39](https://github.com/libp2p/js-interfaces/issues/39)) ([78e015c](https://github.com/libp2p/js-interfaces/commit/78e015c)) + + + + +## [0.2.6](https://github.com/libp2p/js-interfaces/compare/v0.2.5...v0.2.6) (2020-02-17) + + +### Bug Fixes + +* remove use of assert module ([#34](https://github.com/libp2p/js-interfaces/issues/34)) ([c77d8de](https://github.com/libp2p/js-interfaces/commit/c77d8de)) + + + + +## [0.2.5](https://github.com/libp2p/js-interfaces/compare/v0.2.4...v0.2.5) (2020-02-04) + + +### Bug Fixes + +* **connection:** tracks streams properly ([#25](https://github.com/libp2p/js-interfaces/issues/25)) ([5c88d77](https://github.com/libp2p/js-interfaces/commit/5c88d77)) + + + + +## [0.2.4](https://github.com/libp2p/js-interfaces/compare/v0.2.3...v0.2.4) (2020-02-04) + + +### Bug Fixes + +* dependencies for tests should not be needed by who requires the tests ([#18](https://github.com/libp2p/js-interfaces/issues/18)) ([c5b724a](https://github.com/libp2p/js-interfaces/commit/c5b724a)) + + + + +## [0.2.3](https://github.com/libp2p/js-interfaces/compare/v0.2.2...v0.2.3) (2020-01-21) + + +### Bug Fixes + +* **transport:** make close listener test more resilient ([#21](https://github.com/libp2p/js-interfaces/issues/21)) ([2de533e](https://github.com/libp2p/js-interfaces/commit/2de533e)) + + + + +## [0.2.2](https://github.com/libp2p/js-interfaces/compare/v0.2.1...v0.2.2) (2020-01-17) + + +### Bug Fixes + +* **connection:** dont require remoteAddr on creation ([#20](https://github.com/libp2p/js-interfaces/issues/20)) ([5967834](https://github.com/libp2p/js-interfaces/commit/5967834)) + + + + +## [0.2.1](https://github.com/libp2p/js-interfaces/compare/v0.2.0...v0.2.1) (2019-12-28) + + +### Features + +* add crypto transmission error ([#17](https://github.com/libp2p/js-interfaces/issues/17)) ([d98bb23](https://github.com/libp2p/js-interfaces/commit/d98bb23)) + + + + +# [0.2.0](https://github.com/libp2p/js-interfaces/compare/v0.1.7...v0.2.0) (2019-12-20) + + +### Bug Fixes + +* transport should not handle connection if upgradeInbound throws ([#16](https://github.com/libp2p/js-interfaces/issues/16)) ([ff03137](https://github.com/libp2p/js-interfaces/commit/ff03137)) + + + + +## [0.1.7](https://github.com/libp2p/js-interfaces/compare/v0.1.6...v0.1.7) (2019-12-15) + + +### Features + +* export connection status' ([#15](https://github.com/libp2p/js-interfaces/issues/15)) ([bdbd58e](https://github.com/libp2p/js-interfaces/commit/bdbd58e)) + + + + +## [0.1.6](https://github.com/libp2p/js-interfaces/compare/v0.1.5...v0.1.6) (2019-12-02) + + +### Bug Fixes + +* multicodec topology disconnect with peer param ([#12](https://github.com/libp2p/js-interfaces/issues/12)) ([d5dd256](https://github.com/libp2p/js-interfaces/commit/d5dd256)) + + + + +## [0.1.5](https://github.com/libp2p/js-interfaces/compare/v0.1.4...v0.1.5) (2019-11-15) + + +### Bug Fixes + +* multicodec topology update peers with multicodec ([#10](https://github.com/libp2p/js-interfaces/issues/10)) ([21d8ae6](https://github.com/libp2p/js-interfaces/commit/21d8ae6)) + + +### Features + +* add class-is to topology ([#11](https://github.com/libp2p/js-interfaces/issues/11)) ([a67abcc](https://github.com/libp2p/js-interfaces/commit/a67abcc)) + + + + +## [0.1.4](https://github.com/libp2p/js-interfaces/compare/v0.1.3...v0.1.4) (2019-11-14) + + +### Features + +* add topology interfaces ([#7](https://github.com/libp2p/js-interfaces/issues/7)) ([8bee747](https://github.com/libp2p/js-interfaces/commit/8bee747)) + + + + +## [0.1.3](https://github.com/libp2p/js-interfaces/compare/v0.1.2...v0.1.3) (2019-10-30) + + +### Bug Fixes + +* localAddr should be optional ([#6](https://github.com/libp2p/js-interfaces/issues/6)) ([749a8d0](https://github.com/libp2p/js-interfaces/commit/749a8d0)) + + + + +## [0.1.2](https://github.com/libp2p/js-interfaces/compare/v0.1.1...v0.1.2) (2019-10-29) + + +### Features + +* crypto errors ([#4](https://github.com/libp2p/js-interfaces/issues/4)) ([d2fe2d1](https://github.com/libp2p/js-interfaces/commit/d2fe2d1)) + + + + +## [0.1.1](https://github.com/libp2p/js-interfaces/compare/v0.1.0...v0.1.1) (2019-10-21) + + +### Features + +* crypto interface ([#2](https://github.com/libp2p/js-interfaces/issues/2)) ([5a5c44a](https://github.com/libp2p/js-interfaces/commit/5a5c44a)) + + + + +# 0.1.0 (2019-10-20) + + +### Bug Fixes + +* add async support to setup ([#11](https://github.com/libp2p/js-interfaces/issues/11)) ([2814c76](https://github.com/libp2p/js-interfaces/commit/2814c76)) +* **test:** close with timeout ([#54](https://github.com/libp2p/js-interfaces/issues/54)) ([583f02d](https://github.com/libp2p/js-interfaces/commit/583f02d)) +* avoid making webpacky funky by not trying to inject tcp ([6695b80](https://github.com/libp2p/js-interfaces/commit/6695b80)) +* improve the close test ([d9c8681](https://github.com/libp2p/js-interfaces/commit/d9c8681)) +* move dirty-chai to dependencies ([#52](https://github.com/libp2p/js-interfaces/issues/52)) ([f9a7908](https://github.com/libp2p/js-interfaces/commit/f9a7908)) +* some fixes for incorrect tests ([23a75d1](https://github.com/libp2p/js-interfaces/commit/23a75d1)) +* when things are in the same process, there is a order to them :) ([1635977](https://github.com/libp2p/js-interfaces/commit/1635977)) +* wrong main path in package.json ([54b83a7](https://github.com/libp2p/js-interfaces/commit/54b83a7)) +* **deps:** fix package.json ([e0f7db3](https://github.com/libp2p/js-interfaces/commit/e0f7db3)) +* **dial-test:** ensure goodbye works over tcp ([e1346da](https://github.com/libp2p/js-interfaces/commit/e1346da)) +* **package.json:** point to right main ([ace6150](https://github.com/libp2p/js-interfaces/commit/ace6150)) +* **package.json:** point to right main ([84cd2ca](https://github.com/libp2p/js-interfaces/commit/84cd2ca)) +* **tests:** add place holder test script for releases ([8e9f7cf](https://github.com/libp2p/js-interfaces/commit/8e9f7cf)) + + +### Code Refactoring + +* API changes and switch to async await ([#55](https://github.com/libp2p/js-interfaces/issues/55)) ([dd837ba](https://github.com/libp2p/js-interfaces/commit/dd837ba)) +* API changes and switch to async iterators ([#29](https://github.com/libp2p/js-interfaces/issues/29)) ([bf5c646](https://github.com/libp2p/js-interfaces/commit/bf5c646)) + + +### Features + +* add onStreamEnd, muxer.streams and timeline ([#56](https://github.com/libp2p/js-interfaces/issues/56)) ([0f60832](https://github.com/libp2p/js-interfaces/commit/0f60832)) +* add support for timeline proxying ([#31](https://github.com/libp2p/js-interfaces/issues/31)) ([541bf83](https://github.com/libp2p/js-interfaces/commit/541bf83)) +* add type to AbortError ([#45](https://github.com/libp2p/js-interfaces/issues/45)) ([4fd37bb](https://github.com/libp2p/js-interfaces/commit/4fd37bb)) +* add upgrader support to transports ([#53](https://github.com/libp2p/js-interfaces/issues/53)) ([a5ad120](https://github.com/libp2p/js-interfaces/commit/a5ad120)) +* async crypto + sauce labs + aegir 9 ([b40114c](https://github.com/libp2p/js-interfaces/commit/b40114c)) +* callbacks -> async / await ([#44](https://github.com/libp2p/js-interfaces/issues/44)) ([b30ee5f](https://github.com/libp2p/js-interfaces/commit/b30ee5f)) +* initial commit ([584a69b](https://github.com/libp2p/js-interfaces/commit/584a69b)) +* make listen take an array of addrs ([#46](https://github.com/libp2p/js-interfaces/issues/46)) ([1dc5baa](https://github.com/libp2p/js-interfaces/commit/1dc5baa)) +* move to next aegir ([11980ac](https://github.com/libp2p/js-interfaces/commit/11980ac)) +* timeline and close checking ([#55](https://github.com/libp2p/js-interfaces/issues/55)) ([993ca1c](https://github.com/libp2p/js-interfaces/commit/993ca1c)) +* **api:** update the interface usage from dial to dialer and listen to listener ([5069679](https://github.com/libp2p/js-interfaces/commit/5069679)) +* **connection:** migrate to pull-streams ([ed5727a](https://github.com/libp2p/js-interfaces/commit/ed5727a)) +* **dialer:** remove conn from on connect callback ([1bd20d9](https://github.com/libp2p/js-interfaces/commit/1bd20d9)) +* **pull:** migration to pull streams. Upgrade tests to use mocha as ([cc3130f](https://github.com/libp2p/js-interfaces/commit/cc3130f)) +* **spec:** update the dial interface to cope with new pull additions ([2e12166](https://github.com/libp2p/js-interfaces/commit/2e12166)) +* **tests:** add closing tests, make sure errors are propagated ([c06da3b](https://github.com/libp2p/js-interfaces/commit/c06da3b)) +* **tests:** add dial and listen tests ([d50224d](https://github.com/libp2p/js-interfaces/commit/d50224d)) +* **tests:** stub test for aegir to verify ([949faf0](https://github.com/libp2p/js-interfaces/commit/949faf0)) + + +### Reverts + +* "feat: make listen take an array of addrs ([#46](https://github.com/libp2p/js-interfaces/issues/46))" ([#51](https://github.com/libp2p/js-interfaces/issues/51)) ([030195e](https://github.com/libp2p/js-interfaces/commit/030195e)) + + +### 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. +* This adds new validations to the stream muxer, which will cause existing tests to fail. +* the API is now async / await. See https://github.com/libp2p/interface-stream-muxer/pull/55#issue-275014779 for a summary of the changes. +* Transports must now be passed and use an `Upgrader` instance. See the Readme for usage. Compliance test suites will now need to pass `options` from `common.setup(options)` to their Transport constructor. + +* docs: update readme to include upgrader + +* docs: update readme to include MultiaddrConnection ref + +* feat: add upgrader spy to test suite + +* test: validate returned value of spy +* All places in the API that used callbacks are now replaced with async/await + +* test: add tests for canceling dials + +* feat: Adapter class diff --git a/packages/interfaces/LICENSE b/packages/interfaces/LICENSE new file mode 100644 index 0000000000..20ce483c86 --- /dev/null +++ b/packages/interfaces/LICENSE @@ -0,0 +1,4 @@ +This project is dual licensed under MIT and Apache-2.0. + +MIT: https://www.opensource.org/licenses/mit +Apache-2.0: https://www.apache.org/licenses/license-2.0 diff --git a/packages/interfaces/LICENSE-APACHE b/packages/interfaces/LICENSE-APACHE new file mode 100644 index 0000000000..14478a3b60 --- /dev/null +++ b/packages/interfaces/LICENSE-APACHE @@ -0,0 +1,5 @@ +Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. diff --git a/packages/interfaces/LICENSE-MIT b/packages/interfaces/LICENSE-MIT new file mode 100644 index 0000000000..72dc60d84b --- /dev/null +++ b/packages/interfaces/LICENSE-MIT @@ -0,0 +1,19 @@ +The MIT License (MIT) + +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/packages/interfaces/README.md b/packages/interfaces/README.md new file mode 100644 index 0000000000..4e17b6d5c2 --- /dev/null +++ b/packages/interfaces/README.md @@ -0,0 +1,101 @@ +# @libp2p/interfaces + +[![libp2p.io](https://img.shields.io/badge/project-libp2p-yellow.svg?style=flat-square)](http://libp2p.io/) +[![Discuss](https://img.shields.io/discourse/https/discuss.libp2p.io/posts.svg?style=flat-square)](https://discuss.libp2p.io) +[![codecov](https://img.shields.io/codecov/c/github/libp2p/js-libp2p.svg?style=flat-square)](https://codecov.io/gh/libp2p/js-libp2p) +[![CI](https://img.shields.io/github/actions/workflow/status/libp2p/js-libp2p/js-test-and-release.yml?branch=master\&style=flat-square)](https://github.com/libp2p/js-libp2p/actions/workflows/js-test-and-release.yml?query=branch%3Amaster) + +> Common code shared by the various libp2p interfaces + +## Table of contents + +- [Install](#install) +- [Usage](#usage) + - [AbortError](#aborterror) + - [Events](#events) + - [AbortOptions](#abortoptions) + - [Startable](#startable) +- [API Docs](#api-docs) +- [License](#license) +- [Contribution](#contribution) + +## Install + +```console +$ npm i @libp2p/interfaces +``` + +## Usage + +### AbortError + +Throw an error with a `.code` property of `'ABORT_ERR'`: + +```js +import { AbortError } from '@libp2p/interfaces/errors' + +throw new AbortError() +``` + +### Events + +Typed events: + +```js +import { EventEmitter, CustomEvent } from '@libp2p/interfaces/events' + +export interface MyEmitterEvents { + 'some-event': CustomEvent; +} + +class MyEmitter extends EventEmitter { + +} + +// later +const myEmitter = new MyEmitter() +myEmitter.addEventListener('some-event', (evt) => { + const num = evt.detail // <-- inferred as number +}) +``` + +### AbortOptions + +```js +import type { AbortOptions } from '@libp2p/interfaces' +``` + +### Startable + +Lifecycles for components + +```js +import { start, stop, isStartable } from '@libp2p/interfaces/startable' +import type { Startable } from '@libp2p/interfaces/startable' + +class MyStartable implements Startable { + // .. implementation methods +} + +const myStartable = new MyStartable() + +isStartable(myStartable) // returns true + +await start(myStartable) +await stop(myStartable) +``` + +## API Docs + +- + +## License + +Licensed under either of + +- Apache 2.0, ([LICENSE-APACHE](LICENSE-APACHE) / ) +- MIT ([LICENSE-MIT](LICENSE-MIT) / ) + +## Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. diff --git a/packages/interfaces/package.json b/packages/interfaces/package.json new file mode 100644 index 0000000000..d55124fae2 --- /dev/null +++ b/packages/interfaces/package.json @@ -0,0 +1,164 @@ +{ + "name": "@libp2p/interfaces", + "version": "3.3.2", + "description": "Common code shared by the various libp2p interfaces", + "license": "Apache-2.0 OR MIT", + "homepage": "https://github.com/libp2p/js-libp2p/tree/master/packages/interfaces#readme", + "repository": { + "type": "git", + "url": "git+https://github.com/libp2p/js-libp2p.git" + }, + "bugs": { + "url": "https://github.com/libp2p/js-libp2p/issues" + }, + "keywords": [ + "interface", + "libp2p" + ], + "type": "module", + "types": "./dist/src/index.d.ts", + "typesVersions": { + "*": { + "*": [ + "*", + "dist/*", + "dist/src/*", + "dist/src/*/index" + ], + "src/*": [ + "*", + "dist/*", + "dist/src/*", + "dist/src/*/index" + ] + } + }, + "files": [ + "src", + "dist", + "!dist/test", + "!**/*.tsbuildinfo" + ], + "exports": { + ".": { + "types": "./dist/src/index.d.ts", + "import": "./dist/src/index.js" + }, + "./errors": { + "types": "./dist/src/errors.d.ts", + "import": "./dist/src/errors.js" + }, + "./events": { + "types": "./dist/src/events.d.ts", + "import": "./dist/src/events.js" + }, + "./startable": { + "types": "./dist/src/startable.d.ts", + "import": "./dist/src/startable.js" + } + }, + "eslintConfig": { + "extends": "ipfs", + "parserOptions": { + "sourceType": "module" + } + }, + "release": { + "branches": [ + "master" + ], + "plugins": [ + [ + "@semantic-release/commit-analyzer", + { + "preset": "conventionalcommits", + "releaseRules": [ + { + "breaking": true, + "release": "major" + }, + { + "revert": true, + "release": "patch" + }, + { + "type": "feat", + "release": "minor" + }, + { + "type": "fix", + "release": "patch" + }, + { + "type": "docs", + "release": "patch" + }, + { + "type": "test", + "release": "patch" + }, + { + "type": "deps", + "release": "patch" + }, + { + "scope": "no-release", + "release": false + } + ] + } + ], + [ + "@semantic-release/release-notes-generator", + { + "preset": "conventionalcommits", + "presetConfig": { + "types": [ + { + "type": "feat", + "section": "Features" + }, + { + "type": "fix", + "section": "Bug Fixes" + }, + { + "type": "chore", + "section": "Trivial Changes" + }, + { + "type": "docs", + "section": "Documentation" + }, + { + "type": "deps", + "section": "Dependencies" + }, + { + "type": "test", + "section": "Tests" + } + ] + } + } + ], + "@semantic-release/changelog", + "@semantic-release/npm", + "@semantic-release/github", + "@semantic-release/git" + ] + }, + "scripts": { + "clean": "aegir clean", + "lint": "aegir lint", + "dep-check": "aegir dep-check", + "build": "aegir build", + "release": "aegir release" + }, + "devDependencies": { + "aegir": "^39.0.5" + }, + "typedoc": { + "entryPoint": "./src/index.ts" + } +} diff --git a/packages/interfaces/src/errors.ts b/packages/interfaces/src/errors.ts new file mode 100644 index 0000000000..a25be515db --- /dev/null +++ b/packages/interfaces/src/errors.ts @@ -0,0 +1,35 @@ + +/** + * When this error is thrown it means an operation was aborted, + * usually in response to the `abort` event being emitted by an + * AbortSignal. + */ +export class AbortError extends Error { + public readonly code: string + public readonly type: string + + constructor (message: string = 'The operation was aborted') { + super(message) + this.code = AbortError.code + this.type = AbortError.type + } + + static readonly code = 'ABORT_ERR' + + static readonly type = 'aborted' +} + +export class CodeError = Record> extends Error { + public readonly props: T + + constructor ( + message: string, + public readonly code: string, + props?: T + ) { + super(message) + + this.name = props?.name ?? 'CodeError' + this.props = props ?? {} as T // eslint-disable-line @typescript-eslint/consistent-type-assertions + } +} diff --git a/packages/interfaces/src/events.ts b/packages/interfaces/src/events.ts new file mode 100644 index 0000000000..b92e51879e --- /dev/null +++ b/packages/interfaces/src/events.ts @@ -0,0 +1,101 @@ + +export interface EventCallback { (evt: EventType): void } +export interface EventObject { handleEvent: EventCallback } +export type EventHandler = EventCallback | EventObject + +interface Listener { + once: boolean + callback: any +} + +/** + * Adds types to the EventTarget class. Hopefully this won't be necessary forever. + * + * https://github.com/microsoft/TypeScript/issues/28357 + * https://github.com/microsoft/TypeScript/issues/43477 + * https://github.com/microsoft/TypeScript/issues/299 + * etc + */ +export class EventEmitter> extends EventTarget { + #listeners = new Map() + + listenerCount (type: string): number { + const listeners = this.#listeners.get(type) + + if (listeners == null) { + return 0 + } + + return listeners.length + } + + addEventListener(type: K, listener: EventHandler | null, options?: boolean | AddEventListenerOptions): void + addEventListener (type: string, listener: EventHandler, options?: boolean | AddEventListenerOptions): void { + super.addEventListener(type, listener, options) + + let list = this.#listeners.get(type) + + if (list == null) { + list = [] + this.#listeners.set(type, list) + } + + list.push({ + callback: listener, + once: (options !== true && options !== false && options?.once) ?? false + }) + } + + removeEventListener(type: K, listener?: EventHandler | null, options?: boolean | EventListenerOptions): void + removeEventListener (type: string, listener?: EventHandler, options?: boolean | EventListenerOptions): void { + super.removeEventListener(type.toString(), listener ?? null, options) + + let list = this.#listeners.get(type) + + if (list == null) { + return + } + + list = list.filter(({ callback }) => callback !== listener) + this.#listeners.set(type, list) + } + + dispatchEvent (event: Event): boolean { + const result = super.dispatchEvent(event) + + let list = this.#listeners.get(event.type) + + if (list == null) { + return result + } + + list = list.filter(({ once }) => !once) + this.#listeners.set(event.type, list) + + return result + } + + safeDispatchEvent(type: keyof EventMap, detail: CustomEventInit): boolean { + return this.dispatchEvent(new CustomEvent(type as string, detail)) + } +} + +/** + * CustomEvent is a standard event but it's not supported by node. + * + * Remove this when https://github.com/nodejs/node/issues/40678 is closed. + * + * Ref: https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent + */ +class CustomEventPolyfill extends Event { + /** Returns any custom data event was created with. Typically used for synthetic events. */ + public detail: T + + constructor (message: string, data?: EventInit & { detail: T }) { + super(message, data) + // @ts-expect-error could be undefined + this.detail = data?.detail + } +} + +export const CustomEvent = globalThis.CustomEvent ?? CustomEventPolyfill diff --git a/packages/interfaces/src/index.ts b/packages/interfaces/src/index.ts new file mode 100644 index 0000000000..170a6ca762 --- /dev/null +++ b/packages/interfaces/src/index.ts @@ -0,0 +1,30 @@ + +/** + * An object that contains an AbortSignal as + * the optional `signal` property. + * + * @example + * + * ```js + * const controller = new AbortController() + * + * aLongRunningOperation({ + * signal: controller.signal + * }) + * + * // later + * + * controller.abort() + */ +export interface AbortOptions { + signal?: AbortSignal +} + +/** + * Returns a new type with all fields marked optional. + * + * Borrowed from the tsdef module. + */ +export type RecursivePartial = { + [P in keyof T]?: T[P] extends Array ? Array> : T[P] extends (...args: any[]) => any ? T[P] : RecursivePartial +} diff --git a/packages/interfaces/src/startable.ts b/packages/interfaces/src/startable.ts new file mode 100644 index 0000000000..5d9be74c9c --- /dev/null +++ b/packages/interfaces/src/startable.ts @@ -0,0 +1,117 @@ + +/** + * Implemented by components that have a lifecycle + */ +export interface Startable { + isStarted: () => boolean + + /** + * If implemented, this method will be invoked before the start method. + * + * It should not assume any other components have been started. + */ + beforeStart?: () => void | Promise + + /** + * This method will be invoked to start the component. + * + * It should not assume that any other components have been started. + */ + start: () => void | Promise + + /** + * If implemented, this method will be invoked after the start method. + * + * All other components will have had their start method invoked before this method is called. + */ + afterStart?: () => void | Promise + + /** + * If implemented, this method will be invoked before the stop method. + * + * Any other components will still be running when this method is called. + */ + beforeStop?: () => void | Promise + + /** + * This method will be invoked to stop the component. + * + * It should not assume any other components are running when it is called. + */ + stop: () => void | Promise + + /** + * If implemented, this method will be invoked after the stop method. + * + * All other components will have had their stop method invoked before this method is called. + */ + afterStop?: () => void | Promise +} + +export function isStartable (obj: any): obj is Startable { + return obj != null && typeof obj.start === 'function' && typeof obj.stop === 'function' +} + +export async function start (...objs: any[]): Promise { + const startables: Startable[] = [] + + for (const obj of objs) { + if (isStartable(obj)) { + startables.push(obj) + } + } + + await Promise.all( + startables.map(async s => { + if (s.beforeStart != null) { + await s.beforeStart() + } + }) + ) + + await Promise.all( + startables.map(async s => { + await s.start() + }) + ) + + await Promise.all( + startables.map(async s => { + if (s.afterStart != null) { + await s.afterStart() + } + }) + ) +} + +export async function stop (...objs: any[]): Promise { + const startables: Startable[] = [] + + for (const obj of objs) { + if (isStartable(obj)) { + startables.push(obj) + } + } + + await Promise.all( + startables.map(async s => { + if (s.beforeStop != null) { + await s.beforeStop() + } + }) + ) + + await Promise.all( + startables.map(async s => { + await s.stop() + }) + ) + + await Promise.all( + startables.map(async s => { + if (s.afterStop != null) { + await s.afterStop() + } + }) + ) +} diff --git a/packages/interfaces/tsconfig.json b/packages/interfaces/tsconfig.json new file mode 100644 index 0000000000..5fe8ea40d7 --- /dev/null +++ b/packages/interfaces/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "aegir/src/config/tsconfig.aegir.json", + "compilerOptions": { + "outDir": "dist" + }, + "include": [ + "src" + ] +} diff --git a/packages/libp2p/.aegir.js b/packages/libp2p/.aegir.js index d9c81e8028..21e54fde91 100644 --- a/packages/libp2p/.aegir.js +++ b/packages/libp2p/.aegir.js @@ -1,10 +1,4 @@ -import { webSockets } from '@libp2p/websockets' -import { mplex } from '@libp2p/mplex' -import { noise } from '@chainsafe/libp2p-noise' import { pipe } from 'it-pipe' -import { createEd25519PeerId } from '@libp2p/peer-id-factory' -import { yamux } from '@chainsafe/libp2p-yamux' -import { WebSockets } from '@multiformats/mafmt' /** @type {import('aegir').PartialOptions} */ export default { @@ -14,6 +8,12 @@ export default { test: { before: async () => { // use dynamic import because we only want to reference these files during the test run, e.g. after building + const { webSockets } = await import('@libp2p/websockets') + const { mplex } = await import('@libp2p/mplex') + const { noise } = await import('@chainsafe/libp2p-noise') + const { createEd25519PeerId } = await import('@libp2p/peer-id-factory') + const { yamux } = await import('@chainsafe/libp2p-yamux') + const { WebSockets } = await import('@multiformats/mafmt') const { createLibp2p } = await import('./dist/src/index.js') const { plaintext } = await import('./dist/src/insecure/index.js') const { circuitRelayServer, circuitRelayTransport } = await import('./dist/src/circuit-relay/index.js') diff --git a/packages/libp2p/package.json b/packages/libp2p/package.json index 602a2f370a..546978e727 100644 --- a/packages/libp2p/package.json +++ b/packages/libp2p/package.json @@ -3,7 +3,7 @@ "version": "0.45.9", "description": "JavaScript implementation of libp2p, a modular peer to peer network stack", "license": "Apache-2.0 OR MIT", - "homepage": "https://github.com/libp2p/js-libp2p#readme", + "homepage": "https://github.com/libp2p/js-libp2p/tree/master/packages/libp2p#readme", "repository": { "type": "git", "url": "git+https://github.com/libp2p/js-libp2p.git" @@ -19,10 +19,6 @@ "peer", "peer-to-peer" ], - "engines": { - "node": ">=16.0.0", - "npm": ">=7.0.0" - }, "type": "module", "types": "./dist/src/index.d.ts", "typesVersions": { @@ -72,10 +68,6 @@ "types": "./dist/src/insecure/index.d.ts", "import": "./dist/src/insecure/index.js" }, - "./upnp-nat": { - "types": "./dist/src/upnp-nat/index.d.ts", - "import": "./dist/src/upnp-nat/index.js" - }, "./ping": { "types": "./dist/src/ping/index.d.ts", "import": "./dist/src/ping/index.js" @@ -83,6 +75,10 @@ "./pnet": { "types": "./dist/src/pnet/index.d.ts", "import": "./dist/src/pnet/index.js" + }, + "./upnp-nat": { + "types": "./dist/src/upnp-nat/index.d.ts", + "import": "./dist/src/upnp-nat/index.js" } }, "eslintConfig": { @@ -121,29 +117,29 @@ "@achingbrain/nat-port-mapper": "^1.0.9", "@libp2p/crypto": "^1.0.17", "@libp2p/interface-address-manager": "^3.0.0", - "@libp2p/interface-connection": "^5.1.1", + "@libp2p/interface-connection": "^5.0.0", "@libp2p/interface-connection-encrypter": "^4.0.0", "@libp2p/interface-connection-gater": "^3.0.0", "@libp2p/interface-connection-manager": "^3.0.0", - "@libp2p/interface-content-routing": "^2.1.0", - "@libp2p/interface-keychain": "^2.0.4", - "@libp2p/interface-libp2p": "^3.2.0", + "@libp2p/interface-content-routing": "^2.0.0", + "@libp2p/interface-keychain": "^2.0.0", + "@libp2p/interface-libp2p": "^3.0.0", "@libp2p/interface-metrics": "^4.0.0", "@libp2p/interface-peer-discovery": "^2.0.0", - "@libp2p/interface-peer-id": "^2.0.1", - "@libp2p/interface-peer-info": "^1.0.3", - "@libp2p/interface-peer-routing": "^1.1.0", - "@libp2p/interface-peer-store": "^2.0.4", + "@libp2p/interface-peer-id": "^2.0.0", + "@libp2p/interface-peer-info": "^1.0.0", + "@libp2p/interface-peer-routing": "^1.0.0", + "@libp2p/interface-peer-store": "^2.0.0", "@libp2p/interface-pubsub": "^4.0.0", - "@libp2p/interface-record": "^2.0.6", - "@libp2p/interface-registrar": "^2.0.3", + "@libp2p/interface-record": "^2.0.0", + "@libp2p/interface-registrar": "^2.0.0", "@libp2p/interface-stream-muxer": "^4.0.0", "@libp2p/interface-transport": "^4.0.0", - "@libp2p/interfaces": "^3.2.0", + "@libp2p/interfaces": "^3.0.0", "@libp2p/keychain": "^2.0.0", "@libp2p/logger": "^2.1.1", "@libp2p/multistream-select": "^3.1.8", - "@libp2p/peer-collections": "^3.0.0", + "@libp2p/peer-collections": "^3.0.1", "@libp2p/peer-id": "^2.0.0", "@libp2p/peer-id-factory": "^2.0.0", "@libp2p/peer-record": "^5.0.0", @@ -178,7 +174,7 @@ "private-ip": "^3.0.0", "protons-runtime": "^5.0.0", "rate-limiter-flexible": "^2.3.11", - "uint8arraylist": "^2.3.2", + "uint8arraylist": "^2.4.3", "uint8arrays": "^4.0.2", "wherearewe": "^2.0.0", "xsalsa20": "^1.1.0" @@ -191,8 +187,8 @@ "@libp2p/daemon-client": "^6.0.2", "@libp2p/daemon-server": "^5.0.2", "@libp2p/floodsub": "^7.0.1", - "@libp2p/interface-compliance-tests": "^3.0.6", - "@libp2p/interface-connection-compliance-tests": "^2.0.8", + "@libp2p/interface-compliance-tests": "^3.0.0", + "@libp2p/interface-connection-compliance-tests": "^2.0.0", "@libp2p/interface-connection-encrypter-compliance-tests": "^5.0.0", "@libp2p/interface-mocks": "^12.0.0", "@libp2p/interop": "^8.0.0", diff --git a/packages/libp2p/tsconfig.json b/packages/libp2p/tsconfig.json index 13a3599639..9ac0d9429c 100644 --- a/packages/libp2p/tsconfig.json +++ b/packages/libp2p/tsconfig.json @@ -6,5 +6,79 @@ "include": [ "src", "test" + ], + "references": [ + { + "path": "../interface-address-manager" + }, + { + "path": "../interface-compliance-tests" + }, + { + "path": "../interface-connection" + }, + { + "path": "../interface-connection-compliance-tests" + }, + { + "path": "../interface-connection-encrypter" + }, + { + "path": "../interface-connection-encrypter-compliance-tests" + }, + { + "path": "../interface-connection-gater" + }, + { + "path": "../interface-connection-manager" + }, + { + "path": "../interface-content-routing" + }, + { + "path": "../interface-keychain" + }, + { + "path": "../interface-libp2p" + }, + { + "path": "../interface-metrics" + }, + { + "path": "../interface-mocks" + }, + { + "path": "../interface-peer-discovery" + }, + { + "path": "../interface-peer-id" + }, + { + "path": "../interface-peer-info" + }, + { + "path": "../interface-peer-routing" + }, + { + "path": "../interface-peer-store" + }, + { + "path": "../interface-pubsub" + }, + { + "path": "../interface-record" + }, + { + "path": "../interface-registrar" + }, + { + "path": "../interface-stream-muxer" + }, + { + "path": "../interface-transport" + }, + { + "path": "../interfaces" + } ] }