├── scripts ├── index.js └── peer.js ├── .gitignore ├── README.md ├── assets ├── n6-background.mp4 ├── n6-background.png └── icons │ ├── phone-192.png │ ├── phone-48.png │ ├── phone-96.png │ └── phone-48.svg ├── .vscode └── settings.json ├── package.json ├── manifest.json ├── sw.js ├── css └── main.css ├── record.html ├── index.html └── LICENCE /scripts/index.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Accepts a video file as input and will put an Android Device 2 | Frame around it. -------------------------------------------------------------------------------- /assets/n6-background.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaulKinlan/screenrecord/HEAD/assets/n6-background.mp4 -------------------------------------------------------------------------------- /assets/n6-background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaulKinlan/screenrecord/HEAD/assets/n6-background.png -------------------------------------------------------------------------------- /assets/icons/phone-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaulKinlan/screenrecord/HEAD/assets/icons/phone-192.png -------------------------------------------------------------------------------- /assets/icons/phone-48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaulKinlan/screenrecord/HEAD/assets/icons/phone-48.png -------------------------------------------------------------------------------- /assets/icons/phone-96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaulKinlan/screenrecord/HEAD/assets/icons/phone-96.png -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | // Place your settings in this file to overwrite default and user settings. 2 | { 3 | "editor.tabSize": 2, 4 | } -------------------------------------------------------------------------------- /assets/icons/phone-48.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "devicefram.es", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "dependencies": { 12 | "ffmpeg.js": "^3.1.9000" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Screen Recorder", 3 | "short_name": "Screen Recorder", 4 | "start_url": "./index.html", 5 | "icons": [ 6 | { 7 | "src": "./assets/icons/phone-192.png", 8 | "type": "image/png", 9 | "sizes": "192x192" 10 | } 11 | ], 12 | "scope": "/", 13 | "display": "standalone", 14 | "orientation": "portrait", 15 | "background_color": "#FF9800", 16 | "theme_color": "#FF9800" 17 | } -------------------------------------------------------------------------------- /sw.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * Device Framers 4 | * Copyright 2015 Google Inc. All rights reserved. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * https://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License 17 | * 18 | */ 19 | // verison 1 20 | 21 | self.addEventListener('install', e => { 22 | e.waitUntil( 23 | caches.open('framer').then(cache => { 24 | return cache.addAll([ 25 | './index.html', 26 | './record.html', 27 | './css/main.css', 28 | ]) 29 | .then(() => self.skipWaiting()); 30 | }) 31 | ) 32 | }); 33 | 34 | self.addEventListener('activate', event => { 35 | event.waitUntil(self.clients.claim()); 36 | }); 37 | 38 | self.addEventListener('fetch', event => { 39 | // event.respondWith( 40 | // caches.match(event.request).then(response => { 41 | // return response || fetch(event.request); 42 | // }) 43 | // ); 44 | }); -------------------------------------------------------------------------------- /css/main.css: -------------------------------------------------------------------------------- 1 | html, body { 2 | margin: 0; 3 | width: 100%; 4 | height: 100%; 5 | } 6 | 7 | html { 8 | display: flex; 9 | } 10 | 11 | body { 12 | display: flex; 13 | flex-direction: column; 14 | align-items: center; 15 | justify-content: center; 16 | background-image: radial-gradient(circle at 50% 20%, #FF9800 0%, #FF5722 100%); 17 | padding: 0 16px; 18 | color: white; 19 | font-size: 1.6em; 20 | font-family: Arial, Helvetica, sans-serif; 21 | } 22 | 23 | body.goodfile { 24 | background-image: radial-gradient(circle at 50% 20%, #bdde97 0%, #1cca23 100%); 25 | } 26 | 27 | body.badfile { 28 | background-image: radial-gradient(circle at 50% 20%, #fd7a70 0%, #e20909 100%); 29 | } 30 | 31 | body.loading #loading { 32 | display: block; 33 | } 34 | 35 | body #loading { 36 | display: none; 37 | } 38 | 39 | body.loading input[type=file] { 40 | display: none; 41 | } 42 | 43 | body.loading label { 44 | display: none; 45 | } 46 | 47 | body.waiting #sessionElement { 48 | display: block; 49 | } 50 | 51 | #recordBtn, #stopRecordingBtn, #sessionElement { 52 | display: none; 53 | } 54 | 55 | body.streaming #recordBtn { 56 | display: block; 57 | } 58 | 59 | body.recording #stopRecordingBtn{ 60 | display: block; 61 | } 62 | 63 | #preview { 64 | position: relative; 65 | height: 100%; 66 | } 67 | 68 | #preview video { 69 | width: auto; 70 | height: 100%; 71 | } 72 | 73 | #outputlog { 74 | position: relative; 75 | display: flex; 76 | flex-direction: column; 77 | height: 100%; 78 | } 79 | 80 | #log { 81 | font-size: 1rem; 82 | overflow: auto; 83 | height: 100%; 84 | text-align: left; 85 | flex-grow: 1; 86 | } 87 | -------------------------------------------------------------------------------- /record.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Screen Recorder 4 | 5 | 6 | 7 | 22 | 23 | 24 | 25 | 26 | 27 | 89 | 90 | 91 | 92 |

Enable chrome://flags#enable-usermedia-screen-capturing

