├── Procfile
├── .gitignore
├── Dockerfile
├── src
├── config.js
├── bin.js
├── index.js
├── index.html
├── utils.js
└── routes.js
├── .travis.yml
├── DEPLOYMENT.md
├── LICENSE
├── package.json
├── README.md
├── CHANGELOG.md
└── test
└── rendezvous.spec.js
/Procfile:
--------------------------------------------------------------------------------
1 | web: npm run start
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | package-lock.json
2 | yarn.lock
3 |
4 | **/node_modules
5 | **/*.log
6 | test/setup/tmp-disposable-nodes-addrs.json
7 | dist
8 | coverage
9 | .nyc_output
10 | **/*.swp
11 | **/*.bak
12 | examples/sub-module/**/bundle.js
13 | examples/sub-module/**/*-minified.js
14 | examples/sub-module/*-bundle.js
15 | docs
16 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:10
2 | RUN wget -O /usr/local/bin/dumb-init https://github.com/Yelp/dumb-init/releases/download/v1.2.0/dumb-init_1.2.0_amd64 && chmod +x /usr/local/bin/dumb-init
3 | WORKDIR /usr/src/app
4 | COPY package.json .
5 | RUN npm i --production
6 | COPY . .
7 | ENTRYPOINT ["/usr/local/bin/dumb-init", "node", "--max-old-space-size=8192", "src/bin.js"]
8 |
--------------------------------------------------------------------------------
/src/config.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const debug = require('debug')
4 | const log = debug('libp2p-websocket-star-rendezvous')
5 | log.error = debug('libp2p-websocket-star-rendezvous:error')
6 |
7 | module.exports = {
8 | log: log,
9 | hapi: {
10 | port: process.env.PORT || 13579,
11 | host: '0.0.0.0',
12 | options: {
13 | routes: {
14 | cors: true
15 | }
16 | }
17 | },
18 | refreshPeerListIntervalMS: 10000,
19 | cryptoChallenge: true,
20 | strictMultiaddr: false,
21 | metrics: false
22 | }
23 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | cache: npm
3 | stages:
4 | - check
5 | - test
6 | - cov
7 |
8 | node_js:
9 | - '12'
10 | - '10'
11 |
12 | os:
13 | - linux
14 | - osx
15 | - windows
16 |
17 | script: npx nyc -s npm run test:node -- --bail
18 | after_success: npx nyc report --reporter=text-lcov > coverage.lcov && npx codecov
19 |
20 | jobs:
21 | include:
22 | - stage: check
23 | script:
24 | - npx aegir dep-check -- -i wrtc -i electron-webrtc
25 | - npm run lint
26 |
27 | notifications:
28 | email: false
29 |
--------------------------------------------------------------------------------
/DEPLOYMENT.md:
--------------------------------------------------------------------------------
1 | # Deployment
2 |
3 | ## IPFS Infra
4 |
5 | We have a [dokku](https://github.com/ipfs/ops-requests/issues/31) setup ready for this to be deployed, to deploy simple do (you have to have permission first):
6 |
7 | ```sh
8 | # if you already have added the remote, you don't need to do it again
9 | > git remote add dokku dokku@cloud.ipfs.team:ws-star
10 | > git push dokku master
11 | ```
12 |
13 | More info: https://github.com/libp2p/js-libp2p-webrtc-star/pull/48
14 |
15 | ## Other
16 |
17 | # mkg20001
18 | The nodes `ws-star-signal-{2,4,h}.servep2p.com` run on `host0.zion.host`
19 |
20 | Upgrades are done by running `bash /home/maciej/upgrade-rendezvous.sh` which runs docker pull and re-creates the containers
21 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 libp2p
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/src/bin.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | 'use strict'
4 |
5 | const signalling = require('./index')
6 | const argv = require('minimist')(process.argv.slice(2))
7 |
8 | /* eslint-disable no-console */
9 |
10 | async function start () {
11 | const server = await signalling.start({
12 | port: argv.port || argv.p || process.env.PORT || 9090,
13 | host: argv.host || argv.h || process.env.HOST || '0.0.0.0',
14 | key: argv.key || process.env.KEY,
15 | cert: argv.cert || process.env.CERT,
16 | pfx: argv.pfx || process.env.PFX,
17 | passphrase: argv.passphrase || process.env.PFX_PASSPHRASE,
18 | cryptoChallenge: !(argv.disableCryptoChallenge || process.env.DISABLE_CRYPTO_CHALLENGE),
19 | strictMultiaddr: !(argv.disableStrictMultiaddr || process.env.DISABLE_STRICT_MULTIADDR),
20 | metrics: !(argv.disableMetrics || process.env.DISABLE_METRICS)
21 | })
22 |
23 | console.log('Listening on:', server.info.uri)
24 |
25 | process.on('SIGINT', async () => {
26 | try {
27 | await server.stop()
28 | } catch (err) {
29 | console.error(err)
30 | process.exit(2)
31 | }
32 |
33 | console.log('Rendezvous server stopped')
34 | process.exit(0)
35 | })
36 | }
37 |
38 | start()
39 | .catch((err) => {
40 | console.error(err)
41 | process.exit(2)
42 | })
43 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const Hapi = require('@hapi/hapi')
4 | const path = require('path')
5 | const menoetius = require('menoetius')
6 | const merge = require('merge-recursive').recursive
7 | const Inert = require('@hapi/inert')
8 | const { readFileSync } = require('fs')
9 |
10 | exports = module.exports
11 |
12 | exports.start = async (options = {}) => {
13 | const config = merge(Object.assign({}, require('./config')), Object.assign({}, options))
14 | const log = config.log
15 |
16 | const port = options.port || config.hapi.port
17 | const host = options.host || config.hapi.host
18 |
19 | let tls
20 | if (options.key && options.cert) {
21 | tls = {
22 | key: readFileSync(options.key),
23 | cert: readFileSync(options.cert),
24 | passphrase: options.passphrase
25 | }
26 | } else if (options.pfx) {
27 | tls = {
28 | pfx: readFileSync(options.pfx),
29 | passphrase: options.passphrase
30 | }
31 | }
32 |
33 | const http = new Hapi.Server(Object.assign({
34 | port,
35 | host,
36 | tls
37 | }, config.hapi.options))
38 |
39 | await http.register(Inert)
40 | await http.start()
41 |
42 | log('rendezvous server has started on: ' + http.info.uri)
43 |
44 | http.peers = require('./routes')(config, http).peers
45 |
46 | http.route({
47 | method: 'GET',
48 | path: '/',
49 | handler: (request, reply) => reply.file(path.join(__dirname, 'index.html'), {
50 | confine: false
51 | })
52 | })
53 |
54 | if (config.metrics) {
55 | menoetius.instrument(http)
56 | }
57 |
58 | return http
59 | }
60 |
--------------------------------------------------------------------------------
/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Signalling Server
8 |
9 |
20 |
21 |
22 |
23 |
24 |
25 |
26 | This is a libp2p-websocket-star signalling-server
27 | Signaling Servers are used in libp2p to allow browsers and clients with restricted port-forwarding to communicate with other peers in the libp2p network
28 |
29 | » Learn more
30 |
31 |
32 |
33 |
62 |
63 |
64 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "libp2p-websocket-star-rendezvous",
3 | "version": "0.4.1",
4 | "description": "The rendezvous service for libp2p-websocket-star",
5 | "leadMaintainer": "Jacob Heun ",
6 | "main": "src/index.js",
7 | "files": [
8 | "dist",
9 | "src"
10 | ],
11 | "bin": {
12 | "rendezvous": "src/bin.js",
13 | "websocket-star": "src/bin.js"
14 | },
15 | "scripts": {
16 | "start": "node src/bin.js",
17 | "lint": "aegir lint",
18 | "build": "aegir build",
19 | "test": "aegir test -t node",
20 | "test:node": "aegir test -t node",
21 | "release": "aegir release -t node",
22 | "release-minor": "aegir release --type minor -t node",
23 | "release-major": "aegir release --type major -t node",
24 | "coverage": "aegir coverage",
25 | "coverage-publish": "aegir coverage --provider coveralls"
26 | },
27 | "keywords": [
28 | "libp2p",
29 | "websocket"
30 | ],
31 | "license": "MIT",
32 | "dependencies": {
33 | "data-queue": "0.0.3",
34 | "debug": "^4.1.1",
35 | "@hapi/hapi": "^18.3.1",
36 | "@hapi/inert": "^5.2.1",
37 | "libp2p-crypto": "~0.17.0",
38 | "mafmt": "^6.0.7",
39 | "menoetius": "~0.0.2",
40 | "merge-recursive": "0.0.3",
41 | "minimist": "^1.2.0",
42 | "multiaddr": "^6.1.0",
43 | "once": "^1.4.0",
44 | "peer-id": "~0.13.2",
45 | "peer-info": "~0.16.0",
46 | "prom-client": "^11.5.3",
47 | "socket.io": "^2.2.0",
48 | "socket.io-client": "^2.2.0",
49 | "socket.io-pull-stream": "^0.1.5",
50 | "uuid": "^3.1.0"
51 | },
52 | "directories": {
53 | "test": "test"
54 | },
55 | "devDependencies": {
56 | "aegir": "^20.0.0",
57 | "chai": "^4.2.0",
58 | "dirty-chai": "^2.0.1",
59 | "lodash": "^4.17.11"
60 | },
61 | "repository": {
62 | "type": "git",
63 | "url": "git+https://github.com/libp2p/js-libp2p-websocket-star-rendezvous.git"
64 | },
65 | "bugs": {
66 | "url": "https://github.com/libp2p/js-libp2p-websocket-star-rendezvous/issues"
67 | },
68 | "homepage": "https://github.com/libp2p/js-libp2p-websocket-star-rendezvous#readme",
69 | "contributors": [
70 | "David Dias ",
71 | "Dirk McCormick ",
72 | "Haad ",
73 | "Jacob Heun ",
74 | "Jim Pick ",
75 | "Justin Maier ",
76 | "LEE JAE HO ",
77 | "Maciej Krüger ",
78 | "Vasco Santos ",
79 | "Victor Bjelkholm ",
80 | "achingbrain "
81 | ]
82 | }
83 |
--------------------------------------------------------------------------------
/src/utils.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const multiaddr = require('multiaddr')
4 | const Id = require('peer-id')
5 | const crypto = require('libp2p-crypto')
6 | const mafmt = require('mafmt')
7 |
8 | function isIP (ma) {
9 | const protos = ma.protos()
10 |
11 | if (protos[0].code !== 4 && protos[0].code !== 41) {
12 | return false
13 | }
14 | if (protos[1].code !== 6 && protos[1].code !== 17) {
15 | return false
16 | }
17 |
18 | return true
19 | }
20 |
21 | function cleanUrlSIO (ma) {
22 | const maStrSplit = ma.toString().split('/')
23 |
24 | if (isIP(ma)) {
25 | if (maStrSplit[1] === 'ip4') {
26 | return 'http://' + maStrSplit[2] + ':' + maStrSplit[4]
27 | } else if (maStrSplit[1] === 'ip6') {
28 | return 'http://[' + maStrSplit[2] + ']:' + maStrSplit[4]
29 | } else {
30 | throw new Error('invalid multiaddr: ' + ma.toString())
31 | }
32 | } else if (multiaddr.isName(ma)) {
33 | const wsProto = ma.protos()[1].name
34 | if (wsProto === 'ws') {
35 | return 'http://' + maStrSplit[2]
36 | } else if (wsProto === 'wss') {
37 | return 'https://' + maStrSplit[2]
38 | } else {
39 | throw new Error('invalid multiaddr: ' + ma.toString())
40 | }
41 | } else {
42 | throw new Error('invalid multiaddr: ' + ma.toString())
43 | }
44 | }
45 |
46 | const types = {
47 | string: (v) => (typeof v === 'string'),
48 | object: (v) => (typeof v === 'object'),
49 | multiaddr: (v) => {
50 | if (!types.string(v)) { return }
51 |
52 | try {
53 | multiaddr(v)
54 | return true
55 | } catch (err) {
56 | return false
57 | }
58 | },
59 | function: (v) => (typeof v === 'function')
60 | }
61 |
62 | function validate (def, data) {
63 | if (!Array.isArray(data)) throw new Error('Data is not an array')
64 | def.forEach((type, index) => {
65 | if (!types[type]) {
66 | throw new Error('Type ' + type + ' does not exist')
67 | }
68 |
69 | if (!types[type](data[index])) {
70 | throw new Error('Data at index ' + index + ' is invalid for type ' + type)
71 | }
72 | })
73 | }
74 |
75 | function Protocol (log) {
76 | log = log || function noop () {}
77 |
78 | this.requests = {}
79 | this.addRequest = (name, def, handle) => {
80 | this.requests[name] = { def: def, handle: handle }
81 | }
82 | this.handleSocket = (socket) => {
83 | socket.r = {}
84 | for (const request in this.requests) {
85 | if (Object.prototype.hasOwnProperty.call(this.requests, request)) {
86 | const r = this.requests[request]
87 | socket.on(request, function () {
88 | const data = [...arguments]
89 | try {
90 | validate(r.def, data)
91 | data.unshift(socket)
92 | r.handle.apply(null, data)
93 | } catch (err) {
94 | log(err)
95 | log('peer %s has sent invalid data for request %s', socket.id || '', request, data)
96 | }
97 | })
98 | }
99 | }
100 | }
101 | }
102 |
103 | async function getIdAndValidate (pub, id) {
104 | const _id = await Id.createFromPubKey(Buffer.from(pub, 'hex'))
105 | if (_id.toB58String() !== id) {
106 | throw Error('Id is not matching')
107 | }
108 |
109 | return crypto.keys.unmarshalPublicKey(Buffer.from(pub, 'hex'))
110 | }
111 |
112 | exports = module.exports
113 | exports.cleanUrlSIO = cleanUrlSIO
114 | exports.validate = validate
115 | exports.Protocol = Protocol
116 | exports.getIdAndValidate = getIdAndValidate
117 | exports.validateMa = (ma) => mafmt.WebSocketStar.matches(multiaddr(ma))
118 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ⛔️ DEPRECATED: libp2p-websocket-star-rendezvous is not supported anymore from [libp2p@0.27.0](https://github.com/libp2p/js-libp2p/releases/tag/v0.27.0). Check [js-libp2p/doc/CONFIGURATION.md](https://github.com/libp2p/js-libp2p/blob/master/doc/CONFIGURATION.md) for what modules are currently supported.
2 |
3 | # libp2p-websocket-star-rendezvous
4 |
5 | [](http://ipn.io)
6 | [](http://libp2p.io/)
7 | [](http://webchat.freenode.net/?channels=%23libp2p)
8 | [](https://discuss.libp2p.io)
9 | [](https://codecov.io/gh/libp2p/js-libp2p-websocket-star-rendezvous)
10 | [](https://travis-ci.com/libp2p/js-libp2p-websocket-star-rendezvous)
11 | [](https://david-dm.org/libp2p/js-libp2p-websocket-star-rendezvous)
12 | [](https://github.com/feross/standard)
13 |
14 | > The rendezvous service for [libp2p-websocket-star](https://github.com/libp2p/js-libp2p-websocket-star).
15 |
16 | ## Lead Maintainer
17 |
18 | [Jacob Heun](https://github.com/jacobheun)
19 |
20 | ## Descriptions
21 |
22 | Nodes using `libp2p-websocket-star` will connect to a known point in the network, a rendezvous point where they can learn about other nodes (Discovery) and route their messages to other nodes (2 hop message routing, also known as relay).
23 |
24 | ## Usage
25 |
26 | `libp2p-websocket-star-rendezvous` is the rendezvous server required for `libp2p-websocket-star` and can be used to start a rendezvous server for development. To do that, first install the module globally in your machine with:
27 |
28 | ```bash
29 | > npm install --global libp2p-websocket-star-rendezvous
30 | ```
31 |
32 | This will install a `rendezvous` CLI tool. Now you can spawn the server with:
33 |
34 | ```bash
35 | > rendezvous --port=9090 --host=127.0.0.1
36 | ```
37 |
38 | Defaults:
39 |
40 | - `port` - 9090
41 | - `host` - '0.0.0.0'
42 |
43 | ## Docker
44 |
45 | A docker image is offered for running this service in production
46 |
47 | ```
48 | docker pull libp2p/websocket-star-rendezvous:release
49 | docker run -d -p 9090:9090 --name rendezvous libp2p/websocket-star-rendezvous:release
50 | ```
51 |
52 | To disable prometheus metrics run the server with `-e DISABLE_METRICS=1`
53 |
54 | ```
55 | docker run -d -p 9090:9090 --name rendezvous -e DISABLE_METRICS=1 libp2p/websocket-star-rendezvous:release
56 | ```
57 |
58 | ## Hosted Rendezvous server
59 |
60 | We host a rendezvous server at `ws-star.discovery.libp2p.io` that can be used for practical demos and experimentation, it **should not be used for apps in production**.
61 |
62 | A libp2p-websocket-star address, using the signalling server we provide, looks like:
63 |
64 | `/dns4/ws-star.discovery.libp2p.io/wss/p2p-websocket-star/ipfs/`
65 |
66 | Note: The address above indicates WebSockets Secure, which can be accessed from both http and https.
67 |
68 |
69 | ### Using WSS
70 |
71 | To be able to interact with a rendezvous server from an HTTPS site, you will need to use websocket secure. To host a secure websocket server, you must provide a keypair to the server.
72 |
73 | #### Using key and certificate
74 |
75 | ```bash
76 | > rendezvous --key="path/to/key.key" --cert="path/to/cert.cert"
77 | ```
78 |
79 | #### Using PFX with passphrase
80 |
81 | ```bash
82 | > rendezvous --pfx="path/to/pair.pfx" --passphrase="passphrase"
83 | ```
84 |
85 |
86 | ### This module uses `pull-streams`
87 |
88 | We expose a streaming interface based on `pull-streams`, rather then on the Node.js core streams implementation (aka Node.js streams). `pull-streams` offers us a better mechanism for error handling and flow control guarantees. If you would like to know more about why we did this, see the discussion at this [issue](https://github.com/ipfs/js-ipfs/issues/362).
89 |
90 | You can learn more about pull-streams at:
91 |
92 | - [The history of Node.js streams, nodebp April 2014](https://www.youtube.com/watch?v=g5ewQEuXjsQ)
93 | - [The history of streams, 2016](http://dominictarr.com/post/145135293917/history-of-streams)
94 | - [pull-streams, the simple streaming primitive](http://dominictarr.com/post/149248845122/pull-streams-pull-streams-are-a-very-simple)
95 | - [pull-streams documentation](https://pull-stream.github.io/)
96 |
97 | #### Converting `pull-streams` to Node.js Streams
98 |
99 | If you are a Node.js streams user, you can convert a pull-stream to a Node.js stream using the module [`pull-stream-to-stream`](https://github.com/pull-stream/pull-stream-to-stream), giving you an instance of a Node.js stream that is linked to the pull-stream. For example:
100 |
101 | ```js
102 | const pullToStream = require('pull-stream-to-stream')
103 |
104 | const nodeStreamInstance = pullToStream(pullStreamInstance)
105 | // nodeStreamInstance is an instance of a Node.js Stream
106 | ```
107 |
108 | To learn more about this utility, visit https://pull-stream.github.io/#pull-stream-to-stream.
109 |
110 | LICENSE MIT
111 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 |
2 | ## [0.4.1](https://github.com/libp2p/js-libp2p-websocket-star-rendezvous/compare/v0.4.0...v0.4.1) (2019-08-02)
3 |
4 |
5 | ### Bug Fixes
6 |
7 | * crypto challenge stall due to callback usage of async/await ([#37](https://github.com/libp2p/js-libp2p-websocket-star-rendezvous/issues/37)) ([2647595](https://github.com/libp2p/js-libp2p-websocket-star-rendezvous/commit/2647595))
8 |
9 |
10 |
11 |
12 | # [0.4.0](https://github.com/libp2p/js-libp2p-websocket-star-rendezvous/compare/v0.3.0...v0.4.0) (2019-07-19)
13 |
14 |
15 | ### Features
16 |
17 | * switches to async/await and upgrade hapi to v18 ([946e8a1](https://github.com/libp2p/js-libp2p-websocket-star-rendezvous/commit/946e8a1))
18 |
19 |
20 | ### BREAKING CHANGES
21 |
22 | * All functions that took callbacks now return promises
23 |
24 |
25 |
26 |
27 | # [0.3.0](https://github.com/libp2p/js-libp2p-websocket-star-rendezvous/compare/v0.2.4...v0.3.0) (2018-11-29)
28 |
29 |
30 | ### Bug Fixes
31 |
32 | * dont use 'this' in root anon function ([c6a833e](https://github.com/libp2p/js-libp2p-websocket-star-rendezvous/commit/c6a833e))
33 | * logo was broken on main page ([#25](https://github.com/libp2p/js-libp2p-websocket-star-rendezvous/issues/25)) ([41eed04](https://github.com/libp2p/js-libp2p-websocket-star-rendezvous/commit/41eed04))
34 | * regex bug for ipv4 test ([#24](https://github.com/libp2p/js-libp2p-websocket-star-rendezvous/issues/24)) ([696ed92](https://github.com/libp2p/js-libp2p-websocket-star-rendezvous/commit/696ed92))
35 | * remove warning for too many listeners on socket.io sockets ([#28](https://github.com/libp2p/js-libp2p-websocket-star-rendezvous/issues/28)) ([3d9b96e](https://github.com/libp2p/js-libp2p-websocket-star-rendezvous/commit/3d9b96e))
36 |
37 |
38 | ### Features
39 |
40 | * include existing peers in response to ss-join ([f12aea3](https://github.com/libp2p/js-libp2p-websocket-star-rendezvous/commit/f12aea3))
41 | * use node 10 in docker image ([#26](https://github.com/libp2p/js-libp2p-websocket-star-rendezvous/issues/26)) ([91db9cf](https://github.com/libp2p/js-libp2p-websocket-star-rendezvous/commit/91db9cf))
42 |
43 |
44 |
45 |
46 | ## [0.2.4](https://github.com/libp2p/js-libp2p-websocket-star-rendezvous/compare/v0.2.3...v0.2.4) (2018-10-16)
47 |
48 |
49 | ### Bug Fixes
50 |
51 | * give crypto.verify a buffer ([#23](https://github.com/libp2p/js-libp2p-websocket-star-rendezvous/issues/23)) ([0c8c290](https://github.com/libp2p/js-libp2p-websocket-star-rendezvous/commit/0c8c290))
52 | * make it executable available through websocket-star ([d18087c](https://github.com/libp2p/js-libp2p-websocket-star-rendezvous/commit/d18087c))
53 |
54 |
55 |
56 |
57 | ## [0.2.3](https://github.com/libp2p/js-libp2p-websocket-star-rendezvous/compare/v0.2.2...v0.2.3) (2018-02-12)
58 |
59 |
60 |
61 |
62 | ## [0.2.2](https://github.com/libp2p/js-libp2p-websocket-star-rendezvous/compare/v0.2.1...v0.2.2) (2017-12-07)
63 |
64 |
65 | ### Features
66 |
67 | * Add libp2p logo to about page ([66be194](https://github.com/libp2p/js-libp2p-websocket-star-rendezvous/commit/66be194))
68 | * cryptoChallenge can be enabled by default after all! https://github.com/ipfs/js-ipfs/pull/1090/files\#r153143252 ([143a0a4](https://github.com/libp2p/js-libp2p-websocket-star-rendezvous/commit/143a0a4))
69 |
70 |
71 |
72 |
73 | ## [0.2.1](https://github.com/libp2p/js-libp2p-websocket-star-rendezvous/compare/v0.2.0...v0.2.1) (2017-11-19)
74 |
75 |
76 | ### Bug Fixes
77 |
78 | * Docker cmd - feat: Disable metrics option ([21f95d2](https://github.com/libp2p/js-libp2p-websocket-star-rendezvous/commit/21f95d2))
79 | * release command ([37e5b1f](https://github.com/libp2p/js-libp2p-websocket-star-rendezvous/commit/37e5b1f))
80 |
81 |
82 |
83 |
84 | # [0.2.0](https://github.com/libp2p/js-libp2p-websocket-star-rendezvous/compare/v0.1.2...v0.2.0) (2017-10-28)
85 |
86 |
87 | ### Bug Fixes
88 |
89 | * {webrtc => websocket} ([df53c25](https://github.com/libp2p/js-libp2p-websocket-star-rendezvous/commit/df53c25))
90 | * discovery fix - fix: debug log name ([1f163b8](https://github.com/libp2p/js-libp2p-websocket-star-rendezvous/commit/1f163b8))
91 | * lint ([585525e](https://github.com/libp2p/js-libp2p-websocket-star-rendezvous/commit/585525e))
92 | * package.json ([f5e91fe](https://github.com/libp2p/js-libp2p-websocket-star-rendezvous/commit/f5e91fe))
93 | * small name fix ([de84807](https://github.com/libp2p/js-libp2p-websocket-star-rendezvous/commit/de84807))
94 |
95 |
96 | ### Features
97 |
98 | * Joins metric - fix: config ([81c8eb7](https://github.com/libp2p/js-libp2p-websocket-star-rendezvous/commit/81c8eb7))
99 | * Link directly to readme in about page ([d7fba03](https://github.com/libp2p/js-libp2p-websocket-star-rendezvous/commit/d7fba03))
100 | * metrics (WIP) - feat: Dockerfile - fix/feat: various other things ([fa518b1](https://github.com/libp2p/js-libp2p-websocket-star-rendezvous/commit/fa518b1))
101 | * Update README - feat: Use dumb-init in docker-image ([4fbed33](https://github.com/libp2p/js-libp2p-websocket-star-rendezvous/commit/4fbed33))
102 |
103 |
104 |
105 |
106 | ## [0.1.2](https://github.com/libp2p/js-libp2p-websocket-star-rendezvous/compare/v0.1.1...v0.1.2) (2017-09-08)
107 |
108 |
109 | ### Bug Fixes
110 |
111 | * point to right location of bin ([3049ca8](https://github.com/libp2p/js-libp2p-websocket-star-rendezvous/commit/3049ca8))
112 |
113 |
114 |
115 |
116 | ## [0.1.1](https://github.com/libp2p/js-libp2p-websocket-star-rendezvous/compare/v0.1.0...v0.1.1) (2017-09-08)
117 |
118 |
119 | ### Bug Fixes
120 |
121 | * add main to package.json ([7ff704c](https://github.com/libp2p/js-libp2p-websocket-star-rendezvous/commit/7ff704c))
122 |
123 |
124 |
125 |
126 | # 0.1.0 (2017-09-08)
127 |
128 |
129 |
130 |
--------------------------------------------------------------------------------
/test/rendezvous.spec.js:
--------------------------------------------------------------------------------
1 | /* eslint-env mocha */
2 | 'use strict'
3 |
4 | const chai = require('chai')
5 | const dirtyChai = require('dirty-chai')
6 | const expect = chai.expect
7 | chai.use(dirtyChai)
8 | const io = require('socket.io-client')
9 | const multiaddr = require('multiaddr')
10 | const uuid = require('uuid')
11 |
12 | const rendezvous = require('../src')
13 |
14 | describe('rendezvous', () => {
15 | it('start and stop signalling server (default port)', async () => {
16 | const server = await rendezvous.start()
17 |
18 | expect(server.info.port).to.equal(13579)
19 | expect(server.info.protocol).to.equal('http')
20 | expect(server.info.address).to.equal('0.0.0.0')
21 |
22 | await server.stop()
23 | })
24 |
25 | it('start and stop signalling server (custom port)', async () => {
26 | const options = {
27 | port: 12345
28 | }
29 |
30 | const server = await rendezvous.start(options)
31 |
32 | expect(server.info.port).to.equal(options.port)
33 | expect(server.info.protocol).to.equal('http')
34 | expect(server.info.address).to.equal('0.0.0.0')
35 |
36 | await server.stop()
37 | })
38 | })
39 |
40 | describe('signalling server client', () => {
41 | const sioOptions = {
42 | transports: ['websocket'],
43 | 'force new connection': true
44 | }
45 |
46 | let sioUrl
47 | let r
48 | let c1
49 | let c2
50 | let c3
51 | let c4
52 |
53 | const c1mh = multiaddr('/ip4/127.0.0.1/tcp/9090/ws/p2p-websocket-star/ipfs/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSoooo1')
54 | const c2mh = multiaddr('/ip4/127.0.0.1/tcp/9090/ws/p2p-websocket-star/ipfs/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSoooo2')
55 | const c3mh = multiaddr('/ip4/127.0.0.1/tcp/9090/ws/p2p-websocket-star/ipfs/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSoooo3')
56 | const c4mh = multiaddr('/ip4/127.0.0.1/tcp/9090/ws/p2p-websocket-star/ipfs/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSoooo4')
57 |
58 | before(async () => {
59 | const options = {
60 | port: 12345,
61 | refreshPeerListIntervalMS: 1000,
62 | cryptoChallenge: false,
63 | strictMultiaddr: false,
64 | metrics: true
65 | }
66 |
67 | const server = await rendezvous.start(options)
68 |
69 | expect(server.info.port).to.equal(12345)
70 | expect(server.info.protocol).to.equal('http')
71 | expect(server.info.address).to.equal('0.0.0.0')
72 | sioUrl = server.info.uri
73 | r = server
74 | })
75 |
76 | after(async () => {
77 | if (c1) {
78 | c1.disconnect()
79 | }
80 |
81 | if (c2) {
82 | c2.disconnect()
83 | }
84 |
85 | if (r) {
86 | await r.stop()
87 | }
88 | })
89 |
90 | it('zero peers', () => {
91 | expect(Object.keys(r.peers).length).to.equal(0)
92 | })
93 |
94 | it('connect one client', (done) => {
95 | c1 = io.connect(sioUrl, sioOptions)
96 | c1.on('connect', done)
97 | })
98 |
99 | it('connect three more clients', (done) => {
100 | let count = 0
101 |
102 | c2 = io.connect(sioUrl, sioOptions)
103 | c3 = io.connect(sioUrl, sioOptions)
104 | c4 = io.connect(sioUrl, sioOptions)
105 |
106 | c2.on('connect', connected)
107 | c3.on('connect', connected)
108 | c4.on('connect', connected)
109 |
110 | function connected () {
111 | if (++count === 3) {
112 | done()
113 | }
114 | }
115 | })
116 |
117 | it('ss-join first client', (done) => {
118 | c1.emit('ss-join', c1mh.toString(), '', (err, sig, peers) => {
119 | expect(err).to.not.exist()
120 | expect(peers).to.eql([])
121 | expect(Object.keys(r.peers()).length).to.equal(1)
122 | done()
123 | })
124 | })
125 |
126 | it('ss-join and ss-leave second client', (done) => {
127 | let c1WsPeerEvent
128 | c1.once('ws-peer', (p) => {
129 | c1WsPeerEvent = p
130 | })
131 |
132 | c2.emit('ss-join', c2mh.toString(), '', (err, sig, peers) => {
133 | expect(err).to.not.exist()
134 | expect(peers).to.eql([c1mh.toString()])
135 | expect(c1WsPeerEvent).to.equal(c2mh.toString())
136 | expect(Object.keys(r.peers()).length).to.equal(2)
137 | c2.emit('ss-leave', c2mh.toString())
138 |
139 | setTimeout(() => {
140 | expect(Object.keys(r.peers()).length).to.equal(1)
141 | done()
142 | }, 10)
143 | })
144 | })
145 |
146 | it('ss-join and disconnect third client', (done) => {
147 | c3.emit('ss-join', c3mh.toString(), '', (err, sig, peers) => {
148 | expect(err).to.not.exist()
149 | expect(peers).to.eql([c1mh.toString()])
150 | expect(Object.keys(r.peers()).length).to.equal(2)
151 | c3.disconnect()
152 | setTimeout(() => {
153 | expect(Object.keys(r.peers()).length).to.equal(1)
154 | done()
155 | }, 10)
156 | })
157 | })
158 |
159 | it('ss-join the fourth', (done) => {
160 | c1.once('ws-peer', (multiaddr) => {
161 | expect(multiaddr).to.equal(c4mh.toString())
162 | expect(Object.keys(r.peers()).length).to.equal(2)
163 | done()
164 | })
165 | c4.emit('ss-join', c4mh.toString(), '', () => {})
166 | })
167 |
168 | it('c1 dial c4', done => {
169 | const dialId = uuid()
170 | c4.once('ss-incomming', (dialId, dialFrom, cb) => {
171 | expect(dialId).to.eql(dialId)
172 | expect(dialFrom).to.eql(c1mh.toString())
173 | cb()
174 | })
175 | c1.emit('ss-dial', c1mh.toString(), c4mh.toString(), dialId, err => {
176 | expect(err).to.not.exist()
177 | done()
178 | })
179 | })
180 |
181 | it('c1 dial c2 fail (does not exist() anymore)', done => {
182 | const dialId = uuid()
183 | c1.emit('ss-dial', c1mh.toString(), c2mh.toString(), dialId, err => {
184 | expect(err).to.exist()
185 | done()
186 | })
187 | })
188 |
189 | it('disconnects every client', (done) => {
190 | [c1, c2, c3, c4].forEach((c) => c.disconnect())
191 | done()
192 | })
193 |
194 | it('emits ws-peer every second', (done) => {
195 | let peersEmitted = 0
196 |
197 | c1 = io.connect(sioUrl, sioOptions)
198 | c2 = io.connect(sioUrl, sioOptions)
199 | c1.emit('ss-join', '/ip4/0.0.0.0', '', err => expect(err).to.not.exist())
200 | c2.emit('ss-join', '/ip4/127.0.0.1', '', err => expect(err).to.not.exist())
201 |
202 | c1.on('ws-peer', (p) => {
203 | expect(p).to.be.equal('/ip4/127.0.0.1')
204 | check()
205 | })
206 |
207 | function check () {
208 | if (++peersEmitted === 2) {
209 | done()
210 | }
211 | }
212 | }).timeout(4000)
213 | })
214 |
--------------------------------------------------------------------------------
/src/routes.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | /* eslint-disable standard/no-callback-literal */
4 | // Needed because JSON.stringify(Error) returns "{}"
5 |
6 | const SocketIO = require('socket.io')
7 | const sp = require('socket.io-pull-stream')
8 | const util = require('./utils')
9 | const uuid = require('uuid')
10 | const client = require('prom-client')
11 | const fake = {
12 | gauge: {
13 | set: () => {}
14 | },
15 | counter: {
16 | inc: () => {}
17 | }
18 | }
19 |
20 | module.exports = (config, http) => {
21 | const log = config.log
22 | const io = new SocketIO(http.listener)
23 | const proto = new util.Protocol(log)
24 | const getConfig = () => config
25 |
26 | proto.addRequest('ss-join', ['multiaddr', 'string', 'function'], join)
27 | proto.addRequest('ss-leave', ['multiaddr'], leave)
28 | proto.addRequest('disconnect', [], disconnect)
29 | proto.addRequest('ss-dial', ['multiaddr', 'multiaddr', 'string', 'function'], dial) // dialFrom, dialTo, dialId, cb
30 | io.on('connection', handle)
31 |
32 | log('create new server', config)
33 |
34 | const _peers = {}
35 | const nonces = {}
36 |
37 | const peersMetric = config.metrics ? new client.Gauge({ name: 'rendezvous_peers', help: 'peers online now' }) : fake.gauge
38 | const dialsSuccessTotal = config.metrics ? new client.Counter({ name: 'rendezvous_dials_total_success', help: 'sucessfully completed dials since server started' }) : fake.counter
39 | const dialsFailureTotal = config.metrics ? new client.Counter({ name: 'rendezvous_dials_total_failure', help: 'failed dials since server started' }) : fake.counter
40 | const dialsTotal = config.metrics ? new client.Counter({ name: 'rendezvous_dials_total', help: 'all dials since server started' }) : fake.counter
41 | const joinsSuccessTotal = config.metrics ? new client.Counter({ name: 'rendezvous_joins_total_success', help: 'sucessfully completed joins since server started' }) : fake.counter
42 | const joinsFailureTotal = config.metrics ? new client.Counter({ name: 'rendezvous_joins_total_failure', help: 'failed joins since server started' }) : fake.counter
43 | const joinsTotal = config.metrics ? new client.Counter({ name: 'rendezvous_joins_total', help: 'all joins since server started' }) : fake.counter
44 |
45 | const refreshMetrics = () => peersMetric.set(Object.keys(_peers).length)
46 |
47 | function safeEmit (addr, event, arg) {
48 | const peer = _peers[addr]
49 | if (!peer) {
50 | log('trying to emit %s but peer is gone', event)
51 | return
52 | }
53 |
54 | peer.emit(event, arg)
55 | }
56 |
57 | function handle (socket) {
58 | socket.addrs = []
59 | socket.cleanaddrs = {}
60 | socket.setMaxListeners(0)
61 | sp(socket, {
62 | codec: 'buffer'
63 | })
64 | proto.handleSocket(socket)
65 | }
66 |
67 | // join this signaling server network
68 | async function join (socket, multiaddr, pub, cb) {
69 | const log = socket.log = config.log.bind(config.log, '[' + socket.id + ']')
70 |
71 | if (getConfig().strictMultiaddr && !util.validateMa(multiaddr)) {
72 | joinsTotal.inc()
73 | joinsFailureTotal.inc()
74 | return cb('Invalid multiaddr')
75 | }
76 |
77 | if (getConfig().cryptoChallenge) {
78 | if (!pub.length) {
79 | joinsTotal.inc()
80 | joinsFailureTotal.inc()
81 | return cb('Crypto Challenge required but no Id provided')
82 | }
83 |
84 | if (!nonces[socket.id]) {
85 | nonces[socket.id] = {}
86 | }
87 |
88 | if (nonces[socket.id][multiaddr]) {
89 | log('response cryptoChallenge', multiaddr)
90 |
91 | let ok
92 | try {
93 | ok = await nonces[socket.id][multiaddr].key.verify(
94 | Buffer.from(nonces[socket.id][multiaddr].nonce),
95 | Buffer.from(pub, 'hex')
96 | )
97 | } catch (err) {
98 | log('crypto error', err)
99 | }
100 |
101 | if (!ok) {
102 | joinsTotal.inc()
103 | joinsFailureTotal.inc()
104 | }
105 |
106 | if (ok === undefined) { return cb('Crypto error') } // the errors NEED to be a string otherwise JSON.stringify() turns them into {}
107 | if (ok !== true) { return cb('Signature Invalid') }
108 |
109 | joinFinalize(socket, multiaddr, cb)
110 | } else {
111 | joinsTotal.inc()
112 | const addr = multiaddr.split('ipfs/').pop()
113 |
114 | log('do cryptoChallenge', multiaddr, addr)
115 |
116 | let key
117 | try {
118 | key = await util.getIdAndValidate(pub, addr)
119 | } catch (err) {
120 | joinsFailureTotal.inc()
121 | return cb(err)
122 | }
123 |
124 | const nonce = uuid() + uuid()
125 | socket.once('disconnect', () => {
126 | delete nonces[socket.id]
127 | })
128 |
129 | nonces[socket.id][multiaddr] = { nonce: nonce, key: key }
130 | cb(null, nonce)
131 | }
132 | } else {
133 | joinsTotal.inc()
134 | joinFinalize(socket, multiaddr, cb)
135 | }
136 | }
137 |
138 | function joinFinalize (socket, multiaddr, cb) {
139 | const log = getConfig().log.bind(getConfig().log, '[' + socket.id + ']')
140 | _peers[multiaddr] = socket
141 | if (!socket.stopSendingPeersIntv) socket.stopSendingPeersIntv = {}
142 | joinsSuccessTotal.inc()
143 | refreshMetrics()
144 | socket.addrs.push(multiaddr)
145 | log('registered as', multiaddr)
146 |
147 | // discovery
148 |
149 | let refreshInterval = setInterval(sendPeers, getConfig().refreshPeerListIntervalMS)
150 |
151 | socket.once('disconnect', stopSendingPeers)
152 |
153 | sendPeers()
154 |
155 | function sendPeers () {
156 | const list = Object.keys(_peers)
157 | log(multiaddr, 'sending', (list.length - 1).toString(), 'peer(s)')
158 | list.forEach((mh) => {
159 | if (mh === multiaddr) {
160 | return
161 | }
162 |
163 | safeEmit(mh, 'ws-peer', multiaddr)
164 | })
165 | }
166 |
167 | function stopSendingPeers () {
168 | if (refreshInterval) {
169 | log(multiaddr, 'stop sending peers')
170 | clearInterval(refreshInterval)
171 | refreshInterval = null
172 | }
173 | }
174 |
175 | socket.stopSendingPeersIntv[multiaddr] = stopSendingPeers
176 |
177 | const otherPeers = Object.keys(_peers).filter(mh => mh !== multiaddr)
178 | cb(null, null, otherPeers)
179 | }
180 |
181 | function leave (socket, multiaddr) {
182 | if (_peers[multiaddr] && _peers[multiaddr].id === socket.id) {
183 | socket.log('leaving', multiaddr)
184 | delete _peers[multiaddr]
185 | socket.addrs = socket.addrs.filter(m => m !== multiaddr)
186 | if (socket.stopSendingPeersIntv[multiaddr]) {
187 | socket.stopSendingPeersIntv[multiaddr]()
188 | delete socket.stopSendingPeersIntv[multiaddr]
189 | }
190 | refreshMetrics()
191 | }
192 | }
193 |
194 | function disconnect (socket) {
195 | socket.log('disconnected')
196 | Object.keys(_peers).forEach((mh) => {
197 | if (_peers[mh].id === socket.id) {
198 | leave(socket, mh)
199 | }
200 | })
201 | }
202 |
203 | function dial (socket, from, to, dialId, cb) {
204 | const log = socket.log
205 | const s = socket.addrs.filter((a) => a === from)[0]
206 |
207 | dialsTotal.inc()
208 |
209 | if (!s) {
210 | dialsFailureTotal.inc()
211 | return cb('Not authorized for this address')
212 | }
213 |
214 | log(from, 'is dialing', to)
215 | const peer = _peers[to]
216 |
217 | if (!peer) {
218 | dialsFailureTotal.inc()
219 | return cb('Peer not found')
220 | }
221 |
222 | socket.createProxy(dialId + '.dialer', peer)
223 |
224 | peer.emit('ss-incomming', dialId, from, err => {
225 | if (err) {
226 | dialsFailureTotal.inc()
227 | return cb(err)
228 | }
229 |
230 | dialsSuccessTotal.inc()
231 | peer.createProxy(dialId + '.listener', socket)
232 | cb()
233 | })
234 | }
235 |
236 | return {
237 | peers: () => _peers
238 | }
239 | }
240 |
--------------------------------------------------------------------------------