├── .gitignore
├── LICENSE
├── README.md
├── collaborators.md
├── index.js
├── package.json
└── test.js
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 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 | # webrtc-swarm
2 |
3 | > Create a swarm of p2p connections using webrtc and a
4 | [signalhub](https://github.com/mafintosh/signalhub).
5 |
6 | ```
7 | npm install webrtc-swarm
8 | ```
9 |
10 | ## Usage
11 |
12 | ``` js
13 | var swarm = require('webrtc-swarm')
14 | var signalhub = require('signalhub')
15 |
16 | var hub = signalhub('swarm-example', ['http://yourdomain.com'])
17 |
18 | var sw = swarm(hub, {
19 | wrtc: require('wrtc') // don't need this if used in the browser
20 | })
21 |
22 | sw.on('peer', function (peer, id) {
23 | console.log('connected to a new peer:', id)
24 | console.log('total peers:', sw.peers.length)
25 | })
26 |
27 | sw.on('disconnect', function (peer, id) {
28 | console.log('disconnected from a peer:', id)
29 | console.log('total peers:', sw.peers.length)
30 | })
31 | ```
32 |
33 | ## API
34 |
35 | ```js
36 | var swarm = require('webrtc-swarm')
37 | ```
38 |
39 | ### var sw = swarm(hub, opts)
40 |
41 | Creates a new webrtc swarm using
42 | [signalhub](https://github.com/mafintosh/signalhub) `hub` for discovery and
43 | connection brokering.
44 |
45 | Valid keys for `opts` include:
46 |
47 | - `wrtc` - (optional) a reference to the `wrtc` library, if using Node.
48 | - `uuid` - (optional) a unique identifier for this peer. One is generated for
49 | you if not supplied.
50 | - `maxPeers` - (optional) the maximum number of peers you wish to connect to.
51 | Defaults to unlimited.
52 | - `wrap` - (optional) a function that can modify the WebRTC signaling data
53 | before it gets send out. It's called with `wrap(outgoingSignalingData,
54 | destinationSignalhubChannel)` and must return the wrapped signaling data.
55 | - `unwrap` - (optional) a function that can modify the WebRTC signaling data
56 | before it gets processed. It's called with `unwrap(incomingData,
57 | sourceSignalhubChannel)` and must return the raw signaling data.
58 |
59 | Additional optional keys can be passed through to the underlying
60 | [simple-peer](https://www.npmjs.com/package/simple-peer) instances:
61 |
62 | - `channelConfig` - custom webrtc data channel configuration (used by
63 | `createDataChannel`)
64 | - `config` - custom webrtc configuration (used by `RTCPeerConnection`
65 | constructor)
66 | - `stream` - if video/voice is desired, pass stream returned from
67 | `getUserMedia`
68 |
69 |
70 | ### sw.close()
71 |
72 | Disconnect from swarm
73 |
74 | ### sw.on('peer|connect', peer, id)
75 |
76 | `peer` and `connect` are interchangeable. Fires when a connection has been
77 | established to a new peer `peer`, with unique id `id`.
78 |
79 | ### sw.on('disconnect', peer, id)
80 |
81 | Fires when an existing peer connection is lost.
82 |
83 | `peer` is a [simple-peer](https://www.npmjs.com/package/simple-peer) instance.
84 |
85 | ### sw.on('close')
86 |
87 | Fires when all peer and signalhub connections are closed
88 |
89 | ### sw.peers
90 |
91 | A list of peers that `sw` is currently connected to.
92 |
93 | ### swarm.WEBRTC_SUPPORT
94 |
95 | Detect native WebRTC support in the javascript environment.
96 |
97 | ```js
98 | var swarm = require('webrtc-swarm')
99 |
100 | if (swarm.WEBRTC_SUPPORT) {
101 | // webrtc support!
102 | } else {
103 | // fallback
104 | }
105 | ```
106 |
107 | ## License
108 |
109 | MIT
110 |
--------------------------------------------------------------------------------
/collaborators.md:
--------------------------------------------------------------------------------
1 | ## Collaborators
2 |
3 | webrtc-swarm is only possible due to the excellent work of the following collaborators:
4 |
5 |
8 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | var SimplePeer = require('simple-peer')
2 | var inherits = require('inherits')
3 | var events = require('events')
4 | var through = require('through2')
5 | var cuid = require('cuid')
6 | var once = require('once')
7 | var debug = require('debug')('webrtc-swarm')
8 |
9 | module.exports = WebRTCSwarm
10 |
11 | function WebRTCSwarm (hub, opts) {
12 | if (!(this instanceof WebRTCSwarm)) return new WebRTCSwarm(hub, opts)
13 | if (!hub) throw new Error('SignalHub instance required')
14 | if (!opts) opts = {}
15 |
16 | events.EventEmitter.call(this)
17 | this.setMaxListeners(0)
18 |
19 | this.hub = hub
20 | this.wrtc = opts.wrtc
21 | this.channelConfig = opts.channelConfig
22 | this.config = opts.config
23 | this.stream = opts.stream
24 | this.wrap = opts.wrap || function (data) { return data }
25 | this.unwrap = opts.unwrap || function (data) { return data }
26 | this.offerConstraints = opts.offerConstraints || {}
27 | this.maxPeers = opts.maxPeers || Infinity
28 | this.me = opts.uuid || cuid()
29 | debug('my uuid:', this.me)
30 |
31 | this.remotes = {}
32 | this.peers = []
33 | this.closed = false
34 |
35 | subscribe(this, hub)
36 | }
37 |
38 | inherits(WebRTCSwarm, events.EventEmitter)
39 |
40 | WebRTCSwarm.WEBRTC_SUPPORT = SimplePeer.WEBRTC_SUPPORT
41 |
42 | WebRTCSwarm.prototype.close = function (cb) {
43 | if (this.closed) return
44 | this.closed = true
45 |
46 | if (cb) this.once('close', cb)
47 |
48 | var self = this
49 | this.hub.close(function () {
50 | var len = self.peers.length
51 | if (len > 0) {
52 | var closed = 0
53 | self.peers.forEach(function (peer) {
54 | peer.once('close', function () {
55 | if (++closed === len) {
56 | self.emit('close')
57 | }
58 | })
59 | process.nextTick(function () {
60 | peer.destroy()
61 | })
62 | })
63 | } else {
64 | self.emit('close')
65 | }
66 | })
67 | }
68 |
69 | function setup (swarm, peer, id) {
70 | peer.on('connect', function () {
71 | debug('connected to peer', id)
72 | swarm.peers.push(peer)
73 | swarm.emit('peer', peer, id)
74 | swarm.emit('connect', peer, id)
75 | })
76 |
77 | var onclose = once(function (err) {
78 | debug('disconnected from peer', id, err)
79 | if (swarm.remotes[id] === peer) delete swarm.remotes[id]
80 | var i = swarm.peers.indexOf(peer)
81 | if (i > -1) swarm.peers.splice(i, 1)
82 | swarm.emit('disconnect', peer, id)
83 | })
84 |
85 | var signals = []
86 | var sending = false
87 |
88 | function kick () {
89 | if (swarm.closed || sending || !signals.length) return
90 | sending = true
91 | var data = {from: swarm.me, signal: signals.shift()}
92 | data = swarm.wrap(data, id)
93 | swarm.hub.broadcast(id, data, function () {
94 | sending = false
95 | kick()
96 | })
97 | }
98 |
99 | peer.on('signal', function (sig) {
100 | signals.push(sig)
101 | kick()
102 | })
103 |
104 | peer.on('error', onclose)
105 | peer.once('close', onclose)
106 | }
107 |
108 | function subscribe (swarm, hub) {
109 | hub.subscribe('all').pipe(through.obj(function (data, enc, cb) {
110 | data = swarm.unwrap(data, 'all')
111 | if (swarm.closed || !data) return cb()
112 |
113 | debug('/all', data)
114 | if (data.from === swarm.me) {
115 | debug('skipping self', data.from)
116 | return cb()
117 | }
118 |
119 | if (data.type === 'connect') {
120 | if (swarm.peers.length >= swarm.maxPeers) {
121 | debug('skipping because maxPeers is met', data.from)
122 | return cb()
123 | }
124 | if (swarm.remotes[data.from]) {
125 | debug('skipping existing remote', data.from)
126 | return cb()
127 | }
128 |
129 | debug('connecting to new peer (as initiator)', data.from)
130 | var peer = new SimplePeer({
131 | wrtc: swarm.wrtc,
132 | initiator: true,
133 | channelConfig: swarm.channelConfig,
134 | config: swarm.config,
135 | stream: swarm.stream,
136 | offerConstraints: swarm.offerConstraints
137 | })
138 |
139 | setup(swarm, peer, data.from)
140 | swarm.remotes[data.from] = peer
141 | }
142 |
143 | cb()
144 | }))
145 |
146 | hub.subscribe(swarm.me).once('open', connect.bind(null, swarm, hub)).pipe(through.obj(function (data, enc, cb) {
147 | data = swarm.unwrap(data, swarm.me)
148 | if (swarm.closed || !data) return cb()
149 |
150 | var peer = swarm.remotes[data.from]
151 | if (!peer) {
152 | if (!data.signal || data.signal.type !== 'offer') {
153 | debug('skipping non-offer', data)
154 | return cb()
155 | }
156 |
157 | debug('connecting to new peer (as not initiator)', data.from)
158 | peer = swarm.remotes[data.from] = new SimplePeer({
159 | wrtc: swarm.wrtc,
160 | channelConfig: swarm.channelConfig,
161 | config: swarm.config,
162 | stream: swarm.stream,
163 | offerConstraints: swarm.offerConstraints
164 | })
165 |
166 | setup(swarm, peer, data.from)
167 | }
168 |
169 | debug('signalling', data.from, data.signal)
170 | peer.signal(data.signal)
171 | cb()
172 | }))
173 | }
174 |
175 | function connect (swarm, hub) {
176 | if (swarm.closed || swarm.peers.length >= swarm.maxPeers) return
177 | var data = {type: 'connect', from: swarm.me}
178 | data = swarm.wrap(data, 'all')
179 | hub.broadcast('all', data, function () {
180 | setTimeout(connect.bind(null, swarm, hub), Math.floor(Math.random() * 2000) + (swarm.peers.length ? 13000 : 3000))
181 | })
182 | }
183 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "webrtc-swarm",
3 | "version": "2.9.0",
4 | "description": "Create a swarm of p2p connections using webrtc and a signalhub",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "standard && tape test.js"
8 | },
9 | "dependencies": {
10 | "cuid": "^1.2.4",
11 | "debug": "^2.2.0",
12 | "inherits": "^2.0.1",
13 | "once": "^1.3.1",
14 | "simple-peer": "^6.0.1",
15 | "through2": "^2.0.0"
16 | },
17 | "devDependencies": {
18 | "electron-webrtc": "^0.2.6",
19 | "signalhub": "^4.7.1",
20 | "standard": "^7.1.2",
21 | "tape": "^4.6.0"
22 | },
23 | "repository": {
24 | "type": "git",
25 | "url": "https://github.com/mafintosh/webrtc-swarm.git"
26 | },
27 | "author": "Mathias Buus (@mafintosh)",
28 | "license": "MIT",
29 | "bugs": {
30 | "url": "https://github.com/mafintosh/webrtc-swarm/issues"
31 | },
32 | "homepage": "https://github.com/mafintosh/webrtc-swarm"
33 | }
34 |
--------------------------------------------------------------------------------
/test.js:
--------------------------------------------------------------------------------
1 | var server = require('signalhub/server')()
2 | var signalhub = require('signalhub')
3 | var swarm = require('./')
4 | var test = require('tape')
5 | var wrtc = require('electron-webrtc')()
6 |
7 | test.onFinish(function () {
8 | server.close()
9 | wrtc.close()
10 | })
11 |
12 | server.listen(9000, function () {
13 | test('greet and close', function (t) {
14 | t.plan(8)
15 |
16 | var hub1 = signalhub('app', 'localhost:9000')
17 | var hub2 = signalhub('app', 'localhost:9000')
18 |
19 | var sw1 = swarm(hub1, {wrtc})
20 | var sw2 = swarm(hub2, {wrtc})
21 |
22 | var hello = 'hello'
23 | var goodbye = 'goodbye'
24 |
25 | var peerIds = {}
26 |
27 | sw1.on('peer', function (peer, id) {
28 | t.pass('connected to peer from sw2')
29 | peerIds.sw2 = id
30 | peer.send(hello)
31 | peer.on('data', function (data) {
32 | t.equal(data.toString(), goodbye, 'goodbye received')
33 | sw1.close(function () {
34 | t.pass('swarm sw1 closed')
35 | })
36 | })
37 | })
38 |
39 | sw2.on('peer', function (peer, id) {
40 | t.pass('connected to peer from sw1')
41 | peerIds.sw1 = id
42 | peer.on('data', function (data) {
43 | t.equal(data.toString(), hello, 'hello received')
44 | peer.send(goodbye)
45 | sw2.close(function () {
46 | t.pass('swarm sw2 closed')
47 | })
48 | })
49 | })
50 |
51 | sw1.on('disconnect', function (peer, id) {
52 | if (id === peerIds.sw2) {
53 | t.pass('connection to peer from sw2 lost')
54 | }
55 | })
56 |
57 | sw2.on('disconnect', function (peer, id) {
58 | if (id === peerIds.sw1) {
59 | t.pass('connection to peer from sw1 lost')
60 | }
61 | })
62 | })
63 | })
64 |
--------------------------------------------------------------------------------