├── .gitignore ├── .travis.yml ├── .zuul.yml ├── AUTHORS ├── LICENSE ├── README.md ├── docs.json ├── docs ├── README.md ├── announce.msc ├── announce.png ├── events.md ├── identifying-participants.md ├── protocol.md ├── purpose.md └── signalflow-diagrams.md ├── examples ├── getting-started.js └── signalling-via-switchboard.js ├── index.js ├── package.json ├── signaller.js └── test ├── all-browser.js ├── all-node.js ├── all.js ├── announce-concurrent.js ├── announce-customid.js ├── announce-debounce.js ├── announce-event-local.js ├── announce-raw.js ├── browser.js ├── events.js ├── helpers ├── messenger-group.js └── signaller.js ├── message-announce.js ├── peer-filter.js ├── primus-load.js ├── send-falsey-parts.js ├── server.js ├── set-id.js ├── switchboard-announce-concurrent.js ├── switchboard-announce-customid.js ├── switchboard-announce.js ├── switchboard-auto.js ├── switchboard-manualclose.js ├── switchboard-send-closed.js └── to.js /.gitignore: -------------------------------------------------------------------------------- 1 | npm-debug.log 2 | node_modules/ 3 | .DS_Store 4 | .travis -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | node_js: 4 | - 0.10 5 | - 4.1 6 | - stable 7 | 8 | env: 9 | matrix: 10 | - BROWSER=chrome BVER=stable 11 | - BROWSER=chrome BVER=beta 12 | - BROWSER=chrome BVER=unstable 13 | - BROWSER=firefox BVER=stable 14 | - BROWSER=firefox BVER=beta 15 | - BROWSER=firefox BVER=unstable 16 | 17 | matrix: 18 | fast_finish: true 19 | 20 | allow_failures: 21 | - env: BROWSER=chrome BVER=unstable 22 | - env: BROWSER=firefox BVER=unstable 23 | 24 | before_script: 25 | - ./node_modules/travis-multirunner/setup.sh 26 | - export DISPLAY=:99.0 27 | - sh -e /etc/init.d/xvfb start 28 | 29 | after_failure: 30 | - for file in *.log; do echo $file; echo "======================"; cat $file; done || true 31 | 32 | notifications: 33 | email: 34 | - nathan.oehlman@nicta.com.au -------------------------------------------------------------------------------- /.zuul.yml: -------------------------------------------------------------------------------- 1 | ui: tape 2 | browsers: 3 | - name: chrome 4 | version: [latest,beta] 5 | 6 | - name: firefox 7 | version: latest 8 | 9 | server: ./test/server.js 10 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Damon Oehlman (https://github.com/DamonOehlman) 2 | Silvia Pfeiffer (https://github.com/silviapfeiffer) 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright 2013 National ICT Australia Limited (NICTA) 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # rtc-signaller 2 | 3 | The `rtc-signaller` module provides a transportless signalling 4 | mechanism for WebRTC. 5 | 6 | 7 | [![NPM](https://nodei.co/npm/rtc-signaller.png)](https://nodei.co/npm/rtc-signaller/) 8 | 9 | [![Build Status](https://img.shields.io/travis/rtc-io/rtc-signaller.svg?branch=master)](https://travis-ci.org/rtc-io/rtc-signaller) [![stable](https://img.shields.io/badge/stability-stable-green.svg)](https://github.com/dominictarr/stability#stable) 10 | [![Gitter chat](https://badges.gitter.im/rtc-io.png)](https://gitter.im/rtc-io) 11 | 12 | 13 | 14 | ## Purpose 15 | 16 | The signaller provides set of client-side tools that assist with the setting up an `PeerConnection` and helping them communicate. All that is required for the signaller to operate is a [suitable messenger](https://github.com/DamonOehlman/messenger-archetype). A messenger is simply a function that is able to create a [pull-stream](https://github.com/dominictarr/pull-stream) `Source` and/or `Sink`. From version `5.0.0` the `rtc-signaller` package will use pull-streams to ensure robust delivery of messages. 17 | 18 | By using this approach, we can conduct signalling over any number of mechanisms: 19 | 20 | - local, [in memory](https://github.com/DamonOehlman/messenger-memory) message passing 21 | - via [WebSockets](https://github.com/DamonOehlman/messenger-ws) and higher level abstractions (such as [primus](https://github.com/primus/primus)) 22 | 23 | In the event that you want to implement a signaller without using pull-streams, then you can work from a base signaller using the [`rtc-signal/signaller`](https://github.com/rtc-io/rtc-signal/blob/master/signaller.js) implementation. 24 | 25 | 26 | ## Getting Started 27 | 28 | While the signaller is capable of communicating by a number of different 29 | messengers (i.e. anything that can send and receive messages over a wire) 30 | it comes with support for understanding how to connect to an 31 | [rtc-switchboard](https://github.com/rtc-io/rtc-switchboard) out of the box. 32 | 33 | The following code sample demonstrates how: 34 | 35 | ```js 36 | // create a new signaller, connecting to the target switchboard 37 | var messenger = require('rtc-switchboard-messenger'); 38 | var signaller = require('rtc-signaller')(messenger('//switchboard.rtc.io/')); 39 | 40 | // when a new peer is announced, log it 41 | signaller.on('peer:announce', function(data) { 42 | console.log('new peer found in room: ', data); 43 | }); 44 | 45 | // for our sanity, pop a message once we are connected 46 | signaller.once('connected', function() { 47 | console.log('we have successfully connected'); 48 | }); 49 | 50 | // send through an announce message 51 | // this will occur once the websocket has been opened and active 52 | signaller.announce({ room: 'signaller-getting-started' }); 53 | 54 | ``` 55 | 56 | ## Signaller Events 57 | 58 | There are a number of events that are generating throughout the lifecycle of a signaller. 59 | 60 | ### Events regarding local state 61 | 62 | The following events are generated by the signaller in response to updates n it's own state: 63 | 64 | - `connected` 65 | 66 | A connection has been established via the underlying messenger to a signalling server (or equivalent). 67 | 68 | - `disconnected` 69 | 70 | The connection has been lost (possibly temporarily) with the signalling server (or transport). It is possible that the connection will be re-established so this does not necessarily mean the end. 71 | 72 | - `local:announce` 73 | 74 | This event is trigged when an `/announce` message is sent via the messenging channel. The event includes a single `data` argument which contains the object data that has been sent. 75 | 76 | ### Events regarding peer state 77 | 78 | The following events relate to information that has been relayed to this signaller about other peers: 79 | 80 | - `peer:filter` 81 | 82 | The `peer:filter` event is triggered prior to the `peer:announce` or `peer:update` events being fired and provides an application the opportunity to reject a peer. The handler for this event is passed the id of the peer that has connected to the room and a JS `data` object for the announce data. This data only differs from the `peer:announce` (or `peer:update`) data in that an `allow` attribute is included and controls whether we will acknowledge the presence of this new peer. 83 | 84 | Due to the way event emitters behave in node, the last handler invoked is the authority on whether the peer is accepted or not (so make sure to check the previous state of the allow flag): 85 | 86 | ```js 87 | // only accept connections from Bob 88 | signaller.on('peer:filter', function(id, data) { 89 | data.allow = data.allow && (data.name === 'Bob'); 90 | }); 91 | ``` 92 | 93 | - `peer:connected` 94 | 95 | If a peer has passed the `peer:filter` test (either no filtering has been applied, or the allow flag is set to true in the filter events) then a `peer:connected` event will be emitted: 96 | 97 | ```js 98 | signaller.on('peer:connected', function(id) { 99 | console.log('peer ' + id + ' has connected'); 100 | }); 101 | ``` 102 | 103 | This event can be useful if you wish to know when a peer has connected to the signalling server, and don't care whether it is a new peer (generating a `peer:announce` event) or known peer (generating a `peer:update` event). 104 | 105 | - `peer:announce` 106 | 107 | While the `peer:connected` event is triggered each time a peer reconnects and announces to the signalling server, a `peer:announce` event is only emitted by your local signaller if this is considered a new connection from a peer. 108 | 109 | If you are writing a WebRTC application, then this event is the best place to start creating `RTCPeerConnection` objects between the local machine and your remote, announced counterpart. You will then be able to [couple](https://github.com/rtc-io/rtc#rtccouple) those connections together using the signaller. 110 | 111 | ```js 112 | signaller.on('peer:announce', function(data) { 113 | console.log('discovered new peer: ' + data.id, data); 114 | 115 | // TODO: create a peer connection with our new friend :) 116 | }); 117 | ``` 118 | 119 | - `peer:update` 120 | 121 | An existing peer in the system has been "re-announced" possibly with some data changes: 122 | 123 | ```js 124 | signaller.on('peer:update', function(data) { 125 | console.log('data update from peer: ' + data.id, data); 126 | }); 127 | ``` 128 | 129 | - `message:` 130 | 131 | When a signaller receives a command that is not associated with a specific handler (such as announce) it emits an event for that command prefixed with `message:`. For example: 132 | 133 | ```js 134 | signallerA.on('message:greet', function(text) { 135 | console.log('signallerB sends greeting: ' + text); 136 | }); 137 | 138 | signallerB.send('/greet', 'hello friend'); 139 | ``` 140 | 141 | 142 | ## Signal Flow Diagrams 143 | 144 | Displayed below are some diagrams how the signalling flow between peers behaves. In each of the diagrams we illustrate three peers (A, B and C) participating discovery and coordinating RTCPeerConnection handshakes. 145 | 146 | In each case, only the interaction between the clients is represented not how a signalling server (such as [rtc-switchboard](https://github.com/rtc-io/rtc-switchboard)) would pass on broadcast messages, etc. This is done for two reasons: 147 | 148 | 1. It is out of scope of this documentation. 149 | 2. The `rtc-signaller` has been designed to work without having to rely on any intelligence in the server side signalling component. In the instance that a signaller broadcasts all messages to all connected peers then `rtc-signaller` should be smart enough to make sure everything works as expected. 150 | 151 | ### Peer Discovery / Announcement 152 | 153 | This diagram illustrates the process of how peer `A` announces itself to peers `B` and `C`, and in turn they announce themselves. 154 | 155 | ![](https://raw.github.com/rtc-io/rtc-signaller/master/docs/announce.png) 156 | 157 | ### Editing / Updating the Diagrams 158 | 159 | Each of the diagrams has been generated using [mscgen](http://www.mcternan.me.uk/mscgen/index.html) and the source for these documents can be found in the `docs/` folder of this repository. 160 | 161 | 162 | ## Identifying Participants 163 | 164 | When working with `rtc-signaller` and upstream packages (such as `rtc-quickconnect`) there is a temptation to provide a custom `id` field when creating the signaller instance. While this is supported in the API, it is __strongly discouraged and will likely be removed__ at some point in the future. The reason for this is that the signaller relies on this id field being unique in order to deliver messages that relate to a particular peer connection, to that peer connection. 165 | 166 | A better approach is to include an additional `uid` (or similar) field in the announce message that will be sent to peers as part of the data payload. The value of this field can be unique for each session, or persist between sessions (consider saving the value to `localStorage` for example) and thus used to identify users within the context of WebRTC sessions. 167 | 168 | ## Reference 169 | 170 | The `rtc-signaller` module is designed to be used primarily in a functional 171 | way and when called it creates a new signaller that will enable 172 | you to communicate with other peers via your messaging network. 173 | 174 | ```js 175 | // create a signaller from something that knows how to send messages 176 | var signaller = require('rtc-signaller')(messenger); 177 | ``` 178 | 179 | As demonstrated in the getting started guide, you can also pass through 180 | a string value instead of a messenger instance if you simply want to 181 | connect to an existing `rtc-switchboard` instance. 182 | 183 | ### `signaller.connect()` 184 | 185 | Manually connect the signaller using the supplied messenger. 186 | 187 | __NOTE:__ This should never have to be called if the default setting 188 | for `autoconnect` is used. 189 | 190 | ### announce(data?) 191 | 192 | The `announce` function of the signaller will pass an `/announce` message 193 | through the messenger network. When no additional data is supplied to 194 | this function then only the id of the signaller is sent to all active 195 | members of the messenging network. 196 | 197 | #### Joining Rooms 198 | 199 | To join a room using an announce call you simply provide the name of the 200 | room you wish to join as part of the data block that you annouce, for 201 | example: 202 | 203 | ```js 204 | signaller.announce({ room: 'testroom' }); 205 | ``` 206 | 207 | Signalling servers (such as 208 | [rtc-switchboard](https://github.com/rtc-io/rtc-switchboard)) will then 209 | place your peer connection into a room with other peers that have also 210 | announced in this room. 211 | 212 | Once you have joined a room, the server will only deliver messages that 213 | you `send` to other peers within that room. 214 | 215 | #### Providing Additional Announce Data 216 | 217 | There may be instances where you wish to send additional data as part of 218 | your announce message in your application. For instance, maybe you want 219 | to send an alias or nick as part of your announce message rather than just 220 | use the signaller's generated id. 221 | 222 | If for instance you were writing a simple chat application you could join 223 | the `webrtc` room and tell everyone your name with the following announce 224 | call: 225 | 226 | ```js 227 | signaller.announce({ 228 | room: 'webrtc', 229 | nick: 'Damon' 230 | }); 231 | ``` 232 | 233 | #### Announcing Updates 234 | 235 | The signaller is written to distinguish between initial peer announcements 236 | and peer data updates (see the docs on the announce handler below). As 237 | such it is ok to provide any data updates using the announce method also. 238 | 239 | For instance, I could send a status update as an announce message to flag 240 | that I am going offline: 241 | 242 | ```js 243 | signaller.announce({ status: 'offline' }); 244 | ``` 245 | 246 | ### leave() 247 | 248 | Tell the signalling server we are leaving. Calling this function is 249 | usually not required though as the signalling server should issue correct 250 | `/leave` messages when it detects a disconnect event. 251 | 252 | ## License(s) 253 | 254 | ### Apache 2.0 255 | 256 | Copyright 2013 - 2014 National ICT Australia Limited (NICTA) 257 | 258 | Licensed under the Apache License, Version 2.0 (the "License"); 259 | you may not use this file except in compliance with the License. 260 | You may obtain a copy of the License at 261 | 262 | http://www.apache.org/licenses/LICENSE-2.0 263 | 264 | Unless required by applicable law or agreed to in writing, software 265 | distributed under the License is distributed on an "AS IS" BASIS, 266 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 267 | See the License for the specific language governing permissions and 268 | limitations under the License. 269 | -------------------------------------------------------------------------------- /docs.json: -------------------------------------------------------------------------------- 1 | { 2 | "badges": { 3 | "nodeico": true, 4 | "travis": true, 5 | "stability": "stable", 6 | "gitter": "rtc-io" 7 | }, 8 | 9 | "license": { 10 | "year": "2013 - 2014", 11 | "holder": "National ICT Australia Limited (NICTA)" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | This folder contains documentation resources specific for the signaller. 2 | 3 | ## Building Diagrams 4 | 5 | Diagrams here have been built using [mscgen](http://www.mcternan.me.uk/mscgen/index.html), which can be installed on your system and then used to generate the files. -------------------------------------------------------------------------------- /docs/announce.msc: -------------------------------------------------------------------------------- 1 | msc { 2 | A, B, C; 3 | 4 | |||; 5 | 6 | --- [label=" A announces via the switchboard (server side signaller) "]; 7 | A->* [label="/announce|{\"id\":\"A\"}"]; 8 | 9 | |||; 10 | 11 | --- [label=" B and C announce back to A (via switchboard) "]; 12 | B->A [label="/to|A|/announce|{\"id\":\"B\"}"]; 13 | C->A [label="/to|A|/announce|{\"id\":\"C\"}"]; 14 | 15 | |||; 16 | 17 | --- [label=" B & C are now aware of A, and A also of B and C" ]; 18 | 19 | } -------------------------------------------------------------------------------- /docs/announce.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rtc-io/rtc-signaller/7f8ecc3b17d85cdc2ae98fdaaca3170f24dcad5f/docs/announce.png -------------------------------------------------------------------------------- /docs/events.md: -------------------------------------------------------------------------------- 1 | ## Signaller Events 2 | 3 | There are a number of events that are generating throughout the lifecycle of a signaller. 4 | 5 | ### Events regarding local state 6 | 7 | The following events are generated by the signaller in response to updates n it's own state: 8 | 9 | - `connected` 10 | 11 | A connection has been established via the underlying messenger to a signalling server (or equivalent). 12 | 13 | - `disconnected` 14 | 15 | The connection has been lost (possibly temporarily) with the signalling server (or transport). It is possible that the connection will be re-established so this does not necessarily mean the end. 16 | 17 | - `local:announce` 18 | 19 | This event is trigged when an `/announce` message is sent via the messenging channel. The event includes a single `data` argument which contains the object data that has been sent. 20 | 21 | ### Events regarding peer state 22 | 23 | The following events relate to information that has been relayed to this signaller about other peers: 24 | 25 | - `peer:filter` 26 | 27 | The `peer:filter` event is triggered prior to the `peer:announce` or `peer:update` events being fired and provides an application the opportunity to reject a peer. The handler for this event is passed the id of the peer that has connected to the room and a JS `data` object for the announce data. This data only differs from the `peer:announce` (or `peer:update`) data in that an `allow` attribute is included and controls whether we will acknowledge the presence of this new peer. 28 | 29 | Due to the way event emitters behave in node, the last handler invoked is the authority on whether the peer is accepted or not (so make sure to check the previous state of the allow flag): 30 | 31 | ```js 32 | // only accept connections from Bob 33 | signaller.on('peer:filter', function(id, data) { 34 | data.allow = data.allow && (data.name === 'Bob'); 35 | }); 36 | ``` 37 | 38 | - `peer:connected` 39 | 40 | If a peer has passed the `peer:filter` test (either no filtering has been applied, or the allow flag is set to true in the filter events) then a `peer:connected` event will be emitted: 41 | 42 | ```js 43 | signaller.on('peer:connected', function(id) { 44 | console.log('peer ' + id + ' has connected'); 45 | }); 46 | ``` 47 | 48 | This event can be useful if you wish to know when a peer has connected to the signalling server, and don't care whether it is a new peer (generating a `peer:announce` event) or known peer (generating a `peer:update` event). 49 | 50 | - `peer:announce` 51 | 52 | While the `peer:connected` event is triggered each time a peer reconnects and announces to the signalling server, a `peer:announce` event is only emitted by your local signaller if this is considered a new connection from a peer. 53 | 54 | If you are writing a WebRTC application, then this event is the best place to start creating `RTCPeerConnection` objects between the local machine and your remote, announced counterpart. You will then be able to [couple](https://github.com/rtc-io/rtc#rtccouple) those connections together using the signaller. 55 | 56 | ```js 57 | signaller.on('peer:announce', function(data) { 58 | console.log('discovered new peer: ' + data.id, data); 59 | 60 | // TODO: create a peer connection with our new friend :) 61 | }); 62 | ``` 63 | 64 | - `peer:update` 65 | 66 | An existing peer in the system has been "re-announced" possibly with some data changes: 67 | 68 | ```js 69 | signaller.on('peer:update', function(data) { 70 | console.log('data update from peer: ' + data.id, data); 71 | }); 72 | ``` 73 | 74 | - `message:` 75 | 76 | When a signaller receives a command that is not associated with a specific handler (such as announce) it emits an event for that command prefixed with `message:`. For example: 77 | 78 | ```js 79 | signallerA.on('message:greet', function(text) { 80 | console.log('signallerB sends greeting: ' + text); 81 | }); 82 | 83 | signallerB.send('/greet', 'hello friend'); 84 | ``` 85 | -------------------------------------------------------------------------------- /docs/identifying-participants.md: -------------------------------------------------------------------------------- 1 | ## Identifying Participants 2 | 3 | When working with `rtc-signaller` and upstream packages (such as `rtc-quickconnect`) there is a temptation to provide a custom `id` field when creating the signaller instance. While this is supported in the API, it is __strongly discouraged and will likely be removed__ at some point in the future. The reason for this is that the signaller relies on this id field being unique in order to deliver messages that relate to a particular peer connection, to that peer connection. 4 | 5 | A better approach is to include an additional `uid` (or similar) field in the announce message that will be sent to peers as part of the data payload. The value of this field can be unique for each session, or persist between sessions (consider saving the value to `localStorage` for example) and thus used to identify users within the context of WebRTC sessions. -------------------------------------------------------------------------------- /docs/protocol.md: -------------------------------------------------------------------------------- 1 | # rtc-signaller Protocol Overview 2 | 3 | The signalling used by `rtc-signaller` follows some very simple rules: 4 | 5 | - All messages are text (utf-8 encoded at present) 6 | 7 | - Message parts are delimited by a pipe (`|`) character 8 | 9 | - Message commands must be contained in the initial message part and can be recognized simply as their first character is the forward slash (`/`) character. 10 | 11 | - All messages (apart from `/to` messages) are distributed to all active peers currently "announced" in a room. 12 | 13 | - All signaling clients identify themselves with a unique, [non-reusable](https://github.com/rtc-io/rtc-signaller/issues/10) id. 14 | 15 | --- 16 | 17 | ## Transport Agnostic 18 | 19 | While the `rtc-signaller` module provides some default behaviour to connect 20 | via [websockets](http://www.websocket.org/), it is in fact a transport 21 | "agnostic" protocol. 22 | 23 | Basically, if you can send text over _x_ then you could use _x_ to send 24 | `rtc-signaller` messages. 25 | 26 | --- 27 | 28 | ## Sender Metadata 29 | 30 | Sender metadata is injected into a message directly after the command (or initial message part) for all messages. The only exception is a `/to` message which has no sender metadata attached as this should be contained within the wrapped message. 31 | 32 | At this stage only the sender id is included in the sender metadata (JSON), but this may be extended in the future to include additional information. 33 | 34 | --- 35 | 36 | ## Core Commands 37 | 38 | There are only a few core commands which make up the rtc-signaller signalling. These core commands should receive "special" treatment from a signalling server, whereas other commands are simply "passed through" to connected clients. 39 | 40 | --- 41 | 42 | ## /announce 43 | 44 | The announce command is used to tell a signalling server (and connected peers) that a new client is joining a virtual room. The payload of the command is JSON and requires __at least__ an `id` and a `room` attribute to be specified. 45 | 46 | For example, this is what an announce message would typically look like (line breaks added for clarity): 47 | 48 | ``` 49 | /announce 50 | |{"id":"dc6ac0ae-6e15-409b-b211-228a8f4a43b9"} 51 | |{"browser":"node","browserVersion":"?","id":"dc6ac0ae-6e15-409b-b211-228a8f4a43b9","agent":"signaller@0.18.3","room":"test-room"} 52 | ``` 53 | 54 | --- 55 | 56 | ## /leave 57 | 58 | Unsurprisingly, a `/leave` message is the counterpart to an `/announce` message and is sent when a peer is disconnecting from the room. 59 | 60 | __NOTE:__ As most client leave actions are "hard closes", i.e. a browser window / tab has been closed, a signalling server should monitor disconnections and issue an appropriate `/leave` message if the client has not issued one already. 61 | 62 | --- 63 | 64 | ## /to 65 | 66 | The `/to` command allows you to direct a message to a particular peer rather than broadcasting it to all peers connected to the same room as you. 67 | 68 | An example `/to` command might look something like (again line breaks for clarity): 69 | 70 | ``` 71 | /to 72 | |51469ae5-5d9f-4294-84dd-83ce3b37b7dd 73 | |/hello 74 | |{"id":"98e17678-a89e-4f91-aee0-5b0d93ad546d"} 75 | ``` 76 | 77 | For security reasons a signaling server is encouraged to direct `/to` messages only the connected peer; however, a client implementing this protocol should drop all '/to' messages that do not match it's own id. 78 | 79 | --- 80 | 81 | ## That's Pretty Much It 82 | 83 | From a "protocol" perspective that's really all there is to it. 84 | 85 | --- 86 | 87 | ## Writing a Client 88 | 89 | The responsibilities of a client are fairly simple: 90 | 91 | - For `/to` messages ensure the target matches, otherwise throw the message away. 92 | - For all other messages: 93 | - divide messages on the `|` character 94 | - JSON parse any JSON parts into objects 95 | - extract the 2nd part as sender metadata 96 | 97 | --- 98 | 99 | ## Writing a Server 100 | 101 | The responsibilities of a server are also simple: 102 | 103 | - Only send `/to` messages to the appropriate peer. 104 | - When an `/announce` message is received place the peer in a logical room with other peers announcing in the same room. 105 | - Handle client disconnection and `/leave` messages appropriately, i.e. remove a peer from the logical room. 106 | - Distribute "non `/to`" messages to all peers in the same room as the peer that the message originated from. 107 | 108 | --- 109 | 110 | ## What About Authentication? 111 | 112 | While this hasn't been implemented in any applications to date, encrypted credentials or a session token could be supplied as part of the announce metadata. 113 | 114 | Additionally for per message authentication a session token could be included as part of the sender metadata that is included in each message. This could be validated by the signaling server and stripped from the message before passing onto connected peers. 115 | 116 | --- 117 | 118 | ## What About Scaling? 119 | 120 | My intention was to minimally but adequately map out the interation required between peers, ensuring that both broadcast and direct messages were catered for. The likelihood is that implementing server -> server message passing and routing will require some additional work but this isn't something the end clients should have to care about. 121 | 122 | -------------------------------------------------------------------------------- /docs/purpose.md: -------------------------------------------------------------------------------- 1 | The signaller provides set of client-side tools that assist with the setting up an `PeerConnection` and helping them communicate. All that is required for the signaller to operate is a [suitable messenger](https://github.com/DamonOehlman/messenger-archetype). A messenger is simply a function that is able to create a [pull-stream](https://github.com/dominictarr/pull-stream) `Source` and/or `Sink`. From version `5.0.0` the `rtc-signaller` package will use pull-streams to ensure robust delivery of messages. 2 | 3 | By using this approach, we can conduct signalling over any number of mechanisms: 4 | 5 | - local, [in memory](https://github.com/DamonOehlman/messenger-memory) message passing 6 | - via [WebSockets](https://github.com/DamonOehlman/messenger-ws) and higher level abstractions (such as [primus](https://github.com/primus/primus)) 7 | 8 | In the event that you want to implement a signaller without using pull-streams, then you can work from a base signaller using the [`rtc-signal/signaller`](https://github.com/rtc-io/rtc-signal/blob/master/signaller.js) implementation. 9 | -------------------------------------------------------------------------------- /docs/signalflow-diagrams.md: -------------------------------------------------------------------------------- 1 | ## Signal Flow Diagrams 2 | 3 | Displayed below are some diagrams how the signalling flow between peers behaves. In each of the diagrams we illustrate three peers (A, B and C) participating discovery and coordinating RTCPeerConnection handshakes. 4 | 5 | In each case, only the interaction between the clients is represented not how a signalling server (such as [rtc-switchboard](https://github.com/rtc-io/rtc-switchboard)) would pass on broadcast messages, etc. This is done for two reasons: 6 | 7 | 1. It is out of scope of this documentation. 8 | 2. The `rtc-signaller` has been designed to work without having to rely on any intelligence in the server side signalling component. In the instance that a signaller broadcasts all messages to all connected peers then `rtc-signaller` should be smart enough to make sure everything works as expected. 9 | 10 | ### Peer Discovery / Announcement 11 | 12 | This diagram illustrates the process of how peer `A` announces itself to peers `B` and `C`, and in turn they announce themselves. 13 | 14 | ![](https://raw.github.com/rtc-io/rtc-signaller/master/docs/announce.png) 15 | 16 | ### Editing / Updating the Diagrams 17 | 18 | Each of the diagrams has been generated using [mscgen](http://www.mcternan.me.uk/mscgen/index.html) and the source for these documents can be found in the `docs/` folder of this repository. 19 | -------------------------------------------------------------------------------- /examples/getting-started.js: -------------------------------------------------------------------------------- 1 | // create a new signaller, connecting to the target switchboard 2 | var messenger = require('rtc-switchboard-messenger'); 3 | var signaller = require('..')(messenger('//switchboard.rtc.io/')); 4 | 5 | // when a new peer is announced, log it 6 | signaller.on('peer:announce', function(data) { 7 | console.log('new peer found in room: ', data); 8 | }); 9 | 10 | // for our sanity, pop a message once we are connected 11 | signaller.once('connected', function() { 12 | console.log('we have successfully connected'); 13 | }); 14 | 15 | // send through an announce message 16 | // this will occur once the websocket has been opened and active 17 | signaller.announce({ room: 'signaller-getting-started' }); 18 | -------------------------------------------------------------------------------- /examples/signalling-via-switchboard.js: -------------------------------------------------------------------------------- 1 | // to be completed -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /* jshint node: true */ 2 | 'use strict'; 3 | 4 | var detect = require('rtc-core/detect'); 5 | var extend = require('cog/extend'); 6 | var mbus = require('mbus'); 7 | var getable = require('cog/getable'); 8 | var uuid = require('cuid'); 9 | var pull = require('pull-stream'); 10 | var pushable = require('pull-pushable'); 11 | var prepare = require('rtc-signal/prepare'); 12 | var createQueue = require('pull-pushable'); 13 | 14 | // ready state constants 15 | var RS_DISCONNECTED = 0; 16 | var RS_CONNECTING = 1; 17 | var RS_CONNECTED = 2; 18 | 19 | // initialise signaller metadata so we don't have to include the package.json 20 | // TODO: make this checkable with some kind of prepublish script 21 | var metadata = { 22 | version: '6.3.0' 23 | }; 24 | 25 | /** 26 | # rtc-signaller 27 | 28 | The `rtc-signaller` module provides a transportless signalling 29 | mechanism for WebRTC. 30 | 31 | ## Purpose 32 | 33 | <<< docs/purpose.md 34 | 35 | ## Getting Started 36 | 37 | While the signaller is capable of communicating by a number of different 38 | messengers (i.e. anything that can send and receive messages over a wire) 39 | it comes with support for understanding how to connect to an 40 | [rtc-switchboard](https://github.com/rtc-io/rtc-switchboard) out of the box. 41 | 42 | The following code sample demonstrates how: 43 | 44 | <<< examples/getting-started.js 45 | 46 | <<< docs/events.md 47 | 48 | <<< docs/signalflow-diagrams.md 49 | 50 | <<< docs/identifying-participants.md 51 | 52 | ## Reference 53 | 54 | The `rtc-signaller` module is designed to be used primarily in a functional 55 | way and when called it creates a new signaller that will enable 56 | you to communicate with other peers via your messaging network. 57 | 58 | ```js 59 | // create a signaller from something that knows how to send messages 60 | var signaller = require('rtc-signaller')(messenger); 61 | ``` 62 | 63 | As demonstrated in the getting started guide, you can also pass through 64 | a string value instead of a messenger instance if you simply want to 65 | connect to an existing `rtc-switchboard` instance. 66 | 67 | **/ 68 | module.exports = function(messenger, opts) { 69 | var autoconnect = (opts || {}).autoconnect; 70 | var reconnect = (opts || {}).reconnect; 71 | var queue = createQueue(); 72 | var connectionCount = 0; 73 | 74 | // create the signaller 75 | var signaller = require('rtc-signal/signaller')(opts, bufferMessage); 76 | 77 | var announced = false; 78 | var announceTimer = 0; 79 | var readyState = RS_DISCONNECTED; 80 | 81 | function bufferMessage(message) { 82 | queue.push(message); 83 | 84 | // if we are not connected (and should autoconnect), then attempt connection 85 | if (readyState === RS_DISCONNECTED && (autoconnect === undefined || autoconnect)) { 86 | connect(); 87 | } 88 | } 89 | 90 | function handleDisconnect() { 91 | if (reconnect === undefined || reconnect) { 92 | setTimeout(connect, 50); 93 | } 94 | } 95 | 96 | /** 97 | ### `signaller.connect()` 98 | 99 | Manually connect the signaller using the supplied messenger. 100 | 101 | __NOTE:__ This should never have to be called if the default setting 102 | for `autoconnect` is used. 103 | **/ 104 | var connect = signaller.connect = function() { 105 | // if we are already connecting then do nothing 106 | if (readyState === RS_CONNECTING) { 107 | return; 108 | } 109 | 110 | // initiate the messenger 111 | readyState = RS_CONNECTING; 112 | messenger(function(err, source, sink) { 113 | if (err) { 114 | readyState = RS_DISCONNECTED; 115 | return signaller('error', err); 116 | } 117 | 118 | // increment the connection count 119 | connectionCount += 1; 120 | 121 | // flag as connected 122 | readyState = RS_CONNECTED; 123 | 124 | // pass messages to the processor 125 | pull( 126 | source, 127 | 128 | // monitor disconnection 129 | pull.through(null, function() { 130 | queue = createQueue(); 131 | readyState = RS_DISCONNECTED; 132 | signaller('disconnected'); 133 | }), 134 | pull.drain(signaller._process) 135 | ); 136 | 137 | // pass the queue to the sink 138 | pull(queue, sink); 139 | 140 | // handle disconnection 141 | signaller.removeListener('disconnected', handleDisconnect); 142 | signaller.on('disconnected', handleDisconnect); 143 | 144 | // trigger the connected event 145 | signaller('connected'); 146 | 147 | // if this is a reconnection, then reannounce 148 | if (announced && connectionCount > 1) { 149 | signaller._announce(); 150 | } 151 | }); 152 | }; 153 | 154 | /** 155 | ### announce(data?) 156 | 157 | The `announce` function of the signaller will pass an `/announce` message 158 | through the messenger network. When no additional data is supplied to 159 | this function then only the id of the signaller is sent to all active 160 | members of the messenging network. 161 | 162 | #### Joining Rooms 163 | 164 | To join a room using an announce call you simply provide the name of the 165 | room you wish to join as part of the data block that you annouce, for 166 | example: 167 | 168 | ```js 169 | signaller.announce({ room: 'testroom' }); 170 | ``` 171 | 172 | Signalling servers (such as 173 | [rtc-switchboard](https://github.com/rtc-io/rtc-switchboard)) will then 174 | place your peer connection into a room with other peers that have also 175 | announced in this room. 176 | 177 | Once you have joined a room, the server will only deliver messages that 178 | you `send` to other peers within that room. 179 | 180 | #### Providing Additional Announce Data 181 | 182 | There may be instances where you wish to send additional data as part of 183 | your announce message in your application. For instance, maybe you want 184 | to send an alias or nick as part of your announce message rather than just 185 | use the signaller's generated id. 186 | 187 | If for instance you were writing a simple chat application you could join 188 | the `webrtc` room and tell everyone your name with the following announce 189 | call: 190 | 191 | ```js 192 | signaller.announce({ 193 | room: 'webrtc', 194 | nick: 'Damon' 195 | }); 196 | ``` 197 | 198 | #### Announcing Updates 199 | 200 | The signaller is written to distinguish between initial peer announcements 201 | and peer data updates (see the docs on the announce handler below). As 202 | such it is ok to provide any data updates using the announce method also. 203 | 204 | For instance, I could send a status update as an announce message to flag 205 | that I am going offline: 206 | 207 | ```js 208 | signaller.announce({ status: 'offline' }); 209 | ``` 210 | 211 | **/ 212 | signaller.announce = function(data) { 213 | announced = true; 214 | signaller._update(data); 215 | clearTimeout(announceTimer); 216 | 217 | // send the attributes over the network 218 | return announceTimer = setTimeout(signaller._announce, (opts || {}).announceDelay || 10); 219 | }; 220 | 221 | /** 222 | ### leave() 223 | 224 | Tell the signalling server we are leaving. Calling this function is 225 | usually not required though as the signalling server should issue correct 226 | `/leave` messages when it detects a disconnect event. 227 | 228 | **/ 229 | signaller.leave = signaller.close = function() { 230 | // send the leave signal 231 | signaller.send('/leave', { id: signaller.id }); 232 | 233 | // stop announcing on reconnect 234 | signaller.removeListener('disconnected', handleDisconnect); 235 | signaller.removeListener('connected', signaller._announce); 236 | 237 | // end our current queue 238 | queue.end(); 239 | 240 | // set connected to false 241 | readyState = RS_DISCONNECTED; 242 | }; 243 | 244 | // update the signaller agent 245 | signaller._update({ agent: 'signaller@' + metadata.version }); 246 | 247 | // autoconnect 248 | if (autoconnect === undefined || autoconnect) { 249 | connect(); 250 | } 251 | 252 | return signaller; 253 | }; 254 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rtc-signaller", 3 | "version": "6.3.0", 4 | "description": "rtc.io transportless signalling for WebRTC", 5 | "directories": { 6 | "test": "test" 7 | }, 8 | "dependencies": { 9 | "cog": "^1", 10 | "cuid": "^1.2.4", 11 | "formatter": "^0.4.1", 12 | "mbus": "^2.0.0", 13 | "pull-pushable": "^1.1.4", 14 | "pull-stream": "^2.26.0", 15 | "rtc-core": "^4.0.0", 16 | "rtc-signal": "^1.2.0" 17 | }, 18 | "devDependencies": { 19 | "async": "~0.9", 20 | "broth": "^2.0.0", 21 | "browserify": "^9.0.3", 22 | "fdom": "^1.2.0", 23 | "messenger-memory": "^2.0.0", 24 | "rtc-signaller-testrun": "^1.2.2", 25 | "rtc-switchboard": "^3.0.0", 26 | "rtc-switchboard-messenger": "^2.0.3", 27 | "tap-spec": "^3.0.0", 28 | "tape": "^4.0.0", 29 | "travis-multirunner": "^3.0.0", 30 | "whisk": "^1.0.0" 31 | }, 32 | "scripts": { 33 | "test": "node test/all-node.js && npm run test-browser", 34 | "test-browser": "browserify test/all-browser.js | broth ./node_modules/travis-multirunner/start.sh | tap-spec", 35 | "gendocs": "gendocs > README.md", 36 | "hint": "jshint index.js handlers/*.js" 37 | }, 38 | "repository": { 39 | "type": "git", 40 | "url": "https://github.com/rtc-io/rtc-signaller.git" 41 | }, 42 | "keywords": [ 43 | "rtc.io", 44 | "webrtc", 45 | "signalling" 46 | ], 47 | "author": "Damon Oehlman ", 48 | "license": "Apache-2.0", 49 | "bugs": { 50 | "url": "https://github.com/rtc-io/rtc-signaller/issues" 51 | } 52 | } -------------------------------------------------------------------------------- /signaller.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./index.js'); 2 | -------------------------------------------------------------------------------- /test/all-browser.js: -------------------------------------------------------------------------------- 1 | var signaller = require('..'); 2 | var messenger = require('rtc-switchboard-messenger'); 3 | 4 | function createSignaller(opts) { 5 | return signaller(messenger(location.origin), opts); 6 | } 7 | 8 | require('./all')(location.origin); 9 | // require('rtc-signaller-testrun')(createSignaller); 10 | -------------------------------------------------------------------------------- /test/all-node.js: -------------------------------------------------------------------------------- 1 | var test = require('tape'); 2 | var server = require('./server')(); 3 | 4 | server.listen(0, function(err) { 5 | if (err) { 6 | console.error('could not start server: ', err); 7 | return process.exit(1); 8 | } 9 | 10 | require('./all')('http://localhost:' + server.address().port); 11 | 12 | test('close the server', function(t) { 13 | t.plan(1); 14 | server.close(); 15 | t.pass('server closed'); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /test/all.js: -------------------------------------------------------------------------------- 1 | var createGroup = require('./helpers/messenger-group'); 2 | // test signalling logic 3 | // require('./to')(messenger, peers); 4 | 5 | module.exports = function(signalingServer) { 6 | require('./announce-event-local'); 7 | require('./announce-concurrent'); 8 | require('./announce-customid'); 9 | require('./announce-debounce'); 10 | require('./peer-filter'); 11 | require('./set-id'); 12 | require('./events'); 13 | require('./to'); 14 | 15 | // inspect generated messages 16 | require('./announce-raw')(createGroup(3)); 17 | require('./send-falsey-parts')(createGroup(2)); 18 | require('./message-announce')(createGroup(2)); 19 | 20 | // test automatic messenger implementation 21 | require('./switchboard-auto')(signalingServer); 22 | require('./switchboard-announce')(signalingServer); 23 | require('./switchboard-announce-customid')(signalingServer); 24 | require('./switchboard-manualclose')(signalingServer); 25 | require('./switchboard-announce-concurrent')(signalingServer); 26 | }; 27 | -------------------------------------------------------------------------------- /test/announce-concurrent.js: -------------------------------------------------------------------------------- 1 | var test = require('tape'); 2 | var messenger = require('messenger-memory')({ delay: Math.random() * 200 }); 3 | var peers = [ messenger, messenger ]; 4 | var signaller = require('./helpers/signaller'); 5 | var signallers; 6 | 7 | test('create signallers', function(t) { 8 | t.plan(3); 9 | signallers = peers.map(signaller); 10 | t.equal(signallers.length, 2); 11 | t.equal(typeof signallers[0].announce, 'function', 'first signaller'); 12 | t.equal(typeof signallers[1].announce, 'function', 'second signaller'); 13 | t.end(); 14 | }); 15 | 16 | test('concurrent announce', function(t) { 17 | t.plan(5); 18 | 19 | signallers[1].on('peer:announce', function(data) { 20 | t.equal(data.name, 'Fred', 'signaller 0 announce captured by signaller 1'); 21 | t.ok(signallers[1].peers.get(data.id), 'signaller 1 has noted relationship with signaller 0'); 22 | }); 23 | 24 | signallers[0].on('peer:announce', function(data) { 25 | // once peer:1 has processed peer:0 announce it will respond 26 | // if it is a new peer 27 | t.equal(data.id, signallers[1].id, 'signaller 1 has announced itself in response'); 28 | t.ok(signallers[0].peers.get(data.id), 'signaller 0 has noted relationship with signaller 1'); 29 | }); 30 | 31 | setTimeout(function() { 32 | signallers[0].removeAllListeners(); 33 | signallers[1].removeAllListeners(); 34 | t.pass('received only the 1 announce message for each peer'); 35 | }, 1000); 36 | 37 | // peer 0 initiates the announce process 38 | signallers[0].announce({ name: 'Fred' }); 39 | signallers[1].announce({ name: 'Bob' }); 40 | }); 41 | 42 | test('ab roles have been correctly assigned', function(t) { 43 | var data0; 44 | var data1; 45 | 46 | t.plan(4); 47 | t.ok(data0 = signallers[1].peers.get(signallers[0].id), 'got data for peer 0'); 48 | t.ok(data1 = signallers[0].peers.get(signallers[1].id), 'got data for peer 1'); 49 | 50 | // ensure that data0 and data1 have inverse relationships for local and remote 51 | t.equal(data0.remote, data1.local, 'data 0 remote === data 1 local'); 52 | t.equal(data1.local, data0.remote, 'data 1 local === data 0 remote'); 53 | }); 54 | 55 | test('isMaster checks are accurate', function(t) { 56 | var alphaId = [signallers[0].id, signallers[1].id].sort()[0]; 57 | 58 | t.plan(2); 59 | 60 | if (alphaId === signallers[0].id) { 61 | t.ok(signallers[0].isMaster(signallers[1].id)); 62 | t.notOk(signallers[1].isMaster(signallers[0].id)); 63 | } 64 | else { 65 | t.notOk(signallers[0].isMaster(signallers[1].id)); 66 | t.ok(signallers[1].isMaster(signallers[0].id)); 67 | } 68 | }); 69 | -------------------------------------------------------------------------------- /test/announce-customid.js: -------------------------------------------------------------------------------- 1 | var test = require('tape'); 2 | var messenger = require('messenger-memory')({ delay: Math.random() * 200 }); 3 | var peers = [ messenger, messenger ]; 4 | var signaller = require('./helpers/signaller'); 5 | var signallers; 6 | 7 | test('create signallers', function(t) { 8 | t.plan(5); 9 | signallers = [ 10 | signaller(peers[0], { id: 1 }), 11 | signaller(peers[1], { id: 2 }) 12 | ]; 13 | t.equal(signallers.length, 2); 14 | t.equal(signallers[0].id, 1, 'signaller:0 id === 1'); 15 | t.equal(signallers[1].id, 2, 'signaller:1 id === 2'); 16 | t.equal(typeof signallers[0].announce, 'function', 'first signaller'); 17 | t.equal(typeof signallers[1].announce, 'function', 'second signaller'); 18 | t.end(); 19 | }); 20 | 21 | test('concurrent announce', function(t) { 22 | t.plan(5); 23 | 24 | signallers[1].on('peer:announce', function(data) { 25 | t.equal(data.name, 'Fred', 'signaller 0 announce captured by signaller 1'); 26 | t.ok(signallers[1].peers.get(data.id), 'signaller 1 has noted relationship with signaller 0'); 27 | }); 28 | 29 | signallers[0].on('peer:announce', function(data) { 30 | // once peer:1 has processed peer:0 announce it will respond 31 | // if it is a new peer 32 | t.equal(data.id, signallers[1].id, 'signaller 1 has announced itself in response'); 33 | t.ok(signallers[0].peers.get(data.id), 'signaller 0 has noted relationship with signaller 1'); 34 | }); 35 | 36 | setTimeout(function() { 37 | signallers[0].removeAllListeners(); 38 | signallers[1].removeAllListeners(); 39 | t.pass('received only the 1 announce message for each peer'); 40 | }, 1000); 41 | 42 | // peer 0 initiates the announce process 43 | signallers[0].announce({ name: 'Fred' }); 44 | signallers[1].announce({ name: 'Bob' }); 45 | }); 46 | 47 | test('ab roles have been correctly assigned', function(t) { 48 | var data0; 49 | var data1; 50 | 51 | t.plan(4); 52 | t.ok(data0 = signallers[1].peers.get(signallers[0].id), 'got data for peer 0'); 53 | t.ok(data1 = signallers[0].peers.get(signallers[1].id), 'got data for peer 1'); 54 | 55 | // ensure that data0 and data1 have inverse relationships for local and remote 56 | t.equal(data0.remote, data1.local, 'data 0 remote === data 1 local'); 57 | t.equal(data1.local, data0.remote, 'data 1 local === data 0 remote'); 58 | }); 59 | 60 | test('isMaster checks are accurate', function(t) { 61 | var alphaId = [signallers[0].id, signallers[1].id].sort()[0]; 62 | 63 | t.plan(2); 64 | 65 | if (alphaId === signallers[0].id) { 66 | t.ok(signallers[0].isMaster(signallers[1].id)); 67 | t.notOk(signallers[1].isMaster(signallers[0].id)); 68 | } 69 | else { 70 | t.notOk(signallers[0].isMaster(signallers[1].id)); 71 | t.ok(signallers[1].isMaster(signallers[0].id)); 72 | } 73 | }); 74 | -------------------------------------------------------------------------------- /test/announce-debounce.js: -------------------------------------------------------------------------------- 1 | var test = require('tape'); 2 | var messenger = require('messenger-memory')({ delay: Math.random() * 200 }); 3 | var peers = [ messenger, messenger ]; 4 | var signaller = require('./helpers/signaller'); 5 | var signallers; 6 | 7 | test('create signallers', function(t) { 8 | t.plan(3); 9 | signallers = peers.map(signaller); 10 | t.equal(signallers.length, 2); 11 | t.equal(typeof signallers[0].announce, 'function', 'first signaller'); 12 | t.equal(typeof signallers[1].announce, 'function', 'second signaller'); 13 | t.end(); 14 | }); 15 | 16 | test('debounce announce', function(t) { 17 | var cappedAnnounce = false; 18 | 19 | function handleAnnounce(data) { 20 | if (cappedAnnounce) { 21 | return t.fail('only expecting a single announce') 22 | } 23 | 24 | t.ok(data, 'got data'); 25 | t.equal(data.name, 'Fred', 'name matches expected'); 26 | t.equal(data.age, 80, 'age matches expected'); 27 | cappedAnnounce = true; 28 | } 29 | 30 | function handleUpdate(data) { 31 | t.fail('should not have received update'); 32 | } 33 | 34 | t.plan(4); 35 | 36 | setTimeout(function() { 37 | t.pass('only a single announce message received'); 38 | signallers[1].removeListener('peer:announce', handleAnnounce); 39 | signallers[1].removeListener('peer:update', handleUpdate); 40 | }, 500); 41 | 42 | signallers[1].on('peer:announce', handleAnnounce); 43 | signallers[1].on('peer:update', handleUpdate); 44 | signallers[0].announce({ name: 'Fred' }); 45 | signallers[0].announce({ name: 'Fred', age: 80 }); 46 | }); 47 | 48 | test('announce after debounce timeout sent normally', function(t) { 49 | t.plan(4); 50 | 51 | signallers[1].once('peer:update', function(data) { 52 | t.ok(data, 'got data'); 53 | t.equal(data.name, 'Fred', 'name matches'); 54 | t.equal(data.age, 80, 'age matches'); 55 | t.equal(data.country, 'AU', 'country'); 56 | }); 57 | 58 | signallers[0].announce({ country: 'AU' }); 59 | }); 60 | -------------------------------------------------------------------------------- /test/announce-event-local.js: -------------------------------------------------------------------------------- 1 | var test = require('tape'); 2 | var messenger = require('messenger-memory')({ delay: Math.random() * 200 }); 3 | var peers = [ messenger, messenger ]; 4 | var signaller = require('./helpers/signaller'); 5 | var signallers; 6 | 7 | test('create signallers', function(t) { 8 | t.plan(3); 9 | signallers = peers.map(signaller); 10 | t.equal(signallers.length, 2); 11 | t.equal(typeof signallers[0].announce, 'function', 'first signaller'); 12 | t.equal(typeof signallers[1].announce, 'function', 'second signaller'); 13 | t.end(); 14 | }); 15 | 16 | test('signallers trigger local:announce event', function(t) { 17 | t.plan(3); 18 | 19 | signallers[0].on('local:announce', function(data) { 20 | t.equal(data.name, 'Fred', 'signaller 0 announce captured by signaller 1'); 21 | }); 22 | 23 | signallers[1].on('local:announce', function(data) { 24 | t.equal(data.id, signallers[1].id, 'signaller 1 has announced itself in response'); 25 | }); 26 | 27 | setTimeout(function() { 28 | signallers[0].removeAllListeners(); 29 | signallers[1].removeAllListeners(); 30 | t.pass('received only the 1 announce message for each peer'); 31 | }, 1000); 32 | 33 | // peer 0 initiates the announce process 34 | signallers[0].announce({ name: 'Fred' }); 35 | signallers[1].announce({ name: 'Bob' }); 36 | }); 37 | -------------------------------------------------------------------------------- /test/announce-raw.js: -------------------------------------------------------------------------------- 1 | var extend = require('cog/extend'); 2 | var test = require('tape'); 3 | var createSignaller = require('./helpers/signaller'); 4 | var version = require('../package.json').version; 5 | var detect = require('rtc-core/detect'); 6 | 7 | var runTest = module.exports = function(group) { 8 | var s; 9 | 10 | function genAnnounce(data) { 11 | return extend({}, data, { 12 | // FIX THIS 13 | agent: 'signaller@' + version, 14 | browser: detect.browser, 15 | browserVersion: detect.browserVersion, 16 | id: s.id 17 | }); 18 | } 19 | 20 | test('create', function(t) { 21 | t.plan(2); 22 | t.ok(s = createSignaller(group.messenger), 'created'); 23 | t.ok(s.id, 'have id'); 24 | }); 25 | 26 | test('announce', function(t) { 27 | group.peers.expect(t, ['/announce', s.id ]); 28 | s.announce(); 29 | }); 30 | 31 | test('disconnect', function(t) { 32 | group.peers.expect(t, ['/leave', s.id ]); 33 | s.leave(); 34 | }); 35 | 36 | test('recreate', function(t) { 37 | t.plan(2); 38 | t.ok(s = createSignaller(group.messenger), 'created'); 39 | t.ok(s.id, 'have id'); 40 | }); 41 | 42 | test('announce with attributes', function(t) { 43 | group.peers.expect(t, ['/announce', s.id, genAnnounce({ name: 'Bob' }) ]); 44 | s.announce({ name: 'Bob' }); 45 | }); 46 | 47 | test('announce with different attributes', function(t) { 48 | group.peers.expect(t, ['/announce', s.id, genAnnounce({ name: 'Fred' }) ]); 49 | s.announce({ name: 'Fred' }); 50 | }); 51 | 52 | test('disconnect', function(t) { 53 | group.peers.expect(t, ['/leave', s.id ]); 54 | s.leave(); 55 | }); 56 | }; 57 | 58 | if (! module.parent) { 59 | runTest(require('./helpers/messenger-group')(3)); 60 | } 61 | -------------------------------------------------------------------------------- /test/browser.js: -------------------------------------------------------------------------------- 1 | var extend = require('cog/extend'); 2 | var messenger = require('rtc-switchboard-messenger'); 3 | var signaller = require('..'); 4 | 5 | require('./all'); 6 | 7 | function createSignaller(opts) { 8 | return signaller(messenger(location.origin, opts)); 9 | } 10 | 11 | require('rtc-signaller-testrun')(createSignaller); 12 | -------------------------------------------------------------------------------- /test/events.js: -------------------------------------------------------------------------------- 1 | var test = require('tape'); 2 | var messenger = require('messenger-memory')({ delay: Math.random() * 200 }); 3 | var peers = [ messenger, messenger ]; 4 | var signaller = require('./helpers/signaller'); 5 | var signallers; 6 | 7 | test('create signallers', function(t) { 8 | t.plan(3); 9 | signallers = peers.map(signaller); 10 | t.equal(signallers.length, 2); 11 | t.equal(typeof signallers[0].announce, 'function', 'first signaller'); 12 | t.equal(typeof signallers[1].announce, 'function', 'second signaller'); 13 | t.end(); 14 | }); 15 | 16 | test('peer:0 announce', function(t) { 17 | t.plan(10); 18 | 19 | signallers[1].once('peer:announce', function(data, srcData) { 20 | t.equal(data.name, 'Fred', 'signaller 0 announce captured by signaller 1'); 21 | t.ok(signallers[1].peers.get(data.id), 'signaller 1 has noted relationship with signaller 0'); 22 | 23 | t.ok(data.browser, 'browser name has been supplied in announce'); 24 | t.ok(data.browserVersion, 'browser version has been supplied in announce'); 25 | t.ok(srcData, 'have source data'); 26 | t.equal(srcData.id, signallers[0].id, 'source data matched expected'); 27 | }); 28 | 29 | signallers[0].once('peer:announce', function(data, src) { 30 | // once peer:1 has processed peer:0 announce it will respond 31 | // if it is a new peer 32 | t.equal(data.id, signallers[1].id, 'signaller 1 has announced itself in response'); 33 | t.ok(signallers[0].peers.get(data.id), 'signaller 0 has noted relationship with signaller 1'); 34 | t.ok(src, 'have source data'); 35 | t.equal(src.id, signallers[1].id, 'source data matched expected'); 36 | }); 37 | 38 | // peer 0 initiates the announce process 39 | signallers[0].announce({ name: 'Fred' }); 40 | }); 41 | 42 | test('ab roles have been correctly assigned', function(t) { 43 | var data0; 44 | var data1; 45 | 46 | t.plan(4); 47 | t.ok(data0 = signallers[1].peers.get(signallers[0].id), 'got data for peer 0'); 48 | t.ok(data1 = signallers[0].peers.get(signallers[1].id), 'got data for peer 1'); 49 | 50 | // ensure that data0 and data1 have inverse relationships for local and remote 51 | t.equal(data0.remote, data1.local, 'data 0 remote === data 1 local'); 52 | t.equal(data1.local, data0.remote, 'data 1 local === data 0 remote'); 53 | }); 54 | 55 | test('second peer:0 announce triggers peer:update event only', function(t) { 56 | var failTest = t.fail.bind(t, 'captured announce'); 57 | 58 | t.plan(4); 59 | 60 | signallers[1].once('peer:announce', failTest); 61 | signallers[1].once('peer:update', function(data, src) { 62 | signallers[1].removeListener('peer:announce', failTest); 63 | t.equal(data.name, 'Fred', 'name retransmitted'); 64 | t.equal(data.age, 30, 'age transmitted also'); 65 | t.ok(src, 'have source data'); 66 | t.equal(src, signallers[0].id, 'src == signaller:0'); 67 | }); 68 | 69 | signallers[0].announce({ age: 30 }); 70 | }); 71 | 72 | test('info for peer:0 updated in signaller:1', function(t) { 73 | var peer; 74 | 75 | t.plan(3); 76 | t.ok(peer = signallers[1].peers.get(signallers[0].id), 'got peer data'); 77 | t.equal(peer.data.name, 'Fred', 'name is Fred'); 78 | t.equal(peer.data.age, 30, 'age = 30'); 79 | }); 80 | 81 | test('peer:0 sends command', function(t) { 82 | t.plan(1); 83 | signallers[1].once('message:hello', function(text) { 84 | t.equal(text, 'there', 'got expected message'); 85 | }); 86 | 87 | signallers[0].send('/hello', 'there'); 88 | }); 89 | 90 | test('peer:0 sends non-command', function(t) { 91 | t.plan(1); 92 | signallers[1].once('data', function(args) { 93 | t.deepEqual(args, ['hello'], 'got expected message'); 94 | }); 95 | 96 | signallers[0].send('hello'); 97 | }); 98 | 99 | test('peer:0 sends non-command with args', function(t) { 100 | t.plan(1); 101 | signallers[1].once('data', function(args) { 102 | t.deepEqual(args, ['hello', 1, 2, 3], 'got expected message'); 103 | }); 104 | 105 | signallers[0].send('hello', 1, 2, 3); 106 | }); 107 | 108 | test('peer:0 sends non-command to peer:1 (with args)', function(t) { 109 | t.plan(1); 110 | signallers[1].once('data', function(args) { 111 | t.deepEqual(args, ['hello', 4, 5, 6], 'got expected message'); 112 | }); 113 | 114 | signallers[0].to(signallers[1].id).send('hello', 4, 5, 6); 115 | }); 116 | -------------------------------------------------------------------------------- /test/helpers/messenger-group.js: -------------------------------------------------------------------------------- 1 | var messenger = require('messenger-memory')(); 2 | var jsonparse = require('cog/jsonparse'); 3 | var signaller = require('../../'); 4 | var times = require('whisk/times'); 5 | 6 | module.exports = function(count) { 7 | var peers; 8 | 9 | if (count < 2) { 10 | throw new Error('require at least two peers'); 11 | } 12 | 13 | // create the peers 14 | peers = times(count - 1).map(function() { 15 | return signaller(messenger) 16 | }); 17 | 18 | // inject an expect helper 19 | peers.expect = function(t, comparisonParts) { 20 | t.plan(peers.length * (comparisonParts.length + 1)); 21 | 22 | peers.forEach(function(peer) { 23 | peer.once('rawdata', function(data) { 24 | var parts = data.split('|').map(jsonparse); 25 | 26 | t.pass('got data response'); 27 | 28 | // check the comparison against the actual parts 29 | comparisonParts.forEach(function(ref, idx) { 30 | t.deepEqual(parts[idx], ref, 'part matched'); 31 | }); 32 | }); 33 | }); 34 | }; 35 | 36 | return { 37 | messenger: messenger, 38 | peers: peers 39 | }; 40 | }; 41 | -------------------------------------------------------------------------------- /test/helpers/signaller.js: -------------------------------------------------------------------------------- 1 | var extend = require('cog/extend'); 2 | var signaller = require('../../'); 3 | 4 | module.exports = function(host, opts) { 5 | return signaller(host, extend({ reconnect: false }, opts)); 6 | }; 7 | -------------------------------------------------------------------------------- /test/message-announce.js: -------------------------------------------------------------------------------- 1 | var test = require('tape'); 2 | 3 | var runTest = module.exports = function(group) { 4 | var signaller; 5 | 6 | test('create a signaller', function(t) { 7 | t.plan(1); 8 | t.ok(signaller = require('./helpers/signaller')(group.messenger)); 9 | }); 10 | 11 | test('announce with no additional data matches expected', function(t) { 12 | t.plan(4); 13 | group.peers[0].once('rawdata', function(data) { 14 | var parts = data.split('|'); 15 | 16 | t.equal(parts.length, 3); 17 | t.equal(parts[0], '/announce'); 18 | t.equal(parts[1], signaller.id); 19 | t.doesNotThrow(function() { 20 | JSON.parse(parts[2]); 21 | }); 22 | }); 23 | 24 | signaller.announce(); 25 | }); 26 | 27 | test('announce payload contained in the json data', function(t) { 28 | t.plan(5); 29 | group.peers[0].once('rawdata', function(data) { 30 | var parts = data.split('|'); 31 | var payload; 32 | 33 | t.equal(parts.length, 3); 34 | t.equal(parts[0], '/announce'); 35 | t.equal(parts[1], signaller.id); 36 | t.doesNotThrow(function() { 37 | payload = JSON.parse(parts[2]); 38 | }); 39 | t.equal(payload.name, 'Fred'); 40 | }); 41 | 42 | signaller.announce({ name: 'Fred' }); 43 | }); 44 | }; 45 | 46 | if (! module.parent) { 47 | runTest(require('./helpers/messenger-group')(2)); 48 | } 49 | -------------------------------------------------------------------------------- /test/peer-filter.js: -------------------------------------------------------------------------------- 1 | var test = require('tape'); 2 | var messenger = require('messenger-memory')({ delay: Math.random() * 200 }); 3 | var peers = [ messenger, messenger ]; 4 | var signaller = require('./helpers/signaller'); 5 | var signallers; 6 | 7 | test('create signallers', function(t) { 8 | t.plan(3); 9 | signallers = peers.map(signaller); 10 | t.equal(signallers.length, 2); 11 | t.equal(typeof signallers[0].announce, 'function', 'first signaller'); 12 | t.equal(typeof signallers[1].announce, 'function', 'second signaller'); 13 | t.end(); 14 | }); 15 | 16 | test('filter out announce', function(t) { 17 | t.plan(5); 18 | 19 | signallers[1].once('peer:filter', function(id, data) { 20 | t.ok(data, 'Got event data'); 21 | t.equal(id, signallers[0].id, 'got id for peer'); 22 | t.equal(data.name, 'Fred', 'name is as expected'); 23 | t.equal(data.allow, true, 'Allow flag set to true'); 24 | 25 | // set allow to false 26 | data.allow = false; 27 | }); 28 | 29 | signallers[1].once('peer:announce', function(data, srcData) { 30 | t.fail('should not have received announce message'); 31 | }); 32 | 33 | setTimeout(function() { 34 | t.pass('did not receive announce'); 35 | }, 500); 36 | 37 | // peer 0 initiates the announce process 38 | signallers[0].announce({ name: 'Fred' }); 39 | }); 40 | -------------------------------------------------------------------------------- /test/primus-load.js: -------------------------------------------------------------------------------- 1 | var test = require('tape'); 2 | var loader = require('../primus-loader'); 3 | var qsa = require('fdom/qsa'); 4 | var async = require('async'); 5 | 6 | test('can load primus', function(t) { 7 | t.plan(2); 8 | loader(location.origin, function(err, p) { 9 | t.ifError(err); 10 | t.ok(p === Primus, 'primus loaded, p is a valid Primus reference'); 11 | }); 12 | }); 13 | 14 | test('remove the primus script from the page', function(t) { 15 | t.plan(1); 16 | 17 | // iterate through the scripts and remove any primus scripts 18 | qsa('script').forEach(function(script) { 19 | if (/primus\.js$/.test(script.src)) { 20 | script.parentNode.removeChild(script); 21 | } 22 | }); 23 | 24 | // unset Primus 25 | Primus = undefined; 26 | t.equal(typeof Primus, 'undefined', 'Primus now undefined'); 27 | }); 28 | 29 | test('concurrent loads pass', function(t) { 30 | var dl = function(cb) { 31 | setTimeout(function() { 32 | loader(location.origin, cb); 33 | }, 0); 34 | }; 35 | 36 | t.plan(7); 37 | async.parallel([dl, dl, dl, dl, dl], function(err, instances) { 38 | t.ifError(err); 39 | t.ok(Array.isArray(instances), 'got instances'); 40 | 41 | instances.forEach(function(instance) { 42 | t.ok(instance === Primus, 'valid Primus instance'); 43 | }) 44 | }); 45 | }); 46 | 47 | test('remove the primus script from the page', function(t) { 48 | t.plan(1); 49 | 50 | // iterate through the scripts and remove any primus scripts 51 | qsa('script').forEach(function(script) { 52 | if (/primus\.js$/.test(script.src)) { 53 | script.parentNode.removeChild(script); 54 | } 55 | }); 56 | 57 | // unset Primus 58 | Primus = undefined; 59 | t.equal(typeof Primus, 'undefined', 'Primus now undefined'); 60 | }); 61 | 62 | test('can specify an alternative script location for primus', function(t) { 63 | t.plan(2); 64 | loader(location.origin, { primusPath: '/primus/primus.js' }, function(err, p) { 65 | t.ifError(err); 66 | t.ok(p === Primus, 'primus loaded, p is a valid Primus reference'); 67 | }); 68 | }); 69 | 70 | test('remove the primus script from the page', function(t) { 71 | t.plan(1); 72 | 73 | // iterate through the scripts and remove any primus scripts 74 | qsa('script').forEach(function(script) { 75 | if (/primus\.js$/.test(script.src)) { 76 | script.parentNode.removeChild(script); 77 | } 78 | }); 79 | 80 | // unset Primus 81 | Primus = undefined; 82 | t.equal(typeof Primus, 'undefined', 'Primus now undefined'); 83 | }); 84 | -------------------------------------------------------------------------------- /test/send-falsey-parts.js: -------------------------------------------------------------------------------- 1 | var extend = require('cog/extend'); 2 | var test = require('tape'); 3 | var createSignaller = require('./helpers/signaller'); 4 | 5 | var runTest = module.exports = function(group) { 6 | var s; 7 | 8 | test('create', function(t) { 9 | t.plan(2); 10 | t.ok(s = createSignaller(group.messenger), 'created'); 11 | t.ok(s.id, 'have id'); 12 | }); 13 | 14 | test('announce', function(t) { 15 | group.peers.expect(t, ['/announce', s.id ]); 16 | s.announce(); 17 | }); 18 | 19 | test('can send simple message', function(t) { 20 | group.peers.expect(t, ['/hi', s.id ]); 21 | s.send('/hi'); 22 | }); 23 | 24 | test('can send message with 0 as arguments', function(t) { 25 | group.peers.expect(t, ['/value', s.id, 0]); 26 | s.send('/value', 0); 27 | }); 28 | 29 | test('can send message with empty string as arguments', function(t) { 30 | group.peers.expect(t, ['/message', s.id, '']); 31 | s.send('/message', ''); 32 | }); 33 | 34 | test('can send message with value false as argument', function(t) { 35 | group.peers.expect(t, ['/connected', s.id, false ]); 36 | s.send('/connected', false); 37 | }); 38 | }; 39 | 40 | if (! module.parent) { 41 | runTest(require('./helpers/messenger-group')(3)); 42 | } 43 | -------------------------------------------------------------------------------- /test/server.js: -------------------------------------------------------------------------------- 1 | module.exports = function() { 2 | var server = require('http').createServer(); 3 | var switchboard = require('rtc-switchboard')(server, { servelib: true }); 4 | 5 | switchboard.on('fake:disconnect', function(msg, spark) { 6 | spark.end(null, { reconnect: true }); 7 | }); 8 | 9 | switchboard.on('fake:leave', function(msg, spark) { 10 | spark.end(); 11 | }); 12 | 13 | return server; 14 | }; 15 | -------------------------------------------------------------------------------- /test/set-id.js: -------------------------------------------------------------------------------- 1 | var test = require('tape'); 2 | var messenger = require('messenger-memory'); 3 | var signaller = require('./helpers/signaller'); 4 | 5 | test('a new signaller without an id specified will initialize an id', function(t) { 6 | t.plan(1); 7 | t.ok(signaller(messenger()).id, 'new signaller has an id'); 8 | }); 9 | 10 | test('a new signaller with an ide specified will use that id', function(t) { 11 | var id = require('cuid')(); 12 | var sig; 13 | 14 | t.plan(1); 15 | t.equal(signaller(messenger(), { id: id }).id, id, 'got id match'); 16 | }); 17 | -------------------------------------------------------------------------------- /test/switchboard-announce-concurrent.js: -------------------------------------------------------------------------------- 1 | var test = require('tape'); 2 | var signaller = require('./helpers/signaller'); 3 | var uuid = require('cuid'); 4 | var signallers = []; 5 | var roomId = uuid(); 6 | var signallerCount = 20; 7 | var times = require('whisk/times'); 8 | var pluck = require('whisk/pluck'); 9 | 10 | module.exports = function(signalingServer) { 11 | test('create signallers', function(t) { 12 | var pending; 13 | 14 | function connectNext() { 15 | var idx; 16 | var sig = signaller(require('rtc-switchboard-messenger')(signalingServer)).once('connected', function() { 17 | t.pass('signaller ' + idx + ' connected'); 18 | 19 | if (signallers.length < signallerCount) { 20 | connectNext(); 21 | } 22 | }); 23 | 24 | signallers[idx = signallers.length] = sig; 25 | } 26 | 27 | t.plan(signallerCount); 28 | connectNext(); 29 | }); 30 | 31 | test('concurrent announce', function(t) { 32 | t.plan(signallers.length); 33 | 34 | signallers.forEach(function(sig) { 35 | var expected = signallers.map(pluck('id')).filter(function(id) { 36 | return id !== sig.id; 37 | }); 38 | 39 | function handleAnnounce(data) { 40 | var idx = expected.indexOf(data.id); 41 | 42 | if (idx >= 0) { 43 | expected.splice(idx, 1); 44 | } 45 | 46 | if (expected.length === 0) { 47 | t.pass(sig.id + ' has received all expected ennounce messages'); 48 | sig.removeListener('peer:announce', handleAnnounce); 49 | } 50 | } 51 | 52 | sig.on('peer:announce', handleAnnounce); 53 | sig.announce({ room: roomId }); 54 | }); 55 | }); 56 | 57 | test('close the signallers', function(t) { 58 | t.plan(signallers.length); 59 | signallers.forEach(function(signaller, idx) { 60 | signaller.on('disconnected', t.pass.bind(t, 'signaller ' + idx + ' disconnected')); 61 | signaller.close(); 62 | }); 63 | }); 64 | }; 65 | -------------------------------------------------------------------------------- /test/switchboard-announce-customid.js: -------------------------------------------------------------------------------- 1 | var test = require('tape'); 2 | var messenger = require('messenger-memory'); 3 | var signaller = require('./helpers/signaller'); 4 | var uuid = require('cuid'); 5 | var scope = []; 6 | var signallers = []; 7 | var roomId = uuid(); 8 | 9 | module.exports = function(signallingServer) { 10 | 11 | test('create signaller:0', function(t) { 12 | t.plan(3); 13 | t.ok(signallers[0] = require('../')(require('rtc-switchboard-messenger')(signallingServer), { id: 1 }), 'created'); 14 | t.equal(signallers[0].id, 1, 'id === 1'); 15 | signallers[0].once('connected', t.pass.bind(t, 'connected')); 16 | }); 17 | 18 | test('create signaller:1', function(t) { 19 | t.plan(3); 20 | t.ok(signallers[1] = require('../')(require('rtc-switchboard-messenger')(signallingServer), { id: 2 }), 'created'); 21 | t.equal(signallers[1].id, 2, 'id === 2'); 22 | signallers[1].once('connected', t.pass.bind(t, 'connected')); 23 | }); 24 | 25 | test('announce via websocket, custom ids', function(t) { 26 | t.plan(5); 27 | 28 | signallers[1].on('peer:announce', function(data) { 29 | t.equal(data.name, 'Fred', 'signaller 0 announce captured by signaller 1'); 30 | t.ok(signallers[1].peers.get(data.id), 'signaller 1 has noted relationship with signaller 0'); 31 | }); 32 | 33 | signallers[0].on('peer:announce', function(data) { 34 | // once peer:1 has processed peer:0 announce it will respond 35 | // if it is a new peer 36 | t.equal(data.id, signallers[1].id, 'signaller 1 has announced itself in response'); 37 | t.ok(signallers[0].peers.get(data.id), 'signaller 0 has noted relationship with signaller 1'); 38 | }); 39 | 40 | setTimeout(function() { 41 | signallers[0].removeAllListeners(); 42 | signallers[1].removeAllListeners(); 43 | t.pass('received only the 1 announce message for each peer'); 44 | }, 10000); 45 | 46 | // peer 0 initiates the announce process 47 | signallers[0].announce({ room: roomId, name: 'Fred' }); 48 | signallers[1].announce({ room: roomId, name: 'Bob' }); 49 | }); 50 | 51 | test('ab roles have been correctly assigned', function(t) { 52 | var data0; 53 | var data1; 54 | 55 | t.plan(4); 56 | t.ok(data0 = signallers[1].peers.get(signallers[0].id), 'got data for peer 0'); 57 | t.ok(data1 = signallers[0].peers.get(signallers[1].id), 'got data for peer 1'); 58 | 59 | // ensure that data0 and data1 have inverse relationships for local and remote 60 | t.equal(data0.remote, data1.local, 'data 0 remote === data 1 local'); 61 | t.equal(data1.local, data0.remote, 'data 1 local === data 0 remote'); 62 | }); 63 | 64 | test('isMaster checks are accurate', function(t) { 65 | var alphaId = [signallers[0].id, signallers[1].id].sort()[0]; 66 | 67 | t.plan(2); 68 | 69 | if (alphaId === signallers[0].id) { 70 | t.ok(signallers[0].isMaster(signallers[1].id)); 71 | t.notOk(signallers[1].isMaster(signallers[0].id)); 72 | } 73 | else { 74 | t.notOk(signallers[0].isMaster(signallers[1].id)); 75 | t.ok(signallers[1].isMaster(signallers[0].id)); 76 | } 77 | }); 78 | 79 | test('close the signallers', function(t) { 80 | t.plan(signallers.length); 81 | signallers.forEach(function(signaller, idx) { 82 | signaller.on('disconnected', t.pass.bind(t, 'signaller ' + idx + ' disconnected')); 83 | signaller.close(); 84 | }); 85 | }); 86 | }; 87 | -------------------------------------------------------------------------------- /test/switchboard-announce.js: -------------------------------------------------------------------------------- 1 | var test = require('tape'); 2 | var messenger = require('messenger-memory'); 3 | var signaller = require('./helpers/signaller'); 4 | var uuid = require('cuid'); 5 | var scope = []; 6 | var signallers = []; 7 | var roomId = uuid(); 8 | 9 | module.exports = function(signallingServer) { 10 | test('create signaller:0', function(t) { 11 | t.plan(2); 12 | t.ok(signallers[0] = require('../')(require('rtc-switchboard-messenger')(signallingServer)), 'created'); 13 | signallers[0].once('connected', t.pass.bind(t, 'connected')); 14 | }); 15 | 16 | test('create signaller:1', function(t) { 17 | t.plan(2); 18 | t.ok(signallers[1] = require('../')(require('rtc-switchboard-messenger')(signallingServer)), 'created'); 19 | signallers[1].once('connected', t.pass.bind(t, 'connected')); 20 | }); 21 | 22 | test('concurrent announce via websockets', function(t) { 23 | t.plan(5); 24 | 25 | signallers[1].on('peer:announce', function(data) { 26 | t.equal(data.name, 'Fred', 'signaller 0 announce captured by signaller 1'); 27 | t.ok(signallers[1].peers.get(data.id), 'signaller 1 has noted relationship with signaller 0'); 28 | }); 29 | 30 | signallers[0].on('peer:announce', function(data) { 31 | // once peer:1 has processed peer:0 announce it will respond 32 | // if it is a new peer 33 | t.equal(data.id, signallers[1].id, 'signaller 1 has announced itself in response'); 34 | t.ok(signallers[0].peers.get(data.id), 'signaller 0 has noted relationship with signaller 1'); 35 | }); 36 | 37 | setTimeout(function() { 38 | signallers[0].removeAllListeners(); 39 | signallers[1].removeAllListeners(); 40 | t.equal(t.assertCount, 4, 'four previous tests passed ok'); 41 | }, 10000); 42 | 43 | // peer 0 initiates the announce process 44 | signallers[0].announce({ room: roomId, name: 'Fred' }); 45 | signallers[1].announce({ room: roomId, name: 'Bob' }); 46 | }); 47 | 48 | test('ab roles have been correctly assigned', function(t) { 49 | var data0; 50 | var data1; 51 | 52 | t.plan(4); 53 | t.ok(data0 = signallers[1].peers.get(signallers[0].id), 'got data for peer 0'); 54 | t.ok(data1 = signallers[0].peers.get(signallers[1].id), 'got data for peer 1'); 55 | 56 | // ensure that data0 and data1 have inverse relationships for local and remote 57 | t.equal(data0.remote, data1.local, 'data 0 remote === data 1 local'); 58 | t.equal(data1.local, data0.remote, 'data 1 local === data 0 remote'); 59 | }); 60 | 61 | test('isMaster checks are accurate', function(t) { 62 | var alphaId = [signallers[0].id, signallers[1].id].sort()[0]; 63 | 64 | t.plan(2); 65 | 66 | if (alphaId === signallers[0].id) { 67 | t.ok(signallers[0].isMaster(signallers[1].id)); 68 | t.notOk(signallers[1].isMaster(signallers[0].id)); 69 | } 70 | else { 71 | t.notOk(signallers[0].isMaster(signallers[1].id)); 72 | t.ok(signallers[1].isMaster(signallers[0].id)); 73 | } 74 | }); 75 | 76 | test('close the signallers', function(t) { 77 | t.plan(signallers.length); 78 | signallers.forEach(function(signaller, idx) { 79 | signaller.on('disconnected', t.pass.bind(t, 'signaller ' + idx + ' disconnected')); 80 | signaller.close(); 81 | }); 82 | }); 83 | }; 84 | -------------------------------------------------------------------------------- /test/switchboard-auto.js: -------------------------------------------------------------------------------- 1 | var test = require('tape'); 2 | var signaller = require('./helpers/signaller'); 3 | var uuid = require('cuid'); 4 | var sig; 5 | 6 | module.exports = function(signallingServer) { 7 | test('can create a signalling instance that automatically connects via websockets', function(t) { 8 | t.plan(1); 9 | t.ok(sig = signaller(require('rtc-switchboard-messenger')(signallingServer)), 'signaller created'); 10 | }); 11 | 12 | test('can announce prior to the connection being established', function(t) { 13 | t.plan(2); 14 | sig.announce({ name: 'Fred', room: uuid() }); 15 | sig.once('connected', t.pass.bind(t, 'signaller open')); 16 | t.pass('Announce called without error'); 17 | }); 18 | 19 | test('close the signaller', function(t) { 20 | t.plan(1); 21 | sig.once('disconnected', t.pass.bind(t, 'disconnected')); 22 | sig.close(); 23 | }); 24 | }; 25 | -------------------------------------------------------------------------------- /test/switchboard-manualclose.js: -------------------------------------------------------------------------------- 1 | var test = require('tape'); 2 | var signaller = require('./helpers/signaller'); 3 | var uuid = require('cuid'); 4 | var sigA; 5 | var sigB; 6 | 7 | module.exports = function(signallingServer) { 8 | test('connect signaller', function(t) { 9 | t.plan(2); 10 | t.ok(sigA = signaller(require('rtc-switchboard-messenger')(signallingServer)), 'signaller created'); 11 | sigA.announce({ name: 'Fred', room: uuid() }); 12 | sigA.once('connected', t.pass.bind(t, 'signaller open')); 13 | }); 14 | 15 | test('signaller.leave(), receive disconnect event', function(t) { 16 | t.plan(1); 17 | sigA.once('disconnected', t.pass.bind(t, 'disconnected')); 18 | sigA.leave(); 19 | }); 20 | 21 | test('reconnect signaller', function(t) { 22 | t.plan(2); 23 | t.ok(sigA = signaller(require('rtc-switchboard-messenger')(signallingServer)), 'signaller created'); 24 | sigA.announce({ name: 'Fred', room: uuid() }); 25 | sigA.once('connected', t.pass.bind(t, 'signaller open')); 26 | }); 27 | 28 | test('signaller.close(), receive disconnect event', function(t) { 29 | t.plan(1); 30 | sigA.once('disconnected', t.pass.bind(t, 'disconnected')); 31 | sigA.close(); 32 | }); 33 | } 34 | -------------------------------------------------------------------------------- /test/switchboard-send-closed.js: -------------------------------------------------------------------------------- 1 | var test = require('tape'); 2 | var messenger = require('messenger-memory'); 3 | var signaller = require('./helpers/signaller'); 4 | var uuid = require('cuid'); 5 | var scope = []; 6 | var signallers = []; 7 | var roomId = uuid(); 8 | 9 | module.exports = function(signallingServer) { 10 | test('create signaller:0', function(t) { 11 | t.plan(2); 12 | t.ok(signallers[0] = require('../')(require('rtc-switchboard-messenger')(signallingServer)), 'created'); 13 | signallers[0].once('connected', t.pass.bind(t, 'connected')); 14 | }); 15 | 16 | test('create signaller:1', function(t) { 17 | t.plan(2); 18 | t.ok(signallers[1] = require('../')(require('rtc-switchboard-messenger')(signallingServer)), 'created'); 19 | signallers[1].once('connected', t.pass.bind(t, 'connected')); 20 | }); 21 | 22 | test('announce via signalling server', function(t) { 23 | t.plan(5); 24 | 25 | signallers[1].on('peer:announce', function(data) { 26 | t.equal(data.name, 'Fred', 'signaller 0 announce captured by signaller 1'); 27 | t.ok(signallers[1].peers.get(data.id), 'signaller 1 has noted relationship with signaller 0'); 28 | }); 29 | 30 | signallers[0].on('peer:announce', function(data) { 31 | // once peer:1 has processed peer:0 announce it will respond 32 | // if it is a new peer 33 | t.equal(data.id, signallers[1].id, 'signaller 1 has announced itself in response'); 34 | t.ok(signallers[0].peers.get(data.id), 'signaller 0 has noted relationship with signaller 1'); 35 | }); 36 | 37 | setTimeout(function() { 38 | signallers[0].removeAllListeners(); 39 | signallers[1].removeAllListeners(); 40 | t.pass('received only the 1 announce message for each peer'); 41 | }, 10000); 42 | 43 | // peer 0 initiates the announce process 44 | signallers[0].announce({ room: roomId, name: 'Fred' }); 45 | signallers[1].announce({ room: roomId, name: 'Bob' }); 46 | }); 47 | 48 | test('close signaller:0', function(t) { 49 | t.plan(1); 50 | signallers[0].close(); 51 | signallers[0].once('disconnected', t.pass.bind(t, 'signaller:0 disconnected')); 52 | }); 53 | 54 | test('send from 0 --> 1, reopen 0', function(t) { 55 | var timer = setTimeout(t.fail.bind(t, 'did not receive message'), 10000); 56 | 57 | t.plan(1); 58 | signallers[0].send('/hello'); 59 | signallers[1].once('message:hello', function() { 60 | clearTimeout(timer); 61 | t.pass('received hello from signaller 0'); 62 | }); 63 | }); 64 | 65 | test('close signaller:1', function(t) { 66 | t.plan(1); 67 | signallers[1].close(); 68 | signallers[1].once('disconnected', t.pass.bind(t, 'signaller:0 disconnected')); 69 | }); 70 | }; 71 | -------------------------------------------------------------------------------- /test/to.js: -------------------------------------------------------------------------------- 1 | var test = require('tape'); 2 | var messenger = require('messenger-memory')(); 3 | var signaller = require('./helpers/signaller'); 4 | var times = require('whisk/times'); 5 | var pluck = require('whisk/pluck'); 6 | var roomId = require('cuid')(); 7 | var signallers; 8 | 9 | test('/to tests: create signallers', function(t) { 10 | t.plan(1); 11 | signallers = times(5).map(function() { 12 | return signaller(messenger); 13 | }); 14 | 15 | t.equal(signallers.length, 5, 'created signallers'); 16 | }); 17 | 18 | test('/to tests: concurrent announce', function(t) { 19 | t.plan(signallers.length); 20 | 21 | signallers.forEach(function(sig) { 22 | var expected = signallers.map(pluck('id')).filter(function(id) { 23 | return id !== sig.id; 24 | }); 25 | 26 | function handleAnnounce(data) { 27 | var idx = expected.indexOf(data.id); 28 | 29 | if (idx >= 0) { 30 | expected.splice(idx, 1); 31 | } 32 | 33 | if (expected.length === 0) { 34 | t.pass(sig.id + ' has received all expected ennounce messages'); 35 | sig.removeListener('peer:announce', handleAnnounce); 36 | } 37 | } 38 | 39 | sig.on('peer:announce', handleAnnounce); 40 | sig.announce({ room: roomId }); 41 | }); 42 | }); 43 | 44 | test('/to tests: send message from 0 --> 1', function(t) { 45 | var timer = setTimeout(function() { 46 | signallers.slice(2).forEach(function(s) { 47 | s.removeListener('message:hello', handleBadMessage); 48 | }); 49 | 50 | t.pass('no other signallers received the message'); 51 | }, 500); 52 | 53 | function handleBadMessage() { 54 | t.fail('received hello when should not have'); 55 | } 56 | 57 | t.plan(2); 58 | 59 | signallers[1].once('message:hello', function() { 60 | t.pass('signaller:1 received hello'); 61 | }); 62 | 63 | signallers.slice(2).forEach(function(s) { 64 | s.on('message:hello', handleBadMessage); 65 | }); 66 | 67 | signallers[0].to(signallers[1].id).send('/hello'); 68 | }); 69 | 70 | --------------------------------------------------------------------------------