├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── example ├── client.js └── server.js ├── index.js └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - '0.10' 4 | - '0.12' 5 | - '4.0' 6 | - '6.0' 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Mathias Buus 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 13 | all 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 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # peer-network 2 | 3 | Create servers/clients that listen on names instead of ports and hostnames and are accessible over the internet. 4 | Uses [hyperdht](https://github.com/mafintosh/hyperdht) to discover peers and holepunch connections to them. 5 | 6 | Per default it uses `bootstrap1.hyperdht.org` to bootstrap the DHT but you can configure your own. 7 | 8 | ``` 9 | npm install peer-network 10 | ``` 11 | 12 | [![build status](http://img.shields.io/travis/mafintosh/peer-network.svg?style=flat)](http://travis-ci.org/mafintosh/peer-network) 13 | 14 | ## Usage 15 | 16 | First create a server 17 | 18 | ``` js 19 | var peernet = require('peer-network') 20 | var network = peernet() 21 | 22 | var server = network.createServer() 23 | 24 | server.on('connection', function (stream) { 25 | console.log('new connection') 26 | stream.pipe(stream) // echo 27 | }) 28 | 29 | server.listen('echo-server') // listen on a name 30 | ``` 31 | 32 | In another process (on any machine) 33 | 34 | ``` js 35 | // will connect to a server annoucing itself as echo-server 36 | var stream = network.connect('echo-server') 37 | 38 | stream.write('hello world') 39 | stream.on('data', function (data) { 40 | console.log('data:', data.toString()) 41 | }) 42 | ``` 43 | 44 | ## API 45 | 46 | #### `var network = peernet(opts)` 47 | 48 | Create a new network instance. Options are forwarded to the [hyperdht](https://github.com/mafintosh/hyperdht) constructor. 49 | If you do not provide a bootstrap list, `bootstrap1.hyperdht.org` is used. 50 | 51 | #### `var server = network.createServer([onconnection])` 52 | 53 | Create a new server. 54 | 55 | #### `server.listen(name, [onlistening])` 56 | 57 | Listen on a name. Can be any buffer/string. Optionally you can specify a port to bound to as well. If not specified a random open port will be used. 58 | The server will use discovery-channel to announce itself to other peers using multicast-dns, the bittorrent dht and potentially a series of dns servers. 59 | 60 | #### `server.close([onclose])` 61 | 62 | Close the server and stop announcing its pressence 63 | 64 | #### `server.on('connection', stream)` 65 | 66 | Emitted when a client connects 67 | 68 | #### `server.on('listening')` 69 | 70 | Emitted when the server is listening. 71 | 72 | #### `server.on('error', err)` 73 | 74 | Emitted if the server has a critical error. 75 | 76 | #### `server.on('close')` 77 | 78 | Emitted when the server is fully close 79 | 80 | #### `var stream = network.connect(name)` 81 | 82 | Connect to a server listening on a name. If multiple servers are listening it will connect to the first one to which an connection can be established. 83 | 84 | #### `stream.on('connect')` 85 | 86 | Emitted when the stream is fully connected to another peer. You do not need to wait for this event before writing data to the socket. 87 | 88 | ## License 89 | 90 | MIT 91 | -------------------------------------------------------------------------------- /example/client.js: -------------------------------------------------------------------------------- 1 | var net = require('../')() 2 | 3 | var socket = net.connect(process.argv[2] || 'example') 4 | 5 | socket.on('peer', function (peer) { 6 | console.log('(trying to connect to %s:%d)', peer.host, peer.port) 7 | }) 8 | 9 | socket.on('connect', function () { 10 | console.log('(connected)') 11 | }) 12 | 13 | process.stdin.pipe(socket).pipe(process.stdout) 14 | -------------------------------------------------------------------------------- /example/server.js: -------------------------------------------------------------------------------- 1 | var net = require('../')() 2 | 3 | var server = net.createServer(function (socket) { 4 | console.log('(new connection)') 5 | socket.on('data', function (data) { 6 | process.stdout.write(data) 7 | }) 8 | process.stdin.on('data', function (data) { 9 | socket.write(data) 10 | }) 11 | }) 12 | 13 | server.listen(process.argv[2] || 'example') 14 | 15 | process.on('SIGINT', function () { 16 | server.close(function () { 17 | process.exit() 18 | }) 19 | }) 20 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var hyperdht = require('hyperdht') 2 | var thunky = require('thunky') 3 | var crypto = require('crypto') 4 | var events = require('events') 5 | var addr = require('network-address') 6 | var duplexify = require('duplexify') 7 | var utp = require('utp-native') 8 | 9 | module.exports = Network 10 | 11 | function Network (opts) { 12 | if (!(this instanceof Network)) return new Network(opts) 13 | if (!opts) opts = {} 14 | 15 | this.socket = opts.socket || utp() 16 | 17 | this.dht = hyperdht({ 18 | bootstrap: opts.bootstrap || ['bootstrap1.hyperdht.org'], 19 | ephemeral: opts.ephemeral !== false, 20 | socket: this.socket 21 | }) 22 | 23 | var self = this 24 | 25 | this.bind = thunky(function (cb) { 26 | self.dht.listen(function (err) { 27 | if (err) return cb(err) 28 | cb(null, self.dht.address().port) 29 | }) 30 | }) 31 | 32 | this.bind() 33 | } 34 | 35 | Network.prototype.connect = function (name) { 36 | var self = this 37 | var stream = duplexify() 38 | 39 | this.bind(function (err, port) { 40 | if (err) return stream.destroy(err) 41 | self.dht.lookup(hash(name), {localAddress: {port: port, host: addr()}}, function (err, nodes) { 42 | if (err) return stream.destroy(err) 43 | loopNodes() 44 | 45 | function loopNodes () { 46 | var next = nodes.shift() 47 | if (!next) return stream.destroy(new Error('Could not connect to any peers')) 48 | 49 | var peers = next.localPeers.concat(next.peers) 50 | loop() 51 | 52 | function loop (err) { 53 | if (err) return stream.destroy(err) 54 | 55 | var peer = peers.shift() 56 | if (!peer) return loopNodes() 57 | 58 | stream.emit('peer', peer) 59 | tryConnect(peer, next.node, function (err) { 60 | if (err) return loop() 61 | 62 | var socket = self.socket.connect(peer.port, peer.host) 63 | stream.setReadable(socket) 64 | stream.setWritable(socket) 65 | stream.emit('connect') 66 | }) 67 | } 68 | } 69 | 70 | function tryConnect (peer, node, cb) { 71 | self.dht.ping(peer, function (err) { 72 | if (!err) return cb() 73 | self.dht.holepunch(peer, node, cb) 74 | }) 75 | } 76 | }) 77 | }) 78 | 79 | return stream 80 | } 81 | 82 | Network.prototype.createServer = function (onconnection) { 83 | var self = this 84 | var server = new events.EventEmitter() 85 | var listening = null 86 | 87 | if (onconnection) { 88 | server.on('connection', onconnection) 89 | } 90 | 91 | this.socket.on('connection', function (socket) { 92 | server.emit('connection', socket) 93 | }) 94 | 95 | if (this.socket._firewalled) { 96 | this.socket._firewalled = false 97 | this.socket._handle.onsocket(this.socket._onsocket) 98 | } 99 | 100 | server.listen = function (name) { 101 | if (listening) throw new Error('Already listening') 102 | self.bind(function (err, port) { 103 | if (err) server.emit('error', err) 104 | listening = {name: hash(name), port: port, host: addr()} 105 | self.dht.announce(listening.name, {localAddress: listening}, function (err, nodes) { 106 | if (err) server.emit('error', err) 107 | server.emit('listening') 108 | }) 109 | }) 110 | } 111 | 112 | server.close = function (cb) { 113 | if (!cb) cb = noop 114 | if (!listening) return cb() 115 | self.dht.unannounce(listening.name, {localAddress: listening}, function (err) { 116 | if (err) return cb(err) 117 | server.emit('close') 118 | cb(null) 119 | }) 120 | } 121 | 122 | return server 123 | } 124 | 125 | function noop () {} 126 | 127 | function hash (val) { 128 | return crypto.createHash('sha256').update(val).digest() 129 | } 130 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "peer-network", 3 | "version": "2.0.2", 4 | "description": "Create servers/clients that listen on names instead of ports and hostnames and are accessible over the internet", 5 | "main": "index.js", 6 | "dependencies": { 7 | "discovery-channel": "^5.3.0", 8 | "duplexify": "^3.4.5", 9 | "hash-to-port": "^1.0.0", 10 | "hyperdht": "^1.3.0", 11 | "inherits": "^2.0.1", 12 | "network-address": "^1.1.2", 13 | "thunky": "^1.0.2", 14 | "utp-native": "^1.5.0" 15 | }, 16 | "devDependencies": { 17 | "standard": "^7.1.2" 18 | }, 19 | "scripts": { 20 | "test": "standard" 21 | }, 22 | "repository": { 23 | "type": "git", 24 | "url": "https://github.com/mafintosh/peer-network.git" 25 | }, 26 | "author": "Mathias Buus (@mafintosh)", 27 | "license": "MIT", 28 | "bugs": { 29 | "url": "https://github.com/mafintosh/peer-network/issues" 30 | }, 31 | "homepage": "https://github.com/mafintosh/peer-network" 32 | } 33 | --------------------------------------------------------------------------------