├── 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 | Libp2p Logo 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 | [![](https://img.shields.io/badge/made%20by-mkg20001-blue.svg?style=flat-square)](http://ipn.io) 6 | [![](https://img.shields.io/badge/project-libp2p-yellow.svg?style=flat-square)](http://libp2p.io/) 7 | [![](https://img.shields.io/badge/freenode-%23libp2p-yellow.svg?style=flat-square)](http://webchat.freenode.net/?channels=%23libp2p) 8 | [![Discourse posts](https://img.shields.io/discourse/https/discuss.libp2p.io/posts.svg)](https://discuss.libp2p.io) 9 | [![](https://img.shields.io/codecov/c/github/libp2p/js-libp2p-websocket-star-rendezvous.svg?style=flat-square)](https://codecov.io/gh/libp2p/js-libp2p-websocket-star-rendezvous) 10 | [![](https://img.shields.io/travis/libp2p/js-libp2p-websocket-star-rendezvous.svg?style=flat-square)](https://travis-ci.com/libp2p/js-libp2p-websocket-star-rendezvous) 11 | [![Dependency Status](https://david-dm.org/libp2p/js-libp2p-websocket-star-rendezvous.svg?style=flat-square)](https://david-dm.org/libp2p/js-libp2p-websocket-star-rendezvous) 12 | [![js-standard-style](https://img.shields.io/badge/code%20style-standard-brightgreen.svg?style=flat-square)](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 | --------------------------------------------------------------------------------