93 | 103 | 104 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Android Device Framer 4 | 5 | 6 | 7 | 22 | 23 | 24 | 25 | 26 | 27 | 144 | 145 | 146 | 147 |
148 |
Open on your Android device.
149 |
150 |
151 | 152 | 153 | 154 | 164 | 165 | 166 | -------------------------------------------------------------------------------- /LICENCE: -------------------------------------------------------------------------------- 1 | All image and audio files (including *.png, *.jpg, *.svg, *.mp3, *.wav 2 | and *.ogg) are licensed under the CC-BY-NC license. All other files are 3 | licensed under the Apache 2 license. 4 | 5 | ======================================================================= 6 | 7 | 8 | Apache License 9 | -------------- 10 | 11 | Apache License 12 | Version 2.0, January 2004 13 | http://www.apache.org/licenses/ 14 | 15 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 16 | 17 | 1. Definitions. 18 | 19 | "License" shall mean the terms and conditions for use, reproduction, 20 | and distribution as defined by Sections 1 through 9 of this document. 21 | 22 | "Licensor" shall mean the copyright owner or entity authorized by 23 | the copyright owner that is granting the License. 24 | 25 | "Legal Entity" shall mean the union of the acting entity and all 26 | other entities that control, are controlled by, or are under common 27 | control with that entity. For the purposes of this definition, 28 | "control" means (i) the power, direct or indirect, to cause the 29 | direction or management of such entity, whether by contract or 30 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 31 | outstanding shares, or (iii) beneficial ownership of such entity. 32 | 33 | "You" (or "Your") shall mean an individual or Legal Entity 34 | exercising permissions granted by this License. 35 | 36 | "Source" form shall mean the preferred form for making modifications, 37 | including but not limited to software source code, documentation 38 | source, and configuration files. 39 | 40 | "Object" form shall mean any form resulting from mechanical 41 | transformation or translation of a Source form, including but 42 | not limited to compiled object code, generated documentation, 43 | and conversions to other media types. 44 | 45 | "Work" shall mean the work of authorship, whether in Source or 46 | Object form, made available under the License, as indicated by a 47 | copyright notice that is included in or attached to the work 48 | (an example is provided in the Appendix below). 49 | 50 | "Derivative Works" shall mean any work, whether in Source or Object 51 | form, that is based on (or derived from) the Work and for which the 52 | editorial revisions, annotations, elaborations, or other modifications 53 | represent, as a whole, an original work of authorship. For the purposes 54 | of this License, Derivative Works shall not include works that remain 55 | separable from, or merely link (or bind by name) to the interfaces of, 56 | the Work and Derivative Works thereof. 57 | 58 | "Contribution" shall mean any work of authorship, including 59 | the original version of the Work and any modifications or additions 60 | to that Work or Derivative Works thereof, that is intentionally 61 | submitted to Licensor for inclusion in the Work by the copyright owner 62 | or by an individual or Legal Entity authorized to submit on behalf of 63 | the copyright owner. For the purposes of this definition, "submitted" 64 | means any form of electronic, verbal, or written communication sent 65 | to the Licensor or its representatives, including but not limited to 66 | communication on electronic mailing lists, source code control systems, 67 | and issue tracking systems that are managed by, or on behalf of, the 68 | Licensor for the purpose of discussing and improving the Work, but 69 | excluding communication that is conspicuously marked or otherwise 70 | designated in writing by the copyright owner as "Not a Contribution." 71 | 72 | "Contributor" shall mean Licensor and any individual or Legal Entity 73 | on behalf of whom a Contribution has been received by Licensor and 74 | subsequently incorporated within the Work. 75 | 76 | 2. Grant of Copyright License. Subject to the terms and conditions of 77 | this License, each Contributor hereby grants to You a perpetual, 78 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 79 | copyright license to reproduce, prepare Derivative Works of, 80 | publicly display, publicly perform, sublicense, and distribute the 81 | Work and such Derivative Works in Source or Object form. 82 | 83 | 3. Grant of Patent License. Subject to the terms and conditions of 84 | this License, each Contributor hereby grants to You a perpetual, 85 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 86 | (except as stated in this section) patent license to make, have made, 87 | use, offer to sell, sell, import, and otherwise transfer the Work, 88 | where such license applies only to those patent claims licensable 89 | by such Contributor that are necessarily infringed by their 90 | Contribution(s) alone or by combination of their Contribution(s) 91 | with the Work to which such Contribution(s) was submitted. If You 92 | institute patent litigation against any entity (including a 93 | cross-claim or counterclaim in a lawsuit) alleging that the Work 94 | or a Contribution incorporated within the Work constitutes direct 95 | or contributory patent infringement, then any patent licenses 96 | granted to You under this License for that Work shall terminate 97 | as of the date such litigation is filed. 98 | 99 | 4. Redistribution. You may reproduce and distribute copies of the 100 | Work or Derivative Works thereof in any medium, with or without 101 | modifications, and in Source or Object form, provided that You 102 | meet the following conditions: 103 | 104 | (a) You must give any other recipients of the Work or 105 | Derivative Works a copy of this License; and 106 | 107 | (b) You must cause any modified files to carry prominent notices 108 | stating that You changed the files; and 109 | 110 | (c) You must retain, in the Source form of any Derivative Works 111 | that You distribute, all copyright, patent, trademark, and 112 | attribution notices from the Source form of the Work, 113 | excluding those notices that do not pertain to any part of 114 | the Derivative Works; and 115 | 116 | (d) If the Work includes a "NOTICE" text file as part of its 117 | distribution, then any Derivative Works that You distribute must 118 | include a readable copy of the attribution notices contained 119 | within such NOTICE file, excluding those notices that do not 120 | pertain to any part of the Derivative Works, in at least one 121 | of the following places: within a NOTICE text file distributed 122 | as part of the Derivative Works; within the Source form or 123 | documentation, if provided along with the Derivative Works; or, 124 | within a display generated by the Derivative Works, if and 125 | wherever such third-party notices normally appear. The contents 126 | of the NOTICE file are for informational purposes only and 127 | do not modify the License. You may add Your own attribution 128 | notices within Derivative Works that You distribute, alongside 129 | or as an addendum to the NOTICE text from the Work, provided 130 | that such additional attribution notices cannot be construed 131 | as modifying the License. 132 | 133 | You may add Your own copyright statement to Your modifications and 134 | may provide additional or different license terms and conditions 135 | for use, reproduction, or distribution of Your modifications, or 136 | for any such Derivative Works as a whole, provided Your use, 137 | reproduction, and distribution of the Work otherwise complies with 138 | the conditions stated in this License. 139 | 140 | 5. Submission of Contributions. Unless You explicitly state otherwise, 141 | any Contribution intentionally submitted for inclusion in the Work 142 | by You to the Licensor shall be under the terms and conditions of 143 | this License, without any additional terms or conditions. 144 | Notwithstanding the above, nothing herein shall supersede or modify 145 | the terms of any separate license agreement you may have executed 146 | with Licensor regarding such Contributions. 147 | 148 | 6. Trademarks. This License does not grant permission to use the trade 149 | names, trademarks, service marks, or product names of the Licensor, 150 | except as required for reasonable and customary use in describing the 151 | origin of the Work and reproducing the content of the NOTICE file. 152 | 153 | 7. Disclaimer of Warranty. Unless required by applicable law or 154 | agreed to in writing, Licensor provides the Work (and each 155 | Contributor provides its Contributions) on an "AS IS" BASIS, 156 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 157 | implied, including, without limitation, any warranties or conditions 158 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 159 | PARTICULAR PURPOSE. You are solely responsible for determining the 160 | appropriateness of using or redistributing the Work and assume any 161 | risks associated with Your exercise of permissions under this License. 162 | 163 | 8. Limitation of Liability. In no event and under no legal theory, 164 | whether in tort (including negligence), contract, or otherwise, 165 | unless required by applicable law (such as deliberate and grossly 166 | negligent acts) or agreed to in writing, shall any Contributor be 167 | liable to You for damages, including any direct, indirect, special, 168 | incidental, or consequential damages of any character arising as a 169 | result of this License or out of the use or inability to use the 170 | Work (including but not limited to damages for loss of goodwill, 171 | work stoppage, computer failure or malfunction, or any and all 172 | other commercial damages or losses), even if such Contributor 173 | has been advised of the possibility of such damages. 174 | 175 | 9. Accepting Warranty or Additional Liability. While redistributing 176 | the Work or Derivative Works thereof, You may choose to offer, 177 | and charge a fee for, acceptance of support, warranty, indemnity, 178 | or other liability obligations and/or rights consistent with this 179 | License. However, in accepting such obligations, You may act only 180 | on Your own behalf and on Your sole responsibility, not on behalf 181 | of any other Contributor, and only if You agree to indemnify, 182 | defend, and hold each Contributor harmless for any liability 183 | incurred by, or claims asserted against, such Contributor by reason 184 | of your accepting any such warranty or additional liability. 185 | 186 | END OF TERMS AND CONDITIONS 187 | 188 | All image and audio files (including *.png, *.jpg, *.svg, *.mp3, *.wav 189 | and *.ogg) are licensed under the CC-BY-NC license. All other files are 190 | licensed under the Apache 2 license. 191 | 192 | CC-BY-NC License 193 | ---------------- 194 | 195 | Attribution-NonCommercial-ShareAlike 4.0 International 196 | 197 | ======================================================================= 198 | 199 | Creative Commons Corporation ("Creative Commons") is not a law firm and 200 | does not provide legal services or legal advice. Distribution of 201 | Creative Commons public licenses does not create a lawyer-client or 202 | other relationship. Creative Commons makes its licenses and related 203 | information available on an "as-is" basis. Creative Commons gives no 204 | warranties regarding its licenses, any material licensed under their 205 | terms and conditions, or any related information. Creative Commons 206 | disclaims all liability for damages resulting from their use to the 207 | fullest extent possible. 208 | 209 | Using Creative Commons Public Licenses 210 | 211 | Creative Commons public licenses provide a standard set of terms and 212 | conditions that creators and other rights holders may use to share 213 | original works of authorship and other material subject to copyright 214 | and certain other rights specified in the public license below. The 215 | following considerations are for informational purposes only, are not 216 | exhaustive, and do not form part of our licenses. 217 | 218 | Considerations for licensors: Our public licenses are 219 | intended for use by those authorized to give the public 220 | permission to use material in ways otherwise restricted by 221 | copyright and certain other rights. Our licenses are 222 | irrevocable. Licensors should read and understand the terms 223 | and conditions of the license they choose before applying it. 224 | Licensors should also secure all rights necessary before 225 | applying our licenses so that the public can reuse the 226 | material as expected. Licensors should clearly mark any 227 | material not subject to the license. This includes other CC- 228 | licensed material, or material used under an exception or 229 | limitation to copyright. More considerations for licensors: 230 | wiki.creativecommons.org/Considerations_for_licensors 231 | 232 | Considerations for the public: By using one of our public 233 | licenses, a licensor grants the public permission to use the 234 | licensed material under specified terms and conditions. If 235 | the licensor's permission is not necessary for any reason--for 236 | example, because of any applicable exception or limitation to 237 | copyright--then that use is not regulated by the license. Our 238 | licenses grant only permissions under copyright and certain 239 | other rights that a licensor has authority to grant. Use of 240 | the licensed material may still be restricted for other 241 | reasons, including because others have copyright or other 242 | rights in the material. A licensor may make special requests, 243 | such as asking that all changes be marked or described. 244 | Although not required by our licenses, you are encouraged to 245 | respect those requests where reasonable. More_considerations 246 | for the public: 247 | wiki.creativecommons.org/Considerations_for_licensees 248 | 249 | ======================================================================= 250 | 251 | Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International 252 | Public License 253 | 254 | By exercising the Licensed Rights (defined below), You accept and agree 255 | to be bound by the terms and conditions of this Creative Commons 256 | Attribution-NonCommercial-ShareAlike 4.0 International Public License 257 | ("Public License"). To the extent this Public License may be 258 | interpreted as a contract, You are granted the Licensed Rights in 259 | consideration of Your acceptance of these terms and conditions, and the 260 | Licensor grants You such rights in consideration of benefits the 261 | Licensor receives from making the Licensed Material available under 262 | these terms and conditions. 263 | 264 | 265 | Section 1 -- Definitions. 266 | 267 | a. Adapted Material means material subject to Copyright and Similar 268 | Rights that is derived from or based upon the Licensed Material 269 | and in which the Licensed Material is translated, altered, 270 | arranged, transformed, or otherwise modified in a manner requiring 271 | permission under the Copyright and Similar Rights held by the 272 | Licensor. For purposes of this Public License, where the Licensed 273 | Material is a musical work, performance, or sound recording, 274 | Adapted Material is always produced where the Licensed Material is 275 | synched in timed relation with a moving image. 276 | 277 | b. Adapter's License means the license You apply to Your Copyright 278 | and Similar Rights in Your contributions to Adapted Material in 279 | accordance with the terms and conditions of this Public License. 280 | 281 | c. BY-NC-SA Compatible License means a license listed at 282 | creativecommons.org/compatiblelicenses, approved by Creative 283 | Commons as essentially the equivalent of this Public License. 284 | 285 | d. Copyright and Similar Rights means copyright and/or similar rights 286 | closely related to copyright including, without limitation, 287 | performance, broadcast, sound recording, and Sui Generis Database 288 | Rights, without regard to how the rights are labeled or 289 | categorized. For purposes of this Public License, the rights 290 | specified in Section 2(b)(1)-(2) are not Copyright and Similar 291 | Rights. 292 | 293 | e. Effective Technological Measures means those measures that, in the 294 | absence of proper authority, may not be circumvented under laws 295 | fulfilling obligations under Article 11 of the WIPO Copyright 296 | Treaty adopted on December 20, 1996, and/or similar international 297 | agreements. 298 | 299 | f. Exceptions and Limitations means fair use, fair dealing, and/or 300 | any other exception or limitation to Copyright and Similar Rights 301 | that applies to Your use of the Licensed Material. 302 | 303 | g. License Elements means the license attributes listed in the name 304 | of a Creative Commons Public License. The License Elements of this 305 | Public License are Attribution, NonCommercial, and ShareAlike. 306 | 307 | h. Licensed Material means the artistic or literary work, database, 308 | or other material to which the Licensor applied this Public 309 | License. 310 | 311 | i. Licensed Rights means the rights granted to You subject to the 312 | terms and conditions of this Public License, which are limited to 313 | all Copyright and Similar Rights that apply to Your use of the 314 | Licensed Material and that the Licensor has authority to license. 315 | 316 | j. Licensor means the individual(s) or entity(ies) granting rights 317 | under this Public License. 318 | 319 | k. NonCommercial means not primarily intended for or directed towards 320 | commercial advantage or monetary compensation. For purposes of 321 | this Public License, the exchange of the Licensed Material for 322 | other material subject to Copyright and Similar Rights by digital 323 | file-sharing or similar means is NonCommercial provided there is 324 | no payment of monetary compensation in connection with the 325 | exchange. 326 | 327 | l. Share means to provide material to the public by any means or 328 | process that requires permission under the Licensed Rights, such 329 | as reproduction, public display, public performance, distribution, 330 | dissemination, communication, or importation, and to make material 331 | available to the public including in ways that members of the 332 | public may access the material from a place and at a time 333 | individually chosen by them. 334 | 335 | m. Sui Generis Database Rights means rights other than copyright 336 | resulting from Directive 96/9/EC of the European Parliament and of 337 | the Council of 11 March 1996 on the legal protection of databases, 338 | as amended and/or succeeded, as well as other essentially 339 | equivalent rights anywhere in the world. 340 | 341 | n. You means the individual or entity exercising the Licensed Rights 342 | under this Public License. Your has a corresponding meaning. 343 | 344 | 345 | Section 2 -- Scope. 346 | 347 | a. License grant. 348 | 349 | 1. Subject to the terms and conditions of this Public License, 350 | the Licensor hereby grants You a worldwide, royalty-free, 351 | non-sublicensable, non-exclusive, irrevocable license to 352 | exercise the Licensed Rights in the Licensed Material to: 353 | 354 | a. reproduce and Share the Licensed Material, in whole or 355 | in part, for NonCommercial purposes only; and 356 | 357 | b. produce, reproduce, and Share Adapted Material for 358 | NonCommercial purposes only. 359 | 360 | 2. Exceptions and Limitations. For the avoidance of doubt, where 361 | Exceptions and Limitations apply to Your use, this Public 362 | License does not apply, and You do not need to comply with 363 | its terms and conditions. 364 | 365 | 3. Term. The term of this Public License is specified in Section 366 | 6(a). 367 | 368 | 4. Media and formats; technical modifications allowed. The 369 | Licensor authorizes You to exercise the Licensed Rights in 370 | all media and formats whether now known or hereafter created, 371 | and to make technical modifications necessary to do so. The 372 | Licensor waives and/or agrees not to assert any right or 373 | authority to forbid You from making technical modifications 374 | necessary to exercise the Licensed Rights, including 375 | technical modifications necessary to circumvent Effective 376 | Technological Measures. For purposes of this Public License, 377 | simply making modifications authorized by this Section 2(a) 378 | (4) never produces Adapted Material. 379 | 380 | 5. Downstream recipients. 381 | 382 | a. Offer from the Licensor -- Licensed Material. Every 383 | recipient of the Licensed Material automatically 384 | receives an offer from the Licensor to exercise the 385 | Licensed Rights under the terms and conditions of this 386 | Public License. 387 | 388 | b. Additional offer from the Licensor -- Adapted Material. 389 | Every recipient of Adapted Material from You 390 | automatically receives an offer from the Licensor to 391 | exercise the Licensed Rights in the Adapted Material 392 | under the conditions of the Adapter's License You apply. 393 | 394 | c. No downstream restrictions. You may not offer or impose 395 | any additional or different terms or conditions on, or 396 | apply any Effective Technological Measures to, the 397 | Licensed Material if doing so restricts exercise of the 398 | Licensed Rights by any recipient of the Licensed 399 | Material. 400 | 401 | 6. No endorsement. Nothing in this Public License constitutes or 402 | may be construed as permission to assert or imply that You 403 | are, or that Your use of the Licensed Material is, connected 404 | with, or sponsored, endorsed, or granted official status by, 405 | the Licensor or others designated to receive attribution as 406 | provided in Section 3(a)(1)(A)(i). 407 | 408 | b. Other rights. 409 | 410 | 1. Moral rights, such as the right of integrity, are not 411 | licensed under this Public License, nor are publicity, 412 | privacy, and/or other similar personality rights; however, to 413 | the extent possible, the Licensor waives and/or agrees not to 414 | assert any such rights held by the Licensor to the limited 415 | extent necessary to allow You to exercise the Licensed 416 | Rights, but not otherwise. 417 | 418 | 2. Patent and trademark rights are not licensed under this 419 | Public License. 420 | 421 | 3. To the extent possible, the Licensor waives any right to 422 | collect royalties from You for the exercise of the Licensed 423 | Rights, whether directly or through a collecting society 424 | under any voluntary or waivable statutory or compulsory 425 | licensing scheme. In all other cases the Licensor expressly 426 | reserves any right to collect such royalties, including when 427 | the Licensed Material is used other than for NonCommercial 428 | purposes. 429 | 430 | 431 | Section 3 -- License Conditions. 432 | 433 | Your exercise of the Licensed Rights is expressly made subject to the 434 | following conditions. 435 | 436 | a. Attribution. 437 | 438 | 1. If You Share the Licensed Material (including in modified 439 | form), You must: 440 | 441 | a. retain the following if it is supplied by the Licensor 442 | with the Licensed Material: 443 | 444 | i. identification of the creator(s) of the Licensed 445 | Material and any others designated to receive 446 | attribution, in any reasonable manner requested by 447 | the Licensor (including by pseudonym if 448 | designated); 449 | 450 | ii. a copyright notice; 451 | 452 | iii. a notice that refers to this Public License; 453 | 454 | iv. a notice that refers to the disclaimer of 455 | warranties; 456 | 457 | v. a URI or hyperlink to the Licensed Material to the 458 | extent reasonably practicable; 459 | 460 | b. indicate if You modified the Licensed Material and 461 | retain an indication of any previous modifications; and 462 | 463 | c. indicate the Licensed Material is licensed under this 464 | Public License, and include the text of, or the URI or 465 | hyperlink to, this Public License. 466 | 467 | 2. You may satisfy the conditions in Section 3(a)(1) in any 468 | reasonable manner based on the medium, means, and context in 469 | which You Share the Licensed Material. For example, it may be 470 | reasonable to satisfy the conditions by providing a URI or 471 | hyperlink to a resource that includes the required 472 | information. 473 | 3. If requested by the Licensor, You must remove any of the 474 | information required by Section 3(a)(1)(A) to the extent 475 | reasonably practicable. 476 | 477 | b. ShareAlike. 478 | 479 | In addition to the conditions in Section 3(a), if You Share 480 | Adapted Material You produce, the following conditions also apply. 481 | 482 | 1. The Adapter's License You apply must be a Creative Commons 483 | license with the same License Elements, this version or 484 | later, or a BY-NC-SA Compatible License. 485 | 486 | 2. You must include the text of, or the URI or hyperlink to, the 487 | Adapter's License You apply. You may satisfy this condition 488 | in any reasonable manner based on the medium, means, and 489 | context in which You Share Adapted Material. 490 | 491 | 3. You may not offer or impose any additional or different terms 492 | or conditions on, or apply any Effective Technological 493 | Measures to, Adapted Material that restrict exercise of the 494 | rights granted under the Adapter's License You apply. 495 | 496 | 497 | Section 4 -- Sui Generis Database Rights. 498 | 499 | Where the Licensed Rights include Sui Generis Database Rights that 500 | apply to Your use of the Licensed Material: 501 | 502 | a. for the avoidance of doubt, Section 2(a)(1) grants You the right 503 | to extract, reuse, reproduce, and Share all or a substantial 504 | portion of the contents of the database for NonCommercial purposes 505 | only; 506 | 507 | b. if You include all or a substantial portion of the database 508 | contents in a database in which You have Sui Generis Database 509 | Rights, then the database in which You have Sui Generis Database 510 | Rights (but not its individual contents) is Adapted Material, 511 | including for purposes of Section 3(b); and 512 | 513 | c. You must comply with the conditions in Section 3(a) if You Share 514 | all or a substantial portion of the contents of the database. 515 | 516 | For the avoidance of doubt, this Section 4 supplements and does not 517 | replace Your obligations under this Public License where the Licensed 518 | Rights include other Copyright and Similar Rights. 519 | 520 | 521 | Section 5 -- Disclaimer of Warranties and Limitation of Liability. 522 | 523 | a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE 524 | EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS 525 | AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF 526 | ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS, 527 | IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION, 528 | WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR 529 | PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS, 530 | ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT 531 | KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT 532 | ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU. 533 | 534 | b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE 535 | TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION, 536 | NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT, 537 | INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES, 538 | COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR 539 | USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN 540 | ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR 541 | DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR 542 | IN PART, THIS LIMITATION MAY NOT APPLY TO YOU. 543 | 544 | c. The disclaimer of warranties and limitation of liability provided 545 | above shall be interpreted in a manner that, to the extent 546 | possible, most closely approximates an absolute disclaimer and 547 | waiver of all liability. 548 | 549 | 550 | Section 6 -- Term and Termination. 551 | 552 | a. This Public License applies for the term of the Copyright and 553 | Similar Rights licensed here. However, if You fail to comply with 554 | this Public License, then Your rights under this Public License 555 | terminate automatically. 556 | 557 | b. Where Your right to use the Licensed Material has terminated under 558 | Section 6(a), it reinstates: 559 | 560 | 1. automatically as of the date the violation is cured, provided 561 | it is cured within 30 days of Your discovery of the 562 | violation; or 563 | 564 | 2. upon express reinstatement by the Licensor. 565 | 566 | For the avoidance of doubt, this Section 6(b) does not affect any 567 | right the Licensor may have to seek remedies for Your violations 568 | of this Public License. 569 | 570 | c. For the avoidance of doubt, the Licensor may also offer the 571 | Licensed Material under separate terms or conditions or stop 572 | distributing the Licensed Material at any time; however, doing so 573 | will not terminate this Public License. 574 | 575 | d. Sections 1, 5, 6, 7, and 8 survive termination of this Public 576 | License. 577 | 578 | 579 | Section 7 -- Other Terms and Conditions. 580 | 581 | a. The Licensor shall not be bound by any additional or different 582 | terms or conditions communicated by You unless expressly agreed. 583 | 584 | b. Any arrangements, understandings, or agreements regarding the 585 | Licensed Material not stated herein are separate from and 586 | independent of the terms and conditions of this Public License. 587 | 588 | 589 | Section 8 -- Interpretation. 590 | 591 | a. For the avoidance of doubt, this Public License does not, and 592 | shall not be interpreted to, reduce, limit, restrict, or impose 593 | conditions on any use of the Licensed Material that could lawfully 594 | be made without permission under this Public License. 595 | 596 | b. To the extent possible, if any provision of this Public License is 597 | deemed unenforceable, it shall be automatically reformed to the 598 | minimum extent necessary to make it enforceable. If the provision 599 | cannot be reformed, it shall be severed from this Public License 600 | without affecting the enforceability of the remaining terms and 601 | conditions. 602 | 603 | c. No term or condition of this Public License will be waived and no 604 | failure to comply consented to unless expressly agreed to by the 605 | Licensor. 606 | 607 | d. Nothing in this Public License constitutes or may be interpreted 608 | as a limitation upon, or waiver of, any privileges and immunities 609 | that apply to the Licensor or You, including from the legal 610 | processes of any jurisdiction or authority. 611 | 612 | ======================================================================= 613 | 614 | Creative Commons is not a party to its public licenses. 615 | Notwithstanding, Creative Commons may elect to apply one of its public 616 | licenses to material it publishes and in those instances will be 617 | considered the "Licensor." Except for the limited purpose of indicating 618 | that material is shared under a Creative Commons public license or as 619 | otherwise permitted by the Creative Commons policies published at 620 | creativecommons.org/policies, Creative Commons does not authorize the 621 | use of the trademark "Creative Commons" or any other trademark or logo 622 | of Creative Commons without its prior written consent including, 623 | without limitation, in connection with any unauthorized modifications 624 | to any of its public licenses or any other arrangements, 625 | understandings, or agreements concerning use of licensed material. For 626 | the avoidance of doubt, this paragraph does not form part of the public 627 | licenses. 628 | 629 | Creative Commons may be contacted at creativecommons.org. 630 | 631 | -------------------------------------------------------------------------------- /scripts/peer.js: -------------------------------------------------------------------------------- 1 | /*! peerjs build:0.3.13, development. Copyright(c) 2013 Michelle Bu */(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o util.chunkedMTU) { 186 | this._sendChunks(blob); 187 | return; 188 | } 189 | 190 | // DataChannel currently only supports strings. 191 | if (!util.supports.sctp) { 192 | util.blobToBinaryString(blob, function(str) { 193 | self._bufferedSend(str); 194 | }); 195 | } else if (!util.supports.binaryBlob) { 196 | // We only do this if we really need to (e.g. blobs are not supported), 197 | // because this conversion is costly. 198 | util.blobToArrayBuffer(blob, function(ab) { 199 | self._bufferedSend(ab); 200 | }); 201 | } else { 202 | this._bufferedSend(blob); 203 | } 204 | } else { 205 | this._bufferedSend(data); 206 | } 207 | } 208 | 209 | DataConnection.prototype._bufferedSend = function(msg) { 210 | if (this._buffering || !this._trySend(msg)) { 211 | this._buffer.push(msg); 212 | this.bufferSize = this._buffer.length; 213 | } 214 | } 215 | 216 | // Returns true if the send succeeds. 217 | DataConnection.prototype._trySend = function(msg) { 218 | try { 219 | this._dc.send(msg); 220 | } catch (e) { 221 | this._buffering = true; 222 | 223 | var self = this; 224 | setTimeout(function() { 225 | // Try again. 226 | self._buffering = false; 227 | self._tryBuffer(); 228 | }, 100); 229 | return false; 230 | } 231 | return true; 232 | } 233 | 234 | // Try to send the first message in the buffer. 235 | DataConnection.prototype._tryBuffer = function() { 236 | if (this._buffer.length === 0) { 237 | return; 238 | } 239 | 240 | var msg = this._buffer[0]; 241 | 242 | if (this._trySend(msg)) { 243 | this._buffer.shift(); 244 | this.bufferSize = this._buffer.length; 245 | this._tryBuffer(); 246 | } 247 | } 248 | 249 | DataConnection.prototype._sendChunks = function(blob) { 250 | var blobs = util.chunk(blob); 251 | for (var i = 0, ii = blobs.length; i < ii; i += 1) { 252 | var blob = blobs[i]; 253 | this.send(blob, true); 254 | } 255 | } 256 | 257 | DataConnection.prototype.handleMessage = function(message) { 258 | var payload = message.payload; 259 | 260 | switch (message.type) { 261 | case 'ANSWER': 262 | this._peerBrowser = payload.browser; 263 | 264 | // Forward to negotiator 265 | Negotiator.handleSDP(message.type, this, payload.sdp); 266 | break; 267 | case 'CANDIDATE': 268 | Negotiator.handleCandidate(this, payload.candidate); 269 | break; 270 | default: 271 | util.warn('Unrecognized message type:', message.type, 'from peer:', this.peer); 272 | break; 273 | } 274 | } 275 | 276 | module.exports = DataConnection; 277 | 278 | },{"./negotiator":5,"./util":8,"eventemitter3":9,"reliable":12}],3:[function(require,module,exports){ 279 | window.Socket = require('./socket'); 280 | window.MediaConnection = require('./mediaconnection'); 281 | window.DataConnection = require('./dataconnection'); 282 | window.Peer = require('./peer'); 283 | window.RTCPeerConnection = require('./adapter').RTCPeerConnection; 284 | window.RTCSessionDescription = require('./adapter').RTCSessionDescription; 285 | window.RTCIceCandidate = require('./adapter').RTCIceCandidate; 286 | window.Negotiator = require('./negotiator'); 287 | window.util = require('./util'); 288 | window.BinaryPack = require('js-binarypack'); 289 | 290 | },{"./adapter":1,"./dataconnection":2,"./mediaconnection":4,"./negotiator":5,"./peer":6,"./socket":7,"./util":8,"js-binarypack":10}],4:[function(require,module,exports){ 291 | var util = require('./util'); 292 | var EventEmitter = require('eventemitter3'); 293 | var Negotiator = require('./negotiator'); 294 | 295 | /** 296 | * Wraps the streaming interface between two Peers. 297 | */ 298 | function MediaConnection(peer, provider, options) { 299 | if (!(this instanceof MediaConnection)) return new MediaConnection(peer, provider, options); 300 | EventEmitter.call(this); 301 | 302 | this.options = util.extend({}, options); 303 | 304 | this.open = false; 305 | this.type = 'media'; 306 | this.peer = peer; 307 | this.provider = provider; 308 | this.metadata = this.options.metadata; 309 | this.localStream = this.options._stream; 310 | 311 | this.id = this.options.connectionId || MediaConnection._idPrefix + util.randomToken(); 312 | if (this.localStream) { 313 | Negotiator.startConnection( 314 | this, 315 | {_stream: this.localStream, originator: true} 316 | ); 317 | } 318 | }; 319 | 320 | util.inherits(MediaConnection, EventEmitter); 321 | 322 | MediaConnection._idPrefix = 'mc_'; 323 | 324 | MediaConnection.prototype.addStream = function(remoteStream) { 325 | util.log('Receiving stream', remoteStream); 326 | 327 | this.remoteStream = remoteStream; 328 | this.emit('stream', remoteStream); // Should we call this `open`? 329 | 330 | }; 331 | 332 | MediaConnection.prototype.handleMessage = function(message) { 333 | var payload = message.payload; 334 | 335 | switch (message.type) { 336 | case 'ANSWER': 337 | // Forward to negotiator 338 | Negotiator.handleSDP(message.type, this, payload.sdp); 339 | this.open = true; 340 | break; 341 | case 'CANDIDATE': 342 | Negotiator.handleCandidate(this, payload.candidate); 343 | break; 344 | default: 345 | util.warn('Unrecognized message type:', message.type, 'from peer:', this.peer); 346 | break; 347 | } 348 | } 349 | 350 | MediaConnection.prototype.answer = function(stream) { 351 | if (this.localStream) { 352 | util.warn('Local stream already exists on this MediaConnection. Are you answering a call twice?'); 353 | return; 354 | } 355 | 356 | this.options._payload._stream = stream; 357 | 358 | this.localStream = stream; 359 | Negotiator.startConnection( 360 | this, 361 | this.options._payload 362 | ) 363 | // Retrieve lost messages stored because PeerConnection not set up. 364 | var messages = this.provider._getMessages(this.id); 365 | for (var i = 0, ii = messages.length; i < ii; i += 1) { 366 | this.handleMessage(messages[i]); 367 | } 368 | this.open = true; 369 | }; 370 | 371 | /** 372 | * Exposed functionality for users. 373 | */ 374 | 375 | /** Allows user to close connection. */ 376 | MediaConnection.prototype.close = function() { 377 | if (!this.open) { 378 | return; 379 | } 380 | this.open = false; 381 | Negotiator.cleanup(this); 382 | this.emit('close') 383 | }; 384 | 385 | module.exports = MediaConnection; 386 | 387 | },{"./negotiator":5,"./util":8,"eventemitter3":9}],5:[function(require,module,exports){ 388 | var util = require('./util'); 389 | var RTCPeerConnection = require('./adapter').RTCPeerConnection; 390 | var RTCSessionDescription = require('./adapter').RTCSessionDescription; 391 | var RTCIceCandidate = require('./adapter').RTCIceCandidate; 392 | 393 | /** 394 | * Manages all negotiations between Peers. 395 | */ 396 | var Negotiator = { 397 | pcs: { 398 | data: {}, 399 | media: {} 400 | }, // type => {peerId: {pc_id: pc}}. 401 | //providers: {}, // provider's id => providers (there may be multiple providers/client. 402 | queue: [] // connections that are delayed due to a PC being in use. 403 | } 404 | 405 | Negotiator._idPrefix = 'pc_'; 406 | 407 | /** Returns a PeerConnection object set up correctly (for data, media). */ 408 | Negotiator.startConnection = function(connection, options) { 409 | var pc = Negotiator._getPeerConnection(connection, options); 410 | 411 | if (connection.type === 'media' && options._stream) { 412 | // Add the stream. 413 | pc.addStream(options._stream); 414 | } 415 | 416 | // Set the connection's PC. 417 | connection.pc = connection.peerConnection = pc; 418 | // What do we need to do now? 419 | if (options.originator) { 420 | if (connection.type === 'data') { 421 | // Create the datachannel. 422 | var config = {}; 423 | // Dropping reliable:false support, since it seems to be crashing 424 | // Chrome. 425 | /*if (util.supports.sctp && !options.reliable) { 426 | // If we have canonical reliable support... 427 | config = {maxRetransmits: 0}; 428 | }*/ 429 | // Fallback to ensure older browsers don't crash. 430 | if (!util.supports.sctp) { 431 | config = {reliable: options.reliable}; 432 | } 433 | var dc = pc.createDataChannel(connection.label, config); 434 | connection.initialize(dc); 435 | } 436 | 437 | if (!util.supports.onnegotiationneeded) { 438 | Negotiator._makeOffer(connection); 439 | } 440 | } else { 441 | Negotiator.handleSDP('OFFER', connection, options.sdp); 442 | } 443 | } 444 | 445 | Negotiator._getPeerConnection = function(connection, options) { 446 | if (!Negotiator.pcs[connection.type]) { 447 | util.error(connection.type + ' is not a valid connection type. Maybe you overrode the `type` property somewhere.'); 448 | } 449 | 450 | if (!Negotiator.pcs[connection.type][connection.peer]) { 451 | Negotiator.pcs[connection.type][connection.peer] = {}; 452 | } 453 | var peerConnections = Negotiator.pcs[connection.type][connection.peer]; 454 | 455 | var pc; 456 | // Not multiplexing while FF and Chrome have not-great support for it. 457 | /*if (options.multiplex) { 458 | ids = Object.keys(peerConnections); 459 | for (var i = 0, ii = ids.length; i < ii; i += 1) { 460 | pc = peerConnections[ids[i]]; 461 | if (pc.signalingState === 'stable') { 462 | break; // We can go ahead and use this PC. 463 | } 464 | } 465 | } else */ 466 | if (options.pc) { // Simplest case: PC id already provided for us. 467 | pc = Negotiator.pcs[connection.type][connection.peer][options.pc]; 468 | } 469 | 470 | if (!pc || pc.signalingState !== 'stable') { 471 | pc = Negotiator._startPeerConnection(connection); 472 | } 473 | return pc; 474 | } 475 | 476 | /* 477 | Negotiator._addProvider = function(provider) { 478 | if ((!provider.id && !provider.disconnected) || !provider.socket.open) { 479 | // Wait for provider to obtain an ID. 480 | provider.on('open', function(id) { 481 | Negotiator._addProvider(provider); 482 | }); 483 | } else { 484 | Negotiator.providers[provider.id] = provider; 485 | } 486 | }*/ 487 | 488 | 489 | /** Start a PC. */ 490 | Negotiator._startPeerConnection = function(connection) { 491 | util.log('Creating RTCPeerConnection.'); 492 | 493 | var id = Negotiator._idPrefix + util.randomToken(); 494 | var optional = {}; 495 | 496 | if (connection.type === 'data' && !util.supports.sctp) { 497 | optional = {optional: [{RtpDataChannels: true}]}; 498 | } else if (connection.type === 'media') { 499 | // Interop req for chrome. 500 | optional = {optional: [{DtlsSrtpKeyAgreement: true}]}; 501 | } 502 | 503 | var pc = new RTCPeerConnection(connection.provider.options.config, optional); 504 | Negotiator.pcs[connection.type][connection.peer][id] = pc; 505 | 506 | Negotiator._setupListeners(connection, pc, id); 507 | 508 | return pc; 509 | } 510 | 511 | /** Set up various WebRTC listeners. */ 512 | Negotiator._setupListeners = function(connection, pc, pc_id) { 513 | var peerId = connection.peer; 514 | var connectionId = connection.id; 515 | var provider = connection.provider; 516 | 517 | // ICE CANDIDATES. 518 | util.log('Listening for ICE candidates.'); 519 | pc.onicecandidate = function(evt) { 520 | if (evt.candidate) { 521 | util.log('Received ICE candidates for:', connection.peer); 522 | provider.socket.send({ 523 | type: 'CANDIDATE', 524 | payload: { 525 | candidate: evt.candidate, 526 | type: connection.type, 527 | connectionId: connection.id 528 | }, 529 | dst: peerId 530 | }); 531 | } 532 | }; 533 | 534 | pc.oniceconnectionstatechange = function() { 535 | switch (pc.iceConnectionState) { 536 | case 'disconnected': 537 | case 'failed': 538 | util.log('iceConnectionState is disconnected, closing connections to ' + peerId); 539 | connection.close(); 540 | break; 541 | case 'completed': 542 | pc.onicecandidate = util.noop; 543 | break; 544 | } 545 | }; 546 | 547 | // Fallback for older Chrome impls. 548 | pc.onicechange = pc.oniceconnectionstatechange; 549 | 550 | // ONNEGOTIATIONNEEDED (Chrome) 551 | util.log('Listening for `negotiationneeded`'); 552 | pc.onnegotiationneeded = function() { 553 | util.log('`negotiationneeded` triggered'); 554 | if (pc.signalingState == 'stable') { 555 | Negotiator._makeOffer(connection); 556 | } else { 557 | util.log('onnegotiationneeded triggered when not stable. Is another connection being established?'); 558 | } 559 | }; 560 | 561 | // DATACONNECTION. 562 | util.log('Listening for data channel'); 563 | // Fired between offer and answer, so options should already be saved 564 | // in the options hash. 565 | pc.ondatachannel = function(evt) { 566 | util.log('Received data channel'); 567 | var dc = evt.channel; 568 | var connection = provider.getConnection(peerId, connectionId); 569 | connection.initialize(dc); 570 | }; 571 | 572 | // MEDIACONNECTION. 573 | util.log('Listening for remote stream'); 574 | pc.onaddstream = function(evt) { 575 | util.log('Received remote stream'); 576 | var stream = evt.stream; 577 | var connection = provider.getConnection(peerId, connectionId); 578 | // 10/10/2014: looks like in Chrome 38, onaddstream is triggered after 579 | // setting the remote description. Our connection object in these cases 580 | // is actually a DATA connection, so addStream fails. 581 | // TODO: This is hopefully just a temporary fix. We should try to 582 | // understand why this is happening. 583 | if (connection.type === 'media') { 584 | connection.addStream(stream); 585 | } 586 | }; 587 | } 588 | 589 | Negotiator.cleanup = function(connection) { 590 | util.log('Cleaning up PeerConnection to ' + connection.peer); 591 | 592 | var pc = connection.pc; 593 | 594 | if (!!pc && (pc.readyState !== 'closed' || pc.signalingState !== 'closed')) { 595 | pc.close(); 596 | connection.pc = null; 597 | } 598 | } 599 | 600 | Negotiator._makeOffer = function(connection) { 601 | var pc = connection.pc; 602 | pc.createOffer(function(offer) { 603 | util.log('Created offer.'); 604 | 605 | if (!util.supports.sctp && connection.type === 'data' && connection.reliable) { 606 | offer.sdp = Reliable.higherBandwidthSDP(offer.sdp); 607 | } 608 | 609 | pc.setLocalDescription(offer, function() { 610 | util.log('Set localDescription: offer', 'for:', connection.peer); 611 | connection.provider.socket.send({ 612 | type: 'OFFER', 613 | payload: { 614 | sdp: offer, 615 | type: connection.type, 616 | label: connection.label, 617 | connectionId: connection.id, 618 | reliable: connection.reliable, 619 | serialization: connection.serialization, 620 | metadata: connection.metadata, 621 | browser: util.browser 622 | }, 623 | dst: connection.peer 624 | }); 625 | }, function(err) { 626 | connection.provider.emitError('webrtc', err); 627 | util.log('Failed to setLocalDescription, ', err); 628 | }); 629 | }, function(err) { 630 | connection.provider.emitError('webrtc', err); 631 | util.log('Failed to createOffer, ', err); 632 | }, connection.options.constraints); 633 | } 634 | 635 | Negotiator._makeAnswer = function(connection) { 636 | var pc = connection.pc; 637 | 638 | pc.createAnswer(function(answer) { 639 | util.log('Created answer.'); 640 | 641 | if (!util.supports.sctp && connection.type === 'data' && connection.reliable) { 642 | answer.sdp = Reliable.higherBandwidthSDP(answer.sdp); 643 | } 644 | 645 | pc.setLocalDescription(answer, function() { 646 | util.log('Set localDescription: answer', 'for:', connection.peer); 647 | connection.provider.socket.send({ 648 | type: 'ANSWER', 649 | payload: { 650 | sdp: answer, 651 | type: connection.type, 652 | connectionId: connection.id, 653 | browser: util.browser 654 | }, 655 | dst: connection.peer 656 | }); 657 | }, function(err) { 658 | connection.provider.emitError('webrtc', err); 659 | util.log('Failed to setLocalDescription, ', err); 660 | }); 661 | }, function(err) { 662 | connection.provider.emitError('webrtc', err); 663 | util.log('Failed to create answer, ', err); 664 | }); 665 | } 666 | 667 | /** Handle an SDP. */ 668 | Negotiator.handleSDP = function(type, connection, sdp) { 669 | sdp = new RTCSessionDescription(sdp); 670 | var pc = connection.pc; 671 | 672 | util.log('Setting remote description', sdp); 673 | pc.setRemoteDescription(sdp, function() { 674 | util.log('Set remoteDescription:', type, 'for:', connection.peer); 675 | 676 | if (type === 'OFFER') { 677 | Negotiator._makeAnswer(connection); 678 | } 679 | }, function(err) { 680 | connection.provider.emitError('webrtc', err); 681 | util.log('Failed to setRemoteDescription, ', err); 682 | }); 683 | } 684 | 685 | /** Handle a candidate. */ 686 | Negotiator.handleCandidate = function(connection, ice) { 687 | var candidate = ice.candidate; 688 | var sdpMLineIndex = ice.sdpMLineIndex; 689 | connection.pc.addIceCandidate(new RTCIceCandidate({ 690 | sdpMLineIndex: sdpMLineIndex, 691 | candidate: candidate 692 | })); 693 | util.log('Added ICE candidate for:', connection.peer); 694 | } 695 | 696 | module.exports = Negotiator; 697 | 698 | },{"./adapter":1,"./util":8}],6:[function(require,module,exports){ 699 | var util = require('./util'); 700 | var EventEmitter = require('eventemitter3'); 701 | var Socket = require('./socket'); 702 | var MediaConnection = require('./mediaconnection'); 703 | var DataConnection = require('./dataconnection'); 704 | 705 | /** 706 | * A peer who can initiate connections with other peers. 707 | */ 708 | function Peer(id, options) { 709 | if (!(this instanceof Peer)) return new Peer(id, options); 710 | EventEmitter.call(this); 711 | 712 | // Deal with overloading 713 | if (id && id.constructor == Object) { 714 | options = id; 715 | id = undefined; 716 | } else if (id) { 717 | // Ensure id is a string 718 | id = id.toString(); 719 | } 720 | // 721 | 722 | // Configurize options 723 | options = util.extend({ 724 | debug: 0, // 1: Errors, 2: Warnings, 3: All logs 725 | host: util.CLOUD_HOST, 726 | port: util.CLOUD_PORT, 727 | key: 'peerjs', 728 | path: '/', 729 | token: util.randomToken(), 730 | config: util.defaultConfig 731 | }, options); 732 | this.options = options; 733 | // Detect relative URL host. 734 | if (options.host === '/') { 735 | options.host = window.location.hostname; 736 | } 737 | // Set path correctly. 738 | if (options.path[0] !== '/') { 739 | options.path = '/' + options.path; 740 | } 741 | if (options.path[options.path.length - 1] !== '/') { 742 | options.path += '/'; 743 | } 744 | 745 | // Set whether we use SSL to same as current host 746 | if (options.secure === undefined && options.host !== util.CLOUD_HOST) { 747 | options.secure = util.isSecure(); 748 | } 749 | // Set a custom log function if present 750 | if (options.logFunction) { 751 | util.setLogFunction(options.logFunction); 752 | } 753 | util.setLogLevel(options.debug); 754 | // 755 | 756 | // Sanity checks 757 | // Ensure WebRTC supported 758 | if (!util.supports.audioVideo && !util.supports.data ) { 759 | this._delayedAbort('browser-incompatible', 'The current browser does not support WebRTC'); 760 | return; 761 | } 762 | // Ensure alphanumeric id 763 | if (!util.validateId(id)) { 764 | this._delayedAbort('invalid-id', 'ID "' + id + '" is invalid'); 765 | return; 766 | } 767 | // Ensure valid key 768 | if (!util.validateKey(options.key)) { 769 | this._delayedAbort('invalid-key', 'API KEY "' + options.key + '" is invalid'); 770 | return; 771 | } 772 | // Ensure not using unsecure cloud server on SSL page 773 | if (options.secure && options.host === '0.peerjs.com') { 774 | this._delayedAbort('ssl-unavailable', 775 | 'The cloud server currently does not support HTTPS. Please run your own PeerServer to use HTTPS.'); 776 | return; 777 | } 778 | // 779 | 780 | // States. 781 | this.destroyed = false; // Connections have been killed 782 | this.disconnected = false; // Connection to PeerServer killed but P2P connections still active 783 | this.open = false; // Sockets and such are not yet open. 784 | // 785 | 786 | // References 787 | this.connections = {}; // DataConnections for this peer. 788 | this._lostMessages = {}; // src => [list of messages] 789 | // 790 | 791 | // Start the server connection 792 | this._initializeServerConnection(); 793 | if (id) { 794 | this._initialize(id); 795 | } else { 796 | this._retrieveId(); 797 | } 798 | // 799 | } 800 | 801 | util.inherits(Peer, EventEmitter); 802 | 803 | // Initialize the 'socket' (which is actually a mix of XHR streaming and 804 | // websockets.) 805 | Peer.prototype._initializeServerConnection = function() { 806 | var self = this; 807 | this.socket = new Socket(this.options.secure, this.options.host, this.options.port, this.options.path, this.options.key); 808 | this.socket.on('message', function(data) { 809 | self._handleMessage(data); 810 | }); 811 | this.socket.on('error', function(error) { 812 | self._abort('socket-error', error); 813 | }); 814 | this.socket.on('disconnected', function() { 815 | // If we haven't explicitly disconnected, emit error and disconnect. 816 | if (!self.disconnected) { 817 | self.emitError('network', 'Lost connection to server.'); 818 | self.disconnect(); 819 | } 820 | }); 821 | this.socket.on('close', function() { 822 | // If we haven't explicitly disconnected, emit error. 823 | if (!self.disconnected) { 824 | self._abort('socket-closed', 'Underlying socket is already closed.'); 825 | } 826 | }); 827 | }; 828 | 829 | /** Get a unique ID from the server via XHR. */ 830 | Peer.prototype._retrieveId = function(cb) { 831 | var self = this; 832 | var http = new XMLHttpRequest(); 833 | var protocol = this.options.secure ? 'https://' : 'http://'; 834 | var url = protocol + this.options.host + //':' + this.options.port + 835 | this.options.path + this.options.key + '/id'; 836 | var queryString = '?ts=' + new Date().getTime() + '' + Math.random(); 837 | url += queryString; 838 | 839 | // If there's no ID we need to wait for one before trying to init socket. 840 | http.open('get', url, true); 841 | http.onerror = function(e) { 842 | util.error('Error retrieving ID', e); 843 | var pathError = ''; 844 | if (self.options.path === '/' && self.options.host !== util.CLOUD_HOST) { 845 | pathError = ' If you passed in a `path` to your self-hosted PeerServer, ' + 846 | 'you\'ll also need to pass in that same path when creating a new ' + 847 | 'Peer.'; 848 | } 849 | self._abort('server-error', 'Could not get an ID from the server.' + pathError); 850 | }; 851 | http.onreadystatechange = function() { 852 | if (http.readyState !== 4) { 853 | return; 854 | } 855 | if (http.status !== 200) { 856 | http.onerror(); 857 | return; 858 | } 859 | self._initialize(http.responseText); 860 | }; 861 | http.send(null); 862 | }; 863 | 864 | /** Initialize a connection with the server. */ 865 | Peer.prototype._initialize = function(id) { 866 | this.id = id; 867 | this.socket.start(this.id, this.options.token); 868 | }; 869 | 870 | /** Handles messages from the server. */ 871 | Peer.prototype._handleMessage = function(message) { 872 | var type = message.type; 873 | var payload = message.payload; 874 | var peer = message.src; 875 | var connection; 876 | 877 | switch (type) { 878 | case 'OPEN': // The connection to the server is open. 879 | this.emit('open', this.id); 880 | this.open = true; 881 | break; 882 | case 'ERROR': // Server error. 883 | this._abort('server-error', payload.msg); 884 | break; 885 | case 'ID-TAKEN': // The selected ID is taken. 886 | this._abort('unavailable-id', 'ID `' + this.id + '` is taken'); 887 | break; 888 | case 'INVALID-KEY': // The given API key cannot be found. 889 | this._abort('invalid-key', 'API KEY "' + this.options.key + '" is invalid'); 890 | break; 891 | 892 | // 893 | case 'LEAVE': // Another peer has closed its connection to this peer. 894 | util.log('Received leave message from', peer); 895 | this._cleanupPeer(peer); 896 | break; 897 | 898 | case 'EXPIRE': // The offer sent to a peer has expired without response. 899 | this.emitError('peer-unavailable', 'Could not connect to peer ' + peer); 900 | break; 901 | case 'OFFER': // we should consider switching this to CALL/CONNECT, but this is the least breaking option. 902 | var connectionId = payload.connectionId; 903 | connection = this.getConnection(peer, connectionId); 904 | 905 | if (connection) { 906 | util.warn('Offer received for existing Connection ID:', connectionId); 907 | //connection.handleMessage(message); 908 | } else { 909 | // Create a new connection. 910 | if (payload.type === 'media') { 911 | connection = new MediaConnection(peer, this, { 912 | connectionId: connectionId, 913 | _payload: payload, 914 | metadata: payload.metadata 915 | }); 916 | this._addConnection(peer, connection); 917 | this.emit('call', connection); 918 | } else if (payload.type === 'data') { 919 | connection = new DataConnection(peer, this, { 920 | connectionId: connectionId, 921 | _payload: payload, 922 | metadata: payload.metadata, 923 | label: payload.label, 924 | serialization: payload.serialization, 925 | reliable: payload.reliable 926 | }); 927 | this._addConnection(peer, connection); 928 | this.emit('connection', connection); 929 | } else { 930 | util.warn('Received malformed connection type:', payload.type); 931 | return; 932 | } 933 | // Find messages. 934 | var messages = this._getMessages(connectionId); 935 | for (var i = 0, ii = messages.length; i < ii; i += 1) { 936 | connection.handleMessage(messages[i]); 937 | } 938 | } 939 | break; 940 | default: 941 | if (!payload) { 942 | util.warn('You received a malformed message from ' + peer + ' of type ' + type); 943 | return; 944 | } 945 | 946 | var id = payload.connectionId; 947 | connection = this.getConnection(peer, id); 948 | 949 | if (connection && connection.pc) { 950 | // Pass it on. 951 | connection.handleMessage(message); 952 | } else if (id) { 953 | // Store for possible later use 954 | this._storeMessage(id, message); 955 | } else { 956 | util.warn('You received an unrecognized message:', message); 957 | } 958 | break; 959 | } 960 | }; 961 | 962 | /** Stores messages without a set up connection, to be claimed later. */ 963 | Peer.prototype._storeMessage = function(connectionId, message) { 964 | if (!this._lostMessages[connectionId]) { 965 | this._lostMessages[connectionId] = []; 966 | } 967 | this._lostMessages[connectionId].push(message); 968 | }; 969 | 970 | /** Retrieve messages from lost message store */ 971 | Peer.prototype._getMessages = function(connectionId) { 972 | var messages = this._lostMessages[connectionId]; 973 | if (messages) { 974 | delete this._lostMessages[connectionId]; 975 | return messages; 976 | } else { 977 | return []; 978 | } 979 | }; 980 | 981 | /** 982 | * Returns a DataConnection to the specified peer. See documentation for a 983 | * complete list of options. 984 | */ 985 | Peer.prototype.connect = function(peer, options) { 986 | if (this.disconnected) { 987 | util.warn('You cannot connect to a new Peer because you called ' + 988 | '.disconnect() on this Peer and ended your connection with the ' + 989 | 'server. You can create a new Peer to reconnect, or call reconnect ' + 990 | 'on this peer if you believe its ID to still be available.'); 991 | this.emitError('disconnected', 'Cannot connect to new Peer after disconnecting from server.'); 992 | return; 993 | } 994 | var connection = new DataConnection(peer, this, options); 995 | this._addConnection(peer, connection); 996 | return connection; 997 | }; 998 | 999 | /** 1000 | * Returns a MediaConnection to the specified peer. See documentation for a 1001 | * complete list of options. 1002 | */ 1003 | Peer.prototype.call = function(peer, stream, options) { 1004 | if (this.disconnected) { 1005 | util.warn('You cannot connect to a new Peer because you called ' + 1006 | '.disconnect() on this Peer and ended your connection with the ' + 1007 | 'server. You can create a new Peer to reconnect.'); 1008 | this.emitError('disconnected', 'Cannot connect to new Peer after disconnecting from server.'); 1009 | return; 1010 | } 1011 | if (!stream) { 1012 | util.error('To call a peer, you must provide a stream from your browser\'s `getUserMedia`.'); 1013 | return; 1014 | } 1015 | options = options || {}; 1016 | options._stream = stream; 1017 | var call = new MediaConnection(peer, this, options); 1018 | this._addConnection(peer, call); 1019 | return call; 1020 | }; 1021 | 1022 | /** Add a data/media connection to this peer. */ 1023 | Peer.prototype._addConnection = function(peer, connection) { 1024 | if (!this.connections[peer]) { 1025 | this.connections[peer] = []; 1026 | } 1027 | this.connections[peer].push(connection); 1028 | }; 1029 | 1030 | /** Retrieve a data/media connection for this peer. */ 1031 | Peer.prototype.getConnection = function(peer, id) { 1032 | var connections = this.connections[peer]; 1033 | if (!connections) { 1034 | return null; 1035 | } 1036 | for (var i = 0, ii = connections.length; i < ii; i++) { 1037 | if (connections[i].id === id) { 1038 | return connections[i]; 1039 | } 1040 | } 1041 | return null; 1042 | }; 1043 | 1044 | Peer.prototype._delayedAbort = function(type, message) { 1045 | var self = this; 1046 | util.setZeroTimeout(function(){ 1047 | self._abort(type, message); 1048 | }); 1049 | }; 1050 | 1051 | /** 1052 | * Destroys the Peer and emits an error message. 1053 | * The Peer is not destroyed if it's in a disconnected state, in which case 1054 | * it retains its disconnected state and its existing connections. 1055 | */ 1056 | Peer.prototype._abort = function(type, message) { 1057 | util.error('Aborting!'); 1058 | if (!this._lastServerId) { 1059 | this.destroy(); 1060 | } else { 1061 | this.disconnect(); 1062 | } 1063 | this.emitError(type, message); 1064 | }; 1065 | 1066 | /** Emits a typed error message. */ 1067 | Peer.prototype.emitError = function(type, err) { 1068 | util.error('Error:', err); 1069 | if (typeof err === 'string') { 1070 | err = new Error(err); 1071 | } 1072 | err.type = type; 1073 | this.emit('error', err); 1074 | }; 1075 | 1076 | /** 1077 | * Destroys the Peer: closes all active connections as well as the connection 1078 | * to the server. 1079 | * Warning: The peer can no longer create or accept connections after being 1080 | * destroyed. 1081 | */ 1082 | Peer.prototype.destroy = function() { 1083 | if (!this.destroyed) { 1084 | this._cleanup(); 1085 | this.disconnect(); 1086 | this.destroyed = true; 1087 | } 1088 | }; 1089 | 1090 | 1091 | /** Disconnects every connection on this peer. */ 1092 | Peer.prototype._cleanup = function() { 1093 | if (this.connections) { 1094 | var peers = Object.keys(this.connections); 1095 | for (var i = 0, ii = peers.length; i < ii; i++) { 1096 | this._cleanupPeer(peers[i]); 1097 | } 1098 | } 1099 | this.emit('close'); 1100 | }; 1101 | 1102 | /** Closes all connections to this peer. */ 1103 | Peer.prototype._cleanupPeer = function(peer) { 1104 | var connections = this.connections[peer]; 1105 | for (var j = 0, jj = connections.length; j < jj; j += 1) { 1106 | connections[j].close(); 1107 | } 1108 | }; 1109 | 1110 | /** 1111 | * Disconnects the Peer's connection to the PeerServer. Does not close any 1112 | * active connections. 1113 | * Warning: The peer can no longer create or accept connections after being 1114 | * disconnected. It also cannot reconnect to the server. 1115 | */ 1116 | Peer.prototype.disconnect = function() { 1117 | var self = this; 1118 | util.setZeroTimeout(function(){ 1119 | if (!self.disconnected) { 1120 | self.disconnected = true; 1121 | self.open = false; 1122 | if (self.socket) { 1123 | self.socket.close(); 1124 | } 1125 | self.emit('disconnected', self.id); 1126 | self._lastServerId = self.id; 1127 | self.id = null; 1128 | } 1129 | }); 1130 | }; 1131 | 1132 | /** Attempts to reconnect with the same ID. */ 1133 | Peer.prototype.reconnect = function() { 1134 | if (this.disconnected && !this.destroyed) { 1135 | util.log('Attempting reconnection to server with ID ' + this._lastServerId); 1136 | this.disconnected = false; 1137 | this._initializeServerConnection(); 1138 | this._initialize(this._lastServerId); 1139 | } else if (this.destroyed) { 1140 | throw new Error('This peer cannot reconnect to the server. It has already been destroyed.'); 1141 | } else if (!this.disconnected && !this.open) { 1142 | // Do nothing. We're still connecting the first time. 1143 | util.error('In a hurry? We\'re still trying to make the initial connection!'); 1144 | } else { 1145 | throw new Error('Peer ' + this.id + ' cannot reconnect because it is not disconnected from the server!'); 1146 | } 1147 | }; 1148 | 1149 | /** 1150 | * Get a list of available peer IDs. If you're running your own server, you'll 1151 | * want to set allow_discovery: true in the PeerServer options. If you're using 1152 | * the cloud server, email team@peerjs.com to get the functionality enabled for 1153 | * your key. 1154 | */ 1155 | Peer.prototype.listAllPeers = function(cb) { 1156 | cb = cb || function() {}; 1157 | var self = this; 1158 | var http = new XMLHttpRequest(); 1159 | var protocol = this.options.secure ? 'https://' : 'http://'; 1160 | var url = protocol + this.options.host + //':' + this.options.port + 1161 | this.options.path + this.options.key + '/peers'; 1162 | var queryString = '?ts=' + new Date().getTime() + '' + Math.random(); 1163 | url += queryString; 1164 | 1165 | // If there's no ID we need to wait for one before trying to init socket. 1166 | http.open('get', url, true); 1167 | http.onerror = function(e) { 1168 | self._abort('server-error', 'Could not get peers from the server.'); 1169 | cb([]); 1170 | }; 1171 | http.onreadystatechange = function() { 1172 | if (http.readyState !== 4) { 1173 | return; 1174 | } 1175 | if (http.status === 401) { 1176 | var helpfulError = ''; 1177 | if (self.options.host !== util.CLOUD_HOST) { 1178 | helpfulError = 'It looks like you\'re using the cloud server. You can email ' + 1179 | 'team@peerjs.com to enable peer listing for your API key.'; 1180 | } else { 1181 | helpfulError = 'You need to enable `allow_discovery` on your self-hosted ' + 1182 | 'PeerServer to use this feature.'; 1183 | } 1184 | cb([]); 1185 | throw new Error('It doesn\'t look like you have permission to list peers IDs. ' + helpfulError); 1186 | } else if (http.status !== 200) { 1187 | cb([]); 1188 | } else { 1189 | cb(JSON.parse(http.responseText)); 1190 | } 1191 | }; 1192 | http.send(null); 1193 | }; 1194 | 1195 | module.exports = Peer; 1196 | 1197 | },{"./dataconnection":2,"./mediaconnection":4,"./socket":7,"./util":8,"eventemitter3":9}],7:[function(require,module,exports){ 1198 | var util = require('./util'); 1199 | var EventEmitter = require('eventemitter3'); 1200 | 1201 | /** 1202 | * An abstraction on top of WebSockets and XHR streaming to provide fastest 1203 | * possible connection for peers. 1204 | */ 1205 | function Socket(secure, host, port, path, key) { 1206 | if (!(this instanceof Socket)) return new Socket(secure, host, port, path, key); 1207 | 1208 | EventEmitter.call(this); 1209 | 1210 | // Disconnected manually. 1211 | this.disconnected = false; 1212 | this._queue = []; 1213 | 1214 | var httpProtocol = secure ? 'https://' : 'http://'; 1215 | var wsProtocol = secure ? 'wss://' : 'ws://'; 1216 | this._httpUrl = httpProtocol + host + path + key; 1217 | this._wsUrl = wsProtocol + host + path + 'peerjs?key=' + key; 1218 | } 1219 | 1220 | util.inherits(Socket, EventEmitter); 1221 | 1222 | 1223 | /** Check in with ID or get one from server. */ 1224 | Socket.prototype.start = function(id, token) { 1225 | this.id = id; 1226 | 1227 | this._httpUrl += '/' + id + '/' + token; 1228 | this._wsUrl += '&id=' + id + '&token=' + token; 1229 | 1230 | this._startXhrStream(); 1231 | this._startWebSocket(); 1232 | } 1233 | 1234 | 1235 | /** Start up websocket communications. */ 1236 | Socket.prototype._startWebSocket = function(id) { 1237 | var self = this; 1238 | 1239 | if (this._socket) { 1240 | return; 1241 | } 1242 | 1243 | this._socket = new WebSocket(this._wsUrl); 1244 | 1245 | this._socket.onmessage = function(event) { 1246 | try { 1247 | var data = JSON.parse(event.data); 1248 | } catch(e) { 1249 | util.log('Invalid server message', event.data); 1250 | return; 1251 | } 1252 | self.emit('message', data); 1253 | }; 1254 | 1255 | this._socket.onclose = function(event) { 1256 | util.log('Socket closed.'); 1257 | self.disconnected = true; 1258 | self.emit('disconnected'); 1259 | }; 1260 | 1261 | // Take care of the queue of connections if necessary and make sure Peer knows 1262 | // socket is open. 1263 | this._socket.onopen = function() { 1264 | if (self._timeout) { 1265 | clearTimeout(self._timeout); 1266 | setTimeout(function(){ 1267 | self._http.abort(); 1268 | self._http = null; 1269 | }, 5000); 1270 | } 1271 | self._sendQueuedMessages(); 1272 | util.log('Socket open'); 1273 | }; 1274 | } 1275 | 1276 | /** Start XHR streaming. */ 1277 | Socket.prototype._startXhrStream = function(n) { 1278 | try { 1279 | var self = this; 1280 | this._http = new XMLHttpRequest(); 1281 | this._http._index = 1; 1282 | this._http._streamIndex = n || 0; 1283 | this._http.open('post', this._httpUrl + '/id?i=' + this._http._streamIndex, true); 1284 | this._http.onerror = function() { 1285 | // If we get an error, likely something went wrong. 1286 | // Stop streaming. 1287 | clearTimeout(self._timeout); 1288 | self.emit('disconnected'); 1289 | } 1290 | this._http.onreadystatechange = function() { 1291 | if (this.readyState == 2 && this.old) { 1292 | this.old.abort(); 1293 | delete this.old; 1294 | } else if (this.readyState > 2 && this.status === 200 && this.responseText) { 1295 | self._handleStream(this); 1296 | } 1297 | }; 1298 | this._http.send(null); 1299 | this._setHTTPTimeout(); 1300 | } catch(e) { 1301 | util.log('XMLHttpRequest not available; defaulting to WebSockets'); 1302 | } 1303 | } 1304 | 1305 | 1306 | /** Handles onreadystatechange response as a stream. */ 1307 | Socket.prototype._handleStream = function(http) { 1308 | // 3 and 4 are loading/done state. All others are not relevant. 1309 | var messages = http.responseText.split('\n'); 1310 | 1311 | // Check to see if anything needs to be processed on buffer. 1312 | if (http._buffer) { 1313 | while (http._buffer.length > 0) { 1314 | var index = http._buffer.shift(); 1315 | var bufferedMessage = messages[index]; 1316 | try { 1317 | bufferedMessage = JSON.parse(bufferedMessage); 1318 | } catch(e) { 1319 | http._buffer.shift(index); 1320 | break; 1321 | } 1322 | this.emit('message', bufferedMessage); 1323 | } 1324 | } 1325 | 1326 | var message = messages[http._index]; 1327 | if (message) { 1328 | http._index += 1; 1329 | // Buffering--this message is incomplete and we'll get to it next time. 1330 | // This checks if the httpResponse ended in a `\n`, in which case the last 1331 | // element of messages should be the empty string. 1332 | if (http._index === messages.length) { 1333 | if (!http._buffer) { 1334 | http._buffer = []; 1335 | } 1336 | http._buffer.push(http._index - 1); 1337 | } else { 1338 | try { 1339 | message = JSON.parse(message); 1340 | } catch(e) { 1341 | util.log('Invalid server message', message); 1342 | return; 1343 | } 1344 | this.emit('message', message); 1345 | } 1346 | } 1347 | } 1348 | 1349 | Socket.prototype._setHTTPTimeout = function() { 1350 | var self = this; 1351 | this._timeout = setTimeout(function() { 1352 | var old = self._http; 1353 | if (!self._wsOpen()) { 1354 | self._startXhrStream(old._streamIndex + 1); 1355 | self._http.old = old; 1356 | } else { 1357 | old.abort(); 1358 | } 1359 | }, 25000); 1360 | } 1361 | 1362 | /** Is the websocket currently open? */ 1363 | Socket.prototype._wsOpen = function() { 1364 | return this._socket && this._socket.readyState == 1; 1365 | } 1366 | 1367 | /** Send queued messages. */ 1368 | Socket.prototype._sendQueuedMessages = function() { 1369 | for (var i = 0, ii = this._queue.length; i < ii; i += 1) { 1370 | this.send(this._queue[i]); 1371 | } 1372 | } 1373 | 1374 | /** Exposed send for DC & Peer. */ 1375 | Socket.prototype.send = function(data) { 1376 | if (this.disconnected) { 1377 | return; 1378 | } 1379 | 1380 | // If we didn't get an ID yet, we can't yet send anything so we should queue 1381 | // up these messages. 1382 | if (!this.id) { 1383 | this._queue.push(data); 1384 | return; 1385 | } 1386 | 1387 | if (!data.type) { 1388 | this.emit('error', 'Invalid message'); 1389 | return; 1390 | } 1391 | 1392 | var message = JSON.stringify(data); 1393 | if (this._wsOpen()) { 1394 | this._socket.send(message); 1395 | } else { 1396 | var http = new XMLHttpRequest(); 1397 | var url = this._httpUrl + '/' + data.type.toLowerCase(); 1398 | http.open('post', url, true); 1399 | http.setRequestHeader('Content-Type', 'application/json'); 1400 | http.send(message); 1401 | } 1402 | } 1403 | 1404 | Socket.prototype.close = function() { 1405 | if (!this.disconnected && this._wsOpen()) { 1406 | this._socket.close(); 1407 | this.disconnected = true; 1408 | } 1409 | } 1410 | 1411 | module.exports = Socket; 1412 | 1413 | },{"./util":8,"eventemitter3":9}],8:[function(require,module,exports){ 1414 | var defaultConfig = {'iceServers': [{ 'url': 'stun:stun.l.google.com:19302' }]}; 1415 | var dataCount = 1; 1416 | 1417 | var BinaryPack = require('js-binarypack'); 1418 | var RTCPeerConnection = require('./adapter').RTCPeerConnection; 1419 | 1420 | var util = { 1421 | noop: function() {}, 1422 | 1423 | CLOUD_HOST: '0.peerjs.com', 1424 | CLOUD_PORT: 9000, 1425 | 1426 | // Browsers that need chunking: 1427 | chunkedBrowsers: {'Chrome': 1}, 1428 | chunkedMTU: 16300, // The original 60000 bytes setting does not work when sending data from Firefox to Chrome, which is "cut off" after 16384 bytes and delivered individually. 1429 | 1430 | // Logging logic 1431 | logLevel: 0, 1432 | setLogLevel: function(level) { 1433 | var debugLevel = parseInt(level, 10); 1434 | if (!isNaN(parseInt(level, 10))) { 1435 | util.logLevel = debugLevel; 1436 | } else { 1437 | // If they are using truthy/falsy values for debug 1438 | util.logLevel = level ? 3 : 0; 1439 | } 1440 | util.log = util.warn = util.error = util.noop; 1441 | if (util.logLevel > 0) { 1442 | util.error = util._printWith('ERROR'); 1443 | } 1444 | if (util.logLevel > 1) { 1445 | util.warn = util._printWith('WARNING'); 1446 | } 1447 | if (util.logLevel > 2) { 1448 | util.log = util._print; 1449 | } 1450 | }, 1451 | setLogFunction: function(fn) { 1452 | if (fn.constructor !== Function) { 1453 | util.warn('The log function you passed in is not a function. Defaulting to regular logs.'); 1454 | } else { 1455 | util._print = fn; 1456 | } 1457 | }, 1458 | 1459 | _printWith: function(prefix) { 1460 | return function() { 1461 | var copy = Array.prototype.slice.call(arguments); 1462 | copy.unshift(prefix); 1463 | util._print.apply(util, copy); 1464 | }; 1465 | }, 1466 | _print: function () { 1467 | var err = false; 1468 | var copy = Array.prototype.slice.call(arguments); 1469 | copy.unshift('PeerJS: '); 1470 | for (var i = 0, l = copy.length; i < l; i++){ 1471 | if (copy[i] instanceof Error) { 1472 | copy[i] = '(' + copy[i].name + ') ' + copy[i].message; 1473 | err = true; 1474 | } 1475 | } 1476 | err ? console.error.apply(console, copy) : console.log.apply(console, copy); 1477 | }, 1478 | // 1479 | 1480 | // Returns browser-agnostic default config 1481 | defaultConfig: defaultConfig, 1482 | // 1483 | 1484 | // Returns the current browser. 1485 | browser: (function() { 1486 | if (window.mozRTCPeerConnection) { 1487 | return 'Firefox'; 1488 | } else if (window.webkitRTCPeerConnection) { 1489 | return 'Chrome'; 1490 | } else if (window.RTCPeerConnection) { 1491 | return 'Supported'; 1492 | } else { 1493 | return 'Unsupported'; 1494 | } 1495 | })(), 1496 | // 1497 | 1498 | // Lists which features are supported 1499 | supports: (function() { 1500 | if (typeof RTCPeerConnection === 'undefined') { 1501 | return {}; 1502 | } 1503 | 1504 | var data = true; 1505 | var audioVideo = true; 1506 | 1507 | var binaryBlob = false; 1508 | var sctp = false; 1509 | var onnegotiationneeded = !!window.webkitRTCPeerConnection; 1510 | 1511 | var pc, dc; 1512 | try { 1513 | pc = new RTCPeerConnection(defaultConfig, {optional: [{RtpDataChannels: true}]}); 1514 | } catch (e) { 1515 | data = false; 1516 | audioVideo = false; 1517 | } 1518 | 1519 | if (data) { 1520 | try { 1521 | dc = pc.createDataChannel('_PEERJSTEST'); 1522 | } catch (e) { 1523 | data = false; 1524 | } 1525 | } 1526 | 1527 | if (data) { 1528 | // Binary test 1529 | try { 1530 | dc.binaryType = 'blob'; 1531 | binaryBlob = true; 1532 | } catch (e) { 1533 | } 1534 | 1535 | // Reliable test. 1536 | // Unfortunately Chrome is a bit unreliable about whether or not they 1537 | // support reliable. 1538 | var reliablePC = new RTCPeerConnection(defaultConfig, {}); 1539 | try { 1540 | var reliableDC = reliablePC.createDataChannel('_PEERJSRELIABLETEST', {}); 1541 | sctp = reliableDC.reliable; 1542 | } catch (e) { 1543 | } 1544 | reliablePC.close(); 1545 | } 1546 | 1547 | // FIXME: not really the best check... 1548 | if (audioVideo) { 1549 | audioVideo = !!pc.addStream; 1550 | } 1551 | 1552 | // FIXME: this is not great because in theory it doesn't work for 1553 | // av-only browsers (?). 1554 | if (!onnegotiationneeded && data) { 1555 | // sync default check. 1556 | var negotiationPC = new RTCPeerConnection(defaultConfig, {optional: [{RtpDataChannels: true}]}); 1557 | negotiationPC.onnegotiationneeded = function() { 1558 | onnegotiationneeded = true; 1559 | // async check. 1560 | if (util && util.supports) { 1561 | util.supports.onnegotiationneeded = true; 1562 | } 1563 | }; 1564 | negotiationPC.createDataChannel('_PEERJSNEGOTIATIONTEST'); 1565 | 1566 | setTimeout(function() { 1567 | negotiationPC.close(); 1568 | }, 1000); 1569 | } 1570 | 1571 | if (pc) { 1572 | pc.close(); 1573 | } 1574 | 1575 | return { 1576 | audioVideo: audioVideo, 1577 | data: data, 1578 | binaryBlob: binaryBlob, 1579 | binary: sctp, // deprecated; sctp implies binary support. 1580 | reliable: sctp, // deprecated; sctp implies reliable data. 1581 | sctp: sctp, 1582 | onnegotiationneeded: onnegotiationneeded 1583 | }; 1584 | }()), 1585 | // 1586 | 1587 | // Ensure alphanumeric ids 1588 | validateId: function(id) { 1589 | // Allow empty ids 1590 | return !id || /^[A-Za-z0-9_-]+(?:[ _-][A-Za-z0-9]+)*$/.exec(id); 1591 | }, 1592 | 1593 | validateKey: function(key) { 1594 | // Allow empty keys 1595 | return !key || /^[A-Za-z0-9_-]+(?:[ _-][A-Za-z0-9]+)*$/.exec(key); 1596 | }, 1597 | 1598 | 1599 | debug: false, 1600 | 1601 | inherits: function(ctor, superCtor) { 1602 | ctor.super_ = superCtor; 1603 | ctor.prototype = Object.create(superCtor.prototype, { 1604 | constructor: { 1605 | value: ctor, 1606 | enumerable: false, 1607 | writable: true, 1608 | configurable: true 1609 | } 1610 | }); 1611 | }, 1612 | extend: function(dest, source) { 1613 | for(var key in source) { 1614 | if(source.hasOwnProperty(key)) { 1615 | dest[key] = source[key]; 1616 | } 1617 | } 1618 | return dest; 1619 | }, 1620 | pack: BinaryPack.pack, 1621 | unpack: BinaryPack.unpack, 1622 | 1623 | log: function () { 1624 | if (util.debug) { 1625 | var err = false; 1626 | var copy = Array.prototype.slice.call(arguments); 1627 | copy.unshift('PeerJS: '); 1628 | for (var i = 0, l = copy.length; i < l; i++){ 1629 | if (copy[i] instanceof Error) { 1630 | copy[i] = '(' + copy[i].name + ') ' + copy[i].message; 1631 | err = true; 1632 | } 1633 | } 1634 | err ? console.error.apply(console, copy) : console.log.apply(console, copy); 1635 | } 1636 | }, 1637 | 1638 | setZeroTimeout: (function(global) { 1639 | var timeouts = []; 1640 | var messageName = 'zero-timeout-message'; 1641 | 1642 | // Like setTimeout, but only takes a function argument. There's 1643 | // no time argument (always zero) and no arguments (you have to 1644 | // use a closure). 1645 | function setZeroTimeoutPostMessage(fn) { 1646 | timeouts.push(fn); 1647 | global.postMessage(messageName, '*'); 1648 | } 1649 | 1650 | function handleMessage(event) { 1651 | if (event.source == global && event.data == messageName) { 1652 | if (event.stopPropagation) { 1653 | event.stopPropagation(); 1654 | } 1655 | if (timeouts.length) { 1656 | timeouts.shift()(); 1657 | } 1658 | } 1659 | } 1660 | if (global.addEventListener) { 1661 | global.addEventListener('message', handleMessage, true); 1662 | } else if (global.attachEvent) { 1663 | global.attachEvent('onmessage', handleMessage); 1664 | } 1665 | return setZeroTimeoutPostMessage; 1666 | }(window)), 1667 | 1668 | // Binary stuff 1669 | 1670 | // chunks a blob. 1671 | chunk: function(bl) { 1672 | var chunks = []; 1673 | var size = bl.size; 1674 | var start = index = 0; 1675 | var total = Math.ceil(size / util.chunkedMTU); 1676 | while (start < size) { 1677 | var end = Math.min(size, start + util.chunkedMTU); 1678 | var b = bl.slice(start, end); 1679 | 1680 | var chunk = { 1681 | __peerData: dataCount, 1682 | n: index, 1683 | data: b, 1684 | total: total 1685 | }; 1686 | 1687 | chunks.push(chunk); 1688 | 1689 | start = end; 1690 | index += 1; 1691 | } 1692 | dataCount += 1; 1693 | return chunks; 1694 | }, 1695 | 1696 | blobToArrayBuffer: function(blob, cb){ 1697 | var fr = new FileReader(); 1698 | fr.onload = function(evt) { 1699 | cb(evt.target.result); 1700 | }; 1701 | fr.readAsArrayBuffer(blob); 1702 | }, 1703 | blobToBinaryString: function(blob, cb){ 1704 | var fr = new FileReader(); 1705 | fr.onload = function(evt) { 1706 | cb(evt.target.result); 1707 | }; 1708 | fr.readAsBinaryString(blob); 1709 | }, 1710 | binaryStringToArrayBuffer: function(binary) { 1711 | var byteArray = new Uint8Array(binary.length); 1712 | for (var i = 0; i < binary.length; i++) { 1713 | byteArray[i] = binary.charCodeAt(i) & 0xff; 1714 | } 1715 | return byteArray.buffer; 1716 | }, 1717 | randomToken: function () { 1718 | return Math.random().toString(36).substr(2); 1719 | }, 1720 | // 1721 | 1722 | isSecure: function() { 1723 | return location.protocol === 'https:'; 1724 | } 1725 | }; 1726 | 1727 | module.exports = util; 1728 | 1729 | },{"./adapter":1,"js-binarypack":10}],9:[function(require,module,exports){ 1730 | 'use strict'; 1731 | 1732 | /** 1733 | * Representation of a single EventEmitter function. 1734 | * 1735 | * @param {Function} fn Event handler to be called. 1736 | * @param {Mixed} context Context for function execution. 1737 | * @param {Boolean} once Only emit once 1738 | * @api private 1739 | */ 1740 | function EE(fn, context, once) { 1741 | this.fn = fn; 1742 | this.context = context; 1743 | this.once = once || false; 1744 | } 1745 | 1746 | /** 1747 | * Minimal EventEmitter interface that is molded against the Node.js 1748 | * EventEmitter interface. 1749 | * 1750 | * @constructor 1751 | * @api public 1752 | */ 1753 | function EventEmitter() { /* Nothing to set */ } 1754 | 1755 | /** 1756 | * Holds the assigned EventEmitters by name. 1757 | * 1758 | * @type {Object} 1759 | * @private 1760 | */ 1761 | EventEmitter.prototype._events = undefined; 1762 | 1763 | /** 1764 | * Return a list of assigned event listeners. 1765 | * 1766 | * @param {String} event The events that should be listed. 1767 | * @returns {Array} 1768 | * @api public 1769 | */ 1770 | EventEmitter.prototype.listeners = function listeners(event) { 1771 | if (!this._events || !this._events[event]) return []; 1772 | 1773 | for (var i = 0, l = this._events[event].length, ee = []; i < l; i++) { 1774 | ee.push(this._events[event][i].fn); 1775 | } 1776 | 1777 | return ee; 1778 | }; 1779 | 1780 | /** 1781 | * Emit an event to all registered event listeners. 1782 | * 1783 | * @param {String} event The name of the event. 1784 | * @returns {Boolean} Indication if we've emitted an event. 1785 | * @api public 1786 | */ 1787 | EventEmitter.prototype.emit = function emit(event, a1, a2, a3, a4, a5) { 1788 | if (!this._events || !this._events[event]) return false; 1789 | 1790 | var listeners = this._events[event] 1791 | , length = listeners.length 1792 | , len = arguments.length 1793 | , ee = listeners[0] 1794 | , args 1795 | , i, j; 1796 | 1797 | if (1 === length) { 1798 | if (ee.once) this.removeListener(event, ee.fn, true); 1799 | 1800 | switch (len) { 1801 | case 1: return ee.fn.call(ee.context), true; 1802 | case 2: return ee.fn.call(ee.context, a1), true; 1803 | case 3: return ee.fn.call(ee.context, a1, a2), true; 1804 | case 4: return ee.fn.call(ee.context, a1, a2, a3), true; 1805 | case 5: return ee.fn.call(ee.context, a1, a2, a3, a4), true; 1806 | case 6: return ee.fn.call(ee.context, a1, a2, a3, a4, a5), true; 1807 | } 1808 | 1809 | for (i = 1, args = new Array(len -1); i < len; i++) { 1810 | args[i - 1] = arguments[i]; 1811 | } 1812 | 1813 | ee.fn.apply(ee.context, args); 1814 | } else { 1815 | for (i = 0; i < length; i++) { 1816 | if (listeners[i].once) this.removeListener(event, listeners[i].fn, true); 1817 | 1818 | switch (len) { 1819 | case 1: listeners[i].fn.call(listeners[i].context); break; 1820 | case 2: listeners[i].fn.call(listeners[i].context, a1); break; 1821 | case 3: listeners[i].fn.call(listeners[i].context, a1, a2); break; 1822 | default: 1823 | if (!args) for (j = 1, args = new Array(len -1); j < len; j++) { 1824 | args[j - 1] = arguments[j]; 1825 | } 1826 | 1827 | listeners[i].fn.apply(listeners[i].context, args); 1828 | } 1829 | } 1830 | } 1831 | 1832 | return true; 1833 | }; 1834 | 1835 | /** 1836 | * Register a new EventListener for the given event. 1837 | * 1838 | * @param {String} event Name of the event. 1839 | * @param {Functon} fn Callback function. 1840 | * @param {Mixed} context The context of the function. 1841 | * @api public 1842 | */ 1843 | EventEmitter.prototype.on = function on(event, fn, context) { 1844 | if (!this._events) this._events = {}; 1845 | if (!this._events[event]) this._events[event] = []; 1846 | this._events[event].push(new EE( fn, context || this )); 1847 | 1848 | return this; 1849 | }; 1850 | 1851 | /** 1852 | * Add an EventListener that's only called once. 1853 | * 1854 | * @param {String} event Name of the event. 1855 | * @param {Function} fn Callback function. 1856 | * @param {Mixed} context The context of the function. 1857 | * @api public 1858 | */ 1859 | EventEmitter.prototype.once = function once(event, fn, context) { 1860 | if (!this._events) this._events = {}; 1861 | if (!this._events[event]) this._events[event] = []; 1862 | this._events[event].push(new EE(fn, context || this, true )); 1863 | 1864 | return this; 1865 | }; 1866 | 1867 | /** 1868 | * Remove event listeners. 1869 | * 1870 | * @param {String} event The event we want to remove. 1871 | * @param {Function} fn The listener that we need to find. 1872 | * @param {Boolean} once Only remove once listeners. 1873 | * @api public 1874 | */ 1875 | EventEmitter.prototype.removeListener = function removeListener(event, fn, once) { 1876 | if (!this._events || !this._events[event]) return this; 1877 | 1878 | var listeners = this._events[event] 1879 | , events = []; 1880 | 1881 | if (fn) for (var i = 0, length = listeners.length; i < length; i++) { 1882 | if (listeners[i].fn !== fn && listeners[i].once !== once) { 1883 | events.push(listeners[i]); 1884 | } 1885 | } 1886 | 1887 | // 1888 | // Reset the array, or remove it completely if we have no more listeners. 1889 | // 1890 | if (events.length) this._events[event] = events; 1891 | else this._events[event] = null; 1892 | 1893 | return this; 1894 | }; 1895 | 1896 | /** 1897 | * Remove all listeners or only the listeners for the specified event. 1898 | * 1899 | * @param {String} event The event want to remove all listeners for. 1900 | * @api public 1901 | */ 1902 | EventEmitter.prototype.removeAllListeners = function removeAllListeners(event) { 1903 | if (!this._events) return this; 1904 | 1905 | if (event) this._events[event] = null; 1906 | else this._events = {}; 1907 | 1908 | return this; 1909 | }; 1910 | 1911 | // 1912 | // Alias methods names because people roll like that. 1913 | // 1914 | EventEmitter.prototype.off = EventEmitter.prototype.removeListener; 1915 | EventEmitter.prototype.addListener = EventEmitter.prototype.on; 1916 | 1917 | // 1918 | // This function doesn't apply anymore. 1919 | // 1920 | EventEmitter.prototype.setMaxListeners = function setMaxListeners() { 1921 | return this; 1922 | }; 1923 | 1924 | // 1925 | // Expose the module. 1926 | // 1927 | EventEmitter.EventEmitter = EventEmitter; 1928 | EventEmitter.EventEmitter2 = EventEmitter; 1929 | EventEmitter.EventEmitter3 = EventEmitter; 1930 | 1931 | if ('object' === typeof module && module.exports) { 1932 | module.exports = EventEmitter; 1933 | } 1934 | 1935 | },{}],10:[function(require,module,exports){ 1936 | var BufferBuilder = require('./bufferbuilder').BufferBuilder; 1937 | var binaryFeatures = require('./bufferbuilder').binaryFeatures; 1938 | 1939 | var BinaryPack = { 1940 | unpack: function(data){ 1941 | var unpacker = new Unpacker(data); 1942 | return unpacker.unpack(); 1943 | }, 1944 | pack: function(data){ 1945 | var packer = new Packer(); 1946 | packer.pack(data); 1947 | var buffer = packer.getBuffer(); 1948 | return buffer; 1949 | } 1950 | }; 1951 | 1952 | module.exports = BinaryPack; 1953 | 1954 | function Unpacker (data){ 1955 | // Data is ArrayBuffer 1956 | this.index = 0; 1957 | this.dataBuffer = data; 1958 | this.dataView = new Uint8Array(this.dataBuffer); 1959 | this.length = this.dataBuffer.byteLength; 1960 | } 1961 | 1962 | Unpacker.prototype.unpack = function(){ 1963 | var type = this.unpack_uint8(); 1964 | if (type < 0x80){ 1965 | var positive_fixnum = type; 1966 | return positive_fixnum; 1967 | } else if ((type ^ 0xe0) < 0x20){ 1968 | var negative_fixnum = (type ^ 0xe0) - 0x20; 1969 | return negative_fixnum; 1970 | } 1971 | var size; 1972 | if ((size = type ^ 0xa0) <= 0x0f){ 1973 | return this.unpack_raw(size); 1974 | } else if ((size = type ^ 0xb0) <= 0x0f){ 1975 | return this.unpack_string(size); 1976 | } else if ((size = type ^ 0x90) <= 0x0f){ 1977 | return this.unpack_array(size); 1978 | } else if ((size = type ^ 0x80) <= 0x0f){ 1979 | return this.unpack_map(size); 1980 | } 1981 | switch(type){ 1982 | case 0xc0: 1983 | return null; 1984 | case 0xc1: 1985 | return undefined; 1986 | case 0xc2: 1987 | return false; 1988 | case 0xc3: 1989 | return true; 1990 | case 0xca: 1991 | return this.unpack_float(); 1992 | case 0xcb: 1993 | return this.unpack_double(); 1994 | case 0xcc: 1995 | return this.unpack_uint8(); 1996 | case 0xcd: 1997 | return this.unpack_uint16(); 1998 | case 0xce: 1999 | return this.unpack_uint32(); 2000 | case 0xcf: 2001 | return this.unpack_uint64(); 2002 | case 0xd0: 2003 | return this.unpack_int8(); 2004 | case 0xd1: 2005 | return this.unpack_int16(); 2006 | case 0xd2: 2007 | return this.unpack_int32(); 2008 | case 0xd3: 2009 | return this.unpack_int64(); 2010 | case 0xd4: 2011 | return undefined; 2012 | case 0xd5: 2013 | return undefined; 2014 | case 0xd6: 2015 | return undefined; 2016 | case 0xd7: 2017 | return undefined; 2018 | case 0xd8: 2019 | size = this.unpack_uint16(); 2020 | return this.unpack_string(size); 2021 | case 0xd9: 2022 | size = this.unpack_uint32(); 2023 | return this.unpack_string(size); 2024 | case 0xda: 2025 | size = this.unpack_uint16(); 2026 | return this.unpack_raw(size); 2027 | case 0xdb: 2028 | size = this.unpack_uint32(); 2029 | return this.unpack_raw(size); 2030 | case 0xdc: 2031 | size = this.unpack_uint16(); 2032 | return this.unpack_array(size); 2033 | case 0xdd: 2034 | size = this.unpack_uint32(); 2035 | return this.unpack_array(size); 2036 | case 0xde: 2037 | size = this.unpack_uint16(); 2038 | return this.unpack_map(size); 2039 | case 0xdf: 2040 | size = this.unpack_uint32(); 2041 | return this.unpack_map(size); 2042 | } 2043 | } 2044 | 2045 | Unpacker.prototype.unpack_uint8 = function(){ 2046 | var byte = this.dataView[this.index] & 0xff; 2047 | this.index++; 2048 | return byte; 2049 | }; 2050 | 2051 | Unpacker.prototype.unpack_uint16 = function(){ 2052 | var bytes = this.read(2); 2053 | var uint16 = 2054 | ((bytes[0] & 0xff) * 256) + (bytes[1] & 0xff); 2055 | this.index += 2; 2056 | return uint16; 2057 | } 2058 | 2059 | Unpacker.prototype.unpack_uint32 = function(){ 2060 | var bytes = this.read(4); 2061 | var uint32 = 2062 | ((bytes[0] * 256 + 2063 | bytes[1]) * 256 + 2064 | bytes[2]) * 256 + 2065 | bytes[3]; 2066 | this.index += 4; 2067 | return uint32; 2068 | } 2069 | 2070 | Unpacker.prototype.unpack_uint64 = function(){ 2071 | var bytes = this.read(8); 2072 | var uint64 = 2073 | ((((((bytes[0] * 256 + 2074 | bytes[1]) * 256 + 2075 | bytes[2]) * 256 + 2076 | bytes[3]) * 256 + 2077 | bytes[4]) * 256 + 2078 | bytes[5]) * 256 + 2079 | bytes[6]) * 256 + 2080 | bytes[7]; 2081 | this.index += 8; 2082 | return uint64; 2083 | } 2084 | 2085 | 2086 | Unpacker.prototype.unpack_int8 = function(){ 2087 | var uint8 = this.unpack_uint8(); 2088 | return (uint8 < 0x80 ) ? uint8 : uint8 - (1 << 8); 2089 | }; 2090 | 2091 | Unpacker.prototype.unpack_int16 = function(){ 2092 | var uint16 = this.unpack_uint16(); 2093 | return (uint16 < 0x8000 ) ? uint16 : uint16 - (1 << 16); 2094 | } 2095 | 2096 | Unpacker.prototype.unpack_int32 = function(){ 2097 | var uint32 = this.unpack_uint32(); 2098 | return (uint32 < Math.pow(2, 31) ) ? uint32 : 2099 | uint32 - Math.pow(2, 32); 2100 | } 2101 | 2102 | Unpacker.prototype.unpack_int64 = function(){ 2103 | var uint64 = this.unpack_uint64(); 2104 | return (uint64 < Math.pow(2, 63) ) ? uint64 : 2105 | uint64 - Math.pow(2, 64); 2106 | } 2107 | 2108 | Unpacker.prototype.unpack_raw = function(size){ 2109 | if ( this.length < this.index + size){ 2110 | throw new Error('BinaryPackFailure: index is out of range' 2111 | + ' ' + this.index + ' ' + size + ' ' + this.length); 2112 | } 2113 | var buf = this.dataBuffer.slice(this.index, this.index + size); 2114 | this.index += size; 2115 | 2116 | //buf = util.bufferToString(buf); 2117 | 2118 | return buf; 2119 | } 2120 | 2121 | Unpacker.prototype.unpack_string = function(size){ 2122 | var bytes = this.read(size); 2123 | var i = 0, str = '', c, code; 2124 | while(i < size){ 2125 | c = bytes[i]; 2126 | if ( c < 128){ 2127 | str += String.fromCharCode(c); 2128 | i++; 2129 | } else if ((c ^ 0xc0) < 32){ 2130 | code = ((c ^ 0xc0) << 6) | (bytes[i+1] & 63); 2131 | str += String.fromCharCode(code); 2132 | i += 2; 2133 | } else { 2134 | code = ((c & 15) << 12) | ((bytes[i+1] & 63) << 6) | 2135 | (bytes[i+2] & 63); 2136 | str += String.fromCharCode(code); 2137 | i += 3; 2138 | } 2139 | } 2140 | this.index += size; 2141 | return str; 2142 | } 2143 | 2144 | Unpacker.prototype.unpack_array = function(size){ 2145 | var objects = new Array(size); 2146 | for(var i = 0; i < size ; i++){ 2147 | objects[i] = this.unpack(); 2148 | } 2149 | return objects; 2150 | } 2151 | 2152 | Unpacker.prototype.unpack_map = function(size){ 2153 | var map = {}; 2154 | for(var i = 0; i < size ; i++){ 2155 | var key = this.unpack(); 2156 | var value = this.unpack(); 2157 | map[key] = value; 2158 | } 2159 | return map; 2160 | } 2161 | 2162 | Unpacker.prototype.unpack_float = function(){ 2163 | var uint32 = this.unpack_uint32(); 2164 | var sign = uint32 >> 31; 2165 | var exp = ((uint32 >> 23) & 0xff) - 127; 2166 | var fraction = ( uint32 & 0x7fffff ) | 0x800000; 2167 | return (sign == 0 ? 1 : -1) * 2168 | fraction * Math.pow(2, exp - 23); 2169 | } 2170 | 2171 | Unpacker.prototype.unpack_double = function(){ 2172 | var h32 = this.unpack_uint32(); 2173 | var l32 = this.unpack_uint32(); 2174 | var sign = h32 >> 31; 2175 | var exp = ((h32 >> 20) & 0x7ff) - 1023; 2176 | var hfrac = ( h32 & 0xfffff ) | 0x100000; 2177 | var frac = hfrac * Math.pow(2, exp - 20) + 2178 | l32 * Math.pow(2, exp - 52); 2179 | return (sign == 0 ? 1 : -1) * frac; 2180 | } 2181 | 2182 | Unpacker.prototype.read = function(length){ 2183 | var j = this.index; 2184 | if (j + length <= this.length) { 2185 | return this.dataView.subarray(j, j + length); 2186 | } else { 2187 | throw new Error('BinaryPackFailure: read index out of range'); 2188 | } 2189 | } 2190 | 2191 | function Packer(){ 2192 | this.bufferBuilder = new BufferBuilder(); 2193 | } 2194 | 2195 | Packer.prototype.getBuffer = function(){ 2196 | return this.bufferBuilder.getBuffer(); 2197 | } 2198 | 2199 | Packer.prototype.pack = function(value){ 2200 | var type = typeof(value); 2201 | if (type == 'string'){ 2202 | this.pack_string(value); 2203 | } else if (type == 'number'){ 2204 | if (Math.floor(value) === value){ 2205 | this.pack_integer(value); 2206 | } else{ 2207 | this.pack_double(value); 2208 | } 2209 | } else if (type == 'boolean'){ 2210 | if (value === true){ 2211 | this.bufferBuilder.append(0xc3); 2212 | } else if (value === false){ 2213 | this.bufferBuilder.append(0xc2); 2214 | } 2215 | } else if (type == 'undefined'){ 2216 | this.bufferBuilder.append(0xc0); 2217 | } else if (type == 'object'){ 2218 | if (value === null){ 2219 | this.bufferBuilder.append(0xc0); 2220 | } else { 2221 | var constructor = value.constructor; 2222 | if (constructor == Array){ 2223 | this.pack_array(value); 2224 | } else if (constructor == Blob || constructor == File) { 2225 | this.pack_bin(value); 2226 | } else if (constructor == ArrayBuffer) { 2227 | if(binaryFeatures.useArrayBufferView) { 2228 | this.pack_bin(new Uint8Array(value)); 2229 | } else { 2230 | this.pack_bin(value); 2231 | } 2232 | } else if ('BYTES_PER_ELEMENT' in value){ 2233 | if(binaryFeatures.useArrayBufferView) { 2234 | this.pack_bin(new Uint8Array(value.buffer)); 2235 | } else { 2236 | this.pack_bin(value.buffer); 2237 | } 2238 | } else if (constructor == Object){ 2239 | this.pack_object(value); 2240 | } else if (constructor == Date){ 2241 | this.pack_string(value.toString()); 2242 | } else if (typeof value.toBinaryPack == 'function'){ 2243 | this.bufferBuilder.append(value.toBinaryPack()); 2244 | } else { 2245 | throw new Error('Type "' + constructor.toString() + '" not yet supported'); 2246 | } 2247 | } 2248 | } else { 2249 | throw new Error('Type "' + type + '" not yet supported'); 2250 | } 2251 | this.bufferBuilder.flush(); 2252 | } 2253 | 2254 | 2255 | Packer.prototype.pack_bin = function(blob){ 2256 | var length = blob.length || blob.byteLength || blob.size; 2257 | if (length <= 0x0f){ 2258 | this.pack_uint8(0xa0 + length); 2259 | } else if (length <= 0xffff){ 2260 | this.bufferBuilder.append(0xda) ; 2261 | this.pack_uint16(length); 2262 | } else if (length <= 0xffffffff){ 2263 | this.bufferBuilder.append(0xdb); 2264 | this.pack_uint32(length); 2265 | } else{ 2266 | throw new Error('Invalid length'); 2267 | } 2268 | this.bufferBuilder.append(blob); 2269 | } 2270 | 2271 | Packer.prototype.pack_string = function(str){ 2272 | var length = utf8Length(str); 2273 | 2274 | if (length <= 0x0f){ 2275 | this.pack_uint8(0xb0 + length); 2276 | } else if (length <= 0xffff){ 2277 | this.bufferBuilder.append(0xd8) ; 2278 | this.pack_uint16(length); 2279 | } else if (length <= 0xffffffff){ 2280 | this.bufferBuilder.append(0xd9); 2281 | this.pack_uint32(length); 2282 | } else{ 2283 | throw new Error('Invalid length'); 2284 | } 2285 | this.bufferBuilder.append(str); 2286 | } 2287 | 2288 | Packer.prototype.pack_array = function(ary){ 2289 | var length = ary.length; 2290 | if (length <= 0x0f){ 2291 | this.pack_uint8(0x90 + length); 2292 | } else if (length <= 0xffff){ 2293 | this.bufferBuilder.append(0xdc) 2294 | this.pack_uint16(length); 2295 | } else if (length <= 0xffffffff){ 2296 | this.bufferBuilder.append(0xdd); 2297 | this.pack_uint32(length); 2298 | } else{ 2299 | throw new Error('Invalid length'); 2300 | } 2301 | for(var i = 0; i < length ; i++){ 2302 | this.pack(ary[i]); 2303 | } 2304 | } 2305 | 2306 | Packer.prototype.pack_integer = function(num){ 2307 | if ( -0x20 <= num && num <= 0x7f){ 2308 | this.bufferBuilder.append(num & 0xff); 2309 | } else if (0x00 <= num && num <= 0xff){ 2310 | this.bufferBuilder.append(0xcc); 2311 | this.pack_uint8(num); 2312 | } else if (-0x80 <= num && num <= 0x7f){ 2313 | this.bufferBuilder.append(0xd0); 2314 | this.pack_int8(num); 2315 | } else if ( 0x0000 <= num && num <= 0xffff){ 2316 | this.bufferBuilder.append(0xcd); 2317 | this.pack_uint16(num); 2318 | } else if (-0x8000 <= num && num <= 0x7fff){ 2319 | this.bufferBuilder.append(0xd1); 2320 | this.pack_int16(num); 2321 | } else if ( 0x00000000 <= num && num <= 0xffffffff){ 2322 | this.bufferBuilder.append(0xce); 2323 | this.pack_uint32(num); 2324 | } else if (-0x80000000 <= num && num <= 0x7fffffff){ 2325 | this.bufferBuilder.append(0xd2); 2326 | this.pack_int32(num); 2327 | } else if (-0x8000000000000000 <= num && num <= 0x7FFFFFFFFFFFFFFF){ 2328 | this.bufferBuilder.append(0xd3); 2329 | this.pack_int64(num); 2330 | } else if (0x0000000000000000 <= num && num <= 0xFFFFFFFFFFFFFFFF){ 2331 | this.bufferBuilder.append(0xcf); 2332 | this.pack_uint64(num); 2333 | } else{ 2334 | throw new Error('Invalid integer'); 2335 | } 2336 | } 2337 | 2338 | Packer.prototype.pack_double = function(num){ 2339 | var sign = 0; 2340 | if (num < 0){ 2341 | sign = 1; 2342 | num = -num; 2343 | } 2344 | var exp = Math.floor(Math.log(num) / Math.LN2); 2345 | var frac0 = num / Math.pow(2, exp) - 1; 2346 | var frac1 = Math.floor(frac0 * Math.pow(2, 52)); 2347 | var b32 = Math.pow(2, 32); 2348 | var h32 = (sign << 31) | ((exp+1023) << 20) | 2349 | (frac1 / b32) & 0x0fffff; 2350 | var l32 = frac1 % b32; 2351 | this.bufferBuilder.append(0xcb); 2352 | this.pack_int32(h32); 2353 | this.pack_int32(l32); 2354 | } 2355 | 2356 | Packer.prototype.pack_object = function(obj){ 2357 | var keys = Object.keys(obj); 2358 | var length = keys.length; 2359 | if (length <= 0x0f){ 2360 | this.pack_uint8(0x80 + length); 2361 | } else if (length <= 0xffff){ 2362 | this.bufferBuilder.append(0xde); 2363 | this.pack_uint16(length); 2364 | } else if (length <= 0xffffffff){ 2365 | this.bufferBuilder.append(0xdf); 2366 | this.pack_uint32(length); 2367 | } else{ 2368 | throw new Error('Invalid length'); 2369 | } 2370 | for(var prop in obj){ 2371 | if (obj.hasOwnProperty(prop)){ 2372 | this.pack(prop); 2373 | this.pack(obj[prop]); 2374 | } 2375 | } 2376 | } 2377 | 2378 | Packer.prototype.pack_uint8 = function(num){ 2379 | this.bufferBuilder.append(num); 2380 | } 2381 | 2382 | Packer.prototype.pack_uint16 = function(num){ 2383 | this.bufferBuilder.append(num >> 8); 2384 | this.bufferBuilder.append(num & 0xff); 2385 | } 2386 | 2387 | Packer.prototype.pack_uint32 = function(num){ 2388 | var n = num & 0xffffffff; 2389 | this.bufferBuilder.append((n & 0xff000000) >>> 24); 2390 | this.bufferBuilder.append((n & 0x00ff0000) >>> 16); 2391 | this.bufferBuilder.append((n & 0x0000ff00) >>> 8); 2392 | this.bufferBuilder.append((n & 0x000000ff)); 2393 | } 2394 | 2395 | Packer.prototype.pack_uint64 = function(num){ 2396 | var high = num / Math.pow(2, 32); 2397 | var low = num % Math.pow(2, 32); 2398 | this.bufferBuilder.append((high & 0xff000000) >>> 24); 2399 | this.bufferBuilder.append((high & 0x00ff0000) >>> 16); 2400 | this.bufferBuilder.append((high & 0x0000ff00) >>> 8); 2401 | this.bufferBuilder.append((high & 0x000000ff)); 2402 | this.bufferBuilder.append((low & 0xff000000) >>> 24); 2403 | this.bufferBuilder.append((low & 0x00ff0000) >>> 16); 2404 | this.bufferBuilder.append((low & 0x0000ff00) >>> 8); 2405 | this.bufferBuilder.append((low & 0x000000ff)); 2406 | } 2407 | 2408 | Packer.prototype.pack_int8 = function(num){ 2409 | this.bufferBuilder.append(num & 0xff); 2410 | } 2411 | 2412 | Packer.prototype.pack_int16 = function(num){ 2413 | this.bufferBuilder.append((num & 0xff00) >> 8); 2414 | this.bufferBuilder.append(num & 0xff); 2415 | } 2416 | 2417 | Packer.prototype.pack_int32 = function(num){ 2418 | this.bufferBuilder.append((num >>> 24) & 0xff); 2419 | this.bufferBuilder.append((num & 0x00ff0000) >>> 16); 2420 | this.bufferBuilder.append((num & 0x0000ff00) >>> 8); 2421 | this.bufferBuilder.append((num & 0x000000ff)); 2422 | } 2423 | 2424 | Packer.prototype.pack_int64 = function(num){ 2425 | var high = Math.floor(num / Math.pow(2, 32)); 2426 | var low = num % Math.pow(2, 32); 2427 | this.bufferBuilder.append((high & 0xff000000) >>> 24); 2428 | this.bufferBuilder.append((high & 0x00ff0000) >>> 16); 2429 | this.bufferBuilder.append((high & 0x0000ff00) >>> 8); 2430 | this.bufferBuilder.append((high & 0x000000ff)); 2431 | this.bufferBuilder.append((low & 0xff000000) >>> 24); 2432 | this.bufferBuilder.append((low & 0x00ff0000) >>> 16); 2433 | this.bufferBuilder.append((low & 0x0000ff00) >>> 8); 2434 | this.bufferBuilder.append((low & 0x000000ff)); 2435 | } 2436 | 2437 | function _utf8Replace(m){ 2438 | var code = m.charCodeAt(0); 2439 | 2440 | if(code <= 0x7ff) return '00'; 2441 | if(code <= 0xffff) return '000'; 2442 | if(code <= 0x1fffff) return '0000'; 2443 | if(code <= 0x3ffffff) return '00000'; 2444 | return '000000'; 2445 | } 2446 | 2447 | function utf8Length(str){ 2448 | if (str.length > 600) { 2449 | // Blob method faster for large strings 2450 | return (new Blob([str])).size; 2451 | } else { 2452 | return str.replace(/[^\u0000-\u007F]/g, _utf8Replace).length; 2453 | } 2454 | } 2455 | 2456 | },{"./bufferbuilder":11}],11:[function(require,module,exports){ 2457 | var binaryFeatures = {}; 2458 | binaryFeatures.useBlobBuilder = (function(){ 2459 | try { 2460 | new Blob([]); 2461 | return false; 2462 | } catch (e) { 2463 | return true; 2464 | } 2465 | })(); 2466 | 2467 | binaryFeatures.useArrayBufferView = !binaryFeatures.useBlobBuilder && (function(){ 2468 | try { 2469 | return (new Blob([new Uint8Array([])])).size === 0; 2470 | } catch (e) { 2471 | return true; 2472 | } 2473 | })(); 2474 | 2475 | module.exports.binaryFeatures = binaryFeatures; 2476 | var BlobBuilder = module.exports.BlobBuilder; 2477 | if (typeof window != 'undefined') { 2478 | BlobBuilder = module.exports.BlobBuilder = window.WebKitBlobBuilder || 2479 | window.MozBlobBuilder || window.MSBlobBuilder || window.BlobBuilder; 2480 | } 2481 | 2482 | function BufferBuilder(){ 2483 | this._pieces = []; 2484 | this._parts = []; 2485 | } 2486 | 2487 | BufferBuilder.prototype.append = function(data) { 2488 | if(typeof data === 'number') { 2489 | this._pieces.push(data); 2490 | } else { 2491 | this.flush(); 2492 | this._parts.push(data); 2493 | } 2494 | }; 2495 | 2496 | BufferBuilder.prototype.flush = function() { 2497 | if (this._pieces.length > 0) { 2498 | var buf = new Uint8Array(this._pieces); 2499 | if(!binaryFeatures.useArrayBufferView) { 2500 | buf = buf.buffer; 2501 | } 2502 | this._parts.push(buf); 2503 | this._pieces = []; 2504 | } 2505 | }; 2506 | 2507 | BufferBuilder.prototype.getBuffer = function() { 2508 | this.flush(); 2509 | if(binaryFeatures.useBlobBuilder) { 2510 | var builder = new BlobBuilder(); 2511 | for(var i = 0, ii = this._parts.length; i < ii; i++) { 2512 | builder.append(this._parts[i]); 2513 | } 2514 | return builder.getBlob(); 2515 | } else { 2516 | return new Blob(this._parts); 2517 | } 2518 | }; 2519 | 2520 | module.exports.BufferBuilder = BufferBuilder; 2521 | 2522 | },{}],12:[function(require,module,exports){ 2523 | var util = require('./util'); 2524 | 2525 | /** 2526 | * Reliable transfer for Chrome Canary DataChannel impl. 2527 | * Author: @michellebu 2528 | */ 2529 | function Reliable(dc, debug) { 2530 | if (!(this instanceof Reliable)) return new Reliable(dc); 2531 | this._dc = dc; 2532 | 2533 | util.debug = debug; 2534 | 2535 | // Messages sent/received so far. 2536 | // id: { ack: n, chunks: [...] } 2537 | this._outgoing = {}; 2538 | // id: { ack: ['ack', id, n], chunks: [...] } 2539 | this._incoming = {}; 2540 | this._received = {}; 2541 | 2542 | // Window size. 2543 | this._window = 1000; 2544 | // MTU. 2545 | this._mtu = 500; 2546 | // Interval for setInterval. In ms. 2547 | this._interval = 0; 2548 | 2549 | // Messages sent. 2550 | this._count = 0; 2551 | 2552 | // Outgoing message queue. 2553 | this._queue = []; 2554 | 2555 | this._setupDC(); 2556 | }; 2557 | 2558 | // Send a message reliably. 2559 | Reliable.prototype.send = function(msg) { 2560 | // Determine if chunking is necessary. 2561 | var bl = util.pack(msg); 2562 | if (bl.size < this._mtu) { 2563 | this._handleSend(['no', bl]); 2564 | return; 2565 | } 2566 | 2567 | this._outgoing[this._count] = { 2568 | ack: 0, 2569 | chunks: this._chunk(bl) 2570 | }; 2571 | 2572 | if (util.debug) { 2573 | this._outgoing[this._count].timer = new Date(); 2574 | } 2575 | 2576 | // Send prelim window. 2577 | this._sendWindowedChunks(this._count); 2578 | this._count += 1; 2579 | }; 2580 | 2581 | // Set up interval for processing queue. 2582 | Reliable.prototype._setupInterval = function() { 2583 | // TODO: fail gracefully. 2584 | 2585 | var self = this; 2586 | this._timeout = setInterval(function() { 2587 | // FIXME: String stuff makes things terribly async. 2588 | var msg = self._queue.shift(); 2589 | if (msg._multiple) { 2590 | for (var i = 0, ii = msg.length; i < ii; i += 1) { 2591 | self._intervalSend(msg[i]); 2592 | } 2593 | } else { 2594 | self._intervalSend(msg); 2595 | } 2596 | }, this._interval); 2597 | }; 2598 | 2599 | Reliable.prototype._intervalSend = function(msg) { 2600 | var self = this; 2601 | msg = util.pack(msg); 2602 | util.blobToBinaryString(msg, function(str) { 2603 | self._dc.send(str); 2604 | }); 2605 | if (self._queue.length === 0) { 2606 | clearTimeout(self._timeout); 2607 | self._timeout = null; 2608 | //self._processAcks(); 2609 | } 2610 | }; 2611 | 2612 | // Go through ACKs to send missing pieces. 2613 | Reliable.prototype._processAcks = function() { 2614 | for (var id in this._outgoing) { 2615 | if (this._outgoing.hasOwnProperty(id)) { 2616 | this._sendWindowedChunks(id); 2617 | } 2618 | } 2619 | }; 2620 | 2621 | // Handle sending a message. 2622 | // FIXME: Don't wait for interval time for all messages... 2623 | Reliable.prototype._handleSend = function(msg) { 2624 | var push = true; 2625 | for (var i = 0, ii = this._queue.length; i < ii; i += 1) { 2626 | var item = this._queue[i]; 2627 | if (item === msg) { 2628 | push = false; 2629 | } else if (item._multiple && item.indexOf(msg) !== -1) { 2630 | push = false; 2631 | } 2632 | } 2633 | if (push) { 2634 | this._queue.push(msg); 2635 | if (!this._timeout) { 2636 | this._setupInterval(); 2637 | } 2638 | } 2639 | }; 2640 | 2641 | // Set up DataChannel handlers. 2642 | Reliable.prototype._setupDC = function() { 2643 | // Handle various message types. 2644 | var self = this; 2645 | this._dc.onmessage = function(e) { 2646 | var msg = e.data; 2647 | var datatype = msg.constructor; 2648 | // FIXME: msg is String until binary is supported. 2649 | // Once that happens, this will have to be smarter. 2650 | if (datatype === String) { 2651 | var ab = util.binaryStringToArrayBuffer(msg); 2652 | msg = util.unpack(ab); 2653 | self._handleMessage(msg); 2654 | } 2655 | }; 2656 | }; 2657 | 2658 | // Handles an incoming message. 2659 | Reliable.prototype._handleMessage = function(msg) { 2660 | var id = msg[1]; 2661 | var idata = this._incoming[id]; 2662 | var odata = this._outgoing[id]; 2663 | var data; 2664 | switch (msg[0]) { 2665 | // No chunking was done. 2666 | case 'no': 2667 | var message = id; 2668 | if (!!message) { 2669 | this.onmessage(util.unpack(message)); 2670 | } 2671 | break; 2672 | // Reached the end of the message. 2673 | case 'end': 2674 | data = idata; 2675 | 2676 | // In case end comes first. 2677 | this._received[id] = msg[2]; 2678 | 2679 | if (!data) { 2680 | break; 2681 | } 2682 | 2683 | this._ack(id); 2684 | break; 2685 | case 'ack': 2686 | data = odata; 2687 | if (!!data) { 2688 | var ack = msg[2]; 2689 | // Take the larger ACK, for out of order messages. 2690 | data.ack = Math.max(ack, data.ack); 2691 | 2692 | // Clean up when all chunks are ACKed. 2693 | if (data.ack >= data.chunks.length) { 2694 | util.log('Time: ', new Date() - data.timer); 2695 | delete this._outgoing[id]; 2696 | } else { 2697 | this._processAcks(); 2698 | } 2699 | } 2700 | // If !data, just ignore. 2701 | break; 2702 | // Received a chunk of data. 2703 | case 'chunk': 2704 | // Create a new entry if none exists. 2705 | data = idata; 2706 | if (!data) { 2707 | var end = this._received[id]; 2708 | if (end === true) { 2709 | break; 2710 | } 2711 | data = { 2712 | ack: ['ack', id, 0], 2713 | chunks: [] 2714 | }; 2715 | this._incoming[id] = data; 2716 | } 2717 | 2718 | var n = msg[2]; 2719 | var chunk = msg[3]; 2720 | data.chunks[n] = new Uint8Array(chunk); 2721 | 2722 | // If we get the chunk we're looking for, ACK for next missing. 2723 | // Otherwise, ACK the same N again. 2724 | if (n === data.ack[2]) { 2725 | this._calculateNextAck(id); 2726 | } 2727 | this._ack(id); 2728 | break; 2729 | default: 2730 | // Shouldn't happen, but would make sense for message to just go 2731 | // through as is. 2732 | this._handleSend(msg); 2733 | break; 2734 | } 2735 | }; 2736 | 2737 | // Chunks BL into smaller messages. 2738 | Reliable.prototype._chunk = function(bl) { 2739 | var chunks = []; 2740 | var size = bl.size; 2741 | var start = 0; 2742 | while (start < size) { 2743 | var end = Math.min(size, start + this._mtu); 2744 | var b = bl.slice(start, end); 2745 | var chunk = { 2746 | payload: b 2747 | } 2748 | chunks.push(chunk); 2749 | start = end; 2750 | } 2751 | util.log('Created', chunks.length, 'chunks.'); 2752 | return chunks; 2753 | }; 2754 | 2755 | // Sends ACK N, expecting Nth blob chunk for message ID. 2756 | Reliable.prototype._ack = function(id) { 2757 | var ack = this._incoming[id].ack; 2758 | 2759 | // if ack is the end value, then call _complete. 2760 | if (this._received[id] === ack[2]) { 2761 | this._complete(id); 2762 | this._received[id] = true; 2763 | } 2764 | 2765 | this._handleSend(ack); 2766 | }; 2767 | 2768 | // Calculates the next ACK number, given chunks. 2769 | Reliable.prototype._calculateNextAck = function(id) { 2770 | var data = this._incoming[id]; 2771 | var chunks = data.chunks; 2772 | for (var i = 0, ii = chunks.length; i < ii; i += 1) { 2773 | // This chunk is missing!!! Better ACK for it. 2774 | if (chunks[i] === undefined) { 2775 | data.ack[2] = i; 2776 | return; 2777 | } 2778 | } 2779 | data.ack[2] = chunks.length; 2780 | }; 2781 | 2782 | // Sends the next window of chunks. 2783 | Reliable.prototype._sendWindowedChunks = function(id) { 2784 | util.log('sendWindowedChunks for: ', id); 2785 | var data = this._outgoing[id]; 2786 | var ch = data.chunks; 2787 | var chunks = []; 2788 | var limit = Math.min(data.ack + this._window, ch.length); 2789 | for (var i = data.ack; i < limit; i += 1) { 2790 | if (!ch[i].sent || i === data.ack) { 2791 | ch[i].sent = true; 2792 | chunks.push(['chunk', id, i, ch[i].payload]); 2793 | } 2794 | } 2795 | if (data.ack + this._window >= ch.length) { 2796 | chunks.push(['end', id, ch.length]) 2797 | } 2798 | chunks._multiple = true; 2799 | this._handleSend(chunks); 2800 | }; 2801 | 2802 | // Puts together a message from chunks. 2803 | Reliable.prototype._complete = function(id) { 2804 | util.log('Completed called for', id); 2805 | var self = this; 2806 | var chunks = this._incoming[id].chunks; 2807 | var bl = new Blob(chunks); 2808 | util.blobToArrayBuffer(bl, function(ab) { 2809 | self.onmessage(util.unpack(ab)); 2810 | }); 2811 | delete this._incoming[id]; 2812 | }; 2813 | 2814 | // Ups bandwidth limit on SDP. Meant to be called during offer/answer. 2815 | Reliable.higherBandwidthSDP = function(sdp) { 2816 | // AS stands for Application-Specific Maximum. 2817 | // Bandwidth number is in kilobits / sec. 2818 | // See RFC for more info: http://www.ietf.org/rfc/rfc2327.txt 2819 | 2820 | // Chrome 31+ doesn't want us munging the SDP, so we'll let them have their 2821 | // way. 2822 | var version = navigator.appVersion.match(/Chrome\/(.*?) /); 2823 | if (version) { 2824 | version = parseInt(version[1].split('.').shift()); 2825 | if (version < 31) { 2826 | var parts = sdp.split('b=AS:30'); 2827 | var replace = 'b=AS:102400'; // 100 Mbps 2828 | if (parts.length > 1) { 2829 | return parts[0] + replace + parts[1]; 2830 | } 2831 | } 2832 | } 2833 | 2834 | return sdp; 2835 | }; 2836 | 2837 | // Overwritten, typically. 2838 | Reliable.prototype.onmessage = function(msg) {}; 2839 | 2840 | module.exports.Reliable = Reliable; 2841 | 2842 | },{"./util":13}],13:[function(require,module,exports){ 2843 | var BinaryPack = require('js-binarypack'); 2844 | 2845 | var util = { 2846 | debug: false, 2847 | 2848 | inherits: function(ctor, superCtor) { 2849 | ctor.super_ = superCtor; 2850 | ctor.prototype = Object.create(superCtor.prototype, { 2851 | constructor: { 2852 | value: ctor, 2853 | enumerable: false, 2854 | writable: true, 2855 | configurable: true 2856 | } 2857 | }); 2858 | }, 2859 | extend: function(dest, source) { 2860 | for(var key in source) { 2861 | if(source.hasOwnProperty(key)) { 2862 | dest[key] = source[key]; 2863 | } 2864 | } 2865 | return dest; 2866 | }, 2867 | pack: BinaryPack.pack, 2868 | unpack: BinaryPack.unpack, 2869 | 2870 | log: function () { 2871 | if (util.debug) { 2872 | var copy = []; 2873 | for (var i = 0; i < arguments.length; i++) { 2874 | copy[i] = arguments[i]; 2875 | } 2876 | copy.unshift('Reliable: '); 2877 | console.log.apply(console, copy); 2878 | } 2879 | }, 2880 | 2881 | setZeroTimeout: (function(global) { 2882 | var timeouts = []; 2883 | var messageName = 'zero-timeout-message'; 2884 | 2885 | // Like setTimeout, but only takes a function argument. There's 2886 | // no time argument (always zero) and no arguments (you have to 2887 | // use a closure). 2888 | function setZeroTimeoutPostMessage(fn) { 2889 | timeouts.push(fn); 2890 | global.postMessage(messageName, '*'); 2891 | } 2892 | 2893 | function handleMessage(event) { 2894 | if (event.source == global && event.data == messageName) { 2895 | if (event.stopPropagation) { 2896 | event.stopPropagation(); 2897 | } 2898 | if (timeouts.length) { 2899 | timeouts.shift()(); 2900 | } 2901 | } 2902 | } 2903 | if (global.addEventListener) { 2904 | global.addEventListener('message', handleMessage, true); 2905 | } else if (global.attachEvent) { 2906 | global.attachEvent('onmessage', handleMessage); 2907 | } 2908 | return setZeroTimeoutPostMessage; 2909 | }(this)), 2910 | 2911 | blobToArrayBuffer: function(blob, cb){ 2912 | var fr = new FileReader(); 2913 | fr.onload = function(evt) { 2914 | cb(evt.target.result); 2915 | }; 2916 | fr.readAsArrayBuffer(blob); 2917 | }, 2918 | blobToBinaryString: function(blob, cb){ 2919 | var fr = new FileReader(); 2920 | fr.onload = function(evt) { 2921 | cb(evt.target.result); 2922 | }; 2923 | fr.readAsBinaryString(blob); 2924 | }, 2925 | binaryStringToArrayBuffer: function(binary) { 2926 | var byteArray = new Uint8Array(binary.length); 2927 | for (var i = 0; i < binary.length; i++) { 2928 | byteArray[i] = binary.charCodeAt(i) & 0xff; 2929 | } 2930 | return byteArray.buffer; 2931 | }, 2932 | randomToken: function () { 2933 | return Math.random().toString(36).substr(2); 2934 | } 2935 | }; 2936 | 2937 | module.exports = util; 2938 | 2939 | },{"js-binarypack":10}]},{},[3]); --------------------------------------------------------------------------------